module Satchmo.Boolean.Op

( constant
, and, or, xor, equals2, equals, implies, (||), (&&)
, fun2, fun3
, ifThenElse, ifThenElseM
, assert_fun2, assert_fun3
, monadic
)

where

import Prelude hiding ( and, or, not, (&&), (||) )
import qualified Prelude
import Control.Applicative ((<$>))
import Satchmo.MonadSAT
import Satchmo.Code
import Satchmo.Boolean.Data

-- import Satchmo.SAT ( SAT) -- for specializations

import Control.Monad ( foldM, when )

and :: MonadSAT m => [ Boolean ] -> m Boolean

and [] = constant True
and [x]= return x
and xs = do
    y <- boolean
    sequence_ $ do
        x <- xs
        return $ assertOr [ not y, x ]
    assertOr $ y : map not xs
    return y

or :: MonadSAT m => [ Boolean ] -> m Boolean
or [] = constant False
or [x]= return x
or xs = do
    y <- and $ map not xs
    return $ not y

x && y = and [x,y]
x || y = or [x,y]

xor :: MonadSAT m => [ Boolean ] -> m Boolean
xor [] = constant False
xor (x:xs) = foldM xor2 x xs

equals :: MonadSAT m => [ Boolean ] -> m Boolean
equals [] = constant True
equals [x] = constant True
equals (x:xs) = foldM equals2 x xs

equals2 :: MonadSAT m => Boolean -> Boolean -> m Boolean
equals2 a b = not <$> xor2 a b

implies :: MonadSAT m => Boolean -> Boolean -> m Boolean
implies a b = or [not a, b]

ifThenElse :: MonadSAT m => Boolean -> m Boolean -> m Boolean -> m Boolean
ifThenElse condition ifTrue ifFalse = do
  trueBranch <- ifTrue
  falseBranch <- ifFalse
  monadic and [ condition `implies` trueBranch
              , not condition `implies` falseBranch ]

ifThenElseM :: MonadSAT m => m Boolean -> m Boolean -> m Boolean -> m Boolean
ifThenElseM conditionM ifTrue ifFalse = do
  c <- conditionM
  ifThenElse c ifTrue ifFalse

-- | implement the function by giving a full CNF
-- that determines the outcome
fun2 :: MonadSAT m => 
        ( Bool -> Bool -> Bool )
     -> Boolean -> Boolean 
     -> m Boolean
fun2 f x y = do
    r <- boolean
    sequence_ $ do
        a <- [ False, True ]
        b <- [ False, True ]
        let pack flag var = if flag then not var else var
        return $ assertOr
            [ pack a x, pack b y, pack (Prelude.not $ f a b) r ]
    return r

assert_fun2 :: MonadSAT m => 
        ( Bool -> Bool -> Bool )
     -> Boolean -> Boolean 
     -> m ()
assert_fun2 f x y = sequence_ $ do
        a <- [ False, True ]
        b <- [ False, True ]
        let pack flag var = if flag then not var else var
        return $ when ( Prelude.not $ f a b ) $ assert 
            [ pack a x, pack b y ]
     

-- | implement the function by giving a full CNF
-- that determines the outcome
fun3 :: MonadSAT m => 
        ( Bool -> Bool -> Bool -> Bool )
     -> Boolean -> Boolean -> Boolean
     -> m Boolean
fun3 f x y z = do
    r <- boolean
    sequence_ $ do
        a <- [ False, True ]
        b <- [ False, True ]
        c <- [ False, True ]
        let pack flag var = if flag then not var else var
        return $ assertOr
            [ pack a x, pack b y, pack c z
            , pack (Prelude.not $ f a b c) r 
            ]
    return r

assert_fun3 :: MonadSAT m => 
        ( Bool -> Bool -> Bool -> Bool )
     -> Boolean -> Boolean -> Boolean
     -> m ()
assert_fun3 f x y z = sequence_ $ do
        a <- [ False, True ]
        b <- [ False, True ]
        c <- [ False, True ]
        let pack flag var = if flag then not var else var
        return $ when ( Prelude.not $ f a b c ) $ assert 
            [ pack a x, pack b y, pack c z ]
     

xor2 :: MonadSAT m => Boolean -> Boolean -> m Boolean
xor2 = fun2 (/=)
-- xor2 = xor2_orig

-- for historic reasons:
xor2_orig :: MonadSAT m => Boolean -> Boolean -> m Boolean
xor2_orig x y = do
    a <- and [ x, not y ]
    b <- and [ not x, y ]
    or [ a, b ]