import applications.functor_category
import grothendieck
import sheaf
import category.element
import tactic.equiv_rw
import data.quot
import pullback_colimit
import category.colimits

namespace category_theory

universes v u

variables {C : Type u} [small_category C] (J : sieve_set C) [grothendieck J]

open classifier limits category

noncomputable theory

@[ext]
structure closure_operator (α : Type*) [has_le α] :=
(to_fun : α → α)
(ord' : ∀ {x y : α}, x ≤ y → to_fun x ≤ to_fun y)
(inflate' : ∀ x, x ≤ to_fun x)
(idem' : ∀ x, to_fun (to_fun x) = to_fun x)

instance (α : Type*) [has_le α] : has_coe_to_fun (closure_operator α) :=
⟨_, closure_operator.to_fun⟩

instance (α : Type*) [has_le α] : has_le (closure_operator α) :=
⟨λ c₁ c₂, ∀ x, c₁ x ≤ c₂ x⟩

lemma closure_operator.ord {α : Type*} [has_le α] (c : closure_operator α) {x y : α} (h : x ≤ y) :
  c x ≤ c y :=
c.ord' h

lemma closure_operator.inflate {α : Type*} [has_le α] (c : closure_operator α) (x : α) :
  x ≤ c x := c.inflate' x

lemma closure_operator.idem {α : Type*} [has_le α] (c : closure_operator α) (x : α) :
  c (c x) = c x := c.idem' x

/-- The sieve-set induced by a closure operator on sieves. -/
def grothendieck_from_natural_operator (c : Π (X : C), closure_operator (sieve X)) :
  sieve_set C :=
λ X S, c X S = ⊤

/-- If a closure operator on sieves is natural, it induces a Grothendieck topology. -/
instance natural_operator_is_grothendieck (c : Π (X : C), closure_operator (sieve X))
  (hc : ∀ (X Y : C) (f : X ⟶ Y) (S : sieve Y), (c Y S).pullback f = c _ (S.pullback f)) :
grothendieck (grothendieck_from_natural_operator c) :=
{ max := λ X,
  begin
    apply top_unique,
    apply (c _).inflate,
  end,
  stab := λ X Y S hS f,
  begin
    change _ = _ at hS,
    change _ = _,
    rw [← hc, hS, sieve.pullback_top],
  end,
  trans := λ X S hS T hT,
  begin
    change _ = _,
    rw ← (c X).idem,
    apply top_unique,
    change _ = _ at hS,
    rw ← hS,
    apply (c _).ord,
    intros Y g hg,
    rw sieve.pullback_eq_top_iff_mem,
    rw hc,
    apply hT g hg,
  end }

@[simps]
def close {c : C} (S : sieve c) : sieve c :=
{ arrows := λ g, grothendieck.covers J S g.hom,
  subs := λ d e f h k,
  begin
    change S.pullback (h ≫ f) ∈ J e,
    change S.pullback f ∈ J d at k,
    rw sieve.pullback_comp,
    apply grothendieck.stab (S.pullback f) k h,
  end }

lemma close_preserves_order {c : C} {S T : sieve c} (h : S ≤ T) :
  close J S ≤ close J T :=
begin
  intros d g hg,
  change S.pullback g ∈ J d at hg,
  apply grothendieck.superset_covering (sieve.pullback_le_map h g) hg,
end

lemma close_inflationary {c : C} (S : sieve c) :
  S ≤ close J S :=
begin
  intros d g hg,
  change S.pullback g ∈ J d,
  rw sieve.pullback_eq_top_iff_mem at hg,
  rw hg,
  apply grothendieck.max,
end

lemma close_idem (c : C) (S : sieve c) :
  close J (close J S) = close J S :=
begin
  apply le_antisymm,
    intros d g hg,
    change (close J S).pullback g ∈ J d at hg,
    apply grothendieck.trans _ hg,
    intros e g' hg',
    change (S.pullback g).pullback g' ∈ J e,
    rw ← S.pullback_comp,
    exact hg',
  apply close_preserves_order J (close_inflationary _ _),
end

lemma close_inter (c : C) (S T : sieve c) :
  close J (S ⊓ T) = close J S ⊓ close J T :=
begin
  apply le_antisymm,
  { refine le_inf (close_preserves_order _ inf_le_left) (close_preserves_order _ inf_le_right) },
  rintros d g ⟨hg₁, hg₂⟩,
  change S.pullback g ∈ J d at hg₁,
  change (S ⊓ T).pullback g ∈ J d,
  rw sieve.pullback_inter,
  apply grothendieck.intersection_covering hg₁ hg₂,
end

lemma close_pullback (c d : C) (S : sieve c) (f : d ⟶ c) :
  close J (S.pullback f) = (close J S).pullback f :=
begin
  ext e g,
  change (S.pullback f).pullback g ∈ J e ↔ S.pullback (g ≫ f) ∈ J e,
  rw S.pullback_comp,
end

lemma incl_iff_closure_eq_top (c : C) (S : sieve c) : S ∈ J c ↔ close J S = ⊤ :=
begin
  split,
    intro h,
    ext d f,
    change S.pullback f ∈ J d ↔ true,
    simpa using grothendieck.stab S h f,
  rw [close],
  intro q,
  refine grothendieck.trans ⊤ (grothendieck.max _) _ _,
  intros d g hg,
  rw ← q at hg,
  exact hg,
end

/-- Natural closure operators on the set of sieves are the same thing as Grothendieck topologies. -/
def operators_equiv_topology :
  {c : Π (X : C), closure_operator (sieve X) // ∀ X Y f S, (c Y S).pullback f = c X (S.pullback f)}
≃ {J : sieve_set C // grothendieck J} :=
{ to_fun := λ c, ⟨grothendieck_from_natural_operator c.1, category_theory.natural_operator_is_grothendieck _ c.2⟩,
  inv_fun := λ J, by { haveI := J.2, refine ⟨λ X, ⟨close J.1, λ x y, close_preserves_order _, close_inflationary _, close_idem _ _⟩, λ X Y f S, _⟩, symmetry, apply close_pullback, },
  left_inv :=
  begin
    rintro ⟨c₁, c₂⟩,
    ext x S y f,
    dsimp [close, grothendieck.covers, grothendieck_from_natural_operator],
    change c₁ y (S.pullback f) = ⊤ ↔ over.mk f ∈ (c₁ x S).arrows,
    rw ← c₂,
    rw sieve.pullback_eq_top_iff_mem,
  end,
  right_inv :=
  begin
    rintro ⟨J, _⟩,
    resetI,
    ext c S,
    apply (incl_iff_closure_eq_top J c S).symm,
  end }

@[simps]
def j : Ω (Cᵒᵖ ⥤ Type u) ⟶ Ω (Cᵒᵖ ⥤ Type u) :=
{ app := λ c S, close J S,
  naturality' := λ c c' f,
  begin
    ext1 S,
    change sieve c.unop at S,
    apply close_pullback,
  end }

lemma yoneda_generates (X Y : Cᵒᵖ ⥤ Type u) (f g : X ⟶ Y)
  (h : ∀ (c : Cᵒᵖ) (k : yoneda.obj c.unop ⟶ X), k ≫ f = k ≫ g) :
  f = g :=
begin
  ext c t,
  specialize h c ((yoneda_sections_small c.unop X).inv t),
  have := congr_arg (λ i, nat_trans.app i c) h,
  dsimp [yoneda_lemma, ulift_trivial] at this,
  have z := congr_fun this (has_hom.hom.unop (𝟙 _)),
  dsimp at z,
  rw [X.map_id] at z,
  exact z,
end

def sieve_equiv_arrow (c : C) : sieve c ≃ (yoneda.obj c ⟶ Ω _) :=
(yoneda_sections_small c (Ω _)).to_equiv.symm

lemma equiv_close (c : C) (S : sieve c) :
  sieve_equiv_arrow _ (close J S) = sieve_equiv_arrow _ S ≫ j J :=
begin
  ext d f : 3,
  symmetry,
  apply close_pullback,
end

def sub_repr (c : C) : sieve c ≃ subq (yoneda.obj c) :=
(sieve_equiv_arrow c).trans classification

lemma sub_repr_eq (c : C) (S : sieve c) : sub_repr c S = subq.mk S.functor_inclusion :=
begin
  change classification _ = _,
  rw ← classification.eq_symm_apply,
  ext c' f c'' g,
  change over.mk (g ≫ f) ∈ S.arrows ↔ ∃ (x : {h // over.mk h ∈ S.arrows}), x.1 = g ≫ f,
  rw [subtype.exists],
  simp,
end

def sieve_subq (c : C) :
sieve c ≃o subq (yoneda.obj c) :=
{ to_equiv := sub_repr c,
  map_rel_iff' :=
  begin
    intros S T,
    rw [sub_repr_eq, sub_repr_eq],
    change S ≤ T ↔ nonempty (_ ⟶ _),
    split,
    intro h,
      exact ⟨sub.hom_mk (sieve.le_as_functor h) (sieve.le_as_functor_comm h)⟩,
    rintro ⟨a⟩ d f hf,
    let f' := a.left.app (opposite.op d) ⟨f, hf⟩,
    have := sub.w a,
    dsimp at *,
    have q := congr_arg (λ t, nat_trans.app t (opposite.op d)) this,
    dsimp at q,
    replace q := congr_fun q ⟨f, hf⟩,
    dsimp at q,
    rw ← q,
    apply (a.left.app (opposite.op d) ⟨f, hf⟩).2,
  end }

lemma inclusion_inter (c : C) (S T : sieve c) :
  sieve_subq _ (S ⊓ T) = sieve_subq _ S ⊓ sieve_subq _ T :=
order_iso.map_inf _ _ _

@[reassoc]
lemma and_arrow_sieve (c : C) (S T : sieve c) :
  (prod.lift (sieve_equiv_arrow _ S) (sieve_equiv_arrow _ T) ≫ and_arrow _) = sieve_equiv_arrow _ (S ⊓ T) :=
begin
  have : ∀ (S : sieve _), sieve_equiv_arrow c S = classify (sieve_subq _ S),
    intro S,
    change _ = classify (classification (sieve_equiv_arrow c S)),
    symmetry,
    apply classification.left_inv,
  rw [this, this, this],
  rw and_property,
  rw inclusion_inter,
end

instance : topology (j J) :=
{ ax1 :=
  begin
    ext c ⟨⟩ d f,
    change J d ((⊤ : sieve _).pullback f) ↔ true,
    rw sieve.pullback_top,
    simp only [iff_true],
    exact grothendieck.max d,
  end,
  ax2 :=
  begin
    ext c S : 3,
    apply close_idem,
  end,
  ax3 :=
  begin
    apply yoneda_generates,
    intros c k,
    equiv_rw prod.equiv (yoneda.obj c.unop) (Ω _) (Ω _) at k,
    cases k with k₁ k₂,
    change prod.lift k₁ k₂ ≫ _ = prod.lift k₁ k₂ ≫ _ ≫ _,
    rw prod.lift_map_assoc,
    equiv_rw (sieve_equiv_arrow c.unop).symm at k₁,
    equiv_rw (sieve_equiv_arrow c.unop).symm at k₂,

    rw [← equiv_close, ← equiv_close, and_arrow_sieve, and_arrow_sieve_assoc, ← equiv_close],
    rw (sieve_equiv_arrow _).apply_eq_iff_eq,
    rw close_inter,
  end }.

def dense_inclusion (c : C) (S : sieve c) (h : S ∈ J c) : closure.dense (j J) S.functor_inclusion :=
begin
  constructor,
  change classification (classify (subq.mk _) ≫ _) = _,
  rw ← sub_repr_eq,
  dsimp only [sub_repr, equiv.trans],
  change classification (classification.symm (classification _) ≫ _) = _,
  rw classification.symm_apply_apply,
  erw ← equiv_close,
  change sieve_subq _ _ = _,
  rw ← order_iso.map_top (sieve_subq c),
  congr' 1,
  ext d f,
  change S.pullback f ∈ J d ↔ true,
  simpa using grothendieck.stab S h f,
end

lemma dense_inclusion_iff (c : C) (S : sieve c) (h : closure.dense (j J) S.functor_inclusion) :
  S ∈ J c :=
begin
  have := h.closure_eq_top,
  change classification (classify (subq.mk _) ≫ _) = _ at this,
  rw ← sub_repr_eq at this,
  dsimp only [sub_repr, equiv.trans] at this,
  change classification (classification.symm (classification _) ≫ _) = _ at this,
  rw classification.symm_apply_apply at this,
  erw ← equiv_close at this,
  change sieve_subq _ _ = _ at this,
  rw ← order_iso.map_top (sieve_subq c) at this,
  erw (sieve_subq c).to_equiv.apply_eq_iff_eq at this,
  rw close at this,
  refine grothendieck.trans ⊤ (grothendieck.max _) _ _,
  intros d g hg,
  rw ← this at hg,
  exact hg,
end

def jsheaf_is_Jsheaf (P : Cᵒᵖ ⥤ Type u) (h : sheaf_condition (j J) P) : grothendieck.sheaf_condition J P :=
begin
  intros c S γ hS,
  change S.as_functor ⟶ _ at γ,
  haveI : closure.dense (j J) S.functor_inclusion := dense_inclusion _ _ _ hS,
  apply h S.functor_inclusion γ,
end

-- This can be generalised to show it suffices to check the sheaf condition on a
-- generating set (in the sense of colimits).
def sheaf.yoneda_mk (P : Cᵒᵖ ⥤ Type u)
  (h : Π c S f' (m : S ⟶ yoneda.obj c) [closure.dense (j J) m], {f : yoneda.obj c ⟶ P // m ≫ f = f' ∧ ∀ a, m ≫ a = f' → a = f}) :
  sheaf_condition (j J) P :=
sheaf_condition.mk' _ _
begin
  introsI E A m σ _,
  let A' : (E.elements)ᵒᵖ → (Cᵒᵖ ⥤ Type u) := λ i, pullback ((the_cocone E).ι.app i) m,
  let m' : Π (i : E.elementsᵒᵖ), A' i ⟶ yoneda.obj i.unop.1.unop := λ i, pullback.fst,
  let top_map : Π (i : E.elementsᵒᵖ), A' i ⟶ A := λ i, pullback.snd,
  have pb : ∀ (i : E.elementsᵒᵖ), m' i ≫ _ = top_map i ≫ m := λ i, pullback.condition,
  let A'diagram : E.elementsᵒᵖ ⥤ (Cᵒᵖ ⥤ Type u),
  { refine { functor . obj := A',
             map := λ i j f,
              pullback.lift (m' i ≫ yoneda.map f.unop.1.unop) (top_map i)
                (by erw [← pb i, assoc, (the_cocone E).w f]), map_id' := _, map_comp' := _},
    { intro j,
      apply pullback.hom_ext;
      simp },
    { intros i₁ i₂ i₃ f g,
      apply pullback.hom_ext; simp } },
  let τ : A'diagram ⟶ ((category_of_elements.π E).left_op ⋙ yoneda) :=
    { nat_trans . app := m', naturality' := λ i j f, pullback.lift_fst _ _ _ },
  let A'cocone : cocone A'diagram,
    refine ⟨A, λ i, top_map i, _⟩,
    intros i j f,
    rw pullback.lift_snd,
    apply (comp_id _).symm,
  let A'colimit := pullback_colimit A'cocone (is_a_limit E) τ m pb (λ i, cone_is_pullback _ _),
  let h' : Π (i : E.elementsᵒᵖ), {f // m' i ≫ f = top_map i ≫ σ ∧ ∀ a, m' i ≫ a = top_map i ≫ σ → a = f} :=
    λ i, (h _ _ (top_map i ≫ σ) (m' i)),
  let h'₁ : Π (i : E.elementsᵒᵖ), yoneda.obj i.unop.1.unop ⟶ P := λ i, (h' i).1,
  have h'₂ : ∀ (i : E.elementsᵒᵖ), m' i ≫ h'₁ i = top_map i ≫ σ := λ i, (h' i).2.1,
  have h'₃ : ∀ (i : E.elementsᵒᵖ) a, m' i ≫ a = top_map i ≫ σ → a = h'₁ i := λ i, (h' i).2.2,
  have legs : ∀ (i j : E.elementsᵒᵖ) (f : i ⟶ j), yoneda.map (has_hom.hom.unop f).1.unop ≫ h'₁ j = h'₁ i ≫ 𝟙 P,
  { intros,
    rw comp_id,
    apply h'₃ i,
    let hf : A' i ⟶ A' j := pullback.lift (m' i ≫ yoneda.map f.unop.1.unop) (top_map i)
                              (by erw [← pb i, assoc, (the_cocone E).w f]),
    have : hf ≫ m' j = m' i ≫ yoneda.map _ := pullback.lift_fst _ _ _,
    rw ← reassoc_of this,
    rw h'₂ j,
    apply pullback.lift_snd_assoc },
  refine ⟨(is_a_limit E).desc ⟨P, h'₁, legs⟩, _, _⟩,
  { apply A'colimit.hom_ext,
    intro i,
    rw ← pullback.condition_assoc,
    rw (is_a_limit E).fac,
    apply h'₂ },
  { intros q hq,
    apply (is_a_limit E).hom_ext,
    intro i,
    rw (is_a_limit E).fac,
    apply h'₃ i,
    rw pullback.condition_assoc,
    rw hq }
end.

def Jsheaf_is_jsheaf (P : Cᵒᵖ ⥤ Type u) (h : grothendieck.sheaf_condition J P) : sheaf_condition (j J) P :=
sheaf.yoneda_mk J P
begin
  introsI c S' f' m hm,
  let S := (sub_repr _).symm ⟦sub.mk' m⟧,
  have same_class := sub_repr_eq _ S,
  rw (sub_repr _).apply_symm_apply at same_class,
  have : closure.dense (j J) S.functor_inclusion,
  refine ⟨_⟩,
  change closure.operator _ (subq.mk _) = _,
  rw ← same_class,
  apply hm.closure_eq_top,
  have := dense_inclusion_iff J _ S this,
  have : classifier_of m = classifier_of S.functor_inclusion,
    change classify ⟦sub.mk' m⟧ = classify ⟦sub.mk' S.functor_inclusion⟧,
    rw same_class,
  let i := how_inj_is_classifier m S.functor_inclusion this,
  have hi : i.hom ≫ _ = _ := c_very_inj this,
  refine ⟨(h _ S (i.inv ≫ f') ‹S ∈ J c›).1.1.1, _, _⟩,
  rw [← hi, assoc, (h _ S (i.inv ≫ f') ‹S ∈ J c›).1.1.2], simp,
  intros a ha,
  apply subtype.ext_iff.1 ((h _ S (i.inv ≫ f') ‹S ∈ J c›).2 ⟨a, _⟩),
  rw iso.eq_inv_comp,
  rw reassoc_of hi,
  rw ha,
end

def equivalent_sheaf_conditions (P : Cᵒᵖ ⥤ Type u) :
  grothendieck.sheaf_condition J P ≃ sheaf_condition (j J) P :=
{ to_fun := Jsheaf_is_jsheaf _ _,
  inv_fun := jsheaf_is_Jsheaf _ _,
  left_inv := λ _, subsingleton.elim _ _,
  right_inv := λ _, subsingleton.elim _ _ }

end category_theory


/-
Copyright (c) 2022 James Gallicchio.

Authors: James Gallicchio
-/

namespace LeanColls

@[extern "leancolls_array_initialize"] private opaque arrayInit : IO Unit

builtin_initialize arrayInit

opaque ArrayUninit.Pointed (α : Type u) (n m : Nat) (h : m ≤ n): NonemptyType.{u}

def ArrayUninit (α) (n m : Nat) (h : m ≤ n) := (ArrayUninit.Pointed α n m h).type

namespace ArrayUninit

instance : Nonempty (ArrayUninit α n m h) := (ArrayUninit.Pointed α n m h).property

axiom partiallyInit_inhabited {α n m h} (A : ArrayUninit α n (.succ m) h) : Inhabited α

@[extern "leancolls_array_new"]
opaque new (n : @& Nat) : ArrayUninit α n 0 (Nat.zero_le _)

@[extern "leancolls_array_get"]
opaque get {n m : @& Nat} {h} (A : @& ArrayUninit α n m h) (i : @& Nat) (h_in : i < n) (h_im : i < m) : α
  := match m with
  | 0 => by contradiction
  | m+1 => (partiallyInit_inhabited A).default

@[extern "leancolls_array_push"]
opaque push {n m : @& Nat} {h} (A : ArrayUninit α n m h) (x : α) (h' : m < n) : ArrayUninit α n m.succ h'

@[extern "leancolls_array_pop"]
opaque pop {n m : @& Nat} {h} (A : ArrayUninit α n m.succ h) : ArrayUninit α n m (Nat.le_of_succ_le h)

@[extern "leancolls_array_set"]
opaque set {n m : @& Nat} {h} (A : ArrayUninit α n m h) (i : @& Nat) (h_i : i < n) (x : α) : ArrayUninit α n m h

@[extern "leancolls_array_resize"]
opaque resize {n m : @& Nat} {h} (A : ArrayUninit α n m h) (n' : @& Nat) (h' : m <= n') : ArrayUninit α n' m h'

@[extern "leancolls_array_isexclusive"]
opaque isExclusive (a : @& A) : Bool

@[simp]
axiom get_push {α n m h} {A : ArrayUninit α n m h} {x h' i hn hm}
  : get (push A x h') i hn hm =
    if h_i : i = m then x
    else get A i hn (Nat.lt_of_le_of_ne (Nat.le_of_succ_le_succ hm) h_i)

@[simp]
axiom get_pop {α n m h} {A : ArrayUninit α n (.succ m) h} {i hn hm}
  : get (pop A) i hn hm = get A i hn (Nat.le_step hm)

@[simp]
axiom get_set {α n m h} {A : ArrayUninit α n m h} {i hi x j hn hm}
  : get (set A i hi x) j hn hm =
    if i = j then x
    else get A j hn hm

@[simp]
axiom get_resize {α n m h} {A : ArrayUninit α n m h} {n' h' i hn hm}
  : get (resize A n' h') i hn hm = get A i (Nat.lt_of_lt_of_le hm h) hm

axiom ext {α n m h} {A B : ArrayUninit α n m h} : A.get = B.get → A = B

@[simp]
theorem ext_iff {α n m h} {A B : ArrayUninit α n m h}
  : A = B ↔ A.get = B.get := by
  constructor
  intro eq; rw [eq]
  exact ext


import simplifier.level_commutator_2

/-
Here you might want to use `simp` together with the fact that multiplication is associative
(this lemma is called `mul_assoc` as you might have guessed). Recall that you can tell
`simp` to use lemma `h` when simplifying by writing `simp[h]`.
-/

/- Lemma :
-/
lemma commutator_inv {G : Type*} [group G] {x y : G} : [y, x] = [x, y]⁻¹ :=
begin
  rw [commutator_def, commutator_def, mul_inv_rev, mul_inv_rev, mul_inv_rev, inv_inv, inv_inv,
    mul_assoc, mul_assoc],

end


import TBA.Eulerian.Nat

open Nat List

namespace Eq

protected theorem symm_iff {a b : α} : b = a ↔ a = b := ⟨Eq.symm, Eq.symm⟩

end Eq

namespace List

@[simp] theorem length_append {as bs : List α} :
  length (as ++ bs) = length as + length bs :=
  match as with
  | []      => by simp
  | a :: as => by simp [Nat.succ_add, length_append]

theorem length_zero_iff_nil {as : List α} : length as = 0 ↔ as = [] :=
⟨fun e => by cases as; rfl; simp [length_cons] at e, fun e => by rw [e]; rfl⟩

theorem length_cons_ne_zero {as : List α} {a : α} : length (a :: as) ≠ 0 := by
  rw [List.length_cons]; exact Nat.succNeZero _

-- Some lemmas about filters

@[simp] theorem filter_nil {p : α → Bool} : filter p [] = [] := by
  simp [filter, filterAux, reverse, reverseAux]

theorem cons_eq_append (a : α) (as : List α) : a :: as = [a] ++ as := rfl

theorem reverseAux_append {rs as : List α} : reverseAux rs as = reverseAux rs [] ++ as :=
  match rs with
  | []      => rfl
  | r :: rs => by
    simp only [reverseAux]
    rw [reverseAux_append, reverseAux_append (as := [r]), cons_eq_append r as, append_assoc]

theorem filterAux_aux {p : α → Bool} (as : List α) :
  (rs : List α) → filterAux p as rs = rs.reverse ++ (filterAux p as []) :=
  match as with
  | [] => by intros; simp [filterAux, reverse, reverseAux];
  | a :: as => by
    intro rs
    simp only [filterAux]
    cases p a
    case false => simp [filterAux_aux as rs]
    case true =>
      rw [filterAux_aux as (a :: rs), filterAux_aux as [a]]
      simp only [reverse, reverseAux, List.append, List.cons_append, List.nil_append]
      rw [reverseAux_append, cons_eq_append _ (filterAux p as []), append_assoc]

theorem filter_cons (a : α) (as : List α) :
  filter p (a :: as) = if p a then a :: filter p as else filter p as := by
  simp only [filter, filterAux]
  cases p a
  simp
  rw [filterAux_aux]; simp [reverse, reverseAux]

@[simp] theorem filter_append {as bs : List α} {p : α → Bool} :
  filter p (as ++ bs) = filter p as ++ filter p bs := by
  induction as with
  | nil      => simp
  | cons a as ih =>
    rw [filter_cons, cons_append, filter_cons]
    cases p a <;> simp [ih]

-- A membership predicate

inductive Mem : α → List α → Prop where
  | head (a : α) (as : List α)   : Mem a (a::as)
  | tail (a b : α) (bs : List α) : Mem a bs → Mem a (b::bs)

infix:50 " ∈ " => Mem

class IsBEqEq (α : Type) [BEq α] : Prop where
  beq_is_eq {a b : α} : (a == b) = (a = b)

instance [DecidableEq α] : IsBEqEq α where
  beq_is_eq := @fun a b => by byCases h : a = b <;> simp_all [BEq.beq, decideEqTrue, decideEqFalse]

instance [DecidableEq α] : IsBEqEq (α × α) where
  beq_is_eq := @fun (a₁, a₂) (b₁, b₂) => by byCases h₁ : a₁ = b₁ <;> byCases h₂ : a₂ = b₂ <;> simp_all [BEq.beq, decideEqTrue, decideEqFalse]

export IsBEqEq (beq_is_eq)

attribute [local simp] beq_is_eq

@[simp] theorem beq_refl [BEq α] [IsBEqEq α] : ((a : α) == a) = true := by
  simp

@[simp] theorem beq_neq [BEq α] [IsBEqEq α] {a b : α} (h : ¬ a = b) : (a == b) = false := by
   cases hbeq : a == b <;> simp_all

theorem length_erase_mem [BEq α] [IsBEqEq α] {a : α} {as : List α} (h : a ∈ as) :
  length (List.erase as a) + 1 = length as :=
  match a, as, h with
  | _, _, Mem.head a bs => by simp [List.erase]
  | _, _, Mem.tail a b bs h => by
    simp only [List.erase]
    match b == a with
    | true => simp
    | false => { simp only [length_cons]; rw [←length_erase_mem h] }

theorem mem_singleton [BEq α] [IsBEqEq α] {a b : α} (h : a ∈ [b]) : a = b := by
  cases h with
  | head h       => rfl
  | tail _ _ _ h => cases h

def mem_of_nonzero_length [BEq α] [IsBEqEq α] {as : List α} :
  (h : length as > 0) → { a // a ∈ as } :=
  match as with
  | []      => by { simp only [length_nil]; intro fa; cases fa }
  | a :: as => by { intros; apply Subtype.mk; apply Mem.head }

theorem mem_append {as bs : List α} : ∀ a, a ∈ (as ++ bs) → a ∈ as ∨ a ∈ bs :=
  match as with
  | [] => by intros; apply Or.inr; assumption
  | a :: as => by
    simp only [List.cons_append]
    intros a h
    cases h with
    | head h => apply Or.inl; apply Mem.head
    | tail _ _ _ h =>
      cases mem_append (as := as) (bs := bs) a h
      case inl => apply Or.inl; apply Mem.tail; assumption
      case inr => apply Or.inr; assumption

theorem mem_of_mem_filter {as : List α} {a : α} {p : α → Bool} : a ∈ filter p as → a ∈ as :=
  match as with
  | [] => by intros; assumption
  | a' :: as => by
      rw [filter_cons]; cases p a'
      case false => intro h; apply Mem.tail; apply mem_of_mem_filter h
      case true =>
        intro h
        cases h with
        | head => apply Mem.head
        | tail _ _ _ h => apply Mem.tail; apply mem_of_mem_filter h

theorem mem_filter_of_prop {as : List α} {a : α} {p : α → Bool} (ha : a ∈ as) (hpa : p a = true) :
  a ∈ filter p as := by
  induction ha with
  | head a as => rw [filter_cons, hpa]; simp; apply Mem.head
  | tail a b as ha' ih =>
    rw [filter_cons]
    cases hpb : p b
    case false => simp; exact ih hpa
    case true  => simp; apply Mem.tail; exact ih hpa

theorem filterProp_of_mem {as : List α} {p : α → Bool} {a : α} : a ∈ filter p as → p a = true :=
  match as with
  | [] => by intro h; cases h
  | a' :: as => by
      rw [filter_cons]; byCases hpa : p a'
      case inr => simp [hpa]; exact filterProp_of_mem
      case inl => simp [hpa]; intro h; cases h; assumption; apply filterProp_of_mem; assumption

theorem mem_split {a : α} {as : List α} (h : a ∈ as) : ∃ s t, as = s ++ a :: t :=
  match a, as, h with
  | _, _, Mem.head a bs     => ⟨[], ⟨bs, rfl⟩⟩
  | _, _, Mem.tail a b bs h =>
    match bs, mem_split h with
    | _, ⟨s, ⟨t, rfl⟩⟩ => ⟨b::s, ⟨t, List.cons_append .. ▸ rfl⟩⟩

-- Couting elements

section Count
variable [BEq α] [IsBEqEq α] (as bs as' bs' : List α) (a b : α)

def count : Nat := (as.filter $ fun b => b == a).length

@[simp] theorem count_empty : count [] a = 0 := by simp [count]

@[simp] theorem count_self_cons : (a :: as).count a = Nat.succ (as.count a) := by
  simp [count, filter_cons]

theorem count_neq_cons {as} {a b : α} (h : ¬ b = a) : (b :: as).count a = as.count a := by
  simp [count, filter_cons, h]

@[simp] theorem count_append : (as ++ bs).count a = as.count a + bs.count a :=
  by simp [count]

--set_option trace.Meta.Tactic.simp true
theorem count_erase {as : List α} {a b : α} :
  (as.erase a).count b = if (a == b) then (as.count b) - 1 else as.count b :=
  match as with
  | [] => by simp only [List.erase]; cases (a == b) <;> simp
  | a' :: as => by
    simp only [List.erase]
    byCases h : a' = a
    case inl =>
      cases h
      simp only [beq_refl]
      byCases h' : a = b
      case inl => cases h'; simp only [beq_refl, Lean.Simp.ite_True, Nat.succ_sub_succ, count_self_cons]; apply Nat.sub_zero
      case inr => rw [beq_neq h']; simp [count_neq_cons h']
    case inr =>
      rw [beq_neq h]
      byCases h' : a' = b
      case inl => cases h'; simp [count_erase (as := as), beq_neq (Ne.symm h)]
      case inr => simp [count_neq_cons h', count_neq_cons h', count_erase]

theorem count_le_cons : as.count a ≤ (b :: as).count a := by
  byCases h : (b = a)
  case inl => cases h; simp [count_self_cons, Nat.leSucc]
  case inr => rw [count_neq_cons h]; simp [Nat.leRefl]

theorem mem_nonzeroCount {as : List α} {a : α}: a ∈ as ↔ as.count a > 0 := by
  apply Iff.intro
  case mp =>
    intro h
    induction h with
    | head a => simp [Nat.zeroLtSucc]
    | tail _ _ _ h ih => apply Nat.ltOfLtOfLe ih; apply count_le_cons
  case mpr =>
    intro h
    let ⟨a', ha'⟩ := mem_of_nonzero_length h
    let foo := filterProp_of_mem ha'
    rw [←beq_is_eq.mp foo]
    apply mem_of_mem_filter ha'

-- Erasing elements from lists

theorem erase_comm {as : List α} : (as.erase a).erase b  = (as.erase b).erase a :=
  match as with
  | [] => rfl
  | a' :: as => by
    byCases h : a' = a
    case inl =>
      cases h; simp only [List.erase]
      byCases h' : b = a
      case inl => cases h'; simp
      case inr => rw [beq_neq (Ne.symm h')]; simp [List.erase]
    case inr =>
      simp only [List.erase]
      rw [beq_neq h]
      byCases h' : a' = b
      case inl => cases h'; simp [List.erase]
      case inr => simp [List.erase, beq_neq h', beq_neq h, erase_comm (as := as)]

theorem filter_erase_false {as : List α} {a : α} {p : α → Bool} (h : p a = false) :
  filter p (as.erase a) = filter p as :=
  match as with
  | [] => by simp [List.erase]
  | b :: as => by
    simp only [List.erase, filter_cons]
    byCases h' : b = a
    case inl => cases h'; simp [h]
    case inr => simp [h', filter_cons]; rw [filter_erase_false h]

theorem filter_erase_true {as : List α} {a : α} {p : α → Bool} (hpa : p a = true) :
  filter p (as.erase a) = (filter p as).erase a :=
  match as with
  | [] => rfl
  | b :: as => by
    simp
    byCases h : b = a
    case inl => cases h; simp [List.erase, filter_cons, hpa]
    case inr =>
      simp only [List.erase, beq_neq h, filter_cons]
      cases hpb : p b
      case false => exact filter_erase_true hpa
      case true => rw [filter_erase_true hpa]; simp [List.erase, h]

def eraseAll (as bs : List α) : List α :=
  match bs with
  | []      => as
  | b :: bs => eraseAll (as.erase b) bs

infixl:55 " -l " => eraseAll

theorem erase_eraseAll {as bs : List α} {a : α} : (as.erase a) -l bs = (as -l bs).erase a :=
  match bs with
  | [] => rfl
  | b :: bs => by simp only [eraseAll]; rw [←erase_eraseAll, erase_comm]

@[simp] theorem count_eraseAll (as bs : List α) (a : α) :
  (as -l bs).count a = as.count a - bs.count a :=
  match bs with
  | [] => rfl
  | b :: bs => by
    simp only [eraseAll]; rw [erase_eraseAll]; simp
    byCases hba : b = a;
    case inl => cases hba; rw [count_self_cons, count_erase, count_eraseAll as bs a, beq_refl]; rfl
    case inr => rw [count_erase, beq_neq hba, count_eraseAll as bs a, count_neq_cons]; repeat simp_all

-- Lists which are permutations of each other, and sublists modulo permutation

def isPermEqvTo : Prop := ∀ a, as.count a = bs.count a
infixl:50 " ≃ " => isPermEqvTo -- Type as \simeq

def isPermSubOf : Prop := ∀ a, as.count a ≤ bs.count a
infixl:50 " ⊆ " => isPermSubOf -- Type as \sub

theorem permSubOfEraseSub : (as -l bs) ⊆ as := fun a => by simp [Nat.subLe]

theorem mem_of_mem_eraseAll {as bs : List α} {a : α} : a ∈ (as -l bs) → a ∈ as := by
  rw [mem_nonzeroCount, mem_nonzeroCount, count_eraseAll]
  intro h; exact Nat.ltOfLtOfLe h (Nat.subLe _ _)

theorem permSubEraseAllLength {as bs : List α} : bs ⊆ as → length (as -l bs) = length as - length bs :=
  match bs with
  | [] => fun _ => rfl
  | b :: bs => fun hsub => by
    simp only [eraseAll, length_cons]
    have hbas : b ∈ as := by
      have hb := hsub b
      rw [count_self_cons] at hb
      rw [mem_nonzeroCount]
      exact Nat.ltOfLeOfLt (zeroLe _) (ltOfSuccLe hb)
    have hsub' : bs.isPermSubOf (as.erase b) := fun c => by
      have hc := hsub c
      byCases h : b = c
      case inl =>
        cases h
        rw [count_self_cons] at hc
        rw [count_erase]
        simp [leOfSuccLeSucc (Nat.leTrans hc leSuccSubOne)]
      case inr =>
        rw [count_neq_cons h] at hc
        apply Nat.leTrans hc
        rw [count_erase, beq_neq h]
        simp [Nat.leRefl]
    rw [permSubEraseAllLength hsub', ←length_erase_mem hbas, Nat.succ_sub_succ]

theorem permSubExtend {as bs : List α} {b} (hsub : bs ⊆ as) (ha : b ∈ (as -l bs)) : (b :: bs) ⊆ as := by
  intro a
  rw [mem_nonzeroCount, count_eraseAll, ←zeroLtIffSub] at ha
  byCases h : b = a
  case inl => cases h; simp; assumption
  case inr => rw [count_neq_cons h]; apply hsub

def permSubObtainComplement {as bs : List α} (hsub : bs ⊆ as)
    (hlength : bs.length < as.length) : { e // e ∈ as -l bs} := by
  have hl : (as -l bs).length > 0 := by rw [permSubEraseAllLength hsub, ←zeroLtIffSub]; assumption
  revert hl; cases as -l bs
  case nil => simp only [length_nil]; intro hl'; cases hl'
  case cons => intros; exact ⟨_, Mem.head _ _⟩

theorem permSubEqvClosed {as bs bs' : List α} (heqv : bs ≃ bs') (hsub : bs ⊆ as) : bs' ⊆ as :=
  fun a => by rw [←heqv a]; exact hsub a

theorem permSubSingleton {as : List α} {a : α} : a ∈ as → [a] ⊆ as := by
  intros ha b
  rw [mem_nonzeroCount] at ha
  byCases h : a = b
  case inl => cases h; simp [count_self_cons]; assumption
  case inr => rw [count_neq_cons h]; simp [Nat.zeroLe]

theorem permEqvRotate : (as ++ bs) ≃ (bs ++ as) :=
  fun a => by simp [Nat.add_comm];

theorem permEqvRefl {as : List α} : as ≃ as :=
  fun a => rfl

theorem permEqvTrans {as bs cs : List α} (h : as ≃ bs) (h' : bs ≃ cs) : as ≃ cs :=
  fun a => Eq.trans (h a) (h' a)

theorem permEqvSymm {as bs : List α} (h : as ≃ bs) : bs ≃ as :=
  fun a => by simp [Nat.add_comm, h a]

theorem permEqvToEraseAppend {as bs : List α} (ps : bs ⊆ as) : as ≃ ((as -l bs) ++ bs) :=
  fun a => by simp only [count_append, count_eraseAll]; rw [←Nat.le_subAdd (ps a)]

theorem permEqvToEraseCons {as : List α} {a : α} (h : a ∈ as) : as ≃ (a :: (as.erase a)) := by
  exact permEqvTrans (permEqvToEraseAppend (permSubSingleton h)) (permEqvRotate _ _)

theorem permEqvCons {as bs : List α} {a : α} (h : as ≃ bs) : (a :: as) ≃ (a :: bs) := by
  intro b
  byCases hba : (a = b)
  case inl => cases hba; simp [count_self_cons, h a];
  case inr => rw [count_neq_cons hba, count_neq_cons hba, h b]

theorem permSubEraseOfpermEqvCons {as bs : List α} {a : α} (h : (a :: as) ⊆ bs) :
  as ⊆ (bs.erase a) := fun b => by
  let ha := h a
  byCases h' : a = b
  case inl =>
    cases h'
    rw [count_erase]
    rw [count_self_cons] at ha
    simp [Nat.leOfSuccLeSucc (Nat.leTrans ha leSuccSubOne)]
  case inr =>
    let hb := h b
    rw [count_neq_cons h'] at hb
    rw [count_erase, beq_neq h']
    exact hb

theorem permEqvOfPermSub {as bs : List α} : as ⊆ bs → as.length = bs.length → as ≃ bs :=
  match as with
  | [] => fun hsub hl => by
    rw [length_nil, Eq.symm_iff, length_zero_iff_nil] at hl
    rw [hl]; intro b; rfl
  | a :: as => fun hsub hl => by
    have habs : a ∈ bs := by
      rw [mem_nonzeroCount]
      let hsuba := hsub a
      simp only [count_self_cons] at hsuba
      exact Nat.ltOfLtOfLe (Nat.zeroLtSucc _) hsuba
    have hsub' : as.isPermSubOf (bs.erase a) := permSubEraseOfpermEqvCons hsub
    have hl' : as.length = (bs.erase a).length := by
      apply Nat.add_right_cancel (m := 1)
      rw [length_erase_mem habs, ←hl, length_cons]
    have hp : bs.isPermEqvTo (a :: (bs.erase a)) := permEqvToEraseCons habs
    exact permEqvTrans (permEqvCons (permEqvOfPermSub hsub' hl')) (permEqvSymm hp)

theorem permEqvMemClosed {as bs : List α} {a : α} (hp : as ≃ bs) : a ∈ as → a ∈ bs := by
  rw [mem_nonzeroCount, mem_nonzeroCount, hp a]; intros; assumption

theorem permEqvEraseOfpermEqvCons {as bs : List α} {a : α} (h : (a :: as) ≃ bs) : as ≃ (bs.erase a) := fun a' => by
  let ha := h a
  byCases h' : a = a'
  case inl =>
    cases h'
    rw [count_erase]
    simp only [beq_refl, Lean.Simp.ite_True]
    rw [←ha, count_self_cons, succ_sub_succ,  Nat.sub_zero]
  case inr =>
    let ha' := h a'
    rw [count_neq_cons h'] at ha'
    rw [count_erase, beq_neq h']
    exact ha'

theorem permEqvLength {as bs : List α} : as ≃ bs → as.length = bs.length :=
  match as with
  | [] =>
    match bs with
    | [] => fun _ => rfl
    | b :: bs => fun h => by
      let hb := h b
      simp only [count] at hb; rw [filter_cons, beq_refl] at hb
      simp at hb
  | a :: as => fun h => by
    let ha := h a
    rw [count_self_cons] at ha
    rw [length_cons, permEqvLength (permEqvEraseOfpermEqvCons h)]
    exact length_erase_mem (mem_nonzeroCount.mpr (Nat.ltOfLtOfEq (Nat.zeroLtSucc (count as a)) ha))

theorem permEqv_filter_erase_true {as : List α} {a : α} {p : α → Bool} (hpa : p a = true) (ha : a ∈ as) :
  (a :: filter p (List.erase as a)) ≃ (filter p as) := by
  intro b
  byCases h : a = b
  case inl =>
    cases h
    rw [count_self_cons, filter_erase_true hpa, count_erase]
    simp only [beq_refl, Lean.Simp.ite_True]
    have h' : 1 ≤ count (filter p as) a := by
      apply Nat.succLeOfLt
      apply mem_nonzeroCount.mp
      apply mem_filter_of_prop ha hpa
    exact Eq.symm (le_subAdd h')
  case inr =>
    rw [count_neq_cons h, filter_erase_true hpa, count_erase, beq_neq h]
    simp

theorem permEqvFilter {as bs : List α} (p : α → Bool) : as ≃ bs → (filter p as) ≃ (filter p bs) :=
  match as with
  | [] => by
    intro h
    rw [length_zero_iff_nil.mp $ Eq.symm (permEqvLength h)]
    exact permEqvRefl
  | a :: as => by
    intro h
    rw [filter_cons]
    have h' := permEqvFilter p (permEqvEraseOfpermEqvCons h)
    cases hpa : p a with
    | true =>
      simp only [beq_refl, Lean.Simp.ite_True]
      apply permEqvTrans (permEqvCons h')
      have ha : a ∈ bs := by rw [mem_nonzeroCount, ←h a]; simp [zeroLtSucc]
      exact permEqv_filter_erase_true (as := bs) hpa ha
    | false =>
      refine permEqvTrans h' ?_
      rw [filter_erase_false hpa]
      exact permEqvRefl

end Count

end List


/-
Author: <Redacted for anonymized submission>
-/
-- import snarks.groth16.declarations
import ...attributes
import ...integral_domain_tactic
import ...general_lemmas.polynomial_degree
import data.mv_polynomial.basic
import data.mv_polynomial.funext
import data.polynomial.field_division
import algebra.polynomial.big_operators
-- import ...attributes
import .vars

/-!
# Knowledge Soundness

This file proves the knowledge-soundness property of the Groth16 system for type III pairings, as
presented in "Another Look at Extraction and Randomization of Groth’s zk-SNARK" by
[Baghery et al.](https://eprint.iacr.org/2020/811.pdf).

-/

open_locale big_operators classical

section groth16

open mv_polynomial groth16

noncomputable theory

universes u

/-- The finite field parameter of our SNARK -/
parameter {F : Type u}
parameter [field F]

/-- The naturals representing:
  n_stmt - the statement size,
  n_wit - the witness size -/
parameters {n_stmt n_wit n_var : ℕ}

/-- u_stmt and u_wit are fin-indexed collections of polynomials from the square span program -/
parameter {u_stmt : fin n_stmt → (polynomial F) }
parameter {u_wit : fin n_wit → (polynomial F) }
parameter {v_stmt : fin n_stmt → (polynomial F) }
parameter {v_wit : fin n_wit → (polynomial F) }
parameter {w_stmt : fin n_stmt → (polynomial F) }
parameter {w_wit : fin n_wit → (polynomial F) }

/-- The roots of the polynomial t -/
parameter {r : fin n_wit → F}
/-- t is the polynomial divisibility by which is used to verify satisfaction of the SSP -/
def t : polynomial F := ∏ i in (finset.fin_range n_wit), (polynomial.X - polynomial.C (r i))
-- TODO this could potentially be spun off into a mathlib definition

/-- Checks whether a statement witness pair satisfies the SSP -/
def satisfying (a_stmt : fin n_stmt → F ) (a_wit : fin n_wit → F) :=
((∑ i in (finset.fin_range n_stmt), a_stmt i • u_stmt i
  + (∑ i in (finset.fin_range n_wit), a_wit i • u_wit i))
  *
(∑ i in (finset.fin_range n_stmt), a_stmt i • v_stmt i
  + (∑ i in (finset.fin_range n_wit), a_wit i • v_wit i))
  -
(∑ i in (finset.fin_range n_stmt), a_stmt i • w_stmt i
  + (∑ i in (finset.fin_range n_wit), a_wit i • w_wit i)))
   %ₘ t = 0

run_cmd mk_simp_attr `crs
run_cmd tactic.add_doc_string `simp_attr.crs "Attribute for defintions of CRS elements"

/-- The crs elements
These funtions are actually multivariate Laurent polynomials of the toxic waste samples,
but we represent them here as functions on assignments of the variables to values.
-/
@[crs]
def crs_α  (f : groth16.vars → F) : polynomial F := polynomial.C (f vars.α)
@[crs]
def crs_β (f : groth16.vars → F) : polynomial F := polynomial.C (f vars.β)
@[crs]
def crs_γ (f : groth16.vars → F) : polynomial F := polynomial.C (f vars.γ)
@[crs]
def crs_δ (f : groth16.vars → F) : polynomial F := polynomial.C (f vars.δ)
@[crs]
def crs_powers_of_x (i : fin n_var) (f : groth16.vars → F) : polynomial F := ((polynomial.X)^(i : ℕ))
@[crs]
def crs_l (i : fin n_stmt) (f : groth16.vars → F) : polynomial F :=
polynomial.C (1 / f vars.γ) * (polynomial.C (f vars.β / f vars.γ) * u_stmt i
+
polynomial.C  (f vars.α / f vars.γ) * v_stmt i
+
(w_stmt i))
@[crs]
def crs_m (i : fin n_wit) (f : groth16.vars → F) : polynomial F :=
polynomial.C (1 / f vars.δ) * (polynomial.C  (f vars.β / f vars.δ) * (u_wit i)
+
polynomial.C  (f vars.α / f vars.δ) * (v_wit i)
+
(w_wit i))
@[crs]
def crs_n (i : fin (n_var - 1)) (f : groth16.vars → F) : polynomial F :=
(polynomial.X)^(i : ℕ) * t * polynomial.C (1 / f vars.δ)

/-- The coefficients of the CRS elements in the algebraic adversary's representation -/
parameters {A_α A_β A_γ A_δ B_α B_β B_γ B_δ C_α C_β C_γ C_δ  : F}
parameters {A_x B_x C_x : fin n_var → F}
parameters {A_l B_l C_l : fin n_stmt → F}
parameters {A_m B_m C_m : fin n_wit → F}
parameters {A_h B_h C_h : fin (n_var-1) → F}

/-- Polynomial forms of the adversary's proof representation -/
def A : (groth16.vars → F) -> polynomial F := λ f,
  polynomial.C A_α * (crs_α f)

def B : (groth16.vars → F) -> polynomial F  :=  λ f,
  polynomial.C B_β * crs_β f

def C : (groth16.vars → F) -> polynomial F  :=  λ f,
  polynomial.C C_α * crs_α f

local notation `groth16polynomial` := mv_polynomial vars (polynomial F)

/-- The modified crs elements
these are multivariate (non-Laurent!) polynomials of the toxic waste samples,
obtained by multiplying the Laurent polynomial forms of the CRS through by γδ.
We will later prove that the laurent polynomial equation is equivalent to a similar equation of the modified crs elements, allowing us to construct a proof in terms of polynomials -/
@[crs]
def crs'_α  : groth16polynomial := X vars.α * X vars.γ * X vars.δ
@[crs]
def crs'_β : groth16polynomial := X vars.β * X vars.γ * X vars.δ
@[crs]
def crs'_γ : groth16polynomial := X vars.γ * X vars.γ * X vars.δ
@[crs]
def crs'_δ : groth16polynomial := X vars.δ * X vars.γ * X vars.δ
@[crs]
def crs'_powers_of_x (i : fin n_var) : (groth16polynomial) := mv_polynomial.C (polynomial.X ^ (i : ℕ)) * X vars.γ * X vars.δ
-- I define prodcuts of these crs elements without the division, then later claim identities. Is this right?
@[crs]
def crs'_l (i : fin n_stmt) : (groth16polynomial) :=
(X vars.β * X vars.δ) * mv_polynomial.C (u_stmt i)
+
(X vars.α * X vars.δ) * mv_polynomial.C (v_stmt i)
+
X vars.δ * mv_polynomial.C (w_stmt i)
@[crs]
def crs'_m (i : fin n_wit) : (groth16polynomial) :=
(X vars.β * X vars.γ) * mv_polynomial.C (u_wit i)
+
(X vars.α * X vars.γ) * mv_polynomial.C (v_wit i)
+
X vars.γ * mv_polynomial.C (w_wit i)
@[crs]
def crs'_t (i : fin (n_var - 1)) : (groth16polynomial) :=
X vars.γ * mv_polynomial.C ((polynomial.X)^(i : ℕ) * t)

/-- Polynomial form of A in the adversary's proof representation -/
def A'  : groth16polynomial :=
  crs'_α * mv_polynomial.C (polynomial.C (A_α))
  +
  crs'_β * mv_polynomial.C (polynomial.C (A_β))

/-- Polynomial form of B in the adversary's proof representation -/
def B'  : groth16polynomial :=
  crs'_β * mv_polynomial.C (polynomial.C (B_β))

/-- Polynomial form of C in the adversary's proof representation -/
def C'  : groth16polynomial :=
  crs'_α * mv_polynomial.C (polynomial.C (C_α))
  +
  crs'_β * mv_polynomial.C (polynomial.C (C_β))
  +
  crs'_δ * mv_polynomial.C (polynomial.C (C_δ))

lemma modification_equivalence (a_stmt : fin n_stmt → F ) :
  A * B = C -> A' * B' = C'
:=
begin
  -- TODO different now that we switch to mv_poly vars (poly F)
  -- rw verified,
  -- rw verified',
  intro h,
  -- Apply functional extensionality
  simp [A', B', C'],
  rw function.funext_iff at h,
  simp [A, B, C] at h,

end

end groth16


lemma contrapositive2 (P Q : Prop) : (¬ Q → ¬ P) → (P → Q) :=
begin
by_cases p : P; by_cases q : Q,
intros h p2,
exact q,
repeat { tauto! },
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel

! This file was ported from Lean 3 source module topology.fiber_bundle.trivialization
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Bundle
import Mathlib.Topology.Algebra.Order.Field
import Mathlib.Topology.LocalHomeomorph

/-!
# Trivializations

## Main definitions

### Basic definitions

* `Trivialization F p` : structure extending local homeomorphisms, defining a local
  trivialization of a topological space `Z` with projection `p` and fiber `F`.

* `Pretrivialization F proj` : trivialization as a local equivalence, mainly used when the
  topology on the total space has not yet been defined.

### Operations on bundles

We provide the following operations on `Trivialization`s.

* `Trivialization.compHomeomorph`: given a local trivialization `e` of a fiber bundle
  `p : Z → B` and a homeomorphism `h : Z' ≃ₜ Z`, returns a local trivialization of the fiber bundle
  `p ∘ h`.

## Implementation notes

Previously, in mathlib, there was a structure `topological_vector_bundle.trivialization` which
extended another structure `topological_fibre_bundle.trivialization` by a linearity hypothesis. As
of PR leanprover-community/mathlib#17359, we have changed this to a single structure
`Trivialization` (no namespace), together with a mixin class `trivialization.is_linear`.

This permits all the *data* of a vector bundle to be held at the level of fibre bundles, so that the
same trivializations can underlie an object's structure as (say) a vector bundle over `ℂ` and as a
vector bundle over `ℝ`, as well as its structure simply as a fibre bundle.

This might be a little surprising, given the general trend of the library to ever-increased
bundling.  But in this case the typical motivation for more bundling does not apply: there is no
algebraic or order structure on the whole type of linear (say) trivializations of a bundle.
Indeed, since trivializations only have meaning on their base sets (taking junk values outside), the
type of linear trivializations is not even particularly well-behaved.
-/

open TopologicalSpace Filter Set Function Bundle Topology
open scoped Classical

variable {ι : Type _} {B : Type _} {F : Type _} {E : B → Type _}

variable (F) {Z : Type _} [TopologicalSpace B] [TopologicalSpace F] {proj : Z → B}

/-- This structure contains the information left for a local trivialization (which is implemented
below as `Trivialization F proj`) if the total space has not been given a topology, but we
have a topology on both the fiber and the base space. Through the construction
`topological_fiber_prebundle F proj` it will be possible to promote a
`Pretrivialization F proj` to a `Trivialization F proj`. -/
structure Pretrivialization (proj : Z → B) extends LocalEquiv Z (B × F) where
  open_target : IsOpen target
  baseSet : Set B
  open_baseSet : IsOpen baseSet
  source_eq : source = proj ⁻¹' baseSet
  target_eq : target = baseSet ×ˢ univ
  proj_toFun : ∀ p ∈ source, (toFun p).1 = proj p
#align pretrivialization Pretrivialization

namespace Pretrivialization

variable {F}
variable (e : Pretrivialization F proj) {x : Z}

/-- Coercion of a pretrivialization to a function. We don't use `e.toFun` in the `CoeFun` instance
because it is actually `e.toLocalEquiv.toFun`, so `simp` will apply lemmas about
`toLocalEquiv`. While we may want to switch to this behavior later, doing it mid-port will break a
lot of proofs.  -/
@[coe] def toFun' : Z → (B × F) := e.toFun

instance : CoeFun (Pretrivialization F proj) fun _ => Z → B × F := ⟨toFun'⟩

@[ext]
lemma ext' (e e' : Pretrivialization F proj) (h₁ : e.toLocalEquiv = e'.toLocalEquiv)
    (h₂ : e.baseSet = e'.baseSet) : e = e' := by
  cases e; cases e'; congr
#align pretrivialization.ext Pretrivialization.ext'

-- porting note: todo: move `ext` here?
lemma ext {e e' : Pretrivialization F proj} (h₁ : ∀ x, e x = e' x)
    (h₂ : ∀ x, e.toLocalEquiv.symm x = e'.toLocalEquiv.symm x) (h₃ : e.baseSet = e'.baseSet) :
    e = e' := by
  ext1 <;> [ext1, exact h₃]
  · apply h₁
  · apply h₂
  · rw [e.source_eq, e'.source_eq, h₃]

/-- If the fiber is nonempty, then the projection to -/
lemma toLocalEquiv_injective [Nonempty F] :
    Injective (toLocalEquiv : Pretrivialization F proj → LocalEquiv Z (B × F)) := fun e e' h => by
  refine ext' _ _ h ?_
  simpa only [fst_image_prod, univ_nonempty, target_eq]
    using congr_arg (Prod.fst '' LocalEquiv.target ·) h

@[simp, mfld_simps]
theorem coe_coe : ⇑e.toLocalEquiv = e :=
  rfl
#align pretrivialization.coe_coe Pretrivialization.coe_coe

@[simp, mfld_simps]
theorem coe_fst (ex : x ∈ e.source) : (e x).1 = proj x :=
  e.proj_toFun x ex
#align pretrivialization.coe_fst Pretrivialization.coe_fst

theorem mem_source : x ∈ e.source ↔ proj x ∈ e.baseSet := by rw [e.source_eq, mem_preimage]
#align pretrivialization.mem_source Pretrivialization.mem_source

theorem coe_fst' (ex : proj x ∈ e.baseSet) : (e x).1 = proj x :=
  e.coe_fst (e.mem_source.2 ex)
#align pretrivialization.coe_fst' Pretrivialization.coe_fst'

protected theorem eqOn : EqOn (Prod.fst ∘ e) proj e.source := fun _ hx => e.coe_fst hx
#align pretrivialization.eq_on Pretrivialization.eqOn

theorem mk_proj_snd (ex : x ∈ e.source) : (proj x, (e x).2) = e x :=
  Prod.ext (e.coe_fst ex).symm rfl
#align pretrivialization.mk_proj_snd Pretrivialization.mk_proj_snd

theorem mk_proj_snd' (ex : proj x ∈ e.baseSet) : (proj x, (e x).2) = e x :=
  Prod.ext (e.coe_fst' ex).symm rfl
#align pretrivialization.mk_proj_snd' Pretrivialization.mk_proj_snd'

/-- Composition of inverse and coercion from the subtype of the target. -/
def setSymm : e.target → Z :=
  e.target.restrict e.toLocalEquiv.symm
#align pretrivialization.set_symm Pretrivialization.setSymm

theorem mem_target {x : B × F} : x ∈ e.target ↔ x.1 ∈ e.baseSet := by
  rw [e.target_eq, prod_univ, mem_preimage]
#align pretrivialization.mem_target Pretrivialization.mem_target

theorem proj_symm_apply {x : B × F} (hx : x ∈ e.target) : proj (e.toLocalEquiv.symm x) = x.1 := by
  have := (e.coe_fst (e.map_target hx)).symm
  rwa [← e.coe_coe, e.right_inv hx] at this
#align pretrivialization.proj_symm_apply Pretrivialization.proj_symm_apply

theorem proj_symm_apply' {b : B} {x : F} (hx : b ∈ e.baseSet) :
    proj (e.toLocalEquiv.symm (b, x)) = b :=
  e.proj_symm_apply (e.mem_target.2 hx)
#align pretrivialization.proj_symm_apply' Pretrivialization.proj_symm_apply'

theorem proj_surjOn_baseSet [Nonempty F] : Set.SurjOn proj e.source e.baseSet := fun b hb =>
  let ⟨y⟩ := ‹Nonempty F›
  ⟨e.toLocalEquiv.symm (b, y), e.toLocalEquiv.map_target <| e.mem_target.2 hb,
    e.proj_symm_apply' hb⟩
#align pretrivialization.proj_surj_on_base_set Pretrivialization.proj_surjOn_baseSet

theorem apply_symm_apply {x : B × F} (hx : x ∈ e.target) : e (e.toLocalEquiv.symm x) = x :=
  e.toLocalEquiv.right_inv hx
#align pretrivialization.apply_symm_apply Pretrivialization.apply_symm_apply

theorem apply_symm_apply' {b : B} {x : F} (hx : b ∈ e.baseSet) :
    e (e.toLocalEquiv.symm (b, x)) = (b, x) :=
  e.apply_symm_apply (e.mem_target.2 hx)
#align pretrivialization.apply_symm_apply' Pretrivialization.apply_symm_apply'

theorem symm_apply_apply {x : Z} (hx : x ∈ e.source) : e.toLocalEquiv.symm (e x) = x :=
  e.toLocalEquiv.left_inv hx
#align pretrivialization.symm_apply_apply Pretrivialization.symm_apply_apply

@[simp, mfld_simps]
theorem symm_apply_mk_proj {x : Z} (ex : x ∈ e.source) :
    e.toLocalEquiv.symm (proj x, (e x).2) = x := by
  rw [← e.coe_fst ex, Prod.mk.eta, ← e.coe_coe, e.left_inv ex]
#align pretrivialization.symm_apply_mk_proj Pretrivialization.symm_apply_mk_proj

@[simp, mfld_simps]
theorem preimage_symm_proj_baseSet :
    e.toLocalEquiv.symm ⁻¹' (proj ⁻¹' e.baseSet) ∩ e.target = e.target := by
  refine' inter_eq_right_iff_subset.mpr fun x hx => _
  simp only [mem_preimage, LocalEquiv.invFun_as_coe, e.proj_symm_apply hx]
  exact e.mem_target.mp hx
#align pretrivialization.preimage_symm_proj_base_set Pretrivialization.preimage_symm_proj_baseSet

@[simp, mfld_simps]
theorem preimage_symm_proj_inter (s : Set B) :
    e.toLocalEquiv.symm ⁻¹' (proj ⁻¹' s) ∩ e.baseSet ×ˢ univ = (s ∩ e.baseSet) ×ˢ univ := by
  ext ⟨x, y⟩
  suffices x ∈ e.baseSet → (proj (e.toLocalEquiv.symm (x, y)) ∈ s ↔ x ∈ s) by
    simpa only [prod_mk_mem_set_prod_eq, mem_inter_iff, and_true_iff, mem_univ, and_congr_left_iff]
  intro h
  rw [e.proj_symm_apply' h]
#align pretrivialization.preimage_symm_proj_inter Pretrivialization.preimage_symm_proj_inter

theorem target_inter_preimage_symm_source_eq (e f : Pretrivialization F proj) :
    f.target ∩ f.toLocalEquiv.symm ⁻¹' e.source = (e.baseSet ∩ f.baseSet) ×ˢ univ := by
  rw [inter_comm, f.target_eq, e.source_eq, f.preimage_symm_proj_inter]
#align pretrivialization.target_inter_preimage_symm_source_eq Pretrivialization.target_inter_preimage_symm_source_eq

theorem trans_source (e f : Pretrivialization F proj) :
    (f.toLocalEquiv.symm.trans e.toLocalEquiv).source = (e.baseSet ∩ f.baseSet) ×ˢ univ := by
  rw [LocalEquiv.trans_source, LocalEquiv.symm_source, e.target_inter_preimage_symm_source_eq]
#align pretrivialization.trans_source Pretrivialization.trans_source

theorem symm_trans_symm (e e' : Pretrivialization F proj) :
    (e.toLocalEquiv.symm.trans e'.toLocalEquiv).symm = e'.toLocalEquiv.symm.trans e.toLocalEquiv :=
  by rw [LocalEquiv.trans_symm_eq_symm_trans_symm, LocalEquiv.symm_symm]
#align pretrivialization.symm_trans_symm Pretrivialization.symm_trans_symm

theorem symm_trans_source_eq (e e' : Pretrivialization F proj) :
    (e.toLocalEquiv.symm.trans e'.toLocalEquiv).source = (e.baseSet ∩ e'.baseSet) ×ˢ univ := by
  rw [LocalEquiv.trans_source, e'.source_eq, LocalEquiv.symm_source, e.target_eq, inter_comm,
    e.preimage_symm_proj_inter, inter_comm]
#align pretrivialization.symm_trans_source_eq Pretrivialization.symm_trans_source_eq

variable (e' : Pretrivialization F (π E)) {x' : TotalSpace E} {b : B} {y : E b}

theorem coe_mem_source : ↑y ∈ e'.source ↔ b ∈ e'.baseSet :=
  e'.mem_source
#align pretrivialization.coe_mem_source Pretrivialization.coe_mem_source

@[simp, mfld_simps]
theorem coe_coe_fst (hb : b ∈ e'.baseSet) : (e' y).1 = b :=
  e'.coe_fst (e'.mem_source.2 hb)
#align pretrivialization.coe_coe_fst Pretrivialization.coe_coe_fst

theorem mk_mem_target {x : B} {y : F} : (x, y) ∈ e'.target ↔ x ∈ e'.baseSet :=
  e'.mem_target
#align pretrivialization.mk_mem_target Pretrivialization.mk_mem_target

theorem symm_coe_proj {x : B} {y : F} (e' : Pretrivialization F (π E)) (h : x ∈ e'.baseSet) :
    (e'.toLocalEquiv.symm (x, y)).1 = x :=
  e'.proj_symm_apply' h
#align pretrivialization.symm_coe_proj Pretrivialization.symm_coe_proj

section Zero

variable [∀ x, Zero (E x)]

/-- A fiberwise inverse to `e`. This is the function `F → E b` that induces a local inverse
`B × F → total_space E` of `e` on `e.baseSet`. It is defined to be `0` outside `e.baseSet`. -/
protected noncomputable def symm (e : Pretrivialization F (π E)) (b : B) (y : F) : E b :=
  if hb : b ∈ e.baseSet then
    cast (congr_arg E (e.proj_symm_apply' hb)) (e.toLocalEquiv.symm (b, y)).2
  else 0
#align pretrivialization.symm Pretrivialization.symm

theorem symm_apply (e : Pretrivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    e.symm b y = cast (congr_arg E (e.symm_coe_proj hb)) (e.toLocalEquiv.symm (b, y)).2 :=
  dif_pos hb
#align pretrivialization.symm_apply Pretrivialization.symm_apply

theorem symm_apply_of_not_mem (e : Pretrivialization F (π E)) {b : B} (hb : b ∉ e.baseSet) (y : F) :
    e.symm b y = 0 :=
  dif_neg hb
#align pretrivialization.symm_apply_of_not_mem Pretrivialization.symm_apply_of_not_mem

theorem coe_symm_of_not_mem (e : Pretrivialization F (π E)) {b : B} (hb : b ∉ e.baseSet) :
    (e.symm b : F → E b) = 0 :=
  funext fun _ => dif_neg hb
#align pretrivialization.coe_symm_of_not_mem Pretrivialization.coe_symm_of_not_mem

theorem mk_symm (e : Pretrivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    totalSpaceMk b (e.symm b y) = e.toLocalEquiv.symm (b, y) := by
  rw [e.symm_apply hb, TotalSpace.mk_cast (e.proj_symm_apply' hb), TotalSpace.eta]
#align pretrivialization.mk_symm Pretrivialization.mk_symm

theorem symm_proj_apply (e : Pretrivialization F (π E)) (z : TotalSpace E)
    (hz : z.proj ∈ e.baseSet) : e.symm z.proj (e z).2 = z.2 := by
  rw [e.symm_apply hz, cast_eq_iff_heq, e.mk_proj_snd' hz, e.symm_apply_apply (e.mem_source.mpr hz)]
#align pretrivialization.symm_proj_apply Pretrivialization.symm_proj_apply

theorem symm_apply_apply_mk (e : Pretrivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : E b) :
    e.symm b (e (totalSpaceMk b y)).2 = y :=
  e.symm_proj_apply (totalSpaceMk b y) hb
#align pretrivialization.symm_apply_apply_mk Pretrivialization.symm_apply_apply_mk

theorem apply_mk_symm (e : Pretrivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    e (totalSpaceMk b (e.symm b y)) = (b, y) := by
  rw [e.mk_symm hb, e.apply_symm_apply (e.mk_mem_target.mpr hb)]
#align pretrivialization.apply_mk_symm Pretrivialization.apply_mk_symm

end Zero

end Pretrivialization

variable [TopologicalSpace Z] [TopologicalSpace (TotalSpace E)]

/-- A structure extending local homeomorphisms, defining a local trivialization of a projection
`proj : Z → B` with fiber `F`, as a local homeomorphism between `Z` and `B × F` defined between two
sets of the form `proj ⁻¹' baseSet` and `baseSet × F`, acting trivially on the first coordinate.
-/
-- porting note: todo: was @[nolint has_nonempty_instance]
structure Trivialization (proj : Z → B) extends LocalHomeomorph Z (B × F) where
  baseSet : Set B
  open_baseSet : IsOpen baseSet
  source_eq : source = proj ⁻¹' baseSet
  target_eq : target = baseSet ×ˢ univ
  proj_toFun : ∀ p ∈ source, (toLocalHomeomorph p).1 = proj p
#align trivialization Trivialization

namespace Trivialization

variable {F}
variable (e : Trivialization F proj) {x : Z}

@[ext]
lemma ext' (e e' : Trivialization F proj) (h₁ : e.toLocalHomeomorph = e'.toLocalHomeomorph)
    (h₂ : e.baseSet = e'.baseSet) : e = e' := by
  cases e; cases e'; congr
#align trivialization.ext Trivialization.ext'

/-- Coercion of a trivialization to a function. We don't use `e.toFun` in the `CoeFun` instance
because it is actually `e.toLocalEquiv.toFun`, so `simp` will apply lemmas about
`toLocalEquiv`. While we may want to switch to this behavior later, doing it mid-port will break a
lot of proofs.  -/
@[coe] def toFun' : Z → (B × F) := e.toFun

/-- Natural identification as a `Pretrivialization`. -/
def toPretrivialization : Pretrivialization F proj :=
  { e with }
#align trivialization.to_pretrivialization Trivialization.toPretrivialization

instance : CoeFun (Trivialization F proj) fun _ => Z → B × F := ⟨toFun'⟩

instance : Coe (Trivialization F proj) (Pretrivialization F proj) :=
  ⟨toPretrivialization⟩

theorem toPretrivialization_injective :
    Function.Injective fun e : Trivialization F proj => e.toPretrivialization := fun e e' h => by
  ext1
  exacts [LocalHomeomorph.toLocalEquiv_injective (congr_arg Pretrivialization.toLocalEquiv h),
    congr_arg Pretrivialization.baseSet h]
#align trivialization.to_pretrivialization_injective Trivialization.toPretrivialization_injective

@[simp, mfld_simps]
theorem coe_coe : ⇑e.toLocalHomeomorph = e :=
  rfl
#align trivialization.coe_coe Trivialization.coe_coe

@[simp, mfld_simps]
theorem coe_fst (ex : x ∈ e.source) : (e x).1 = proj x :=
  e.proj_toFun x ex
#align trivialization.coe_fst Trivialization.coe_fst

protected theorem eqOn : EqOn (Prod.fst ∘ e) proj e.source := fun _x hx => e.coe_fst hx
#align trivialization.eq_on Trivialization.eqOn

theorem mem_source : x ∈ e.source ↔ proj x ∈ e.baseSet := by rw [e.source_eq, mem_preimage]
#align trivialization.mem_source Trivialization.mem_source

theorem coe_fst' (ex : proj x ∈ e.baseSet) : (e x).1 = proj x :=
  e.coe_fst (e.mem_source.2 ex)
#align trivialization.coe_fst' Trivialization.coe_fst'

theorem mk_proj_snd (ex : x ∈ e.source) : (proj x, (e x).2) = e x :=
  Prod.ext (e.coe_fst ex).symm rfl
#align trivialization.mk_proj_snd Trivialization.mk_proj_snd

theorem mk_proj_snd' (ex : proj x ∈ e.baseSet) : (proj x, (e x).2) = e x :=
  Prod.ext (e.coe_fst' ex).symm rfl
#align trivialization.mk_proj_snd' Trivialization.mk_proj_snd'

theorem source_inter_preimage_target_inter (s : Set (B × F)) :
    e.source ∩ e ⁻¹' (e.target ∩ s) = e.source ∩ e ⁻¹' s :=
  e.toLocalHomeomorph.source_inter_preimage_target_inter s
#align trivialization.source_inter_preimage_target_inter Trivialization.source_inter_preimage_target_inter

@[simp, mfld_simps]
theorem coe_mk (e : LocalHomeomorph Z (B × F)) (i j k l m) (x : Z) :
    (Trivialization.mk e i j k l m : Trivialization F proj) x = e x :=
  rfl
#align trivialization.coe_mk Trivialization.coe_mk

theorem mem_target {x : B × F} : x ∈ e.target ↔ x.1 ∈ e.baseSet :=
  e.toPretrivialization.mem_target
#align trivialization.mem_target Trivialization.mem_target

theorem map_target {x : B × F} (hx : x ∈ e.target) : e.toLocalHomeomorph.symm x ∈ e.source :=
  e.toLocalHomeomorph.map_target hx
#align trivialization.map_target Trivialization.map_target

theorem proj_symm_apply {x : B × F} (hx : x ∈ e.target) : proj (e.toLocalHomeomorph.symm x) = x.1 :=
  e.toPretrivialization.proj_symm_apply hx
#align trivialization.proj_symm_apply Trivialization.proj_symm_apply

theorem proj_symm_apply' {b : B} {x : F} (hx : b ∈ e.baseSet) :
    proj (e.toLocalHomeomorph.symm (b, x)) = b :=
  e.toPretrivialization.proj_symm_apply' hx
#align trivialization.proj_symm_apply' Trivialization.proj_symm_apply'

theorem proj_surjOn_baseSet [Nonempty F] : Set.SurjOn proj e.source e.baseSet :=
  e.toPretrivialization.proj_surjOn_baseSet
#align trivialization.proj_surj_on_base_set Trivialization.proj_surjOn_baseSet

theorem apply_symm_apply {x : B × F} (hx : x ∈ e.target) : e (e.toLocalHomeomorph.symm x) = x :=
  e.toLocalHomeomorph.right_inv hx
#align trivialization.apply_symm_apply Trivialization.apply_symm_apply

theorem apply_symm_apply' {b : B} {x : F} (hx : b ∈ e.baseSet) :
    e (e.toLocalHomeomorph.symm (b, x)) = (b, x) :=
  e.toPretrivialization.apply_symm_apply' hx
#align trivialization.apply_symm_apply' Trivialization.apply_symm_apply'

@[simp, mfld_simps]
theorem symm_apply_mk_proj (ex : x ∈ e.source) : e.toLocalHomeomorph.symm (proj x, (e x).2) = x :=
  e.toPretrivialization.symm_apply_mk_proj ex
#align trivialization.symm_apply_mk_proj Trivialization.symm_apply_mk_proj

theorem symm_trans_source_eq (e e' : Trivialization F proj) :
    (e.toLocalEquiv.symm.trans e'.toLocalEquiv).source = (e.baseSet ∩ e'.baseSet) ×ˢ univ :=
  Pretrivialization.symm_trans_source_eq e.toPretrivialization e'
#align trivialization.symm_trans_source_eq Trivialization.symm_trans_source_eq

theorem symm_trans_target_eq (e e' : Trivialization F proj) :
    (e.toLocalEquiv.symm.trans e'.toLocalEquiv).target = (e.baseSet ∩ e'.baseSet) ×ˢ univ :=
  Pretrivialization.symm_trans_target_eq e.toPretrivialization e'
#align trivialization.symm_trans_target_eq Trivialization.symm_trans_target_eq

theorem coe_fst_eventuallyEq_proj (ex : x ∈ e.source) : Prod.fst ∘ e =ᶠ[𝓝 x] proj :=
  mem_nhds_iff.2 ⟨e.source, fun _y hy => e.coe_fst hy, e.open_source, ex⟩
#align trivialization.coe_fst_eventually_eq_proj Trivialization.coe_fst_eventuallyEq_proj

theorem coe_fst_eventuallyEq_proj' (ex : proj x ∈ e.baseSet) : Prod.fst ∘ e =ᶠ[𝓝 x] proj :=
  e.coe_fst_eventuallyEq_proj (e.mem_source.2 ex)
#align trivialization.coe_fst_eventually_eq_proj' Trivialization.coe_fst_eventuallyEq_proj'

theorem map_proj_nhds (ex : x ∈ e.source) : map proj (𝓝 x) = 𝓝 (proj x) := by
  rw [← e.coe_fst ex, ← map_congr (e.coe_fst_eventuallyEq_proj ex), ← map_map, ← e.coe_coe,
    e.map_nhds_eq ex, map_fst_nhds]
#align trivialization.map_proj_nhds Trivialization.map_proj_nhds

theorem preimage_subset_source {s : Set B} (hb : s ⊆ e.baseSet) : proj ⁻¹' s ⊆ e.source :=
  fun _p hp => e.mem_source.mpr (hb hp)
#align trivialization.preimage_subset_source Trivialization.preimage_subset_source

theorem image_preimage_eq_prod_univ {s : Set B} (hb : s ⊆ e.baseSet) :
    e '' (proj ⁻¹' s) = s ×ˢ univ :=
  Subset.antisymm
    (image_subset_iff.mpr fun p hp =>
      ⟨(e.proj_toFun p (e.preimage_subset_source hb hp)).symm ▸ hp, trivial⟩)
    fun p hp =>
    let hp' : p ∈ e.target := e.mem_target.mpr (hb hp.1)
    ⟨e.invFun p, mem_preimage.mpr ((e.proj_symm_apply hp').symm ▸ hp.1), e.apply_symm_apply hp'⟩
#align trivialization.image_preimage_eq_prod_univ Trivialization.image_preimage_eq_prod_univ

/-- The preimage of a subset of the base set is homeomorphic to the product with the fiber. -/
def preimageHomeomorph {s : Set B} (hb : s ⊆ e.baseSet) : proj ⁻¹' s ≃ₜ s × F :=
  (e.toLocalHomeomorph.homeomorphOfImageSubsetSource (e.preimage_subset_source hb)
        (e.image_preimage_eq_prod_univ hb)).trans
    ((Homeomorph.Set.prod s univ).trans ((Homeomorph.refl s).prodCongr (Homeomorph.Set.univ F)))
#align trivialization.preimage_homeomorph Trivialization.preimageHomeomorph

@[simp]
theorem preimageHomeomorph_apply {s : Set B} (hb : s ⊆ e.baseSet) (p : proj ⁻¹' s) :
    e.preimageHomeomorph hb p = (⟨proj p, p.2⟩, (e p).2) :=
  Prod.ext (Subtype.ext (e.proj_toFun p (e.mem_source.mpr (hb p.2)))) rfl
#align trivialization.preimage_homeomorph_apply Trivialization.preimageHomeomorph_apply

@[simp]
theorem preimageHomeomorph_symm_apply {s : Set B} (hb : s ⊆ e.baseSet) (p : s × F) :
    (e.preimageHomeomorph hb).symm p = ⟨e.symm (p.1, p.2), ((e.preimageHomeomorph hb).symm p).2⟩ :=
  rfl
#align trivialization.preimage_homeomorph_symm_apply Trivialization.preimageHomeomorph_symm_apply

/-- The source is homeomorphic to the product of the base set with the fiber. -/
def sourceHomeomorphBaseSetProd : e.source ≃ₜ e.baseSet × F :=
  (Homeomorph.setCongr e.source_eq).trans (e.preimageHomeomorph subset_rfl)
#align trivialization.source_homeomorph_base_set_prod Trivialization.sourceHomeomorphBaseSetProd

@[simp]
theorem sourceHomeomorphBaseSetProd_apply (p : e.source) :
    e.sourceHomeomorphBaseSetProd p = (⟨proj p, e.mem_source.mp p.2⟩, (e p).2) :=
  e.preimageHomeomorph_apply subset_rfl ⟨p, e.mem_source.mp p.2⟩
#align trivialization.source_homeomorph_base_set_prod_apply Trivialization.sourceHomeomorphBaseSetProd_apply

@[simp]
theorem sourceHomeomorphBaseSetProd_symm_apply (p : e.baseSet × F) :
    e.sourceHomeomorphBaseSetProd.symm p =
      ⟨e.symm (p.1, p.2), (e.sourceHomeomorphBaseSetProd.symm p).2⟩ :=
  rfl
#align trivialization.source_homeomorph_base_set_prod_symm_apply Trivialization.sourceHomeomorphBaseSetProd_symm_apply

/-- Each fiber of a trivialization is homeomorphic to the specified fiber. -/
def preimageSingletonHomeomorph {b : B} (hb : b ∈ e.baseSet) : proj ⁻¹' {b} ≃ₜ F :=
  .trans (e.preimageHomeomorph (Set.singleton_subset_iff.mpr hb)) <|
    .trans (.prodCongr (Homeomorph.homeomorphOfUnique ({b} : Set B) PUnit.{1}) (Homeomorph.refl F))
      (Homeomorph.punitProd F)
#align trivialization.preimage_singleton_homeomorph Trivialization.preimageSingletonHomeomorph

@[simp]
theorem preimageSingletonHomeomorph_apply {b : B} (hb : b ∈ e.baseSet) (p : proj ⁻¹' {b}) :
    e.preimageSingletonHomeomorph hb p = (e p).2 :=
  rfl
#align trivialization.preimage_singleton_homeomorph_apply Trivialization.preimageSingletonHomeomorph_apply

@[simp]
theorem preimageSingletonHomeomorph_symm_apply {b : B} (hb : b ∈ e.baseSet) (p : F) :
    (e.preimageSingletonHomeomorph hb).symm p =
      ⟨e.symm (b, p), by rw [mem_preimage, e.proj_symm_apply' hb, mem_singleton_iff]⟩ :=
  rfl
#align trivialization.preimage_singleton_homeomorph_symm_apply Trivialization.preimageSingletonHomeomorph_symm_apply

/-- In the domain of a bundle trivialization, the projection is continuous-/
theorem continuousAt_proj (ex : x ∈ e.source) : ContinuousAt proj x :=
  (e.map_proj_nhds ex).le
#align trivialization.continuous_at_proj Trivialization.continuousAt_proj

/-- Composition of a `Trivialization` and a `Homeomorph`. -/
protected def compHomeomorph {Z' : Type _} [TopologicalSpace Z'] (h : Z' ≃ₜ Z) :
    Trivialization F (proj ∘ h) where
  toLocalHomeomorph := h.toLocalHomeomorph.trans e.toLocalHomeomorph
  baseSet := e.baseSet
  open_baseSet := e.open_baseSet
  source_eq := by simp [source_eq, preimage_preimage, (· ∘ ·)]
  target_eq := by simp [target_eq]
  proj_toFun p hp := by
    have hp : h p ∈ e.source := by simpa using hp
    simp [hp]
#align trivialization.comp_homeomorph Trivialization.compHomeomorph

/-- Read off the continuity of a function `f : Z → X` at `z : Z` by transferring via a
trivialization of `Z` containing `z`. -/
theorem continuousAt_of_comp_right {X : Type _} [TopologicalSpace X] {f : Z → X} {z : Z}
    (e : Trivialization F proj) (he : proj z ∈ e.baseSet)
    (hf : ContinuousAt (f ∘ e.toLocalEquiv.symm) (e z)) : ContinuousAt f z := by
  have hez : z ∈ e.toLocalEquiv.symm.target := by
    rw [LocalEquiv.symm_target, e.mem_source]
    exact he
  rwa [e.toLocalHomeomorph.symm.continuousAt_iff_continuousAt_comp_right hez,
    LocalHomeomorph.symm_symm]
#align trivialization.continuous_at_of_comp_right Trivialization.continuousAt_of_comp_right

/-- Read off the continuity of a function `f : X → Z` at `x : X` by transferring via a
trivialization of `Z` containing `f x`. -/
theorem continuousAt_of_comp_left {X : Type _} [TopologicalSpace X] {f : X → Z} {x : X}
    (e : Trivialization F proj) (hf_proj : ContinuousAt (proj ∘ f) x) (he : proj (f x) ∈ e.baseSet)
    (hf : ContinuousAt (e ∘ f) x) : ContinuousAt f x := by
  rw [e.continuousAt_iff_continuousAt_comp_left]
  · exact hf
  rw [e.source_eq, ← preimage_comp]
  exact hf_proj.preimage_mem_nhds (e.open_baseSet.mem_nhds he)
#align trivialization.continuous_at_of_comp_left Trivialization.continuousAt_of_comp_left

variable (e' : Trivialization F (π E)) {x' : TotalSpace E} {b : B} {y : E b}

protected theorem continuousOn : ContinuousOn e' e'.source :=
  e'.continuous_toFun
#align trivialization.continuous_on Trivialization.continuousOn

theorem coe_mem_source : ↑y ∈ e'.source ↔ b ∈ e'.baseSet :=
  e'.mem_source
#align trivialization.coe_mem_source Trivialization.coe_mem_source

@[deprecated LocalHomeomorph.open_target]
theorem open_target' : IsOpen e'.target := e'.open_target
#align trivialization.open_target Trivialization.open_target'

@[simp, mfld_simps]
theorem coe_coe_fst (hb : b ∈ e'.baseSet) : (e' y).1 = b :=
  e'.coe_fst (e'.mem_source.2 hb)
#align trivialization.coe_coe_fst Trivialization.coe_coe_fst

theorem mk_mem_target {y : F} : (b, y) ∈ e'.target ↔ b ∈ e'.baseSet :=
  e'.toPretrivialization.mem_target
#align trivialization.mk_mem_target Trivialization.mk_mem_target

theorem symm_apply_apply {x : TotalSpace E} (hx : x ∈ e'.source) :
    e'.toLocalHomeomorph.symm (e' x) = x :=
  e'.toLocalEquiv.left_inv hx
#align trivialization.symm_apply_apply Trivialization.symm_apply_apply

@[simp, mfld_simps]
theorem symm_coe_proj {x : B} {y : F} (e : Trivialization F (π E)) (h : x ∈ e.baseSet) :
    (e.toLocalHomeomorph.symm (x, y)).1 = x :=
  e.proj_symm_apply' h
#align trivialization.symm_coe_proj Trivialization.symm_coe_proj

section Zero

variable [∀ x, Zero (E x)]

/-- A fiberwise inverse to `e'`. The function `F → E x` that induces a local inverse
`B × F → total_space E` of `e'` on `e'.baseSet`. It is defined to be `0` outside `e'.baseSet`. -/
protected noncomputable def symm (e : Trivialization F (π E)) (b : B) (y : F) : E b :=
  e.toPretrivialization.symm b y
#align trivialization.symm Trivialization.symm

theorem symm_apply (e : Trivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    e.symm b y = cast (congr_arg E (e.symm_coe_proj hb)) (e.toLocalHomeomorph.symm (b, y)).2 :=
  dif_pos hb
#align trivialization.symm_apply Trivialization.symm_apply

theorem symm_apply_of_not_mem (e : Trivialization F (π E)) {b : B} (hb : b ∉ e.baseSet) (y : F) :
    e.symm b y = 0 :=
  dif_neg hb
#align trivialization.symm_apply_of_not_mem Trivialization.symm_apply_of_not_mem

theorem mk_symm (e : Trivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    totalSpaceMk b (e.symm b y) = e.toLocalHomeomorph.symm (b, y) :=
  e.toPretrivialization.mk_symm hb y
#align trivialization.mk_symm Trivialization.mk_symm

theorem symm_proj_apply (e : Trivialization F (π E)) (z : TotalSpace E) (hz : z.proj ∈ e.baseSet) :
    e.symm z.proj (e z).2 = z.2 :=
  e.toPretrivialization.symm_proj_apply z hz
#align trivialization.symm_proj_apply Trivialization.symm_proj_apply

theorem symm_apply_apply_mk (e : Trivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : E b) :
    e.symm b (e (totalSpaceMk b y)).2 = y :=
  e.symm_proj_apply (totalSpaceMk b y) hb
#align trivialization.symm_apply_apply_mk Trivialization.symm_apply_apply_mk

theorem apply_mk_symm (e : Trivialization F (π E)) {b : B} (hb : b ∈ e.baseSet) (y : F) :
    e (totalSpaceMk b (e.symm b y)) = (b, y) :=
  e.toPretrivialization.apply_mk_symm hb y
#align trivialization.apply_mk_symm Trivialization.apply_mk_symm

theorem continuousOn_symm (e : Trivialization F (π E)) :
    ContinuousOn (fun z : B × F => totalSpaceMk z.1 (e.symm z.1 z.2)) (e.baseSet ×ˢ univ) := by
  have : ∀ z ∈ e.baseSet ×ˢ (univ : Set F),
      totalSpaceMk z.1 (e.symm z.1 z.2) = e.toLocalHomeomorph.symm z := by
    rintro x ⟨hx : x.1 ∈ e.baseSet, _⟩
    rw [e.mk_symm hx]
  refine' ContinuousOn.congr _ this
  rw [← e.target_eq]
  exact e.toLocalHomeomorph.continuousOn_symm
#align trivialization.continuous_on_symm Trivialization.continuousOn_symm

end Zero

/-- If `e` is a `Trivialization` of `proj : Z → B` with fiber `F` and `h` is a homeomorphism
`F ≃ₜ F'`, then `e.trans_fiber_homeomorph h` is the trivialization of `proj` with the fiber `F'`
that sends `p : Z` to `((e p).1, h (e p).2)`. -/
def transFiberHomeomorph {F' : Type _} [TopologicalSpace F'] (e : Trivialization F proj)
    (h : F ≃ₜ F') : Trivialization F' proj where
  toLocalHomeomorph := e.toLocalHomeomorph.transHomeomorph <| (Homeomorph.refl _).prodCongr h
  baseSet := e.baseSet
  open_baseSet := e.open_baseSet
  source_eq := e.source_eq
  target_eq := by simp [target_eq, prod_univ, preimage_preimage]
  proj_toFun := e.proj_toFun
#align trivialization.trans_fiber_homeomorph Trivialization.transFiberHomeomorph

@[simp]
theorem transFiberHomeomorph_apply {F' : Type _} [TopologicalSpace F'] (e : Trivialization F proj)
    (h : F ≃ₜ F') (x : Z) : e.transFiberHomeomorph h x = ((e x).1, h (e x).2) :=
  rfl
#align trivialization.trans_fiber_homeomorph_apply Trivialization.transFiberHomeomorph_apply

/-- Coordinate transformation in the fiber induced by a pair of bundle trivializations. See also
`Trivialization.coordChangeHomeomorph` for a version bundled as `F ≃ₜ F`. -/
def coordChange (e₁ e₂ : Trivialization F proj) (b : B) (x : F) : F :=
  (e₂ <| e₁.toLocalHomeomorph.symm (b, x)).2
#align trivialization.coord_change Trivialization.coordChange

theorem mk_coordChange (e₁ e₂ : Trivialization F proj) {b : B} (h₁ : b ∈ e₁.baseSet)
    (h₂ : b ∈ e₂.baseSet) (x : F) :
    (b, e₁.coordChange e₂ b x) = e₂ (e₁.toLocalHomeomorph.symm (b, x)) := by
  refine' Prod.ext _ rfl
  rw [e₂.coe_fst', ← e₁.coe_fst', e₁.apply_symm_apply' h₁]
  · rwa [e₁.proj_symm_apply' h₁]
  · rwa [e₁.proj_symm_apply' h₁]
#align trivialization.mk_coord_change Trivialization.mk_coordChange

theorem coordChange_apply_snd (e₁ e₂ : Trivialization F proj) {p : Z} (h : proj p ∈ e₁.baseSet) :
    e₁.coordChange e₂ (proj p) (e₁ p).snd = (e₂ p).snd := by
  rw [coordChange, e₁.symm_apply_mk_proj (e₁.mem_source.2 h)]
#align trivialization.coord_change_apply_snd Trivialization.coordChange_apply_snd

theorem coordChange_same_apply (e : Trivialization F proj) {b : B} (h : b ∈ e.baseSet) (x : F) :
    e.coordChange e b x = x := by rw [coordChange, e.apply_symm_apply' h]
#align trivialization.coord_change_same_apply Trivialization.coordChange_same_apply

theorem coordChange_same (e : Trivialization F proj) {b : B} (h : b ∈ e.baseSet) :
    e.coordChange e b = id :=
  funext <| e.coordChange_same_apply h
#align trivialization.coord_change_same Trivialization.coordChange_same

theorem coordChange_coordChange (e₁ e₂ e₃ : Trivialization F proj) {b : B} (h₁ : b ∈ e₁.baseSet)
    (h₂ : b ∈ e₂.baseSet) (x : F) :
    e₂.coordChange e₃ b (e₁.coordChange e₂ b x) = e₁.coordChange e₃ b x := by
  rw [coordChange, e₁.mk_coordChange _ h₁ h₂, ← e₂.coe_coe, e₂.left_inv, coordChange]
  rwa [e₂.mem_source, e₁.proj_symm_apply' h₁]
#align trivialization.coord_change_coord_change Trivialization.coordChange_coordChange

theorem continuous_coordChange (e₁ e₂ : Trivialization F proj) {b : B} (h₁ : b ∈ e₁.baseSet)
    (h₂ : b ∈ e₂.baseSet) : Continuous (e₁.coordChange e₂ b) := by
  refine' continuous_snd.comp (e₂.toLocalHomeomorph.continuousOn.comp_continuous
    (e₁.toLocalHomeomorph.continuousOn_symm.comp_continuous _ _) _)
  · exact continuous_const.prod_mk continuous_id
  · exact fun x => e₁.mem_target.2 h₁
  · intro x
    rwa [e₂.mem_source, e₁.proj_symm_apply' h₁]
#align trivialization.continuous_coord_change Trivialization.continuous_coordChange

/-- Coordinate transformation in the fiber induced by a pair of bundle trivializations,
as a homeomorphism. -/
protected def coordChangeHomeomorph (e₁ e₂ : Trivialization F proj) {b : B} (h₁ : b ∈ e₁.baseSet)
    (h₂ : b ∈ e₂.baseSet) : F ≃ₜ F where
  toFun := e₁.coordChange e₂ b
  invFun := e₂.coordChange e₁ b
  left_inv x := by simp only [*, coordChange_coordChange, coordChange_same_apply]
  right_inv x := by simp only [*, coordChange_coordChange, coordChange_same_apply]
  continuous_toFun := e₁.continuous_coordChange e₂ h₁ h₂
  continuous_invFun := e₂.continuous_coordChange e₁ h₂ h₁
#align trivialization.coord_change_homeomorph Trivialization.coordChangeHomeomorph

@[simp]
theorem coordChangeHomeomorph_coe (e₁ e₂ : Trivialization F proj) {b : B} (h₁ : b ∈ e₁.baseSet)
    (h₂ : b ∈ e₂.baseSet) : ⇑(e₁.coordChangeHomeomorph e₂ h₁ h₂) = e₁.coordChange e₂ b :=
  rfl
#align trivialization.coord_change_homeomorph_coe Trivialization.coordChangeHomeomorph_coe

variable {B' : Type _} [TopologicalSpace B']

theorem isImage_preimage_prod (e : Trivialization F proj) (s : Set B) :
    e.toLocalHomeomorph.IsImage (proj ⁻¹' s) (s ×ˢ univ) := fun x hx => by simp [e.coe_fst', hx]
#align trivialization.is_image_preimage_prod Trivialization.isImage_preimage_prod

/-- Restrict a `Trivialization` to an open set in the base. `-/
protected def restrOpen (e : Trivialization F proj) (s : Set B) (hs : IsOpen s) :
    Trivialization F proj where
  toLocalHomeomorph :=
    ((e.isImage_preimage_prod s).symm.restr (IsOpen.inter e.open_target (hs.prod isOpen_univ))).symm
  baseSet := e.baseSet ∩ s
  open_baseSet := IsOpen.inter e.open_baseSet hs
  source_eq := by simp [source_eq]
  target_eq := by simp [target_eq, prod_univ]
  proj_toFun p hp := e.proj_toFun p hp.1
#align trivialization.restr_open Trivialization.restrOpen

section Piecewise

theorem frontier_preimage (e : Trivialization F proj) (s : Set B) :
    e.source ∩ frontier (proj ⁻¹' s) = proj ⁻¹' (e.baseSet ∩ frontier s) := by
  rw [← (e.isImage_preimage_prod s).frontier.preimage_eq, frontier_prod_univ_eq,
    (e.isImage_preimage_prod _).preimage_eq, e.source_eq, preimage_inter]
#align trivialization.frontier_preimage Trivialization.frontier_preimage

/-- Given two bundle trivializations `e`, `e'` of `proj : Z → B` and a set `s : Set B` such that
the base sets of `e` and `e'` intersect `frontier s` on the same set and `e p = e' p` whenever
`proj p ∈ e.baseSet ∩ frontier s`, `e.piecewise e' s Hs Heq` is the bundle trivialization over
`Set.ite s e.baseSet e'.baseSet` that is equal to `e` on `proj ⁻¹ s` and is equal to `e'`
otherwise. -/
noncomputable def piecewise (e e' : Trivialization F proj) (s : Set B)
    (Hs : e.baseSet ∩ frontier s = e'.baseSet ∩ frontier s)
    (Heq : EqOn e e' <| proj ⁻¹' (e.baseSet ∩ frontier s)) : Trivialization F proj where
  toLocalHomeomorph :=
    e.toLocalHomeomorph.piecewise e'.toLocalHomeomorph (proj ⁻¹' s) (s ×ˢ univ)
      (e.isImage_preimage_prod s) (e'.isImage_preimage_prod s)
      (by rw [e.frontier_preimage, e'.frontier_preimage, Hs]) (by rwa [e.frontier_preimage])
  baseSet := s.ite e.baseSet e'.baseSet
  open_baseSet := e.open_baseSet.ite e'.open_baseSet Hs
  source_eq := by simp [source_eq]
  target_eq := by simp [target_eq, prod_univ]
  proj_toFun p := by
    rintro (⟨he, hs⟩ | ⟨he, hs⟩)
    -- porting note: was `<;> simp [*]`
    · simp [piecewise_eq_of_mem _ _ _ hs, *]
    · simp [piecewise_eq_of_not_mem _ _ _ hs, *]
#align trivialization.piecewise Trivialization.piecewise

/-- Given two bundle trivializations `e`, `e'` of a topological fiber bundle `proj : Z → B`
over a linearly ordered base `B` and a point `a ∈ e.baseSet ∩ e'.baseSet` such that
`e` equals `e'` on `proj ⁻¹' {a}`, `e.piecewise_le_of_eq e' a He He' Heq` is the bundle
trivialization over `Set.ite (Iic a) e.baseSet e'.baseSet` that is equal to `e` on points `p`
such that `proj p ≤ a` and is equal to `e'` otherwise. -/
noncomputable def piecewiseLeOfEq [LinearOrder B] [OrderTopology B] (e e' : Trivialization F proj)
    (a : B) (He : a ∈ e.baseSet) (He' : a ∈ e'.baseSet) (Heq : ∀ p, proj p = a → e p = e' p) :
    Trivialization F proj :=
  e.piecewise e' (Iic a)
    (Set.ext fun x => and_congr_left_iff.2 fun hx => by
      obtain rfl : x = a := mem_singleton_iff.1 (frontier_Iic_subset _ hx)
      simp [He, He'])
    fun p hp => Heq p <| frontier_Iic_subset _ hp.2
#align trivialization.piecewise_le_of_eq Trivialization.piecewiseLeOfEq

/-- Given two bundle trivializations `e`, `e'` of a topological fiber bundle `proj : Z → B` over a
linearly ordered base `B` and a point `a ∈ e.baseSet ∩ e'.baseSet`, `e.piecewise_le e' a He He'`
is the bundle trivialization over `Set.ite (Iic a) e.baseSet e'.baseSet` that is equal to `e` on
points `p` such that `proj p ≤ a` and is equal to `((e' p).1, h (e' p).2)` otherwise, where
`h = `e'.coord_change_homeomorph e _ _` is the homeomorphism of the fiber such that
`h (e' p).2 = (e p).2` whenever `e p = a`. -/
noncomputable def piecewiseLe [LinearOrder B] [OrderTopology B] (e e' : Trivialization F proj)
    (a : B) (He : a ∈ e.baseSet) (He' : a ∈ e'.baseSet) : Trivialization F proj :=
  e.piecewiseLeOfEq (e'.transFiberHomeomorph (e'.coordChangeHomeomorph e He' He)) a He He' <| by
    rintro p rfl
    ext1
    · simp [e.coe_fst', e'.coe_fst', *]
    · simp [coordChange_apply_snd, *]
#align trivialization.piecewise_le Trivialization.piecewiseLe

/-- Given two bundle trivializations `e`, `e'` over disjoint sets, `e.disjoint_union e' H` is the
bundle trivialization over the union of the base sets that agrees with `e` and `e'` over their
base sets. -/
noncomputable def disjointUnion (e e' : Trivialization F proj) (H : Disjoint e.baseSet e'.baseSet) :
    Trivialization F proj where
  toLocalHomeomorph :=
    e.toLocalHomeomorph.disjointUnion e'.toLocalHomeomorph
      (by
        rw [e.source_eq, e'.source_eq]
        exact H.preimage _)
      (by
        rw [e.target_eq, e'.target_eq, disjoint_iff_inf_le]
        intro x hx
        exact H.le_bot ⟨hx.1.1, hx.2.1⟩)
  baseSet := e.baseSet ∪ e'.baseSet
  open_baseSet := IsOpen.union e.open_baseSet e'.open_baseSet
  source_eq := congr_arg₂ (· ∪ ·) e.source_eq e'.source_eq
  target_eq := (congr_arg₂ (· ∪ ·) e.target_eq e'.target_eq).trans union_prod.symm
  proj_toFun := by
    rintro p (hp | hp')
    · show (e.source.piecewise e e' p).1 = proj p
      rw [piecewise_eq_of_mem, e.coe_fst] <;> exact hp
    · show (e.source.piecewise e e' p).1 = proj p
      rw [piecewise_eq_of_not_mem, e'.coe_fst hp']
      simp only [source_eq] at hp' ⊢
      exact fun h => H.le_bot ⟨h, hp'⟩
#align trivialization.disjoint_union Trivialization.disjointUnion

end Piecewise

end Trivialization


/- Inaccessible names -/

example : ∀ x y : Nat, x = y → y = x := by
  intros
  apply Eq.symm
  assumption

example : ∀ x y : Nat, x = y → y = x := by
  intros
  apply Eq.symm
  rename_i a b hab
  exact hab