import Definitions.Def_CohCarrier_Level

set_option autoImplicit false

namespace CohCarrier

open CongruenceSubgroup
open scoped MatrixGroups

section RightCosets

variable {Γ : Type*} [Group Γ] (S : Subgroup Γ)

noncomputable def rep (q : Quotient (QuotientGroup.rightRel S)) : Γ := q.out

abbrev cls (g : Γ) : Quotient (QuotientGroup.rightRel S) := Quotient.mk _ g

noncomputable def slip (q : Quotient (QuotientGroup.rightRel S)) (γ : Γ) : S :=
  ⟨rep S q * γ * (rep S (cls S (rep S q * γ)))⁻¹, by
    have h : QuotientGroup.rightRel S (rep S (cls S (rep S q * γ))) (rep S q * γ) :=
      Quotient.exact' (by simp [rep, cls])
    rw [QuotientGroup.rightRel_apply] at h
    simpa [mul_assoc] using h⟩

end RightCosets

section Unipotent

def uMat (t : ℤ) : SL(2, ℤ) :=
  ⟨!![1, t; 0, 1], by rw [Matrix.det_fin_two_of]; ring⟩

@[simp] theorem uMat_apply_00 (t : ℤ) : (uMat t) 0 0 = 1 := rfl
@[simp] theorem uMat_apply_01 (t : ℤ) : (uMat t) 0 1 = t := rfl
@[simp] theorem uMat_apply_10 (t : ℤ) : (uMat t) 1 0 = 0 := rfl
@[simp] theorem uMat_apply_11 (t : ℤ) : (uMat t) 1 1 = 1 := rfl

variable (M : ℕ) (H : Subgroup (ZMod M)ˣ)

theorem uMat_mem_GammaH (t : ℤ) : uMat t ∈ GammaH M H := by
  have h0 : uMat t ∈ Gamma0 M := by
    rw [Gamma0_mem]; show ((0 : ℤ) : ZMod M) = 0; exact Int.cast_zero
  refine mem_GammaH_iff.mpr ⟨h0, ?_⟩
  have e : gamma0Units M ⟨uMat t, h0⟩ = 1 := by
    apply Units.ext; rw [val_gamma0Units, Units.val_one]
    show ((1 : ℤ) : ZMod M) = 1; exact Int.cast_one
  rw [e]; exact H.one_mem

def uElt (t : ℤ) : ↥(GammaH M H) := ⟨uMat t, uMat_mem_GammaH M H t⟩

@[simp] theorem coe_uElt (t : ℤ) : ((uElt M H t : ↥(GammaH M H)) : SL(2, ℤ)) = uMat t := rfl

theorem mul_inv_apply_01 (x y : SL(2, ℤ)) :
    (x * y⁻¹ : SL(2, ℤ)) 0 1 = -(x 0 0 * y 0 1) + x 0 1 * y 0 0 := by
  rw [Matrix.SpecialLinearGroup.coe_mul, Matrix.SpecialLinearGroup.coe_inv, Matrix.adjugate_fin_two,
    Matrix.mul_apply, Fin.sum_univ_two]
  simp only [Fin.isValue, Matrix.of_apply, Matrix.cons_val', Matrix.empty_val',
    Matrix.cons_val_fin_one, Matrix.cons_val_zero, Matrix.cons_val_one]
  ring

variable (ℓ : ℕ)

theorem rightRel_mk_eq_mk_iff (x y : ↥(GammaH M H)) :
    (Quotient.mk'' x : Quotient (QuotientGroup.rightRel (GammaHUpper M H ℓ))) = Quotient.mk'' y ↔
      ((((y : SL(2, ℤ)) * (x : SL(2, ℤ))⁻¹) 0 1 : ℤ) : ZMod ℓ) = 0 := by
  rw [Quotient.eq'', QuotientGroup.rightRel_apply, Subgroup.mem_subgroupOf, mem_Gamma0Upper,
    Subgroup.coe_mul, Subgroup.coe_inv]

attribute [local instance] Subgroup.fintypeQuotientOfFiniteIndex

