{-# LANGUAGE ExistentialQuantification, FlexibleContexts,
             ScopedTypeVariables, CPP #-}

module Agda.Auto.Typecheck where

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

import Data.IORef
import Control.Monad (liftM)

import Agda.Auto.NarrowingSearch
import Agda.Auto.Syntax
import Agda.Auto.SearchControl


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

tcExp :: Bool -> Ctx o -> CExp o -> MExp o -> EE (MyPB o)
tcExp isdep ctx typ@(TrBr typtrs ityp@(Clos _ itypexp)) trm =
  mbpcase prioTypeUnknown Nothing (hnn_checkstep ityp) $ \(hntyp, iotastepdone) ->
  mmpcase (True, prioTypecheck isdep, Just (RIMainInfo (length ctx) hntyp iotastepdone)) trm $ \trm -> case trm of
   App _ okh elr args -> case hntyp of
    HNPi{} | isdep -> mpret $ Error "tcExp, dep terms should be eta-long"
    _ -> do
     (ityp, sc) <- case elr of
              Var v -> -- assuming within scope
               return (weak (v + 1) (snd $ ctx !! v), id)
              Const c -> do
               cdef <- readIORef c
               return (closify (cdtype cdef), \x -> mpret $ And (Just [Term args]) (noiotastep_term c args) x)

     ndfv <- case elr of
              Var{} -> return 0
              Const c -> readIORef c >>= \cd -> return (cddeffreevars cd)


     isconstructor <- case elr of
      Var{} -> return False
      Const c -> do
       cdef <- readIORef c
       return $ case cdcont cdef of {Constructor{} -> True; _ -> False}

     sc $ tcargs ndfv isdep ctx ityp args (NotM $ App Nothing (NotM OKVal) elr (NotM ALNil)) isconstructor $ \ityp _ -> mpret $ ConnectHandle okh (comp' True typ ityp)
   Lam hid (Abs id1 b) -> case hntyp of
    HNPi _ hid2 _ it (Abs id2 ot) | hid == hid2 ->
     tcExp isdep ((pickid id1 id2, t it) : ctx) (t ot) b
    _ -> mpret $ Error "tcExp, type of lam should be fun or pi (and same hid)"
   Pi _ _ _ it (Abs id ot) -> case hntyp of
    HNSort s ->
     mpret $ And (Just [Term ctx, Term it])
      (tcExp True ctx (closify (NotM $ Sort s)) it)
      (tcExp isdep ((id, closify it) : ctx) (closify (NotM $ Sort s)) ot)
    _ -> mpret $ Error "tcExp, type of pi should be set"
   Sort (Set i) -> case hntyp of
    HNSort s2 -> case s2 of
     Set j -> mpret $ if i < j then OK else Error "tcExp, type of set should be larger set"

     UnknownSort -> mpret OK -- mpret $ Error "tcExp, type of set i unknown sort" -- OK instead? (prev __IMPOSSIBLE__)

     Type -> mpret OK
    _ -> mpret $ Error "tcExp, type of set should be set"

   Sort UnknownSort -> __IMPOSSIBLE__

   Sort Type -> __IMPOSSIBLE__

   AbsurdLambda hid -> case hntyp of
    HNPi _ hid2 _ it _ | hid == hid2 ->
     mbpcase prioAbsurdLambda Nothing (getDatatype it) $ \res -> case res of
      Just (indeces, cons) ->
       foldl (\p con -> mpret $ And Nothing p (
        constructorImpossible indeces con
       )) (mpret OK) cons
      Nothing -> mpret $ Error "tcExp, absurd lambda, datatype needed"
    _ -> mpret $ Error "tcExp, type of absurd lam should be fun or pi (and same hid)"


  where
   t = TrBr typtrs


getDatatype :: ICExp o -> EE (MyMB (Maybe (ICArgList o, [ConstRef o])) o)
getDatatype t =
 mbcase (hnn t) $ \hnt -> case hnt of
  HNApp _ (Const c) args -> do
   cd <- readIORef c
   case cdcont cd of
    Datatype cons _ -> mbret $ Just (args, cons) -- ?? check that lenth args corresponds to type of datatype
    _ -> mbret Nothing
  _ -> mbret Nothing

constructorImpossible :: ICArgList o -> ConstRef o -> EE (MyPB o)
constructorImpossible args c = do
 cd <- readIORef c
 mbpcase prioAbsurdLambda Nothing (traversePi (-1) (Clos [] $ cdtype cd)) $ \hnot ->
  case hnot of
   HNApp _ _ args2 -> unequals args args2 (\_ -> mpret $ Error "not unequal") []
   _ -> mpret $ Error "constructorImpossible 1"

unequals :: ICArgList o -> ICArgList o -> ([(Nat, HNExp o)] -> EE (MyPB o)) -> [(Nat, HNExp o)] -> EE (MyPB o)
unequals es1 es2 cont unifier2 =
 mbpcase prioAbsurdLambda Nothing (hnarglist es1) $ \hnes1 ->
 mbpcase prioAbsurdLambda Nothing (hnarglist es2) $ \hnes2 ->
 case (hnes1, hnes2) of
  (HNALCons _ e1 es1, HNALCons _ e2 es2) -> unequal e1 e2 (unequals es1 es2 cont) unifier2

  (HNALConPar es1, HNALConPar es2) -> unequals es1 es2 cont unifier2

  _ -> cont unifier2

unequal :: ICExp o -> ICExp o -> ([(Nat, HNExp o)] -> EE (MyPB o)) -> [(Nat, HNExp o)] -> EE (MyPB o)
unequal e1 e2 cont unifier2 =
 mbpcase prioAbsurdLambda Nothing (hnn e1) $ \hne1 ->
 mbpcase prioAbsurdLambda Nothing (hnn e2) $ \hne2 ->
  case hne2 of
   HNApp _ (Var v2) es2 | v2 < 0 ->
    mbpcase prioAbsurdLambda Nothing (hnarglist es2) $ \hnes2 -> case hnes2 of
     HNALNil ->
      case lookup v2 unifier2 of
       Nothing -> cont ((v2, hne1) : unifier2)
       Just hne2' -> cc hne1 hne2'
     HNALCons{} -> cont unifier2

     HNALConPar{} -> __IMPOSSIBLE__

   _ -> cc hne1 hne2
 where
  cc hne1 hne2 = case (hne1, hne2) of
   (HNApp _ (Const c1) es1, HNApp _ (Const c2) es2) -> do
    cd1 <- readIORef c1
    cd2 <- readIORef c2
    case (cdcont cd1, cdcont cd2) of
     (Constructor{}, Constructor{}) ->
      if c1 == c2 then
       unequals es1 es2 cont unifier2
      else
       mpret OK
     _ -> cont unifier2
   _ -> cont unifier2

traversePi :: Int -> ICExp o -> EE (MyMB (HNExp o) o)
traversePi v t =
 mbcase (hnn t) $ \hnt ->
 case hnt of
  HNPi _ _ _ _ (Abs _ ot) -> traversePi (v - 1) (subi (NotM $ App Nothing (NotM OKVal) (Var v) (NotM ALNil)) ot)
  _ -> mbret hnt


tcargs :: Nat -> Bool -> Ctx o -> CExp o -> MArgList o -> MExp o -> Bool -> (CExp o -> MExp o -> EE (MyPB o)) -> EE (MyPB o)
tcargs ndfv isdep ctx ityp@(TrBr ityptrs iityp) args elimtrm isconstructor cont = mmpcase (True, prioTypecheckArgList, (Just $ RICheckElim $ isdep || isconstructor)) args $ \args' -> case args' of
 ALNil -> cont ityp elimtrm
 ALCons hid a as ->
  mbpcase prioInferredTypeUnknown (Just RIInferredTypeUnknown) (hnn iityp) $ \hnityp -> case hnityp of
   HNPi _ hid2 possdep it (Abs _ ot) | ndfv > 0 || copyarg a || hid == hid2 -> mpret $
    And (Just ((if possdep then [Term a] else []) ++ [Term ctx, Term ityptrs]))
        (if ndfv > 0 then mpret OK else (tcExp (isdep || possdep) ctx (t it) a))
        (tcargs (ndfv - 1) isdep ctx (sub a (t ot)) as (addend hid a elimtrm) isconstructor cont)
   _ -> mpret $ Error "tcargs, inf type should be fun or pi (and same hid)"


 ALProj{} | ndfv > 0 -> __IMPOSSIBLE__

 ALProj preas projidx hid as ->
  mbpcase prioInferredTypeUnknown (Just RIInferredTypeUnknown) (hnn iityp) $ \hnityp -> case hnityp of
   HNApp _ (Const dd) _ -> do
    dddef <- readIORef dd
    case cdcont dddef of
     Datatype _ projs ->
      mmpcase (True, prioProjIndex, Just (RICheckProjIndex projs)) projidx $
      \projidx -> do
       projd <- readIORef projidx
       tcargs (cddeffreevars projd) isdep ctx (closify $ cdtype projd) preas (NotM $ App Nothing (NotM OKVal) (Const projidx) (NotM ALNil)) True $
        \ityp2@(TrBr ityp2trs iityp2) elimtrm2 ->
         case iityp2 of
          Clos _ (NotM (Pi _ _ _ (NotM (App _ _ (Const dd2) _)) _)) | dd2 == dd ->
           mbpcase prioInferredTypeUnknown (Just RIInferredTypeUnknown) (hnn iityp2) $ \hnityp2 -> case hnityp2 of
            HNPi _ hid2 possdep it (Abs _ ot) | hid == hid2 -> mpret $
             And Nothing
                 (comp' True (TrBr ityp2trs it) ityp)
                 (tcargs 0 isdep ctx (sub elimtrm (t ot)) as (addend hid elimtrm elimtrm2) isconstructor cont)
            _ -> mpret $ Error "proj function type is not a Pi"
          _ -> mpret $ Error "proj function type is not correct"
     _ -> mpret $ Error "proj, not a datatype"
   _ -> mpret $ Error "proj, not a const app"


 ALConPar _ -> __IMPOSSIBLE__

 where
  t = TrBr ityptrs


addend hid a (NotM (App uid okh elr as)) = NotM $ App uid okh elr (f as)
 where f (NotM ALNil) = NotM $ ALCons hid a (NotM $ ALNil)
       f (NotM (ALCons hid a as)) = NotM $ ALCons hid a (f as)
       f _ = __IMPOSSIBLE__
addend _ _ _ = __IMPOSSIBLE__
copyarg _ = False


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


type HNNBlks o = [HNExp o]

noblks = []
addblk = (:)


hnn :: ICExp o -> EE (MyMB (HNExp o) o)
hnn e = mbcase (hnn_blks e) $ \(hne, _) -> mbret hne

hnn_blks :: ICExp o -> EE (MyMB (HNExp o, HNNBlks o) o)
hnn_blks e = hnn' e CALNil

hnn_checkstep :: ICExp o -> EE (MyMB (HNExp o, Bool) o)
hnn_checkstep e =
 mbcase (hnb e CALNil) $ \hne ->
  mbcase (iotastep True hne) $ \res -> case res of
   Right _ -> mbret (hne, False)
   Left (e, as) ->
    mbcase (hnn' e as) $ \(hne, _) -> mbret (hne, True)


hnn' :: ICExp o -> ICArgList o -> EE (MyMB (HNExp o, HNNBlks o) o)
hnn' e as =
 mbcase (hnb e as) $ \hne ->
  mbcase (iotastep True hne) $ \res -> case res of
   Right blks -> mbret (hne, blks)
   Left (e, as) -> hnn' e as

hnb :: ICExp o -> ICArgList o -> EE (MyMB (HNExp o) o)
hnb e as = mbcase (hnc False e as []) $ \res -> case res of
            HNDone _ hne -> mbret hne
            HNMeta{} -> __IMPOSSIBLE__

data HNRes o = HNDone (Maybe (Metavar (Exp o) (RefInfo o))) (HNExp o)
             | HNMeta (ICExp o) (ICArgList o) [Maybe (UId o)]

hnc :: Bool -> ICExp o -> ICArgList o -> [Maybe (UId o)] -> EE (MyMB (HNRes o) o)
hnc haltmeta = loop
 where
  loop ce@(Clos cl e) cargs seenuids =
   (if haltmeta then mmmcase e (mbret $ HNMeta ce cargs seenuids) else mmcase e) $
   \ee -> case ee of
    App uid okh elr args ->
     let ncargs = CALConcat (Clos cl args) cargs
     in case elr of
      Var v -> case doclos cl v of
       Left v' -> mbret $ HNDone expmeta (HNApp (uid : seenuids) (Var v') ncargs)
       Right f -> loop f ncargs (uid : seenuids)
      Const _ -> mbret $ HNDone expmeta (HNApp (uid : seenuids) elr ncargs)
    Lam hid (Abs id b) ->
     mbcase (hnarglist cargs) $ \hncargs -> case hncargs of
      HNALNil -> mbret $ HNDone expmeta (HNLam seenuids hid (Abs id (Clos (Skip : cl) b)))
      HNALCons _ arg cargs' -> loop (Clos (Sub arg : cl) b) cargs' seenuids

      HNALConPar{} -> __IMPOSSIBLE__

    Pi uid hid possdep it (Abs id ot) -> checkNoArgs cargs $ mbret $ HNDone expmeta (HNPi (uid : seenuids) hid possdep (Clos cl it) (Abs id (Clos (Skip : cl) ot)))
    Sort s -> checkNoArgs cargs $ mbret $ HNDone expmeta (HNSort s)

    AbsurdLambda{} -> mbfailed "hnc: encountered absurdlambda"


   where expmeta = case e of {Meta m -> Just m; NotM _ -> Nothing}
  checkNoArgs cargs c =
   mbcase (hnarglist cargs) $ \hncargs -> case hncargs of
    HNALNil -> c
    HNALCons{} -> mbfailed "hnc: there should be no args"

    HNALConPar{} -> __IMPOSSIBLE__


hnarglist :: ICArgList o -> EE (MyMB (HNArgList o) o)
hnarglist args =
 case args of
  CALNil -> mbret HNALNil
  CALConcat (Clos cl args) args2 ->
   mmcase args $ \args -> case args of
    ALNil -> hnarglist args2
    ALCons hid arg argsb -> mbret $ HNALCons hid (Clos cl arg) (CALConcat (Clos cl argsb) args2)

    ALProj{} -> mbret HNALNil -- dirty hack to make check of no-iota in term work


    ALConPar args -> mbret $ HNALConPar (CALConcat (Clos cl args) args2)
-- -----------------------------

getNArgs :: Nat -> ICArgList o -> EE (MyMB (Maybe ([ICExp o], ICArgList o)) o)
getNArgs 0 args = mbret $ Just ([], args)
getNArgs narg args =
 mbcase (hnarglist args) $ \hnargs -> case hnargs of
  HNALNil -> mbret Nothing
  HNALCons _ arg args' ->
   mbcase (getNArgs (narg - 1) args') $ \res -> case res of
    Nothing -> mbret Nothing
    Just (pargs, rargs) -> mbret $ Just (arg : pargs, rargs)

  HNALConPar{} -> __IMPOSSIBLE__


getAllArgs :: ICArgList o -> EE (MyMB [ICExp o] o)
getAllArgs args =
 mbcase (hnarglist args) $ \hnargs -> case hnargs of
  HNALNil -> mbret []
  HNALCons _ arg args' ->
   mbcase (getAllArgs args') $ \args'' ->
    mbret (arg : args'')

  HNALConPar args2 ->
   mbcase (getAllArgs args2) $ \args3 -> mbret (__IMPOSSIBLE__ : args3)


data PEval o = PENo (ICExp o)
             | PEConApp (ICExp o) (ConstRef o) [PEval o]

iotastep :: Bool -> HNExp o -> EE (MyMB (Either (ICExp o, ICArgList o) (HNNBlks o)) o)
iotastep smartcheck e = case e of
 HNApp _ (Const c) args -> do
  cd <- readIORef c
  case cdcont cd of
   Def narg cls _ _ ->
    mbcase (getNArgs narg args) $ \res -> case res of
     Nothing -> mbret (Right noblks)
     Just (pargs, rargs) ->
      mbcase (dorules cls (map PENo pargs)) $ \res -> case res of
       Right blks -> mbret (Right blks)
       Left rhs -> mbret $ Left (rhs, rargs)
   _ -> mbret $ Right noblks
 _ -> mbret $ Right noblks
 where
 dorules :: [Clause o] -> [PEval o] -> EE (MyMB (Either (ICExp o) (HNNBlks o)) o)
 dorules [] _ = mbret $ Right noblks
 dorules (rule:rules') as =
  mbcase (dorule rule as) $ \x -> case x of
   Left (Left as') -> dorules rules' as'
   Left (Right blks) -> mbret (Right blks)
   Right rhs -> mbret $ Left rhs

 dorule :: Clause o -> [PEval o] -> EE (MyMB (Either (Either [PEval o] (HNNBlks o)) (ICExp o)) o)
 dorule (pats, rhs) as =
  mbcase (dopats pats as) $ \x -> case x of
   Right (_, ss) -> mbret $ Right (Clos (map Sub ss) rhs)
   Left hnas -> mbret $ Left hnas

 dopats :: [Pat o] -> [PEval o] -> EE (MyMB (Either (Either [PEval o] (HNNBlks o)) ([PEval o], [ICExp o])) o)
 dopats [] [] = mbret $ Right ([], [])
 dopats (p:ps') (a:as') =
  mbcase (dopat p a) $ \x -> case x of
   Right (hna, ss) ->
    mbcase (dopats ps' as') $ \x -> case x of
     Right (hnas, ss2) -> mbret $ Right (hna : hnas, ss2 ++ ss)
     Left (Right blks) -> mbret $ Left (Right blks)
     Left (Left hnas) -> mbret $ Left $ Left (hna : hnas)
   Left (Right blks) -> mbret $ Left (Right blks)
   Left (Left hna) -> mbret $ Left $ Left (hna : as')
 dopats _ _ = __IMPOSSIBLE__

 dopat :: Pat o -> PEval o -> EE (MyMB (Either (Either (PEval o) (HNNBlks o)) (PEval o, [ICExp o])) o)
 dopat (PatConApp c pas) a =
  case a of
   PENo a ->
    if smartcheck then
     mbcase (meta_not_constructor a) $ \notcon -> if notcon then mbret $ Left $ Right noblks else qq -- to know more often if iota step is possible
    else
     qq
    where
     qq =
      mbcase (hnn_blks a) $ \(hna, blks) -> case hna of
       HNApp _ (Const c') as ->
        if c == c' then
         mbcase (getAllArgs as) $ \as' ->
          if length as' == length pas then
           mbcase (dopats pas (map PENo as')) $ \x -> case x of
            Right (hnas, ss) -> mbret $ Right (PEConApp a c' hnas, ss)
            Left (Right blks) -> mbret $ Left (Right blks)
            Left (Left hnas) -> mbret $ Left $ Left (PEConApp a c' hnas)
          else
           mbfailed "dopat: wrong amount of args"
        else do
         cd <- readIORef c'
         case cdcont cd of
          Constructor{} -> mbcase (getAllArgs as) $ \as' ->
           mbret $ Left (Left (PEConApp a c' (map PENo as')))
          _ -> mbret $ Left (Right (addblk hna blks))
       _ -> mbret $ Left (Right (addblk hna blks))
   aa@(PEConApp a c' as) ->
    if c == c' then
     if length as == length pas then
      mbcase (dopats pas as) $ \x -> case x of
       Right (hnas, ss) -> mbret $ Right (PEConApp a c' hnas, ss)
       Left (Right blks) -> mbret $ Left (Right blks)
       Left (Left hnas) -> mbret $ Left $ Left (PEConApp a c' hnas)
     else
      mbfailed "dopat: wrong amount of args"
    else
     mbret $ Left (Left aa)
 dopat PatVar{} a@(PENo a') = mbret $ Right (a, [a'])
 dopat PatVar{} a@(PEConApp a' _ _) = mbret $ Right (a, [a'])
 dopat PatExp a = mbret $ Right (a, [])

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

noiotastep :: HNExp o -> EE (MyPB o)
noiotastep hne =
 mbpcase prioNoIota Nothing (iotastep False hne) $ \res -> case res of
  Left _ -> mpret $ Error "iota step possible contrary to assumed"
  Right _ -> mpret OK

noiotastep_term :: ConstRef o -> MArgList o -> EE (MyPB o)
noiotastep_term c args = f (HNApp [] (Const c) (CALConcat (Clos [] args) CALNil))
 where
  f hne@(HNApp _ (Const c) _) = do
   cd <- readIORef c
   let isshorthand =
        case cdcont cd of
         Def _ [(pats, _)] _ _ -> True -- all (\pat -> case pat of {PatConApp{} -> False; _ -> True}) pats
         _ -> False
   if isshorthand then
     mpret OK
    else
     noiotastep hne
  f _ = __IMPOSSIBLE__

data CMode o = CMRigid (Maybe (Metavar (Exp o) (RefInfo o))) (HNExp o)
             | forall b . Refinable b (RefInfo o) => CMFlex (MM b (RefInfo o)) (CMFlex o)
data CMFlex o = CMFFlex (ICExp o) (ICArgList o) [Maybe (UId o)]
              | CMFSemi (Maybe (Metavar (Exp o) (RefInfo o))) (HNExp o)
              | CMFBlocked (Maybe (Metavar (Exp o) (RefInfo o))) (HNExp o)

comp' :: forall o . Bool -> CExp o -> CExp o -> EE (MyPB o)
comp' ineq lhs@(TrBr trs1 e1) rhs@(TrBr trs2 e2) = comp ineq e1 e2
 where
  comp :: Bool -> ICExp o -> ICExp o -> EE (MyPB o)
  comp ineq e1 e2 =
   proc e1 e2

   where
    proc e1 e2 = f True e1 CALNil [] $ \res1 -> f True e2 CALNil [] $ \res2 -> g res1 res2
    f semifok e as seenuids cont =
     mbpcase prioCompBeta Nothing (hnc True e as seenuids) $ \res ->
      case res of
       HNDone mexpmeta hne -> fhn semifok mexpmeta hne cont

       HNMeta ce@(Clos cl m) cargs seenuids -> do
        b1 <- boringClos cl
        b2 <- boringArgs cargs
        if b1 && b2 then
          cont $ CMFlex m (CMFFlex ce cargs seenuids)
         else
          mbpcase prioCompBetaStructured Nothing (hnc False ce cargs seenuids) $ \res ->
           case res of
            HNDone mexpmeta hne -> cont $ CMFlex m (CMFBlocked mexpmeta hne)
            HNMeta{} -> __IMPOSSIBLE__


    fhn semifok mexpmeta hne cont =
     mmbpcase (iotastep True hne)
      (\m -> do
        sf <- return False {- semiflex hne -}
        if semifok && sf then
          cont (CMFlex m (CMFSemi mexpmeta hne))
         else
          cont (CMFlex m (CMFBlocked mexpmeta hne))
      )
      (\res -> case res of
        Right _ -> cont (CMRigid mexpmeta hne)
        Left (e, as) -> f semifok e as [] cont
      )
    g res1 res2 =
     case (res1, res2) of
      (CMRigid mexpmeta1 hne1, CMRigid mexpmeta2 hne2) -> comphn ineq mexpmeta1 hne1 mexpmeta2 hne2
      (CMFlex m1 (CMFBlocked mexpmeta1 hne1), _) -> mstp False mexpmeta1 hne1 $ \res1 -> g res1 res2
      (_, CMFlex m2 (CMFBlocked mexpmeta2 hne2)) -> mstp False mexpmeta2 hne2 $ \res2 -> g res1 res2
      (CMRigid mexpmeta1 hne1, CMFlex _ fl2) -> unif True mexpmeta1 hne1 fl2
      (CMFlex _ fl1, CMRigid mexpmeta2 hne2) -> unif False mexpmeta2 hne2 fl1


      (CMFlex m1 fl1, CMFlex m2 fl2) -> doubleblock m1 m2 $ fcm fl1 $ \res1 -> fcm fl2 $ \res2 -> g res1 res2
    fcm (CMFFlex ce cargs seenuids) = f True ce cargs seenuids
    fcm (CMFSemi mexpmeta hne) = fhn True mexpmeta hne
    fcm (CMFBlocked _ hne) = __IMPOSSIBLE__ -- not used. if so should be: fhn False hne
    mstp semif mexpmeta hne cont =
     mpret $ Or prioCompChoice
      (mpret $ And (Just [Term lhs, Term rhs])
       (noiotastep hne)
       (cont (CMRigid mexpmeta hne))
      )
      (stp semif hne cont)
    stp semif hne cont =
     mbpcase prioCompIota (Just $ RIIotaStep semif) (iotastep True hne) $ \res -> case res of
      Right _ -> mpret $ Error "no iota step possible, contrary to assumed"
      Left (e, as) -> f semif e as [] cont
    unif oppis1 oppmexpmeta opphne res =
     let iter res = if oppis1 then
                     g (CMRigid oppmexpmeta opphne) res
                    else
                     g res (CMRigid oppmexpmeta opphne)
     in case res of
      CMFFlex ce cargs seenuids -> do
       poss <- iotapossmeta ce cargs
       maybeor poss prioCompChoice
        (loop ce cargs seenuids)
-- (mbpcase prioCompBeta (Just $ RIIotaStep False) (hnb ce cargs) $ \hne ->
        (mbpcase prioCompBeta (Just $ RIIotaStep False) (hnc False ce cargs seenuids) $ \res ->
          -- RIIotaStep here on beta-norm to make cost high when guessing elim const in type par
          case res of
           HNDone mexpmeta hne -> stp False hne iter
           HNMeta{} -> __IMPOSSIBLE__
        )
       where
        loop ce@(Clos cl m) cargs seenuids =
         mmpcase (False, prioCompUnif, Just (RIUnifInfo cl opphne)) m $ \_ ->
         mbpcase prioCompBeta Nothing (hnc True ce cargs seenuids) $ \res -> case res of
          HNDone mexpmeta hne ->
           mpret $ And (Just [Term lhs, Term rhs])
            (noiotastep hne)
            (iter (CMRigid mexpmeta hne))
          HNMeta ce cargs seenuids -> loop ce cargs seenuids
      CMFSemi _ hne ->
       __IMPOSSIBLE__ -- CMFSemi disabled, if used should be: stp True hne iter
      CMFBlocked{} -> __IMPOSSIBLE__
    comphn :: Bool -> Maybe (Metavar (Exp o) (RefInfo o)) -> HNExp o -> Maybe (Metavar (Exp o) (RefInfo o)) -> HNExp o -> EE (MyPB o)
    comphn ineq mexpmeta1 hne1 mexpmeta2 hne2 =
     case (hne1, hne2) of
      (HNApp _ elr1 args1, HNApp _ elr2 args2) ->
       let ce = case (elr1, elr2) of
                 (Var v1, Var v2) -> if v1 == v2 then Nothing else Just "comphn, elr, vars not equal"
                 (Const c1, Const c2) -> if c1 == c2 then Nothing else Just "comphn, elr, consts not equal"
                 (_, _) -> Just "comphn, elrs not equal"
       in case ce of
            Nothing -> compargs args1 args2
            Just msg -> mpret $ Error msg
      (HNLam _ hid1 (Abs id1 b1), HNLam _ hid2 (Abs id2 b2)) -> comp False b1 b2
      (HNLam seenuids _ (Abs _ b1), HNApp uid2 elr2 args2) ->
       f True b1 CALNil seenuids $ \res1 -> fhn True mexpmeta2 (HNApp uid2 (weakelr 1 elr2) (addtrailingargs (Clos [] $ NotM $ ALCons NotHidden{- arbitrary -} (NotM $ App Nothing (NotM OKVal) (Var 0) (NotM ALNil)) (NotM ALNil)) (weakarglist 1 args2))) $ \res2 -> g res1 res2
      (HNApp uid1 elr1 args1, HNLam seenuids _ (Abs _ b2)) ->
       fhn True mexpmeta1 (HNApp uid1 (weakelr 1 elr1) (addtrailingargs (Clos [] $ NotM $ ALCons NotHidden{- arbitrary -} (NotM $ App Nothing (NotM OKVal) (Var 0) (NotM ALNil)) (NotM ALNil)) (weakarglist 1 args1))) $ \res1 -> f True b2 CALNil seenuids $ \res2 -> g res1 res2
{-
      (HNLam _ (Abs _ b1), HNApp uid2 elr2 args2) ->
       f True b1 CALNil $ \res1 -> g res1
        (CMRigid mexpmeta2 (HNApp uid2 (weakelr 1 elr2) (addtrailingargs (Clos [] $ NotM $ ALCons NotHidden{- arbitrary -} (NotM $ App Nothing (NotM OKVal) (Var 0) (NotM ALNil)) (NotM ALNil)) (weakarglist 1 args2))))
      (HNApp uid1 elr1 args1, HNLam _ (Abs _ b2)) ->
       f True b2 CALNil $ \res2 -> g
        (CMRigid mexpmeta1 (HNApp uid1 (weakelr 1 elr1) (addtrailingargs (Clos [] $ NotM $ ALCons NotHidden{- arbitrary -} (NotM $ App Nothing (NotM OKVal) (Var 0) (NotM ALNil)) (NotM ALNil)) (weakarglist 1 args1))))
        res2
-}
      (HNPi _ hid1 _ it1 (Abs id1 ot1), HNPi _ hid2 _ it2 (Abs id2 ot2)) -> mpret $
       And (Just [Term trs1, Term trs2]) (comp False it1 it2) (comp ineq ot1 ot2)
      (HNSort s1, HNSort s2) -> mpret $
       case (s1, s2) of
        (Set i1, Set i2) -> if i1 == i2 || ineq && i1 > i2 then OK else Error "comphn, set levels not matching"
        (Set _, UnknownSort) -> OK
        (UnknownSort, Set _) -> OK
        (UnknownSort, UnknownSort) -> OK
        (Type, Set _) | ineq -> OK
        (Type, UnknownSort) | ineq -> OK
        _ -> __IMPOSSIBLE__
      (HNApp uid1 (Const c1) _, _) -> case mexpmeta2 of
       Nothing -> mpret $ Error "comphn, not equal (2)"
       Just m2 -> mpret $ AddExtraRef "comphn: not equal, adding extra ref" m2 (extraref m2 uid1 c1)
      (_, HNApp uid2 (Const c2) _) -> case mexpmeta1 of
       Nothing -> mpret $ Error "comphn, not equal (3)"
       Just m1 -> mpret $ AddExtraRef "comphn: not equal, adding extra ref" m1 (extraref m1 uid2 c2)
      (_, _) -> mpret $ Error "comphn, not equal"

    compargs :: ICArgList o -> ICArgList o -> EE (MyPB o)
    compargs args1 args2 =
     mbpcase prioCompareArgList Nothing (hnarglist args1) $ \hnargs1 ->
     mbpcase prioCompareArgList Nothing (hnarglist args2) $ \hnargs2 ->
     case (hnargs1, hnargs2) of
      (HNALNil, HNALNil) -> mpret OK
      (HNALCons hid1 arg1 args1b, HNALCons hid2 arg2 args2b) -> mpret $
       And (Just [Term trs1, Term trs2]) (comp False arg1 arg2) (compargs args1b args2b)

      (HNALConPar args1b, HNALCons _ _ args2b) -> compargs args1b args2b
      (HNALCons _ _ args1b, HNALConPar args2b) -> compargs args1b args2b
      (HNALConPar args1', HNALConPar args2') -> compargs args1' args2'

      (_, _) -> mpret $ Error $ "comphnargs, not equal"


    boringExp :: ICExp o -> EE Bool
    boringExp (Clos cl e) = do
     e <- expandbind e
     case e of
      Meta{} -> boringClos cl
      NotM e -> case e of
       App _ _ (Var v) as -> do
        as <- expandbind as
        case as of
         Meta{} -> return False
         NotM as -> case as of
          ALNil -> case doclos cl v of
           Left _ -> return True
           Right e -> boringExp e
          ALCons{} -> return False

          ALProj{} -> __IMPOSSIBLE__


          ALConPar{} -> return False

       _ -> return False

    boringClos :: [CAction o] -> EE Bool
    boringClos cl = liftM (all id) $ mapM f cl
     where f (Sub e) = boringExp e
           f Skip = return True
           f (Weak _) = return True

    boringArgs :: ICArgList o -> EE Bool
    boringArgs CALNil = return True
    boringArgs (CALConcat (Clos cl as) as2) = do
     b1 <- f cl as
     b2 <- boringArgs as2
     return $ b1 && b2
     where
      f cl as = do
       as <- expandbind as
       case as of
        Meta{} -> return False
        NotM as -> case as of
         ALNil -> return True
         ALCons _ a as -> do
          b1 <- boringExp (Clos cl a)
          b2 <- f cl as
          return $ b1 && b2

         ALProj{} -> __IMPOSSIBLE__


         ALConPar as -> f cl as
-- ---------------------------------

checkeliminand :: MExp o -> EE (MyPB o)
checkeliminand = f [] []
 where
  f uids used e =
   mmpcase (False, prioNo, Just (RIUsedVars uids used)) e $ \e -> case e of
    App uid _ elr@(Var{}) args -> fs (adduid uid uids) (elr : used) args
    App uid _ elr@(Const c) args -> do
     cd <- readIORef c
     case cdcont cd of
      Def _ _ (Just i) _ -> mpret $ Sidecondition (fs (adduid uid uids) (elr : used) args) (g i args)
       where
        g i as = mmpcase (False, prioNo, Nothing) as $ \as -> case as of
                  ALNil -> mpret OK
                  ALCons _ a as -> case i of
                   0 -> mmpcase (False, prioNo, Just RINotConstructor) a $ \_ ->
                         mpret OK
                   _ -> g (i - 1) as

                  ALProj eas _ _ as -> mpret OK


                  ALConPar as -> case i of
                   0 -> __IMPOSSIBLE__
                   _ -> g (i - 1) as

      _ -> fs (adduid uid uids) (elr : used) args
    Lam _ (Abs _ e) -> f uids (w used) e
    Pi uid _ _ e1 (Abs _ e2) -> mpret $ Sidecondition (f (adduid uid uids) used e1) (f (adduid uid uids) (w used) e2)
    Sort _ -> mpret OK

    AbsurdLambda{} -> mpret OK


  fs uids used as =
   mmpcase (False, prioNo, Nothing) as $ \as -> case as of
    ALNil -> mpret OK
    ALCons _ a as -> mpret $ Sidecondition (f uids used a) (fs uids used as)

    ALProj eas _ _ as -> mpret $ Sidecondition (fs uids used eas) (fs uids used as)


    ALConPar as -> fs uids used as

  w = map (\x -> case x of {Var v -> Var (v + 1); Const{} -> x})
  adduid (Just uid) uids = uid : uids
  adduid Nothing uids = uids

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


maybeor _ _ mainalt _ = mainalt

iotapossmeta :: ICExp o -> ICArgList o -> EE Bool
iotapossmeta ce@(Clos cl _) cargs = do
 xs <- mapM ncaction cl
 y <- nccargs cargs
 return $ not (all id xs && y)
 where
  ncaction (Sub ce) = nonconstructor ce
  ncaction Skip = return True
  ncaction (Weak{}) = return True
  nccargs CALNil = return True
  nccargs (CALConcat (Clos cl margs) cargs) = do
   x <- ncmargs cl margs
   y <- nccargs cargs
   return $ x && y
  ncmargs cl (Meta m) = do
   mb <- readIORef (mbind m)
   case mb of
    Nothing -> return False
    Just x -> ncargs cl x
  ncmargs cl (NotM args) = ncargs cl args
  ncargs cl ALNil = return True
  ncargs cl (ALCons _ a args) = do
   x <- nonconstructor (Clos cl a)
   y <- ncmargs cl args
   return $ x && y

  ncargs _ (ALProj{}) = __IMPOSSIBLE__


  ncargs cl (ALConPar args) = ncmargs cl args

  nonconstructor :: ICExp o -> EE Bool
  nonconstructor ce = do
   res <- hnc True ce CALNil []
   case res of
    Blocked{} -> return False
    Failed{} -> return False
    NotB res -> case res of
     HNMeta ce _ _ -> do
      let (Clos _ (Meta m)) = ce
      infos <- extractblkinfos m
      if any (\info -> case info of {RINotConstructor -> True; _ -> False}) infos then do
        return True
       else
        return False
      -- return False -- return True -- ?? removes completeness - Yes, in DavidW1.additionRight
     HNDone{} -> do
      res <- hnn ce
      case res of
       NotB hne -> case hne of
        HNApp _ (Const c) _ -> do
         cd <- readIORef c
         case cdcont cd of
          Constructor{} -> return False
          _ -> return True
        _ -> return True
       Blocked m _ -> return False -- not necessary to do check here because already done by hnn (!! if it's known that m stands for an eliminator then it cannot be constructor so True instead)
       Failed _ -> return False

meta_not_constructor :: ICExp o -> EE (MB Bool (RefInfo o))
meta_not_constructor a =
 mbcase (hnc True a CALNil []) $ \res -> case res of
  HNMeta ce _ _ -> do
    let (Clos _ (Meta m)) = ce
    infos <- extractblkinfos m
    if any (\info -> case info of {RINotConstructor -> True; _ -> False}) infos then do
      b <- iotapossmeta ce CALNil
      mbret $ not b
     else
      mbret False
  HNDone{} -> mbret False

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

calcEqRState :: EqReasoningConsts o -> MExp o -> EE (MyPB o)
calcEqRState cs = f EqRSNone
 where
  f s e =
   mmpcase (False, prioNo, Just (RIEqRState s)) e $ \e -> case e of
    App _ _ (Const c) args -> case () of
     _ | c == eqrcBegin cs -> fs [EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSChain] args
     _ | c == eqrcStep cs -> fs [EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSPrf1, EqRSChain] args
     _ | c == eqrcSym cs -> fs [EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSPrf2] args
     _ | c == eqrcCong cs -> fs [EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSNone, EqRSPrf3] args
     _ -> fs [] args
    App _ _ (Var{}) args -> fs [] args
    Lam _ (Abs _ b) -> f EqRSNone b
    Pi _ _ _ it (Abs _ ot) -> mpret $ Sidecondition (f EqRSNone it) (f EqRSNone ot)
    Sort{} -> mpret OK

    AbsurdLambda{} -> mpret OK


  fs ss args =
   mmpcase (False, prioNo, Nothing) args $ \args -> case (ss, args) of
    (_, ALNil) -> mpret OK
    (s : ss, ALCons _ a args) -> mpret $ Sidecondition (f s a) (fs ss args)
    ([], ALCons _ a args) -> mpret $ Sidecondition (f EqRSNone a) (fs [] args)

    (_, ALProj eas _ _ as) -> mpret $ Sidecondition (fs [] eas) (fs [] as) -- when eqr-hint is given manually, ss can be non-empty here


    (_ : ss, ALConPar args) -> fs ss args
    ([], ALConPar args) -> fs [] args


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

pickid :: MId -> MId -> MId
pickid mid1@(Id _) _ = mid1
pickid _ mid2 = mid2

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

tcSearch :: Bool -> Ctx o -> CExp o -> MExp o -> EE (MyPB o)
tcSearch isdep ctx typ trm = mpret $ Sidecondition (checkeliminand trm)
                              (tcExp isdep ctx typ trm)

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