{-# LANGUAGE CPP #-}
module Agda.TypeChecking.Substitute where

import Control.Monad.Identity
import Control.Monad.Reader
import Data.Generics
import Data.List hiding (sort)
import Data.Map (Map)
import qualified Data.Map as Map

import Agda.Syntax.Common
import Agda.Syntax.Internal

import Agda.TypeChecking.Monad.Base
import Agda.TypeChecking.Free

import Agda.Utils.Monad
import Agda.Utils.Size
import Agda.Utils.Permutation

#include "../undefined.h"
import Agda.Utils.Impossible

-- | Apply something to a bunch of arguments.
--   Preserves blocking tags (application can never resolve blocking).
class Apply t where
    apply :: t -> Args -> t

instance Apply Term where
    apply m [] = m
    apply m args@(Arg _ v:args0) =
	case m of
	    Var i args'   -> Var i (args' ++ args)
	    Def c args'   -> Def c (args' ++ args)
	    Con c args'   -> Con c (args' ++ args)
	    Lam _ u	  -> absApp u v `apply` args0
	    MetaV x args' -> MetaV x (args' ++ args)
	    Lit l	  -> __IMPOSSIBLE__
	    Pi _ _	  -> __IMPOSSIBLE__
	    Fun _ _	  -> __IMPOSSIBLE__
	    Sort _	  -> __IMPOSSIBLE__

instance Apply Type where
  apply = piApply

instance Apply Sort where
  apply s [] = s
  apply s _  = __IMPOSSIBLE__

instance Apply Telescope where
  apply tel		  []	   = tel
  apply EmptyTel	  _	   = __IMPOSSIBLE__
  apply (ExtendTel _ tel) (t : ts) = absApp tel (unArg t) `apply` ts

instance Apply Definition where
    apply (Defn x t df m d) args = Defn x (piApply t args) df m (apply d args)

instance Apply Defn where
  apply d args = case d of
    Axiom{} -> d
    Function{ funClauses = cs, funInv = inv } ->
      d { funClauses = apply cs args, funInv = apply inv args }
    Datatype{ dataPars = np, dataClause = cl } ->
      d { dataPars = np - size args, dataClause = apply cl args }
    Record{ recPars = np, recClause = cl, recTel = tel } ->
      d { recPars = np - size args, recClause = apply cl args, recTel = apply tel args }
    Constructor{ conPars = np } ->
      d { conPars = np - size args }
    Primitive{ primClauses = cs } ->
      d { primClauses = apply cs args }

instance Apply PrimFun where
    apply (PrimFun x ar def) args   = PrimFun x (ar - size args) $ \vs -> def (args ++ vs)

instance Apply Clause where
    apply (Clause r tel perm ps b) args =
      Clause r (apply tel args) (apply perm args)
             (drop (size args) ps) (apply b args)

instance Apply FunctionInverse where
  apply NotInjective  args = NotInjective
  apply (Inverse inv) args = Inverse $ apply inv args

instance Apply ClauseBody where
    apply  b		   []		  = b
    apply (Bind (Abs _ b)) (Arg _ v:args) = subst v b `apply` args
    apply (NoBind b)	   (_:args)	  = b `apply` args
    apply (Body _)	   (_:_)	  = __IMPOSSIBLE__
    apply  NoBody	    _		  = NoBody

