import Mathlib.RingTheory.Polynomial.Basic
import Goppadecoding.nat
import Goppadecoding.zero
import Goppadecoding.poly
import Goppadecoding.vanishing
import Goppadecoding.interpolator

open Polynomial BigOperators Finset

namespace Goppadecoding

section goppa_parity

variable {U: Type _} [DecidableEq U]
variable {k: Type _} [Field k] [DecidableEq k]

theorem goppa_parity_lemma (α f: U → k) (S: Finset U) (d: ℕ)
: X^d * (∑ s in S, C (f s) * monic_vanishing_at_except α S s)
  - (∑ s in S, C (f s * (α s)^d) * monic_vanishing_at_except α S s)
  = monic_vanishing_at α S * ∑ s in S, ∑ j in range d, C (f s * (α s)^j) * X^(d-1-j) :=
by
  have inner1: ∀ s, s ∈ S →
         (X^d * (C (f s) * monic_vanishing_at_except α S s) - C ((f s * (α s)^d)) * monic_vanishing_at_except α S s)
        = (monic_vanishing_at α S) * ((∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s)) :=
  by
    intro s sinS
    calc
      _ = (X^d - C ((α s)^d)) * C (f s) * monic_vanishing_at_except α S s := by rw[sub_mul,C_mul]; ring_nf
      _ = (X - C (α s)) * (∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s) * monic_vanishing_at_except α S s := by rw[mul_neg_geom_sum2_X_C d (α s)]
      _ = (monic_vanishing_at_except α S s * (X - C (α s))) * ((∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s)) := by ring
      _ = (monic_vanishing_at α S) * ((∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s)) := by rw[monic_vanishing_at_except_filter α S s sinS]
  have inner2: ∀ s j,
    (C ((α s)^j) * X^(d-1-j)) * C (f s) = C (f s * (α s)^j) * X^(d-1-j) :=
  by
    intro s j
    rw[C_mul]
    group
  have inner3: ∀ s, 
    (∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s)
    = ∑ j in range d, C (f s * (α s)^j) * X^(d-1-j) :=
  by
    intro s
    rw[sum_mul]
    apply sum_congr
    simp only
    intro j _
    rw[inner2 s j]
  calc
    _ = X^d * (∑ s in S, C (f s) * monic_vanishing_at_except α S s) - (∑ s in S, C ((f s * (α s)^d)) * monic_vanishing_at_except α S s) := rfl
    _ = (∑ s in S, X^d * (C (f s) * monic_vanishing_at_except α S s)) - (∑ s in S, C ((f s * (α s)^d)) * monic_vanishing_at_except α S s) := by rw[mul_sum]
    _ = ∑ s in S, (X^d * (C (f s) * monic_vanishing_at_except α S s) - C ((f s * (α s)^d)) * monic_vanishing_at_except α S s) := by rw[sum_sub_distrib]
    _ = ∑ s in S, (monic_vanishing_at α S) * ((∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s)) := by
        apply sum_congr
        simp only
        intro s sinS
        rw[inner1 s sinS]
    _ = (monic_vanishing_at α S) * ∑ s in S, (∑ j in range d, C ((α s)^j) * X^(d-1-j)) * C (f s) := by rw[mul_sum]
    _ = (monic_vanishing_at α S) * ∑ s in S, ∑ j in range d, C (f s * (α s)^j) * X^(d-1-j) := by
        apply congr_arg
        apply sum_congr
        simp only
        intro s _
        rw[inner3 s]
  
theorem goppa_parity_lemma2 {α f: U → k} {S: Finset U} {d: ℕ}
: X^d * (∑ s in S, C (f s) * monic_vanishing_at_except α S s)
  - (∑ s in S, C (f s * (α s)^d) * monic_vanishing_at_except α S s)
  = monic_vanishing_at α S * ∑ j in range d, C (∑ s in S, f s * (α s)^j) * X^(d-1-j) :=
