/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Meta.Tactic.Assert
import Lean.Meta.Match.CaseValues

namespace Lean.Meta

structure CaseArraySizesSubgoal where
  mvarId : MVarId
  elems  : Array FVarId := #[]
  diseqs : Array FVarId := #[]
  subst  : FVarSubst := {}

instance : Inhabited CaseArraySizesSubgoal :=
  ⟨{ mvarId := arbitrary }⟩

def getArrayArgType (a : Expr) : MetaM Expr := do
  let aType ← inferType a
  let aType ← whnfD aType
  unless aType.isAppOfArity `Array 1 do
    throwError "array expected{indentExpr a}"
  pure aType.appArg!

private def mkArrayGetLit (a : Expr) (i : Nat) (n : Nat) (h : Expr) : MetaM Expr := do
  let lt    ← mkLt (mkNatLit i) (mkNatLit n)
  let ltPrf ← mkDecideProof lt
  mkAppM `Array.getLit #[a, mkNatLit i, h, ltPrf]

private partial def introArrayLit (mvarId : MVarId) (a : Expr) (n : Nat) (xNamePrefix : Name) (aSizeEqN : Expr) : MetaM MVarId := do
  let α ← getArrayArgType a
  let rec loop (i : Nat) (xs : Array Expr) (args : Array Expr) := do
    if i < n then
      withLocalDeclD (xNamePrefix.appendIndexAfter (i+1)) α fun xi => do
        let xs := xs.push xi
        let ai ← mkArrayGetLit a i n aSizeEqN
        let args := args.push ai
        loop (i+1) xs args
    else
      let xsLit     ← mkArrayLit α xs.toList
      let aEqXsLit  ← mkEq a xsLit
      let aEqLitPrf ← mkAppM `Array.toArrayLitEq #[a, mkNatLit n, aSizeEqN]
      withLocalDeclD `hEqALit aEqXsLit fun heq => do
        let target    ← getMVarType mvarId
        let newTarget ← mkForallFVars (xs.push heq) target
        pure (newTarget, args.push aEqLitPrf)
  let (newTarget, args) ← loop 0 #[] #[]
  let tag ← getMVarTag mvarId
  let newMVar   ← mkFreshExprSyntheticOpaqueMVar newTarget tag
  assignExprMVar mvarId (mkAppN newMVar args)
  pure newMVar.mvarId!

/--
  Split goal `... |- C a` into sizes.size + 1 subgoals
  1) `..., x_1 ... x_{sizes[0]} |- C #[x_1, ... x_{sizes[0]}]`
  ...
  n) `..., x_1 ... x_{sizes[n-1]}  |- C #[x_1, ..., x_{sizes[n-1]}]`
  n+1) `..., (h_1 : a.size != sizes[0]), ..., (h_n : a.size != sizes[n-1]) |- C a`
  where `n = sizes.size` -/
def caseArraySizes (mvarId : MVarId) (fvarId : FVarId) (sizes : Array Nat) (xNamePrefix := `x) (hNamePrefix := `h) : MetaM (Array CaseArraySizesSubgoal) :=
  withMVarContext mvarId do
    let a := mkFVar fvarId
    let α ← getArrayArgType a
    let aSize ← mkAppM `Array.size #[a]
    let mvarId ← assertExt mvarId `aSize (mkConst `Nat) aSize
    let (aSizeFVarId, mvarId) ← intro1 mvarId
    let (hEq, mvarId) ← intro1 mvarId
    let subgoals ← caseValues mvarId aSizeFVarId (sizes.map mkNatLit) hNamePrefix
    subgoals.mapIdxM fun i subgoal => do
      let subst  := subgoal.subst
      let mvarId := subgoal.mvarId
      let hEqSz  := (subst.get hEq).fvarId!
      if h : i.val < sizes.size then
         let n := sizes.get ⟨i, h⟩
         let mvarId ← clear mvarId subgoal.newHs[0]
         let mvarId ← clear mvarId (subst.get aSizeFVarId).fvarId!
         withMVarContext mvarId do
           let hEqSzSymm ← mkEqSymm (mkFVar hEqSz)
           let mvarId ← introArrayLit mvarId a n xNamePrefix hEqSzSymm
           let (xs, mvarId)  ← introN mvarId n
           let (hEqLit, mvarId) ← intro1 mvarId
           let mvarId ← clear mvarId hEqSz
           let (subst, mvarId) ← substCore mvarId hEqLit false subst
           pure { mvarId := mvarId, elems := xs, subst := subst }
      else
         let (subst, mvarId) ← substCore mvarId hEq false subst
         let diseqs := subgoal.newHs.map fun fvarId => (subst.get fvarId).fvarId!
         pure { mvarId := mvarId, diseqs := diseqs, subst := subst }

end Lean.Meta