noncomputable def rightQuotEquivOfDvd [NeZero ℓ] (hℓM : ℓ ∣ M) :
    ZMod ℓ ≃ Quotient (QuotientGroup.rightRel (GammaHUpper M H ℓ)) := by
  refine Equiv.ofBijective (fun j => Quotient.mk'' (uElt M H (j.val : ℤ))) ⟨?_, ?_⟩
  · intro j j' h
    rw [rightRel_mk_eq_mk_iff, mul_inv_apply_01] at h
    simp only [coe_uElt, uMat_apply_00, uMat_apply_01] at h
    push_cast at h; simp only [ZMod.natCast_zmod_val] at h
    linear_combination -h
  · intro q
    induction q using Quotient.inductionOn' with
    | h g =>
      have hdet := Matrix.SpecialLinearGroup.det_coe (g : SL(2, ℤ))
      rw [Matrix.det_fin_two] at hdet
      have hdet' := congrArg (Int.cast : ℤ → ZMod ℓ) hdet
      push_cast at hdet'
      have hg0 : ((g : SL(2, ℤ)) : SL(2, ℤ)) ∈ Gamma0 M := (mem_GammaH_iff.mp g.2).1
      have hc : ((((g : SL(2, ℤ)) 1 0 : ℤ)) : ZMod ℓ) = 0 := by
        rw [ZMod.intCast_zmod_eq_zero_iff_dvd]
        rw [Gamma0_mem, ZMod.intCast_zmod_eq_zero_iff_dvd] at hg0
        exact dvd_trans (Int.natCast_dvd_natCast.mpr hℓM) hg0
      refine ⟨(((g : SL(2, ℤ)) 1 1 : ℤ) : ZMod ℓ) * (((g : SL(2, ℤ)) 0 1 : ℤ) : ZMod ℓ), ?_⟩
      show (Quotient.mk'' (uElt M H (((((g : SL(2, ℤ)) 1 1 : ℤ) : ZMod ℓ)
          * (((g : SL(2, ℤ)) 0 1 : ℤ) : ZMod ℓ)).val : ℤ)) :
          Quotient (QuotientGroup.rightRel (GammaHUpper M H ℓ))) = Quotient.mk'' g
      rw [rightRel_mk_eq_mk_iff, mul_inv_apply_01]
      simp only [coe_uElt, uMat_apply_00, uMat_apply_01]
      push_cast; rw [ZMod.natCast_zmod_val]
      linear_combination (-((((g : SL(2, ℤ)) 0 1 : ℤ)) : ZMod ℓ)) * hdet'
        + (-(((((g : SL(2, ℤ)) 0 1 : ℤ)) : ZMod ℓ) * ((((g : SL(2, ℤ)) 0 1 : ℤ)) : ZMod ℓ))) * hc

@[simp] theorem rightQuotEquivOfDvd_apply [NeZero ℓ] (hℓM : ℓ ∣ M) (j : ZMod ℓ) :
    rightQuotEquivOfDvd M H ℓ hℓM j = Quotient.mk'' (uElt M H (j.val : ℤ)) := rfl

end Unipotent

section CosetProjection

