{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}

import Language.JsonGrammar
import Types

import Prelude hiding (id, (.))
import Control.Category (Category(..))
import Data.Aeson.Types (Value, parseMaybe)
import Data.Char (toLower)
import Data.Monoid ((<>))
import Data.StackPrism (StackPrism)
import Data.StackPrism.TH (deriveStackPrismsWith)
import Language.TypeScript (renderDeclarationSourceFile)
import Test.Framework (Test, defaultMain)
import Test.Framework.Providers.HUnit (testCase)
import Test.HUnit (assertBool)


-- Create stack prisms for our three datatypes (defined in module Types)

deriveStackPrismsWith (map toLower) ''Person
deriveStackPrismsWith (map toLower) ''Coords
deriveStackPrismsWith (map toLower) ''Gender


-- Write Json instances for the datatypes

instance Json Gender where
  grammar = fromPrism male   . literal "male"
         <> fromPrism female . literal "female"

instance Json Person where
  grammar = label "Person" $
    fromPrism person . object
    ( prop "name"
    . prop "gender"
    . (prop "age" <> defaultValue 42)
    . fromPrism coords
    . property "coords" (array (el . el))
    )


-- Create two example values

alice :: Person
alice = Person "Alice" Female 21 (Coords 52 5)

bob :: Person
bob = Person "Bob" Male 22 (Coords 53 6)


-- Two tests: one for lists, the other for tuples

test1, test2 :: Test
test1 = testCase "PersonList"  $ assertBool "" (checkInverse [alice, bob])
test2 = testCase "PersonTuple" $ assertBool "" (checkInverse (alice, bob))

checkInverse :: (Json a, Eq a) => a -> Bool
checkInverse value = value == value'
  where
    Just json   = serialize grammar value
    Just value' = parseMaybe (parse grammar) json


-- Write the TypeScript definition to stdout and run the tests

main :: IO ()
main = do
  printInterfaces [SomeGrammar personGrammar]
  defaultMain [test1, test2]

personGrammar :: Grammar 'Val (Value :- t) (Person :- t)
personGrammar = grammar

printInterfaces :: [SomeGrammar 'Val] -> IO ()
printInterfaces gs = putStrLn (renderDeclarationSourceFile (interfaces gs))
