-----------------------------------------------------------------------------
-- Copyright 2019, Advise-Me project team. This file is distributed under 
-- the terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Utilities for the logic domain
--
-----------------------------------------------------------------------------

module Domain.Logic.Utils
   ( propositionalId, makeSimpleRule, makeListRule
     -- groups of rules
   , groupAbsorption, groupAssociativity, groupCommutativity, groupDeMorgan
   , groupDistribution, groupIdempotency
   , groupDoubleNegation, groupEquivalence, groupFalseComplement
   , groupFalseConjunction, groupFalseDisjunction, groupNotTrue
   , groupImplication, groupTrueComplement, groupTrueConjunction
   , groupTrueDisjunction, groupNotFalse
     -- other utility functions
   , makeInvRule, makeInvRuleWithUse
   , collect, andView, orView, eqView
   , somewhereOr, somewhereAnd
   , (===), (==>), (<=>)
   ) where

import Domain.Logic.Formula
import Ideas.Common.Library

propositionalId :: Id
propositionalId = newId "logic.propositional"

makeListRule :: String -> (a -> [a]) -> Rule a
makeListRule s = makeRule (propositionalId # s)

makeSimpleRule :: String -> (a -> Maybe a) -> Rule a
makeSimpleRule s = makeRule (propositionalId # s)

-----------------------------------------------------------------------------
-- Groups of rules

groupAbsorption, groupAssociativity, groupCommutativity, groupDeMorgan,
   groupDistribution, groupIdempotency :: Id
groupAbsorption    = makeGroup "Absorption"
groupAssociativity = makeGroup "Associativity"
groupCommutativity = makeGroup "Commutativity"
groupDeMorgan      = makeGroup "DeMorgan"
groupDistribution  = makeGroup "Distribution"
groupIdempotency   = makeGroup "Idempotency"

groupDoubleNegation, groupEquivalence, groupFalseComplement,
   groupFalseConjunction, groupFalseDisjunction, groupNotTrue,
   groupImplication, groupTrueComplement, groupTrueConjunction,
   groupTrueDisjunction, groupNotFalse :: Id
groupDoubleNegation   = makeGroup "doublenegation"
groupEquivalence      = makeGroup "equivalence"
groupFalseComplement  = makeGroup "falsecomplement"
groupFalseConjunction = makeGroup "falseconjunction"
groupFalseDisjunction = makeGroup "falsedisjunction"
groupNotTrue          = makeGroup "group-nottrue"
groupImplication      = makeGroup "implication"
groupTrueComplement   = makeGroup "truecomplement"
groupTrueConjunction  = makeGroup "trueconjunction"
groupTrueDisjunction  = makeGroup "truedisjunction"
groupNotFalse         = makeGroup "group-notfalse"

makeGroup :: String -> Id
makeGroup s = describe "Group of logic rules" (propositionalId # s)

-----------------------------------------------------------------------------
-- Inverse of a rule

makeInvRule :: IsId n => (a -> a -> Bool) -> n -> Rule a -> Rule a
makeInvRule sim name r =
   addRecognizerBool eq $ setSiblings $ setBuggy (isBuggy r) $ makeRule name (const Nothing)
 where
   eq a b = any (sim a) (applyAll r b)
   setSiblings n = foldr siblingOf n (ruleSiblings r)

makeInvRuleWithUse :: (IsTerm a, IsTerm b, IsId n, Show a)
                => (Context a -> Context a -> Bool) -> n -> Rule b -> Rule (Context a)
makeInvRuleWithUse sim name r =
   addRecognizerBool eq $ setSiblings $ setBuggy (isBuggy r) $ makeRule name (const Nothing)
 where
   eq a b = any (sim a) (applyAll (somewhere (use r)) b)
   setSiblings n = foldr siblingOf n (ruleSiblings r)

collect :: View a (a, a) -> Isomorphism a [a]
collect v = f <-> g
 where
   f x = maybe [x] (\(y, z) -> f y ++ f z) (match v x)
   g   = foldr1 (curry (build v))

andView, orView, eqView :: View (Logic a) (Logic a, Logic a)
andView = makeView isAnd (uncurry (<&&>))
orView  = makeView isOr  (uncurry (<||>))
eqView  = makeView isEq  (uncurry equivalent)
 where
   isEq (p :<->: q) = Just (p, q)
   isEq _           = Nothing

-- A specialized variant of the somewhere traversal combinator. Apply
-- the strategy only at (top-level) disjuncts
somewhereOr :: IsStrategy g => g (Context SLogic) -> Strategy (Context SLogic)
somewhereOr = somewhereWhen $ \a ->
   case currentInContext a of
      Just (_ :||: _) -> False
      _               -> True

somewhereAnd :: IsStrategy g => g (Context SLogic) -> Strategy (Context SLogic)
somewhereAnd = somewhereWhen $ \a ->
   case currentInContext a of
      Just (_ :&&: _) -> False
      _               -> True

infix 6 ===, ==>, <=>

(===), (==>), (<=>) :: Eq a => Logic a -> Logic a -> Bool
(===)   = eqLogic
p ==> q = tautology (p :->: q)
p <=> q = tautology (p :<->: q)