-- CaseStudies/Velvet/VelvetExamples/SpMSpV_Example.lean

import Auto

import Lean

import Mathlib.Algebra.BigOperators.Intervals

import Mathlib.Algebra.Ring.Int.Defs

import Loom.MonadAlgebras.NonDetT.Extract

import Loom.MonadAlgebras.WP.Tactic

import Loom.MonadAlgebras.WP.DoNames'

import CaseStudies.Velvet.Std

section SpMV

structure SpV (valTyp : Type) where
  ind: Array Nat
  val: Array valTyp
  size: ℕ
  size_eq: ind.size = size ∧ val.size = size
  inc: ∀ (i j: Nat), i < size → j < size →  i < j → ind[i]! < ind[j]!

def spv_dot (spv1 spv2: SpV Int) (pnt1 pnt2: ℕ): Int :=
  if (spv1.size) ≤ pnt1 ∨ (spv2.size) ≤ pnt2 then
    0
  else
    if (spv1.ind)[pnt1]! = (spv2.ind)[pnt2]! then
      (spv1.val)[pnt1]! * (spv2.val)[pnt2]! + spv_dot spv1 spv2 (pnt1 + 1) (pnt2 + 1)
    else
      if (spv1.ind)[pnt1]! < (spv2.ind)[pnt2]! then
        spv_dot spv1 spv2 (pnt1 + 1) pnt2
      else
        spv_dot spv1 spv2 pnt1 (pnt2 + 1)
  termination_by ((spv1.size) + (spv2.size) - pnt1 - pnt2)


method SpVSpV
  (mut out: Array Int)
  (spv1: SpV Int)
  (spv2: SpV Int) return (u: Unit)
  ensures out.size = 1
  ensures out[0]! = spv_dot spv1 spv2 0 0
  do
    out := Array.replicate 1 0
    let mut pnt1 := 0
    let mut pnt2 := 0
    while pnt1 ≠ spv1.size ∧ pnt2 ≠ spv2.size
    invariant out.size = 1
    invariant pnt1 ≤ spv1.size ∧ pnt2 ≤ spv2.size
    invariant out[0]! + spv_dot spv1 spv2 pnt1 pnt2 = spv_dot spv1 spv2 0 0
    done_with pnt1 = spv1.size ∨ pnt2 = spv2.size
    do
      if (spv1.ind)[pnt1]! = (spv2.ind)[pnt2]! then
        out[0] += (spv1.val)[pnt1]! * (spv2.val)[pnt2]!
        pnt1 := pnt1 + 1
        pnt2 := pnt2 + 1
      else
        if (spv1.ind)[pnt1]! < (spv2.ind)[pnt2]! then
          pnt1 := pnt1 + 1
        else
          pnt2 := pnt2 + 1
    return


method SpMSpV
  (mut out: Array Int)
  (spm: Array (SpV Int))
  (spv: SpV Int) return (u: Unit)
  ensures out.size = spm.size
  ensures ∀ i < spm.size, out[i]! = spv_dot spm[i]! spv 0 0
  do
    out := Array.replicate spm.size 0
    let mut spmInd := Array.replicate spm.size 0
    let mut spvInd := Array.replicate spm.size 0
    while_some i :| i < spm.size ∧ spmInd[i]! < spm[i]!.size ∧ spvInd[i]! < spv.size
    invariant spvInd.size = spm.size
    invariant spmInd.size = spm.size
    invariant out.size = spm.size
    invariant ∀ i < spmInd.size, spmInd[i]! <= spm[i]!.size
    invariant ∀ i < spvInd.size, spvInd[i]! <= spv.size
    invariant ∀ i < spm.size, out[i]! + spv_dot spm[i]! spv spmInd[i]! spvInd[i]! = spv_dot spm[i]! spv 0 0
    done_with ∀ i < spm.size, spmInd[i]! = spm[i]!.size ∨ spvInd[i]! = spv.size
    do
      let ind_m := spmInd[i]!
      let ind_v := spvInd[i]!
      if spm[i]!.ind[ind_m]! = spv.ind[ind_v]! then
        out[i] += spm[i]!.val[ind_m]! * spv.val[ind_v]!
        spmInd[i] += 1
        spvInd[i] += 1
      else
        if spm[i]!.ind[ind_m]! < spv.ind[ind_v]! then
          spmInd[i] += 1
        else
          spvInd[i] += 1
    return

