module Satchmo.Relation.Prop

( implies
, symmetric 
, transitive
, irreflexive
, reflexive
, regular
, regular_in_degree
, regular_out_degree
, max_in_degree
, min_in_degree
, max_out_degree
, min_out_degree
, empty
, complete
, disjoint
, equals
, is_function
, is_partial_function
, is_bijection
, is_permutation
)

where

import Prelude hiding ( and, or, not, product )
import qualified Prelude

import Satchmo.Code
import Satchmo.Boolean hiding (implies, equals)
import Satchmo.Counting
import Satchmo.Relation.Data
import Satchmo.Relation.Op
import qualified Satchmo.Counting as C

import Control.Monad ( guard )
import Data.Ix

import Satchmo.SAT

implies :: ( Ix a, Ix b, MonadSAT m ) 
        => Relation a b -> Relation a b -> m Boolean
{-# specialize inline implies :: ( Ix a, Ix b ) => Relation a b -> Relation a b -> SAT Boolean #-}      
implies r s = monadic and $ do
    i <- indices r
    return $ or [ not $ r ! i, s ! i ]

empty ::  ( Ix a, Ix b, MonadSAT m ) 
        => Relation a b -> m Boolean
empty r = and $ do
    i <- indices r
    return $ not $ r ! i

complete r = empty $ complement r

disjoint r s = do
    i <- intersection r s
    empty i

equals r s = do
    rs <- implies r s
    sr <- implies s r
    and [ rs, sr ]

symmetric :: ( Ix a, MonadSAT m) => Relation a a -> m Boolean
{-# specialize inline symmetric :: ( Ix a ) => Relation a a -> SAT Boolean #-}      
symmetric r = implies r ( mirror r )

irreflexive :: ( Ix a, MonadSAT m) => Relation a a -> m Boolean
{-# specialize inline irreflexive :: ( Ix a ) =>  Relation a a -> SAT Boolean #-}      
irreflexive r = and $ do
    let ((a,b),(c,d)) = bounds r
    x <- range ( a, c)
    return $ Satchmo.Boolean.not $ r ! (x,x) 

reflexive :: ( Ix a, MonadSAT m) => Relation a a -> m Boolean
{-# specialize inline reflexive :: ( Ix a ) => Relation a a -> SAT Boolean #-}      
reflexive r = and $ do
    let ((a,b),(c,d)) = bounds r
    x <- range (a,c)
    return $ r ! (x,x) 

regular, regular_in_degree, regular_out_degree, max_in_degree, min_in_degree, max_out_degree, min_out_degree
  :: ( Ix a, Ix b, MonadSAT m) => Int -> Relation a b -> m Boolean

regular deg r = monadic and [ regular_in_degree deg r, regular_out_degree deg r ]

regular_out_degree = out_degree_helper exactly
max_out_degree = out_degree_helper atmost
min_out_degree = out_degree_helper atleast
regular_in_degree deg r = regular_out_degree deg $ mirror r
max_in_degree deg r = max_out_degree deg $ mirror r
min_in_degree deg r = min_out_degree deg $ mirror r


out_degree_helper f deg r = monadic and $ do
    let ((a,b),(c,d)) = bounds r
    x <- range ( a , c )
    return $ f deg $ do 
        y <- range (b,d)
        return $ r !(x,y)

transitive :: ( Ix a, MonadSAT m ) 
           => Relation a a -> m Boolean
{-# specialize inline transitive :: ( Ix a ) => Relation a a -> SAT Boolean #-}      
transitive r = do
    r2 <- product r r
    implies r2 r

-- | relation R is a function iff for each x,
-- there is exactly one y such that R(x,y)
is_function :: (Ix a, Ix b, MonadSAT m)
         => Relation a b -> m Boolean
is_function r = regular_out_degree 1 r

-- | relation R is a partial function iff for each x,
-- there is at most one y such that R(x,y)
is_partial_function :: (Ix a, Ix b, MonadSAT m)
         => Relation a b -> m Boolean
is_partial_function r = max_out_degree 1 r


is_bijection :: (Ix a, Ix b, MonadSAT m)
         => Relation a b -> m Boolean
is_bijection r = monadic and [ is_function r , is_function (mirror r) ]

is_permutation :: (Ix a, MonadSAT m)
                  => Relation a a -> m Boolean
is_permutation r = is_bijection r