import tactic
import order.zorn

namespace heyting

universe u

class has_imp (X : Type u) := (imp : X -> X -> X)

infix ` => `:50 := has_imp.imp

class heyting (X : Type u) extends has_inter X, has_union X, has_imp X,
has_bot X, has_top X :=
(max_assoc : ∀a b c : X, a ∪ (b ∪ c) = (a ∪ b) ∪ c)
(min_assoc : ∀a b c : X, a ∩ (b ∩ c) = (a ∩ b) ∩ c)
(max_comm : ∀a b : X, a ∪ b = b ∪ a)
(min_comm : ∀a b : X, a ∩ b = b ∩ a)
(min_dist : ∀a b c : X, a ∩ (b ∪ c) = (a ∩ b) ∪ (a ∩ c))
(max_dist : ∀a b c : X, a ∪ (b ∩ c) = (a ∪ b) ∩ (a ∪ c))
(max_id : ∀a : X, a ∪ ⊥ = a)
(min_id : ∀a : X, a ∩ ⊤ = a)
(max_self : ∀a : X, a ∪ a = a)
(imp_ge : ∀a b c : X, (a ∩ c) ∪ b = b ↔ c ∪ (a => b) = (a => b))

variables {X : Type u} [heyting X] (a b c x y : X)

@[simp]lemma max_bot : a ∪ ⊥ = a := heyting.max_id a
@[simp]lemma bot_max : ⊥ ∪ a = a := by {rw _inst_1.max_comm, exact max_bot a}
@[simp]lemma min_top : a ∩ ⊤ = a := heyting.min_id a
@[simp]lemma top_min : ⊤ ∩ a = a := by {rw _inst_1.min_comm, exact min_top a}
@[simp]lemma max_self : a ∪ a = a := heyting.max_self a

instance heyting_le : has_le X := ⟨λa b, a ∪ b = b⟩

lemma le_iff : a ∪ b = b ↔ a ≤ b := iff.refl (a ∪ b = b)

instance heyting_partial : partial_order X :=
{
  le := has_le.le,
  le_refl := begin
    unfold has_le.le,
    simp,
  end,
  le_trans := begin
    unfold has_le.le,
    intros a b c hab hbc,
    rw [←hbc, _inst_1.max_assoc, hab],
  end,
  le_antisymm := begin
    unfold has_le.le,
    unfold preorder.le,
    unfold has_le.le,
    intros a b hab hba,
    rw [←hab], rw _inst_1.max_comm at hba,
    exact eq.symm hba,
  end
}

@[simp]lemma min_bot : a ∩ ⊥ = ⊥ :=
begin
	have := _inst_1.imp_ge,
	specialize this a ⊥ ⊥,
	simp at this,
	exact this,
end

@[simp]lemma bot_min : ⊥ ∩ a = ⊥ := by {rw _inst_1.min_comm, simp}

@[simp]lemma min_self : a ∩ a = a :=
begin
	suffices : ((a ∪ ⊥) ∩ a) ∪ ((a ∪ ⊥) ∩ ⊥) = a ∪ (⊥ ∩ ⊥),
	{ simp at *, exact this },
	rw [←_inst_1.min_dist, ←_inst_1.max_dist],
end

lemma le_max_self_left : a ≤ a ∪ b :=
begin
  rw← le_iff,
  rw _inst_1.max_assoc,
  simp,
end

lemma le_max_self_right : b ≤ a ∪ b :=
begin
  rw← le_iff,
  rw _inst_1.max_comm,
  rw← _inst_1.max_assoc,
  simp,
end

lemma min_le_self_left : a ∩ b ≤ a :=
begin
	rw ← le_iff,
	suffices : a ∪ ((a ∩ ⊥) ∪ (a ∩ b)) = a ∪ (⊥ ∩ b),
	{ simp at this,rwa _inst_1.max_comm at this },
	suffices : ((a ∪ ⊥) ∩ a) ∪ ((a ∪ ⊥) ∩ b) = a ∪ (⊥ ∩ b),
	{simp at *, exact this},
	rw← _inst_1.min_dist,
	rw _inst_1.max_dist,
end

lemma min_le_self_right : a ∩ b ≤ b :=
begin
	rw _inst_1.min_comm,
	exact min_le_self_left b a,
end

lemma le_min_of_le_left {a c} : a ≤ c → a ∩ b ≤ c := λ h, le_trans (min_le_self_left a b) h
lemma le_min_of_le_right {a c} : a ≤ c → b ∩ a ≤ c := λ h, le_trans (min_le_self_right b a) h

@[simp]lemma max_le_iff : a ∪ b ≤ c ↔ a ≤ c ∧ b ≤ c :=
begin
	--rw [←le_iff,←le_iff,←le_iff],
	split,
	{
		intro h,
		have h1 := le_max_self_left a b,
		have h2 := le_max_self_right a b,
		exact ⟨le_trans h1 h, le_trans h2 h⟩,
	},
	{
    intro h,
    cases h with h1 h2,
    rw← le_iff at *,
		rw← _inst_1.max_assoc,
		rwa h2,
	},
end

@[simp]lemma le_min_iff : a ≤ b ∩ c ↔ a ≤ b ∧ a ≤ c :=
begin
  split,
  {
    intro h,
		have h1 := min_le_self_left b c,
		have h2 := min_le_self_right b c,
		exact ⟨le_trans h h1, le_trans h h2⟩,
  },
  {
    intro h,
    cases h with h1 h2,
    rw← le_iff at *,
    rw _inst_1.max_dist,
    rw h1, rw h2,
  }
end

@[simp]lemma max_top : a ∪ ⊤ = ⊤ :=
begin
	have := min_le_self_left ⊤ a,
	rw← le_iff at this,
	simp at this,
	exact this,
end

@[simp]lemma top_max : ⊤ ∪ a = ⊤ :=
begin
	rw _inst_1.max_comm,
	exact max_top a,
end

@[simp]lemma le_bot_iff : a ≤ ⊥ ↔ a = ⊥ :=
begin
	rw← le_iff,
	simp,
end

def upper_bound (A : set X) (x : X) := ∀{a}, a ∈ A → a ≤ x
def lower_bound (A : set X) (x : X) := ∀{a}, a ∈ A → x ≤ a
def supremum (A : set X) (x : X) := upper_bound A x ∧ ∀{s}, upper_bound A s → x ≤ s
def infimum (A : set X) (x : X) := lower_bound A x ∧ ∀{s}, lower_bound A s → s ≤ x

lemma infimum_empty : infimum (∅ : set X) ⊤ :=
begin
	split,
	{ intros x hx, exfalso, exact set.not_mem_empty x hx },
	{
		intros s' hs',
		rw← heyting.le_iff,
		simp,
	}
end

lemma infimum_insert {B : set X} {a t : X} (ht : infimum B t) : infimum (insert a B) (t ∩ a) :=
begin
	split,
	{
	intros x hx,
	simp at *,
	cases hx,
	{
		rw hx,
		exact min_le_self_right t a,
	},
	{
		cases ht with ht1 ht2,
		have h1 := ht1 hx,
		exact le_min_of_le_left a (ht1 hx),
	},
	},
	{
	intros s' hs',
	simp,
	have : lower_bound B s',
	{
		intros y hy,
		exact hs' (set.mem_insert_of_mem a hy),
	},
		exact ⟨ht.2 (by assumption), hs' (set.mem_insert a B)⟩,
	}
end

lemma has_infimum_of_finite {A : set X} (A_fin : set.finite A) : ∃ s, infimum A s :=
begin
	refine set.finite.induction_on A_fin ⟨⊤, infimum_empty⟩ _,
	{
		intros a B has B_fin ih,
		rcases ih with ⟨t, ht⟩,
		use t ∩ a,
		exact infimum_insert ht,
	}
end

instance min_comm_inst :
@is_commutative X (heyting.to_has_inter).inter := ⟨λ a b, heyting.min_comm a b⟩

instance min_assoc_inst :
@is_associative X (heyting.to_has_inter).inter := ⟨λ a b c, eq.symm (heyting.min_assoc a b c)⟩

noncomputable def infimum_of_finite (A : set X) (A_fin : set.finite A) :=
 classical.some (has_infimum_of_finite A_fin)

def infimum_of_finite_spec (A : set X) (A_fin : set.finite A) :=
 classical.some_spec (has_infimum_of_finite A_fin)

lemma infimum_iff {A : set X} (A_fin : set.finite A) (s : X) :
infimum A s ↔ s = infimum_of_finite A A_fin :=
begin
	split,
	{
		intro h,
		have h_inf : infimum A (infimum_of_finite A A_fin) := infimum_of_finite_spec A A_fin,
		exact ge_antisymm (h.2 h_inf.1) (h_inf.2 h.1),
	},
	{
		intro h,
		rw h,
		exact infimum_of_finite_spec A A_fin,
	}
end

variables {H : Type u} [heyting H]
structure filter (F : set H) : Prop :=
(mem_inter : ∀ {a b}, a ∈ F → b ∈ F → a ∩ b ∈ F)
(mem_ge : ∀ {a b}, a ∈ F → a ≤ b → b ∈ F)

structure proper_filter (F : set H) extends filter F : Prop :=
(ne_univ : F ≠ set.univ)

lemma proper_filter_iff {F : set H} (F_filter : filter F) : proper_filter F ↔ ∃ a, a ∉ F :=
⟨λh, (set.ne_univ_iff_exists_not_mem F).mp (h.ne_univ), λ h,
	{
		mem_inter := F_filter.mem_inter,
		mem_ge := F_filter.mem_ge,
		ne_univ := (set.ne_univ_iff_exists_not_mem F).mpr h
	}
⟩

lemma proper_filter_iff_bot_not_in {F : set H} (F_filter : filter F) :
proper_filter F ↔ ⊥ ∉ F :=
begin
	rw proper_filter_iff F_filter,
	split,
	{
		intros h hbot,
		cases h with a ha, apply ha, clear ha,
		exact F_filter.mem_ge hbot (by {rw← le_iff, simp}),
	},
	{ exact λ h, ⟨⊥, h⟩ },
end

structure prime_filter (F : set H) extends filter F : Prop :=
(mem_union : ∀ {a b}, a ∪ b ∈ F → a ∈ F ∨ b ∈ F)

lemma smallest_filter_insert {F : set H} (F_filter : filter F)
(F_nonempty : F.nonempty) (b : H)  :
let F' := {a : H | ∃c : H, c ∈ F ∧ b ∩ c ≤ a} in
filter F' ∧ F ∪ {b} ⊆ F' ∧ ∀ {G : set H}, filter G → F ∪ {b} ⊆ G → F' ⊆ G :=
begin
	split, fconstructor,
	{
		intros x y hx hy,
		simp at *,
		rcases hx with ⟨c, hc, hcx⟩,
		rcases hy with ⟨d, hd, hdy⟩,
		use [c ∩ d, F_filter.mem_inter hc hd],
		split,
		{ rw heyting.min_assoc, exact le_min_of_le_left d hcx },
		{
			rw [heyting.min_assoc, heyting.min_comm b c,
			 ←heyting.min_assoc, heyting.min_comm],
			 exact le_min_of_le_left c hdy,
		},
	},
	{
		intros x y hx hxy,
		simp at *,
		rcases hx with ⟨c, hc, hcx⟩,
		use [c, hc, le_trans hcx hxy],
	}, split,
	{
		intros x hx,
		simp at *,
		cases hx,
		{
			use ⊤, split,
			{
				cases set.nonempty_def.mp F_nonempty with f hf,
				apply F_filter.mem_ge hf,
				rw← le_iff,
				simp,
			},
			{ rw hx, simp }
		},
		{
			use [x, hx],
			exact min_le_self_right b x,
		},
	},
	{
		intros G G_filter G_sup x hx,
		simp at hx,
		rcases hx with ⟨c, hc, hcx⟩,
		have hcG : c ∈ G := G_sup (or.inl hc),
		have hbG : b ∈ G := by {apply G_sup, simp},
		exact G_filter.mem_ge (G_filter.mem_inter hbG hcG) hcx,
	}
end

lemma prime_of_maximal_proper_filter {F : set H} (F_filter : filter F)
(F_max : ∀ G : set H, proper_filter G → F ⊆ G → G = F) : prime_filter F :=
{
	mem_inter := F_filter.mem_inter,
	mem_ge := F_filter.mem_ge,
	mem_union :=
	begin
		intros a b hab,
		by_contra, push_neg at h,
		have F_nonempty : F.nonempty := set.nonempty_of_mem hab,
		suffices : ¬ (¬proper_filter {x : H | ∃c : H, c ∈ F ∧ a ∩ c ≤ x} ∧
		¬proper_filter {x : H | ∃c : H, c ∈ F ∧ b ∩ c ≤ x}),
		{
			cases or_iff_not_and_not.mpr this,
			{
				have temp1 := (smallest_filter_insert F_filter F_nonempty a),
				have contra := F_max _ h_1 _,
				{
					have contra2 : a ∈ {x : H | ∃ (c : H), c ∈ F ∧ a ∩ c ≤ x},
					{
						apply temp1.2.1,
						simp,
					},
					rw contra at contra2,
					exact h.1 contra2,
				},
				have := temp1.2.1,
				intros x hx,
				exact @this x (or.inl hx),
			},
			{
				have temp1 := (smallest_filter_insert F_filter F_nonempty b),
				have contra := F_max _ h_1 _,
				{
					have contra2 : b ∈ {x : H | ∃ (c : H), c ∈ F ∧ b ∩ c ≤ x},
					{
						apply temp1.2.1,
						simp,
					},
					rw contra at contra2,
					exact h.2 contra2,
				},
				have := temp1.2.1,
				intros x hx,
				exact @this x (or.inl hx),
			}
		},
		{
			intro hcontra,
			have htemp : ∀{k : H}, let F' := {x : H | ∃c : H, c ∈ F ∧ k ∩ c ≤ x} in
			  filter F' → ¬proper_filter F' → ∃f ∈ F, k ∩ f = ⊥,
			{
				intros k F' hF1 hF2,
				have := (proper_filter_iff_bot_not_in hF1).not_left.mp hF2,
				simp at this,
				rcases this with ⟨c, hc1, hc2⟩,
				exact ⟨c, hc1, hc2⟩,
			},
			have := htemp (smallest_filter_insert F_filter F_nonempty a).1 hcontra.1,
			rcases htemp (smallest_filter_insert F_filter F_nonempty a).1 hcontra.1 with ⟨f1, hf1, hfa⟩,
			rcases htemp (smallest_filter_insert F_filter F_nonempty b).1 hcontra.2 with ⟨f2, hf2, hfb⟩,

			have hcalc1 : (f1 ∩ f2) ∩ a = ⊥ :=
			by {rw [heyting.min_comm, heyting.min_assoc, hfa], simp},
			have hcalc2 : (f1 ∩ f2) ∩ b = ⊥ :=
			by {rw [←heyting.min_assoc, heyting.min_comm f2 b, hfb], simp},

			have := calc (f1 ∩ f2) ∩ (a ∪ b)
						= ((f1 ∩ f2) ∩ a) ∪ ((f1 ∩ f2) ∩ b) : heyting.min_dist (f1 ∩ f2) a b
				... = ⊥ ∪ ⊥ : by rw[hcalc1, hcalc2]
				... = ⊥ : by simp,
			have h1 : (f1 ∩ f2) ∩ (a ∪ b) ∈ F := F_filter.mem_inter (F_filter.mem_inter hf1 hf2) hab,
			rw this at h1,
			apply (proper_filter_iff_bot_not_in F_filter).not_left.mpr h1,
			rw proper_filter_iff F_filter,
			exact ⟨a, h.1⟩,
		},
	end
}