by calc
  _ = monic_vanishing_at α S * ∑ s in S, ∑ j in range d, C (f s * (α s)^j) * X^(d-1-j) := by
        rw[goppa_parity_lemma]
  _ = monic_vanishing_at α S * ∑ j in range d, ∑ s in S, C (f s * (α s)^j) * X^(d-1-j) := by
        rw[sum_comm]
  _ = monic_vanishing_at α S * ∑ j in range d, C (∑ s in S, f s * (α s)^j) * X^(d-1-j) := by
        apply congr_arg
        apply sum_congr; simp only; intro j _
        rw[map_sum C,sum_mul]

theorem goppa_parity
  {α c: U → k} {S: Finset U} {A G: k[X]} {d: ℕ}
  (injective: Set.InjOn α S)
  (Adef: A = monic_vanishing_at α S)
  (Gdeg: G.degree = d)
  (GA: IsCoprime G A)
: (G ∣ ∑ s in S, C (c s) * monic_vanishing_at_except α S s
   ↔ ∀ j, j < d → ∑ s in S, (c s / G.eval (α s)) * (α s)^j = 0
  ) :=
by
  have Adeg: A.degree = S.card := by calc
    _ = (monic_vanishing_at α S).degree := by rw[Adef]
    _ = S.card := degree_monic_vanishing_at α S
  have AG: IsCoprime A G := isCoprime_comm.mpr GA
  let Z := ∑ s in S, C (c s) * monic_vanishing_at_except α S s
  let B := ∑ s in S, C (c s / G.eval (α s)) * monic_vanishing_at_except α S s
  let Q := ∑ s in S, C ((c s / G.eval (α s)) * (α s)^d) * monic_vanishing_at_except α S s
  have XdBQA: X^d*B-Q = A * ∑ j in range d, C (∑ s in S, (c s / G.eval (α s)) * (α s)^j) * X^(d-1-j) := by rw[goppa_parity_lemma2,Adef]
  have Zinterp: Z = interpolator α S (fun s ↦ c s * A.diff.eval (α s)) := sum_C_mul_monic_vanishing_at_except_eq_interpolator _ injective Adef
  have Binterp: B = interpolator α S (fun s ↦ (c s / G.eval (α s)) * A.diff.eval (α s)) := sum_C_mul_monic_vanishing_at_except_eq_interpolator _ injective Adef
  have Qinterp: Q = interpolator α S (fun s ↦ ((c s / G.eval (α s)) * (α s)^d) * A.diff.eval (α s)) := sum_C_mul_monic_vanishing_at_except_eq_interpolator _ injective Adef
  have Zdeg: Z.degree < A.degree := by rw[Zinterp,Adeg]; apply degree_interpolator_lt
  have Bdeg: B.degree < A.degree := by rw[Binterp,Adeg]; apply degree_interpolator_lt
  have Qdeg: Q.degree < A.degree := by rw[Qinterp,Adeg]; apply degree_interpolator_lt
  have AZGB: A ∣ Z-G*B := by
    have Zeval: ∀ s, s ∈ S → Z.eval (α s) = c s * A.diff.eval (α s) := by intro s sinS; rw[Zinterp,interpolator_eval injective sinS]
    have Beval: ∀ s, s ∈ S → B.eval (α s) = (c s / G.eval (α s)) * A.diff.eval (α s) := by intro s sinS; rw[Binterp,interpolator_eval injective sinS]
    have ZGBeval: ∀ s, s ∈ S → Z.eval (α s) = G.eval (α s) * B.eval (α s) := by
      intro s sinS
      have Aeval: A.eval (α s) = 0 := by rw[Adef]; exact monic_vanishing_at_vanishes α S sinS
      have Gnonzero: G.eval (α s) ≠ 0 := no_shared_root_if_coprime AG Aeval
      have cGG: G.eval (α s) * (c s / G.eval (α s)) = c s := mul_div_cancel' (c s) Gnonzero
      rw[Zeval s sinS,Beval s sinS,← mul_assoc,cGG]
    have ZGBeval2: ∀ s, s ∈ S → (Z-G*B).eval (α s) = 0 := by
      intro s sinS
      calc
        _ = Z.eval (α s) - G.eval (α s) * B.eval (α s) := by rw[eval_sub,eval_mul]
        _ = 0 := by rw[ZGBeval s sinS]; group
    rw[Adef]
    exact monic_vanishing_at_divides S (Z-G*B) injective ZGBeval2
  constructor
  . intro GdivZ j jd
    have GdivZcopy: G ∣ ∑ s in S, C (c s) * monic_vanishing_at_except α S s := GdivZ
    cases GdivZcopy with | intro ZG GZG =>
    have dZGdeg: d + ZG.degree < A.degree := by calc
      _ = G.degree + ZG.degree := by rw[Gdeg]
      _ = (G*ZG).degree := by rw[degree_mul]
      _ = Z.degree := by rw[← GZG]
      _ < A.degree := Zdeg
    have BZG: B = ZG := by
      have AZGB2: A ∣ ZG-B := by
        have AZZGB: A ∣ G*(ZG-B) := by rw[mul_sub,← GZG]; exact AZGB
        exact dvd_if_dvd_mul_left AG AZZGB
      have ZGBdeg: (ZG-B).degree < A.degree := by
        have ZGdeg: ZG.degree < A.degree := lt_if_nat_add_lt dZGdeg 
        calc
          _ ≤ max ZG.degree B.degree := degree_sub_le ZG B
          _ < A.degree := max_lt ZGdeg Bdeg 
      have ZGB0: ZG-B = 0 := eq_zero_of_dvd_of_degree_lt AZGB2 ZGBdeg
      exact Eq.symm (eq_if_sub_zero ZGB0)
    have XdBQdeg: ((X:k[X])^d*B-Q).degree < A.degree := by
      have dBdeg: d + B.degree < A.degree := by rw[BZG]; exact dZGdeg
      have XdBdeg: ((X:k[X])^d*B).degree < A.degree := by calc
        _ = ((X:k[X])^d).degree + B.degree := degree_mul
        _ = d + B.degree := by rw[degree_pow,degree_X,nsmul_eq_mul,mul_one]
        _ < A.degree := dBdeg
      calc
        _ ≤ max ((X:k[X])^d*B).degree Q.degree := degree_sub_le (X^d*B) Q
        _ < A.degree := max_lt XdBdeg Qdeg
    have sum0: ∑ j in range d, C (∑ s in S, (c s / G.eval (α s)) * (α s)^j) * X^(d-1-j) = 0 := by
      have XdBQA2: A ∣ X^d*B-Q := by
        apply Dvd.intro (∑ j in range d, C (∑ s in S, (c s / G.eval (α s)) * (α s)^j) * X^(d-1-j))
        rw[XdBQA]
      have XdBQ0: X^d*B-Q = 0 := eq_zero_of_dvd_of_degree_lt XdBQA2 XdBQdeg
      have Anonzero: A ≠ 0 := by rw[Adef]; exact monic_vanishing_at_nonzero α S
      rw[XdBQ0] at XdBQA
      apply zero_if_mul_left_zero Anonzero (Eq.symm XdBQA)
    exact zero_coeff_of_zero_poly_as_sum sum0 j jd
  intro zerocoeff
  have XdBQ: X^d*B = Q := by
    have XdBQ0: X^d*B-Q = 0 := by
      rw[XdBQA]
      rw[mul_eq_zero]; right
      apply sum_eq_zero
      intro j jd
      rw[mem_range] at jd
      rw[mul_eq_zero]; left
      rw[zerocoeff,map_zero]
      exact jd
    exact eq_if_sub_zero XdBQ0
  have GBdeg: (G*B).degree < A.degree := by calc
    _ = G.degree + B.degree := by rw[degree_mul]
    _ = d + B.degree := by rw[Gdeg]
    _ = ((X:k[X])^d).degree + B.degree := by rw[degree_pow,degree_X,nsmul_eq_mul,mul_one]
    _ = (X^d*B).degree := by rw[degree_mul]
    _ = Q.degree := by rw[XdBQ]
    _ < A.degree := Qdeg
  have ZGB: Z = G*B := by
    have ZGBdeg: (Z-G*B).degree < A.degree := by calc
      _ ≤ max Z.degree (G*B).degree := degree_sub_le Z (G*B)
      _ < A.degree := max_lt Zdeg GBdeg
    have ZGB0: Z-G*B = 0 := eq_zero_of_dvd_of_degree_lt AZGB ZGBdeg
    exact eq_if_sub_zero ZGB0
  apply Dvd.intro B
  rw[← ZGB]

end goppa_parity
