{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE FlexibleInstances     #-}

module Rewriting where

import Control.Monad
import Control.Monad.State
import qualified Data.Map as M
import Data.Maybe
import Debug.Trace
import Prelude hiding (zip)

import Base
import HFunctor
import HZip
import LeftRight
import Repr
import TEq
import Rules

data Dyn s = forall ix . Ix s ix => Dyn (s ix) ix
type Subst s = M.Map MVar (Dyn s)

rewriteM :: forall a s . (Rewrite s, Ix s a)
         => Rule s a -> a -> Maybe a
rewriteM (lhs :~> rhs) term = match lhs term >>= return . (\s -> inst s index rhs)

rewrite :: forall a s . (Rewrite s, Ix s a)
        => Rule s a -> a -> a
rewrite f term = maybe term id (rewriteM f term)

-- | Matching a term

match :: (Rewrite s,Monad m,Ix s ix)
      => Scheme s ix -> ix -> m (Subst s)
match pat term = execStateT (matchM index pat (I0 term)) M.empty

matchM :: (Rewrite s,Monad m,Ix s ix)
      => s ix -> Scheme s ix -> I0 ix -> StateT (Subst s) m ()
matchM ix scheme (I0 e)
  = case viewScheme scheme of
      MVar var ->
        do subst <- get
           case M.lookup var subst of
             Nothing     -> put (M.insert var (Dyn ix e) subst)
             Just exTerm -> checkEqual ix e exTerm
      PF r -> combine matchM ix r (from'' e)

checkEqual :: (HZip (PF s),Monad m,Ix s ix,Repr s)
           => s ix -> ix -> Dyn s -> m ()
checkEqual ix e (Dyn ix' e')
  = do
    Refl <- ixeq ix ix'
    geq' ix (I0 e) (I0 e')


class (HFunctor (PF s),HZip (PF s),LR (PF s) s,Repr s) => Rewrite s


-- | Apply a substition to a scheme

apply :: forall s ix . (Repr s,HFunctor (PF s)) =>
         Subst s -> s ix -> Scheme s ix -> Scheme s ix
apply s ix p
  = case viewScheme p of
      MVar x ->
        case M.lookup x s of
          Nothing -> mvar x
          Just (Dyn ix' e) ->
            case ixeq ix ix' of
              Just Refl -> toScheme e
              Nothing -> error "Coerce error in apply"
      PF r -> HIn (R (hmap (apply s) r))

-- | Apply a substitution assuming that the result contains
-- no meta-variables

inst :: forall s ix . (Repr s,HFunctor (PF s),Ix s ix) =>
        Subst s -> s ix -> Scheme s ix -> ix
inst s ix p
  = case viewScheme p of
      MVar x ->
        case M.lookup x s of
          Just (Dyn ix' e)
            -> case ixeq ix ix' of
                 Just Refl -> e
                 Nothing -> error "Coerce error in inst"
      PF r ->
        (to . hmap (\ix -> I0 . inst s ix)) r


-- | Checks left/right property (see van Noort et al.)

prop_LeftRight :: forall a s . (Show a,Rewrite s,Repr s,Ix s a) =>
                  s a -> IO ()
prop_LeftRight witness
  = case hzipM fake (from'' lvalue) (from'' rvalue) of
    Nothing -> putStrLn "Left/Right property satisfied" >> showVals
    Just _  -> putStrLn "Left/Right property not satisfied: values are not different enough" >> showVals
  where
    fake :: forall ix r r' r''. Ix s ix => s ix -> r ix -> r' ix -> Maybe (r'' ix)
    fake = undefined
    lvalue = left witness
    rvalue = right witness
    showVals = do
                 putStrLn (concat ["Left value  :    ",take 100 (show lvalue)])
                 putStrLn (concat ["Right value :    ",take 100 (show rvalue)])


-- The horrible stuff below should be erased when the more general "rule" is fixed
-- Actually "rule" is already fixed, but it only works in GHC HEAD, see ASTExamples.
--     -- Alexey

liftTerm2 :: forall s ix' ix'' ix''' .
             (HZip (PF s),LR (PF s) s,Ix s ix',Ix s ix'',Ix s ix''')
          => MVar -> MVar -> (ix' -> ix'' -> ix''') -> Scheme s ix'''
liftTerm2 name1 name2 f =
   let pat1 = insertMVar name1 ix''' 
                (I0 $ f (left ix') (left ix'')) (I0 $ f (right ix') (left ix''))
       pat2 = insertMVar name2 ix''' 
                (I0 $ f (left ix') (left ix'')) (I0 $ f (left ix') (right ix''))
   in  pat1 `mergeSchemes` pat2
  where
    ix'   = index :: s ix'
    ix''  = index :: s ix''
    ix''' = index :: s ix'''

liftRule2 :: (Ix s t1, Ix s ix'', Ix s ix', LR (PF s) s, HZip (PF s)) =>
             s t1 -> (ix' -> ix'' -> RuleSpec t1) -> Rule s t1
liftRule2 _ f = liftTerm2 0 1 (\x y -> lhsR (f x y)) :~>
                liftTerm2 0 1 (\x y -> rhsR (f x y))

rewrite2 :: (Ix s t1,
             Ix s ix'',
             Ix s ix',
             LR (PF s) s,
             Rewrite s) =>
            s t1 -> (ix' -> ix'' -> RuleSpec t1) -> t1 -> Maybe t1
rewrite2 proxy f = rewriteM (liftRule2 proxy f)