lemma exists_big_prime_filter {F : set H} (F_filter : filter F)
(F_nonempty : F.nonempty) {a : H} (haF : a ∉ F) :
∃ (M : set H), prime_filter M ∧ F ⊆ M ∧ a ∉ M :=
begin
	set A := {G : set H | filter G ∧ a ∉ G} with A_def,
	have hFA : F ∈ A := by { rw A_def, simp, exact ⟨F_filter, haF⟩ },
	rcases zorn_subset_nonempty A _ F hFA with ⟨M, hMA, F_sub, M_max⟩,
	{
		use M,
		have M_filter : filter M := by { rw A_def at hMA, simp at hMA, exact hMA.1 },
		have hMa : a ∉ M := by { rw A_def at hMA, simp at hMA, exact hMA.2 },
		have M_nonempty : M.nonempty := set.nonempty.mono F_sub F_nonempty,
		refine ⟨_, F_sub, hMa⟩,
		fconstructor, { exact M_filter },
		intros b c hab,
		by_contra hcontra,
		push_neg at hcontra, cases hcontra with hbM hcM,
		suffices : a ∉ {x : H | ∃d : H, d ∈ M ∧ b ∩ d ≤ x} ∨ a ∉ {x : H | ∃d : H, d ∈ M ∧ c ∩ d ≤ x},
		{
			cases this,
			{
				have hGA : {x : H | ∃ (d : H), d ∈ M ∧ b ∩ d ≤ x} ∈ A,
				{
					rw A_def, rw set.mem_set_of,
					exact ⟨(smallest_filter_insert M_filter M_nonempty b).1, this⟩,
				},

				have temp := (smallest_filter_insert M_filter M_nonempty b).2.1,
				have M_ss : M ⊆ {a : H | ∃ (c : H), c ∈ M ∧ b ∩ c ≤ a},
				{
					intros x hx,
					exact (smallest_filter_insert M_filter M_nonempty b).2.1 (or.inl hx),
				},

				rw M_max _ hGA M_ss at *,
				exact hbM (@temp b (or.inr (set.mem_singleton b))),
			},
			{
				have hGA : {x : H | ∃ (d : H), d ∈ M ∧ c ∩ d ≤ x} ∈ A,
				{
					rw A_def, rw set.mem_set_of,
					exact ⟨(smallest_filter_insert M_filter M_nonempty c).1, this⟩,
				},

				have temp := (smallest_filter_insert M_filter M_nonempty c).2.1,
				have M_ss : M ⊆ {a : H | ∃ (d : H), d ∈ M ∧ c ∩ d ≤ a},
				{
					intros x hx,
					exact (smallest_filter_insert M_filter M_nonempty c).2.1 (or.inl hx),
				},

				rw M_max _ hGA M_ss at *,
				exact hcM (@temp c (or.inr (set.mem_singleton c))),
			},
		},

		rw or_iff_not_and_not,
		intro h, simp at h,
		rcases h with ⟨⟨m1, hm1⟩, ⟨m2, hm2⟩⟩,
		have t1 : (m1 ∩ m2) ∩ b ≤ a,
		{
			rw [heyting.min_comm m1 m2, ←heyting.min_assoc, heyting.min_comm m1 b],
			exact heyting.le_min_of_le_right m2 hm1.2,
		},
		have t2 : (m1 ∩ m2) ∩ c ≤ a,
		{
			rw [←heyting.min_assoc, heyting.min_comm m2 c],
			exact heyting.le_min_of_le_right m1 hm2.2,
		},
		have t3 := calc (m1 ∩ m2) ∩ (b ∪ c) =
			((m1 ∩ m2) ∩ b) ∪ ((m1 ∩ m2) ∩ c) : heyting.min_dist (m1 ∩ m2) b c,
		have t4 : m1 ∩ m2 ∩ b ∪ m1 ∩ m2 ∩ c ≤ a,
		{
			rw heyting.max_le_iff,
			exact ⟨t1, t2⟩,
		},
		have t5 : m1 ∩ m2 ∩ b ∪ m1 ∩ m2 ∩ c ∈ M,
		{
			rw← t3,
			exact M_filter.mem_inter (M_filter.mem_inter hm1.1 hm2.1) hab,
		},
		exact hMa (M_filter.mem_ge t5 t4),
	},
	intros chain chain_ss chain_chain chain_nonempty,

	have filter_of_mem_chain : ∀ {F}, F ∈ chain → filter F,
	{
		intros F hF,
		have := chain_ss hF, rw A_def at this, simp at this,
		exact this.1,
	},

	use ⋃₀ chain,
	split,
	{
		rw A_def, simp, split,
		{
			fconstructor,
			{
				intros a b ha hb,
				rcases ha with ⟨F, F_chain, hFa⟩,
				rcases hb with ⟨G, G_chain, hGb⟩,
				cases is_chain.total chain_chain F_chain G_chain,
				{ exact ⟨G, G_chain, (filter_of_mem_chain G_chain).mem_inter (h hFa) hGb⟩ },
				{ exact ⟨F, F_chain, (filter_of_mem_chain F_chain).mem_inter hFa (h hGb)⟩ },
			},
			{
				intros a b ha hab,
				rcases ha with ⟨F, F_chain, hFa⟩,
				exact ⟨F, F_chain, (filter_of_mem_chain F_chain).mem_ge hFa hab⟩,
			},
		},
		{
			intros F F_chain,
			have := chain_ss F_chain, rw A_def at this, simp at this,
			exact this.2,
		},
	},
	{
		intros F F_chain a ha,
		exact ⟨F, F_chain, ha⟩
	}
end

end heyting


import .nonempty_list

open util.data.nonempty_list

namespace util.data.bin_tree'

universes u v

inductive bin_tree' (α : Type u)
| leaf : α → bin_tree'
| branch : bin_tree' → bin_tree' → bin_tree'

namespace bin_tree'

variables {α : Type u} {β : Type v}

def map (f : α → β) : bin_tree' α → bin_tree' β
| (leaf x) := leaf (f x)
| (branch l r) := branch (map l) (map r)

def to_list : bin_tree' α → nonempty_list α
| (leaf x) := nonempty_list.singleton x
| (branch l r) := to_list l ++  to_list r

def size : bin_tree' α → ℕ
| (leaf _) := 1
| (branch l r) := size l + size r

def from_list_lopsided : nonempty_list α → bin_tree' α
| (nonempty_list.singleton x) := leaf x
| (nonempty_list.cons x xs)   := branch (leaf x) (from_list_lopsided xs)

lemma from_list_lopsided_to_list : Π (l : nonempty_list α), to_list (from_list_lopsided l) = l
| (nonempty_list.singleton x) := by reflexivity
| (nonempty_list.cons x xs)   :=
  begin
    unfold from_list_lopsided,
    unfold to_list,
    rewrite from_list_lopsided_to_list xs,
    reflexivity
  end

