import Definitions.Def_WeierstrassCurve_OddOrderSummingSet
import Definitions.Def_WeierstrassCurve_VeluQuotientOfSums
import Definitions.Def_WeierstrassCurve_VeluOrderTwo

set_option autoImplicit false

namespace WeierstrassCurve

def fullKernelQuotient {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) (N : ℕ) : WeierstrassCurve F :=
  W.veluQuotientOfSums
    (∑ P ∈ W.oddOrderSummingSet Q (N - 1), W.veluGx P.1 P.2)
    (∑ P ∈ W.oddOrderSummingSet Q (N - 1),
      (P.1 * W.veluGx P.1 P.2 - P.2 * W.veluGy P.1 P.2))

theorem veluGx_add_veluGx_negY {R : Type*} [CommRing R] (W : WeierstrassCurve R) (x y : R) :
    W.veluGx x y + W.veluGx x (W.toAffine.negY x y) = W.veluT x y := by
  simp only [veluGx, veluGy, veluT, Affine.negY]
  ring

theorem asymWeight_add_asymWeight_negY {R : Type*} [CommRing R] (W : WeierstrassCurve R) (x y : R) :
    (x * W.veluGx x y - y * W.veluGy x y) +
        (x * W.veluGx x (W.toAffine.negY x y) -
          W.toAffine.negY x y * W.veluGy x (W.toAffine.negY x y)) =
      W.veluW x y := by
  simp only [veluGx, veluGy, veluT, veluW, veluU, Affine.negY]
  ring

theorem coordsOrZero_neg {R : Type*} [CommRing R] {W : WeierstrassCurve R} (P : W.toAffine.Point)
    (hP : P ≠ 0) :
    (-P).coordsOrZero = (P.coordsOrZero.1, W.toAffine.negY P.coordsOrZero.1 P.coordsOrZero.2) := by
  rcases P with _ | ⟨x, y, h⟩
  · exact absurd rfl hP
  · rfl

