-- |This module 'reduceM's something to the conjunction of simpler, 
-- logically equivalent things, or falsity.
--
-- It implements simple /iff/ transformations, some of which are given below.
-- These should be read from left to right, and uses lists to represent conjunction;
-- so [] is logical truth.
--
-- Extensionality:
-- @
--    f = g <==> [f x = g x]  (for some new x)
-- @
--
-- Constructor factoring:
-- @
--    K x1 y1 = K x2 y2 <==> [x1 = x2, y1 = y2]
--    where K is a constructor
-- @
--
-- Constructor inequality:
-- @
--    K ... = J ... <==> _|_
--    where K and J are both constructors
-- @
--
-- Error equality:
-- @
--    _|_ = _|_ <==> []
-- @
--
-- Error-constructor inequality:
-- @
--    _|_ = K ... <==> _|_
--    where K is a constructor
-- @
--
-- Antecedent contradiction
-- @
--    (_|_ ==> A) <==> []
-- @
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
module Zeno.Reduction (
  reduceM, reduce, Reduction (..)
) where

import Prelude ()
import Zeno.Prelude
import Zeno.Core
import Zeno.Evaluation

-- |'Reduction a' is the logical reduction of a value of type 'a'.
-- It is either false ('ReducedToFalse')
-- or the conjunction of a new set of values ('ReducedTo').
-- Truth is represented by 'ReducedTo []', as this is an empty conjunction.
data Reduction a = ReducedToFalse | ReducedTo [a]
  deriving ( Eq, Functor, Foldable, Traversable )

instance Show a => Show (Reduction a) where
  show ReducedToFalse = "_|_"
  show (ReducedTo xs) = "[" ++ intercalate "; " (map show xs) ++ "]"

-- |The Reducible class specifies types which can be logically reduceMd.
-- It possibly creates new variables and takes a list of background facts.
-- This reduction will always be iff.
class Reducible a where
  reduceM :: (IdCounter r, MonadReader [ZEquality] m, MonadState r m) =>
    a -> m (Reduction a)

reduce :: (Reducible a, IdCounter r, Monad m) =>
  [ZEquality] -> a -> StateT r m (Reduction a)
reduce facts = flip runReaderT facts . reduceM

-- |This is the 'Monoid' of reductions under logical conjunction.
-- Think of 'mappend' as '(&&)' and 'mempty' as 'True'.
instance Monoid (Reduction a) where
  mempty = ReducedTo []

  mappend _ ReducedToFalse = ReducedToFalse
  mappend ReducedToFalse _ = ReducedToFalse
  mappend (ReducedTo xs) (ReducedTo ys) = ReducedTo (xs ++ ys)

instance Reducible ZQuantified where
  reduceM (Quantified vars cls) = do
    reduceMd_cls <- reduceM cls
    return (Quantified vars <$> reduceMd_cls)

instance Reducible ZHypothesis where
  reduceM (Hypothesis id binds cls) = do
    reduceMd_cls <- reduceM cls
    return (Hypothesis id binds <$> reduceMd_cls)

instance Reducible ZClause where
  reduceM cls = do
    Clause goal conds <- mapExprM evaluateR cls
    reduceMd_goal <- reduceM goal
    reduceMd_conds <- mapM reduceM conds
    return $ case mconcat reduceMd_conds of
      ReducedToFalse -> mempty
      ReducedTo new_conds -> case reduceMd_goal of
        ReducedToFalse ->
          if null new_conds
            then ReducedToFalse
            else ReducedTo [Clause goal new_conds]
        ReducedTo new_goals ->
          ReducedTo $ map (\g -> Clause g new_conds) new_goals

instance Reducible ZEquality where
  reduceM (Err :=: Err) =
    return (ReducedTo [])
  reduceM eq@(Err :=: x2) =
    if isConstructorTerm x2
      then return ReducedToFalse
      else reduceMFail eq
  reduceM eq@(x1 :=: Err) =
    if isConstructorTerm x1
      then return ReducedToFalse
      else reduceMFail eq
  reduceM (Var v1 :=: Var v2)
    | v1 == v2 = return (ReducedTo [])
    | both_cons && v1 /= v2 = return ReducedToFalse
    where both_cons = isConstructorVar v1 && isConstructorVar v2
  reduceM eq@(x1 :=: x2)
    | isConstructorTerm x1 && isConstructorTerm x2 =
      if fun1 /= fun2
        then return ReducedToFalse
        else assert (length args1 == length args2)
           $ liftM mconcat
           $ zipWithM reduceMSubEq args1 args2
    where
    Var fun1 : args1 = flattenApp x1
    Var fun2 : args2 = flattenApp x2
    reduceMSubEq x1 x2 = do
      x1' <- evaluateR x1
      x2' <- evaluateR x2
      reduceM (x1' :=: x2')
  reduceM eq@(App f1 a1 :=: App f2 a2) = do
    eqF <- reduceM (f1 :=: f2)
    eqA <- reduceM (a1 :=: a2)
    case (eqF, eqA) of
      (ReducedToFalse, _) -> error "Impossible"
      (ReducedTo [], ReducedTo []) -> return (ReducedTo [])
      _ -> reduceMFail eq
  reduceM (Lam v1 x1 :=: Lam v2 x2) = do
    new_id <- newIdS
    let new_var = Var $ ZVar new_id Nothing (getType v1) defaultVarClass
    x1' <- evaluateR (App x1 new_var)
    x2' <- evaluateR (App x2 new_var)
    reduceM (x1' :=: x2')
  reduceM eq = reduceMFail eq

reduceMFail :: (IdCounter c, MonadReader [ZEquality] m, MonadState c m) =>
  ZEquality -> m (Reduction ZEquality)
reduceMFail eq@(x1 :=: x2) = do
  x1' <- evaluateR x1
  x2' <- evaluateR x2
  if x1' /= x1 || x2' /= x2
    then reduceM (x1' :=: x2')
    else return (ReducedTo [eq])
