{-# LANGUAGE TemplateHaskell #-}
module Main where

import Data.Generics.Is
import Language.Haskell.TH
import Control.Applicative
import Control.Monad
import System.Exit

assertEq :: String -> Bool -> Bool -> IO ()                                    
assertEq _ a b | a == b = return ()                                               
assertEq a_str a b = do                                                               
  putStrLn $ a_str ++ " /=\n" ++ show b                                        
  exitFailure                                                                   

main :: IO ()
main = $(
  let
    -- Test cases: pattern, expression, pattern matches expression
    testCasesPat :: [(Q Pat, Q Exp, Bool)]
    testCasesPat = 
        [ ([p| Just _ |]  , [e| Just "a" |] , True)
        , ([p| [_,_,_] |] , [e| [1,2,3] |] , True)
        , ([p| [_,_] |] , [e| [1,2,3] |] , False)
        , ([p| Left{} |] , [e| Left 3 |] , True)
        ]

    -- Test cases: pattern, expression, constructor matches expression
    testCasesName :: [(Name, Q Exp, Bool)]
    testCasesName =
        [ ('Just , [e| Just 'b' |] , True)
        , ('(:) , [e| [] |] , False)
        , ('[]  , [e| [] |] , True)
        , ('(:) , [e| [1,2,3] |] , True)
        , ('(,,,) , [e| (1,2,3,4) |] , True)
        ]
    
    -- Utility functions
    assertEqTH :: Bool -> Exp -> Exp -> Exp -> Exp
    assertEqTH b e_yes e_no e = 
      assertEqExpr (AppE e_yes e) b `andThen` assertEqExpr (AppE e_no e) (not b)

    boolExp :: Bool -> Exp
    boolExp b = if b then ConE 'True else ConE 'False

    andThen :: Exp -> Exp -> Exp
    x `andThen` y = AppE (AppE (VarE '(>>)) x) y

    assertEqExpr :: Exp -> Bool -> Exp
    assertEqExpr e b = AppE (AppE (AppE (VarE 'assertEq) (LitE (StringL (pprint e)))) e) (boolExp b)
  in do
    es1 <- forM testCasesPat $ \(p, e, b) ->
      assertEqTH b <$> isP p <*> isNotP p <*> e

    es2 <- forM testCasesName $ \(n, e, b) ->
      assertEqTH b <$> is n <*> isNot n <*> e

    return $ foldl1 andThen $ es1 ++ es2

  )
