module Agda.Auto.Syntax where

import Data.IORef
import qualified Data.Set as Set

import Agda.Syntax.Common (Hiding)
import Agda.Auto.NarrowingSearch

import Agda.Utils.Impossible

-- | Unique identifiers for variable occurrences in unification.
type UId o = Metavar (Exp o) (RefInfo o)

data HintMode = HMNormal
              | HMRecCall

data EqReasoningConsts o = EqReasoningConsts
  { eqrcId    -- "_≡_"
  , eqrcBegin -- "begin_"
  , eqrcStep  -- "_≡⟨_⟩_"
  , eqrcEnd   -- "_∎"
  , eqrcSym   -- "sym"
  , eqrcCong  -- "cong"
  :: ConstRef o
  }

data EqReasoningState = EqRSNone | EqRSChain | EqRSPrf1 | EqRSPrf2 | EqRSPrf3
 deriving (Eq, Show)

-- | The concrete instance of the 'blk' parameter in 'Metavar'.
--   I.e., the information passed to the search control.

data RefInfo o
  = RIEnv
    { rieHints :: [(ConstRef o, HintMode)]
    , rieDefFreeVars :: Nat
      -- ^ Nat - deffreevars
      --   (to make cost of using module parameters correspond to that of hints).
    , rieEqReasoningConsts :: Maybe (EqReasoningConsts o)
    }
  | RIMainInfo
    { riMainCxtLength :: Nat
      -- ^ Size of typing context in which meta was created.
    , riMainType      :: HNExp o
      -- ^ Head normal form of type of meta.
    , riMainIota      :: Bool
       -- ^ True if iota steps performed when normalising target type
       --   (used to put cost when traversing a definition
       --    by construction instantiation).
    }
  | RIUnifInfo [CAction o] (HNExp o)
    -- meta environment, opp hne
  | RICopyInfo (ICExp o)
  | RIIotaStep Bool -- True - semiflex
  | RIInferredTypeUnknown
  | RINotConstructor
  | RIUsedVars [UId o] [Elr o]
  | RIPickSubsvar

  | RIEqRState EqReasoningState


  | RICheckElim Bool -- isdep
  | RICheckProjIndex [ConstRef o] -- noof proj functions


type MyPB o = PB (RefInfo o)
type MyMB a o = MB a (RefInfo o)

type Nat = Int

data MId = Id String
         | NoId

-- | Abstraction with maybe a name.
--
--   Different from Agda, where there is also info
--   whether function is constant.
data Abs a = Abs MId a

-- | Constant signatures.

data ConstDef o = ConstDef
  { cdname        :: String
    -- ^ For debug printing.
  , cdorigin      :: o
    -- ^ Reference to the Agda constant.
  , cdtype        :: MExp o
    -- ^ Type of constant.
  , cdcont        :: DeclCont o
    -- ^ Constant definition.
  , cddeffreevars :: Nat
    -- ^ Free vars of the module where the constant is defined..
  } -- contains no metas

-- | Constant definitions.

data DeclCont o
  = Def Nat [Clause o] (Maybe Nat) -- maybe an index to elimand argument
                       (Maybe Nat) -- maybe index to elim arg if semiflex
  | Datatype [ConstRef o] -- constructors
             [ConstRef o] -- projection functions (in case it is a record)

  | Constructor Nat -- number of omitted args
  | Postulate

type Clause o = ([Pat o], MExp o)

data Pat o
  = PatConApp (ConstRef o) [Pat o]
  | PatVar String
  | PatExp
    -- ^ Dot pattern.
{- TODO: projection patterns.
  | PatProj (ConstRef o)
    -- ^ Projection pattern.
-}

type ConstRef o = IORef (ConstDef o)

-- | Head of application (elimination).
data Elr o
  = Var Nat
  | Const (ConstRef o)
  deriving (Eq)

getVar :: Elr o -> Maybe Nat
getVar (Var n) = Just n
getVar Const{} = Nothing

getConst :: Elr o -> Maybe (ConstRef o)
getConst (Const c) = Just c
getConst Var{}     = Nothing

data Sort
  = Set Nat
  | UnknownSort
  | Type

