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

module Rules where

import Prelude hiding (zip)

import Base
import HFunctor
import HZip
import LeftRight

-- * Rule types

infix 5 :~>
data RuleSpec a = a :~> a

lhsR :: RuleSpec a -> a
lhsR (lhs :~> rhs) = lhs

rhsR :: RuleSpec a -> a
rhsR (lhs :~> rhs) = rhs

type Rule   s a = RuleSpec (Scheme s a)

-- * Schemes

type MVar = Int
type Scheme s = HFix ((K MVar :+: PF s) s)

mvar :: MVar -> Scheme s ix
mvar = HIn . L . K

pf :: (PF s) s (Scheme s) ix -> Scheme s ix
pf = HIn . R

toScheme :: (HFunctor (PF s), Ix s ix) => ix -> Scheme s ix
toScheme = pf . hmap (\ _ -> toScheme . unI0) . from

data ViewScheme s ix = MVar MVar | PF (PF s s (Scheme s) ix)

viewScheme :: Scheme s ix -> ViewScheme s ix
viewScheme (HIn (L (K x))) = MVar x
viewScheme (HIn (R r))     = PF r

foldScheme :: (HFunctor (PF s), Ix s ix)
           => (forall ix. MVar -> r ix)
           -> (forall ix. Ix s ix => PF s s r ix -> r ix)
           -> Scheme s ix -> r ix
foldScheme f g scheme =
  case viewScheme scheme of
    MVar x -> f x
    PF r   -> g (hmap (\ _ -> foldScheme f g) r)

-- * Making a rule

class Builder a s where
  type Target a :: *
  base          :: s (Target a) -> a -> RuleSpec (Target a)
  diag          :: s (Target a) -> a -> [RuleSpec (Target a)]

instance Builder (RuleSpec a) s where
  type Target (RuleSpec a) = a
  base _ x                 = x
  diag _ x                 = [x]

instance (Builder a s, LR (PF s) s, Ix s b) => Builder (b -> a) s where
  type Target (b -> a) = Target a
  base ix f            = base ix (f (left  (index::s b)))
  diag ix f            = base ix (f (right (index::s b))) :
                         diag ix (f (left  (index::s b)))

rule :: (Builder r s, HZip (PF s),Ix s (Target r)) =>
        s (Target r) -> r -> Rule s (Target r)
rule ix f = foldr1 mergeRules rules
  where
    mergeRules x y = 
      mergeSchemes (lhsR x) (lhsR y) :~>
      mergeSchemes (rhsR x) (rhsR y)
    rules          = zipWith (ins (base ix f)) (diag ix f) [0..]   
    ins x y v      = 
      insertMVar v ix (I0 (lhsR x)) (I0 (lhsR y)) :~>
      insertMVar v ix (I0 (rhsR x)) (I0 (rhsR y))

-- | Merge two schemes by accepting a
-- meta-variable case in one of the 
-- schemes, and otherwise zip the 
-- remaining parts.

mergeSchemes :: HZip (PF s) => Scheme s ix -> Scheme s ix -> Scheme s ix
mergeSchemes p@(HIn x) q@(HIn y)
  = case viewScheme p of
      MVar i -> p
      _     -> case viewScheme q of
                 MVar j -> q
                 _     -> HIn (hzip' mergeSchemes x y)

-- | Extend a value with a meta-variable 
-- case. Two structures are traversed 
-- in parallel, whenever the structures 
-- mismatch, insert a meta-variable.
-- Otherwise, lift the matching parts.

insertMVar :: (HZip (PF s),Ix s ix) => MVar -> s ix -> I0 ix -> I0 ix -> Scheme s ix
insertMVar name ix (I0 x) (I0 y)
  = case hzip (insertMVar name) (from'' x) (from'' y) of
      Just struc -> pf struc
      Nothing    -> mvar name






