{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

{-|

Propositions and combinators for conveniently constructing them.

-}
module Language.Expression.Prop
  (
    -- * Proposition Types
    Prop
  , Prop'
    -- * DSL
  , expr
  , plit
  , pnot
  , (*&&)
  , (*||)
  , (*->)
  , (*<->)
  , propAnd
  , propOr
    -- * HTraversable
  , LogicOp(..)
  ) where

import           Control.Applicative        (liftA2)
import           Data.List                  (foldl')
-- import           Data.Typeable

-- import           Data.Functor.Classes
import           Data.Functor.Identity

import           Data.SBV

import           Language.Expression
import           Language.Expression.Choice
import           Language.Expression.Pretty
import           Language.Expression.Util

-- | Propositions over general expressions.
type Prop = HFree LogicOp

-- | Propositions over expressions with the given list of operators.
type Prop' ops v = Prop (HFree' ops v)

--------------------------------------------------------------------------------
--  DSL
--------------------------------------------------------------------------------

infixl 3 *&&
infixl 2 *||
infixr 1 *->
infix 1 *<->

-- | Lift an expression into the land of propositions.
expr :: expr a -> Prop expr a
expr = HPure

plit :: Bool -> Prop expr Bool
plit = HWrap . LogLit

pnot :: Prop expr Bool -> Prop expr Bool
pnot = HWrap . LogNot

(*&&) :: Prop expr Bool -> Prop expr Bool -> Prop expr Bool
(*&&) = HWrap ... LogAnd

(*||) :: Prop expr Bool -> Prop expr Bool -> Prop expr Bool
(*||) = HWrap ... LogOr

(*->) :: Prop expr Bool -> Prop expr Bool -> Prop expr Bool
(*->) = HWrap ... LogImpl

(*<->) :: Prop expr Bool -> Prop expr Bool -> Prop expr Bool
(*<->) = HWrap ... LogEquiv

propAnd :: [Prop expr Bool] -> Prop expr Bool
propAnd []       = plit True
propAnd (x : xs) = foldl' (*&&) x xs

propOr :: [Prop expr Bool] -> Prop expr Bool
propOr []       = plit False
propOr (x : xs) = foldl' (*||) x xs


--------------------------------------------------------------------------------
--  The HTraversable
--------------------------------------------------------------------------------

-- | Logical operations
data LogicOp t a where
  LogLit :: Bool -> LogicOp t Bool
  LogNot :: t Bool -> LogicOp t Bool
  LogAnd :: t Bool -> t Bool -> LogicOp t Bool
  LogOr :: t Bool -> t Bool -> LogicOp t Bool
  LogImpl :: t Bool -> t Bool -> LogicOp t Bool
  LogEquiv :: t Bool -> t Bool -> LogicOp t Bool

instance HFunctor LogicOp
instance HTraversable LogicOp where
  htraverse f = \case
    LogLit b -> pure $ LogLit b
    LogNot x -> LogNot <$> f x
    LogAnd x y -> LogAnd <$> f x <*> f y
    LogOr x y -> LogOr <$> f x <*> f y
    LogImpl x y -> LogImpl <$> f x <*> f y
    LogEquiv x y -> LogEquiv <$> f x <*> f y

instance HFoldableAt Identity LogicOp where
  hfoldMap = implHfoldMap $ \case
    LogLit b -> pure b
    LogNot x -> not <$> x
    LogAnd x y -> liftA2 (&&) x y
    LogOr x y -> liftA2 (||) x y
    LogImpl x y -> liftA2 (==>) x y
    LogEquiv x y -> liftA2 (<=>) x y

instance HFoldableAt SBV LogicOp where
  hfoldMap = implHfoldMap $ \case
    LogLit b -> fromBool b
    LogNot x -> bnot x
    LogAnd x y -> x &&& y
    LogOr x y -> x ||| y
    LogImpl x y -> x ==> y
    LogEquiv x y -> x <=> y

-- instance HEq LogicOp where
--   liftHEq _ _ (LogLit x) (LogLit y) = x == y
--   liftHEq le _ (LogNot x) (LogNot y) = le svEq x y
--   liftHEq le _ (LogAnd x1 x2) (LogAnd y1 y2) = le svEq x1 y1 && le svEq x2 y2
--   liftHEq le _ (LogOr x1 x2) (LogOr y1 y2) = le svEq x1 y1 && le svEq x2 y2
--   liftHEq le _ (LogImpl x1 x2) (LogImpl y1 y2) = le svEq x1 y1 && le svEq x2 y2
--   liftHEq le _ (LogEquiv x1 x2) (LogEquiv y1 y2) = le svEq x1 y1 && le svEq x2 y2
--   liftHEq _ _ _ _ = False

-- instance (Eq1 t) => Eq1 (LogicOp t) where liftEq = liftLiftEq

-- instance (Eq a, Eq1 t) => Eq (LogicOp t a) where (==) = eq1

instance Pretty2 LogicOp where
  prettys2Prec p = \case
    LogLit True -> \r -> "T" ++ r
    LogLit False -> \r -> "F" ++ r
    LogNot x -> showParen (p > 8) $ showString "¬ " . prettys1Prec 9 x
    LogAnd x y ->
      showParen (p > 3) $ prettys1Prec 4 x . showString " ∧ " . prettys1Prec 4 y
    LogOr  x y ->
      showParen (p > 2) $ prettys1Prec 3 x . showString " ∨ " . prettys1Prec 3 y
    LogImpl  x y ->
      showParen (p > 1) $ prettys1Prec 2 x . showString " -> " . prettys1Prec 2 y
    LogEquiv  x y ->
      showParen (p > 0) $ prettys1Prec 1 x . showString " <-> " . prettys1Prec 1 y

--------------------------------------------------------------------------------
--  Internal Combinators
--------------------------------------------------------------------------------

-- svEq :: (Typeable a, Typeable b, Eq a) => a -> b -> Bool
-- svEq (x :: a) (y :: b)
--   | Just Refl <- eqT :: Maybe (a :~: b) = x == y
--   | otherwise = False