-- |
-- Copyright   : (c) 2010-2012 Benedikt Schmidt, Simon Meier
-- License     : GPL v3 (see LICENSE)
--
-- Maintainer  : Benedikt Schmidt <beschmi@gmail.com>
--
-- Term Equalities, Matching Problems, and Subterm Rules.
module Term.Rewriting.Definitions (
    -- * Equalities
      Equal (..)
    , evalEqual

    -- * Matching problems
    , Match(..)
    , flattenMatch
    , matchWith
    , matchOnlyIf

    -- * Rewriting rules
    , RRule(..)

    ) where

import Control.Arrow        ( (***) )
import Control.Applicative

import Extension.Data.Monoid
import Data.Foldable
import Data.Traversable

----------------------------------------------------------------------
-- Equalities, matching problems, and rewriting rules
----------------------------------------------------------------------

-- | An equality.
data Equal a = Equal { eqLHS :: a, eqRHS :: a }
    deriving (Eq, Show)

-- | True iff the two sides of the equality are equal with respect to their
-- 'Eq' instance.
evalEqual :: Eq a => Equal a -> Bool
evalEqual (Equal l r) = l == r

instance Functor Equal where
    fmap f (Equal lhs rhs) = Equal (f lhs) (f rhs)

instance Monoid a => Monoid (Equal a) where
    mempty                                = Equal mempty mempty
    (Equal l1 r1) `mappend` (Equal l2 r2) =
        Equal (l1 `mappend` l2) (r1 `mappend` r2)

instance Foldable Equal where
    foldMap f (Equal l r) = f l `mappend` f r

instance Traversable Equal where
    traverse f (Equal l r) = Equal <$> f l <*> f r

instance Applicative Equal where
    pure x                        = Equal x x
    (Equal fl fr) <*> (Equal l r) = Equal (fl l) (fr r)

-- | Matching problems. Use the 'Monoid' instance to compose matching
-- problems.
data Match a =
      NoMatch
      -- ^ No matcher exists.
    | DelayedMatches [(a,a)]
      -- ^ A bunch of delayed (term,pattern) pairs.

instance Eq a => Eq (Match a) where
    x == y = flattenMatch x == flattenMatch y

instance Show a => Show (Match a) where
    show = show . flattenMatch


-- Smart constructors
---------------------

-- | Ensure that matching only succeeds if the condition holds.
matchOnlyIf :: Bool -> Match a
matchOnlyIf False = NoMatch
matchOnlyIf True  = mempty

-- | Match a term with a pattern.
matchWith :: a         -- ^ Term
          -> a         -- ^ Pattern
          -> Match a   -- ^ Matching problem.
matchWith t p = DelayedMatches [(t, p)]

-- Destructors
--------------

-- | Flatten a matching problem to a list of (term,pattern) pairs. If no
-- matcher exists, then 'Nothing' is returned.
flattenMatch :: Match a -> Maybe [(a, a)]
flattenMatch NoMatch             = Nothing
flattenMatch (DelayedMatches ms) = Just ms

-- Instances
------------

instance Functor Match where
    fmap _ NoMatch             = NoMatch
    fmap f (DelayedMatches ms) = DelayedMatches (fmap (f *** f) ms)

instance Monoid (Match a) where
    mempty = DelayedMatches []

    NoMatch            `mappend` _                  = NoMatch
    _                  `mappend` NoMatch            = NoMatch
    DelayedMatches ms1 `mappend` DelayedMatches ms2 =
        DelayedMatches (ms1 `mappend` ms2)


instance Foldable Match where
    foldMap _ NoMatch             = mempty
    foldMap f (DelayedMatches ms) = foldMap (\(t, p) -> f t <> f p) ms

instance Traversable Match where
    traverse _ NoMatch             = pure NoMatch
    traverse f (DelayedMatches ms) =
        DelayedMatches <$> traverse (\(t, p) -> (,) <$> f t <*> f p) ms

{-
instance Applicative Match where
    pure x                                = MatchWith x x
    (MatchWith ft fp) <*> (MatchWith t p) = MatchWith (ft t) (fp p)
-}


-- |  A rewrite rule.
data RRule a = RRule a a
    deriving (Show, Ord, Eq)

instance Functor RRule where
    fmap f (RRule lhs rhs) = RRule (f lhs) (f rhs)

instance Monoid a => Monoid (RRule a) where
    mempty                                = RRule mempty mempty
    (RRule l1 r1) `mappend` (RRule l2 r2) =
        RRule (l1 `mappend` l2) (r1 `mappend` r2)

instance Foldable RRule where
    foldMap f (RRule l r) = f l `mappend` f r

instance Traversable RRule where
    traverse f (RRule l r) = RRule <$> f l <*> f r

instance Applicative RRule where
    pure x                        = RRule x x
    (RRule fl fr) <*> (RRule l r) = RRule (fl l) (fr r)