-- | Agsy's internal syntax.
data Exp o
  = App
    { appUId   :: Maybe (UId o)
      -- ^ Unique identifier of the head.
    , appOK    :: OKHandle (RefInfo o)
      -- ^ This application has been type-checked.
    , appHead  :: Elr o
      -- ^ Head.
    , appElims :: MArgList o
      -- ^ Arguments.
    }
  | Lam Hiding (Abs (MExp o))
    -- ^ Lambda with hiding information.
  | Pi (Maybe (UId o)) Hiding Bool (MExp o) (Abs (MExp o))
    -- ^ @True@ if possibly dependent (var not known to not occur).
    --   @False@ if non-dependent.
  | Sort Sort
  | AbsurdLambda Hiding
    -- ^ Absurd lambda with hiding information.

dontCare :: Exp o
dontCare = Sort UnknownSort

-- | "Maybe expression":  Expression or reference to meta variable.
type MExp o = MM (Exp o) (RefInfo o)

data ArgList o
  = ALNil
    -- ^ No more eliminations.
  | ALCons Hiding (MExp o) (MArgList o)
    -- ^ Application and tail.

  | ALProj (MArgList o) (MM (ConstRef o) (RefInfo o)) Hiding (MArgList o)
    -- ^ proj pre args, projfcn idx, tail

  | ALConPar (MArgList o)
    -- ^ Constructor parameter (missing in Agda).
    --   Agsy has monomorphic constructors.
    --   Inserted to cover glitch of polymorphic constructor
    --   applications coming from Agda

type MArgList o = MM (ArgList o) (RefInfo o)

data WithSeenUIds a o = WithSeenUIds
  { seenUIds :: [Maybe (UId o)]
  , rawValue :: a
  }