variable {M M' : ℕ} {H : Subgroup (ZMod M)ˣ} {H' : Subgroup (ZMod M')ˣ}
variable (h : LevelLE M M' H H' 1) (ℓ : ℕ)

theorem coe_iotaDeg_one (γ : ↥(GammaH M' H')) :
    ((iotaDeg M M' H H' 1 h γ : ↥(GammaH M H)) : SL(2, ℤ)) = (γ : SL(2, ℤ)) := by
  refine Matrix.SpecialLinearGroup.ext _ _ fun i j => ?_
  fin_cases i <;> fin_cases j <;> simp [iotaDeg, conjLowerMat, Matrix.of_apply, Fin.isValue]

theorem iotaDeg_one_mem_GammaHUpper_iff (z : ↥(GammaH M' H')) :
    iotaDeg M M' H H' 1 h z ∈ GammaHUpper M H ℓ ↔ z ∈ GammaHUpper M' H' ℓ := by
  simp only [GammaHUpper, Subgroup.mem_subgroupOf, mem_Gamma0Upper, coe_iotaDeg_one h]

noncomputable def πCoset :
    Quotient (QuotientGroup.rightRel (GammaHUpper M' H' ℓ)) →
      Quotient (QuotientGroup.rightRel (GammaHUpper M H ℓ)) :=
  Quotient.map' (iotaDeg M M' H H' 1 h) fun x y hxy => by
    rw [QuotientGroup.rightRel_apply] at hxy ⊢
    rw [show (iotaDeg M M' H H' 1 h y) * (iotaDeg M M' H H' 1 h x)⁻¹
      = iotaDeg M M' H H' 1 h (y * x⁻¹) from by rw [map_mul, map_inv]]
    exact (iotaDeg_one_mem_GammaHUpper_iff h ℓ _).mpr hxy

theorem πCoset_mk (γ' : ↥(GammaH M' H')) :
    πCoset h ℓ (Quotient.mk'' γ') = Quotient.mk'' (iotaDeg M M' H H' 1 h γ') :=
  Quotient.map'_mk'' _ _ γ'

end CosetProjection

section LowerLeg

variable (M : ℕ) (H : Subgroup (ZMod M)ˣ) (q : ℕ)

abbrev GammaHLower : Subgroup ↥(GammaH M H) := (Gamma0 (q * M)).subgroupOf (GammaH M H)

instance GammaHLower_finiteIndex [NeZero M] [NeZero q] : (GammaHLower M H q).FiniteIndex := by
  haveI : NeZero (q * M) := ⟨mul_ne_zero (NeZero.ne q) (NeZero.ne M)⟩
  exact Subgroup.instFiniteIndex_subgroupOf _ _

theorem qM_dvd_of_mem_GammaHLower (γ : ↥(GammaHLower M H q)) :
    ((q * M : ℕ) : ℤ) ∣ (γ : SL(2, ℤ)) 1 0 := by
  have h : ((γ : ↥(GammaH M H)) : SL(2, ℤ)) ∈ Gamma0 (q * M) := Subgroup.mem_subgroupOf.mp γ.2
  rw [Gamma0_mem] at h
  exact (ZMod.intCast_zmod_eq_zero_iff_dvd _ _).mp h

theorem dvd_of_mem_GammaHLower (γ : ↥(GammaHLower M H q)) : (q : ℤ) ∣ (γ : SL(2, ℤ)) 1 0 :=
  dvd_trans (by exact_mod_cast dvd_mul_right q M) (qM_dvd_of_mem_GammaHLower M H q γ)

theorem mem_GammaHLower_iff_of_coprime (hqM : Nat.Coprime q M) (γ : ↥(GammaH M H)) :
    γ ∈ GammaHLower M H q ↔ (q : ℤ) ∣ (γ : SL(2, ℤ)) 1 0 := by
  rw [GammaHLower, Subgroup.mem_subgroupOf, Gamma0_mem, ZMod.intCast_zmod_eq_zero_iff_dvd]
  constructor
  · intro h; exact dvd_trans (by exact_mod_cast dvd_mul_right q M) h
  · intro hq
    have hM : (M : ℤ) ∣ (γ : SL(2, ℤ)) 1 0 :=
      (ZMod.intCast_zmod_eq_zero_iff_dvd _ _).mp (Gamma0_mem.mp ((mem_GammaH_iff.mp γ.2).1))
    have hcop : IsCoprime (q : ℤ) (M : ℤ) := Nat.isCoprime_iff_coprime.mpr hqM
    exact_mod_cast hcop.mul_dvd hq hM

theorem conjLowerMat_mem_of_GammaHLower [NeZero q] (γ : ↥(GammaHLower M H q)) :
    conjLowerMat q (γ : SL(2, ℤ)) (dvd_of_mem_GammaHLower M H q γ) ∈ GammaH M H := by
  obtain ⟨hγ0, hγH⟩ := mem_GammaH_iff.mp (γ : ↥(GammaH M H)).2
  rw [mem_GammaH_iff]
  refine ⟨?_, ?_⟩
  · rw [Gamma0_mem]
    show ((((γ : SL(2, ℤ)) 1 0) / (q : ℤ) : ℤ) : ZMod M) = 0
    rw [ZMod.intCast_zmod_eq_zero_iff_dvd]
    obtain ⟨k, hk⟩ := qM_dvd_of_mem_GammaHLower M H q γ
    rw [hk, show ((q * M : ℕ) : ℤ) * k = (q : ℤ) * ((M : ℤ) * k) from by push_cast; ring,
      Int.mul_ediv_cancel_left _ (Nat.cast_ne_zero.mpr (NeZero.ne q))]
    exact Dvd.intro k rfl
  · exact hγH

noncomputable def conjLowerL [NeZero q] : ↥(GammaHLower M H q) →* ↥(GammaH M H) where
  toFun γ := ⟨conjLowerMat q (γ : SL(2, ℤ)) (dvd_of_mem_GammaHLower M H q γ),
    conjLowerMat_mem_of_GammaHLower M H q γ⟩
  map_one' := by
    apply Subtype.ext
    apply Matrix.SpecialLinearGroup.ext
    intro i j
    fin_cases i <;> fin_cases j <;> simp [conjLowerMat]
  map_mul' γ δ :=
    Subtype.ext (conjLowerMat_mul q (γ : SL(2, ℤ)) (δ : SL(2, ℤ)) (dvd_of_mem_GammaHLower M H q γ)
      (dvd_of_mem_GammaHLower M H q δ) (dvd_of_mem_GammaHLower M H q (γ * δ)))

variable (A : Type*) [AddCommGroup A]

noncomputable def heckeTlower [NeZero M] [NeZero q] : H1 M H A →+ H1 M H A where
  toFun φ :=
    MonoidHom.toAdditiveLeft
      (MonoidHom.transfer ((AddMonoidHom.toMultiplicativeRight φ).comp (conjLowerL M H q)))
  map_zero' := by
    simp only [toMultiplicativeRight_zero, MonoidHom.one_comp, transfer_one, toAdditiveLeft_one]
  map_add' φ ψ := by
    simp only [toMultiplicativeRight_add, MonoidHom.mul_comp, transfer_mul, toAdditiveLeft_mul]

end LowerLeg

end CohCarrier
