/-!
This test checks that simp is able to instantiate the parameter `g` below. It does not
appear on the lhs of the rule, but solving `hf` fully determines it.
-/

example (l : List Nat)  :
  l.attach.foldl (fun acc t => max acc (t.val)) 0 = l.foldl (fun acc t => max acc t) 0 := by
  simp [List.foldl_subtype]

example (l : List Nat)  :
  l.attach.foldl (fun acc ⟨t, _⟩ => max acc t) 0 = l.foldl (fun acc t => max acc t) 0 := by
  simp [List.foldl_subtype]


theorem foldr_to_sum (l : List Nat) (f : Nat → Nat → Nat) (g : Nat → Nat)
  (h : ∀ acc x, f x acc = g x + acc) :
  l.foldr f 0 = List.sum (l.map g) := by
  rw [List.sum, List.foldr_map]
  congr
  funext x acc
  apply h

-- this works:
example (l : List Nat) :
  l.foldr (fun x a => x*x + a) 0 = List.sum (l.map (fun x => x * x)) := by
  simp [foldr_to_sum]

-- this does not, so such a pattern is still quite fragile

/--
error: unsolved goals
l : List Nat
⊢ List.foldr (fun x a => a + x * x) 0 l = (List.map (fun x => x * x) l).sum
-/
#guard_msgs in
set_option linter.unusedSimpArgs false in
example (l : List Nat) :
  l.foldr (fun x a => a + x*x) 0 = List.sum (l.map (fun x => x * x)) := by
  simp (failIfUnchanged := false) [foldr_to_sum]

example (l : List Nat) :
  l.foldr (fun x a => a + x*x) 0 = List.sum (l.map (fun x => x * x)) := by
  simp [List.sum, List.foldr_map, Nat.add_comm]


-- but with stronger simp normal forms, it would work:

@[simp]
theorem add_eq_add_cancel (a x y : Nat) : (a + x = y + a) ↔  (x = y) := by omega

example (l : List Nat) :
  l.foldr (fun x a => a + x*x) 0 = List.sum (l.map (fun x => x * x)) := by
  simp [foldr_to_sum]


-- An example with zipWith

theorem zipWith_ignores_right
  (l₁ : List α) (l₂ : List β) (f : α → β → γ) (g : α → γ)
  (h : ∀ a b, f a b = g a) :
  List.zipWith f l₁ l₂ = List.map g (l₁.take l₂.length) := by
  induction l₁ generalizing l₂ with
  | nil => simp
  | cons x xs ih =>
    cases l₂
    · simp
    · simp [List.zipWith, h, ih]

example (l₁ l₂ : List Nat) (hlen: l₁.length = l₂.length):
  List.zipWith (fun x _ => x*x) l₁ l₂ = l₁.map (fun x => x * x) := by
  simp only [zipWith_ignores_right, hlen.symm, List.take_length]