type HNExp o = WithSeenUIds (HNExp' o) o

data HNExp' o =
    HNApp  (Elr o) (ICArgList o)
  | HNLam  Hiding (Abs (ICExp o))
  | HNPi   Hiding Bool (ICExp o) (Abs (ICExp o))
  | HNSort Sort

-- | Head-normal form of 'ICArgList'.  First entry is exposed.
--
--   Q: Why are there no projection eliminations?
data HNArgList o = HNALNil
                 | HNALCons Hiding (ICExp o) (ICArgList o)
                 | HNALConPar (ICArgList o)

-- | Lazy concatenation of argument lists under explicit substitutions.
data ICArgList o = CALNil
                 | CALConcat (Clos (MArgList o) o) (ICArgList o)

-- | An expression @a@ in an explicit substitution @[CAction a]@.
type ICExp o  = Clos (MExp o) o
data Clos a o = Clos [CAction o] a

type CExp o   = TrBr (ICExp o) o
data TrBr a o = TrBr [MExp o] a

-- | Entry of an explicit substitution.
--
--   An explicit substitution is a list of @CAction@s.
--   This is isomorphic to the usual presentation where
--   @Skip@ and @Weak@ would be constructors of exp. substs.

data CAction o
  = Sub (ICExp o)
    -- ^ Instantation of variable.
  | Skip
    -- ^ For going under a binder, often called "Lift".
  | Weak Nat
    -- ^ Shifting substitution (going to a larger context).

type Ctx o = [(MId, CExp o)]

type EE = IO

-- -------------------------------------------

detecteliminand :: [Clause o] -> Maybe Nat
detecteliminand cls =
 case map cleli cls of
  [] -> Nothing
  (i:is) -> if all (i ==) is then i else Nothing
 where
  cleli (pats, _) = pateli 0 pats
  pateli i (PatConApp _ args : pats) = if all notcon (args ++ pats) then Just i else Nothing
  pateli i (_ : pats) = pateli (i + 1) pats
  pateli i [] = Nothing
  notcon PatConApp{} = False
  notcon _ = True

detectsemiflex :: ConstRef o -> [Clause o] -> IO Bool
detectsemiflex _ _ = return False -- disabled

categorizedecl :: ConstRef o -> IO ()
categorizedecl c = do
 cd <- readIORef c
 case cdcont cd of
  Def narg cls _ _ -> do
   semif <- detectsemiflex c cls
   let elim = detecteliminand cls
       semifb = case (semif, elim) of
                 (True, Just i) -> Just i -- just copying val of elim arg. this should be changed
                 (_, _) -> Nothing
   writeIORef c (cd {cdcont = Def narg cls elim semifb})
  _ -> return ()

-- -------------------------------------------

class MetaliseOKH t where
  metaliseOKH :: t -> IO t

instance MetaliseOKH t => MetaliseOKH (MM t a) where
  metaliseOKH e = case e of
    Meta m -> return $ Meta m
    NotM e -> NotM <$> metaliseOKH e

instance MetaliseOKH t => MetaliseOKH (Abs t) where
  metaliseOKH (Abs id b) = Abs id <$> metaliseOKH b

instance MetaliseOKH (Exp o) where
  metaliseOKH e = case e of
    App uid okh elr args ->
      (\ m -> App uid m elr) <$> (Meta <$> initMeta) <*> metaliseOKH args
    Lam hid b -> Lam hid <$> metaliseOKH b
    Pi uid hid dep it ot ->
      Pi uid hid dep <$> metaliseOKH it <*> metaliseOKH ot
    Sort{} -> return e
    AbsurdLambda{} -> return e

instance MetaliseOKH (ArgList o) where
  metaliseOKH e = case e of
    ALNil -> return ALNil
    ALCons hid a as -> ALCons hid <$> metaliseOKH a <*> metaliseOKH as
    ALProj eas idx hid as ->
      (\ eas -> ALProj eas idx hid) <$> metaliseOKH eas <*> metaliseOKH as
    ALConPar as -> ALConPar <$> metaliseOKH as

metaliseokh :: MExp o -> IO (MExp o)
metaliseokh = metaliseOKH

-- -------------------------------------------

class ExpandMetas t where
  expandMetas :: t -> IO t

instance ExpandMetas t => ExpandMetas (MM t a) where
  expandMetas t = case t of
    NotM e -> NotM <$> expandMetas e
    Meta m -> do
      mb <- readIORef (mbind m)
      case mb of
        Nothing -> return $ Meta m
        Just e  -> NotM <$> expandMetas e

instance ExpandMetas t => ExpandMetas (Abs t) where
  expandMetas (Abs id b) = Abs id <$> expandMetas b

instance ExpandMetas (Exp o) where
  expandMetas t = case t of
    App uid okh elr args -> App uid okh elr <$> expandMetas args
    Lam hid b -> Lam hid <$> expandMetas b
    Pi uid hid dep it ot ->
      Pi uid hid dep <$> expandMetas it <*> expandMetas ot
    Sort{} -> return t
    AbsurdLambda{} -> return t

instance ExpandMetas (ArgList o) where
  expandMetas e = case e of
    ALNil -> return ALNil
    ALCons hid a as -> ALCons hid <$> expandMetas a <*> expandMetas as
    ALProj eas idx hid as ->
      (\ a b -> ALProj a b hid) <$> expandMetas eas
      <*> expandbind idx <*> expandMetas as
    ALConPar as -> ALConPar <$> expandMetas as

-- ---------------------------------

addtrailingargs :: Clos (MArgList o) o -> ICArgList o -> ICArgList o
addtrailingargs newargs CALNil = CALConcat newargs CALNil
addtrailingargs newargs (CALConcat x xs) = CALConcat x (addtrailingargs newargs xs)

-- ---------------------------------

closify :: MExp o -> CExp o
closify e = TrBr [e] (Clos [] e)

sub :: MExp o -> CExp o -> CExp o
-- sub e (Clos [] x) = Clos [Sub e] x
sub e (TrBr trs (Clos (Skip : as) x)) = TrBr (e : trs) (Clos (Sub (Clos [] e) : as) x)
{-sub e (Clos (Weak n : as) x) = if n == 1 then
                                Clos as x
                               else
                                Clos (Weak (n - 1) : as) x-}
sub _ _ = __IMPOSSIBLE__

subi :: MExp o -> ICExp o -> ICExp o
subi e (Clos (Skip : as) x) = Clos (Sub (Clos [] e) : as) x
subi _ _ = __IMPOSSIBLE__

weak :: Weakening t => Nat -> t -> t
weak 0 = id
weak n = weak' n

class Weakening t where
  weak' :: Nat -> t -> t

instance Weakening a => Weakening (TrBr a o) where
  weak' n (TrBr trs e) = TrBr trs (weak' n e)

