import Mathlib.Tactic

open BigOperators Finset

namespace Goppadecoding

section lemmas_sum

-- based on sum_flip in library but easier-to-use interface
theorem sum_flip_simpler {M: Type _} [AddCommMonoid M] {n: ℕ} (f: ℕ → M):
  ∑ x in range n, f x = ∑ x in range n, f (n-1-x) :=
by
  by_cases n0: n = 0
  . rw[n0]
    simp only [range_zero, sum_empty]
  have n1: (n-1)+1 = n := Nat.succ_pred n0
  calc
    _ = ∑ x in range ((n-1)+1), f x := by rw[n1]
    _ = ∑ x in range ((n-1)+1), f (n-1-x) := by rw[← sum_flip]
    _ = ∑ x in range n, f (n-1-x) := by rw[n1]

theorem sum_singleton_ite {U: Type _} [DecidableEq U] {S: Finset U} {M: Type _} [AddCommMonoid M] (m: M) {s: U}:
  s ∈ S → ∑ t in S, (if s = t then m else 0) = m := by
  intro sinS
  calc
    ∑ t in S, (if s = t then m else 0)
      = if s ∈ S then m else 0 := by apply sum_ite_eq
    _ = m := by simp only [sinS, ite_true]

theorem sum_ite_add_sum_ite {S: Type _} (P: S → Prop) {hp: DecidablePred P} [Fintype S] {M: Type _} [AddCommMonoid M] (f: S → M) (g: S → M):
  (∑ x, if P x then f x else 0) + (∑ x, if P x then 0 else g x)
  = ∑ x, if P x then f x else g x := by
    repeat rw[sum_ite]
    repeat rw[sum_const_zero]
    simp only [mem_univ, forall_true_left, add_zero, zero_add]

theorem sum_fin_eq_sum_range_lemma {U: Type _} (n: ℕ) (x: ℕ) (f: Fin (Nat.succ n + 1) → U):
  x ∈ range (n+1) → f (Fin.succ x) = f (x+1) := by
    simp only [mem_range]
    intro xn1
    apply congr_arg
    simp only [Fin.succ, Fin.ofNat_eq_val, Fin.coe_ofNat_eq_mod, Fin.eq_iff_veq]
    have xmodn1: x % (n+1) = x := Nat.mod_eq_of_lt xn1
    have x1: (x: Fin (Nat.succ n + 1)) + 1 = (x+1: ℕ) := by simp only [Nat.cast_add, Nat.cast_one]
    have xsuccn: x < Nat.succ n := by simp only [xn1]
    have x1succn1: x + 1 < Nat.succ n + 1 := by simp only [add_lt_add_iff_right, xsuccn]
    have x1succval: ((x+1: ℕ): Fin (Nat.succ n + 1)).val = (x + 1: ℕ) := Fin.val_cast_of_lt x1succn1
    rw[xmodn1,x1,x1succval]

