
module Agda.Auto.CaseSplit where

import Prelude hiding ((!!))

import Control.Monad.State as St hiding (lift)
import Control.Monad.Reader as Rd hiding (lift)
import qualified Control.Monad.State as St
import Control.Monad.IO.Class ( MonadIO(..) )

import Data.Function
import Data.IORef
import Data.Tuple (swap)
import Data.List (elemIndex)
-- Import of <> needed for 8.2.2, but redundant in 8.8.3
import Data.Monoid ((<>), Sum(..))
import qualified Data.Set    as Set
import qualified Data.IntMap as IntMap

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

import Agda.Auto.SearchControl
import Agda.Auto.Typecheck

import Agda.Utils.Impossible
import Agda.Utils.Monad (or2M)
import Agda.Utils.List ((!!), last1)

abspatvarname :: String
abspatvarname = "\0absurdPattern"

costCaseSplitVeryHigh, costCaseSplitHigh, costCaseSplitLow, costAddVarDepth
  :: Cost
costCaseSplitVeryHigh = 10000
costCaseSplitHigh     = 5000
costCaseSplitLow      = 2000
costAddVarDepth       = 1000

data HI a = HI Hiding a

drophid :: [HI a] -> [a]
drophid = map (\(HI _ x) -> x)

type CSPat o = HI (CSPatI o)
type CSCtx o = [HI (MId, MExp o)]

data CSPatI o = CSPatConApp (ConstRef o) [CSPat o]
              | CSPatVar Nat
              | CSPatExp (MExp o)
              | CSWith (MExp o) -- always an App
              | CSAbsurd

              | CSOmittedArg
type Sol o = [(CSCtx o, [CSPat o], Maybe (MExp o))]

caseSplitSearch ::
  forall o . IORef Int -> Int -> [ConstRef o] ->
  Maybe (EqReasoningConsts o) -> Int -> Cost -> ConstRef o ->
  CSCtx o -> MExp o -> [CSPat o] -> IO [Sol o]