-- Repository definitions used:
content: syntax "method" ident leafny_binder* "return" "(" ident ":" term ")"
  (require_caluse )*
  (ensures_caluse)* "do" doSeq
  Termination.suffix : command

syntax "ensures" termBeforeReqEnsDo : ensures_caluse

syntax "while_some" term ":|" termBeforeDo "do" doSeq : doElem

syntax "while_some" term ":|" term
  (invariantClause)+
  (doneWith)?
  "do" doSeq : doElem

syntax "let" term ":|" term : doElem

syntax "done_with" termBeforeDo : doneWith

syntax "invariant" termBeforeDo linebreak : invariantClause

syntax "while" term
  (invariantClause)*
  (doneWith)?
  (decreasingTerm)?
  "do" doSeq : doElem

syntax "(mut" ident ":" term ")" : leafny_binder

content: macro_rules
  | `(doElem| let $x:term :| $t) => `(doElem| let $x:term <- pickSuchThat _ (fun $x => type_with_name_prefix `choice $t))

content: macro_rules
  | `(doElem| while $t do $seq:doSeq) => do
    let decr <- withRef (<- getRef) `(decreasing none)
    let invs <- withRef (<- getRef) `(invariants [])
    `(doElem|
      for _ in Lean.Loop.mk do
        $invs:term
        onDoneGadget (with_name_prefix `done ¬$t:term)
        $decr:term
        if $t then
          $seq:doSeq
        else break)
  | `(doElem| while $t
              $[invariant $inv:term
              ]*
              $[done_with $inv_done]?
              $[decreasing $measure]?
              do $seq:doSeq) => do
      let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
      let invd_some ← match inv_done with
      | some invd_some => withRef invd_some ``($invd_some)
      | none => ``(¬$t:term)
      match measure with
      | some measure_some =>
        let decr <- withRef measure_some `(decreasing type_with_name_prefix `decreasing $measure_some)
        `(doElem|
          for _ in Lean.Loop.mk do
            $invs:term
            onDoneGadget (with_name_prefix `done $invd_some:term)
            $decr:term
            if $t then
              $seq:doSeq
            else break)
      | none => do
        let decr <- withRef (<- getRef) `(decreasing none)
        let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
        `(doElem|
          for _ in Lean.Loop.mk do
            $invs:term
            onDoneGadget (with_name_prefix `done $invd_some:term)
            $decr:term
            if $t then
              $seq:doSeq
            else break)
  | `(doElem| while_some $x:ident :| $t do $seq:doSeq) =>
    match seq with
    | `(doSeq| $[$seq:doElem]*)
    | `(doSeq| $[$seq:doElem;]*)
    | `(doSeq| { $[$seq:doElem]* }) =>
      `(doElem|
        while ∃ $x:ident, $t do
          let $x :| $t
          $[$seq:doElem]*)
    | _ => Lean.Macro.throwError "while_some expects a sequence of do-elements"
  | `(doElem| while_some $x:ident :| $t
              $[invariant $inv:term
              ]*
              $[done_with $inv_done]? do
                $seq:doSeq) => do
    let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
    let invd_some ← match inv_done with
    | some invd_some => withRef invd_some ``($invd_some)
    | none => ``(¬$t:term)
    match seq with
    | `(doSeq| $[$seq:doElem]*)
    | `(doSeq| $[$seq:doElem;]*)
    | `(doSeq| { $[$seq:doElem]* }) =>
      let decr <- withRef (<- getRef) `(decreasing none)
      `(doElem|
        for _ in Lean.Loop.mk do
          $invs:term
          onDoneGadget (with_name_prefix `done $invd_some:term)
          $decr:term
          if ∃ $x:ident, $t then
            let $x :| $t
            $[$seq:doElem]*
          else break)
    | _ => Lean.Macro.throwError "while_some expects a sequence of do-elements"
  | `(doElem| for $x:ident in $t
            $[invariant $inv:term
            ]*
            do $seq:doSeq) => do
      let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
      match seq with
      | `(doSeq| $[$seq:doElem]*)
      | `(doSeq| $[$seq:doElem;]*)
      | `(doSeq| { $[$seq:doElem]* }) =>
        `(doElem|
          for $x:ident in $t do
            $invs:term
            $[$seq:doElem]*)
      | _ => Lean.Macro.throwError "for expects a sequence of do-elements"

content: macro_rules
  | `(tactic|loom_solver) =>
    `(tactic|(
      try simp at *
      try aesop))

content: macro_rules
  | `(doElem| while $t
              $[invariant $inv:term
              ]*
              $[done_with $inv_done]?
              $[decreasing $measure]?
              do $seq:doSeq) => do
      let balance := mkIdent `balance_name
      let balanceType <- `(term| Bal)
      let inv : Array Term <- inv.mapM fun (inv : Term) => withRef inv ``(fun ($(balance):ident : $balanceType)=> with_name_prefix `inv $inv)
      let invd_some <- match inv_done with
      | some invd_some => withRef invd_some ``(fun ($(balance):ident : $balanceType) => with_name_prefix `done $invd_some)
      | none => ``(fun ($(balance):ident : $balanceType) => with_name_prefix `done ¬$t:term)
      match measure with
      | some measure_some =>
        let measure_some ← withRef measure_some ``(type_with_name_prefix `decreasing ($measure_some:term))
        do
        `(doElem|
          for _ in Lean.Loop.mk do
            invariantGadget [ $[$inv:term],* ]
            onDoneGadget ($invd_some:term)
            decreasingGadget ($measure_some:term)
            if $t then
              $seq:doSeq
            else break)
      | none => do
        `(doElem|
          for _ in Lean.Loop.mk do
            invariantGadget [ $[$inv:term],* ]
            onDoneGadget ($invd_some:term)
            decreasingGadget none
            if $t then
              $seq:doSeq
            else break)