theorem eq_of_coordsOrZero_eq {R : Type*} [CommRing R] {W : WeierstrassCurve R} {P P' : W.toAffine.Point}
    (hP : P ≠ 0) (hP' : P' ≠ 0) (h : P.coordsOrZero = P'.coordsOrZero) : P = P' := by
  rcases P with _ | ⟨x, y, hx⟩
  · exact absurd rfl hP
  rcases P' with _ | ⟨x', y', hx'⟩
  · exact absurd rfl hP'
  simp only [Affine.Point.coordsOrZero_some, Prod.mk.injEq] at h
  obtain ⟨rfl, rfl⟩ := h
  rfl

theorem negPair_injective {R : Type*} [CommRing R] (W : WeierstrassCurve R) :
    Function.Injective (fun P : R × R => (P.1, W.toAffine.negY P.1 P.2)) := by
  intro P P' h
  simp only [Prod.mk.injEq] at h
  obtain ⟨h1, h2⟩ := h
  have h3 := congrArg (W.toAffine.negY P.1) h2
  rw [Affine.negY_negY, h1, Affine.negY_negY] at h3
  exact Prod.ext h1 h3

theorem oddOrderSummingSet_two_mul {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) :
    W.oddOrderSummingSet Q (2 * n) =
      W.oddOrderSummingSet Q n ∪
        (W.oddOrderSummingSet Q n).image (fun P => (P.1, W.toAffine.negY P.1 P.2)) := by
  have hrefl : ∀ k, 1 ≤ k → k ≤ 2 * n → (2 * n + 1 - k) • Q = -(k • Q) := fun k hk1 hk2 => by
    refine eq_neg_of_add_eq_zero_left ?_
    rw [← add_nsmul, Nat.sub_add_cancel (by omega), ← hQ, addOrderOf_nsmul_eq_zero]
  have hne : ∀ k, 1 ≤ k → k ≤ 2 * n → k • Q ≠ 0 := fun k hk1 hk2 =>
    nsmul_ne_zero_of_lt_addOrderOf (by omega) (by rw [hQ]; omega)
  ext P
  simp only [Finset.mem_union, Finset.mem_image, mem_oddOrderSummingSet]
  constructor
  · rintro ⟨k, hk1, hk2, rfl⟩
    by_cases hkn : k ≤ n
    · exact Or.inl ⟨k, hk1, hkn, rfl⟩
    · refine Or.inr ⟨((2 * n + 1 - k) • Q).coordsOrZero, ⟨2 * n + 1 - k, by omega, by omega, rfl⟩, ?_⟩
      rw [hrefl k hk1 hk2, coordsOrZero_neg _ (hne k hk1 hk2), Affine.negY_negY]
  · rintro (⟨k, hk1, hk2, rfl⟩ | ⟨P', ⟨k, hk1, hk2, rfl⟩, rfl⟩)
    · exact ⟨k, hk1, by omega, rfl⟩
    · refine ⟨2 * n + 1 - k, by omega, by omega, ?_⟩
      rw [hrefl k hk1 (by omega), coordsOrZero_neg _ (hne k hk1 (by omega))]

theorem disjoint_oddOrderSummingSet_image_neg {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) :
    Disjoint (W.oddOrderSummingSet Q n)
      ((W.oddOrderSummingSet Q n).image (fun P => (P.1, W.toAffine.negY P.1 P.2))) := by
  have hne : ∀ k, 1 ≤ k → k ≤ n → k • Q ≠ 0 := fun k hk1 hk2 =>
    nsmul_ne_zero_of_lt_addOrderOf (by omega) (by rw [hQ]; omega)
  rw [Finset.disjoint_left]
  rintro P hP hP'
  rw [mem_oddOrderSummingSet] at hP
  simp only [Finset.mem_image, mem_oddOrderSummingSet] at hP'
  obtain ⟨k, hk1, hk2, rfl⟩ := hP
  obtain ⟨P', ⟨l, hl1, hl2, rfl⟩, h⟩ := hP'
  rw [← coordsOrZero_neg _ (hne l hl1 hl2)] at h
  have hkl : -(l • Q) = k • Q := eq_of_coordsOrZero_eq (neg_ne_zero.mpr (hne l hl1 hl2)) (hne k hk1 hk2) h
  have h0 : (k + l) • Q = 0 := by rw [add_nsmul, ← hkl, neg_add_cancel]
  have hdvd : addOrderOf Q ∣ k + l := addOrderOf_dvd_iff_nsmul_eq_zero.mpr h0
  rw [hQ] at hdvd
  exact absurd (Nat.le_of_dvd (by omega) hdvd) (by omega)

theorem sum_oddOrderSummingSet_two_mul {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) (f : F × F → F) :
    ∑ P ∈ W.oddOrderSummingSet Q (2 * n), f P =
      ∑ P ∈ W.oddOrderSummingSet Q n, (f P + f (P.1, W.toAffine.negY P.1 P.2)) := by
  rw [oddOrderSummingSet_two_mul W Q n hQ, Finset.sum_union (disjoint_oddOrderSummingSet_image_neg W Q n hQ),
    Finset.sum_image (fun P _ P' _ h => negPair_injective W h), Finset.sum_add_distrib]

theorem sum_veluGx_oddOrderSummingSet_two_mul {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) :
    ∑ P ∈ W.oddOrderSummingSet Q (2 * n), W.veluGx P.1 P.2 = W.veluTSum (W.oddOrderSummingSet Q n) := by
  rw [sum_oddOrderSummingSet_two_mul W Q n hQ (fun P => W.veluGx P.1 P.2), veluTSum]
  exact Finset.sum_congr rfl fun P _ => veluGx_add_veluGx_negY W P.1 P.2

theorem sum_asymWeight_oddOrderSummingSet_two_mul {F : Type*} [Field F] [DecidableEq F]
    (W : WeierstrassCurve F) (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) :
    ∑ P ∈ W.oddOrderSummingSet Q (2 * n), (P.1 * W.veluGx P.1 P.2 - P.2 * W.veluGy P.1 P.2) =
      W.veluWSum (W.oddOrderSummingSet Q n) := by
  rw [sum_oddOrderSummingSet_two_mul W Q n hQ (fun P => P.1 * W.veluGx P.1 P.2 - P.2 * W.veluGy P.1 P.2),
    veluWSum]
  exact Finset.sum_congr rfl fun P _ => asymWeight_add_asymWeight_negY W P.1 P.2

theorem fullKernelQuotient_eq_veluQuotient_oddOrderSummingSet {F : Type*} [Field F] [DecidableEq F]
    (W : WeierstrassCurve F) (Q : W.toAffine.Point) (n : ℕ) (hQ : addOrderOf Q = 2 * n + 1) :
    W.fullKernelQuotient Q (2 * n + 1) = W.veluQuotient (W.oddOrderSummingSet Q n) := by
  rw [fullKernelQuotient, Nat.add_sub_cancel, sum_veluGx_oddOrderSummingSet_two_mul W Q n hQ,
    sum_asymWeight_oddOrderSummingSet_two_mul W Q n hQ]
  exact (W.veluQuotient_eq_veluQuotientOfSums _).symm

theorem fullKernelQuotient_one {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F)
    (Q : W.toAffine.Point) : W.fullKernelQuotient Q 1 = W := by
  have h : W.oddOrderSummingSet Q (1 - 1) = ∅ := by
    simp [oddOrderSummingSet]
  rw [fullKernelQuotient, h, Finset.sum_empty, Finset.sum_empty]
  ext <;> simp [veluQuotientOfSums]

theorem fullKernelQuotient_two {F : Type*} [Field F] [DecidableEq F] (W : WeierstrassCurve F) {x y : F}
    (h : W.toAffine.Nonsingular x y) (hgy : W.veluGy x y = 0) :
    W.fullKernelQuotient (.some x y h) 2 = W.veluQuotient2 x y := by
  have hS : W.oddOrderSummingSet (.some x y h) (2 - 1) = {(x, y)} := by
    simp [oddOrderSummingSet]
  rw [fullKernelQuotient, hS]
  simp only [Finset.sum_singleton]
  rw [hgy, mul_zero, sub_zero]
  rfl

end WeierstrassCurve
