{-# LANGUAGE TemplateHaskell #-}

-- | This file should not be imported directly. Import "Test.OITestGenerator"
--   instead.
module Test.OITestGenerator.Axiom (
    Axiom(),
    axiom, withGens, axiom_name, axiom_gens,
    AxiomResult(),
    ar_cond, ar_lhs, ar_rhs, (=!=), (===>)
) where

import Data.Label
import Language.Haskell.TH
import Prelude
import Test.OITestGenerator.GenHelper
import Test.OITestGenerator.HasGens

newtype AxiomResult a = AxiomResult (Bool, a, a)

infix 1 =!=
-- | Combine the results of the left- and right-hand sides of an axiom.
--   A simple example is
--
--   >q1 :: AxiomResult Bool
--   >q1 = isEmpty empty =!= True
--
--   while an axiom with variables would be written as a function like this:
--
--   >q2 :: Int -> IntQueue -> AxiomResult Bool
--   >q2 x q = isEmpty (enqueue x q) =!= False
(=!=) :: a -> a -> AxiomResult a
lhs =!= rhs = AxiomResult (True, lhs, rhs)

infixr 0 ===>
-- | Adds a condition to an axiom. If the 'Bool' argument is 'False', the axiom
--   won't be evaluated. The test will neither count as a failed test, nor as
--   a successful test. It translates to the QuickCheck operator '==>'.
--
--   Example usage:
--
-- >q4 :: Int -> IntQueue -> AxiomResult Int
-- >q4 x q = not (isEmpty q) ===> front (enqueue x q) =!= front q
(===>) :: Bool -> AxiomResult a -> AxiomResult a
cond ===> AxiomResult (cond', lhs, rhs) = AxiomResult (cond && cond', lhs, rhs)

ar_cond :: AxiomResult a -> Bool
ar_cond (AxiomResult (cond, _, _)) = cond

ar_lhs :: AxiomResult a -> a
ar_lhs (AxiomResult (_, lhs, _)) = lhs

ar_rhs :: AxiomResult a -> a
ar_rhs (AxiomResult (_, _, rhs)) = rhs

data Axiom = Axiom {
    _name :: Name,
    _gens :: Q [Name]
}

mkLabel ''Axiom

-- | 'Axiom' constructor. Takes a 'Language.Haskell.TH.Syntax.Name', i.e.
--
-- >axiom 'q1
--
-- or
--
-- >map axiom ['q1, 'q2, 'q3]
--
-- .
axiom :: Name -> Axiom
axiom name' = Axiom {
    _name = name',
    _gens = return []
}

axiom_name :: Axiom -> Name
axiom_name = get name

axiom_gens :: Axiom -> Q [Name]
axiom_gens = get gens

instance HasGens Axiom where
    withGens axiom' gens' = flip (set gens) axiom' $
                            let qargn = num_args_name nm
                                nm = get name axiom'
                            in qargn >>= \argn ->
                               if argn == length gens'
                               then return gens'
                               else fail $ "Axiom " ++ show nm ++ " has "
                                    ++ show argn ++ " arguments, but "
                                    ++ show (length gens') ++ " Gens are given"