/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module

prelude
public import Init.Data.String.Basic
public import Init.Data.String.Lemmas.Splits

/-!
# Helpers for termination arguments about functions operating on strings
-/

public section

namespace String

namespace Slice.Pos

/-- The number of bytes between `p` and the end position. This number decreases as `p` advances. -/
def remainingBytes {s : Slice} (p : s.Pos) : Nat :=
  p.offset.byteDistance s.endPos.offset

theorem remainingBytes_eq_byteDistance {s : Slice} {p : s.Pos} :
    p.remainingBytes = p.offset.byteDistance s.endPos.offset := (rfl)

theorem remainingBytes_eq {s : Slice} {p : s.Pos} :
    p.remainingBytes = s.utf8ByteSize - p.offset.byteIdx := by
  simp [remainingBytes_eq_byteDistance, Pos.Raw.byteDistance_eq]

theorem remainingBytes_inj {s : Slice} {p q : s.Pos} :
    p.remainingBytes = q.remainingBytes ↔ p = q := by
  have := p.isValidForSlice.le_utf8ByteSize
  have := q.isValidForSlice.le_utf8ByteSize
  simp only [remainingBytes_eq, Pos.ext_iff, Pos.Raw.ext_iff]
  omega

theorem le_iff_remainingBytes_le {s : Slice} (p q : s.Pos) :
    p ≤ q ↔ q.remainingBytes ≤ p.remainingBytes := by
  have := p.isValidForSlice.le_utf8ByteSize
  have := q.isValidForSlice.le_utf8ByteSize
  simp only [remainingBytes_eq, Slice.Pos.le_iff, Pos.Raw.le_iff]
  omega

theorem lt_iff_remainingBytes_lt {s : Slice} (p q : s.Pos) :
    p < q ↔ q.remainingBytes < p.remainingBytes := by
  have := p.isValidForSlice.le_utf8ByteSize
  have := q.isValidForSlice.le_utf8ByteSize
  simp only [remainingBytes_eq, Slice.Pos.lt_iff, Pos.Raw.lt_iff]
  omega

theorem wellFounded_lt {s : Slice} : WellFounded (fun (p : s.Pos) q => p < q) := by
  simpa [lt_iff, Pos.Raw.lt_iff] using
    InvImage.wf (Pos.Raw.byteIdx ∘ Slice.Pos.offset) Nat.lt_wfRel.wf

theorem wellFounded_gt {s : Slice} : WellFounded (fun (p : s.Pos) q => q < p) := by
  simpa [lt_iff_remainingBytes_lt] using
    InvImage.wf Slice.Pos.remainingBytes Nat.lt_wfRel.wf

instance {s : Slice} : WellFoundedRelation s.Pos where
  rel p q := q < p
  wf := Slice.Pos.wellFounded_gt

/-- Type alias for `String.Slice.Pos` representing that the given position is expected to decrease
in recursive calls. -/
structure Down (s : Slice) : Type where
  inner : s.Pos

/-- Use `termination_by pos.down` to signify that in a recursive call, the parameter `pos` is
expected to decrease. -/
def down {s : Slice} (p : s.Pos) : Pos.Down s where
  inner := p

@[simp]
theorem inner_down {s : Slice} {p : s.Pos} : p.down.inner = p := (rfl)

instance {s : Slice} : WellFoundedRelation (Pos.Down s) where
  rel p q := p.inner < q.inner
  wf := InvImage.wf Down.inner wellFounded_lt

theorem ne_endPos_of_next?_eq_some {s : Slice} {p q : s.Pos} :
    p.next? = some q → p ≠ s.endPos := by
  simpa [next?] using fun h _ => h

theorem eq_next_of_next?_eq_some {s : Slice} {p q : s.Pos} :
    (h : p.next? = some q) → q = p.next (ne_endPos_of_next?_eq_some h) := by
  simpa [next?] using fun _ h => h.symm

theorem ne_startPos_of_prev?_eq_some {s : Slice} {p q : s.Pos} :
    p.prev? = some q → p ≠ s.startPos := by
  simpa [prev?] using fun h _ => h