instance Apply DisplayTerm where
  apply (DTerm v)	   args = DTerm $ apply v args
  apply (DWithApp v args') args = DWithApp v $ args' ++ args

instance Apply t => Apply [t] where
    apply ts args = map (`apply` args) ts

instance Apply t => Apply (Blocked t) where
    apply b args = fmap (`apply` args) b

instance Apply t => Apply (Maybe t) where
  apply x args = fmap (`apply` args) x

instance Apply v => Apply (Map k v) where
  apply x args = fmap (`apply` args) x

instance (Apply a, Apply b) => Apply (a,b) where
    apply (x,y) args = (apply x args, apply y args)

instance (Apply a, Apply b, Apply c) => Apply (a,b,c) where
    apply (x,y,z) args = (apply x args, apply y args, apply z args)

instance Apply Permutation where
  -- The permutation must start with [0..m - 1]
  apply (Perm n xs) args = Perm (n - m) $ map (flip (-) m) $ genericDrop m xs
    where
      m = size args

instance Abstract Permutation where
  abstract tel (Perm n xs) = Perm (n + m) $ [0..m - 1] ++ map (+ m) xs
    where
      m = size tel

-- | The type must contain the right number of pis without have to perform any
-- reduction.
piApply :: Type -> Args -> Type
piApply t []				= t
piApply (El _ (Pi  _ b)) (Arg _ v:args) = absApp b v `piApply` args
piApply (El _ (Fun _ b)) (_:args)	= b `piApply` args
piApply _ _				= __IMPOSSIBLE__

-- | @(abstract args v) args --> v[args]@.
class Abstract t where
    abstract :: Telescope -> t -> t

instance Abstract Term where
    abstract = teleLam

instance Abstract Type where
    abstract = telePi_

instance Abstract Sort where
    abstract EmptyTel s = s
    abstract _	      s = __IMPOSSIBLE__

instance Abstract Telescope where
  abstract  EmptyTel	        tel = tel
  abstract (ExtendTel arg tel') tel = ExtendTel arg $ fmap (`abstract` tel) tel'

instance Abstract Definition where
    abstract tel (Defn x t df m d) = Defn x (abstract tel t) df m (abstract tel d)

instance Abstract Defn where
  abstract tel d = case d of
    Axiom{} -> d
    Function{ funClauses = cs, funInv = inv } ->
      d { funClauses = abstract tel cs, funInv = abstract tel inv }
    Datatype{ dataPars = np, dataClause = cl } ->
      d { dataPars = np + size tel, dataClause = abstract tel cl }
    Record{ recPars = np, recClause = cl, recTel = tel' } ->
      d { recPars = np + size tel, recClause = abstract tel cl, recTel = abstract tel tel' }
    Constructor{ conPars = np } ->
      d { conPars = np + size tel }
    Primitive{ primClauses = cs } ->
      d { primClauses = abstract tel cs }

instance Abstract PrimFun where
    abstract tel (PrimFun x ar def) = PrimFun x (ar + n) $ \ts -> def $ genericDrop n ts
	where n = size tel

instance Abstract Clause where
  abstract tel (Clause r tel' perm ps b) =
    Clause r (abstract tel tel') (abstract tel perm)
           (telVars tel ++ ps) (abstract tel b)

telVars EmptyTel		    = []
telVars (ExtendTel arg (Abs x tel)) = fmap (const $ VarP x) arg : telVars tel

instance Abstract FunctionInverse where
  abstract tel NotInjective  = NotInjective
  abstract tel (Inverse inv) = Inverse $ abstract tel inv

instance Abstract ClauseBody where
    abstract EmptyTel		 b = b
    abstract (ExtendTel _ tel)	 b = Bind $ fmap (`abstract` b) tel

instance Abstract t => Abstract [t] where
    abstract tel = map (abstract tel)

instance Abstract t => Abstract (Maybe t) where
  abstract tel x = fmap (abstract tel) x

instance Abstract v => Abstract (Map k v) where
  abstract tel m = fmap (abstract tel) m

abstractArgs :: Abstract a => Args -> a -> a
abstractArgs args x = abstract tel x
    where
	tel   = foldr (\(Arg h x) -> ExtendTel (Arg h $ sort Prop) . Abs x) EmptyTel
	      $ zipWith (fmap . const) names args
	names = cycle $ map (:[]) ['a'..'z']

-- | Substitute a term for the nth free variable.
--
class Subst t where
    substs     :: [Term] -> t -> t
    substUnder :: Nat -> Term -> t -> t

idSub :: Telescope -> [Term]
idSub tel = [ Var i [] | i <- [0..size tel - 1] ]

subst :: Subst t => Term -> t -> t
subst u t = substUnder 0 u t

instance Subst Term where
    substs us t =
	case t of
	    Var i vs   -> (us !!! i) `apply` substs us vs
	    Lam h m    -> Lam h $ substs us m
	    Def c vs   -> Def c $ substs us vs
	    Con c vs   -> Con c $ substs us vs
	    MetaV x vs -> MetaV x $ substs us vs
	    Lit l      -> Lit l
	    Pi a b     -> uncurry Pi $ substs us (a,b)
	    Fun a b    -> uncurry Fun $ substs us (a,b)
	    Sort s     -> Sort s
        where
            []     !!! n = error "unbound variable"
            (x:xs) !!! 0 = x
            (_:xs) !!! n = xs !!! (n - 1)
    substUnder n u t =
	case t of
	    Var i vs
	      | i == n	  -> raise n u `apply` substUnder n u vs
	      | i < n	  -> Var i $ substUnder n u vs
	      | otherwise -> Var (i - 1) $ substUnder n u vs
	    Lam h m    -> Lam h $ substUnder n u m
	    Def c vs   -> Def c $ substUnder n u vs
	    Con c vs   -> Con c $ substUnder n u vs
	    MetaV x vs -> MetaV x $ substUnder n u vs
	    Lit l      -> Lit l
	    Pi a b     -> uncurry Pi $ substUnder n u (a,b)
	    Fun a b    -> uncurry Fun $ substUnder n u (a,b)
	    Sort s     -> Sort s

instance Subst Type where
    substs us (El s t) = El s $ substs us t
    substUnder n u (El s t) = El s $ substUnder n u t

instance Subst Pattern where
  substs us p = case p of
    VarP s    -> VarP s
    LitP l    -> LitP l
    ConP c ps -> ConP c $ substs us ps
    DotP t    -> DotP $ substs us t
  substUnder n u p = case p of
    VarP s    -> VarP s
    LitP l    -> LitP l
    ConP c ps -> ConP c $ substUnder n u ps
    DotP t    -> DotP $ substUnder n u t

instance Subst t => Subst (Blocked t) where
    substs us b	     = fmap (substs us) b
    substUnder n u b = fmap (substUnder n u) b

instance Subst DisplayTerm where
  substs us	 (DTerm v)	  = DTerm $ substs us v
  substs us	 (DWithApp vs ws) = uncurry DWithApp $ substs us (vs, ws)
  substUnder n u (DTerm v)	  = DTerm $ substUnder n u v
  substUnder n u (DWithApp vs ws) = uncurry DWithApp $ substUnder n u (vs, ws)

instance Subst Telescope where
  substs us  EmptyTel		   = EmptyTel
  substs us (ExtendTel t tel)	   = uncurry ExtendTel $ substs us (t, tel)
  substUnder n u  EmptyTel	   = EmptyTel
  substUnder n u (ExtendTel t tel) = uncurry ExtendTel $ substUnder n u (t, tel)

instance (Data a, Subst a) => Subst (Abs a) where
    substs us	   (Abs x t) = Abs x $ substs (Var 0 [] : raise 1 us) t
    substUnder n u (Abs x t) = Abs x $ substUnder (n + 1) u t

instance Subst a => Subst (Arg a) where
    substs us	   = fmap (substs us)
    substUnder n u = fmap (substUnder n u)

instance Subst a => Subst (Maybe a) where
  substs us	 = fmap (substs us)
  substUnder n u = fmap (substUnder n u)

instance Subst a => Subst [a] where
    substs us	   = map (substs us)
    substUnder n u = map (substUnder n u)

instance (Subst a, Subst b) => Subst (a,b) where
    substs us (x,y)	 = (substs us x, substs us y)
    substUnder n u (x,y) = (substUnder n u x, substUnder n u y)

instance Subst ClauseBody where
    substs us (Body t)	      = Body $ substs us t
    substs us (Bind b)	      = Bind $ substs us b
    substs us (NoBind b)      = NoBind $ substs us b
    substs _   NoBody	      = NoBody
    substUnder n u (Body t)   = Body $ substUnder n u t
    substUnder n u (Bind b)   = Bind $ substUnder n u b
    substUnder n u (NoBind b) = NoBind $ substUnder n u b
    substUnder _ _   NoBody   = NoBody

-- | Instantiate an abstraction
absApp :: Subst t => Abs t -> Term -> t
absApp (Abs _ v) u = subst u v

-- | Add @k@ to index of each open variable in @x@.
class Raise t where
    raiseFrom :: Nat -> Nat -> t -> t

instance Raise Term where
    raiseFrom m k v =
	case v of
	    Var i vs
		| i < m	    -> Var i $ rf vs
		| otherwise -> Var (i + k) $ rf vs
	    Lam h m	    -> Lam h $ rf m
	    Def c vs	    -> Def c $ rf vs
	    Con c vs	    -> Con c $ rf vs
	    MetaV x vs	    -> MetaV x $ rf vs
	    Lit l	    -> Lit l
	    Pi a b	    -> uncurry Pi $ rf (a,b)
	    Fun a b	    -> uncurry Fun $ rf (a,b)
	    Sort s	    -> Sort s
	where
	    rf x = raiseFrom m k x

instance Raise Type where
    raiseFrom m k (El s t) = El s $ raiseFrom m k t

instance Raise Telescope where
    raiseFrom m k EmptyTel	    = EmptyTel
    raiseFrom m k (ExtendTel a tel) = uncurry ExtendTel $ raiseFrom m k (a, tel)

instance Raise DisplayForm where
  raiseFrom m k (Display n ps v) = Display n (raiseFrom (m + 1) k ps)
					     (raiseFrom (m + n) k v)

instance Raise DisplayTerm where
  raiseFrom m k (DWithApp xs ys) = uncurry DWithApp $ raiseFrom m k (xs, ys)
  raiseFrom m k (DTerm v)	 = DTerm $ raiseFrom m k v

instance Raise t => Raise (Abs t) where
    raiseFrom m k = fmap (raiseFrom (m + 1) k)

instance Raise t => Raise (Arg t) where
    raiseFrom m k = fmap (raiseFrom m k)

instance Raise t => Raise (Blocked t) where
    raiseFrom m k = fmap (raiseFrom m k)

instance Raise t => Raise [t] where
    raiseFrom m k = fmap (raiseFrom m k)

instance Raise t => Raise (Maybe t) where
    raiseFrom m k = fmap (raiseFrom m k)

instance Raise v => Raise (Map k v) where
    raiseFrom m k = fmap (raiseFrom m k)

instance (Raise a, Raise b) => Raise (a,b) where
    raiseFrom m k (x,y) = (raiseFrom m k x, raiseFrom m k y)

raise :: Raise t => Nat -> t -> t
raise = raiseFrom 0

data TelView = TelV Telescope Type

telView :: Type -> TelView
telView t = case unEl t of
  Pi a (Abs x b)  -> absV a x $ telView b
  Fun a b	  -> absV a "_" $ telView (raise 1 b)
  _		  -> TelV EmptyTel t
  where
    absV a x (TelV tel t) = TelV (ExtendTel a (Abs x tel)) t

telePi :: Telescope -> Type -> Type
telePi  EmptyTel	 t = t
telePi (ExtendTel u tel) t = el $ fn u b
  where
    el = El (sLub s1 s2)
    b = fmap (flip telePi t) tel
    s1 = getSort $ unArg u
    s2 = getSort $ absBody b

    fn a b
      | 0 `freeIn` absBody b = Pi a b
      | otherwise	     = Fun a $ absApp b __IMPOSSIBLE__

-- | Everything will be a pi.
telePi_ :: Telescope -> Type -> Type
telePi_  EmptyTel	 t = t
telePi_ (ExtendTel u tel) t = el $ Pi u b
  where
    el = El (sLub s1 s2)
    b = fmap (flip telePi_ t) tel
    s1 = getSort $ unArg u
    s2 = getSort $ absBody b