-- similar to Finset.sum_fin_eq_sum_range
-- but uses the automatic %(n+1) cast
-- see also Finset.sum_range where function is defined on ℕ
theorem sum_fin_eq_sum_range {M: Type _} [AddCommMonoid M] (n: ℕ):
  ∀ (f: Fin (n+1) → M),
  ∑ j: Fin (n+1), f j = ∑ j in range (n+1), f j := by
    induction' n with n prevcase
    . intro f
      rw[Fin.sum_univ_succ f]
      simp only [Nat.zero_eq, univ_eq_empty, sum_empty, add_zero, zero_add, range_one, sum_singleton, Nat.cast_zero]
    . intro f
      rw[Fin.sum_univ_succ f]
      rw[sum_range_succ' (fun (x:ℕ) ↦ f x) (Nat.succ n)]
      simp only [Nat.cast_add, Nat.cast_one, Nat.cast_zero]
      rw[prevcase]
      simp only [Nat.succ_eq_add_one]
      rw[add_comm]
      have main: ∑ j in range (n+1), f (Fin.succ j) = ∑ j in range (n+1), f (j+1) := by
        apply sum_congr
        simp only
        intro x
        simp only [mem_range]
        intro xrange
        rw[sum_fin_eq_sum_range_lemma n x f]
        simp only [mem_range, xrange]
      rw[main]

theorem sum_range_mod {M: Type _} [AddCommMonoid M] (n: ℕ) (f: ℕ → ℕ → M):
  ∑ x in range n, f (x % n) x = ∑ x in range n, f x x := by
    apply sum_congr
    simp only
    intro x xrange
    have xn: x < n := List.mem_range.mp xrange
    have xmodn: x % n = x := by rw[Nat.mod_eq_of_lt xn]
    rw[xmodn]

theorem sum_range_ite {M: Type _} [AddCommMonoid M] {m: ℕ} {n: ℕ} (mn: m ≤ n) (f: ℕ → M):
  ∑ x in range n, (if x < m then f x else 0) = ∑ x in range m, f x := by
    let d:ℕ := n - m
    have md: n = m+d := Eq.symm (Nat.add_sub_of_le mn)
    rw[md]
    rw[sum_range_add]
    simp only [ge_iff_le, add_lt_iff_neg_left, not_lt_zero', ite_false, sum_const_zero, add_zero]
    apply sum_congr
    simp only
    intro x xrange
    have xm: x < m := List.mem_range.mp xrange
    simp only [xm, ite_true]

theorem sum_range_eq_sum_fin_ite {M: Type _} [AddCommMonoid M] {m: ℕ} {n: ℕ} (mn: m ≤ n) (f: ℕ → M):
  ∑ j in range m, f j = ∑ j: Fin n, if (j:ℕ) < m then f j else 0 := by
    rw[← sum_range (fun x ↦ if (x:ℕ) < m then f x else 0)]
    rw[sum_range_ite]
    linarith

theorem Ico_filter_not_lt {α: Type _} [LinearOrder α] [LocallyFiniteOrder α] (a b c : α) : ((Ico a b).filter fun x => ¬x < c) = Ico (max a c) b := by
  rw[← Ico_filter_le]
  simp only [not_lt, ge_iff_le, mem_Ico, and_imp, Ico_filter_le, le_max_iff]

theorem sum_Ico_ite_lt {M: Type _} [AddCommMonoid M] {n: ℕ} (f: ℕ → M):
  ∑ x in Ico 0 n, (if x < n then 0 else f x) = 0 := by
    apply sum_eq_zero
    simp only [ge_iff_le, nonpos_iff_eq_zero, Nat.Ico_zero_eq_range, mem_range, ite_eq_left_iff, not_lt]
    intro x xn
    contrapose
    intro _
    simp only [not_le, xn]

theorem sum_fin_ite_eq_sum_fin_ite_sub {M: Type _} [AddCommMonoid M] {m: ℕ} {n: ℕ} {mn: ℕ} (mplusn: mn = m+n) (f: ℕ → M):
  ∑ x: Fin mn, (if (x:ℕ) < m then f (x:ℕ) else 0) =
  ∑ y: Fin mn, (if (y:ℕ) < n then 0 else f ((y:ℕ) - n)) := by
    calc
      ∑ x: Fin mn, (if (x:ℕ) < m then f (x:ℕ) else 0)
        = ∑ x in range mn, (if x < m then f x else 0)
            := by rw[← sum_range (fun x ↦ if x < m then f x else 0)]
      _ = ∑ x in range (m+n), (if x < m then f x else 0)
            := by simp only [mplusn]
      _ = ∑ x in range m, (if x < m then f x else 0) + ∑ x in range n, (if (m + x) < m then f (m + x) else 0)
            := sum_range_add (fun x => if x < m then f x else 0) m n
      _ = ∑ x in range m, f x + ∑ x in range n, (if (m + x) < m then f (m + x) else 0)
            := by simp only [le_refl, sum_range_ite, add_lt_iff_neg_left, not_lt_zero', ite_false, sum_const_zero, add_zero]
      _ = ∑ x in range m, f x
            := by simp only [add_lt_iff_neg_left, not_lt_zero', ite_false, sum_const_zero, add_zero]
      _ = ∑ x in Ico 0 m, f x
            := by simp only [ge_iff_le, nonpos_iff_eq_zero, Nat.Ico_zero_eq_range]
      _ = ∑ y in Ico (0+n) (m+n), f (y-n)
            := by rw[← sum_Ico_add]; apply sum_congr; simp only [ge_iff_le, nonpos_iff_eq_zero, Nat.Ico_zero_eq_range]; simp only [ge_iff_le, nonpos_iff_eq_zero, Nat.Ico_zero_eq_range, mem_range, add_le_iff_nonpos_right, add_tsub_cancel_left, implies_true, forall_const]
      _ = ∑ y in Ico n mn, f (y-n)
            := by simp only [zero_add, ge_iff_le, add_le_iff_nonpos_left, nonpos_iff_eq_zero, mplusn]
      _ = ∑ y in Ico n mn, if y < n then 0 else f (y-n)
            := by rw[sum_ite,Ico_filter_lt,Ico_filter_not_lt]; simp only [ge_iff_le, min_le_iff, le_refl, or_true, Ico_eq_empty_of_le, sum_const_zero, max_self, zero_add]
      _ = ∑ y in Ico 0 n, (if y < n then 0 else f (y-n)) + ∑ y in Ico n mn, (if y < n then 0 else f (y-n))
            := by rw[sum_Ico_ite_lt,zero_add]
      _ = ∑ y in Ico 0 mn, if y < n then 0 else f (y-n)
            := by rw[sum_Ico_consecutive]; simp only [_root_.zero_le]; rw[mplusn]; apply le_add_self
      _ = ∑ y in range mn, if y < n then 0 else f (y-n)
            := by rw[Nat.Ico_zero_eq_range]
      _ = ∑ y: Fin mn, (if (y:ℕ) < n then 0 else f ((y:ℕ) - n))
            := by rw[sum_range]
    
end lemmas_sum
