{-# LANGUAGE TypeFamilies      #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
-- | Implementation of delayed comparison and composition of
--   properties
module Test.QuickCheck.Property.Common.Internal (
    -- * Comparison for equality
    Equal(..)
  , runEqual
  , Equalable(..)
  ) where



-- | Values to be compared for equality
data Equal a = Equal a a
             | NotE (Equal a)
             | AndE (Equal a) (Equal a)
             | OrE  (Equal a) (Equal a)

-- | Evaluate boolean expression inside 'Equal'
runEqual :: (a -> a -> Bool) -> Equal a -> Bool
runEqual f (Equal a b) = f a b
runEqual f (NotE e)    = not $ runEqual f e
runEqual f (AndE e g)  = runEqual f e && runEqual f g
runEqual f (OrE  e g)  = runEqual f e && runEqual f g



-- | Recurse through function to apply comparison to 'Equal'.
class Equalable a where
  -- | Type which should be compared for equality
  type Result a :: *
  -- | Result of comparison. Could be passed to 'quickCheck'
  type Compared a :: *
  -- | Compare value using custom comparison function
  equalWith :: (Result a -> Result a -> Bool) -> a -> Compared a
  -- | Map property 
  mapEqual  :: (Equal (Result a) -> Equal (Result a)) -> a -> a
  -- | Zip properties
  zipEquals :: (Equal (Result a) -> Equal (Result a) -> Equal (Result a)) -> a -> a -> a

instance Equalable (Equal a) where
  type Result   (Equal a) = a
  type Compared (Equal a) = Bool
  equalWith = runEqual
  mapEqual  = id
  zipEquals = id

instance Equalable a => Equalable (x -> a) where
  type Result   (x -> a) = Result a
  type Compared (x -> a) = x -> Compared a
  equalWith f fun = equalWith f . fun
  mapEqual  f fun = mapEqual  f . fun
  zipEquals f fun1 fun2 = \x -> zipEquals f (fun1 x) (fun2 x)