@[reducible] def lopsided (t : bin_tree' α) : bin_tree' α := from_list_lopsided t.to_list

lemma lopsided_idempotent (t : bin_tree' α) : t.lopsided.lopsided = t.lopsided :=
  begin
    unfold lopsided,
    rewrite from_list_lopsided_to_list (to_list t)
  end

end bin_tree'

end util.data.bin_tree'


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

Author: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.metric_space.basic
import Mathlib.PostPort

universes u l

namespace Mathlib

/-!
# Premetric spaces

Metric spaces are often defined as quotients of spaces endowed with a "distance"
function satisfying the triangular inequality, but for which `dist x y = 0` does
not imply `x = y`. We call such a space a premetric space.
`dist x y = 0` defines an equivalence relation, and the quotient
is canonically a metric space.
-/

class premetric_space (α : Type u)
extends has_dist α
where
  dist_self : ∀ (x : α), dist x x = 0
  dist_comm : ∀ (x y : α), dist x y = dist y x
  dist_triangle : ∀ (x y z : α), dist x z ≤ dist x y + dist y z

namespace premetric

protected theorem dist_nonneg {α : Type u} [premetric_space α] {x : α} {y : α} : 0 ≤ dist x y := sorry

/-- The canonical equivalence relation on a premetric space. -/
def dist_setoid (α : Type u) [premetric_space α] : setoid α :=
  setoid.mk (fun (x y : α) => dist x y = 0) sorry

/-- The canonical quotient of a premetric space, identifying points at distance `0`. -/
def metric_quot (α : Type u) [premetric_space α] :=
  quotient (dist_setoid α)

protected instance has_dist_metric_quot {α : Type u} [premetric_space α] : has_dist (metric_quot α) :=
  has_dist.mk (quotient.lift₂ (fun (p q : α) => dist p q) sorry)

theorem metric_quot_dist_eq {α : Type u} [premetric_space α] (p : α) (q : α) : dist (quotient.mk p) (quotient.mk q) = dist p q :=
  rfl

protected instance metric_space_quot {α : Type u} [premetric_space α] : metric_space (metric_quot α) :=
  metric_space.mk sorry sorry sorry sorry
    (fun (x y : metric_quot α) =>
      ennreal.of_real (quotient.lift₂ (fun (p q : α) => dist p q) has_dist_metric_quot._proof_1 x y))
    (uniform_space_of_dist (quotient.lift₂ (fun (p q : α) => dist p q) has_dist_metric_quot._proof_1) sorry sorry sorry)


import .dual

/-!
# Projections, Loopifications and Pseudominors

For a matroid `M` on Type `α` and sets `C,D` in `M`, this file defines matroids `M ⟋ C` and
`M ⟍ D` which are obtained from `M` by contracting `C` and deleting `D` respectively, but then
adding back in `C` (or `D`) as a set containing only loops. We call these operations 'projection'
and 'loopification'.

The advantage of this over taking minors is that we stay in the Type `matroid α` rather than
changing the ground set and having to deal with the type theory that results. In practice it seems
that many proofs that involve manipulating minors can be phrased only in terms of these modified
minor-like-objects, which we call pseudominors. Kung's theorem and the matroid intersection theorem
are currently both proved in this way.
-/

open set
open matroid

universes u v w

namespace matroid

section project
variables {α : Type*} [fintype α] {e f : α} {M : matroid α} {X Y D C F I R: set α}

/-- contract C and replace it with a set of loops to get a matroid on the same ground set.
Often more convenient than just contracting C  -/
def project (M : matroid α) (C : set α) : matroid α :=
{ r := λ X, M.r (X ∪ C) - M.r C,
  R0 := λ X, by {simp only, linarith [M.rank_mono (subset_union_right X C)]},
  R1 := λ X, by {simp only, linarith [M.R0 (X ∩ C), M.R3 X C, M.R1 X]},
  R2 := λ X Y hXY, by {simp only, linarith [M.rank_mono (union_subset_union_left C hXY)]},
  R3 := λ X Y, by {simp_rw [union_comm _ C], linarith [submod_three_sets M C X Y]} }

infix ` ⟋ ` :75 :=  matroid.project

/-- project all elements of M outside R -/
def project_to (M : matroid α) (R : set α) : matroid α := M ⟋ (Rᶜ)

@[simp] lemma project_r (M : matroid α) (C X : set α) : (M ⟋ C).r X = M.r (X ∪ C) - M.r C := rfl

@[simp] lemma project_empty (M : matroid α) : M ⟋ ∅ = M :=
by { ext X, rw [project_r, union_empty, rank_empty, sub_zero], }

lemma project_rank (M : matroid α) (C : set α) : (M ⟋ C).r univ = M.r univ - M.r C :=
by rw [project_r, univ_union]

lemma project_is_weak_image (M : matroid α) (C : set α) : M ⟋ C ≤ M :=
λ X, by {rw project_r, linarith [M.rank_subadditive X C]}

lemma project_project (M : matroid α) (C C' : set α) : M ⟋ C ⟋ C' = M ⟋ (C ∪ C') :=
by {rw union_comm, ext X, simp only [project_r, ←union_assoc], linarith, }

lemma project_is_quotient (M : matroid α) (C : set α) : M ⟋ C ≼ M :=
begin
  intros X Y hXY, simp_rw project_r,
  have hY := (union_diff_cancel hXY).symm,
  have hle : (M ⟋ Y) ≤ (M ⟋ X) := by {rw [hY, ←project_project],apply project_is_weak_image },
  specialize hle C,
  simp only [project_r, union_comm C] at hle,
  linarith,
end

lemma project_makes_loops (M : matroid α) (C : set α) : loops M ∪ C ⊆ loops (M ⟋ C)  :=
by rw [←rank_zero_iff_subset_loops, project_r, union_assoc, union_self,
        union_comm, rank_eq_rank_union_rank_zero C (rank_loops M), sub_self]

lemma loop_of_project (M : matroid α )(he : e ∈ C) : (M ⟋ C).is_loop e :=
loop_iff_mem_loops.mpr (mem_of_mem_of_subset (by {exact or.inr he}) (project_makes_loops M C))

lemma projected_set_rank_zero (M : matroid α) (C : set α) : (M ⟋ C).r C = 0 :=
by rw [project_r, union_self, sub_self]

lemma projected_set_union_rank_zero (M : matroid α) (C : set α){X : set α} (hX : M.r X = 0) :
  (M ⟋ C).r (X ∪ C) = 0 :=
by rwa [project_r, union_assoc, union_self, union_comm, rank_eq_rank_union_rank_zero, sub_self]

lemma project_rank_zero_eq (h : M.r C = 0) : M ⟋ C = M :=
by {ext X, rw [project_r, rank_eq_rank_union_rank_zero X h, h, sub_zero]}

lemma project_rank_zero_of_rank_zero (C : set α) (hX : M.r X = 0) : (M ⟋ C).r X = 0 :=
by {apply rank_eq_zero_of_le_zero, rw ←hX, apply project_is_weak_image}

lemma project_nonloops_eq (C : set α): (M ⟋ C).nonloops = M.nonloops \ M.cl C :=
begin
  ext x,
  rw [mem_diff, ← nonloop_iff_mem_nonloops, ← nonloop_iff_mem_nonloops, nonloop_iff_r, project_r,
    nonmem_cl_iff_r, union_comm],
  refine ⟨λ h, ⟨by_contra (λ hn, _), by linarith⟩, λ h, by linarith [h.2]⟩,
  rw [← loop_iff_not_nonloop, loop_iff_r] at hn,
  rw [rank_eq_rank_union_rank_zero _ hn, sub_self] at h,
  exact zero_ne_one h,
end

lemma indep_union_project_set_of_project_indep
(hX : is_indep (M ⟋ C) X) (hC : is_indep M C) :
is_indep M (X ∪ C) :=
by {simp_rw [indep_iff_r, project_r] at *,
    linarith [M.R1 (X ∪ C), size_modular X C, size_nonneg (X ∩ C)]}

lemma project_nonloop_fewer_nonloops (he : M.is_nonloop e): (M ⟋ {e}).nonloops ⊂ M.nonloops :=
begin
  simp_rw [nonloops_eq_compl_loops],
  apply ssubset_compl_commpl.mpr,
  rw ssubset_iff_subset_ne,
  refine ⟨subset.trans (subset_union_left _ _) (project_makes_loops M {e}), λ hn, _⟩,
  rw ext_iff at hn,
  have h' := (not_iff_not.mpr (hn e)).mp (nonloop_iff_not_mem_loops.mp he),
  exact h' (loop_iff_mem_loops.mp (loop_of_project M (mem_singleton e))),
end

lemma indep_of_project_indep (h: is_indep (M ⟋ C) X) : is_indep M X :=
indep_of_weak_image_indep (project_is_weak_image M C) h

lemma project_as_project_to (M : matroid α) (C : set α) : (M ⟋ C) = M.project_to Cᶜ :=
by rw [project_to, compl_compl]

lemma project_to_r (M : matroid α) (R X : set α) : (M.project_to R).r X = M.r (X ∪ Rᶜ) - M.r Rᶜ :=
by rw [project_to, project_r]

lemma indep_project_iff : (M ⟋ C).is_indep X ↔ M.is_indep X ∧ M.r (X ∪ C) = M.r X + M.r C :=
begin
  rw [indep_iff_r,project_r, indep_iff_r],
  refine ⟨λ h, ⟨_,_⟩, λ h, _⟩,
  { refine le_antisymm (M.rank_le_size _) _, rw ←h, linarith [rank_subadditive M X C]},
  { refine le_antisymm (M.rank_subadditive X C) _,
    rw (by linarith : M.r (X ∪ C) = M.r C + size X),
    linarith [M.rank_le_size X], },
  rw [←h.1, h.2], simp,
end

lemma flat_of_flat_project (h : (M ⟋ C).is_flat F) : M.is_flat F :=
flat_of_quotient_flat (project_is_quotient _ _) h

lemma flat_project_iff : (M ⟋ C).is_flat F ↔ M.is_flat F ∧ C ⊆ F :=
begin
  refine ⟨λ h, ⟨flat_of_flat_project h,_⟩, λ h, _⟩,
  { refine subset.trans _ (loops_subset_flat _ h),
    rw ←rank_zero_iff_subset_loops,
    apply projected_set_rank_zero, },
  simp_rw [flat_iff_r, project_r, union_comm F C, subset_iff_union_eq_left.mp h.2],
  intros Y hFY,
  suffices : M.r F < M.r Y, linarith [rank_mono_union_left M Y C],
  exact h.1 _ hFY,
end

lemma point_project_nonloop_iff { e : α } (he : M.is_nonloop e){X : set α}:
  (M ⟋ {e}).is_point X ↔ M.is_line X ∧ e ∈ X :=
begin
  dsimp only  [is_point, is_line, is_rank_k_flat, project_r],
  rw [flat_project_iff, rank_nonloop he, singleton_subset_iff],
  split,
  { rintros ⟨⟨hF,he⟩,hX⟩,
    rw [union_mem_singleton he] at hX,
    exact ⟨⟨hF, eq_add_of_sub_eq hX⟩, he⟩ },
  rintros ⟨⟨hF, hX⟩, he⟩,
  rw [union_mem_singleton he, hX],
  tidy,
end

end project

section loopify

variables {α : Type*} [fintype α] {e f : α} {M : matroid α} {X Y D C I R S : set α}

/-- Replace D with loops to get a matroid on the same ground set. Often more
convenient than deleting D -/
def loopify (M : matroid α) (D : set α) : matroid α :=
{ r := λ X, M.r (X \ D),
  R0 := λ X, M.R0 _,
  R1 := λ X, by linarith [M.R1 (X \ D), size_diff_le_size X D],
  R2 := λ X Y hXY, M.rank_mono (diff_subset_diff_left hXY),
  R3 := λ X Y, by {simp only [diff_eq], rw [inter_distrib_right, inter_distrib_inter_left],
                    linarith [M.rank_submod (X ∩ Dᶜ) (Y ∩ Dᶜ)], }}

infix ` ⟍ ` :75 :=  matroid.loopify

/-- loopify all elements of M outside R -/
def loopify_to (M : matroid α) (R : set α) : matroid α := M ⟍ (Rᶜ)

infix ` ‖ `  :75 :=  matroid.loopify_to

@[simp] lemma loopify_r (M : matroid α) (D X : set α) : (M ⟍ D).r X = M.r (X \ D) := rfl
@[simp] lemma loopify_empty (M : matroid α) : M ⟍ ∅ = M :=
by { ext X, rw [loopify_r, diff_empty]}

lemma loopify_is_weak_image (M : matroid α) (D : set α) :
  M ⟍ D ≤ M :=
λ X, rank_mono_diff _ _ _

lemma loopify_loopify (M : matroid α) (D D' : set α) :
   M ⟍ D ⟍ D' = M ⟍ (D ∪ D') :=
by {ext X, simp [diff_eq, ←inter_assoc, inter_right_comm]}

lemma loopify_loops_eq (M : matroid α) (D : set α) :
  M.loops ∪ D = (M ⟍ D).loops :=
begin
  refine subset.antisymm _ (λ x hx, _),
  { rw [←rank_zero_iff_subset_loops, loopify_r, rank_zero_iff_subset_loops], tidy,},

  rw [← loop_iff_mem_loops, loop_iff_r, loopify_r] at hx,
  by_contra hn,
  rw [mem_union_eq, not_or_distrib, ← nonloop_iff_not_mem_loops, nonloop_iff_r] at hn,
  have hxD : {x} \ D = {x},
  { ext,
    simp only [mem_singleton_iff, and_iff_left_iff_imp, mem_diff],
    rintro rfl,
    exact hn.2},
  rw [hxD,hn.1] at hx,
  exact one_ne_zero hx,
end

lemma loops_subset_loopify_loops_eq (M : matroid α)(D : set α):
  M.loops ⊆ (M ⟍ D).loops :=
by {rw ← loopify_loops_eq, apply subset_union_left}

lemma loop_of_loopify (M : matroid α) (he : e ∈ D) :
  (M ⟍ D).is_loop e :=
by {rw [loop_iff_mem_loops, ← loopify_loops_eq], exact or.inr he}

lemma loopify_nonloops_eq (M : matroid α) (D : set α):
  (M ⟍ D).nonloops = M.nonloops \ D :=
by rw [nonloops_eq_compl_loops, nonloops_eq_compl_loops, ← loopify_loops_eq, compl_union, diff_eq]

lemma loopify_nonloop_fewer_nonloops (he : M.is_nonloop e):
  (M ⟍ {e}).nonloops ⊂ M.nonloops :=
begin
  simp_rw [nonloops_eq_compl_loops],
  apply ssubset_compl_commpl.mpr,
  rw ← loopify_loops_eq,
  exact ssubset_of_add_nonmem_iff.mp (nonloop_iff_not_mem_loops.mp he),
end

lemma loopify_rank_zero_eq (h : M.r D = 0) :
  M ⟍ D = M :=
by {ext X, rw [loopify_r, rank_eq_rank_diff_rank_zero X h]}

lemma loopified_set_rank_zero (M : matroid α) (D : set α) :
  (M ⟍ D).r D = 0 :=
by rw [loopify_r, set.diff_self, rank_empty]

lemma loopified_set_union_rank_zero (M : matroid α) (D : set α){X : set α} (h: M.r X = 0) :
  (M ⟍ D).r (X ∪ D) = 0 :=
by {rw [loopify_r, union_diff_right], exact rank_zero_of_subset_rank_zero (diff_subset _ _) h}

lemma loopify_rank_zero_of_rank_zero (D : set α)(hX : M.r X = 0) :
  (M ⟍ D).r X = 0 :=
by {apply rank_eq_zero_of_le_zero, rw ←hX, apply loopify_is_weak_image}

lemma loopify_rank_of_disjoint (M : matroid α) (h : D ∩ X = ∅) :
  (M ⟍ D).r X = M.r X :=
by {rw loopify_r, congr, rwa [inter_comm, disjoint_iff_diff_eq_left] at h}

lemma indep_of_loopify_indep (hX : is_indep (M ⟍ D) X) :
  is_indep M X :=
indep_of_weak_image_indep (loopify_is_weak_image M D) hX

lemma loopify_to_r (M : matroid α) (R X : set α) :
  (M ‖ R).r X = M.r (X ∩ R) :=
by simp [diff_eq, loopify_to, loopify_r]

lemma loopify_as_loopify_to (M : matroid α) (D : set α) :
  (M ⟍ D) = (M ‖ Dᶜ) :=
by rw [loopify_to, compl_compl]

lemma loopify_to.nonloops (M : matroid α) (R : set α):
  (M ‖ R).nonloops = M.nonloops ∩ R :=
by rw [loopify_to, loopify_nonloops_eq, diff_eq, compl_compl]

lemma indep_loopify_to_iff :
  (M ‖ R).is_indep I ↔ M.is_indep I ∧ I ⊆ R :=
begin
  rw [indep_iff_r, loopify_to_r, indep_iff_r],
  refine ⟨λ h, _, λ h, by {cases h with h h', rwa subset_iff_inter_eq_left.mp h'}⟩,
  have h' : I ∩ R = I,
  { refine eq_of_eq_size_subset (inter_subset_left _ _) _,
    refine le_antisymm (size_mono_inter_left _ _) _,
    rw ←h, apply rank_le_size,},
  rw [←h, h', subset_iff_inter_eq_left],
  simpa,
end

lemma indep_loopify_iff :
  (M ⟍ D).is_indep X ↔ M.is_indep X ∧ X ∩ D = ∅ :=
by rw [loopify_as_loopify_to, indep_loopify_to_iff, subset_compl_iff_disjoint]

lemma indep_of_indep_loopify_to (hI : (M ‖ R).is_indep I) :
  M.is_indep I :=
(indep_loopify_to_iff.mp hI).1

lemma indep_loopify_to_subset_is_indep
(hSR: S ⊆ R) (hI: (M ‖ S).is_indep I ) :
  (M ‖ R).is_indep I :=
by {simp_rw [indep_loopify_to_iff] at *, exact ⟨hI.1, subset.trans hI.2 hSR⟩}

lemma loopify_to_eq_loopify_to_iff {M N : matroid α}:
  M ‖ S = N ‖ S ↔ (∀ X ⊆ S, M.r X = N.r X) :=
begin
  refine ⟨λ h, λ X hX, _, λ h, _⟩,
  { rw [← (subset_iff_inter_eq_left.mp hX), ← loopify_to_r, ← loopify_to_r, h]},
  ext Y : 2 ,
  rw [loopify_to_r, loopify_to_r, h _ (inter_subset_right _ _)],
end

end loopify

section pminor

variables {α : Type*} [fintype α] { M N : matroid α } {C D : set α}

/-- a pminor (pseudominor) of a matroid M is a matroid on the same ground set arising
from loopifications and/or projections of M  -/
def is_pminor_of (N M : matroid α) :=
  ∃ C D, N = M ⟋ C ⟍ D

lemma pr_is_pminor (M : matroid α) (C : set α):
  (M ⟋ C).is_pminor_of M :=
⟨C, ∅, by simp⟩

lemma lp_is_pminor (M : matroid α) (D : set α):
  (M ⟍ D).is_pminor_of M :=
⟨∅, D, by simp⟩

lemma pr_lp_eq_lp_pr (M : matroid α) (C D : set α) :
  M ⟋ C ⟍ D = M ⟍ (D \ C) ⟋ C :=
by {ext X, simp only [loopify_r, project_r], convert rfl; {ext, simp, tauto! }}

lemma lp_pr_eq_pr_lp (M : matroid α) (C D : set α) :
  M ⟍ D ⟋ C = M ⟋ (C \ D) ⟍ D :=
by {ext X, simp only [loopify_r, project_r], convert rfl; {ext, simp, tauto! }}

lemma rank_zero_of_lp_pr (M : matroid α) (C D : set α) :
  (M ⟍ D ⟋ C).r (C ∪ D) = 0 :=
begin
  apply rank_zero_of_union_rank_zero, apply projected_set_rank_zero,
  apply project_rank_zero_of_rank_zero, apply loopified_set_rank_zero,
end

lemma rank_zero_of_pr_lp (M : matroid α) (C D : set α) :
  (M ⟋ C ⟍ D).r (C ∪ D) = 0 :=
begin
  apply rank_zero_of_union_rank_zero,
  apply loopify_rank_zero_of_rank_zero, apply projected_set_rank_zero,
  apply loopified_set_rank_zero,
end

lemma pminor_iff_exists_lp_pr_disjoint {N M : matroid α} :
  N.is_pminor_of M ↔ ∃ C D, C ∩ D = ∅ ∧ N = M ⟍ D ⟋ C :=
begin
  split, rintros ⟨C,D,h⟩, refine ⟨C,D \ C, ⟨by simp ,_⟩ ⟩, rwa ←pr_lp_eq_lp_pr,
  rintros ⟨C,D,hCD,h⟩, refine ⟨C \ D, D, _⟩, rwa ←lp_pr_eq_pr_lp,
end

lemma pminor_iff_exists_pr_lp_disjoint {N M : matroid α} :
  N.is_pminor_of M ↔ ∃ C D, C ∩ D = ∅ ∧ N = M ⟋ C ⟍ D :=
begin
  split, swap, rintros ⟨C,D,hCD,h⟩, exact ⟨C, D, h⟩,
  rintros ⟨C,D,h⟩, refine ⟨C,D \ C, ⟨by simp ,_⟩ ⟩,
  rw [h, ←loopify_rank_zero_eq (_ : (M ⟋ C ⟍ (D \ C)).r (C ∩ D) = 0), loopify_loopify],
  { congr, ext, simp, tauto!, },
  refine rank_zero_of_subset_rank_zero _ (rank_zero_of_pr_lp _ _ _),
  intro x, simp, tauto,
end

end pminor

end matroid


/-
Copyright (c) 2019 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn, Yury Kudryashov, Sébastien Gouëzel, Chris Hughes

! This file was ported from Lean 3 source module data.fin.tuple.basic
! leanprover-community/mathlib commit 008205aa645b3f194c1da47025c5f110c8406eab
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Fin.Basic
import Mathlib.Data.Pi.Lex
import Mathlib.Data.Set.Intervals.Basic

/-!
# Operation on tuples

We interpret maps `∀ i : Fin n, α i` as `n`-tuples of elements of possibly varying type `α i`,
`(α 0, …, α (n-1))`. A particular case is `Fin n → α` of elements with all the same type.
In this case when `α i` is a constant map, then tuples are isomorphic (but not definitionally equal)
to `Vector`s.

We define the following operations:

* `Fin.tail` : the tail of an `n+1` tuple, i.e., its last `n` entries;
* `Fin.cons` : adding an element at the beginning of an `n`-tuple, to get an `n+1`-tuple;
* `Fin.init` : the beginning of an `n+1` tuple, i.e., its first `n` entries;
* `Fin.snoc` : adding an element at the end of an `n`-tuple, to get an `n+1`-tuple. The name `snoc`
  comes from `cons` (i.e., adding an element to the left of a tuple) read in reverse order.
* `Fin.insertNth` : insert an element to a tuple at a given position.
* `Fin.find p` : returns the first index `n` where `p n` is satisfied, and `none` if it is never
  satisfied.
* `Fin.append a b` : append two tuples.
* `Fin.repeat n a` : repeat a tuple `n` times.

-/

universe u v

namespace Fin

variable {m n : ℕ}

open Function

section Tuple

/-- There is exactly one tuple of size zero. -/
example (α : Fin 0 → Sort u) : Unique (∀ i : Fin 0, α i) := by infer_instance

@[simp]
theorem tuple0_le {α : ∀ _ : Fin 0, Type _} [∀ i, Preorder (α i)] (f g : ∀ i, α i) : f ≤ g :=
  finZeroElim
#align fin.tuple0_le Fin.tuple0_le

variable {α : Fin (n + 1) → Type u} (x : α 0) (q : ∀ i, α i) (p : ∀ i : Fin n, α i.succ) (i : Fin n)
  (y : α i.succ) (z : α 0)

/-- The tail of an `n+1` tuple, i.e., its last `n` entries. -/
def tail (q : ∀ i, α i) : ∀ i : Fin n, α i.succ := fun i ↦ q i.succ
#align fin.tail Fin.tail

theorem tail_def {n : ℕ} {α : Fin (n + 1) → Type _} {q : ∀ i, α i} :
    (tail fun k : Fin (n + 1) ↦ q k) = fun k : Fin n ↦ q k.succ :=
  rfl
#align fin.tail_def Fin.tail_def

/-- Adding an element at the beginning of an `n`-tuple, to get an `n+1`-tuple. -/
def cons (x : α 0) (p : ∀ i : Fin n, α i.succ) : ∀ i, α i := fun j ↦ Fin.cases x p j
#align fin.cons Fin.cons

@[simp]
theorem tail_cons : tail (cons x p) = p := by simp [tail, cons]
#align fin.tail_cons Fin.tail_cons

@[simp]
theorem cons_succ : cons x p i.succ = p i := by simp [cons]
#align fin.cons_succ Fin.cons_succ

@[simp]
theorem cons_zero : cons x p 0 = x := by simp [cons]
#align fin.cons_zero Fin.cons_zero

/-- Updating a tuple and adding an element at the beginning commute. -/
@[simp]
theorem cons_update : cons x (update p i y) = update (cons x p) i.succ y := by
  ext j
  by_cases h : j = 0
  · rw [h]
    simp [Ne.symm (succ_ne_zero i)]
  · let j' := pred j h
    have : j'.succ = j := succ_pred j h
    rw [← this, cons_succ]
    by_cases h' : j' = i
    · rw [h']
      simp
    · have : j'.succ ≠ i.succ := by rwa [Ne.def, succ_inj]
      rw [update_noteq h', update_noteq this, cons_succ]
#align fin.cons_update Fin.cons_update

/-- As a binary function, `Fin.cons` is injective. -/
theorem cons_injective2 : Function.Injective2 (@cons n α) := fun x₀ y₀ x y h ↦
  ⟨congr_fun h 0, funext fun i ↦ by simpa using congr_fun h (Fin.succ i)⟩
#align fin.cons_injective2 Fin.cons_injective2

@[simp]
theorem cons_eq_cons {x₀ y₀ : α 0} {x y : ∀ i : Fin n, α i.succ} :
    cons x₀ x = cons y₀ y ↔ x₀ = y₀ ∧ x = y :=
  cons_injective2.eq_iff
#align fin.cons_eq_cons Fin.cons_eq_cons

theorem cons_left_injective (x : ∀ i : Fin n, α i.succ) : Function.Injective fun x₀ ↦ cons x₀ x :=
  cons_injective2.left _
#align fin.cons_left_injective Fin.cons_left_injective

theorem cons_right_injective (x₀ : α 0) : Function.Injective (cons x₀) :=
  cons_injective2.right _
#align fin.cons_right_injective Fin.cons_right_injective

/-- Adding an element at the beginning of a tuple and then updating it amounts to adding it
directly. -/
theorem update_cons_zero : update (cons x p) 0 z = cons z p := by
  ext j
  by_cases h : j = 0
  · rw [h]
    simp
  · simp only [h, update_noteq, Ne.def, not_false_iff]
    let j' := pred j h
    have : j'.succ = j := succ_pred j h
    rw [← this, cons_succ, cons_succ]
#align fin.update_cons_zero Fin.update_cons_zero

/-- Concatenating the first element of a tuple with its tail gives back the original tuple -/
@[simp, nolint simpNF] -- Porting note: linter claims LHS doesn't simplify
theorem cons_self_tail : cons (q 0) (tail q) = q := by
  ext j
  by_cases h : j = 0
  · rw [h]
    simp
  · let j' := pred j h
    have : j'.succ = j := succ_pred j h
    rw [← this]
    unfold tail
    rw [cons_succ]
#align fin.cons_self_tail Fin.cons_self_tail

-- Porting note: Mathport removes `_root_`?
/-- Recurse on an `n+1`-tuple by splitting it into a single element and an `n`-tuple. -/
@[elab_as_elim]
def consCases {P : (∀ i : Fin n.succ, α i) → Sort v} (h : ∀ x₀ x, P (Fin.cons x₀ x))
    (x : ∀ i : Fin n.succ, α i) : P x :=
  _root_.cast (by rw [cons_self_tail]) <| h (x 0) (tail x)
#align fin.cons_cases Fin.consCases

@[simp]
theorem consCases_cons {P : (∀ i : Fin n.succ, α i) → Sort v} (h : ∀ x₀ x, P (Fin.cons x₀ x))
    (x₀ : α 0) (x : ∀ i : Fin n, α i.succ) : @consCases _ _ _ h (cons x₀ x) = h x₀ x := by
  rw [consCases, cast_eq]
  congr
#align fin.cons_cases_cons Fin.consCases_cons

/-- Recurse on an tuple by splitting into `Fin.elim0` and `Fin.cons`. -/
@[elab_as_elim]
def consInduction {α : Type _} {P : ∀ {n : ℕ}, (Fin n → α) → Sort v} (h0 : P Fin.elim0)
    (h : ∀ {n} (x₀) (x : Fin n → α), P x → P (Fin.cons x₀ x)) : ∀ {n : ℕ} (x : Fin n → α), P x
  | 0, x => by convert h0
  | n + 1, x => consCases (fun x₀ x ↦ h _ _ <| consInduction h0 h _) x
#align fin.cons_induction Fin.consInductionₓ -- Porting note: universes

theorem cons_injective_of_injective {α} {x₀ : α} {x : Fin n → α} (hx₀ : x₀ ∉ Set.range x)
    (hx : Function.Injective x) : Function.Injective (cons x₀ x : Fin n.succ → α) := by
  refine' Fin.cases _ _
  · refine' Fin.cases _ _
    · intro
      rfl
    · intro j h
      rw [cons_zero, cons_succ] at h
      exact hx₀.elim ⟨_, h.symm⟩
  · intro i
    refine' Fin.cases _ _
    · intro h
      rw [cons_zero, cons_succ] at h
      exact hx₀.elim ⟨_, h⟩
    · intro j h
      rw [cons_succ, cons_succ] at h
      exact congr_arg _ (hx h)
#align fin.cons_injective_of_injective Fin.cons_injective_of_injective

theorem cons_injective_iff {α} {x₀ : α} {x : Fin n → α} :
    Function.Injective (cons x₀ x : Fin n.succ → α) ↔ x₀ ∉ Set.range x ∧ Function.Injective x := by
  refine' ⟨fun h ↦ ⟨_, _⟩, fun h ↦ cons_injective_of_injective h.1 h.2⟩
  · rintro ⟨i, hi⟩
    replace h := @h i.succ 0
    simp [hi, succ_ne_zero] at h
  · simpa [Function.comp] using h.comp (Fin.succ_injective _)
#align fin.cons_injective_iff Fin.cons_injective_iff

@[simp]
theorem forall_fin_zero_pi {α : Fin 0 → Sort _} {P : (∀ i, α i) → Prop} :
    (∀ x, P x) ↔ P finZeroElim :=
  ⟨fun h ↦ h _, fun h x ↦ Subsingleton.elim finZeroElim x ▸ h⟩
#align fin.forall_fin_zero_pi Fin.forall_fin_zero_pi

@[simp]
theorem exists_fin_zero_pi {α : Fin 0 → Sort _} {P : (∀ i, α i) → Prop} :
    (∃ x, P x) ↔ P finZeroElim :=
  ⟨fun ⟨x, h⟩ ↦ Subsingleton.elim x finZeroElim ▸ h, fun h ↦ ⟨_, h⟩⟩
#align fin.exists_fin_zero_pi Fin.exists_fin_zero_pi

theorem forall_fin_succ_pi {P : (∀ i, α i) → Prop} : (∀ x, P x) ↔ ∀ a v, P (Fin.cons a v) :=
  ⟨fun h a v ↦ h (Fin.cons a v), consCases⟩
#align fin.forall_fin_succ_pi Fin.forall_fin_succ_pi

theorem exists_fin_succ_pi {P : (∀ i, α i) → Prop} : (∃ x, P x) ↔ ∃ a v, P (Fin.cons a v) :=
  ⟨fun ⟨x, h⟩ ↦ ⟨x 0, tail x, (cons_self_tail x).symm ▸ h⟩, fun ⟨_, _, h⟩ ↦ ⟨_, h⟩⟩
#align fin.exists_fin_succ_pi Fin.exists_fin_succ_pi

/-- Updating the first element of a tuple does not change the tail. -/
@[simp]
theorem tail_update_zero : tail (update q 0 z) = tail q := by
  ext j
  simp [tail, Fin.succ_ne_zero]
#align fin.tail_update_zero Fin.tail_update_zero

/-- Updating a nonzero element and taking the tail commute. -/
@[simp]
theorem tail_update_succ : tail (update q i.succ y) = update (tail q) i y := by
  ext j
  by_cases h : j = i
  · rw [h]
    simp [tail]
  · simp [tail, (Fin.succ_injective n).ne h, h]
#align fin.tail_update_succ Fin.tail_update_succ

theorem comp_cons {α : Type _} {β : Type _} (g : α → β) (y : α) (q : Fin n → α) :
    g ∘ cons y q = cons (g y) (g ∘ q) := by
  ext j
  by_cases h : j = 0
  · rw [h]
    rfl
  · let j' := pred j h
    have : j'.succ = j := succ_pred j h
    rw [← this, cons_succ, comp, comp, cons_succ]
#align fin.comp_cons Fin.comp_cons

theorem comp_tail {α : Type _} {β : Type _} (g : α → β) (q : Fin n.succ → α) :
    g ∘ tail q = tail (g ∘ q) := by
  ext j
  simp [tail]
#align fin.comp_tail Fin.comp_tail

theorem le_cons [∀ i, Preorder (α i)] {x : α 0} {q : ∀ i, α i} {p : ∀ i : Fin n, α i.succ} :
    q ≤ cons x p ↔ q 0 ≤ x ∧ tail q ≤ p :=
  forall_fin_succ.trans <| and_congr Iff.rfl <| forall_congr' fun j ↦ by simp [tail]
#align fin.le_cons Fin.le_cons

theorem cons_le [∀ i, Preorder (α i)] {x : α 0} {q : ∀ i, α i} {p : ∀ i : Fin n, α i.succ} :
    cons x p ≤ q ↔ x ≤ q 0 ∧ p ≤ tail q :=
  @le_cons _ (fun i ↦ (α i)ᵒᵈ) _ x q p
#align fin.cons_le Fin.cons_le

theorem cons_le_cons [∀ i, Preorder (α i)] {x₀ y₀ : α 0} {x y : ∀ i : Fin n, α i.succ} :
    cons x₀ x ≤ cons y₀ y ↔ x₀ ≤ y₀ ∧ x ≤ y :=
  forall_fin_succ.trans <| and_congr_right' <| by simp only [cons_succ, Pi.le_def]
#align fin.cons_le_cons Fin.cons_le_cons

theorem pi_lex_lt_cons_cons {x₀ y₀ : α 0} {x y : ∀ i : Fin n, α i.succ}
    (s : ∀ {i : Fin n.succ}, α i → α i → Prop) :
    Pi.Lex (· < ·) (@s) (Fin.cons x₀ x) (Fin.cons y₀ y) ↔
      s x₀ y₀ ∨ x₀ = y₀ ∧ Pi.Lex (· < ·) (@fun i : Fin n ↦ @s i.succ) x y := by
  simp_rw [Pi.Lex, Fin.exists_fin_succ, Fin.cons_succ, Fin.cons_zero, Fin.forall_fin_succ]
  simp [and_assoc, exists_and_left]
#align fin.pi_lex_lt_cons_cons Fin.pi_lex_lt_cons_cons

theorem range_fin_succ {α} (f : Fin (n + 1) → α) :
    Set.range f = insert (f 0) (Set.range (Fin.tail f)) :=
  Set.ext fun _ ↦ exists_fin_succ.trans <| eq_comm.or Iff.rfl
#align fin.range_fin_succ Fin.range_fin_succ

@[simp]
theorem range_cons {α : Type _} {n : ℕ} (x : α) (b : Fin n → α) :
    Set.range (Fin.cons x b : Fin n.succ → α) = insert x (Set.range b) := by
  rw [range_fin_succ, cons_zero, tail_cons]
#align fin.range_cons Fin.range_cons

section Append

/-- Append a tuple of length `m` to a tuple of length `n` to get a tuple of length `m + n`.
This is a non-dependent version of `Fin.add_cases`. -/
def append {α : Type _} (a : Fin m → α) (b : Fin n → α) : Fin (m + n) → α :=
  @Fin.addCases _ _ (fun _ => α) a b
#align fin.append Fin.append

@[simp]
theorem append_left {α : Type _} (u : Fin m → α) (v : Fin n → α) (i : Fin m) :
    append u v (Fin.castAdd n i) = u i :=
  addCases_left _ _ _
#align fin.append_left Fin.append_left

@[simp]
theorem append_right {α : Type _} (u : Fin m → α) (v : Fin n → α) (i : Fin n) :
    append u v (natAdd m i) = v i :=
  addCases_right _ _ _
#align fin.append_right Fin.append_right

theorem append_right_nil {α : Type _} (u : Fin m → α) (v : Fin n → α) (hv : n = 0) :
    append u v = u ∘ Fin.cast (by rw [hv, add_zero]) := by
  refine' funext (Fin.addCases (fun l => _) fun r => _)
  · rw [append_left, Function.comp_apply]
    refine' congr_arg u (Fin.ext _)
    simp
  · exact (Fin.cast hv r).elim0'
#align fin.append_right_nil Fin.append_right_nil

@[simp]
theorem append_elim0' {α : Type _} (u : Fin m → α) :
    append u Fin.elim0' = u ∘ Fin.cast (add_zero _) :=
  append_right_nil _ _ rfl
#align fin.append_elim0' Fin.append_elim0'

theorem append_left_nil {α : Type _} (u : Fin m → α) (v : Fin n → α) (hu : m = 0) :
    append u v = v ∘ Fin.cast (by rw [hu, zero_add]) := by
  refine' funext (Fin.addCases (fun l => _) fun r => _)
  · exact (Fin.cast hu l).elim0'
  · rw [append_right, Function.comp_apply]
    refine' congr_arg v (Fin.ext _)
    simp [hu]
#align fin.append_left_nil Fin.append_left_nil

@[simp]
theorem elim0'_append {α : Type _} (v : Fin n → α) :
    append Fin.elim0' v = v ∘ Fin.cast (zero_add _) :=
  append_left_nil _ _ rfl
#align fin.elim0'_append Fin.elim0'_append

theorem append_assoc {p : ℕ} {α : Type _} (a : Fin m → α) (b : Fin n → α) (c : Fin p → α) :
    append (append a b) c = append a (append b c) ∘ Fin.cast (add_assoc _ _ _) := by
  ext i
  rw [Function.comp_apply]
  refine' Fin.addCases (fun l => _) (fun r => _) i
  · rw [append_left]
    refine' Fin.addCases (fun ll => _) (fun lr => _) l
    · rw [append_left]
      simp [castAdd_castAdd]
    · rw [append_right]
      simp [castAdd_natAdd]
  · rw [append_right]
    simp [← natAdd_natAdd]
#align fin.append_assoc Fin.append_assoc

end Append

section Repeat

/-- Repeat `a` `m` times. For example `Fin.repeat 2 ![0, 3, 7] = ![0, 3, 7, 0, 3, 7]`. -/
-- Porting note: removed @[simp]
def «repeat» {α : Type _} (m : ℕ) (a : Fin n → α) : Fin (m * n) → α
  | i => a i.modNat
#align fin.repeat Fin.repeat

-- Porting note: added (leanprover/lean4#2042)
@[simp]
theorem repeat_apply {α : Type _} (a : Fin n → α) (i : Fin (m * n)) :
    Fin.repeat m a i = a i.modNat :=
  rfl

@[simp]
theorem repeat_zero {α : Type _} (a : Fin n → α) :
    Fin.repeat 0 a = Fin.elim0' ∘ cast (zero_mul _) :=
  funext fun x => (cast (zero_mul _) x).elim0'
#align fin.repeat_zero Fin.repeat_zero

@[simp]
theorem repeat_one {α : Type _} (a : Fin n → α) : Fin.repeat 1 a = a ∘ cast (one_mul _) := by
  generalize_proofs h
  apply funext
  rw [(Fin.cast h.symm).surjective.forall]
  intro i
  simp [modNat, Nat.mod_eq_of_lt i.is_lt]
#align fin.repeat_one Fin.repeat_one

theorem repeat_succ {α : Type _} (a : Fin n → α) (m : ℕ) :
    Fin.repeat m.succ a =
      append a (Fin.repeat m a) ∘ cast ((Nat.succ_mul _ _).trans (add_comm _ _)) := by
  generalize_proofs h
  apply funext
  rw [(Fin.cast h.symm).surjective.forall]
  refine' Fin.addCases (fun l => _) fun r => _
  · simp [modNat, Nat.mod_eq_of_lt l.is_lt]
  · simp [modNat]
#align fin.repeat_succ Fin.repeat_succ

@[simp]
theorem repeat_add {α : Type _} (a : Fin n → α) (m₁ m₂ : ℕ) :
    Fin.repeat (m₁ + m₂) a = append (Fin.repeat m₁ a) (Fin.repeat m₂ a) ∘ cast (add_mul _ _ _) := by
  generalize_proofs h
  apply funext
  rw [(Fin.cast h.symm).surjective.forall]
  refine' Fin.addCases (fun l => _) fun r => _
  · simp [modNat, Nat.mod_eq_of_lt l.is_lt]
  · simp [modNat, Nat.add_mod]
#align fin.repeat_add Fin.repeat_add

end Repeat

end Tuple

section TupleRight

/-! In the previous section, we have discussed inserting or removing elements on the left of a
tuple. In this section, we do the same on the right. A difference is that `Fin (n+1)` is constructed
inductively from `Fin n` starting from the left, not from the right. This implies that Lean needs
more help to realize that elements belong to the right types, i.e., we need to insert casts at
several places. -/

-- Porting note: `i.castSucc` does not work like it did in Lean 3; `(castSucc i)` must be used.
variable {α : Fin (n + 1) → Type u} (x : α (last n)) (q : ∀ i, α i)
  (p : ∀ i : Fin n, α (castSucc i)) (i : Fin n) (y : α (castSucc i)) (z : α (last n))

/-- The beginning of an `n+1` tuple, i.e., its first `n` entries -/
def init (q : ∀ i, α i) (i : Fin n) : α (castSucc i) :=
  q (castSucc i)
#align fin.init Fin.init

theorem init_def {n : ℕ} {α : Fin (n + 1) → Type _} {q : ∀ i, α i} :
    (init fun k : Fin (n + 1) ↦ q k) = fun k : Fin n ↦ q (castSucc k) :=
  rfl
#align fin.init_def Fin.init_def

/-- Adding an element at the end of an `n`-tuple, to get an `n+1`-tuple. The name `snoc` comes from
`cons` (i.e., adding an element to the left of a tuple) read in reverse order. -/
def snoc (p : ∀ i : Fin n, α (castSucc i)) (x : α (last n)) (i : Fin (n + 1)) : α i :=
  if h : i.val < n then _root_.cast (by rw [Fin.castSucc_cast_lt i h]) (p (castLt i h))
  else _root_.cast (by rw [eq_last_of_not_lt h]) x
#align fin.snoc Fin.snoc

@[simp]
theorem init_snoc : init (snoc p x) = p := by
  ext i
  simp only [init, snoc, coe_castSucc, is_lt, cast_eq, dite_true]
  convert cast_eq rfl (p i)
#align fin.init_snoc Fin.init_snoc

@[simp]
theorem snoc_cast_succ : snoc p x (castSucc i) = p i := by
  simp only [snoc, coe_castSucc, is_lt, cast_eq, dite_true]
  convert cast_eq rfl (p i)
#align fin.snoc_cast_succ Fin.snoc_cast_succ

@[simp]
theorem snoc_comp_cast_succ {n : ℕ} {α : Sort _} {a : α} {f : Fin n → α} :
    (snoc f a : Fin (n + 1) → α) ∘ castSucc = f :=
  funext fun i ↦ by rw [Function.comp_apply, snoc_cast_succ]
#align fin.snoc_comp_cast_succ Fin.snoc_comp_cast_succ

@[simp]
theorem snoc_last : snoc p x (last n) = x := by simp [snoc]
#align fin.snoc_last Fin.snoc_last

@[simp]
theorem snoc_comp_nat_add {n m : ℕ} {α : Sort _} (f : Fin (m + n) → α) (a : α) :
    (snoc f a : Fin _ → α) ∘ (natAdd m : Fin (n + 1) → Fin (m + n + 1)) =
      snoc (f ∘ natAdd m) a := by
  ext i
  refine' Fin.lastCases _ (fun i ↦ _) i
  · simp only [Function.comp_apply]
    rw [snoc_last, natAdd_last, snoc_last]
  · simp only [comp_apply, snoc_cast_succ]
    rw [natAdd_castSucc, snoc_cast_succ]
#align fin.snoc_comp_nat_add Fin.snoc_comp_nat_add

@[simp]
theorem snoc_cast_add {α : Fin (n + m + 1) → Type _} (f : ∀ i : Fin (n + m), α (castSucc i))
    (a : α (last (n + m))) (i : Fin n) : (snoc f a) (castAdd (m + 1) i) = f (castAdd m i) :=
  dif_pos _
#align fin.snoc_cast_add Fin.snoc_cast_add

-- Porting note: Had to `unfold comp`
@[simp]
theorem snoc_comp_cast_add {n m : ℕ} {α : Sort _} (f : Fin (n + m) → α) (a : α) :
    (snoc f a : Fin _ → α) ∘ castAdd (m + 1) = f ∘ castAdd m :=
  funext (by unfold comp; exact snoc_cast_add _ _)
#align fin.snoc_comp_cast_add Fin.snoc_comp_cast_add

/-- Updating a tuple and adding an element at the end commute. -/
@[simp]
theorem snoc_update : snoc (update p i y) x = update (snoc p x) (castSucc i) y := by
  ext j
  by_cases h : j.val < n
  · rw [snoc]
    simp only [h]
    simp only [dif_pos]
    by_cases h' : j = castSucc i
    · have C1 : α (castSucc i) = α j := by rw [h']
      have E1 : update (snoc p x) (castSucc i) y j = _root_.cast C1 y := by
        have : update (snoc p x) j (_root_.cast C1 y) j = _root_.cast C1 y := by simp
        convert this
        · exact h'.symm
        · exact heq_of_cast_eq (congr_arg α (Eq.symm h')) rfl
      have C2 : α (castSucc i) = α (castSucc (castLt j h)) := by rw [castSucc_cast_lt, h']
      have E2 : update p i y (castLt j h) = _root_.cast C2 y := by
        have : update p (castLt j h) (_root_.cast C2 y) (castLt j h) = _root_.cast C2 y := by simp
        convert this
        · simp [h, h']
        · exact heq_of_cast_eq C2 rfl
      rw [E1, E2]
      exact eq_rec_compose (Eq.trans C2.symm C1) C2 y
    · have : ¬castLt j h = i := by
        intro E
        apply h'
        rw [← E, castSucc_cast_lt]
      simp [h', this, snoc, h]
  · rw [eq_last_of_not_lt h]
    simp [Ne.symm (ne_of_lt (castSucc_lt_last i))]
#align fin.snoc_update Fin.snoc_update

/-- Adding an element at the beginning of a tuple and then updating it amounts to adding it
directly. -/
theorem update_snoc_last : update (snoc p x) (last n) z = snoc p z := by
  ext j
  by_cases h : j.val < n
  · have : j ≠ last n := ne_of_lt h
    simp [h, update_noteq, this, snoc]
  · rw [eq_last_of_not_lt h]
    simp
#align fin.update_snoc_last Fin.update_snoc_last

/-- Concatenating the first element of a tuple with its tail gives back the original tuple -/
@[simp]
theorem snoc_init_self : snoc (init q) (q (last n)) = q := by
  ext j
  by_cases h : j.val < n
  · simp only [init, snoc, h, cast_eq, dite_true]
    have _ : castSucc (castLt j h) = j := castSucc_cast_lt _ _
    rw [← cast_eq rfl (q j)]
    congr
  · rw [eq_last_of_not_lt h]
    simp
#align fin.snoc_init_self Fin.snoc_init_self

/-- Updating the last element of a tuple does not change the beginning. -/
@[simp]
theorem init_update_last : init (update q (last n) z) = init q := by
  ext j
  simp [init, ne_of_lt, castSucc_lt_last]
#align fin.init_update_last Fin.init_update_last

/-- Updating an element and taking the beginning commute. -/
@[simp]
theorem init_update_castSucc : init (update q (castSucc i) y) = update (init q) i y := by
  ext j
  by_cases h : j = i
  · rw [h]
    simp [init]
  · simp [init, h]
#align fin.init_update_cast_succ Fin.init_update_castSucc

/-- `tail` and `init` commute. We state this lemma in a non-dependent setting, as otherwise it
would involve a cast to convince Lean that the two types are equal, making it harder to use. -/
theorem tail_init_eq_init_tail {β : Type _} (q : Fin (n + 2) → β) :
    tail (init q) = init (tail q) := by
  ext i
  simp [tail, init, castSucc_fin_succ]
#align fin.tail_init_eq_init_tail Fin.tail_init_eq_init_tail

/-- `cons` and `snoc` commute. We state this lemma in a non-dependent setting, as otherwise it
would involve a cast to convince Lean that the two types are equal, making it harder to use. -/
theorem cons_snoc_eq_snoc_cons {β : Type _} (a : β) (q : Fin n → β) (b : β) :
    @cons n.succ (fun _ ↦ β) a (snoc q b) = snoc (cons a q) b := by
  ext i
  by_cases h : i = 0
  · rw [h]
    -- Porting note: `refl` finished it here in Lean 3, but I had to add more.
    simp [snoc, castLt]
  set j := pred i h with ji
  have : i = j.succ := by rw [ji, succ_pred]
  rw [this, cons_succ]
  by_cases h' : j.val < n
  · set k := castLt j h' with jk
    have : j = castSucc k := by rw [jk, castSucc_cast_lt]
    rw [this, ← castSucc_fin_succ, snoc]
    simp [pred, snoc, cons]
  rw [eq_last_of_not_lt h', succ_last]
  simp
#align fin.cons_snoc_eq_snoc_cons Fin.cons_snoc_eq_snoc_cons

theorem comp_init {α : Type _} {β : Type _} (g : α → β) (q : Fin n.succ → α) :
    g ∘ init q = init (g ∘ q) := by
  ext j
  simp [init]
#align fin.comp_init Fin.comp_init

end TupleRight

section InsertNth

variable {α : Fin (n + 1) → Type u} {β : Type v}

/- Porting note: Lean told me `(fun x x_1 ↦ α x)` was an invalid motive, but disabling
automatic insertion and specifying that motive seems to work. -/
/-- Define a function on `Fin (n + 1)` from a value on `i : Fin (n + 1)` and values on each
`Fin.succAbove i j`, `j : Fin n`. This version is elaborated as eliminator and works for
propositions, see also `Fin.insertNth` for a version without an `@[elab_as_elim]`
attribute. -/
@[elab_as_elim]
def succAboveCases {α : Fin (n + 1) → Sort u} (i : Fin (n + 1)) (x : α i)
    (p : ∀ j : Fin n, α (i.succAbove j)) (j : Fin (n + 1)) : α j :=
  if hj : j = i then Eq.rec x hj.symm
  else
    if hlt : j < i then @Eq.recOn _ _ (fun x _ ↦ α x) _ (succAbove_castLt hlt) (p _)
    else @Eq.recOn _ _ (fun x _ ↦ α x) _ (succAbove_pred <| (Ne.lt_or_lt hj).resolve_left hlt) (p _)
#align fin.succ_above_cases Fin.succAboveCases

theorem forall_iff_succAbove {p : Fin (n + 1) → Prop} (i : Fin (n + 1)) :
    (∀ j, p j) ↔ p i ∧ ∀ j, p (i.succAbove j) :=
  ⟨fun h ↦ ⟨h _, fun _ ↦ h _⟩, fun h ↦ succAboveCases i h.1 h.2⟩
#align fin.forall_iff_succ_above Fin.forall_iff_succAbove

/-- Insert an element into a tuple at a given position. For `i = 0` see `Fin.cons`,
for `i = Fin.last n` see `Fin.snoc`. See also `Fin.succAboveCases` for a version elaborated
as an eliminator. -/
def insertNth (i : Fin (n + 1)) (x : α i) (p : ∀ j : Fin n, α (i.succAbove j)) (j : Fin (n + 1)) :
    α j :=
  succAboveCases i x p j
#align fin.insert_nth Fin.insertNth

@[simp]
theorem insertNth_apply_same (i : Fin (n + 1)) (x : α i) (p : ∀ j, α (i.succAbove j)) :
    insertNth i x p i = x := by simp [insertNth, succAboveCases]
#align fin.insert_nth_apply_same Fin.insertNth_apply_same

@[simp]
theorem insertNth_apply_succAbove (i : Fin (n + 1)) (x : α i) (p : ∀ j, α (i.succAbove j))
    (j : Fin n) : insertNth i x p (i.succAbove j) = p j := by
  simp only [insertNth, succAboveCases, dif_neg (succAbove_ne _ _), succAbove_lt_iff]
  split_ifs with hlt
  · generalize_proofs H₁ H₂; revert H₂
    generalize hk : castLt ((succAbove i).toEmbedding j) H₁ = k
    rw [castLt_succAbove hlt] at hk; cases hk
    intro; rfl
  · generalize_proofs H₁ H₂; revert H₂
    generalize hk : pred ((succAbove i).toEmbedding j) H₁ = k
    rw [pred_succAbove (le_of_not_lt hlt)] at hk; cases hk
    intro; rfl
#align fin.insert_nth_apply_succ_above Fin.insertNth_apply_succAbove

@[simp]
theorem succAbove_cases_eq_insertNth : @succAboveCases.{u + 1} = @insertNth.{u} :=
  rfl
#align fin.succ_above_cases_eq_insert_nth Fin.succAbove_cases_eq_insertNth

/- Porting note: Had to `unfold comp`. Sometimes, when I use a placeholder, if I try to insert
what Lean says it synthesized, it gives me a type error anyway. In this case, it's `x` and `p`. -/
@[simp]
theorem insertNth_comp_succAbove (i : Fin (n + 1)) (x : β) (p : Fin n → β) :
    insertNth i x p ∘ i.succAbove = p :=
  funext (by unfold comp; exact insertNth_apply_succAbove i _ _)
#align fin.insert_nth_comp_succ_above Fin.insertNth_comp_succAbove

theorem insertNth_eq_iff {i : Fin (n + 1)} {x : α i} {p : ∀ j, α (i.succAbove j)} {q : ∀ j, α j} :
    i.insertNth x p = q ↔ q i = x ∧ p = fun j ↦ q (i.succAbove j) := by
  simp [funext_iff, forall_iff_succAbove i, eq_comm]
#align fin.insert_nth_eq_iff Fin.insertNth_eq_iff

theorem eq_insertNth_iff {i : Fin (n + 1)} {x : α i} {p : ∀ j, α (i.succAbove j)} {q : ∀ j, α j} :
    q = i.insertNth x p ↔ q i = x ∧ p = fun j ↦ q (i.succAbove j) :=
  eq_comm.trans insertNth_eq_iff
#align fin.eq_insert_nth_iff Fin.eq_insertNth_iff

/- Porting note: Once again, Lean told me `(fun x x_1 ↦ α x)` was an invalid motive, but disabling
automatic insertion and specifying that motive seems to work. -/
theorem insertNth_apply_below {i j : Fin (n + 1)} (h : j < i) (x : α i)
    (p : ∀ k, α (i.succAbove k)) :
    i.insertNth x p j = @Eq.recOn _ _ (fun x _ ↦ α x) _ (succAbove_castLt h) (p <| j.castLt _) := by
  rw [insertNth, succAboveCases, dif_neg h.ne, dif_pos h]
#align fin.insert_nth_apply_below Fin.insertNth_apply_below

/- Porting note: Once again, Lean told me `(fun x x_1 ↦ α x)` was an invalid motive, but disabling
automatic insertion and specifying that motive seems to work. -/
theorem insertNth_apply_above {i j : Fin (n + 1)} (h : i < j) (x : α i)
    (p : ∀ k, α (i.succAbove k)) :
    i.insertNth x p j = @Eq.recOn _ _ (fun x _ ↦ α x) _ (succAbove_pred h) (p <| j.pred _) := by
  rw [insertNth, succAboveCases, dif_neg h.ne', dif_neg h.not_lt]
#align fin.insert_nth_apply_above Fin.insertNth_apply_above

theorem insertNth_zero (x : α 0) (p : ∀ j : Fin n, α (succAbove 0 j)) :
    insertNth 0 x p =
      cons x fun j ↦ _root_.cast (congr_arg α (congr_fun succAbove_zero j)) (p j) := by
  refine' insertNth_eq_iff.2 ⟨by simp, _⟩
  ext j
  convert (cons_succ x p j).symm
#align fin.insert_nth_zero Fin.insertNth_zero

@[simp]
theorem insertNth_zero' (x : β) (p : Fin n → β) : @insertNth _ (fun _ ↦ β) 0 x p = cons x p := by
  simp [insertNth_zero]
#align fin.insert_nth_zero' Fin.insertNth_zero'

theorem insertNth_last (x : α (last n)) (p : ∀ j : Fin n, α ((last n).succAbove j)) :
    insertNth (last n) x p =
      snoc (fun j ↦ _root_.cast (congr_arg α (succAbove_last_apply j)) (p j)) x := by
  refine' insertNth_eq_iff.2 ⟨by simp, _⟩
  ext j
  apply eq_of_heq
  trans snoc (fun j ↦ _root_.cast (congr_arg α (succAbove_last_apply j)) (p j)) x (castSucc j)
  · rw [snoc_cast_succ]
    exact (cast_heq _ _).symm
  · apply congr_arg_heq
    rw [succAbove_last]
#align fin.insert_nth_last Fin.insertNth_last

@[simp]
theorem insertNth_last' (x : β) (p : Fin n → β) :
    @insertNth _ (fun _ ↦ β) (last n) x p = snoc p x := by simp [insertNth_last]
#align fin.insert_nth_last' Fin.insertNth_last'

@[simp]
theorem insertNth_zero_right [∀ j, Zero (α j)] (i : Fin (n + 1)) (x : α i) :
    i.insertNth x 0 = Pi.single i x :=
  insertNth_eq_iff.2 <| by simp [succAbove_ne, Pi.zero_def]
#align fin.insert_nth_zero_right Fin.insertNth_zero_right

theorem insertNth_binop (op : ∀ j, α j → α j → α j) (i : Fin (n + 1)) (x y : α i)
    (p q : ∀ j, α (i.succAbove j)) :
    (i.insertNth (op i x y) fun j ↦ op _ (p j) (q j)) = fun j ↦
      op j (i.insertNth x p j) (i.insertNth y q j) :=
  insertNth_eq_iff.2 <| by simp
#align fin.insert_nth_binop Fin.insertNth_binop

@[simp]
theorem insertNth_mul [∀ j, Mul (α j)] (i : Fin (n + 1)) (x y : α i)
    (p q : ∀ j, α (i.succAbove j)) :
    i.insertNth (x * y) (p * q) = i.insertNth x p * i.insertNth y q :=
  insertNth_binop (fun _ ↦ (· * ·)) i x y p q
#align fin.insert_nth_mul Fin.insertNth_mul

@[simp]
theorem insertNth_add [∀ j, Add (α j)] (i : Fin (n + 1)) (x y : α i)
    (p q : ∀ j, α (i.succAbove j)) :
    i.insertNth (x + y) (p + q) = i.insertNth x p + i.insertNth y q :=
  insertNth_binop (fun _ ↦ (· + ·)) i x y p q
#align fin.insert_nth_add Fin.insertNth_add

@[simp]
theorem insertNth_div [∀ j, Div (α j)] (i : Fin (n + 1)) (x y : α i)
    (p q : ∀ j, α (i.succAbove j)) :
    i.insertNth (x / y) (p / q) = i.insertNth x p / i.insertNth y q :=
  insertNth_binop (fun _ ↦ (· / ·)) i x y p q
#align fin.insert_nth_div Fin.insertNth_div

@[simp]
theorem insertNth_sub [∀ j, Sub (α j)] (i : Fin (n + 1)) (x y : α i)
    (p q : ∀ j, α (i.succAbove j)) :
    i.insertNth (x - y) (p - q) = i.insertNth x p - i.insertNth y q :=
  insertNth_binop (fun _ ↦ Sub.sub) i x y p q
#align fin.insert_nth_sub Fin.insertNth_sub

@[simp]
theorem insertNth_sub_same [∀ j, AddGroup (α j)] (i : Fin (n + 1)) (x y : α i)
    (p : ∀ j, α (i.succAbove j)) : i.insertNth x p - i.insertNth y p = Pi.single i (x - y) := by
  simp_rw [← insertNth_sub, ← insertNth_zero_right, Pi.sub_def, sub_self, Pi.zero_def]
#align fin.insert_nth_sub_same Fin.insertNth_sub_same

variable [∀ i, Preorder (α i)]

theorem insertNth_le_iff {i : Fin (n + 1)} {x : α i} {p : ∀ j, α (i.succAbove j)} {q : ∀ j, α j} :
    i.insertNth x p ≤ q ↔ x ≤ q i ∧ p ≤ fun j ↦ q (i.succAbove j) := by
  simp [Pi.le_def, forall_iff_succAbove i]
#align fin.insert_nth_le_iff Fin.insertNth_le_iff

theorem le_insertNth_iff {i : Fin (n + 1)} {x : α i} {p : ∀ j, α (i.succAbove j)} {q : ∀ j, α j} :
    q ≤ i.insertNth x p ↔ q i ≤ x ∧ (fun j ↦ q (i.succAbove j)) ≤ p := by
  simp [Pi.le_def, forall_iff_succAbove i]
#align fin.le_insert_nth_iff Fin.le_insertNth_iff

open Set

theorem insertNth_mem_Icc {i : Fin (n + 1)} {x : α i} {p : ∀ j, α (i.succAbove j)}
    {q₁ q₂ : ∀ j, α j} :
    i.insertNth x p ∈ Icc q₁ q₂ ↔
      x ∈ Icc (q₁ i) (q₂ i) ∧ p ∈ Icc (fun j ↦ q₁ (i.succAbove j)) fun j ↦ q₂ (i.succAbove j) := by
  simp only [mem_Icc, insertNth_le_iff, le_insertNth_iff, and_assoc, @and_left_comm (x ≤ q₂ i)]
#align fin.insert_nth_mem_Icc Fin.insertNth_mem_Icc

theorem preimage_insertNth_Icc_of_mem {i : Fin (n + 1)} {x : α i} {q₁ q₂ : ∀ j, α j}
    (hx : x ∈ Icc (q₁ i) (q₂ i)) :
    i.insertNth x ⁻¹' Icc q₁ q₂ = Icc (fun j ↦ q₁ (i.succAbove j)) fun j ↦ q₂ (i.succAbove j) :=
  Set.ext fun p ↦ by simp only [mem_preimage, insertNth_mem_Icc, hx, true_and_iff]
#align fin.preimage_insert_nth_Icc_of_mem Fin.preimage_insertNth_Icc_of_mem

theorem preimage_insertNth_Icc_of_not_mem {i : Fin (n + 1)} {x : α i} {q₁ q₂ : ∀ j, α j}
    (hx : x ∉ Icc (q₁ i) (q₂ i)) : i.insertNth x ⁻¹' Icc q₁ q₂ = ∅ :=
  Set.ext fun p ↦ by
    simp only [mem_preimage, insertNth_mem_Icc, hx, false_and_iff, mem_empty_iff_false]
#align fin.preimage_insert_nth_Icc_of_not_mem Fin.preimage_insertNth_Icc_of_not_mem

end InsertNth

section Find

/-- `find p` returns the first index `n` where `p n` is satisfied, and `none` if it is never
satisfied. -/
def find : ∀ {n : ℕ} (p : Fin n → Prop) [DecidablePred p], Option (Fin n)
  | 0, _p, _ => none
  | n + 1, p, _ => by
    exact
      Option.casesOn (@find n (fun i ↦ p (i.castLt (Nat.lt_succ_of_lt i.2))) _)
        (if _ : p (Fin.last n) then some (Fin.last n) else none) fun i ↦
        some (i.castLt (Nat.lt_succ_of_lt i.2))
#align fin.find Fin.find

/-- If `find p = some i`, then `p i` holds -/
theorem find_spec :
    ∀ {n : ℕ} (p : Fin n → Prop) [DecidablePred p] {i : Fin n} (_ : i ∈ Fin.find p), p i
  | 0, p, I, i, hi => Option.noConfusion hi
  | n + 1, p, I, i, hi => by
    rw [find] at hi
    cases' h : find fun i : Fin n ↦ p (i.castLt (Nat.lt_succ_of_lt i.2)) with j
    · rw [h] at hi
      dsimp at hi
      split_ifs at hi with hl
      · simp only [Option.mem_def, Option.some.injEq] at hi
        exact hi ▸ hl
      · exact (Option.not_mem_none _ hi).elim
    · rw [h] at hi
      dsimp at hi
      rw [← Option.some_inj.1 hi]
      refine @find_spec n (fun i ↦ p (i.castLt (Nat.lt_succ_of_lt i.2))) _ _ h
#align fin.find_spec Fin.find_spec

/-- `find p` does not return `none` if and only if `p i` holds at some index `i`. -/
theorem isSome_find_iff :
    ∀ {n : ℕ} {p : Fin n → Prop} [DecidablePred p], (find p).isSome ↔ ∃ i, p i
  | 0, p, _ => iff_of_false (fun h ↦ Bool.noConfusion h) fun ⟨i, _⟩ ↦ Fin.elim0' i
  | n + 1, p, _ =>
    ⟨fun h ↦ by
      rw [Option.isSome_iff_exists] at h
      cases' h with i hi
      exact ⟨i, find_spec _ hi⟩, fun ⟨⟨i, hin⟩, hi⟩ ↦ by
      dsimp [find]
      cases' h : find fun i : Fin n ↦ p (i.castLt (Nat.lt_succ_of_lt i.2)) with j
      · split_ifs with hl
        · exact Option.isSome_some
        · have := (@isSome_find_iff n (fun x ↦ p (x.castLt (Nat.lt_succ_of_lt x.2))) _).2
              ⟨⟨i, lt_of_le_of_ne (Nat.le_of_lt_succ hin) fun h ↦ by cases h; exact hl hi⟩, hi⟩
          rw [h] at this
          exact this
      · simp⟩
#align fin.is_some_find_iff Fin.isSome_find_iff

/-- `find p` returns `none` if and only if `p i` never holds. -/
theorem find_eq_none_iff {n : ℕ} {p : Fin n → Prop} [DecidablePred p] :
    find p = none ↔ ∀ i, ¬p i := by rw [← not_exists, ← isSome_find_iff] ; cases find p <;> simp
#align fin.find_eq_none_iff Fin.find_eq_none_iff

/-- If `find p` returns `some i`, then `p j` does not hold for `j < i`, i.e., `i` is minimal among
the indices where `p` holds. -/
theorem find_min :
    ∀ {n : ℕ} {p : Fin n → Prop} [DecidablePred p] {i : Fin n} (_ : i ∈ Fin.find p) {j : Fin n}
      (_ : j < i), ¬p j
  | 0, p, _, i, hi, _, _, _ => Option.noConfusion hi
  | n + 1, p, _, i, hi, ⟨j, hjn⟩, hj, hpj => by
    rw [find] at hi
    cases' h : find fun i : Fin n ↦ p (i.castLt (Nat.lt_succ_of_lt i.2)) with k
    · simp only [h] at hi
      split_ifs at hi with hl
      · cases hi
        rw [find_eq_none_iff] at h
        refine h ⟨j, hj⟩ hpj
      · exact Option.not_mem_none _ hi
    · rw [h] at hi
      dsimp at hi
      obtain rfl := Option.some_inj.1 hi
      exact find_min h (show (⟨j, lt_trans hj k.2⟩ : Fin n) < k from hj) hpj
#align fin.find_min Fin.find_min

theorem find_min' {p : Fin n → Prop} [DecidablePred p] {i : Fin n} (h : i ∈ Fin.find p) {j : Fin n}
    (hj : p j) : i ≤ j :=
  le_of_not_gt fun hij ↦ find_min h hij hj
#align fin.find_min' Fin.find_min'

theorem nat_find_mem_find {p : Fin n → Prop} [DecidablePred p]
    (h : ∃ i, ∃ hin : i < n, p ⟨i, hin⟩) :
    (⟨Nat.find h, (Nat.find_spec h).fst⟩ : Fin n) ∈ find p := by
  let ⟨i, hin, hi⟩ := h
  cases' hf : find p with f
  · rw [find_eq_none_iff] at hf
    exact (hf ⟨i, hin⟩ hi).elim
  · refine' Option.some_inj.2 (le_antisymm _ _)
    · exact find_min' hf (Nat.find_spec h).snd
    · exact Nat.find_min' _ ⟨f.2, by convert find_spec p hf⟩
#align fin.nat_find_mem_find Fin.nat_find_mem_find

theorem mem_find_iff {p : Fin n → Prop} [DecidablePred p] {i : Fin n} :
    i ∈ Fin.find p ↔ p i ∧ ∀ j, p j → i ≤ j :=
  ⟨fun hi ↦ ⟨find_spec _ hi, fun _ ↦ find_min' hi⟩, by
    rintro ⟨hpi, hj⟩
    cases hfp : Fin.find p
    · rw [find_eq_none_iff] at hfp
      exact (hfp _ hpi).elim
    · exact Option.some_inj.2 (le_antisymm (find_min' hfp hpi) (hj _ (find_spec _ hfp)))⟩
#align fin.mem_find_iff Fin.mem_find_iff

theorem find_eq_some_iff {p : Fin n → Prop} [DecidablePred p] {i : Fin n} :
    Fin.find p = some i ↔ p i ∧ ∀ j, p j → i ≤ j :=
  mem_find_iff
#align fin.find_eq_some_iff Fin.find_eq_some_iff

theorem mem_find_of_unique {p : Fin n → Prop} [DecidablePred p] (h : ∀ i j, p i → p j → i = j)
    {i : Fin n} (hi : p i) : i ∈ Fin.find p :=
  mem_find_iff.2 ⟨hi, fun j hj ↦ le_of_eq <| h i j hi hj⟩
#align fin.mem_find_of_unique Fin.mem_find_of_unique

end Find

/-- To show two sigma pairs of tuples agree, it to show the second elements are related via
`Fin.cast`. -/
theorem sigma_eq_of_eq_comp_cast {α : Type _} :
    ∀ {a b : Σii, Fin ii → α} (h : a.fst = b.fst), a.snd = b.snd ∘ Fin.cast h → a = b
  | ⟨ai, a⟩, ⟨bi, b⟩, hi, h => by
    dsimp only at hi
    subst hi
    simpa using h
#align fin.sigma_eq_of_eq_comp_cast Fin.sigma_eq_of_eq_comp_cast

/-- `Fin.sigma_eq_of_eq_comp_cast` as an `iff`. -/
theorem sigma_eq_iff_eq_comp_cast {α : Type _} {a b : Σii, Fin ii → α} :
    a = b ↔ ∃ h : a.fst = b.fst, a.snd = b.snd ∘ Fin.cast h :=
  ⟨fun h ↦ h ▸ ⟨rfl, funext <| Fin.rec fun _ _ ↦ rfl⟩, fun ⟨_, h'⟩ ↦
    sigma_eq_of_eq_comp_cast _ h'⟩
#align fin.sigma_eq_iff_eq_comp_cast Fin.sigma_eq_iff_eq_comp_cast

end Fin


/-
Copyright (c) 2019 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Yury Kudryashov
-/
import data.equiv.basic
import data.list.basic
import algebra.star.basic

/-!
# Free monoid over a given alphabet

## Main definitions

* `free_monoid α`: free monoid over alphabet `α`; defined as a synonym for `list α`
  with multiplication given by `(++)`.
* `free_monoid.of`: embedding `α → free_monoid α` sending each element `x` to `[x]`;
* `free_monoid.lift`: natural equivalence between `α → M` and `free_monoid α →* M`
* `free_monoid.map`: embedding of `α → β` into `free_monoid α →* free_monoid β` given by `list.map`.
-/

variables {α : Type*} {β : Type*} {γ : Type*} {M : Type*} [monoid M] {N : Type*} [monoid N]

/-- Free monoid over a given alphabet. -/
@[to_additive "Free nonabelian additive monoid over a given alphabet"]
def free_monoid (α) := list α

namespace free_monoid

@[to_additive]
instance : monoid (free_monoid α) :=
{ one := [],
  mul := λ x y, (x ++ y : list α),
  mul_one := by intros; apply list.append_nil,
  one_mul := by intros; refl,
  mul_assoc := by intros; apply list.append_assoc }

@[to_additive]
instance : inhabited (free_monoid α) := ⟨1⟩

@[to_additive]
lemma one_def : (1 : free_monoid α) = [] := rfl

@[to_additive]
lemma mul_def (xs ys : list α) : (xs * ys : free_monoid α) = (xs ++ ys : list α) :=
rfl

/-- Embeds an element of `α` into `free_monoid α` as a singleton list. -/
@[to_additive "Embeds an element of `α` into `free_add_monoid α` as a singleton list." ]
def of (x : α) : free_monoid α := [x]

@[to_additive]
lemma of_def (x : α) : of x = [x] := rfl

@[to_additive]
lemma of_injective : function.injective (@of α) :=
λ a b, list.head_eq_of_cons_eq

/-- Recursor for `free_monoid` using `1` and `of x * xs` instead of `[]` and `x :: xs`. -/
@[elab_as_eliminator, to_additive
  "Recursor for `free_add_monoid` using `0` and `of x + xs` instead of `[]` and `x :: xs`."]
def rec_on {C : free_monoid α → Sort*} (xs : free_monoid α) (h0 : C 1)
  (ih : Π x xs, C xs → C (of x * xs)) : C xs := list.rec_on xs h0 ih

@[ext, to_additive]
lemma hom_eq ⦃f g : free_monoid α →* M⦄ (h : ∀ x, f (of x) = g (of x)) :
  f = g :=
monoid_hom.ext $ λ l, rec_on l (f.map_one.trans g.map_one.symm) $
  λ x xs hxs, by simp only [h, hxs, monoid_hom.map_mul]

/-- Equivalence between maps `α → M` and monoid homomorphisms `free_monoid α →* M`. -/
@[to_additive "Equivalence between maps `α → A` and additive monoid homomorphisms
`free_add_monoid α →+ A`."]
def lift : (α → M) ≃ (free_monoid α →* M) :=
{ to_fun := λ f, ⟨λ l, (l.map f).prod, rfl,
    λ l₁ l₂, by simp only [mul_def, list.map_append, list.prod_append]⟩,
  inv_fun := λ f x, f (of x),
  left_inv := λ f, funext $ λ x, one_mul (f x),
  right_inv := λ f, hom_eq $ λ x, one_mul (f (of x)) }

@[simp, to_additive]
lemma lift_symm_apply (f : free_monoid α →* M) : lift.symm f = f ∘ of := rfl

@[to_additive]
lemma lift_apply (f : α → M) (l : free_monoid α) : lift f l = (l.map f).prod := rfl

@[to_additive]
lemma lift_comp_of (f : α → M) : (lift f) ∘ of = f := lift.symm_apply_apply f

@[simp, to_additive]
lemma lift_eval_of (f : α → M) (x : α) : lift f (of x) = f x :=
congr_fun (lift_comp_of f) x

@[simp, to_additive]
lemma lift_restrict (f : free_monoid α →* M) : lift (f ∘ of) = f :=
lift.apply_symm_apply f

@[to_additive]
lemma comp_lift (g : M →* N) (f : α → M) : g.comp (lift f) = lift (g ∘ f) :=
by { ext, simp }

@[to_additive]
lemma hom_map_lift (g : M →* N) (f : α → M) (x : free_monoid α) : g (lift f x) = lift (g ∘ f) x :=
monoid_hom.ext_iff.1 (comp_lift g f) x

/-- The unique monoid homomorphism `free_monoid α →* free_monoid β` that sends
each `of x` to `of (f x)`. -/
@[to_additive "The unique additive monoid homomorphism `free_add_monoid α →+ free_add_monoid β`
that sends each `of x` to `of (f x)`."]
def map (f : α → β) : free_monoid α →* free_monoid β :=
{ to_fun := list.map f,
  map_one' := rfl,
  map_mul' := λ l₁ l₂, list.map_append _ _ _ }

@[simp, to_additive] lemma map_of (f : α → β) (x : α) : map f (of x) = of (f x) := rfl

@[to_additive]
lemma lift_of_comp_eq_map (f : α → β) :
  lift (λ x, of (f x)) = map f :=
hom_eq $ λ x, rfl

@[to_additive]
lemma map_comp (g : β → γ) (f : α → β) : map (g ∘ f) = (map g).comp (map f) :=
hom_eq $ λ x, rfl

instance : star_monoid (free_monoid α) :=
{ star := list.reverse,
  star_involutive := list.reverse_reverse,
  star_mul := list.reverse_append, }

@[simp]
lemma star_of (x : α) : star (of x) = of x := rfl

/-- Note that `star_one` is already a global simp lemma, but this one works with dsimp too -/
@[simp]
lemma star_one : star (1 : free_monoid α) = 1 := rfl

end free_monoid


import data.real.basic -- hide
/-
## The `ring` tactic

In this level we learn a high-level tactic, `ring`, which tries to prove a goal that involves
an algebraic expression (in any ring). Let's see it in action, it's quite powerful!
-/
/- Symbol:
ℤ : \Z
-/
/- Lemma : no-side-bar
For every $x,y,z\in \mathbb{Z}$, we have
$$(x+y)^3 - z^2 + 2xz - x^2 = x^3 + 3x^2y + 3xy^2 + y^3 - (z-x)^2$$.
-/
lemma r0 (x y z : ℤ) : (x+y)^3 - z^2 + 2*x*z - x^2 = x^3 + 3*x^2*y + 3*x*y^2 + y^3 - (z-x)^2 :=
begin
  ring,

end


import tactic
import data.real.basic -- for part d

lemma part_a (n : ℕ) : (11 : ℤ) ∣ 5^(2*n) - 3^n :=
begin
  sorry
end

-- let m be n-1 so 4n-1 becomes 4m+3
lemma part_b (m : ℕ) : (2^(4 * m + 3)) % 10 = 8 :=
begin
  sorry
end

-- "positive" is not needed here
lemma part_c (n : ℕ) : 9 ∣ n^3 + (n+1)^3 + (n+2)^3 :=
begin
  sorry
end

-- also works for n=0
lemma part_d (n : ℕ) (x : ℝ) (hx : 2 ≤ x) : (n : ℝ) * x ≤ x^n :=
begin
  sorry
end

lemma part_e (n : ℕ) (hn : 3 ≤ n) : 4^n + 3^n + 2^n < 5^n :=
begin
  sorry
end


/-
Copyright (c) 2018 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Mario Carneiro, Simon Hudon
-/
import data.pfunctor.multivariate.basic
import data.pfunctor.univariate.M

/-!
# The M construction as a multivariate polynomial functor.

M types are potentially infinite tree-like structures. They are defined
as the greatest fixpoint of a polynomial functor.

## Main definitions

 * `M.mk`     - constructor
 * `M.dest`   - destructor
 * `M.corec`  - corecursor: useful for formulating infinite, productive computations
 * `M.bisim`  - bisimulation: proof technique to show the equality of infinite objects

## Implementation notes

Dual view of M-types:

 * `Mp`: polynomial functor
 * `M`: greatest fixed point of a polynomial functor

Specifically, we define the polynomial functor `Mp` as:

 * A := a possibly infinite tree-like structure without information in the nodes
 * B := given the tree-like structure `t`, `B t` is a valid path
   from the root of `t` to any given node.

As a result `Mp.obj α` is made of a dataless tree and a function from
its valid paths to values of `α`

The difference with the polynomial functor of an initial algebra is
that `A` is a possibly infinite tree.

## Reference

 * Jeremy Avigad, Mario M. Carneiro and Simon Hudon.
   [*Data Types as Quotients of Polynomial Functors*][avigad-carneiro-hudon2019]
-/

universe u

open_locale mvfunctor

namespace mvpfunctor
open typevec

variables {n : ℕ} (P : mvpfunctor.{u} (n+1))

/-- A path from the root of a tree to one of its node -/
inductive M.path : P.last.M → fin2 n → Type u
| root (x : P.last.M) (a : P.A) (f : P.last.B a → P.last.M) (h : pfunctor.M.dest x = ⟨a, f⟩)
       (i : fin2 n) (c : P.drop.B a i) :
    M.path x i
| child (x : P.last.M) (a : P.A) (f : P.last.B a → P.last.M) (h : pfunctor.M.dest x = ⟨a, f⟩)
      (j : P.last.B a) (i : fin2 n) (c : M.path (f j) i) :
    M.path x i

instance M.path.inhabited (x : P.last.M) {i} [inhabited (P.drop.B x.head i)] :
  inhabited (M.path P x i) :=
⟨ M.path.root _ (pfunctor.M.head x) (pfunctor.M.children x)
  (pfunctor.M.cases_on' x $
    by intros; simp [pfunctor.M.dest_mk]; ext; rw pfunctor.M.children_mk; refl) _
    default ⟩

/-- Polynomial functor of the M-type of `P`. `A` is a data-less
possibly infinite tree whereas, for a given `a : A`, `B a` is a valid
path in tree `a` so that `Wp.obj α` is made of a tree and a function
from its valid paths to the values it contains -/
def Mp : mvpfunctor n :=
{ A := P.last.M, B := M.path P }

/-- `n`-ary M-type for `P` -/
def M (α : typevec n) : Type* := P.Mp.obj α

instance mvfunctor_M : mvfunctor P.M := by delta M; apply_instance
instance inhabited_M {α : typevec _}
  [I : inhabited P.A]
  [Π (i : fin2 n), inhabited (α i)] :
  inhabited (P.M α) :=
@obj.inhabited _ (Mp P) _ (@pfunctor.M.inhabited P.last I) _

/-- construct through corecursion the shape of an M-type
without its contents -/
def M.corec_shape {β : Type u}
    (g₀ : β → P.A)
    (g₂ : Π b : β, P.last.B (g₀ b) → β) :
  β → P.last.M :=
pfunctor.M.corec (λ b, ⟨g₀ b, g₂ b⟩)

/-- Proof of type equality as an arrow -/
def cast_dropB {a a' : P.A} (h : a = a') : P.drop.B a ⟹ P.drop.B a' :=
λ i b, eq.rec_on h b

/-- Proof of type equality as a function -/
def cast_lastB {a a' : P.A} (h : a = a') : P.last.B a → P.last.B a' :=
λ b, eq.rec_on h b

/-- Using corecursion, construct the contents of an M-type -/
def M.corec_contents {α : typevec.{u} n} {β : Type u}
    (g₀ : β → P.A)
    (g₁ : Π b : β, P.drop.B (g₀ b) ⟹ α)
    (g₂ : Π b : β, P.last.B (g₀ b) → β) :
  Π x b, x = M.corec_shape P g₀ g₂ b → M.path P x ⟹ α
| ._ b h ._ (M.path.root x a f h' i c)    :=
  have a = g₀ b,
    by { rw [h, M.corec_shape, pfunctor.M.dest_corec] at h', cases h', refl },
  g₁ b i (P.cast_dropB this i c)
| ._ b h ._ (M.path.child x a f h' j i c) :=
  have h₀ : a = g₀ b,
    by { rw [h, M.corec_shape, pfunctor.M.dest_corec] at h', cases h', refl },
  have h₁ : f j = M.corec_shape P g₀ g₂ (g₂ b (cast_lastB P h₀ j)),
    by { rw [h, M.corec_shape, pfunctor.M.dest_corec] at h', cases h', refl },
  M.corec_contents (f j) (g₂ b (P.cast_lastB h₀ j)) h₁ i c

/-- Corecursor for M-type of `P` -/
def M.corec' {α : typevec n} {β : Type u}
    (g₀ : β → P.A)
    (g₁ : Π b : β, P.drop.B (g₀ b) ⟹ α)
    (g₂ : Π b : β, P.last.B (g₀ b) → β) :
  β → P.M α :=
λ b, ⟨M.corec_shape P g₀ g₂ b, M.corec_contents P g₀ g₁ g₂ _ _ rfl⟩

/-- Corecursor for M-type of `P` -/
def M.corec {α : typevec n} {β : Type u} (g : β → P.obj (α.append1 β)) :
  β → P.M α :=
M.corec' P
  (λ b, (g b).fst)
  (λ b, drop_fun (g b).snd)
  (λ b, last_fun (g b).snd)

/-- Implementation of destructor for M-type of `P` -/
def M.path_dest_left {α : typevec n} {x : P.last.M}
    {a : P.A} {f : P.last.B a → P.last.M} (h : pfunctor.M.dest x = ⟨a, f⟩)
    (f' : M.path P x ⟹ α) :
  P.drop.B a ⟹ α :=
λ i c, f' i (M.path.root x a f h i c)

/-- Implementation of destructor for M-type of `P` -/
def M.path_dest_right {α : typevec n} {x : P.last.M}
    {a : P.A} {f : P.last.B a → P.last.M} (h : pfunctor.M.dest x = ⟨a, f⟩)
    (f' : M.path P x ⟹ α) :
  Π j : P.last.B a, M.path P (f j) ⟹ α :=
λ j i c, f' i (M.path.child x a f h j i c)

/-- Destructor for M-type of `P` -/
def M.dest' {α : typevec n} {x : P.last.M}
    {a : P.A} {f : P.last.B a → P.last.M} (h : pfunctor.M.dest x = ⟨a, f⟩)
    (f' : M.path P x ⟹ α) :
  P.obj (α.append1 (P.M α)) :=
⟨a, split_fun (M.path_dest_left P h f') (λ x, ⟨f x, M.path_dest_right P h f' x⟩)⟩

/-- Destructor for M-types -/
def M.dest {α : typevec n} (x : P.M α) : P.obj (α ::: P.M α) :=
M.dest' P (sigma.eta $ pfunctor.M.dest x.fst).symm x.snd

/-- Constructor for M-types -/
def M.mk  {α : typevec n} : P.obj (α.append1 (P.M α)) → P.M α :=
M.corec _ (λ i, append_fun id (M.dest P) <$$> i)

theorem M.dest'_eq_dest' {α : typevec n} {x : P.last.M}
    {a₁ : P.A} {f₁ : P.last.B a₁ → P.last.M} (h₁ : pfunctor.M.dest x = ⟨a₁, f₁⟩)
    {a₂ : P.A} {f₂ : P.last.B a₂ → P.last.M} (h₂ : pfunctor.M.dest x = ⟨a₂, f₂⟩)
    (f' : M.path P x ⟹ α) : M.dest' P h₁ f' = M.dest' P h₂ f' :=
by cases h₁.symm.trans h₂; refl

theorem M.dest_eq_dest' {α : typevec n} {x : P.last.M}
    {a : P.A} {f : P.last.B a → P.last.M} (h : pfunctor.M.dest x = ⟨a, f⟩)
    (f' : M.path P x ⟹ α) : M.dest P ⟨x, f'⟩ = M.dest' P h f' :=
M.dest'_eq_dest' _ _ _ _

theorem M.dest_corec' {α : typevec.{u} n} {β : Type u}
    (g₀ : β → P.A)
    (g₁ : Π b : β, P.drop.B (g₀ b) ⟹ α)
    (g₂ : Π b : β, P.last.B (g₀ b) → β)
    (x : β) :
  M.dest P (M.corec' P g₀ g₁ g₂ x)  =
    ⟨g₀ x, split_fun (g₁ x) (M.corec' P g₀ g₁ g₂ ∘ (g₂ x))⟩ :=
rfl

theorem M.dest_corec {α : typevec n} {β : Type u} (g : β → P.obj (α.append1 β)) (x : β) :
  M.dest P (M.corec P g x) = append_fun id (M.corec P g) <$$> g x :=
begin
  transitivity, apply M.dest_corec',
  cases g x with a f, dsimp,
  rw mvpfunctor.map_eq, congr,
  conv { to_rhs, rw [←split_drop_fun_last_fun f, append_fun_comp_split_fun] },
  refl
end

lemma M.bisim_lemma {α : typevec n}
  {a₁ : (Mp P).A} {f₁ : (Mp P).B a₁ ⟹ α}
  {a' : P.A} {f' : (P.B a').drop ⟹ α} {f₁' : (P.B a').last → M P α}
  (e₁ : M.dest P ⟨a₁, f₁⟩ = ⟨a', split_fun f' f₁'⟩) :
  ∃ g₁' (e₁' : pfunctor.M.dest a₁ = ⟨a', g₁'⟩),
    f' = M.path_dest_left P e₁' f₁ ∧
    f₁' = λ (x : (last P).B a'),
      ⟨g₁' x, M.path_dest_right P e₁' f₁ x⟩ :=
begin
  generalize_hyp ef : @split_fun n _ (append1 α (M P α)) f' f₁' = ff at e₁,
  cases e₁' : pfunctor.M.dest a₁ with a₁' g₁',
  rw M.dest_eq_dest' _ e₁' at e₁,
  cases e₁, exact ⟨_, e₁', split_fun_inj ef⟩,
end

theorem M.bisim {α : typevec n} (R : P.M α → P.M α → Prop)
  (h : ∀ x y, R x y → ∃ a f f₁ f₂,
    M.dest P x = ⟨a, split_fun f f₁⟩ ∧
    M.dest P y = ⟨a, split_fun f f₂⟩ ∧
    ∀ i, R (f₁ i) (f₂ i))
  (x y) (r : R x y) : x = y :=
begin
  cases x with a₁ f₁,
  cases y with a₂ f₂,
  dsimp [Mp] at *,
  have : a₁ = a₂,
  { refine pfunctor.M.bisim
      (λ a₁ a₂, ∃ x y, R x y ∧ x.1 = a₁ ∧ y.1 = a₂) _ _ _
      ⟨⟨a₁, f₁⟩, ⟨a₂, f₂⟩, r, rfl, rfl⟩,
    rintro _ _ ⟨⟨a₁, f₁⟩, ⟨a₂, f₂⟩, r, rfl, rfl⟩,
    rcases h _ _ r with ⟨a', f', f₁', f₂', e₁, e₂, h'⟩,
    rcases M.bisim_lemma P e₁ with ⟨g₁', e₁', rfl, rfl⟩,
    rcases M.bisim_lemma P e₂ with ⟨g₂', e₂', _, rfl⟩,
    rw [e₁', e₂'],
    exact ⟨_, _, _, rfl, rfl, λ b, ⟨_, _, h' b, rfl, rfl⟩⟩ },
  subst this, congr' with i p,
  induction p with x a f h' i c x a f h' i c p IH generalizing f₁ f₂;
  try
  { rcases h _ _ r with ⟨a', f', f₁', f₂', e₁, e₂, h''⟩,
    rcases M.bisim_lemma P e₁ with ⟨g₁', e₁', rfl, rfl⟩,
    rcases M.bisim_lemma P e₂ with ⟨g₂', e₂', e₃, rfl⟩,
    cases h'.symm.trans e₁',
    cases h'.symm.trans e₂' },
  { exact (congr_fun (congr_fun e₃ i) c : _) },
  { exact IH _ _ (h'' _) }
end

theorem M.bisim₀ {α : typevec n} (R : P.M α → P.M α → Prop)
  (h₀ : equivalence R)
  (h : ∀ x y, R x y →
    (id ::: quot.mk R) <$$> M.dest _ x = (id ::: quot.mk R) <$$> M.dest _ y)
  (x y) (r : R x y) : x = y :=
begin
  apply M.bisim P R _ _ _ r, clear r x y,
  introv Hr, specialize h _ _ Hr, clear Hr,
  rcases M.dest P x with ⟨ax,fx⟩, rcases M.dest P y with ⟨ay,fy⟩,
  intro h, rw [map_eq,map_eq] at h, injection h with h₀ h₁, subst ay,
  simp at h₁, clear h,
  have Hdrop : drop_fun fx = drop_fun fy,
  { replace h₁ := congr_arg drop_fun h₁,
    simpa using h₁, },
  existsi [ax,drop_fun fx,last_fun fx,last_fun fy],
  rw [split_drop_fun_last_fun,Hdrop,split_drop_fun_last_fun],
  simp, intro i,
  replace h₁ := congr_fun (congr_fun h₁ fin2.fz) i,
  simp [(⊚),append_fun,split_fun] at h₁,
  replace h₁ := quot.exact _  h₁,
  rw h₀.eqv_gen_iff at h₁,
  exact h₁,
end

theorem M.bisim' {α : typevec n} (R : P.M α → P.M α → Prop)
  (h : ∀ x y, R x y →
    (id ::: quot.mk R) <$$> M.dest _ x = (id ::: quot.mk R) <$$> M.dest _ y)
  (x y) (r : R x y) : x = y :=
begin
  have := M.bisim₀ P (eqv_gen R) _ _,
  { solve_by_elim [eqv_gen.rel] },
  { apply eqv_gen.is_equivalence },
  { clear r x y, introv Hr,
    have : ∀ x y, R x y → eqv_gen R x y := @eqv_gen.rel _ R,
    induction Hr,
    { rw ← quot.factor_mk_eq R (eqv_gen R) this,
      rwa [append_fun_comp_id,← mvfunctor.map_map,← mvfunctor.map_map,h] },
    all_goals { cc } }
end

theorem M.dest_map {α β : typevec n} (g : α ⟹ β) (x : P.M α) :
  M.dest P (g <$$> x) = append_fun g (λ x, g <$$> x) <$$> M.dest P x :=
begin
  cases x with a f,
  rw map_eq,
  conv { to_rhs, rw [M.dest, M.dest', map_eq, append_fun_comp_split_fun] },
  reflexivity
end

theorem M.map_dest {α β : typevec n} (g : α ::: P.M α ⟹ β ::: P.M β) (x : P.M α)
  (h : ∀ x : P.M α, last_fun g x = (drop_fun g <$$> x : P.M β) ):
  g <$$> M.dest P x = M.dest P (drop_fun g <$$> x) :=
begin
  rw M.dest_map, congr,
  apply eq_of_drop_last_eq; simp,
  ext1, apply h
end

end mvpfunctor