import Lean.Meta.Tactic
import Lean.Meta.Sym

open Lean Meta Sym
def profileM {α : Type} (k : MetaM α) (msg : String := "experiment") : MetaM α :=
  profileitM Exception msg (Options.empty.set `profiler true |>.set `profiler.threshold 0)  k

def genTerm (n : Nat) : Expr := Id.run do
  let mut e := mkConst ``True
  let nat := mkConst ``Nat
  for _ in 0...n do
    let eq := mkApp3 (mkConst ``Eq [1]) nat (mkBVar 0) (mkNatAdd (mkBVar 2) (mkBVar 1))
    e := mkApp2 (mkConst ``And) eq e
    e := mkApp2 (mkConst ``Exists [1]) nat (mkLambda `y .default nat e)
    e := mkForall `x .default nat e
    e := mkLet `z nat (mkNatAdd (mkBVar 1) (mkBVar 0)) e
  let eq := mkApp3 (mkConst ``Eq [1]) nat (mkBVar 0) (mkNatAdd (mkBVar 2) (mkBVar 1))
  e := mkApp2 (mkConst ``And) eq e
  e := mkApp2 (mkConst ``Exists [1]) nat (mkLambda `y .default nat e)
  e := mkForall `x .default nat e
  e := mkLet `z nat (mkNatLit 0) e
  return e

set_option maxRecDepth 10000000

def tryIntros? (goals : List MVarId) : SymM (Option (List MVarId)) := do
  try
    let goal :: goals := goals | return none
    let .goal _ goal' ← intros goal | failure
    return some (goal' :: goals)
  catch _ =>
    return none

def tryApply? (rule : BackwardRule) (goals : List MVarId) : SymM (Option (List MVarId)) := do
  let goal :: goals := goals | return none
  let .goals goals' ← rule.apply goal | return none
  return some (goals' ++ goals)

def tryApplyAny? (rules : List BackwardRule) (goals : List MVarId) : SymM (Option (List MVarId)) := do
  match rules with
  | [] => return none
  | rule :: rules =>
    if let some goals' ← tryApply? rule goals then
      return some goals'
    else
      tryApplyAny? rules goals

def solve (n : Nat) (type : Expr) : MetaM Unit := profileM (msg := s!"size {n}") <| SymM.run do
  let mvarId := (← mkFreshExprMVar type).mvarId!
  let rules ← [``Exists.intro, ``And.intro, ``Eq.refl, ``True.intro].mapM fun declName => mkBackwardRuleFromDecl declName
  let goal ← preprocessMVar mvarId
  discard <| go 10000000 rules [goal]
  return ()
where
  go (fuel : Nat) (rules : List BackwardRule) (goals : List MVarId) : SymM Bool := do
    let fuel + 1 := fuel | throwError "out of fuel"
    let goal :: goals' := goals | return true
    if (← goal.isAssigned) then
      go fuel rules goals'
    else
      if let some goals' ← tryIntros? goals then
        go fuel rules goals'
      else if let some goals' ← tryApplyAny? rules goals then
        go fuel rules goals'
      else
        throwError "Stuck at {goal}"

def test (n : Nat) : MetaM Unit := do
  let e := genTerm n
  solve n e

-- We are solving problems of the following form
#eval logInfo (genTerm 2)

#eval test 1000
#eval test 2000
#eval test 3000
#eval test 4000
#eval test 5000
#eval test 6000