caseSplitSearch ticks nsolwanted chints meqr depthinterval depth recdef ctx tt pats = do
 let branchsearch :: Cost -> CSCtx o -> MExp o ->
                     ([Nat], Nat, [Nat]) -> IO (Maybe (MExp o))
     branchsearch depth ctx tt termcheckenv = do

      nsol <- newIORef 1
      m <- initMeta
      sol <- newIORef Nothing
      let trm = Meta m
          hsol = do trm' <- expandMetas trm
                    writeIORef sol (Just trm')
          initcon = mpret
                  $ Sidecondition
                    (localTerminationSidecond termcheckenv recdef trm)
                  $ (case meqr of
                        Nothing  -> id
                        Just eqr -> mpret . Sidecondition (calcEqRState eqr trm)
                     ) $ tcSearch False (map (fmap closify) (drophid ctx))
                         (closify tt) trm
      recdefd <- readIORef recdef
      let env = RIEnv { rieHints = (recdef, HMRecCall) : map (, HMNormal) chints
                      , rieDefFreeVars = cddeffreevars recdefd
                      , rieEqReasoningConsts = meqr
                      }
      depreached <- topSearch ticks nsol hsol env initcon depth (depth + 1)
      readIORef sol
     ctx' = ff 1 ctx
     ff _ [] = []
     ff n (HI hid (id, t) : ctx) = HI hid (id, lift n t) : ff (n + 1) ctx
 caseSplitSearch' branchsearch depthinterval depth recdef ctx' tt pats

caseSplitSearch' :: forall o .
  (Cost -> CSCtx o -> MExp o -> ([Nat], Nat, [Nat]) -> IO (Maybe (MExp o))) ->
  Int -> Cost -> ConstRef o -> CSCtx o -> MExp o -> [CSPat o] -> IO [Sol o]
caseSplitSearch' branchsearch depthinterval depth recdef ctx tt pats = do
  recdefd <- readIORef recdef
  rc depth (cddeffreevars recdefd) ctx tt pats
 where
  rc :: Cost -> Int -> CSCtx o -> MExp o -> [CSPat o] -> IO [Sol o]
  rc depth _ _ _ _ | depth < 0 = return []
  rc depth nscrutavoid ctx tt pats = do

    mblkvar <- getblks tt
    fork
     mblkvar
   where
   fork :: [Nat] -> IO [Sol o]
   fork mblkvar = do
    sols1 <- dobody
    case sols1 of
     (_:_) -> return sols1
     [] -> do
      let r :: [Nat] -> IO [Sol o]
          r [] = return []
          r (v:vs) = do
           sols2 <- splitvar mblkvar v
           case sols2 of
            (_:_) -> return sols2
            [] -> r vs
      r [nv - x | x <- [0..nv]] -- [0..length ctx - 1 - nscrutavoid]
    where nv = length ctx - 1
   dobody :: IO [Sol o]
   dobody = do
    case findperm (map snd (drophid ctx)) of
     Just perm -> do
      let (ctx', tt', pats') = applyperm perm ctx tt pats
      res <- branchsearch depth ctx' tt' (localTerminationEnv pats')
      return $ case res of
       Just trm -> [[(ctx', pats', Just trm)]]
       Nothing -> []
     Nothing -> __IMPOSSIBLE__ -- no permutation found
   splitvar :: [Nat] -> Nat -> IO [Sol o]
   splitvar mblkvar scrut = do
    let scruttype = infertypevar ctx scrut
    case rm __IMPOSSIBLE__ scruttype of
     App _ _ (Const c) _ -> do
      cd <- readIORef c
      case cdcont cd of
       Datatype cons _ -> do
         sols <- dobranches cons
         return $ map (\sol -> case sol of
          [] ->
           case findperm (map snd (drophid ctx)) of
            Just perm ->
             let HI scrhid(_, scrt) = ctx !! scrut
                 ctx1 = take scrut ctx ++ (HI scrhid (Id abspatvarname, scrt)) : drop (scrut + 1) ctx
                 (ctx', _, pats') = applyperm perm ctx1 tt ({-map (replacep scrut 1 CSAbsurd __IMPOSSIBLE__) -}pats)
             in [(ctx', pats', Nothing)]
            Nothing -> __IMPOSSIBLE__ -- no permutation found
          _ -> sol
          ) sols
        where
         dobranches :: [ConstRef o] -> IO [Sol o]
         dobranches [] = return [[]]
         dobranches (con : cons) = do
          cond <- readIORef con
          let ff t = case rm __IMPOSSIBLE__ t of
                        Pi _ h _ it (Abs id ot) ->
                         let (xs, inft) = ff ot
                         in (((h, scrut + length xs), id, lift (scrut + length xs + 1) it) : xs, inft)
                        _ -> ([], lift scrut t)
              (newvars, inftype) = ff (cdtype cond)
              constrapp = NotM $ App Nothing (NotM OKVal) (Const con) (foldl (\xs ((h, v), _, _) -> NotM $ ALCons h (NotM $ App Nothing (NotM OKVal) (Var v) (NotM ALNil)) xs) (NotM ALNil) (reverse newvars))
              pconstrapp = CSPatConApp con (map (\((hid, v), _, _) -> HI hid (CSPatVar v)) newvars)
              thesub = replace scrut (length newvars) constrapp
              Id newvarprefix = fst $ (drophid ctx) !! scrut
              ctx1 = map (\(HI hid (id, t)) -> HI hid (id, thesub t)) (take scrut ctx) ++
                     reverse (zipWith (\((hid, _), id, t) i ->
                       HI hid (Id (case id of {NoId -> newvarprefix{- ++ show i-}; Id id -> id}), t)
                      ) newvars [0..]) ++
                     map (\(HI hid (id, t)) -> HI hid (id, thesub t)) (drop (scrut + 1) ctx)
              tt' = thesub tt
              pats' = map (replacep scrut (length newvars) pconstrapp constrapp) pats
              scruttype' = thesub scruttype  -- scruttype shouldn't really refer to scrutvar so lift is enough, but what if circular ref has been created and this is not detected until case split is done
          case unifyexp inftype scruttype' of
           Nothing -> do
            res <- notequal scrut (length newvars) scruttype' inftype
            if res then -- branch absurd
              dobranches cons
             else -- branch dont know
              return []
           Just unif ->
            do
             let (ctx2, tt2, pats2) = removevar ctx1 tt' pats' unif
                 --cost = if elem scrut mblkvar then costCaseSplit - (costCaseSplit - costCaseSplitFollow) `div` (length mblkvar) else costCaseSplit
                 cost
                   | null mblkvar && scrut < length ctx - nscrutavoid && nothid
                                                                = costCaseSplitLow +
                                                                  costAddVarDepth *
                                                                  Cost (depthofvar scrut pats)
                   | null mblkvar                               = costCaseSplitVeryHigh
                   | scrut `elem` mblkvar                       = costCaseSplitLow
                   | scrut < length ctx - nscrutavoid && nothid = costCaseSplitHigh
                   | otherwise                                  = costCaseSplitVeryHigh

                 nothid = let HI hid _ = ctx !! scrut
                          in hid == NotHidden


             sols <- rc (depth - cost) (length ctx - 1 - scrut) ctx2 tt2 pats2
             case sols of
              [] -> return []
              _ -> do
               sols2 <- dobranches cons
               return $ concatMap (\sol -> map (\sol2 -> sol ++ sol2) sols2) sols
       _ -> return [] -- split failed "scrut type is not datatype"
     _ -> return [] -- split failed "scrut type is not datatype"

infertypevar :: CSCtx o -> Nat -> MExp o
infertypevar ctx v = snd $ (drophid ctx) !! v

class Replace t u where
  type ReplaceWith t u
  replace' :: Nat -> MExp (ReplaceWith t u) -> t -> Reader (Nat, Nat) u

replace :: Replace t u => Nat -> Nat -> MExp (ReplaceWith t u) -> t -> u
replace sv nnew e t = replace' 0 e t `runReader` (sv, nnew)

instance Replace t u => Replace (Abs t) (Abs u) where
  type ReplaceWith (Abs t) (Abs u) = ReplaceWith t u
  replace' n re (Abs mid b) = Abs mid <$> replace' (n + 1) re b

instance Replace (Exp o) (MExp o) where
  type ReplaceWith (Exp o) (MExp o) = o
  replace' n re = \case
    App uid ok elr@(Var v) args -> do
      ih         <- NotM <$> replace' n re args
      (sv, nnew) <- ask
      return $
        if v >= n
        then if v - n == sv
             then betareduce (lift n re) ih
             else if v - n > sv
                  then NotM $ App uid ok (Var (v + nnew - 1)) ih
                  else NotM $ App uid ok elr ih
        else NotM $ App uid ok elr ih
    App uid ok elr@Const{} args ->
      NotM . App uid ok elr . NotM <$> replace' n re args
    Lam hid b -> NotM . Lam hid <$> replace' (n + 1) re b
    Pi uid hid possdep it b ->
      fmap NotM $ Pi uid hid possdep <$> replace' n re it <*> replace' n re b
    e@Sort{} -> return $ NotM e
    e@AbsurdLambda{} -> return $ NotM e

instance Replace t u => Replace (MM t (RefInfo o)) u where
  type ReplaceWith (MM t (RefInfo o)) u = ReplaceWith t u
  replace' n re = replace' n re . rm __IMPOSSIBLE__

instance Replace (ArgList o) (ArgList o) where
  type ReplaceWith (ArgList o) (ArgList o) = o
  replace' n re args = case args of
    ALNil           -> return ALNil
    ALCons hid a as ->
      ALCons hid <$> replace' n re a <*> (NotM <$> replace' n re as)
    ALProj{}        -> __IMPOSSIBLE__
    ALConPar as     -> ALConPar . NotM <$> replace' n re as


betareduce :: MExp o -> MArgList o -> MExp o
betareduce e args = case rm __IMPOSSIBLE__ args of
 ALNil -> e
 ALCons _ a rargs -> case rm __IMPOSSIBLE__ e of
  App uid ok elr eargs -> NotM $ App uid ok elr (concatargs eargs args)
  Lam _ (Abs _ b) -> betareduce (replace 0 0 a b) rargs
  _ -> __IMPOSSIBLE__ -- not type correct if this happens
 ALProj{} -> __IMPOSSIBLE__
 ALConPar as -> __IMPOSSIBLE__

concatargs :: MArgList o -> MArgList o -> MArgList o
concatargs xs ys = case rm __IMPOSSIBLE__ xs of
  ALNil -> ys

  ALCons hid x xs -> NotM $ ALCons hid x (concatargs xs ys)

  ALProj{} -> __IMPOSSIBLE__

  ALConPar as -> NotM $ ALConPar (concatargs xs ys)

replacep :: forall o. Nat -> Nat -> CSPatI o -> MExp o -> CSPat o -> CSPat o
replacep sv nnew rp re = r
 where
  r :: CSPat o -> CSPat o
  r (HI hid (CSPatConApp c ps)) = HI hid (CSPatConApp c (map r ps))
  r (HI hid (CSPatVar v)) | v == sv   = HI hid rp
                          | v > sv    = HI hid (CSPatVar (v + nnew - 1))
                          | otherwise = HI hid (CSPatVar v)
  r (HI hid (CSPatExp e)) = HI hid (CSPatExp $ replace sv nnew re e)

  r p@(HI _ CSOmittedArg) = p

  r _ = __IMPOSSIBLE__ -- other constructors dont appear in indata Pats



-- Unification takes two values of the same type and generates a list
-- of assignments making the two terms equal.

type Assignments o = [(Nat, Exp o)]

class Unify t where
  type UnifiesTo t
  unify'    :: t -> t -> StateT (Assignments (UnifiesTo t)) Maybe ()
  notequal' :: t -> t -> ReaderT (Nat, Nat) (StateT (Assignments (UnifiesTo t)) IO) Bool

unify :: Unify t => t -> t -> Maybe (Assignments (UnifiesTo t))
unify t u = unify' t u `execStateT` []

notequal :: Unify t => Nat -> Nat -> t -> t -> IO Bool
notequal fstnew nbnew t1 t2 = notequal' t1 t2 `runReaderT` (fstnew, nbnew)
                                              `evalStateT` []

instance (Unify t, o ~ UnifiesTo t) => Unify (MM t (RefInfo o)) where
  type UnifiesTo (MM t (RefInfo o)) = o

  unify' = unify' `on` rm __IMPOSSIBLE__

  notequal' = notequal' `on` rm __IMPOSSIBLE__

unifyVar :: Nat -> Exp o -> StateT (Assignments o) Maybe ()
unifyVar v e = do
  unif <- get
  case lookup v unif of
    Nothing -> modify ((v, e) :)
    Just e' -> unify' e e'

instance Unify t => Unify (Abs t) where
  type UnifiesTo (Abs t) = UnifiesTo t
  unify' (Abs _ b1) (Abs _ b2) = unify' b1 b2

  notequal' (Abs _ b1) (Abs _ b2) = notequal' b1 b2

instance Unify (Exp o) where
  type UnifiesTo (Exp o) = o

  unify' e1 e2 = case (e1, e2) of
   (App _ _ elr1 args1, App _ _ elr2 args2) | elr1 == elr2 -> unify' args1 args2
   (Lam hid1 b1, Lam hid2 b2)               | hid1 == hid2 -> unify' b1 b2
   (Pi _ hid1 _ a1 b1, Pi _ hid2 _ a2 b2)   | hid1 == hid2 -> unify' a1 a2
                                                           >> unify' b1 b2
   (Sort _, Sort _) -> return () -- a bit sloppy
   (App _ _ (Var v) (NotM ALNil), _)
     | v `Set.member` (freeVars e2) -> St.lift Nothing -- Occurs check
   (_, App _ _ (Var v) (NotM ALNil))
     | v `Set.member` (freeVars e1) -> St.lift Nothing -- Occurs check
   (App _ _ (Var v) (NotM ALNil), _) -> unifyVar v e2
   (_, App _ _ (Var v) (NotM ALNil)) -> unifyVar v e1
   _ -> St.lift Nothing

  notequal' e1 e2 = do
    (fstnew, nbnew) <- ask
    unifier         <- get
    case (e1, e2) of
      (App _ _ elr1 es1, App _ _ elr2 es2) | elr1 == elr2 -> notequal' es1 es2
      (_, App _ _ (Var v2) (NotM ALNil)) -- why is this not symmetric?!
        | fstnew <= v2 && v2 < fstnew + nbnew ->
        case lookup v2 unifier of
          Nothing  -> modify ((v2, e1):) >> return False
          Just e2' -> notequal' e1 e2'
{-
  GA: Skipped these: Not sure why we'd claim they're impossible
      (_, App _ _ (Var v2) (NotM ALProj{})) -> __IMPOSSIBLE__
      (_, App _ _ (Var v2) (NotM ALConPar{})) -> __IMPOSSIBLE__
-}
      (App _ _ (Const c1) es1, App _ _ (Const c2) es2) -> do
        cd1 <- liftIO $ readIORef c1
        cd2 <- liftIO $ readIORef c2
        case (cdcont cd1, cdcont cd2) of
          (Constructor{}, Constructor{}) -> if c1 == c2 then notequal' es1 es2
                                            else return True
          _ -> return False
{- GA: Why don't we have a case for distinct heads after all these
       unification cases for vars with no spines & metas that can
       be looked up?
      (App _ _ elr1 _, App _ _ elr2 _) | elr1 <> elr2 -> return True
-}
      _ -> return False

instance Unify (ArgList o) where
  type UnifiesTo (ArgList o) = o

  unify' args1 args2 = case (args1, args2) of
   (ALNil, ALNil) -> pure ()
   (ALCons hid1 a1 as1, ALCons hid2 a2 as2) | hid1 == hid2 -> unify' a1 a2
                                                           >> unify' as1 as2
   (ALConPar as1, ALCons _ _ as2) -> unify' as1 as2
   (ALCons _ _ as1, ALConPar as2) -> unify' as1 as2
   (ALConPar as1, ALConPar as2)   -> unify' as1 as2
   _ -> St.lift Nothing

  notequal' args1 args2 = case (args1, args2) of
    (ALCons _ e es, ALCons _ f fs) -> notequal' e f `or2M` notequal' es fs
    (ALConPar es1, ALConPar es2)   -> notequal' es1 es2
    _                              -> return False

-- This definition is only here to respect the previous interface.
unifyexp :: MExp o -> MExp o -> Maybe ([(Nat, MExp o)])
unifyexp e1 e2 = fmap (NotM <$>) <$> unify e1 e2

class Lift t where
  lift' :: Nat -> Nat -> t -> t

lift :: Lift t => Nat -> t -> t
lift 0 = id
lift n = lift' n 0

instance Lift t => Lift (Abs t) where
  lift' n j (Abs mid b) = Abs mid (lift' n (j + 1) b)

instance Lift t => Lift (MM t r) where
  lift' n j = NotM . lift' n j . rm __IMPOSSIBLE__

instance Lift (Exp o) where
  lift' n j = \case
    App uid ok elr args -> case elr of
      Var v | v >= j -> App uid ok (Var (v + n)) (lift' n j args)
      _ -> App uid ok elr (lift' n j args)
    Lam hid b -> Lam hid (lift' n j b)
    Pi uid hid possdep it b -> Pi uid hid possdep (lift' n j it) (lift' n j b)
    e@Sort{} -> e
    e@AbsurdLambda{} -> e

instance Lift (ArgList o) where
  lift' n j args = case args of
    ALNil           -> ALNil
    ALCons hid a as -> ALCons hid (lift' n j a) (lift' n j as)
    ALProj{}        -> __IMPOSSIBLE__
    ALConPar as     -> ALConPar (lift' n j as)


removevar :: CSCtx o -> MExp o -> [CSPat o] -> [(Nat, MExp o)] -> (CSCtx o, MExp o, [CSPat o])
removevar ctx tt pats [] = (ctx, tt, pats)
removevar ctx tt pats ((v, e) : unif) =
 let
  e2 = replace v 0 __IMPOSSIBLE__ {- occurs check failed -} e
  thesub = replace v 0 e2
  ctx1 = map (\(HI hid (id, t)) -> HI hid (id, thesub t)) (take v ctx) ++
         map (\(HI hid (id, t)) -> HI hid (id, thesub t)) (drop (v + 1) ctx)
  tt' = thesub tt
  pats' = map (replacep v 0 (CSPatExp e2) e2) pats
  unif' = map (\(uv, ue) -> (if uv > v then uv - 1 else uv, thesub ue)) unif
 in
  removevar ctx1 tt' pats' unif'

findperm :: [MExp o] -> Maybe [Nat]
findperm ts =
 let
  frees = map freevars ts
  m = IntMap.fromList $
      map (\i -> (i, length (filter (elem i) frees)))
          [0..length ts - 1]
  r _ perm 0 = Just $ reverse perm
  r m perm n =
   case lookup 0 (map swap (IntMap.toList m)) of
    Nothing -> Nothing
    Just i -> r (foldl (flip $ IntMap.adjust (subtract 1))
                       (IntMap.insert i (-1) m)
                       (frees !! i))
                 (i : perm) (n - 1)
 in r m [] (length ts)


freevars :: FreeVars t => t -> [Nat]
freevars = Set.toList . freeVars

applyperm :: [Nat] -> CSCtx o -> MExp o -> [CSPat o] ->
             (CSCtx o, MExp o, [CSPat o])
applyperm perm ctx tt pats =
 let ctx1 = map (\(HI hid (id, t)) -> HI hid (id, rename (ren perm) t)) ctx
     ctx2 = map (\i -> ctx1 !! i) perm
     ctx3 = seqctx ctx2
     tt' = rename (ren perm) tt
     pats' = map (rename (ren perm)) pats
 in (ctx3, tt', pats')

ren :: [Nat] -> Nat -> Int
ren n i = let Just j = elemIndex i n in j

instance Renaming t => Renaming (HI t) where
  renameOffset j ren (HI hid t) = HI hid $ renameOffset j ren t

instance Renaming (CSPatI o) where
  renameOffset j ren = \case
    CSPatConApp c pats -> CSPatConApp c $ map (renameOffset j ren) pats
    CSPatVar i         -> CSPatVar $ j + ren i
    CSPatExp e         -> CSPatExp $ renameOffset j ren e
    e@CSOmittedArg     -> e
    _                  -> __IMPOSSIBLE__

seqctx :: CSCtx o -> CSCtx o
seqctx = r (-1)
 where
  r _ [] = []
  r n (HI hid (id, t) : ctx) = HI hid (id, lift n t) : r (n - 1) ctx
-- --------------------

depthofvar :: Nat -> [CSPat o] -> Nat
depthofvar v pats =
 let [depth] = concatMap (f 0) (drophid pats)
     f d (CSPatConApp _ pats) = concatMap (f (d + 1)) (drophid pats)
     f d (CSPatVar v') = [d | v == v']
     f _ _ = []
 in depth

-- --------------------
-- | Speculation: Type class computing the size (?) of a pattern
--   and collecting the vars it introduces
class LocalTerminationEnv a where
  sizeAndBoundVars :: a -> (Sum Nat, [Nat])

instance LocalTerminationEnv a => LocalTerminationEnv (HI a) where
  sizeAndBoundVars (HI _ p) = sizeAndBoundVars p

instance LocalTerminationEnv (CSPatI o) where
  sizeAndBoundVars = \case
    CSPatConApp _ ps -> (1, []) <> sizeAndBoundVars ps
    CSPatVar n       -> (0, [n])
    CSPatExp e       -> sizeAndBoundVars e
    _                -> (0, [])

instance LocalTerminationEnv a => LocalTerminationEnv [a] where
  sizeAndBoundVars = foldMap sizeAndBoundVars

instance LocalTerminationEnv (MExp o) where
--  sizeAndBoundVars e = case rm __IMPOSSIBLE__ e of
-- GA: 2017 06 27: Not actually impossible! (cf. #2620)
  sizeAndBoundVars Meta{} = (0, [])
-- Does this default behaviour even make sense? The catchall in the
-- following match seems to suggest it does
  sizeAndBoundVars (NotM e) = case e of
    App _ _ (Var v) _      -> (0, [v])
    App _ _ (Const _) args -> (1, []) <> sizeAndBoundVars args
    _                      -> (0, [])

instance (LocalTerminationEnv a, LocalTerminationEnv b) => LocalTerminationEnv (a, b) where
  sizeAndBoundVars (a, b) = sizeAndBoundVars a <> sizeAndBoundVars b

instance LocalTerminationEnv (MArgList o) where
  sizeAndBoundVars as = case rm __IMPOSSIBLE__ as of
    ALNil         -> (0, [])
    ALCons _ a as -> sizeAndBoundVars (a, as)
    ALProj{}      -> __IMPOSSIBLE__
    ALConPar as   -> sizeAndBoundVars as


-- | Take a list of patterns and returns (is, size, vars) where (speculation):
---  * the is are the pattern indices the vars are contained in
--   * size is total number of constructors removed (?) to access vars
localTerminationEnv :: [CSPat o] -> ([Nat], Nat, [Nat])
localTerminationEnv pats = (is, getSum s, vs) where

  (is , s , vs) = g 0 pats

  g :: Nat -> [CSPat o] -> ([Nat], Sum Nat, [Nat])
  g _ [] = ([], 0, [])
  g i (hp@(HI _ p) : ps) = case p of
    CSPatConApp{} -> let (size, vars) = sizeAndBoundVars hp
                     in ([i], size, vars) <> g (i + 1) ps
    _ -> g (i + 1) ps

localTerminationSidecond :: ([Nat], Nat, [Nat]) -> ConstRef o -> MExp o -> EE (MyPB o)
localTerminationSidecond (is, size, vars) reccallc b =
  ok b
 where
     ok e = mmpcase (False, prioNo, Nothing) e $ \e -> case e of
      App _ _ elr args -> mpret $ Sidecondition
       (oks args)
       (case elr of
          Const c | c == reccallc -> if size == 0 then mpret (Error "localTerminationSidecond: no size to decrement") else okcall 0 size vars args
          _ -> mpret OK
       )
      Lam _ (Abs _ e) -> ok e
      Pi _ _ _ it (Abs _ ot) -> mpret $ Sidecondition
       (ok it)
       (ok ot)
      Sort{} -> mpret OK

      AbsurdLambda{} -> mpret OK


     oks as = mmpcase (False, prioNo, Nothing) as $ \as -> case as of
      ALNil -> mpret OK
      ALCons _ a as -> mpret $ Sidecondition
       (ok a)
       (oks as)

      ALProj eas _ _ as -> mpret $ Sidecondition (oks eas) (oks as)


      ALConPar as -> oks as

     okcall i size vars as = mmpcase (False, prioNo, Nothing) as $ \as -> case as of
      ALNil -> mpret OK
      ALCons _ a as | i `elem` is ->
       mbpcase prioNo Nothing (he size vars a) $ \x -> case x of
        Nothing -> mpret $ Error "localTerminationSidecond: reccall not ok"
        Just (size', vars') -> okcall (i + 1) size' vars' as
      ALCons _ a as -> okcall (i + 1) size vars as

      ALProj{} -> mpret OK


      ALConPar as -> __IMPOSSIBLE__

     he size vars e = mmcase e $ \e -> case e of
      App _ _ (Var v) _ ->
       case remove v vars of
        Nothing -> mbret Nothing
        Just vars' -> mbret $ Just (size, vars')
      App _ _ (Const c) args -> do
       cd <- readIORef c
       case cdcont cd of
        Constructor{} ->
         if size == 1 then
          mbret Nothing
         else
          hes (size - 1) vars args
        _ -> mbret Nothing
      _ -> mbret Nothing
     hes size vars as = mmcase as $ \as -> case as of
      ALNil -> mbret $ Just (size, vars)
      ALCons _ a as ->
       mbcase (he size vars a) $ \x -> case x of
        Nothing -> mbret Nothing
        Just (size', vars') -> hes size' vars' as

      ALProj{} -> __IMPOSSIBLE__


      ALConPar as -> __IMPOSSIBLE__

     remove _ [] = Nothing
     remove x (y : ys) | x == y = Just ys
     remove x (y : ys) = case remove x ys of {Nothing -> Nothing; Just ys' -> Just (y : ys')}

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


getblks :: MExp o -> IO [Nat]
getblks tt = do
 NotB (hntt, blks) <- hnn_blks (Clos [] tt)
 case f blks of
  Just v -> return [v]
  Nothing -> case rawValue hntt of
   HNApp (Const c) args -> do
    cd <- readIORef c
    case cdcont cd of
     Datatype{} -> g [] args
     _ -> return []
   _ -> return []
 where
  f blks = case blks of
             (b : bs) -> case rawValue (last1 b bs) of
              HNApp (Var v) _ -> Just v
              _ -> Nothing
             _ -> Nothing
  g vs args = do
   NotB hnargs <- hnarglist args
   case hnargs of
    HNALCons _ a as -> do
     NotB (_, blks) <- hnn_blks a
     let vs' = case f blks of
                Just v | v `notElem` vs -> v : vs
                _ -> vs
     g vs' as
    _ -> return vs
-- ---------------------------