instance Weakening (Clos a o) where
  weak' n (Clos as x) = Clos (Weak n : as) x

instance Weakening (ICArgList o) where
  weak' n e = case e of
    CALNil         -> CALNil
    CALConcat a as -> CALConcat (weak' n a) (weak' n as)

instance Weakening (Elr o) where
  weak' n = rename (n+)

-- | Substituting for a variable.
doclos :: [CAction o] -> Nat -> Either Nat (ICExp o)
doclos = f 0
 where
  -- ns is the number of weakenings
  f ns []            i = Left (ns + i)
  f ns (Weak n : xs) i = f (ns + n) xs i
  f ns (Sub s  : _ ) 0 = Right (weak ns s)
  f ns (Skip   : _ ) 0 = Left ns
  f ns (Skip   : xs) i = f (ns + 1) xs (i - 1)
  f ns (Sub _  : xs) i = f ns xs (i - 1)


-- | FreeVars class and instances

freeVars :: FreeVars t => t -> Set.Set Nat
freeVars = freeVarsOffset 0

class FreeVars t where
  freeVarsOffset :: Nat -> t -> Set.Set Nat

instance (FreeVars a, FreeVars b) => FreeVars (a, b) where
  freeVarsOffset n (a, b) = Set.union (freeVarsOffset n a) (freeVarsOffset n b)

instance FreeVars t => FreeVars (MM t a) where
  freeVarsOffset n e = freeVarsOffset n (rm __IMPOSSIBLE__ e)

instance FreeVars t => FreeVars (Abs t) where
  freeVarsOffset n (Abs id e) = freeVarsOffset (n + 1) e

instance FreeVars (Elr o) where
  freeVarsOffset n e = case e of
    Var v   -> Set.singleton (v - n)
    Const{} -> Set.empty

instance FreeVars (Exp o) where
  freeVarsOffset n e = case e of
   App _ _ elr args -> freeVarsOffset n (elr, args)
   Lam _ b          -> freeVarsOffset n b
   Pi _ _ _ it ot   -> freeVarsOffset n (it, ot)
   Sort{}           -> Set.empty
   AbsurdLambda{}   -> Set.empty

instance FreeVars (ArgList o) where
  freeVarsOffset n es = case es of
    ALNil         -> Set.empty
    ALCons _ e es -> freeVarsOffset n (e, es)
    ALConPar es   -> freeVarsOffset n es
    ALProj{}      -> __IMPOSSIBLE__


-- | Renaming Typeclass and instances
rename :: Renaming t => (Nat -> Nat) -> t -> t
rename = renameOffset 0

class Renaming t where
  renameOffset :: Nat -> (Nat -> Nat) -> t -> t

instance (Renaming a, Renaming b) => Renaming (a, b) where
  renameOffset j ren (a, b) = (renameOffset j ren a, renameOffset j ren b)

instance Renaming t => Renaming (MM t a) where
  renameOffset j ren e = NotM $ renameOffset j ren (rm __IMPOSSIBLE__ e)

instance Renaming t => Renaming (Abs t) where
  renameOffset j ren (Abs id e) = Abs id $ renameOffset (j + 1) ren e

instance Renaming (Elr o) where
  renameOffset j ren e = case e of
    Var v | v >= j -> Var (ren (v - j) + j)
    _              -> e

instance Renaming (Exp o) where
  renameOffset j ren e = case e of
    App uid ok elr args -> uncurry (App uid ok) $ renameOffset j ren (elr, args)
    Lam hid e -> Lam hid (renameOffset j ren e)
    Pi a b c it ot -> uncurry (Pi a b c) $ renameOffset j ren (it, ot)
    Sort{}         -> e
    AbsurdLambda{} -> e

instance Renaming (ArgList o) where
  renameOffset j ren e = case e of
    ALNil           -> ALNil
    ALCons hid a as -> uncurry (ALCons hid) $ renameOffset j ren (a, as)
    ALConPar as     -> ALConPar (renameOffset j ren as)
    ALProj{}        -> __IMPOSSIBLE__