{-# LANGUAGE ExistentialQuantification #-}

module Database.Bolt.Extras.Internal.Condition
  (
    Condition (..)
  , tautology
  , matches
  , itself
  ) where

-- | Conditional expressions over type 'a' and its mappings.
-- Supported operations:
--
-- * equality check ':=='
-- * disunction     ':&&'
-- * conjunction    ':||'
--
-- Typical usage:
--
-- Say we have variable @var :: a@, a function @f :: a -> b@ and a value @val :: b@.
-- Expression @f :== val@ acts as @f var == val@.
--
-- Examples:
--
-- > data D = D { fld1 :: Int
-- >            , fld2 :: String
-- >            , fld3 :: Double
-- >            }
-- >
-- > d = D 42 "noononno" 1.618
-- > d `matches` (fld1 :== 12 :&& fld2 :== "abc")
-- > False
-- >
-- > d `matches` (fld1 :== 42 :|| fld3 == 1.0)
-- > True
--
infix  4 :==
infixr 3 :&&
infixr 2 :||
data Condition a = forall b. Eq b => (a -> b) :== b
                 | Condition a :&& Condition a
                 | Condition a :|| Condition a


-- | Check whether data satisfies conditions on it.
--
matches :: a -> Condition a -> Bool
matches :: a -> Condition a -> Bool
matches a
obj (a -> b
transform :== b
ref) = a -> b
transform a
obj b -> b -> Bool
forall a. Eq a => a -> a -> Bool
== b
ref
matches a
obj (Condition a
u :&& Condition a
v)           = a -> Condition a -> Bool
forall a. a -> Condition a -> Bool
matches a
obj Condition a
u Bool -> Bool -> Bool
&& a -> Condition a -> Bool
forall a. a -> Condition a -> Bool
matches a
obj Condition a
v
matches a
obj (Condition a
u :|| Condition a
v)           = a -> Condition a -> Bool
forall a. a -> Condition a -> Bool
matches a
obj Condition a
u Bool -> Bool -> Bool
|| a -> Condition a -> Bool
forall a. a -> Condition a -> Bool
matches a
obj Condition a
v


-- | Matching 'tautology' will always succeed.
--
-- > whatever `matches` tautology == True
--
-- Match is lazy:
--
-- > undefined `matches` tautology == True
--
tautology :: Condition a
tautology :: Condition a
tautology = Bool -> a -> Bool
forall a b. a -> b -> a
const Bool
True (a -> Bool) -> Bool -> Condition a
forall a b. Eq b => (a -> b) -> b -> Condition a
:== Bool
True


-- | Object itself instead of its mappings is matched with help of this alias.
--
-- > 42 `matches` (itself :== 42) == True
-- > 42 `matches` (itself :== 41) == False
--
itself :: a -> a
itself :: a -> a
itself = a -> a
forall a. a -> a
id