theorem eq_prev_of_prev?_eq_some {s : Slice} {p q : s.Pos} :
    (h : p.prev? = some q) → q = p.prev (ne_startPos_of_prev?_eq_some h) := by
  simpa [prev?] using fun _ h => h.symm

@[simp]
theorem le_refl {s : Slice} (p : s.Pos) : p ≤ p := by
  simp [le_iff]

theorem lt_trans {s : Slice} {p q r : s.Pos} : p < q → q < r → p < r := by
  simpa [Pos.lt_iff, Pos.Raw.lt_iff] using Nat.lt_trans

@[simp]
theorem lt_next_next {s : Slice} {p : s.Pos} {h h'} : p < (p.next h).next h' :=
  lt_trans p.lt_next (p.next h).lt_next

@[simp]
theorem prev_prev_lt {s : Slice} {p : s.Pos} {h h'} : (p.prev h).prev h' < p :=
  lt_trans (p.prev h).prev_lt p.prev_lt

end Slice.Pos

namespace Pos

/-- The number of bytes between `p` and the end position. This number decreases as `p` advances. -/
def remainingBytes {s : String} (p : s.Pos) : Nat :=
  p.toSlice.remainingBytes

@[simp]
theorem remainingBytes_toSlice {s : String} {p : s.Pos} :
    p.toSlice.remainingBytes = p.remainingBytes := (rfl)

theorem remainingBytes_eq_byteDistance {s : String} {p : s.Pos} :
    p.remainingBytes = p.offset.byteDistance s.endPos.offset := (rfl)

theorem remainingBytes_eq {s : String} {p : s.Pos} :
    p.remainingBytes = s.utf8ByteSize - p.offset.byteIdx := by
  simp [remainingBytes_eq_byteDistance, Pos.Raw.byteDistance_eq]

theorem remainingBytes_inj {s : String} {p q : s.Pos} :
    p.remainingBytes = q.remainingBytes ↔ p = q := by
  simp [← remainingBytes_toSlice, Pos.toSlice_inj, Slice.Pos.remainingBytes_inj]

theorem le_iff_remainingBytes_le {s : String} (p q : s.Pos) :
    p ≤ q ↔ q.remainingBytes ≤ p.remainingBytes := by
  simp [← remainingBytes_toSlice, ← Slice.Pos.le_iff_remainingBytes_le]

theorem lt_iff_remainingBytes_lt {s : String} (p q : s.Pos) :
    p < q ↔ q.remainingBytes < p.remainingBytes := by
  simp [← remainingBytes_toSlice, ← Slice.Pos.lt_iff_remainingBytes_lt]

theorem wellFounded_lt {s : String} : WellFounded (fun (p : s.Pos) q => p < q) := by
  simpa [lt_iff, Pos.Raw.lt_iff] using
    InvImage.wf (Pos.Raw.byteIdx ∘ Pos.offset) Nat.lt_wfRel.wf

theorem wellFounded_gt {s : String} : WellFounded (fun (p : s.Pos) q => q < p) := by
  simpa [lt_iff_remainingBytes_lt] using
    InvImage.wf Pos.remainingBytes Nat.lt_wfRel.wf

instance {s : String} : WellFoundedRelation s.Pos where
  rel p q := q < p
  wf := Pos.wellFounded_gt

/-- Type alias for `String.Pos` representing that the given position is expected to decrease
in recursive calls. -/
structure Down (s : String) : Type where
  inner : s.Pos

/-- Use `termination_by pos.down` to signify that in a recursive call, the parameter `pos` is
expected to decrease. -/
def down {s : String} (p : s.Pos) : Pos.Down s where
  inner := p

@[simp]
theorem inner_down {s : String} {p : s.Pos} : p.down.inner = p := (rfl)

instance {s : String} : WellFoundedRelation (Pos.Down s) where
  rel p q := p.inner < q.inner
  wf := InvImage.wf Pos.Down.inner Pos.wellFounded_lt

theorem map_toSlice_next? {s : String} {p : s.Pos} :
    p.next?.map Pos.toSlice = p.toSlice.next? := by
  simp [next?]

theorem map_toSlice_prev? {s : String} {p : s.Pos} :
    p.prev?.map Pos.toSlice = p.toSlice.prev? := by
  simp [prev?]

theorem ne_endPos_of_next?_eq_some {s : String} {p q : s.Pos}
    (h : p.next? = some q) : p ≠ s.endPos :=
  ne_of_apply_ne Pos.toSlice (Slice.Pos.ne_endPos_of_next?_eq_some
    (by simpa only [Pos.map_toSlice_next?, Option.map_some] using congrArg (·.map toSlice) h))

theorem eq_next_of_next?_eq_some {s : String} {p q : s.Pos} (h : p.next? = some q) :
    q = p.next (ne_endPos_of_next?_eq_some h) := by
  simpa only [← toSlice_inj, toSlice_next] using Slice.Pos.eq_next_of_next?_eq_some
    (by simpa [Pos.map_toSlice_next?] using congrArg (·.map toSlice) h)

theorem ne_startPos_of_prev?_eq_some {s : String} {p q : s.Pos}
    (h : p.prev? = some q) : p ≠ s.startPos :=
  ne_of_apply_ne Pos.toSlice (Slice.Pos.ne_startPos_of_prev?_eq_some
    (by simpa only [Pos.map_toSlice_prev?, Option.map_some] using congrArg (·.map toSlice) h))

theorem eq_prev_of_prev?_eq_some {s : String} {p q : s.Pos} (h : p.prev? = some q) :
    q = p.prev (ne_startPos_of_prev?_eq_some h) := by
  simpa only [← toSlice_inj, toSlice_prev] using Slice.Pos.eq_prev_of_prev?_eq_some
    (by simpa [Pos.map_toSlice_prev?] using congrArg (·.map toSlice) h)

@[simp]
theorem le_refl {s : String} (p : s.Pos) : p ≤ p := by
  simp [Pos.le_iff]

theorem lt_trans {s : String} {p q r : s.Pos} : p < q → q < r → p < r := by
  simpa [Pos.lt_iff, Pos.Raw.lt_iff] using Nat.lt_trans

@[simp]
theorem lt_next_next {s : String} {p : s.Pos} {h h'} : p < (p.next h).next h' :=
  lt_trans p.lt_next (p.next h).lt_next

@[simp]
theorem prev_prev_lt {s : String} {p : s.Pos} {h h'} : (p.prev h).prev h' < p :=
  lt_trans (p.prev h).prev_lt p.prev_lt

theorem Splits.remainingBytes_eq {s : String} {p : s.Pos} {t₁ t₂}
    (h : p.Splits t₁ t₂) : p.remainingBytes = t₂.utf8ByteSize := by
  simp [Pos.remainingBytes_eq, h.eq_append, h.offset_eq_rawEndPos]

end Pos

namespace Slice.Pos

@[simp]
theorem remainingBytes_copy {s : Slice} {p : s.Pos} :
    p.copy.remainingBytes = p.remainingBytes := by
  simp [remainingBytes_eq, String.Pos.remainingBytes_eq, Slice.utf8ByteSize_eq]

theorem Splits.remainingBytes_eq {s : Slice} {p : s.Pos} {t₁ t₂} (h : p.Splits t₁ t₂) :
    p.remainingBytes = t₂.utf8ByteSize := by
  simpa using h.copy.remainingBytes_eq

end Slice.Pos

macro_rules | `(tactic| decreasing_trivial) => `(tactic|
  (with_reducible change (_ : Slice.Pos _) < _
   simp [
    Slice.Pos.eq_next_of_next?_eq_some (by assumption),
  ]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
  (with_reducible change (_ : Slice.Pos _) < _
   simp [
    Slice.Pos.eq_prev_of_prev?_eq_some (by assumption),
  ]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
  (with_reducible change (_ : String.Pos _) < _
   simp [
    Pos.eq_next_of_next?_eq_some (by assumption),
  ]) <;> done)
macro_rules | `(tactic| decreasing_trivial) => `(tactic|
  (with_reducible change (_ : String.Pos _) < _
   simp [
    Pos.eq_prev_of_prev?_eq_some (by assumption),
  ]) <;> done)

end String
