/-
Copyright (c) 2020 Ruben Van de Velde. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
-/

import Flt.Primes
import Flt.Edwards
import Mathlib.Tactic.IntervalCases

/-- solutions to Fermat's last theorem for the exponent `3`. -/
def FltSolution (n : ℕ) (a b c : ℤ) :=
  a ≠ 0 ∧ b ≠ 0 ∧ c ≠ 0 ∧ a ^ n + b ^ n = c ^ n
#align flt_solution FltSolution

/-- Coprime solutions to Fermat's last theorem for the exponent `3`. -/
def FltCoprime (n : ℕ) (a b c : ℤ) :=
  FltSolution n a b c ∧ IsCoprime a b ∧ IsCoprime a c ∧ IsCoprime b c
#align flt_coprime FltCoprime

theorem exists_coprime {n : ℕ} (hn : 0 < n) {a b c : ℤ} (ha' : a ≠ 0) (hb' : b ≠ 0) (hc' : c ≠ 0)
    (h : a ^ n + b ^ n = c ^ n) :
    ∃ a' b' c' : ℤ,
      a'.natAbs ≤ a.natAbs ∧ b'.natAbs ≤ b.natAbs ∧ c'.natAbs ≤ c.natAbs ∧ FltCoprime n a' b' c' :=
  by
  set d := Int.gcd a b with hd'
  obtain ⟨A, HA⟩ : ↑d ∣ a := Int.gcd_dvd_left a b
  obtain ⟨B, HB⟩ : ↑d ∣ b := Int.gcd_dvd_right a b
  obtain ⟨C, HC⟩ : ↑d ∣ c :=
    by
    rw [← Int.pow_dvd_pow_iff hn, ← h, HA, HB, mul_pow, mul_pow, ← mul_add]
    exact dvd_mul_right _ _
  have hdpos : 0 < d := Int.gcd_pos_of_non_zero_left _ ha'
  have hd := Int.coe_nat_ne_zero_iff_pos.mpr hdpos
  have hsoln : A ^ n + B ^ n = C ^ n :=
    by
    apply Int.eq_of_mul_eq_mul_left (pow_ne_zero n hd)
    simp only [mul_add, ← mul_pow, ← HA, ← HB, ← HC, h]
  have hsoln' : B ^ n + A ^ n = C ^ n := by rwa [add_comm] at hsoln
  have hcoprime : IsCoprime A B :=
    by
    rw [← Int.gcd_eq_one_iff_coprime]
    apply Nat.eq_of_mul_eq_mul_left hdpos
    rw [← Int.natAbs_ofNat d, ← Int.gcd_mul_left, ← HA, ← HB, hd', Int.natAbs_ofNat, mul_one]
  have HA' : A.natAbs ≤ a.natAbs := by
    rw [HA]
    simp only [Int.natAbs_ofNat, Int.natAbs_mul]
    exact le_mul_of_one_le_left' (Nat.succ_le_iff.mpr hdpos)
  have HB' : B.natAbs ≤ b.natAbs := by
    rw [HB]
    simp only [Int.natAbs_ofNat, Int.natAbs_mul]
    exact le_mul_of_one_le_left' (Nat.succ_le_iff.mpr hdpos)
  have HC' : C.natAbs ≤ c.natAbs := by
    rw [HC]
    simp only [Int.natAbs_ofNat, Int.natAbs_mul]
    exact le_mul_of_one_le_left' (Nat.succ_le_iff.mpr hdpos)
  exact
    ⟨A, B, C, HA', HB', HC',
      ⟨right_ne_zero_of_mul (by rwa [HA] at ha'), right_ne_zero_of_mul (by rwa [HB] at hb'),
        right_ne_zero_of_mul (by rwa [HC] at hc'), hsoln⟩,
      hcoprime, coprime_add_self_pow hn hsoln hcoprime,
      coprime_add_self_pow hn hsoln' hcoprime.symm⟩
#align exists_coprime exists_coprime

theorem descent1a {a b c : ℤ} (h : a ^ 3 + b ^ 3 = c ^ 3) (habcoprime : IsCoprime a b)
    (haccoprime : IsCoprime a c) (hbccoprime : IsCoprime b c) :
    (Even a ∧ ¬Even b ∧ ¬Even c ∨ ¬Even a ∧ Even b ∧ ¬Even c) ∨ ¬Even a ∧ ¬Even b ∧ Even c :=
  by
  have contra : ∀ {x y : ℤ}, IsCoprime x y → Even x → Even y → False :=
    by
    intro x y hcoprime hx hy
    rw [even_iff_two_dvd] at hx hy
    have := Int.isUnit_eq_one_or (hcoprime.isUnit_of_dvd' hx hy)
    norm_num at this
  by_cases haparity : Even a <;> by_cases hbparity : Even b <;> by_cases hcparity : Even c
  · exact False.elim (contra habcoprime ‹_› ‹_›)
  · exact False.elim (contra habcoprime ‹_› ‹_›)
  · exact False.elim (contra haccoprime ‹_› ‹_›)
  · tauto
  · exact False.elim (contra hbccoprime ‹_› ‹_›)
  · tauto
  · tauto
  · exfalso
    apply hcparity
    rw [← Int.even_pow' three_ne_zero, ← h]
    simp [haparity, hbparity, three_ne_zero, parity_simps]
#align descent1a descent1a

theorem flt_not_add_self {a b c : ℤ} (ha : a ≠ 0) (h : a ^ 3 + b ^ 3 = c ^ 3) : a ≠ b :=
  by
  rintro rfl
  rw [← mul_two] at h
  obtain ⟨d, rfl⟩ : a ∣ c :=
    by
    rw [← Int.pow_dvd_pow_iff (by norm_num : 0 < 3), ← h]
    apply dvd_mul_right
  apply Int.two_not_cube d
  rwa [mul_pow, mul_right_inj' (pow_ne_zero 3 ha), eq_comm] at h
#align flt_not_add_self flt_not_add_self

theorem descent1left {a b c : ℤ} (hapos : a ≠ 0) (h : a ^ 3 + b ^ 3 = c ^ 3)
    (hbccoprime : IsCoprime b c) (hb : ¬Even b) (hc : ¬Even c) :
    ∃ p q : ℤ,
      p ≠ 0 ∧ q ≠ 0 ∧ IsCoprime p q ∧ (Even p ↔ ¬Even q) ∧ 2 * p * (p ^ 2 + 3 * q ^ 2) = a ^ 3 :=
  by
  obtain ⟨p, hp⟩ : Even (c - b) := by simp [hb, hc, parity_simps]
  obtain ⟨q, hq⟩ : Even (c + b) := by simp [hb, hc, parity_simps]
  rw [← two_mul] at hp hq
  obtain rfl : p + q = c := by
    apply Int.eq_of_mul_eq_mul_left two_ne_zero
    rw [mul_add, ← hp, ← hq]
    ring
  obtain rfl : q - p = b := by
    apply Int.eq_of_mul_eq_mul_left two_ne_zero
    rw [mul_sub, ← hp, ← hq]
    ring
  have hpnezero : p ≠ 0 := by
    rintro rfl
    rw [sub_zero, zero_add, add_left_eq_self] at h
    exact hapos (pow_eq_zero h)
  have hqnezero : q ≠ 0 := by
    rintro rfl
    rw [zero_sub, add_zero, Odd.neg_pow (by norm_num), ← sub_eq_add_neg, sub_eq_iff_eq_add] at h
    exact flt_not_add_self hpnezero h.symm rfl
  refine' ⟨p, q, hpnezero, hqnezero, _, _, _⟩
  · apply isCoprime_of_dvd _ _ (not_and_of_not_left _ hpnezero)
    rintro z hznu - hzp hzq
    apply hznu
    exact hbccoprime.isUnit_of_dvd' (dvd_sub hzq hzp) (dvd_add hzp hzq)
  · constructor <;> intro H <;> simpa [H, parity_simps] using hc
  · rw [eq_sub_of_add_eq h]
    ring
#align descent1left descent1left

theorem descent1 (a b c : ℤ) (h : FltCoprime 3 a b c) :
    ∃ p q : ℤ,
      p ≠ 0 ∧
        q ≠ 0 ∧
          IsCoprime p q ∧
            (Even p ↔ ¬Even q) ∧
              (2 * p * (p ^ 2 + 3 * q ^ 2) = a ^ 3 ∨
                2 * p * (p ^ 2 + 3 * q ^ 2) = b ^ 3 ∨ 2 * p * (p ^ 2 + 3 * q ^ 2) = c ^ 3) :=
  by
  obtain ⟨⟨hapos, hbpos, hcpos, h⟩, habcoprime, haccoprime, hbccoprime⟩ := h
  obtain (⟨-, hb, hc⟩ | ⟨ha, hb, hc⟩) | ⟨ha, hb, -⟩ :=
    descent1a h habcoprime haccoprime hbccoprime
  · obtain ⟨p, q, hp, hq, hcoprime, hodd, hcube⟩ := descent1left hapos h hbccoprime hb hc
    exact ⟨p, q, hp, hq, hcoprime, hodd, Or.inl hcube⟩
  · rw [add_comm] at h
    obtain ⟨p, q, hp, hq, hcoprime, hodd, hcube⟩ := descent1left hbpos h haccoprime ha hc
    refine' ⟨p, q, hp, hq, hcoprime, hodd, Or.inr <| Or.inl hcube⟩
  · have := descent1left hcpos ?_ habcoprime.neg_left ?_ hb
    obtain ⟨p, q, hp, hq, hcoprime, hodd, hcube⟩ := this
    exact ⟨p, q, hp, hq, hcoprime, hodd, Or.inr <| Or.inr hcube⟩
    · rw [← h]
      ring
    · simp [ha, parity_simps]
#align descent1 descent1

theorem descent11 {a b c d : ℤ} (h : d = a ∨ d = b ∨ d = c) : d ∣ a * b * c :=
  by
  rcases h with (rfl | rfl | rfl)
  · exact (dvd_mul_right _ _).mul_right _
  · exact (dvd_mul_left _ _).mul_right _
  · exact dvd_mul_left _ _
#align descent11 descent11

theorem descent2 (a b c : ℤ) (h : FltCoprime 3 a b c) :
    ∃ p q : ℤ,
      p ≠ 0 ∧
        q ≠ 0 ∧
          IsCoprime p q ∧
            (Even p ↔ ¬Even q) ∧
              (2 * p * (p ^ 2 + 3 * q ^ 2) = a ^ 3 ∨
                  2 * p * (p ^ 2 + 3 * q ^ 2) = b ^ 3 ∨ 2 * p * (p ^ 2 + 3 * q ^ 2) = c ^ 3) ∧
                (2 * p).natAbs < (a ^ 3 * b ^ 3 * c ^ 3).natAbs :=
  by
  obtain ⟨p, q, hp, hq, hcoprime, hodd, hcube⟩ := descent1 a b c h
  refine' ⟨p, q, hp, hq, hcoprime, hodd, hcube, _⟩
  obtain ⟨⟨hapos, hbpos, hcpos, -⟩, -⟩ := h
  set P : ℤ√(-3) := ⟨p, q⟩
  apply lt_of_lt_of_le (b := (2 * p * P.norm).natAbs)
  -- calc
  --   (2 * p).natAbs < (2 * p * P.norm).natAbs := ?_
  --   _ ≤ (a ^ 3 * b ^ 3 * c ^ 3).natAbs := ?_

  · rw [Int.natAbs_mul (2 * p)]
    apply lt_mul_of_one_lt_right (Int.natAbs_pos.mpr (mul_ne_zero two_ne_zero hp))
    rw [← Int.ofNat_lt]
    rw [Int.natAbs_of_nonneg (Zsqrtd.norm_nonneg (by norm_num) P)]
    exact Spts.one_lt_of_im_ne_zero ⟨p, q⟩ hq
  · apply Nat.le_of_dvd
    · rw [pos_iff_ne_zero, Int.natAbs_ne_zero, ← mul_pow, ← mul_pow]
      exact pow_ne_zero 3 (mul_ne_zero (mul_ne_zero hapos hbpos) hcpos)
    · rw [Int.natAbs_dvd_natAbs]
      convert descent11 hcube
      rw [Zsqrtd.norm]
      ring

#align descent2 descent2

theorem Nat.cast_three [AddMonoidWithOne R] : ((3 : ℕ) : R) = (3 : R) := rfl

theorem gcd1or3 (p q : ℤ) (hp : p ≠ 0) (hcoprime : IsCoprime p q) (hparity : Even p ↔ ¬Even q) :
    Int.gcd (2 * p) (p ^ 2 + 3 * q ^ 2) = 1 ∨ Int.gcd (2 * p) (p ^ 2 + 3 * q ^ 2) = 3 :=
  by
  set g := Int.gcd (2 * p) (p ^ 2 + 3 * q ^ 2) with hg'
  suffices H : ∃ k, g = 3 ^ k ∧ k < 2
  · obtain ⟨k, hg, hk⟩ := H
    interval_cases k
    · left
      rw [pow_zero] at hg
      exact hg
    · right
      rw [pow_one] at hg
      exact hg
  have basic : ∀ f, Nat.Prime f → (f : ℤ) ∣ 2 * p → (f : ℤ) ∣ p ^ 2 + 3 * q ^ 2 → f = 3 :=
    by
    intro d hdprime hdleft hdright
    by_contra hne3
    rw [← Ne.def] at hne3
    apply (Nat.prime_iff_prime_int.mp hdprime).not_unit
    have hne2 : d ≠ 2 := by
      rintro rfl
      rw [Nat.cast_two, ← even_iff_two_dvd] at hdright
      simp [hparity, two_ne_zero, parity_simps] at hdright
    have : 2 < d := lt_of_le_of_ne hdprime.two_le hne2.symm
    have : 3 < d := lt_of_le_of_ne this hne3.symm
    obtain ⟨P, hP⟩ := hdleft
    obtain ⟨Q, hQ⟩ := hdright
    obtain ⟨H, hH⟩ : 2 ∣ P := by
      have H := dvd_mul_right 2 p
      rw [hP] at H
      apply (Prime.dvd_or_dvd Int.prime_two H).resolve_left
      rw [Int.coe_nat_dvd_right]
      change ¬2 ∣ d
      rw [Nat.prime_dvd_prime_iff_eq Nat.prime_two hdprime]
      exact hne2.symm
    have hp : p = d * H := by rw [← mul_right_inj' (two_ne_zero' ℤ), hP, hH, mul_left_comm]
    apply hcoprime.isUnit_of_dvd'
    · rw [hp]
      exact dvd_mul_right _ _
    · have h000 : d ∣ 3 * q.natAbs ^ 2 :=
        by
        rw [← Int.coe_nat_dvd, Int.ofNat_mul, Int.coe_nat_pow, Int.natAbs_sq, Nat.cast_three]
        use Q - d * H ^ 2
        rw [mul_sub, ← hQ, hp]
        ring
      cases' (Nat.Prime.dvd_mul hdprime).mp h000 with h000 h000
      · rw [Nat.prime_dvd_prime_iff_eq hdprime Nat.prime_three] at h000
        exact absurd h000 hne3
      · rw [Int.coe_nat_dvd_left]
        exact Nat.Prime.dvd_of_dvd_pow hdprime h000
  set k := g.factors.length
  have hg : g = 3 ^ k := by
    apply Nat.eq_prime_pow_of_unique_prime_dvd
    · apply ne_of_gt
      apply Nat.gcd_pos_of_pos_left
      rw [Int.natAbs_mul]
      apply mul_pos zero_lt_two
      rwa [pos_iff_ne_zero, Int.natAbs_ne_zero]
    intro d hdprime hddvdg
    rw [← Int.coe_nat_dvd] at hddvdg
    apply basic _ hdprime <;> apply dvd_trans hddvdg <;> rw [hg']
    exacts[Int.gcd_dvd_left _ _, Int.gcd_dvd_right _ _]
  refine' ⟨k, hg, _⟩
  by_contra' H
  rw [← pow_mul_pow_sub _ H] at hg
  have : ¬IsUnit (3 : ℤ) := by
    rw [Int.isUnit_iff_natAbs_eq]
    norm_num
  apply this
  have hdvdp : 3 ∣ p :=
    by
    suffices 3 ∣ 2 * p
      by
      apply Int.dvd_mul_cancel_prime' _ dvd_rfl Int.prime_two this
      norm_num
    have : 3 ∣ (g : ℤ) := by
      rw [hg, pow_two, mul_assoc, Int.ofNat_mul]
      apply dvd_mul_right
    exact dvd_trans this (Int.gcd_dvd_left _ _)
  apply IsCoprime.isUnit_of_dvd' hcoprime hdvdp
  · rw [← Int.pow_dvd_pow_iff zero_lt_two] at hdvdp
    apply Prime.dvd_of_dvd_pow Int.prime_three
    rw [← mul_dvd_mul_iff_left (three_ne_zero' ℤ), ← pow_two, dvd_add_iff_right hdvdp]
    refine' dvd_trans _ (Int.gcd_dvd_right (2 * p) (p ^ 2 + 3 * q ^ 2))
    rw [← hg', hg, Int.ofNat_mul]
    apply dvd_mul_right
#align gcd1or3 gcd1or3

theorem obscure' (p q : ℤ) (hp : p ≠ 0) (hcoprime : IsCoprime p q) (hparity : Even p ↔ ¬Even q)
    (hcube : ∃ r, p ^ 2 + 3 * q ^ 2 = r ^ 3) :
    ∃ a b,
      p = a * (a - 3 * b) * (a + 3 * b) ∧
        q = 3 * b * (a - b) * (a + b) ∧ IsCoprime a b ∧ (Even a ↔ ¬Even b) :=
  by
  obtain ⟨u, hu⟩ := hcube
  obtain ⟨a, b, hp', hq'⟩ := step6 p q u hcoprime hu.symm
  refine' ⟨a, b, _, _, _, _⟩
  · rw [hp']
    ring
  · rw [hq']
    ring
  · apply isCoprime_of_dvd
    · rintro ⟨rfl, rfl⟩
      norm_num at hp'
      contradiction
    rintro k hknu - hkdvdleft hkdvdright
    apply hknu
    apply hcoprime.isUnit_of_dvd'
    · rw [hp']
      apply dvd_sub
      · exact dvd_pow hkdvdleft (by norm_num)
      · rw [mul_comm (9 : ℤ), mul_assoc]
        exact hkdvdleft.mul_right _
    · rw [hq']
      apply dvd_sub
      · exact hkdvdright.mul_left _
      · exact (hkdvdright.pow (by norm_num)).mul_left _
  ·
    by_cases haparity : Even a <;> by_cases hbparity : Even b <;> [skip, tauto, tauto, skip] <;>
      · exfalso
        have : Even p := by
          rw [hp']
          simp [haparity, hbparity, three_ne_zero, parity_simps]
        have : Even q := by
          rw [hq']
          simp [haparity, hbparity, three_ne_zero, parity_simps]
        tauto
#align obscure' obscure'

theorem Int.eq_pow_of_mul_eq_pow_odd {a b c : ℤ} (hab : IsCoprime a b) {k : ℕ} (hk : Odd k)
    (h : a * b = c ^ k) : (∃ d, a = d ^ k) ∧ ∃ e, b = e ^ k := by
  obtain ⟨k, rfl⟩ := hk.exists_bit1
  exact Int.eq_pow_of_mul_eq_pow_bit1 hab h

theorem Int.cube_of_coprime (a b c s : ℤ) (ha : a ≠ 0) (hb : b ≠ 0) (hc : c ≠ 0)
    (hcoprimeab : IsCoprime a b) (hcoprimeac : IsCoprime a c) (hcoprimebc : IsCoprime b c)
    (hs : a * b * c = s ^ 3) : ∃ A B C, A ≠ 0 ∧ B ≠ 0 ∧ C ≠ 0 ∧ a = A ^ 3 ∧ b = B ^ 3 ∧ c = C ^ 3 :=
  by
  have : Odd 3 := by norm_num
  obtain ⟨⟨AB, HAB⟩, ⟨C, HC⟩⟩ :=
    Int.eq_pow_of_mul_eq_pow_odd (IsCoprime.mul_left hcoprimeac hcoprimebc) this hs
  obtain ⟨⟨A, HA⟩, ⟨B, HB⟩⟩ := Int.eq_pow_of_mul_eq_pow_odd hcoprimeab this HAB
  refine' ⟨A, B, C, _, _, _, HA, HB, HC⟩ <;> apply ne_zero_pow three_ne_zero
  · rwa [← HA]
  · rwa [← HB]
  · rwa [← HC]
#align int.cube_of_coprime Int.cube_of_coprime

theorem Int.gcd1_coprime12 (u v : ℤ) (huvcoprime : IsCoprime u v) (notdvd_2_2 : ¬2 ∣ u - 3 * v)
    (not_3_dvd_2 : ¬3 ∣ u - 3 * v) : IsCoprime (2 * u) (u - 3 * v) :=
  by
  apply isCoprime_of_prime_dvd
  · rintro ⟨-, h2⟩
    rw [h2] at notdvd_2_2
    norm_num at notdvd_2_2
  intro k hkprime hkdvdleft hkdvdright
  apply hkprime.not_unit
  apply huvcoprime.isUnit_of_dvd'
  · exact Int.dvd_mul_cancel_prime' notdvd_2_2 hkdvdright Int.prime_two hkdvdleft
  · apply Int.dvd_mul_cancel_prime' not_3_dvd_2 hkdvdright Int.prime_three
    apply Int.dvd_mul_cancel_prime' notdvd_2_2 hkdvdright Int.prime_two
    convert dvd_sub hkdvdleft (hkdvdright.mul_left 2) using 1
    ring
#align int.gcd1_coprime12 Int.gcd1_coprime12

theorem Int.gcd1_coprime13 (u v : ℤ) (huvcoprime : IsCoprime u v) (this' : ¬Even (u + 3 * v))
    (notdvd_3_3 : ¬3 ∣ u + 3 * v) : IsCoprime (2 * u) (u + 3 * v) :=
  by
  rw [even_iff_two_dvd] at this'
  apply isCoprime_of_prime_dvd
  · rintro ⟨-, h2⟩
    rw [h2] at this'
    norm_num at this'
  intro k hkprime hkdvdleft hkdvdright
  apply hkprime.not_unit
  apply huvcoprime.isUnit_of_dvd'
  · exact Int.dvd_mul_cancel_prime' this' hkdvdright Int.prime_two hkdvdleft
  · apply Int.dvd_mul_cancel_prime' notdvd_3_3 hkdvdright Int.prime_three
    apply Int.dvd_mul_cancel_prime' this' hkdvdright Int.prime_two
    convert dvd_sub (hkdvdright.mul_left 2) hkdvdleft using 1
    ring
#align int.gcd1_coprime13 Int.gcd1_coprime13

theorem Int.gcd1_coprime23 (u v : ℤ) (huvcoprime : IsCoprime u v) (notdvd_2_2 : ¬2 ∣ u - 3 * v)
    (notdvd_3_3 : ¬3 ∣ u + 3 * v) : IsCoprime (u - 3 * v) (u + 3 * v) :=
  by
  apply isCoprime_of_prime_dvd
  · rintro ⟨h1, -⟩
    rw [h1] at notdvd_2_2
    norm_num at notdvd_2_2
  intro k hkprime hkdvdleft hkdvdright
  apply hkprime.not_unit
  apply huvcoprime.isUnit_of_dvd'
  · apply Int.dvd_mul_cancel_prime' notdvd_2_2 hkdvdleft Int.prime_two
    convert dvd_add hkdvdleft hkdvdright using 1
    ring
  · apply Int.dvd_mul_cancel_prime' notdvd_3_3 hkdvdright Int.prime_three
    apply Int.dvd_mul_cancel_prime' notdvd_2_2 hkdvdleft Int.prime_two
    convert dvd_sub hkdvdright hkdvdleft using 1
    ring
#align int.gcd1_coprime23 Int.gcd1_coprime23

theorem descent_gcd1 (a b c p q : ℤ) (hp : p ≠ 0) (hcoprime : IsCoprime p q)
    (hodd : Even p ↔ ¬Even q)
    (hcube :
      2 * p * (p ^ 2 + 3 * q ^ 2) = a ^ 3 ∨
        2 * p * (p ^ 2 + 3 * q ^ 2) = b ^ 3 ∨ 2 * p * (p ^ 2 + 3 * q ^ 2) = c ^ 3)
    (hgcd : IsCoprime (2 * p) (p ^ 2 + 3 * q ^ 2)) :
    ∃ a' b' c' : ℤ,
      a' ≠ 0 ∧
        b' ≠ 0 ∧
          c' ≠ 0 ∧ (a' ^ 3 * b' ^ 3 * c' ^ 3).natAbs ≤ (2 * p).natAbs ∧ a' ^ 3 + b' ^ 3 = c' ^ 3 :=
  by
  -- 5.
  obtain ⟨r, hr⟩ : ∃ r, 2 * p * (p ^ 2 + 3 * q ^ 2) = r ^ 3 := by
    rcases hcube with (hcube | hcube | hcube) <;> [(use a), (use b), (use c)] <;> exact hcube
  have : Odd 3 := by norm_num
  obtain ⟨hcubeleft, hcuberight⟩ := Int.eq_pow_of_mul_eq_pow_odd hgcd this hr
  -- todo shadowing hq
  obtain ⟨u, v, hpfactor, hq, huvcoprime, huvodd⟩ := obscure' p q hp hcoprime hodd hcuberight
  have u_ne_zero : u ≠ 0 := by
    rintro rfl
    rw [MulZeroClass.zero_mul, MulZeroClass.zero_mul] at hpfactor
    contradiction
  have haaa : 2 * p = 2 * u * (u - 3 * v) * (u + 3 * v) :=
    by
    rw [hpfactor]
    ring
  have : ¬Even (u - 3 * v) := by simp [huvodd, parity_simps]
  have : ¬Even (u + 3 * v) := by simp [huvodd, parity_simps]
  have notdvd_2_2 : ¬2 ∣ u - 3 * v := by
    rw [← even_iff_two_dvd]
    exact ‹¬Even (u - 3 * v)›
  have hddd : ¬3 ∣ p := by
    intro H
    have : 3 ∣ p ^ 2 + 3 * q ^ 2 := by
      apply dvd_add
      · rw [pow_two]
        exact H.mul_left _
      · apply dvd_mul_right
    have : 3 ∣ 2 * p := H.mul_left 2
    have := IsCoprime.isUnit_of_dvd' hgcd ‹_› ‹_›
    rw [isUnit_iff_dvd_one] at this
    norm_num at this
  have not_3_dvd_2 : ¬3 ∣ u - 3 * v := by
    intro hd2
    apply hddd
    rw [hpfactor]
    exact (hd2.mul_left _).mul_right _
  have notdvd_3_3 : ¬3 ∣ u + 3 * v := by
    intro hd3
    apply hddd
    rw [hpfactor]
    exact hd3.mul_left _
  obtain ⟨s, hs⟩ := hcubeleft
  obtain ⟨C, A, B, HCpos, HApos, HBpos, HC, HA, HB⟩ :
    ∃ X Y Z : ℤ, X ≠ 0 ∧ Y ≠ 0 ∧ Z ≠ 0 ∧ 2 * u = X ^ 3 ∧ u - 3 * v = Y ^ 3 ∧ u + 3 * v = Z ^ 3 :=
    by
    apply Int.cube_of_coprime (2 * u) (u - 3 * v) (u + 3 * v) s
    · apply mul_ne_zero two_ne_zero u_ne_zero
    · rw [sub_ne_zero]
      rintro rfl
      simp only [false_or_iff, iff_not_self, parity_simps] at huvodd
    · intro H
      rw [add_eq_zero_iff_eq_neg] at H
      apply iff_not_self
      simpa [H, parity_simps] using huvodd
    · apply Int.gcd1_coprime12 u v <;> assumption
    · apply Int.gcd1_coprime13 u v <;> assumption
    · apply Int.gcd1_coprime23 u v <;> assumption
    · rw [← haaa]
      exact hs
  refine' ⟨A, B, C, HApos, HBpos, HCpos, _, _⟩
  · rw [mul_comm, ← mul_assoc (C ^ 3), ← HA, ← HB, ← HC, ← haaa]
  · rw [← HA, ← HB, ← HC]
    ring
#align descent_gcd1 descent_gcd1

theorem gcd3_coprime {u v : ℤ} (huvcoprime : IsCoprime u v) (huvodd : Even u ↔ ¬Even v) :
    IsCoprime (2 * v) (u + v) ∧ IsCoprime (2 * v) (u - v) ∧ IsCoprime (u - v) (u + v) :=
  by
  have haddodd : ¬Even (u + v) := by simp [huvodd, parity_simps]
  have hsubodd : ¬Even (u - v) := by simp [huvodd, parity_simps]
  have haddcoprime : IsCoprime (u + v) (2 * v) :=
    by
    apply isCoprime_of_prime_dvd
    · rintro ⟨h1, -⟩
      rw [h1] at haddodd
      norm_num at haddodd
    intro k hkprime hkdvdleft hkdvdright
    apply hkprime.not_unit
    have hkdvdright' : k ∣ v := by
      rw [even_iff_two_dvd] at haddodd
      exact Int.dvd_mul_cancel_prime' haddodd hkdvdleft Int.prime_two hkdvdright
    apply huvcoprime.isUnit_of_dvd' _ hkdvdright'
    rw [← add_sub_cancel u v]
    apply dvd_sub hkdvdleft hkdvdright'
  have hsubcoprime : IsCoprime (u - v) (2 * v) :=
    by
    apply isCoprime_of_prime_dvd
    · rintro ⟨h1, -⟩
      rw [h1] at hsubodd
      norm_num at hsubodd
    intro k hkprime hkdvdleft hkdvdright
    apply hkprime.not_unit
    have hkdvdright' : k ∣ v := by
      rw [even_iff_two_dvd] at hsubodd
      exact Int.dvd_mul_cancel_prime' hsubodd hkdvdleft Int.prime_two hkdvdright
    apply huvcoprime.isUnit_of_dvd' _ hkdvdright'
    rw [← sub_add_cancel u v]
    exact dvd_add hkdvdleft hkdvdright'
  have haddsubcoprime : IsCoprime (u + v) (u - v) :=
    by
    apply isCoprime_of_prime_dvd
    · rintro ⟨h1, -⟩
      rw [h1] at haddodd
      norm_num at haddodd
    intro k hkprime hkdvdleft hkdvdright
    apply hkprime.not_unit
    rw [even_iff_two_dvd] at haddodd
    apply huvcoprime.isUnit_of_dvd' <;>
      apply Int.dvd_mul_cancel_prime' haddodd hkdvdleft Int.prime_two
    · convert dvd_add hkdvdleft hkdvdright using 1
      ring
    · convert dvd_sub hkdvdleft hkdvdright using 1
      ring
  exact ⟨haddcoprime.symm, hsubcoprime.symm, haddsubcoprime.symm⟩
#align gcd3_coprime gcd3_coprime

theorem descent_gcd3_coprime {q s : ℤ} (h3_ndvd_q : ¬3 ∣ q) (hspos : s ≠ 0)
    (hcoprime' : IsCoprime s q) (hodd' : Even q ↔ ¬Even s) :
    IsCoprime (3 ^ 2 * 2 * s) (q ^ 2 + 3 * s ^ 2) :=
  by
  have h2ndvd : ¬2 ∣ q ^ 2 + 3 * s ^ 2 :=
    by
    rw [← even_iff_two_dvd]
    simp [two_ne_zero, hodd', parity_simps]
  have h3ndvd : ¬3 ∣ q ^ 2 + 3 * s ^ 2 := by
    intro H
    apply h3_ndvd_q
    rw [← dvd_add_iff_left (dvd_mul_right _ _)] at H
    exact Prime.dvd_of_dvd_pow Int.prime_three H
  apply isCoprime_of_prime_dvd
  · rintro ⟨h1, -⟩
    rw [mul_eq_zero] at h1
    exact hspos (h1.resolve_left (by norm_num))
  intro k hkprime hkdvdleft hkdvdright
  apply hkprime.not_unit
  have : k ∣ s := by
    apply Int.dvd_mul_cancel_prime' h2ndvd hkdvdright Int.prime_two
    apply Int.dvd_mul_cancel_prime' h3ndvd hkdvdright Int.prime_three
    apply Int.dvd_mul_cancel_prime' h3ndvd hkdvdright Int.prime_three
    convert hkdvdleft using 1
    ring
  apply hcoprime'.isUnit_of_dvd' this
  apply hkprime.dvd_of_dvd_pow
  rw [dvd_add_iff_left ((this.pow two_ne_zero).mul_left _)]
  exact hkdvdright
#align descent_gcd3_coprime descent_gcd3_coprime

theorem descent_gcd3 (a b c p q : ℤ) (hp : p ≠ 0) (hq : q ≠ 0) (hcoprime : IsCoprime p q)
    (hodd : Even p ↔ ¬Even q)
    (hcube :
      2 * p * (p ^ 2 + 3 * q ^ 2) = a ^ 3 ∨
        2 * p * (p ^ 2 + 3 * q ^ 2) = b ^ 3 ∨ 2 * p * (p ^ 2 + 3 * q ^ 2) = c ^ 3)
    (hgcd : (2 * p).gcd (p ^ 2 + 3 * q ^ 2) = 3) :
    ∃ a' b' c' : ℤ,
      a' ≠ 0 ∧
        b' ≠ 0 ∧
          c' ≠ 0 ∧ (a' ^ 3 * b' ^ 3 * c' ^ 3).natAbs ≤ (2 * p).natAbs ∧ a' ^ 3 + b' ^ 3 = c' ^ 3 :=
  by
  -- 1.
  have h3_dvd_p : 3 ∣ p :=
    by
    apply Int.dvd_mul_cancel_prime' _ dvd_rfl Int.prime_two
    · zify  at hgcd
      rw [← hgcd]
      exact Int.gcd_dvd_left _ _
    · norm_num
  have h3_ndvd_q : ¬3 ∣ q := by
    intro H
    have := hcoprime.isUnit_of_dvd' h3_dvd_p H
    rw [Int.isUnit_iff_natAbs_eq] at this
    norm_num at this
  -- 2.
  obtain ⟨s, rfl⟩ := h3_dvd_p
  have hspos : s ≠ 0 := right_ne_zero_of_mul hp
  have hps : 2 * (3 * s) * ((3 * s) ^ 2 + 3 * q ^ 2) = 3 ^ 2 * 2 * s * (q ^ 2 + 3 * s ^ 2) := by
    ring
  -- 3.
  have hcoprime' : IsCoprime s q := by
    apply isCoprime_of_prime_dvd
    · rintro ⟨h1, -⟩
      exact hspos h1
    intro k hkprime hkdvdleft hkdvdright
    apply hkprime.not_unit
    apply hcoprime.isUnit_of_dvd' _ hkdvdright
    exact hkdvdleft.mul_left 3
  have hodd' : Even q ↔ ¬Even s :=
    by
    rw [Iff.comm, not_iff_comm, Iff.comm]
    simpa [parity_simps] using hodd
  have hcoprime'' : IsCoprime (3 ^ 2 * 2 * s) (q ^ 2 + 3 * s ^ 2) :=
    descent_gcd3_coprime h3_ndvd_q hspos hcoprime' hodd'
  -- 4.
  obtain ⟨r, hr⟩ : ∃ r, 2 * (3 * s) * ((3 * s) ^ 2 + 3 * q ^ 2) = r ^ 3 := by
    rcases hcube with (hcube | hcube | hcube) <;> [(use a), (use b), (use c)] <;> exact hcube
  rw [hps] at hr
  have : Odd 3 := by norm_num
  obtain ⟨hcubeleft, hcuberight⟩ := Int.eq_pow_of_mul_eq_pow_odd hcoprime'' this hr
  -- 5.
  -- todo shadows hq hq
  obtain ⟨u, v, hq, hs, huvcoprime, huvodd⟩ := obscure' q s hq hcoprime'.symm hodd' hcuberight
  have hv : v ≠ 0 := by
    rintro rfl
    norm_num at hs
    contradiction
  -- 6.
  obtain ⟨haddcoprime, hsubcoprime, haddsubcoprime⟩ := gcd3_coprime huvcoprime huvodd
  -- 7.
  obtain ⟨e, he⟩ := hcubeleft
  have : 3 ∣ e :=
    by
    rw [← Int.pow_dvd_pow_iff (by norm_num : 0 < 3), ← he, hs]
    convert dvd_mul_right _ (2 * v * (u - v) * (u + v)) using 1
    norm_num
    ring
  obtain ⟨t, rfl⟩ := this
  have ht : 2 * v * (u - v) * (u + v) = t ^ 3 :=
    by
    have : (3 ^ 3 : ℤ) ≠ 0 := by norm_num
    rw [← mul_right_inj' this, ← mul_pow, ← he, hs]
    ring
  obtain ⟨A, B, C, HApos, HBpos, HCpos, HA, HB, HC⟩ :
    ∃ X Y Z : ℤ, X ≠ 0 ∧ Y ≠ 0 ∧ Z ≠ 0 ∧ 2 * v = X ^ 3 ∧ u - v = Y ^ 3 ∧ u + v = Z ^ 3 :=
    by
    apply Int.cube_of_coprime
    · exact mul_ne_zero two_ne_zero hv
    · intro H
      rw [sub_eq_zero] at H
      apply iff_not_self
      simpa [H, parity_simps] using huvodd
    · intro H
      rw [add_eq_zero_iff_eq_neg] at H
      apply iff_not_self
      simpa [H, parity_simps] using huvodd
    · exact hsubcoprime
    · exact haddcoprime
    · exact haddsubcoprime
    · exact ht
  refine' ⟨A, B, C, HApos, HBpos, HCpos, _, _⟩
  -- 9.
  · rw [← mul_assoc, mul_comm, ← mul_assoc (C ^ 3), ← HA, ← HB, ← HC]
    set x := v * (u - v) * (u + v) with hx
    calc
      ((u + v) * (2 * v) * (u - v)).natAbs = (2 * x).natAbs :=
        by
        rw [hx]
        congr 1
        ring
      _ = 2 * x.natAbs := by
        rw [Int.natAbs_mul 2]
        rfl
      _ ≤ 3 * x.natAbs := (Nat.mul_le_mul_right _ (by norm_num))
      _ = (3 * x).natAbs := by
        rw [Int.natAbs_mul 3]
        rfl
      _ = s.natAbs := by
        rw [hx, hs]
        congr 1
        ring
      _ ≤ 2 * 3 * s.natAbs := (Nat.le_mul_of_pos_left (by norm_num))
      _ = (2 * 3 * s).natAbs := by
        rw [Int.natAbs_mul (2 * 3)]
        rfl

  · rw [← HA, ← HB, ← HC]
    ring
#align descent_gcd3 descent_gcd3

theorem descent (a b c : ℤ) (h : FltCoprime 3 a b c) :
    ∃ a' b' c' : ℤ,
      a' ≠ 0 ∧
        b' ≠ 0 ∧ c' ≠ 0 ∧ (a' * b' * c').natAbs < (a * b * c).natAbs ∧ a' ^ 3 + b' ^ 3 = c' ^ 3 :=
  by
  -- 3.
  have := descent2 a b c h
  obtain ⟨p, q, hp, hq, hcoprime, hodd, hcube, haaa⟩ := this
  suffices
    ∃ a' b' c' : ℤ,
      a' ≠ 0 ∧
        b' ≠ 0 ∧
          c' ≠ 0 ∧ (a' ^ 3 * b' ^ 3 * c' ^ 3).natAbs ≤ (2 * p).natAbs ∧ a' ^ 3 + b' ^ 3 = c' ^ 3
    by
    obtain ⟨a', b', c', ha', hb', hc', hsmaller, hsolution⟩ := this
    refine' ⟨a', b', c', ha', hb', hc', _, hsolution⟩
    rw [← Nat.pow_lt_iff_lt_left (by norm_num : 1 ≤ 3)]
    convert lt_of_le_of_lt hsmaller haaa <;> simp [mul_pow, Int.natAbs_mul, Int.natAbs_pow]
  -- 4.
  cases' gcd1or3 p q hp hcoprime hodd with hgcd hgcd
  -- 5.
  · rw [Int.gcd_eq_one_iff_coprime] at hgcd
    apply descent_gcd1 a b c p q hp hcoprime hodd hcube hgcd
  · apply descent_gcd3 a b c p q hp hq hcoprime hodd hcube hgcd
#align descent descent

theorem flt_three {a b c : ℤ} (ha : a ≠ 0) (hb : b ≠ 0) (hc : c ≠ 0) : a ^ 3 + b ^ 3 ≠ c ^ 3 :=
  by
  induction' h : (a * b * c).natAbs using Nat.strong_induction_on with k' IH generalizing a b c
  intro H
  obtain ⟨x'', y'', z'', hxle, hyle, hzle, hcoprime⟩ := exists_coprime zero_lt_three ha hb hc H
  obtain ⟨x', y', z', hx'pos, hy'pos, hz'pos, hsmaller, hsolution⟩ := descent x'' y'' z'' hcoprime
  refine' IH (x' * y' * z').natAbs _ hx'pos hy'pos hz'pos rfl hsolution
  apply lt_of_lt_of_le hsmaller
  rw [← h]
  simp only [Int.natAbs_mul]
  exact Nat.mul_le_mul (Nat.mul_le_mul hxle hyle) hzle
#align flt_three flt_three


import tactic
import tactic.nth_rewrite

namespace polyomino

inductive adjacent : ℤ × ℤ → ℤ × ℤ → Prop
| up {x y} : adjacent ⟨x, y⟩ ⟨x, y + 1⟩
| down {x y} : adjacent ⟨x, y⟩ ⟨x, y - 1⟩
| right {x y} : adjacent ⟨x, y⟩ ⟨x + 1, y⟩
| left {x y} : adjacent ⟨x, y⟩ ⟨x - 1, y⟩

@[symm] theorem adjacent.symm {x y} : adjacent x y → adjacent y x :=
begin
  intros adj_x_y,
  cases adj_x_y with x y x y x y x y,
  {
    nth_rewrite 1 ← add_sub_cancel y 1,
    apply adjacent.down,
  },
  {
    nth_rewrite 1 ← sub_add_cancel y 1,
    apply adjacent.up,
  },
  {
    nth_rewrite 1 ← add_sub_cancel x 1,
    apply adjacent.left,
  },
  {
    nth_rewrite 1 ← sub_add_cancel x 1,
    apply adjacent.right,
  },
end

inductive connected : finset (ℤ × ℤ) → Prop
| empty : connected ∅
| singleton (x : ℤ × ℤ) : connected {x}
| insert (s x y) : y ∈ s → adjacent x y → connected s → connected (insert x s)

lemma connected.induction_on {p : finset (ℤ × ℤ) → Prop} {s} : connected s → p ∅ → (∀ x, p {x}) → (∀ x y s, x ∉ s → y ∈ s → adjacent x y → connected s → p (insert x s)) → p s :=
begin
  intros conn_s p_empty p_singleton p_insert,
  induction conn_s with _ s₁ a b b_mem_s _ _ ih,
  {
    apply p_empty,
  },
  {
    apply p_singleton,
  },
  clear s,
  rename s₁ s,
  by_cases h : a ∈ s,
  {
    rw finset.insert_eq_of_mem h,
    apply ih,
  },
  {
    apply p_insert; assumption,
  },
end

end polyomino

structure polyomino (n : ℕ) :=
(shape : finset (ℤ × ℤ))
(card : shape.card = n)
(conn : polyomino.connected shape)


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro

Additional equiv and encodable instances for lists and finsets.
-/
import data.equiv.denumerable data.nat.pairing order.order_iso
  data.vector2 data.array.lemmas data.fintype

open nat list

namespace encodable
variables {α : Type*}

section list
variable [encodable α]

def encode_list : list α → ℕ
| []     := 0
| (a::l) := succ (mkpair (encode a) (encode_list l))

def decode_list : ℕ → option (list α)
| 0        := some []
| (succ v) := match unpair v, unpair_le_right v with
  | (v₁, v₂), h :=
    have v₂ < succ v, from lt_succ_of_le h,
    (::) <$> decode α v₁ <*> decode_list v₂
  end

instance list : encodable (list α) :=
⟨encode_list, decode_list, λ l,
  by induction l with a l IH; simp [encode_list, decode_list, unpair_mkpair, encodek, *]⟩

@[simp] theorem encode_list_nil : encode (@nil α) = 0 := rfl
@[simp] theorem encode_list_cons (a : α) (l : list α) :
  encode (a :: l) = succ (mkpair (encode a) (encode l)) := rfl

@[simp] theorem decode_list_zero : decode (list α) 0 = some [] := rfl

@[simp] theorem decode_list_succ (v : ℕ) :
  decode (list α) (succ v) =
  (::) <$> decode α v.unpair.1 <*> decode (list α) v.unpair.2 :=
show decode_list (succ v) = _, begin
  cases e : unpair v with v₁ v₂,
  simp [decode_list, e], refl
end

theorem length_le_encode : ∀ (l : list α), length l ≤ encode l
| [] := _root_.zero_le _
| (a :: l) := succ_le_succ $
  le_trans (length_le_encode l) (le_mkpair_right _ _)

end list

section finset
variables [encodable α]

private def enle : α → α → Prop := encode ⁻¹'o (≤)

private lemma enle.is_linear_order : is_linear_order α enle :=
(order_embedding.preimage ⟨encode, encode_injective⟩ (≤)).is_linear_order

private def decidable_enle (a b : α) : decidable (enle a b) :=
by unfold enle order.preimage; apply_instance

local attribute [instance] enle.is_linear_order decidable_enle

def encode_multiset (s : multiset α) : ℕ :=
encode (s.sort enle)

def decode_multiset (n : ℕ) : option (multiset α) :=
coe <$> decode (list α) n

instance multiset : encodable (multiset α) :=
⟨encode_multiset, decode_multiset,
 λ s, by simp [encode_multiset, decode_multiset, encodek]⟩

end finset

def encodable_of_list [decidable_eq α] (l : list α) (H : ∀ x, x ∈ l) : encodable α :=
⟨λ a, index_of a l, l.nth, λ a, index_of_nth (H _)⟩

def trunc_encodable_of_fintype (α : Type*) [decidable_eq α] [fintype α] : trunc (encodable α) :=
@@quot.rec_on_subsingleton _
  (λ s : multiset α, (∀ x:α, x ∈ s) → trunc (encodable α)) _
  finset.univ.1
  (λ l H, trunc.mk $ encodable_of_list l H)
  finset.mem_univ

instance vector [encodable α] {n} : encodable (vector α n) :=
encodable.subtype

instance fin_arrow [encodable α] {n} : encodable (fin n → α) :=
of_equiv _ (equiv.vector_equiv_fin _ _).symm

instance fin_pi (n) (π : fin n → Type*) [∀i, encodable (π i)] : encodable (Πi, π i) :=
of_equiv _ (equiv.pi_equiv_subtype_sigma (fin n) π)

instance array [encodable α] {n} : encodable (array n α) :=
of_equiv _ (equiv.array_equiv_fin _ _)

instance finset [encodable α] : encodable (finset α) :=
by haveI := decidable_eq_of_encodable α; exact
 of_equiv {s : multiset α // s.nodup}
  ⟨λ ⟨a, b⟩, ⟨a, b⟩, λ⟨a, b⟩, ⟨a, b⟩, λ ⟨a, b⟩, rfl, λ⟨a, b⟩, rfl⟩

def fintype_arrow (α : Type*) (β : Type*) [fintype α] [decidable_eq α] [encodable β] :
  trunc (encodable (α → β)) :=
(fintype.equiv_fin α).map $
  λf, encodable.of_equiv (fin (fintype.card α) → β) $
  equiv.arrow_congr f (equiv.refl _)

def fintype_pi (α : Type*) (π : α → Type*) [fintype α] [decidable_eq α] [∀a, encodable (π a)] :
  trunc (encodable (Πa, π a)) :=
(encodable.trunc_encodable_of_fintype α).bind $ λa,
  (@fintype_arrow α (Σa, π a) _ _ (@encodable.sigma _ _ a _)).bind $ λf,
  trunc.mk $ @encodable.of_equiv _ _ (@encodable.subtype _ _ f _) (equiv.pi_equiv_subtype_sigma α π)

end encodable

namespace denumerable
variables {α : Type*} {β : Type*} [denumerable α] [denumerable β]
open encodable

section list

theorem denumerable_list_aux : ∀ n : ℕ,
  ∃ a ∈ @decode_list α _ n, encode_list a = n
| 0        := ⟨_, rfl, rfl⟩
| (succ v) := begin
  cases e : unpair v with v₁ v₂,
  have h := unpair_le_right v,
  rw e at h,
  rcases have v₂ < succ v, from lt_succ_of_le h,
    denumerable_list_aux v₂ with ⟨a, h₁, h₂⟩,
  simp at h₁,
  simp [decode_list, e, h₂, h₁, encode_list, mkpair_unpair' e]
end

instance denumerable_list : denumerable (list α) := ⟨denumerable_list_aux⟩

@[simp] theorem list_of_nat_zero : of_nat (list α) 0 = [] := rfl

@[simp] theorem list_of_nat_succ (v : ℕ) :
  of_nat (list α) (succ v) =
  of_nat α v.unpair.1 :: of_nat (list α) v.unpair.2 :=
of_nat_of_decode $ show decode_list (succ v) = _,
begin
  cases e : unpair v with v₁ v₂,
  simp [decode_list, e],
  rw [show decode_list v₂ = decode (list α) v₂,
      from rfl, decode_eq_of_nat]; refl
end

end list

section multiset

def lower : list ℕ → ℕ → list ℕ
| []       n := []
| (m :: l) n := (m - n) :: lower l m

def raise : list ℕ → ℕ → list ℕ
| []       n := []
| (m :: l) n := (m + n) :: raise l (m + n)

lemma lower_raise : ∀ l n, lower (raise l n) n = l
| []       n := rfl
| (m :: l) n := by simp [raise, lower, nat.add_sub_cancel, lower_raise]

lemma raise_lower : ∀ {l n}, list.sorted (≤) (n :: l) → raise (lower l n) n = l
| []       n h := rfl
| (m :: l) n h :=
  have n ≤ m, from list.rel_of_sorted_cons h _ (l.mem_cons_self _),
  by simp [raise, lower, nat.add_sub_cancel' this,
           raise_lower (list.sorted_of_sorted_cons h)]

lemma raise_chain : ∀ l n, list.chain (≤) n (raise l n)
| []       n := list.chain.nil _ _
| (m :: l) n := list.chain.cons (nat.le_add_left _ _) (raise_chain _ _)

lemma raise_sorted : ∀ l n, list.sorted (≤) (raise l n)
| []       n := list.sorted_nil
| (m :: l) n := (list.chain_iff_pairwise (@le_trans _ _)).1 (raise_chain _ _)

/- Warning: this is not the same encoding as used in `encodable` -/
instance multiset : denumerable (multiset α) := mk' ⟨
  λ s : multiset α, encode $ lower ((s.map encode).sort (≤)) 0,
  λ n, multiset.map (of_nat α) (raise (of_nat (list ℕ) n) 0),
  λ s, by have := raise_lower
      (list.sorted_cons.2 ⟨λ n _, zero_le n, (s.map encode).sort_sorted _⟩);
    simp [-multiset.coe_map, this],
  λ n, by simp [-multiset.coe_map, list.merge_sort_eq_self _ (raise_sorted _ _), lower_raise]⟩

end multiset

section finset

def lower' : list ℕ → ℕ → list ℕ
| []       n := []
| (m :: l) n := (m - n) :: lower' l (m + 1)

def raise' : list ℕ → ℕ → list ℕ
| []       n := []
| (m :: l) n := (m + n) :: raise' l (m + n + 1)

lemma lower_raise' : ∀ l n, lower' (raise' l n) n = l
| []       n := rfl
| (m :: l) n := by simp [raise', lower', nat.add_sub_cancel, lower_raise']

lemma raise_lower' : ∀ {l n}, (∀ m ∈ l, n ≤ m) → list.sorted (<) l → raise' (lower' l n) n = l
| []       n h₁ h₂ := rfl
| (m :: l) n h₁ h₂ :=
  have n ≤ m, from h₁ _ (l.mem_cons_self _),
  by simp [raise', lower', nat.add_sub_cancel' this, raise_lower'
    (list.rel_of_sorted_cons h₂ : ∀ a ∈ l, m < a) (list.sorted_of_sorted_cons h₂)]

lemma raise'_chain : ∀ l {m n}, m < n → list.chain (<) m (raise' l n)
| []       m n h := list.chain.nil _ _
| (a :: l) m n h := list.chain.cons
  (lt_of_lt_of_le h (nat.le_add_left _ _)) (raise'_chain _ (lt_succ_self _))

lemma raise'_sorted : ∀ l n, list.sorted (<) (raise' l n)
| []       n := list.sorted_nil
| (m :: l) n := (list.chain_iff_pairwise (@lt_trans _ _)).1
  (raise'_chain _ (lt_succ_self _))

def raise'_finset (l : list ℕ) (n : ℕ) : finset ℕ :=
⟨raise' l n, (raise'_sorted _ _).imp (@ne_of_lt _ _)⟩

/- Warning: this is not the same encoding as used in `encodable` -/
instance finset : denumerable (finset α) := mk' ⟨
  λ s : finset α, encode $ lower' ((s.map (eqv α).to_embedding).sort (≤)) 0,
  λ n, finset.map (eqv α).symm.to_embedding (raise'_finset (of_nat (list ℕ) n) 0),
  λ s, finset.eq_of_veq $ by simp [-multiset.coe_map, raise'_finset,
    raise_lower' (λ n _, zero_le n) (finset.sort_sorted_lt _)],
  λ n, by simp [-multiset.coe_map, finset.map, raise'_finset, finset.sort,
    list.merge_sort_eq_self (≤) ((raise'_sorted _ _).imp (@le_of_lt _ _)),
    lower_raise']⟩

end finset

end denumerable

namespace equiv

def list_nat_equiv_nat : list ℕ ≃ ℕ := denumerable.eqv _

def list_equiv_self_of_equiv_nat {α : Type} (e : α ≃ ℕ) : list α ≃ α :=
calc list α ≃ list ℕ : list_equiv_of_equiv e
        ... ≃ ℕ      : list_nat_equiv_nat
        ... ≃ α      : e.symm
end equiv


import algebra.module.linear_map
import probability.indep
open measure_theory probability_theory filter
open_locale big_operators measure_theory probability_theory topological_space

variables {α β γ : Type*} {mα : measurable_space α} {mβ : measurable_space β} {mγ : measurable_space γ}
  {μ : measure α} [is_finite_measure μ]

namespace probability_theory

noncomputable def cov (X Y : Lp ℝ 2 μ) : ℝ := integral μ (X * Y) - integral μ X * integral μ Y

lemma indep_fun.cov_eq_zero {X Y : Lp ℝ 2 μ} (h : indep_fun X Y μ) :
  cov X Y = 0 :=
sub_eq_zero_of_eq h.integral_mul_of_Lp

noncomputable def var (X : Lp ℝ 2 μ) : ℝ := integral μ (X^2) - (integral μ X)^2

lemma indep_fun.var_add {X Y : Lp ℝ 2 μ} (h : indep_fun X Y μ) :
  var (X + Y) = var X + var Y :=
begin
  have hX : integrable X μ := Lp.integrable X,
  have hY : integrable Y μ := Lp.integrable Y,
  have h1 : integrable (X * X) μ := L2.integrable_mul X X,
  have h2 : integrable (X * Y) μ := L2.integrable_mul X Y,
  have h3 : integrable (Y * Y) μ := L2.integrable_mul Y Y,
  have h4 : ⇑(X + Y) =ᵐ[μ] X + Y := Lp.coe_fn_add X Y,
  have h5 : ⇑(X + Y) * ⇑(X + Y) =ᵐ[μ] (X + Y) * (X + Y) := h4.mul h4,

  simp_rw [var, pow_two, integral_congr_ae h4, integral_congr_ae h5, add_mul, mul_add,
    mul_comm (Y : α → ℝ) X, integral_add' (h1.add h2) (h2.add h3), integral_add' h1 h2,
    integral_add' h2 h3, integral_add' hX hY, h.integral_mul_of_integrable hX hY],
  ring
end

noncomputable def cesaro {α : Type*} [add_comm_group α] [module ℝ α] :
  (ℕ → α) →ₗ[ℝ] (ℕ → α) :=
begin
  use λ X n, (n : ℝ)⁻¹ • (∑ i in finset.range n, X i),
  { intros, ext n, simp [finset.sum_add_distrib] },
  { intros, ext n, simp [← finset.smul_sum], apply smul_comm }
end

theorem lln_of_nonneg
  ⦃X : ℕ → α → ℝ⦄
  (h_int : ∀ i, integrable (X i) μ)
  (h_dist : ∀ i, μ.map (X i) = μ.map (X 0))
  (h_indep : pairwise (λ i j, indep_fun (X i) (X j) μ))
  (h_pos : ∀ i, 0 ≤ᵐ[μ] X i) :
  ∀ᵐ a ∂μ, tendsto (λ n, cesaro X n a) at_top (𝓝 (integral μ (X 0))) :=
sorry

lemma map_map_of_ae_measurable {mβ : measurable_space β} {mγ : measurable_space γ}
  {g : β → γ} {f : α → β}
  (hg : ae_measurable g (measure.map f μ)) (hf : ae_measurable f μ) :
  (μ.map f).map g = μ.map (g ∘ f) :=
begin
  have hg' : ae_measurable g (measure.map (hf.mk f) μ) := measure.map_congr hf.ae_eq_mk ▸ hg,
  rw [measure.map_congr hf.ae_eq_mk, measure.map_congr (hf.ae_eq_mk.fun_comp g),
    measure.map_congr hg'.ae_eq_mk,
    measure.map_congr (ae_eq_comp hf.measurable_mk.ae_measurable hg'.ae_eq_mk)],
  exact measure.map_map hg'.measurable_mk hf.measurable_mk
end

lemma map_map' [measurable_space β] [measurable_space γ] {g : β → γ} {f : α → β}
  (hg : measurable g) (hf : ae_measurable f μ) :
  (μ.map f).map g = μ.map (g ∘ f) :=
measure.ext $ λ s hs, by simp [hf, hg, hs, hg hs, hg.comp_ae_measurable hf, ← set.preimage_comp]

lemma bla2 {mβ : measurable_space β} {mγ : measurable_space γ}
  {X Y : α → β} (mX : ae_measurable X μ) (mY : ae_measurable Y μ) (h : μ.map X = μ.map Y)
  {φ : β → γ} (mφ : measurable φ) :
  μ.map (φ ∘ X) = μ.map (φ ∘ Y) :=
by rw [← map_map' mφ mX, ← map_map' mφ mY, h]

theorem lln
  ⦃X : ℕ → α → ℝ⦄
  (h_int : ∀ i, integrable (X i) μ)
  (h_dist : ∀ i, μ.map (X i) = μ.map (X 0))
  (h_indep : pairwise (λ i j, indep_fun (X i) (X j) μ)) :
  ∀ᵐ a ∂μ, tendsto (λ n, cesaro X n a) at_top (𝓝 (integral μ (X 0))) :=
begin
  have h1 : ∀ i a, X⁺ i a - X⁻ i a = X i a := λ _ _, lattice_ordered_comm_group.pos_sub_neg _,
  have h2 : measurable (λ z : ℝ, z⁺) := measurable_id.sup_const 0,
  have h3 : measurable (λ z : ℝ, z⁻) := measurable_id.neg.sup_const 0,

  have Hp : ∀ᵐ a ∂μ, tendsto (λ n, cesaro (X⁺) n a) at_top (𝓝 (integral μ (X⁺ 0))),
    from lln_of_nonneg (λ i, (h_int i).max_zero)
      (λ i, bla2 (h_int i).ae_measurable (h_int 0).ae_measurable (h_dist i) h2)
      (h_indep.mono (λ i j hij, hij.comp h2 h2)) (λ i, ae_of_all _ (λ a, le_sup_right)),

  have Hn : ∀ᵐ a ∂μ, tendsto (λ n, cesaro (X⁻) n a) at_top (𝓝 (integral μ (X⁻ 0))),
    from lln_of_nonneg (λ i, (h_int i).neg.max_zero)
      (λ i, bla2 (h_int i).ae_measurable (h_int 0).ae_measurable (h_dist i) h3)
      (h_indep.mono (λ i j hij, hij.comp h3 h3)) (λ i, ae_of_all _ (λ a, le_sup_right)),

  refine (Hp.and Hn).mono (λ a c, _),
  convert c.1.sub c.2,
  { funext n, rw [← congr_arg cesaro (lattice_ordered_comm_group.pos_sub_neg X),
    linear_map.map_sub, pi.sub_apply, pi.sub_apply] },
  { exact (congr_arg (integral μ) (funext (λ a, (h1 0 a).symm))).trans
    (integral_sub (h_int 0).max_zero (h_int 0).neg.max_zero) }
end

end probability_theory