content: macro_rules
| `(doElem|balance_set $t) => do
  let balId := mkIdent `balance
  `(doElem|do
    $balId:ident := $t
    set $balId:ident
    $balId:ident ← get)

content: macro_rules
  | `(doElem|$id:ident[$idx:term] := $val:term) =>
    `(doElem| $id:term := ($id:term).modify $idx (fun _ => $val))
  | `(doElem|$id:ident[$idx:term] += $val:term) =>
    `(doElem| $id:term := ($id:term).modify $idx (· + $val))

-- Repository lemmas used:
content: theorem List.getElem_append_left{α : Type u_1} {l₁ l₂ : List α} {i : Nat} (hn : i < l₁.length) :\n(l₁ ++ l₂)[i] = l₁[i]

-- Local definitions used:
content: structure SpV (valTyp : Type) where
  ind: Array Nat
  val: Array valTyp
  size: ℕ
  size_eq: ind.size = size ∧ val.size = size
  inc: ∀ (i j: Nat), i < size → j < size →  i < j → ind[i]! < ind[j]!

content: def spv_dot (spv1 spv2: SpV Int) (pnt1 pnt2: ℕ): Int :=
  if (spv1.size) ≤ pnt1 ∨ (spv2.size) ≤ pnt2 then
    0
  else
    if (spv1.ind)[pnt1]! = (spv2.ind)[pnt2]! then
      (spv1.val)[pnt1]! * (spv2.val)[pnt2]! + spv_dot spv1 spv2 (pnt1 + 1) (pnt2 + 1)
    else
      if (spv1.ind)[pnt1]! < (spv2.ind)[pnt2]! then
        spv_dot spv1 spv2 (pnt1 + 1) pnt2
      else
        spv_dot spv1 spv2 pnt1 (pnt2 + 1)
  termination_by ((spv1.size) + (spv2.size) - pnt1 - pnt2)


method SpVSpV
  (mut out: Array Int)
  (spv1: SpV Int)
  (spv2: SpV Int) return (u: Unit)
  ensures out.size = 1
  ensures out[0]! = spv_dot spv1 spv2 0 0
  do
    out := Array.replicate 1 0
    let mut pnt1 := 0
    let mut pnt2 := 0
    while pnt1 ≠ spv1.size ∧ pnt2 ≠ spv2.size
    invariant out.size = 1
    invariant pnt1 ≤ spv1.size ∧ pnt2 ≤ spv2.size
    invariant out[0]! + spv_dot spv1 spv2 pnt1 pnt2 = spv_dot spv1 spv2 0 0
    done_with pnt1 = spv1.size ∨ pnt2 = spv2.size
    do
      if (spv1.ind)[pnt1]! = (spv2.ind)[pnt2]! then
        out[0] += (spv1.val)[pnt1]! * (spv2.val)[pnt2]!
        pnt1 := pnt1 + 1
        pnt2 := pnt2 + 1
      else
        if (spv1.ind)[pnt1]! < (spv2.ind)[pnt2]! then
          pnt1 := pnt1 + 1
        else
          pnt2 := pnt2 + 1
    return


method SpMSpV
  (mut out: Array Int)
  (spm: Array (SpV Int))
  (spv: SpV Int) return (u: Unit)
  ensures out.size = spm.size
  ensures ∀ i < spm.size, out[i]! = spv_dot spm[i]! spv 0 0
  do
    out := Array.replicate spm.size 0
    let mut spmInd := Array.replicate spm.size 0
    let mut spvInd := Array.replicate spm.size 0
    while_some i :| i < spm.size ∧ spmInd[i]! < spm[i]!.size ∧ spvInd[i]! < spv.size
    invariant spvInd.size = spm.size
    invariant spmInd.size = spm.size
    invariant out.size = spm.size
    invariant ∀ i < spmInd.size, spmInd[i]! <= spm[i]!.size
    invariant ∀ i < spvInd.size, spvInd[i]! <= spv.size
    invariant ∀ i < spm.size, out[i]! + spv_dot spm[i]! spv spmInd[i]! spvInd[i]! = spv_dot spm[i]! spv 0 0
    done_with ∀ i < spm.size, spmInd[i]! = spm[i]!.size ∨ spvInd[i]! = spv.size
    do
      let ind_m := spmInd[i]!
      let ind_v := spvInd[i]!
      if spm[i]!.ind[ind_m]! = spv.ind[ind_v]! then
        out[i] += spm[i]!.val[ind_m]! * spv.val[ind_v]!
        spmInd[i] += 1
        spvInd[i] += 1
      else
        if spm[i]!.ind[ind_m]! < spv.ind[ind_v]! then
          spmInd[i] += 1
        else
          spvInd[i] += 1
    return

-- Local lemmas used:
content: theorem getValSpV_eq (spv: SpV Int) (j: ℕ) (h_ind: j < spv.size): spv[spv.ind[j]!] = (spv.val)[j]!

content: theorem getValSpV_empty (spv: SpV Int) (j: ℕ) (h_empty: ∀ i < spv.size, spv.ind[i]! ≠ j): spv[j] = 0

-- Target theorem and proof:
theorem spv_dot_pure_gen (spv1: SpV Int) (spv2: SpV Int) (n pnt1 pnt2: ℕ)
  (sz1: ∀ i < spv1.size, spv1.ind[i]! < n)
  (sz2: ∀ i < spv2.size, spv2.ind[i]! < n):
  spv_dot spv1 spv2 pnt1 pnt2 =
    ∑ i ∈ Finset.range n,
      if max
        (if spv1.size ≤ pnt1 then n else spv1.ind[pnt1]!)
        (if spv2.size ≤ pnt2 then n else spv2.ind[pnt2]!) ≤ i then
        spv1[i] * spv2[i]
      else
        0
:= by
  fun_induction spv_dot spv1 spv2 pnt1 pnt2 with
  | case1 p1 p2 h =>
    have all_zero: (∀ x ∈ Finset.range n, (if max (if spv1.size ≤ p1 then n else spv1.ind[p1]!) (if spv2.size ≤ p2 then n else spv2.ind[p2]!) ≤ x then spv1[x] * spv2[x] else 0) = 0) := by
      intro x hx
      simp
      rcases h with ob1 | ob2
      { simp [ob1]
        simp at hx
        simp [hx] }
      simp [ob2]
      simp at hx
      simp [hx]
    rw [Finset.sum_eq_zero all_zero]
  | case2 p1 p2 h1 eq ih =>
    rw [ih, ←getValSpV_eq spv1 p1 (by omega), ←getValSpV_eq spv2 p2 (by omega)]
    have sum_eq_single:
      ∑ i ∈ Finset.range n, (if i = spv1.ind[p1]! then spv1[spv1.ind[p1]!] * spv2[spv1.ind[p1]!] else 0) = (if spv1.ind[p1]! = spv1.ind[p1]! then spv1[spv1.ind[p1]!] * spv2[spv1.ind[p1]!] else 0) := by
      have hb: ∀ i ∈ Finset.range n, i ≠ spv1.ind[p1]! → ((if i = spv1.ind[p1]! then spv1[spv1.ind[p1]!]! * spv2[spv1.ind[p1]!]! else 0) = 0) := by
        intro i hi iq
        simp at hi
        simp [iq]
      have hc: spv1.ind[p1]! ∉ Finset.range n → ((if spv1.ind[p1]! = spv1.ind[p1]! then spv1[spv1.ind[p1]!]! * spv2[spv1.ind[p1]!]! else 0) = 0) := by
        intro nin
        simp at nin
        have bnd := sz1 p1 (by omega)
        omega
      apply Finset.sum_eq_single spv1.ind[p1]! hb hc
    rw [if_pos] at sum_eq_single
    rw [←eq, ←sum_eq_single]
    rw [←Finset.sum_add_distrib]
    apply Finset.sum_congr
    { rfl }
    intro x hx
    simp at hx
    have h2: ¬spv1.size ≤ p1 ∧ ¬spv2.size ≤ p2 := by omega
    simp [h2]
    have hnx: ¬(n ≤ x) := by simp [hx]
    by_cases xeq: x = spv1.ind[p1]! <;> simp [xeq]
    { intro in1 in2
      by_cases edg1: spv1.size ≤ p1 + 1 <;> simp [edg1] at in1
      { omega }
      have p1lt := (spv1.inc) p1 (p1 + 1) (by omega) (by omega) (by simp)
      -- simp [getElem] at in1
      omega }
    have outb_lemma (spv: SpV Int) (pos: ℕ) (hsz: 1 ≤ spv.size) (hb: spv.ind[spv.size - 1]! < pos): spv[pos] = 0 := by
      by_cases ex: ∃ i < spv.size, spv.ind[i]! = pos
      { rcases ex with ⟨i, hbi, hi⟩
        by_cases heq: i = spv.size - 1
        { simp [heq] at hi
          omega }
        have contra := (spv.inc) i (spv.size - 1) (by omega) (by omega) (by omega)
        simp at hb
        -- simp [getElem] at hi
        omega }
      simp at ex
      simp [getValSpV_empty spv pos ex]
    by_cases edg1: spv1.size ≤ p1 + 1 <;> simp [edg1]
    { simp [hnx]
      have p1eq : p1 = spv1.size - 1 := by omega
      by_cases lex: spv1.ind[p1]! ≤ x <;> simp [lex]
      have ltx: spv1.ind[p1]! < x := by omega
      simp [p1eq] at ltx
      simp [outb_lemma spv1 x (by omega) ltx] }
    by_cases edg2: spv2.size ≤ p2 + 1 <;> simp [edg2]
    { simp [hnx]
      by_cases lex: spv1.ind[p1]! ≤ x <;> simp [lex]
      have ltx: spv1.ind[p1]! < x := by omega
      have p2eq: p2 = spv2.size - 1 := by omega
      simp [eq, p2eq] at ltx
      simp [outb_lemma spv2 x (by omega) ltx] }
    by_cases val: spv1.ind[p1 + 1]! ≤ x ∧ spv2.ind[p2 + 1]! ≤ x <;> simp [val]
    { have inc: spv1.ind[p1]! < spv1.ind[p1 + 1]! := (spv1.inc) p1 (p1 + 1) (by omega) (by omega) (by simp)
      simp [le_of_lt (lt_of_lt_of_le inc val.left)] }
    by_cases xb: spv1.ind[p1]! ≤ x <;> simp [xb]
    have ltx: spv1.ind[p1]! < x := by omega
    simp at val
    have interm_lemma (spv: SpV Int) (pos idx: ℕ) (hsz: idx + 1 < spv.size) (inter: spv.ind[idx]! < pos ∧ pos < spv.ind[idx + 1]!): spv[pos] = 0 := by
      by_cases ex: ∃ i < spv.size, spv.ind[i]! = pos
      { rcases ex with ⟨i, hbi, hi⟩
        have lt_lemma (i1 i2: ℕ) (hi1: i1 < spv.size) (hi2: i2 < spv.size): spv.ind[i1]! < spv.ind[i2]! → i1 < i2 := by
          intro hlt
          by_cases contra_lt: i2 < i1
          { have inc := spv.inc i2 i1 hi2 hi1 contra_lt
            simp at hlt
            omega }
          by_cases contra_eq: i1 = i2
          { simp [contra_eq] at hlt }
          omega
        have left_x := lt_lemma idx i (by omega) (by omega) (by omega)
        have right_x := lt_lemma i (idx + 1) (by omega) (by omega) (by omega)
        omega }
      simp at ex
      simp [getValSpV_empty spv pos ex]
    rcases (em (spv1.ind[p1 + 1]! ≤ x)) with c1 | c2
    { simp [c1] at val
      simp [interm_lemma spv2 x p2 (by omega) (by omega)] }
    simp at c2
    simp [interm_lemma spv1 x p1 (by omega) (by omega)]
    rfl
  | case3 p1 p2 h1 neq le ih =>
    rw [ih]
    apply Finset.sum_congr
    { rfl }
    intro x hx
    by_cases edg: spv1.size = p1 + 1
    { have sz_concl := sz1 p1 (by omega)
      simp [edg]
      simp at hx
      have nx : ¬(n ≤ x) := by omega
      by_cases b2: spv2.size ≤ p2 <;> simp [b2] <;> simp [nx]
      intro ifc1 ifc2
      by_cases ex: ∃ i < spv1.size, spv1.ind[i]! = x
      { rcases ex with ⟨i, ib, hi⟩
        by_cases il: p1 < i
        { omega }
        simp at il
        rcases (le_iff_eq_or_lt.mp il) with ieq | ilt
        { simp [ieq] at hi
          omega }
        have contra := spv1.inc i p1 ib (by omega) ilt
        omega }
      simp at ex
      simp [getValSpV_empty spv1 x ex] }
    have inb: ¬(spv1.size ≤ p1 + 1) ∧ ¬(spv1.size ≤ p1):= by omega
    simp [inb]
    by_cases edg2: spv2.size ≤ p2 <;> simp [edg2]
    { simp at hx
      have neg: ¬ (n ≤ x) := by omega
      simp [neg] }
    by_cases le2: spv2.ind[p2]! ≤ x <;> simp [le2]
    by_cases upper: spv1.ind[p1 + 1]! ≤ x <;> simp [upper]
    { simp at inb
      simp [getElem]
      simp [le_trans (le_of_lt (spv1.inc p1 (p1 + 1) inb.right inb.left (by simp))) upper] }
    by_cases lower: spv1.ind[p1]! ≤ x <;> simp [lower]
    by_cases ex: ∃ i < spv1.size, spv1.ind[i]! = x
    { rcases ex with ⟨ind, indb, hind⟩
      by_cases ind_x: ind = p1
      { simp [ind_x] at hind
        omega }
      rcases (lt_or_gt_of_ne ind_x) with ltc | gtc
      { have contra := spv1.inc ind p1 indb (by omega) ltc
        -- simp [getElem] at lower
        -- simp [getElem] at hind
        omega }
      by_cases indp1: ind = p1 + 1
      { simp [indp1] at hind
        omega }
      have indlt: p1 + 1 < ind := by omega
      have contra := spv1.inc (p1 + 1) ind (by omega) indb indlt
      -- simp [getElem] at upper
      -- simp [getElem] at hind
      omega }
    simp at ex
    simp [getValSpV_empty spv1 x ex]
  | case4 p1 p2 h1 neq nle ih =>
    rw [ih]
    apply Finset.sum_congr
    { rfl }
    intro x hx
    by_cases edg: spv2.size = p2 + 1
    { have sz_concl := sz2 p2 (by omega)
      simp [edg]
      simp at hx
      have nx : ¬(n ≤ x) := by omega
      by_cases b1: spv1.size ≤ p1 <;> simp [b1] <;> simp [nx]
      intro ifc1 ifc2
      by_cases ex: ∃ i < spv2.size, spv2.ind[i]! = x
      { rcases ex with ⟨i, ib, hi⟩
        by_cases il: p2 < i
        { omega }
        simp at il
        rcases (le_iff_eq_or_lt.mp il) with ieq | ilt
        { simp [ieq] at hi
          omega }
        have contra := (spv2.inc) i p2 ib (by omega) ilt
        -- simp [getElem] at hi
        -- simp [getElem] at ifc2
        omega }
      simp at ex
      simp [getValSpV_empty spv2 x ex] }
    have inb: ¬(spv2.size ≤ p2 + 1) ∧ ¬(spv2.size ≤ p2):= by omega
    simp [inb]
    by_cases edg1: spv1.size ≤ p1 <;> simp [edg1]
    { simp at hx
      have neg: ¬ (n ≤ x) := by omega
      simp [neg] }
    by_cases le1: spv1.ind[p1]! ≤ x <;> simp [le1]
    by_cases upper: spv2.ind[p2 + 1]! ≤ x <;> simp [upper]
    { simp at inb
      simp [getElem]
      simp [le_trans (le_of_lt ((spv2.inc) p2 (p2 + 1) inb.right inb.left (by simp))) upper] }
    by_cases lower: spv2.ind[p2]! ≤ x <;> simp [lower]
    by_cases ex: ∃ i < spv2.size, spv2.ind[i]! = x
    { rcases ex with ⟨ind, indb, hind⟩
      by_cases ind_x: ind = p2
      { simp [ind_x] at hind
        omega }
      rcases (lt_or_gt_of_ne ind_x) with ltc | gtc
      { have contra := (spv2.inc) ind p2 indb (by omega) ltc
        -- simp [getElem] at lower
        -- simp [getElem] at hind
        omega }
      by_cases indp2: ind = p2 + 1
      { simp [indp2] at hind
        omega }
      have indlt: p2 + 1 < ind := by omega
      have contra := (spv2.inc) (p2 + 1) ind (by omega) indb indlt
      -- simp [getElem] at upper
      -- simp [getElem] at hind
      omega }
    simp at ex
    simp [getValSpV_empty spv2 x ex]