/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Johannes Hölzl

! This file was ported from Lean 3 source module data.list.forall2
! leanprover-community/mathlib commit 10708587e81b68c763fcdb7505f279d52e569768
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.List.Infix

/-!
# Double universal quantification on a list

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file provides an API for `list.forall₂` (definition in `data.list.defs`).
`forall₂ R l₁ l₂` means that `l₁` and `l₂` have the same length, and whenever `a` is the nth element
of `l₁`, and `b` is the nth element of `l₂`, then `R a b` is satisfied.
-/

open Nat Function

namespace List

variable {α β γ δ : Type _} {R S : α → β → Prop} {P : γ → δ → Prop} {Rₐ : α → α → Prop}

open Relator

mk_iff_of_inductive_prop List.Forall₂ List.forall₂_iff

/- warning: list.forall₂_cons -> List.forall₂_cons is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {a : α} {b : β} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, Iff (List.Forall₂.{u1, u2} α β R (List.cons.{u1} α a l₁) (List.cons.{u2} β b l₂)) (And (R a b) (List.Forall₂.{u1, u2} α β R l₁ l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {a : α} {b : β} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, Iff (List.Forall₂.{u2, u1} α β R (List.cons.{u2} α a l₁) (List.cons.{u1} β b l₂)) (And (R a b) (List.Forall₂.{u2, u1} α β R l₁ l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂_cons List.forall₂_consₓ'. -/
@[simp]
theorem forall₂_cons {a b l₁ l₂} : Forall₂ R (a :: l₁) (b :: l₂) ↔ R a b ∧ Forall₂ R l₁ l₂ :=
  ⟨fun h => by cases' h with h₁ h₂ <;> constructor <;> assumption, fun ⟨h₁, h₂⟩ =>
    Forall₂.cons h₁ h₂⟩
#align list.forall₂_cons List.forall₂_cons

/- warning: list.forall₂.imp -> List.Forall₂.imp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {S : α -> β -> Prop}, (forall (a : α) (b : β), (R a b) -> (S a b)) -> (forall {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (List.Forall₂.{u1, u2} α β S l₁ l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {S : α -> β -> Prop}, (forall (a : α) (b : β), (R a b) -> (S a b)) -> (forall {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (List.Forall₂.{u2, u1} α β S l₁ l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂.imp List.Forall₂.impₓ'. -/
theorem Forall₂.imp (H : ∀ a b, R a b → S a b) {l₁ l₂} (h : Forall₂ R l₁ l₂) : Forall₂ S l₁ l₂ := by
  induction h <;> constructor <;> solve_by_elim
#align list.forall₂.imp List.Forall₂.imp

/- warning: list.forall₂.mp -> List.Forall₂.mp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {S : α -> β -> Prop} {Q : α -> β -> Prop}, (forall (a : α) (b : β), (Q a b) -> (R a b) -> (S a b)) -> (forall {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β Q l₁ l₂) -> (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (List.Forall₂.{u1, u2} α β S l₁ l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {S : α -> β -> Prop} {Q : α -> β -> Prop}, (forall (a : α) (b : β), (Q a b) -> (R a b) -> (S a b)) -> (forall {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β Q l₁ l₂) -> (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (List.Forall₂.{u2, u1} α β S l₁ l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂.mp List.Forall₂.mpₓ'. -/
theorem Forall₂.mp {Q : α → β → Prop} (h : ∀ a b, Q a b → R a b → S a b) :
    ∀ {l₁ l₂}, Forall₂ Q l₁ l₂ → Forall₂ R l₁ l₂ → Forall₂ S l₁ l₂
  | [], [], forall₂.nil, forall₂.nil => Forall₂.nil
  | a :: l₁, b :: l₂, forall₂.cons hr hrs, forall₂.cons hq hqs =>
    Forall₂.cons (h a b hr hq) (forall₂.mp hrs hqs)
#align list.forall₂.mp List.Forall₂.mp

/- warning: list.forall₂.flip -> List.Forall₂.flip is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {a : List.{u1} α} {b : List.{u2} β}, (List.Forall₂.{u2, u1} β α (flip.{succ u1, succ u2, 1} α β Prop R) b a) -> (List.Forall₂.{u1, u2} α β R a b)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {a : List.{u2} α} {b : List.{u1} β}, (List.Forall₂.{u1, u2} β α (flip.{succ u2, succ u1, 1} α β Prop R) b a) -> (List.Forall₂.{u2, u1} α β R a b)
Case conversion may be inaccurate. Consider using '#align list.forall₂.flip List.Forall₂.flipₓ'. -/
theorem Forall₂.flip : ∀ {a b}, Forall₂ (flip R) b a → Forall₂ R a b
  | _, _, forall₂.nil => Forall₂.nil
  | a :: as, b :: bs, forall₂.cons h₁ h₂ => Forall₂.cons h₁ h₂.flip
#align list.forall₂.flip List.Forall₂.flip

#print List.forall₂_same /-
@[simp]
theorem forall₂_same : ∀ {l : List α}, Forall₂ Rₐ l l ↔ ∀ x ∈ l, Rₐ x x
  | [] => by simp
  | a :: l => by simp [@forall₂_same l]
#align list.forall₂_same List.forall₂_same
-/

#print List.forall₂_refl /-
theorem forall₂_refl [IsRefl α Rₐ] (l : List α) : Forall₂ Rₐ l l :=
  forall₂_same.2 fun a h => refl _
#align list.forall₂_refl List.forall₂_refl
-/

#print List.forall₂_eq_eq_eq /-
@[simp]
theorem forall₂_eq_eq_eq : Forall₂ ((· = ·) : α → α → Prop) = (· = ·) :=
  by
  funext a b; apply propext
  constructor
  · intro h
    induction h
    · rfl
    simp only [*] <;> constructor <;> rfl
  · rintro rfl
    exact forall₂_refl _
#align list.forall₂_eq_eq_eq List.forall₂_eq_eq_eq
-/

#print List.forall₂_nil_left_iff /-
@[simp]
theorem forall₂_nil_left_iff {l} : Forall₂ R nil l ↔ l = nil :=
  ⟨fun H => by cases H <;> rfl, by rintro rfl <;> exact forall₂.nil⟩
#align list.forall₂_nil_left_iff List.forall₂_nil_left_iff
-/

/- warning: list.forall₂_nil_right_iff -> List.forall₂_nil_right_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l : List.{u1} α}, Iff (List.Forall₂.{u1, u2} α β R l (List.nil.{u2} β)) (Eq.{succ u1} (List.{u1} α) l (List.nil.{u1} α))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l : List.{u2} α}, Iff (List.Forall₂.{u2, u1} α β R l (List.nil.{u1} β)) (Eq.{succ u2} (List.{u2} α) l (List.nil.{u2} α))
Case conversion may be inaccurate. Consider using '#align list.forall₂_nil_right_iff List.forall₂_nil_right_iffₓ'. -/
@[simp]
theorem forall₂_nil_right_iff {l} : Forall₂ R l nil ↔ l = nil :=
  ⟨fun H => by cases H <;> rfl, by rintro rfl <;> exact forall₂.nil⟩
#align list.forall₂_nil_right_iff List.forall₂_nil_right_iff

/- warning: list.forall₂_cons_left_iff -> List.forall₂_cons_left_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {a : α} {l : List.{u1} α} {u : List.{u2} β}, Iff (List.Forall₂.{u1, u2} α β R (List.cons.{u1} α a l) u) (Exists.{succ u2} β (fun (b : β) => Exists.{succ u2} (List.{u2} β) (fun (u' : List.{u2} β) => And (R a b) (And (List.Forall₂.{u1, u2} α β R l u') (Eq.{succ u2} (List.{u2} β) u (List.cons.{u2} β b u'))))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {a : α} {l : List.{u2} α} {u : List.{u1} β}, Iff (List.Forall₂.{u2, u1} α β R (List.cons.{u2} α a l) u) (Exists.{succ u1} β (fun (b : β) => Exists.{succ u1} (List.{u1} β) (fun (u' : List.{u1} β) => And (R a b) (And (List.Forall₂.{u2, u1} α β R l u') (Eq.{succ u1} (List.{u1} β) u (List.cons.{u1} β b u'))))))
Case conversion may be inaccurate. Consider using '#align list.forall₂_cons_left_iff List.forall₂_cons_left_iffₓ'. -/
theorem forall₂_cons_left_iff {a l u} :
    Forall₂ R (a :: l) u ↔ ∃ b u', R a b ∧ Forall₂ R l u' ∧ u = b :: u' :=
  Iff.intro
    (fun h =>
      match u, h with
      | b :: u', forall₂.cons h₁ h₂ => ⟨b, u', h₁, h₂, rfl⟩)
    fun h =>
    match u, h with
    | _, ⟨b, u', h₁, h₂, rfl⟩ => Forall₂.cons h₁ h₂
#align list.forall₂_cons_left_iff List.forall₂_cons_left_iff

#print List.forall₂_cons_right_iff /-
theorem forall₂_cons_right_iff {b l u} :
    Forall₂ R u (b :: l) ↔ ∃ a u', R a b ∧ Forall₂ R u' l ∧ u = a :: u' :=
  Iff.intro
    (fun h =>
      match u, h with
      | b :: u', forall₂.cons h₁ h₂ => ⟨b, u', h₁, h₂, rfl⟩)
    fun h =>
    match u, h with
    | _, ⟨b, u', h₁, h₂, rfl⟩ => Forall₂.cons h₁ h₂
#align list.forall₂_cons_right_iff List.forall₂_cons_right_iff
-/

/- warning: list.forall₂_and_left -> List.forall₂_and_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {p : α -> Prop} (l : List.{u1} α) (u : List.{u2} β), Iff (List.Forall₂.{u1, u2} α β (fun (a : α) (b : β) => And (p a) (R a b)) l u) (And (forall (a : α), (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l) -> (p a)) (List.Forall₂.{u1, u2} α β R l u))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {p : α -> Prop} (l : List.{u2} α) (u : List.{u1} β), Iff (List.Forall₂.{u2, u1} α β (fun (a : α) (b : β) => And (p a) (R a b)) l u) (And (forall (a : α), (Membership.mem.{u2, u2} α (List.{u2} α) (List.instMembershipList.{u2} α) a l) -> (p a)) (List.Forall₂.{u2, u1} α β R l u))
Case conversion may be inaccurate. Consider using '#align list.forall₂_and_left List.forall₂_and_leftₓ'. -/
theorem forall₂_and_left {p : α → Prop} :
    ∀ l u, Forall₂ (fun a b => p a ∧ R a b) l u ↔ (∀ a ∈ l, p a) ∧ Forall₂ R l u
  | [], u => by
    simp only [forall₂_nil_left_iff, forall_prop_of_false (not_mem_nil _), imp_true_iff,
      true_and_iff]
  | a :: l, u => by
    simp only [forall₂_and_left l, forall₂_cons_left_iff, forall_mem_cons, and_assoc', and_comm',
      and_left_comm, exists_and_distrib_left.symm]
#align list.forall₂_and_left List.forall₂_and_left

#print List.forall₂_map_left_iff /-
@[simp]
theorem forall₂_map_left_iff {f : γ → α} :
    ∀ {l u}, Forall₂ R (map f l) u ↔ Forall₂ (fun c b => R (f c) b) l u
  | [], _ => by simp only [map, forall₂_nil_left_iff]
  | a :: l, _ => by simp only [map, forall₂_cons_left_iff, forall₂_map_left_iff]
#align list.forall₂_map_left_iff List.forall₂_map_left_iff
-/

/- warning: list.forall₂_map_right_iff -> List.forall₂_map_right_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {R : α -> β -> Prop} {f : γ -> β} {l : List.{u1} α} {u : List.{u3} γ}, Iff (List.Forall₂.{u1, u2} α β R l (List.map.{u3, u2} γ β f u)) (List.Forall₂.{u1, u3} α γ (fun (a : α) (c : γ) => R a (f c)) l u)
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u1}} {γ : Type.{u2}} {R : α -> β -> Prop} {f : γ -> β} {l : List.{u3} α} {u : List.{u2} γ}, Iff (List.Forall₂.{u3, u1} α β R l (List.map.{u2, u1} γ β f u)) (List.Forall₂.{u3, u2} α γ (fun (a : α) (c : γ) => R a (f c)) l u)
Case conversion may be inaccurate. Consider using '#align list.forall₂_map_right_iff List.forall₂_map_right_iffₓ'. -/
@[simp]
theorem forall₂_map_right_iff {f : γ → β} :
    ∀ {l u}, Forall₂ R l (map f u) ↔ Forall₂ (fun a c => R a (f c)) l u
  | _, [] => by simp only [map, forall₂_nil_right_iff]
  | _, b :: u => by simp only [map, forall₂_cons_right_iff, forall₂_map_right_iff]
#align list.forall₂_map_right_iff List.forall₂_map_right_iff

/- warning: list.left_unique_forall₂' -> List.left_unique_forall₂' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.LeftUnique.{u1, u2} α β R) -> (forall {a : List.{u1} α} {b : List.{u1} α} {c : List.{u2} β}, (List.Forall₂.{u1, u2} α β R a c) -> (List.Forall₂.{u1, u2} α β R b c) -> (Eq.{succ u1} (List.{u1} α) a b))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.LeftUnique.{u2, u1} α β R) -> (forall {a : List.{u2} α} {b : List.{u2} α} {c : List.{u1} β}, (List.Forall₂.{u2, u1} α β R a c) -> (List.Forall₂.{u2, u1} α β R b c) -> (Eq.{succ u2} (List.{u2} α) a b))
Case conversion may be inaccurate. Consider using '#align list.left_unique_forall₂' List.left_unique_forall₂'ₓ'. -/
theorem left_unique_forall₂' (hr : LeftUnique R) : ∀ {a b c}, Forall₂ R a c → Forall₂ R b c → a = b
  | a₀, nil, a₁, forall₂.nil, forall₂.nil => rfl
  | a₀ :: l₀, b :: l, a₁ :: l₁, forall₂.cons ha₀ h₀, forall₂.cons ha₁ h₁ =>
    hr ha₀ ha₁ ▸ left_unique_forall₂' h₀ h₁ ▸ rfl
#align list.left_unique_forall₂' List.left_unique_forall₂'

/- warning: relator.left_unique.forall₂ -> Relator.LeftUnique.forall₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.LeftUnique.{u1, u2} α β R) -> (Relator.LeftUnique.{u1, u2} (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.LeftUnique.{u2, u1} α β R) -> (Relator.LeftUnique.{u2, u1} (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R))
Case conversion may be inaccurate. Consider using '#align relator.left_unique.forall₂ Relator.LeftUnique.forall₂ₓ'. -/
theorem Relator.LeftUnique.forall₂ (hr : LeftUnique R) : LeftUnique (Forall₂ R) :=
  @left_unique_forall₂' _ _ _ hr
#align relator.left_unique.forall₂ Relator.LeftUnique.forall₂

/- warning: list.right_unique_forall₂' -> List.right_unique_forall₂' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.RightUnique.{u1, u2} α β R) -> (forall {a : List.{u1} α} {b : List.{u2} β} {c : List.{u2} β}, (List.Forall₂.{u1, u2} α β R a b) -> (List.Forall₂.{u1, u2} α β R a c) -> (Eq.{succ u2} (List.{u2} β) b c))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.RightUnique.{u2, u1} α β R) -> (forall {a : List.{u2} α} {b : List.{u1} β} {c : List.{u1} β}, (List.Forall₂.{u2, u1} α β R a b) -> (List.Forall₂.{u2, u1} α β R a c) -> (Eq.{succ u1} (List.{u1} β) b c))
Case conversion may be inaccurate. Consider using '#align list.right_unique_forall₂' List.right_unique_forall₂'ₓ'. -/
theorem right_unique_forall₂' (hr : RightUnique R) :
    ∀ {a b c}, Forall₂ R a b → Forall₂ R a c → b = c
  | nil, a₀, a₁, forall₂.nil, forall₂.nil => rfl
  | b :: l, a₀ :: l₀, a₁ :: l₁, forall₂.cons ha₀ h₀, forall₂.cons ha₁ h₁ =>
    hr ha₀ ha₁ ▸ right_unique_forall₂' h₀ h₁ ▸ rfl
#align list.right_unique_forall₂' List.right_unique_forall₂'

/- warning: relator.right_unique.forall₂ -> Relator.RightUnique.forall₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.RightUnique.{u1, u2} α β R) -> (Relator.RightUnique.{u1, u2} (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.RightUnique.{u2, u1} α β R) -> (Relator.RightUnique.{u2, u1} (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R))
Case conversion may be inaccurate. Consider using '#align relator.right_unique.forall₂ Relator.RightUnique.forall₂ₓ'. -/
theorem Relator.RightUnique.forall₂ (hr : RightUnique R) : RightUnique (Forall₂ R) :=
  @right_unique_forall₂' _ _ _ hr
#align relator.right_unique.forall₂ Relator.RightUnique.forall₂

/- warning: relator.bi_unique.forall₂ -> Relator.BiUnique.forall₂ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.BiUnique.{u1, u2} α β R) -> (Relator.BiUnique.{u1, u2} (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.BiUnique.{u2, u1} α β R) -> (Relator.BiUnique.{u2, u1} (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R))
Case conversion may be inaccurate. Consider using '#align relator.bi_unique.forall₂ Relator.BiUnique.forall₂ₓ'. -/
theorem Relator.BiUnique.forall₂ (hr : BiUnique R) : BiUnique (Forall₂ R) :=
  ⟨hr.left.forall₂, hr.right.forall₂⟩
#align relator.bi_unique.forall₂ Relator.BiUnique.forall₂

/- warning: list.forall₂.length_eq -> List.Forall₂.length_eq is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (Eq.{1} Nat (List.length.{u1} α l₁) (List.length.{u2} β l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (Eq.{1} Nat (List.length.{u2} α l₁) (List.length.{u1} β l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂.length_eq List.Forall₂.length_eqₓ'. -/
theorem Forall₂.length_eq : ∀ {l₁ l₂}, Forall₂ R l₁ l₂ → length l₁ = length l₂
  | _, _, forall₂.nil => rfl
  | _, _, forall₂.cons h₁ h₂ => congr_arg succ (forall₂.length_eq h₂)
#align list.forall₂.length_eq List.Forall₂.length_eq

/- warning: list.forall₂.nth_le -> List.Forall₂.nthLe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {x : List.{u1} α} {y : List.{u2} β}, (List.Forall₂.{u1, u2} α β R x y) -> (forall {{i : Nat}} (hx : LT.lt.{0} Nat Nat.hasLt i (List.length.{u1} α x)) (hy : LT.lt.{0} Nat Nat.hasLt i (List.length.{u2} β y)), R (List.nthLe.{u1} α x i hx) (List.nthLe.{u2} β y i hy))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {x : List.{u2} α} {y : List.{u1} β}, (List.Forall₂.{u2, u1} α β R x y) -> (forall {{i : Nat}} (hx : LT.lt.{0} Nat instLTNat i (List.length.{u2} α x)) (hy : LT.lt.{0} Nat instLTNat i (List.length.{u1} β y)), R (List.nthLe.{u2} α x i hx) (List.nthLe.{u1} β y i hy))
Case conversion may be inaccurate. Consider using '#align list.forall₂.nth_le List.Forall₂.nthLeₓ'. -/
theorem Forall₂.nthLe :
    ∀ {x : List α} {y : List β} (h : Forall₂ R x y) ⦃i : ℕ⦄ (hx : i < x.length) (hy : i < y.length),
      R (x.nthLe i hx) (y.nthLe i hy)
  | a₁ :: l₁, a₂ :: l₂, forall₂.cons ha hl, 0, hx, hy => ha
  | a₁ :: l₁, a₂ :: l₂, forall₂.cons ha hl, succ i, hx, hy => hl.nthLe _ _
#align list.forall₂.nth_le List.Forall₂.nthLe

/- warning: list.forall₂_of_length_eq_of_nth_le -> List.forall₂_of_length_eq_of_nthLe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {x : List.{u1} α} {y : List.{u2} β}, (Eq.{1} Nat (List.length.{u1} α x) (List.length.{u2} β y)) -> (forall (i : Nat) (h₁ : LT.lt.{0} Nat Nat.hasLt i (List.length.{u1} α x)) (h₂ : LT.lt.{0} Nat Nat.hasLt i (List.length.{u2} β y)), R (List.nthLe.{u1} α x i h₁) (List.nthLe.{u2} β y i h₂)) -> (List.Forall₂.{u1, u2} α β R x y)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {x : List.{u2} α} {y : List.{u1} β}, (Eq.{1} Nat (List.length.{u2} α x) (List.length.{u1} β y)) -> (forall (i : Nat) (h₁ : LT.lt.{0} Nat instLTNat i (List.length.{u2} α x)) (h₂ : LT.lt.{0} Nat instLTNat i (List.length.{u1} β y)), R (List.nthLe.{u2} α x i h₁) (List.nthLe.{u1} β y i h₂)) -> (List.Forall₂.{u2, u1} α β R x y)
Case conversion may be inaccurate. Consider using '#align list.forall₂_of_length_eq_of_nth_le List.forall₂_of_length_eq_of_nthLeₓ'. -/
theorem forall₂_of_length_eq_of_nthLe :
    ∀ {x : List α} {y : List β},
      x.length = y.length → (∀ i h₁ h₂, R (x.nthLe i h₁) (y.nthLe i h₂)) → Forall₂ R x y
  | [], [], hl, h => Forall₂.nil
  | a₁ :: l₁, a₂ :: l₂, hl, h =>
    Forall₂.cons (h 0 (Nat.zero_lt_succ _) (Nat.zero_lt_succ _))
      (forall₂_of_length_eq_of_nth_le (succ.inj hl) fun i h₁ h₂ =>
        h i.succ (succ_lt_succ h₁) (succ_lt_succ h₂))
#align list.forall₂_of_length_eq_of_nth_le List.forall₂_of_length_eq_of_nthLe

/- warning: list.forall₂_iff_nth_le -> List.forall₂_iff_nthLe is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, Iff (List.Forall₂.{u1, u2} α β R l₁ l₂) (And (Eq.{1} Nat (List.length.{u1} α l₁) (List.length.{u2} β l₂)) (forall (i : Nat) (h₁ : LT.lt.{0} Nat Nat.hasLt i (List.length.{u1} α l₁)) (h₂ : LT.lt.{0} Nat Nat.hasLt i (List.length.{u2} β l₂)), R (List.nthLe.{u1} α l₁ i h₁) (List.nthLe.{u2} β l₂ i h₂)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, Iff (List.Forall₂.{u2, u1} α β R l₁ l₂) (And (Eq.{1} Nat (List.length.{u2} α l₁) (List.length.{u1} β l₂)) (forall (i : Nat) (h₁ : LT.lt.{0} Nat instLTNat i (List.length.{u2} α l₁)) (h₂ : LT.lt.{0} Nat instLTNat i (List.length.{u1} β l₂)), R (List.nthLe.{u2} α l₁ i h₁) (List.nthLe.{u1} β l₂ i h₂)))
Case conversion may be inaccurate. Consider using '#align list.forall₂_iff_nth_le List.forall₂_iff_nthLeₓ'. -/
theorem forall₂_iff_nthLe {l₁ : List α} {l₂ : List β} :
    Forall₂ R l₁ l₂ ↔ l₁.length = l₂.length ∧ ∀ i h₁ h₂, R (l₁.nthLe i h₁) (l₂.nthLe i h₂) :=
  ⟨fun h => ⟨h.length_eq, h.nthLe⟩, And.ndrec forall₂_of_length_eq_of_nthLe⟩
#align list.forall₂_iff_nth_le List.forall₂_iff_nthLe

/- warning: list.forall₂_zip -> List.forall₂_zip is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (forall {a : α} {b : β}, (Membership.Mem.{max u1 u2, max u1 u2} (Prod.{u1, u2} α β) (List.{max u1 u2} (Prod.{u1, u2} α β)) (List.hasMem.{max u1 u2} (Prod.{u1, u2} α β)) (Prod.mk.{u1, u2} α β a b) (List.zip.{u1, u2} α β l₁ l₂)) -> (R a b))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (forall {a : α} {b : β}, (Membership.mem.{max u1 u2, max u1 u2} (Prod.{u2, u1} α β) (List.{max u1 u2} (Prod.{u2, u1} α β)) (List.instMembershipList.{max u1 u2} (Prod.{u2, u1} α β)) (Prod.mk.{u2, u1} α β a b) (List.zip.{u2, u1} α β l₁ l₂)) -> (R a b))
Case conversion may be inaccurate. Consider using '#align list.forall₂_zip List.forall₂_zipₓ'. -/
theorem forall₂_zip : ∀ {l₁ l₂}, Forall₂ R l₁ l₂ → ∀ {a b}, (a, b) ∈ zip l₁ l₂ → R a b
  | _, _, forall₂.cons h₁ h₂, x, y, Or.inl rfl => h₁
  | _, _, forall₂.cons h₁ h₂, x, y, Or.inr h₃ => forall₂_zip h₂ h₃
#align list.forall₂_zip List.forall₂_zip

/- warning: list.forall₂_iff_zip -> List.forall₂_iff_zip is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, Iff (List.Forall₂.{u1, u2} α β R l₁ l₂) (And (Eq.{1} Nat (List.length.{u1} α l₁) (List.length.{u2} β l₂)) (forall {a : α} {b : β}, (Membership.Mem.{max u1 u2, max u1 u2} (Prod.{u1, u2} α β) (List.{max u1 u2} (Prod.{u1, u2} α β)) (List.hasMem.{max u1 u2} (Prod.{u1, u2} α β)) (Prod.mk.{u1, u2} α β a b) (List.zip.{u1, u2} α β l₁ l₂)) -> (R a b)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, Iff (List.Forall₂.{u2, u1} α β R l₁ l₂) (And (Eq.{1} Nat (List.length.{u2} α l₁) (List.length.{u1} β l₂)) (forall {a : α} {b : β}, (Membership.mem.{max u1 u2, max u1 u2} (Prod.{u2, u1} α β) (List.{max u1 u2} (Prod.{u2, u1} α β)) (List.instMembershipList.{max u1 u2} (Prod.{u2, u1} α β)) (Prod.mk.{u2, u1} α β a b) (List.zip.{u2, u1} α β l₁ l₂)) -> (R a b)))
Case conversion may be inaccurate. Consider using '#align list.forall₂_iff_zip List.forall₂_iff_zipₓ'. -/
theorem forall₂_iff_zip {l₁ l₂} :
    Forall₂ R l₁ l₂ ↔ length l₁ = length l₂ ∧ ∀ {a b}, (a, b) ∈ zip l₁ l₂ → R a b :=
  ⟨fun h => ⟨Forall₂.length_eq h, @forall₂_zip _ _ _ _ _ h⟩, fun h =>
    by
    cases' h with h₁ h₂
    induction' l₁ with a l₁ IH generalizing l₂
    · cases length_eq_zero.1 h₁.symm
      constructor
    · cases' l₂ with b l₂ <;> injection h₁ with h₁
      exact forall₂.cons (h₂ <| Or.inl rfl) (IH h₁ fun a b h => h₂ <| Or.inr h)⟩
#align list.forall₂_iff_zip List.forall₂_iff_zip

/- warning: list.forall₂_take -> List.forall₂_take is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} (n : Nat) {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (List.Forall₂.{u1, u2} α β R (List.take.{u1} α n l₁) (List.take.{u2} β n l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} (n : Nat) {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (List.Forall₂.{u2, u1} α β R (List.take.{u2} α n l₁) (List.take.{u1} β n l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂_take List.forall₂_takeₓ'. -/
theorem forall₂_take : ∀ (n) {l₁ l₂}, Forall₂ R l₁ l₂ → Forall₂ R (take n l₁) (take n l₂)
  | 0, _, _, _ => by simp only [forall₂.nil, take]
  | n + 1, _, _, forall₂.nil => by simp only [forall₂.nil, take]
  | n + 1, _, _, forall₂.cons h₁ h₂ => by simp [And.intro h₁ h₂, forall₂_take n]
#align list.forall₂_take List.forall₂_take

/- warning: list.forall₂_drop -> List.forall₂_drop is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} (n : Nat) {l₁ : List.{u1} α} {l₂ : List.{u2} β}, (List.Forall₂.{u1, u2} α β R l₁ l₂) -> (List.Forall₂.{u1, u2} α β R (List.drop.{u1} α n l₁) (List.drop.{u2} β n l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} (n : Nat) {l₁ : List.{u2} α} {l₂ : List.{u1} β}, (List.Forall₂.{u2, u1} α β R l₁ l₂) -> (List.Forall₂.{u2, u1} α β R (List.drop.{u2} α n l₁) (List.drop.{u1} β n l₂))
Case conversion may be inaccurate. Consider using '#align list.forall₂_drop List.forall₂_dropₓ'. -/
theorem forall₂_drop : ∀ (n) {l₁ l₂}, Forall₂ R l₁ l₂ → Forall₂ R (drop n l₁) (drop n l₂)
  | 0, _, _, h => by simp only [drop, h]
  | n + 1, _, _, forall₂.nil => by simp only [forall₂.nil, drop]
  | n + 1, _, _, forall₂.cons h₁ h₂ => by simp [And.intro h₁ h₂, forall₂_drop n]
#align list.forall₂_drop List.forall₂_drop

/- warning: list.forall₂_take_append -> List.forall₂_take_append is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} (l : List.{u1} α) (l₁ : List.{u2} β) (l₂ : List.{u2} β), (List.Forall₂.{u1, u2} α β R l (Append.append.{u2} (List.{u2} β) (List.hasAppend.{u2} β) l₁ l₂)) -> (List.Forall₂.{u1, u2} α β R (List.take.{u1} α (List.length.{u2} β l₁) l) l₁)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} (l : List.{u2} α) (l₁ : List.{u1} β) (l₂ : List.{u1} β), (List.Forall₂.{u2, u1} α β R l (HAppend.hAppend.{u1, u1, u1} (List.{u1} β) (List.{u1} β) (List.{u1} β) (instHAppend.{u1} (List.{u1} β) (List.instAppendList.{u1} β)) l₁ l₂)) -> (List.Forall₂.{u2, u1} α β R (List.take.{u2} α (List.length.{u1} β l₁) l) l₁)
Case conversion may be inaccurate. Consider using '#align list.forall₂_take_append List.forall₂_take_appendₓ'. -/
theorem forall₂_take_append (l : List α) (l₁ : List β) (l₂ : List β) (h : Forall₂ R l (l₁ ++ l₂)) :
    Forall₂ R (List.take (length l₁) l) l₁ :=
  by
  have h' : Forall₂ R (take (length l₁) l) (take (length l₁) (l₁ ++ l₂)) :=
    forall₂_take (length l₁) h
  rwa [take_left] at h'
#align list.forall₂_take_append List.forall₂_take_append

/- warning: list.forall₂_drop_append -> List.forall₂_drop_append is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} (l : List.{u1} α) (l₁ : List.{u2} β) (l₂ : List.{u2} β), (List.Forall₂.{u1, u2} α β R l (Append.append.{u2} (List.{u2} β) (List.hasAppend.{u2} β) l₁ l₂)) -> (List.Forall₂.{u1, u2} α β R (List.drop.{u1} α (List.length.{u2} β l₁) l) l₂)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} (l : List.{u2} α) (l₁ : List.{u1} β) (l₂ : List.{u1} β), (List.Forall₂.{u2, u1} α β R l (HAppend.hAppend.{u1, u1, u1} (List.{u1} β) (List.{u1} β) (List.{u1} β) (instHAppend.{u1} (List.{u1} β) (List.instAppendList.{u1} β)) l₁ l₂)) -> (List.Forall₂.{u2, u1} α β R (List.drop.{u2} α (List.length.{u1} β l₁) l) l₂)
Case conversion may be inaccurate. Consider using '#align list.forall₂_drop_append List.forall₂_drop_appendₓ'. -/
theorem forall₂_drop_append (l : List α) (l₁ : List β) (l₂ : List β) (h : Forall₂ R l (l₁ ++ l₂)) :
    Forall₂ R (List.drop (length l₁) l) l₂ :=
  by
  have h' : Forall₂ R (drop (length l₁) l) (drop (length l₁) (l₁ ++ l₂)) :=
    forall₂_drop (length l₁) h
  rwa [drop_left] at h'
#align list.forall₂_drop_append List.forall₂_drop_append

/- warning: list.rel_mem -> List.rel_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, (Relator.BiUnique.{u1, u2} α β R) -> (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} α β ((List.{u1} α) -> Prop) ((List.{u2} β) -> Prop) R (Relator.LiftFun.{succ u1, succ u2, 1, 1} (List.{u1} α) (List.{u2} β) Prop Prop (List.Forall₂.{u1, u2} α β R) Iff) (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α)) (Membership.Mem.{u2, u2} β (List.{u2} β) (List.hasMem.{u2} β)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, (Relator.BiUnique.{u2, u1} α β R) -> (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} α β ((List.{u2} α) -> Prop) ((List.{u1} β) -> Prop) R (Relator.LiftFun.{succ u2, succ u1, 1, 1} (List.{u2} α) (List.{u1} β) Prop Prop (List.Forall₂.{u2, u1} α β R) Iff) (fun (x._@.Mathlib.Data.List.Forall2._hyg.5023 : α) (x._@.Mathlib.Data.List.Forall2._hyg.5025 : List.{u2} α) => Membership.mem.{u2, u2} α (List.{u2} α) (List.instMembershipList.{u2} α) x._@.Mathlib.Data.List.Forall2._hyg.5023 x._@.Mathlib.Data.List.Forall2._hyg.5025) (fun (x._@.Mathlib.Data.List.Forall2._hyg.5038 : β) (x._@.Mathlib.Data.List.Forall2._hyg.5040 : List.{u1} β) => Membership.mem.{u1, u1} β (List.{u1} β) (List.instMembershipList.{u1} β) x._@.Mathlib.Data.List.Forall2._hyg.5038 x._@.Mathlib.Data.List.Forall2._hyg.5040))
Case conversion may be inaccurate. Consider using '#align list.rel_mem List.rel_memₓ'. -/
theorem rel_mem (hr : BiUnique R) : (R ⇒ Forall₂ R ⇒ Iff) (· ∈ ·) (· ∈ ·)
  | a, b, h, [], [], forall₂.nil => by simp only [not_mem_nil]
  | a, b, h, a' :: as, b' :: bs, forall₂.cons h₁ h₂ => rel_or (rel_eq hr h h₁) (rel_mem h h₂)
#align list.rel_mem List.rel_mem

/- warning: list.rel_map -> List.rel_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u1) (succ u3), max (succ u2) (succ u4), max (succ u1) (succ u3), max (succ u2) (succ u4)} (α -> γ) (β -> δ) ((List.{u1} α) -> (List.{u3} γ)) ((List.{u2} β) -> (List.{u4} δ)) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} α β γ δ R P) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} (List.{u1} α) (List.{u2} β) (List.{u3} γ) (List.{u4} δ) (List.Forall₂.{u1, u2} α β R) (List.Forall₂.{u3, u4} γ δ P)) (List.map.{u1, u3} α γ) (List.map.{u2, u4} β δ)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u4) (succ u3), max (succ u2) (succ u1), max (succ u4) (succ u3), max (succ u2) (succ u1)} (α -> γ) (β -> δ) ((List.{u4} α) -> (List.{u3} γ)) ((List.{u2} β) -> (List.{u1} δ)) (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} α β γ δ R P) (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} (List.{u4} α) (List.{u2} β) (List.{u3} γ) (List.{u1} δ) (List.Forall₂.{u4, u2} α β R) (List.Forall₂.{u3, u1} γ δ P)) (List.map.{u4, u3} α γ) (List.map.{u2, u1} β δ)
Case conversion may be inaccurate. Consider using '#align list.rel_map List.rel_mapₓ'. -/
theorem rel_map : ((R ⇒ P) ⇒ Forall₂ R ⇒ Forall₂ P) map map
  | f, g, h, [], [], forall₂.nil => Forall₂.nil
  | f, g, h, a :: as, b :: bs, forall₂.cons h₁ h₂ => Forall₂.cons (h h₁) (rel_map (@h) h₂)
#align list.rel_map List.rel_map

/- warning: list.rel_append -> List.rel_append is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} α) (List.{u2} β) ((List.{u1} α) -> (List.{u1} α)) ((List.{u2} β) -> (List.{u2} β)) (List.Forall₂.{u1, u2} α β R) (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} α) (List.{u2} β) (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R) (List.Forall₂.{u1, u2} α β R)) (Append.append.{u1} (List.{u1} α) (List.hasAppend.{u1} α)) (Append.append.{u2} (List.{u2} β) (List.hasAppend.{u2} β))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} α) (List.{u1} β) ((List.{u2} α) -> (List.{u2} α)) ((List.{u1} β) -> (List.{u1} β)) (List.Forall₂.{u2, u1} α β R) (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} α) (List.{u1} β) (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R) (List.Forall₂.{u2, u1} α β R)) (fun (x._@.Mathlib.Data.List.Forall2._hyg.5724 : List.{u2} α) (x._@.Mathlib.Data.List.Forall2._hyg.5726 : List.{u2} α) => HAppend.hAppend.{u2, u2, u2} (List.{u2} α) (List.{u2} α) (List.{u2} α) (instHAppend.{u2} (List.{u2} α) (List.instAppendList.{u2} α)) x._@.Mathlib.Data.List.Forall2._hyg.5724 x._@.Mathlib.Data.List.Forall2._hyg.5726) (fun (x._@.Mathlib.Data.List.Forall2._hyg.5739 : List.{u1} β) (x._@.Mathlib.Data.List.Forall2._hyg.5741 : List.{u1} β) => HAppend.hAppend.{u1, u1, u1} (List.{u1} β) (List.{u1} β) (List.{u1} β) (instHAppend.{u1} (List.{u1} β) (List.instAppendList.{u1} β)) x._@.Mathlib.Data.List.Forall2._hyg.5739 x._@.Mathlib.Data.List.Forall2._hyg.5741)
Case conversion may be inaccurate. Consider using '#align list.rel_append List.rel_appendₓ'. -/
theorem rel_append : (Forall₂ R ⇒ Forall₂ R ⇒ Forall₂ R) append append
  | [], [], h, l₁, l₂, hl => hl
  | a :: as, b :: bs, forall₂.cons h₁ h₂, l₁, l₂, hl => Forall₂.cons h₁ (rel_append h₂ hl)
#align list.rel_append List.rel_append

/- warning: list.rel_reverse -> List.rel_reverse is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} α) (List.{u2} β) (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R) (List.Forall₂.{u1, u2} α β R) (List.reverse.{u1} α) (List.reverse.{u2} β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} α) (List.{u1} β) (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R) (List.Forall₂.{u2, u1} α β R) (List.reverse.{u2} α) (List.reverse.{u1} β)
Case conversion may be inaccurate. Consider using '#align list.rel_reverse List.rel_reverseₓ'. -/
theorem rel_reverse : (Forall₂ R ⇒ Forall₂ R) reverse reverse
  | [], [], forall₂.nil => Forall₂.nil
  | a :: as, b :: bs, forall₂.cons h₁ h₂ =>
    by
    simp only [reverse_cons]
    exact rel_append (rel_reverse h₂) (forall₂.cons h₁ forall₂.nil)
#align list.rel_reverse List.rel_reverse

/- warning: list.forall₂_reverse_iff -> List.forall₂_reverse_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, Iff (List.Forall₂.{u1, u2} α β R (List.reverse.{u1} α l₁) (List.reverse.{u2} β l₂)) (List.Forall₂.{u1, u2} α β R l₁ l₂)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, Iff (List.Forall₂.{u2, u1} α β R (List.reverse.{u2} α l₁) (List.reverse.{u1} β l₂)) (List.Forall₂.{u2, u1} α β R l₁ l₂)
Case conversion may be inaccurate. Consider using '#align list.forall₂_reverse_iff List.forall₂_reverse_iffₓ'. -/
@[simp]
theorem forall₂_reverse_iff {l₁ l₂} : Forall₂ R (reverse l₁) (reverse l₂) ↔ Forall₂ R l₁ l₂ :=
  Iff.intro
    (fun h => by
      rw [← reverse_reverse l₁, ← reverse_reverse l₂]
      exact rel_reverse h)
    fun h => rel_reverse h
#align list.forall₂_reverse_iff List.forall₂_reverse_iff

/- warning: list.rel_join -> List.rel_join is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} (List.{u1} α)) (List.{u2} (List.{u2} β)) (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R)) (List.Forall₂.{u1, u2} α β R) (List.join.{u1} α) (List.join.{u2} β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop}, Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} (List.{u2} α)) (List.{u1} (List.{u1} β)) (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R)) (List.Forall₂.{u2, u1} α β R) (List.join.{u2} α) (List.join.{u1} β)
Case conversion may be inaccurate. Consider using '#align list.rel_join List.rel_joinₓ'. -/
theorem rel_join : (Forall₂ (Forall₂ R) ⇒ Forall₂ R) join join
  | [], [], forall₂.nil => Forall₂.nil
  | a :: as, b :: bs, forall₂.cons h₁ h₂ => rel_append h₁ (rel_join h₂)
#align list.rel_join List.rel_join

/- warning: list.rel_bind -> List.rel_bind is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{succ u1, succ u2, max (succ u1) (succ u3), max (succ u2) (succ u4)} (List.{u1} α) (List.{u2} β) ((α -> (List.{u3} γ)) -> (List.{u3} γ)) ((β -> (List.{u4} δ)) -> (List.{u4} δ)) (List.Forall₂.{u1, u2} α β R) (Relator.LiftFun.{max (succ u1) (succ u3), max (succ u2) (succ u4), succ u3, succ u4} (α -> (List.{u3} γ)) (β -> (List.{u4} δ)) (List.{u3} γ) (List.{u4} δ) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} α β (List.{u3} γ) (List.{u4} δ) R (List.Forall₂.{u3, u4} γ δ P)) (List.Forall₂.{u3, u4} γ δ P)) (List.bind.{u1, u3} α γ) (List.bind.{u2, u4} β δ)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u2}} {δ : Type.{u1}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{succ u4, succ u3, max (succ u4) (succ u2), max (succ u3) (succ u1)} (List.{u4} α) (List.{u3} β) ((α -> (List.{u2} γ)) -> (List.{u2} γ)) ((β -> (List.{u1} δ)) -> (List.{u1} δ)) (List.Forall₂.{u4, u3} α β R) (Relator.LiftFun.{max (succ u4) (succ u2), max (succ u3) (succ u1), succ u2, succ u1} (α -> (List.{u2} γ)) (β -> (List.{u1} δ)) (List.{u2} γ) (List.{u1} δ) (Relator.LiftFun.{succ u4, succ u3, succ u2, succ u1} α β (List.{u2} γ) (List.{u1} δ) R (List.Forall₂.{u2, u1} γ δ P)) (List.Forall₂.{u2, u1} γ δ P)) (List.bind.{u4, u2} α γ) (List.bind.{u3, u1} β δ)
Case conversion may be inaccurate. Consider using '#align list.rel_bind List.rel_bindₓ'. -/
theorem rel_bind : (Forall₂ R ⇒ (R ⇒ Forall₂ P) ⇒ Forall₂ P) List.bind List.bind :=
  fun a b h₁ f g h₂ => rel_join (rel_map (@h₂) h₁)
#align list.rel_bind List.rel_bind

/- warning: list.rel_foldl -> List.rel_foldl is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u1) (succ u3), max (succ u2) (succ u4), max (succ u1) (succ u3), max (succ u2) (succ u4)} (γ -> α -> γ) (δ -> β -> δ) (γ -> (List.{u1} α) -> γ) (δ -> (List.{u2} β) -> δ) (Relator.LiftFun.{succ u3, succ u4, max (succ u1) (succ u3), max (succ u2) (succ u4)} γ δ (α -> γ) (β -> δ) P (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} α β γ δ R P)) (Relator.LiftFun.{succ u3, succ u4, max (succ u1) (succ u3), max (succ u2) (succ u4)} γ δ ((List.{u1} α) -> γ) ((List.{u2} β) -> δ) P (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} (List.{u1} α) (List.{u2} β) γ δ (List.Forall₂.{u1, u2} α β R) P)) (List.foldl.{u3, u1} γ α) (List.foldl.{u4, u2} δ β)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u4) (succ u3), max (succ u2) (succ u1), max (succ u4) (succ u3), max (succ u2) (succ u1)} (γ -> α -> γ) (δ -> β -> δ) (γ -> (List.{u4} α) -> γ) (δ -> (List.{u2} β) -> δ) (Relator.LiftFun.{succ u3, succ u1, max (succ u4) (succ u3), max (succ u2) (succ u1)} γ δ (α -> γ) (β -> δ) P (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} α β γ δ R P)) (Relator.LiftFun.{succ u3, succ u1, max (succ u4) (succ u3), max (succ u2) (succ u1)} γ δ ((List.{u4} α) -> γ) ((List.{u2} β) -> δ) P (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} (List.{u4} α) (List.{u2} β) γ δ (List.Forall₂.{u4, u2} α β R) P)) (List.foldl.{u3, u4} γ α) (List.foldl.{u1, u2} δ β)
Case conversion may be inaccurate. Consider using '#align list.rel_foldl List.rel_foldlₓ'. -/
theorem rel_foldl : ((P ⇒ R ⇒ P) ⇒ P ⇒ Forall₂ R ⇒ P) foldl foldl
  | f, g, hfg, _, _, h, _, _, forall₂.nil => h
  | f, g, hfg, x, y, hxy, _, _, forall₂.cons hab hs => rel_foldl (@hfg) (hfg hxy hab) hs
#align list.rel_foldl List.rel_foldl

/- warning: list.rel_foldr -> List.rel_foldr is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u1) (succ u3), max (succ u2) (succ u4), max (succ u1) (succ u3), max (succ u2) (succ u4)} (α -> γ -> γ) (β -> δ -> δ) (γ -> (List.{u1} α) -> γ) (δ -> (List.{u2} β) -> δ) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} α β (γ -> γ) (δ -> δ) R (Relator.LiftFun.{succ u3, succ u4, succ u3, succ u4} γ δ γ δ P P)) (Relator.LiftFun.{succ u3, succ u4, max (succ u1) (succ u3), max (succ u2) (succ u4)} γ δ ((List.{u1} α) -> γ) ((List.{u2} β) -> δ) P (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} (List.{u1} α) (List.{u2} β) γ δ (List.Forall₂.{u1, u2} α β R) P)) (List.foldr.{u1, u3} α γ) (List.foldr.{u2, u4} β δ)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u4) (succ u3), max (succ u2) (succ u1), max (succ u4) (succ u3), max (succ u2) (succ u1)} (α -> γ -> γ) (β -> δ -> δ) (γ -> (List.{u4} α) -> γ) (δ -> (List.{u2} β) -> δ) (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} α β (γ -> γ) (δ -> δ) R (Relator.LiftFun.{succ u3, succ u1, succ u3, succ u1} γ δ γ δ P P)) (Relator.LiftFun.{succ u3, succ u1, max (succ u4) (succ u3), max (succ u2) (succ u1)} γ δ ((List.{u4} α) -> γ) ((List.{u2} β) -> δ) P (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} (List.{u4} α) (List.{u2} β) γ δ (List.Forall₂.{u4, u2} α β R) P)) (List.foldr.{u4, u3} α γ) (List.foldr.{u2, u1} β δ)
Case conversion may be inaccurate. Consider using '#align list.rel_foldr List.rel_foldrₓ'. -/
theorem rel_foldr : ((R ⇒ P ⇒ P) ⇒ P ⇒ Forall₂ R ⇒ P) foldr foldr
  | f, g, hfg, _, _, h, _, _, forall₂.nil => h
  | f, g, hfg, x, y, hxy, _, _, forall₂.cons hab hs => hfg hab (rel_foldr (@hfg) hxy hs)
#align list.rel_foldr List.rel_foldr

/- warning: list.rel_filter -> List.rel_filter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {p : α -> Prop} {q : β -> Prop} [_inst_1 : DecidablePred.{succ u1} α p] [_inst_2 : DecidablePred.{succ u2} β q], (Relator.LiftFun.{succ u1, succ u2, 1, 1} α β Prop Prop R Iff p q) -> (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} α) (List.{u2} β) (List.{u1} α) (List.{u2} β) (List.Forall₂.{u1, u2} α β R) (List.Forall₂.{u1, u2} α β R) (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a)) (List.filterₓ.{u2} β q (fun (a : β) => _inst_2 a)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {p : α -> Bool} {q : β -> Bool}, (Relator.LiftFun.{succ u2, succ u1, 1, 1} α β Prop Prop R (fun (x._@.Mathlib.Data.List.Forall2._hyg.7252 : Prop) (x._@.Mathlib.Data.List.Forall2._hyg.7254 : Prop) => Iff x._@.Mathlib.Data.List.Forall2._hyg.7252 x._@.Mathlib.Data.List.Forall2._hyg.7254) (fun (x : α) => Eq.{1} Bool (p x) Bool.true) (fun (x : β) => Eq.{1} Bool (q x) Bool.true)) -> (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} α) (List.{u1} β) (List.{u2} α) (List.{u1} β) (List.Forall₂.{u2, u1} α β R) (List.Forall₂.{u2, u1} α β R) (List.filter.{u2} α p) (List.filter.{u1} β q))
Case conversion may be inaccurate. Consider using '#align list.rel_filter List.rel_filterₓ'. -/
theorem rel_filter {p : α → Prop} {q : β → Prop} [DecidablePred p] [DecidablePred q]
    (hpq : (R ⇒ (· ↔ ·)) p q) : (Forall₂ R ⇒ Forall₂ R) (filter p) (filter q)
  | _, _, forall₂.nil => Forall₂.nil
  | a :: as, b :: bs, forall₂.cons h₁ h₂ => by
    by_cases p a
    · have : q b := by rwa [← hpq h₁]
      simp only [filter_cons_of_pos _ h, filter_cons_of_pos _ this, forall₂_cons, h₁, rel_filter h₂,
        and_true_iff]
    · have : ¬q b := by rwa [← hpq h₁]
      simp only [filter_cons_of_neg _ h, filter_cons_of_neg _ this, rel_filter h₂]
#align list.rel_filter List.rel_filter

/- warning: list.rel_filter_map -> List.rel_filterMap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u1) (succ u3), max (succ u2) (succ u4), max (succ u1) (succ u3), max (succ u2) (succ u4)} (α -> (Option.{u3} γ)) (β -> (Option.{u4} δ)) ((List.{u1} α) -> (List.{u3} γ)) ((List.{u2} β) -> (List.{u4} δ)) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} α β (Option.{u3} γ) (Option.{u4} δ) R (Option.Rel.{u3, u4} γ δ P)) (Relator.LiftFun.{succ u1, succ u2, succ u3, succ u4} (List.{u1} α) (List.{u2} β) (List.{u3} γ) (List.{u4} δ) (List.Forall₂.{u1, u2} α β R) (List.Forall₂.{u3, u4} γ δ P)) (List.filterMap.{u1, u3} α γ) (List.filterMap.{u2, u4} β δ)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {R : α -> β -> Prop} {P : γ -> δ -> Prop}, Relator.LiftFun.{max (succ u4) (succ u3), max (succ u2) (succ u1), max (succ u4) (succ u3), max (succ u2) (succ u1)} (α -> (Option.{u3} γ)) (β -> (Option.{u1} δ)) ((List.{u4} α) -> (List.{u3} γ)) ((List.{u2} β) -> (List.{u1} δ)) (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} α β (Option.{u3} γ) (Option.{u1} δ) R (Option.Rel.{u3, u1} γ δ P)) (Relator.LiftFun.{succ u4, succ u2, succ u3, succ u1} (List.{u4} α) (List.{u2} β) (List.{u3} γ) (List.{u1} δ) (List.Forall₂.{u4, u2} α β R) (List.Forall₂.{u3, u1} γ δ P)) (List.filterMap.{u4, u3} α γ) (List.filterMap.{u2, u1} β δ)
Case conversion may be inaccurate. Consider using '#align list.rel_filter_map List.rel_filterMapₓ'. -/
theorem rel_filterMap : ((R ⇒ Option.Rel P) ⇒ Forall₂ R ⇒ Forall₂ P) filterMap filterMap
  | f, g, hfg, _, _, forall₂.nil => Forall₂.nil
  | f, g, hfg, a :: as, b :: bs, forall₂.cons h₁ h₂ => by
    rw [filter_map_cons, filter_map_cons] <;>
      exact
        match f a, g b, hfg h₁ with
        | _, _, Option.Rel.none => rel_filter_map (@hfg) h₂
        | _, _, Option.Rel.some h => forall₂.cons h (rel_filter_map (@hfg) h₂)
#align list.rel_filter_map List.rel_filterMap

/- warning: list.rel_prod -> List.rel_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} [_inst_1 : Monoid.{u1} α] [_inst_2 : Monoid.{u2} β], (R (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))))) (OfNat.ofNat.{u2} β 1 (OfNat.mk.{u2} β 1 (One.one.{u2} β (MulOneClass.toHasOne.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2)))))) -> (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} α β (α -> α) (β -> β) R (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} α β α β R R) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)))) (HMul.hMul.{u2, u2, u2} β β β (instHMul.{u2} β (MulOneClass.toHasMul.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2))))) -> (Relator.LiftFun.{succ u1, succ u2, succ u1, succ u2} (List.{u1} α) (List.{u2} β) α β (List.Forall₂.{u1, u2} α β R) R (List.prod.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)) (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) (List.prod.{u2} β (MulOneClass.toHasMul.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2)) (MulOneClass.toHasOne.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} [_inst_1 : Monoid.{u2} α] [_inst_2 : Monoid.{u1} β], (R (OfNat.ofNat.{u2} α 1 (One.toOfNat1.{u2} α (Monoid.toOne.{u2} α _inst_1))) (OfNat.ofNat.{u1} β 1 (One.toOfNat1.{u1} β (Monoid.toOne.{u1} β _inst_2)))) -> (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} α β (α -> α) (β -> β) R (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} α β α β R R) (fun (x._@.Mathlib.Data.List.Forall2._hyg.8113 : α) (x._@.Mathlib.Data.List.Forall2._hyg.8115 : α) => HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α _inst_1))) x._@.Mathlib.Data.List.Forall2._hyg.8113 x._@.Mathlib.Data.List.Forall2._hyg.8115) (fun (x._@.Mathlib.Data.List.Forall2._hyg.8128 : β) (x._@.Mathlib.Data.List.Forall2._hyg.8130 : β) => HMul.hMul.{u1, u1, u1} β β β (instHMul.{u1} β (MulOneClass.toMul.{u1} β (Monoid.toMulOneClass.{u1} β _inst_2))) x._@.Mathlib.Data.List.Forall2._hyg.8128 x._@.Mathlib.Data.List.Forall2._hyg.8130)) -> (Relator.LiftFun.{succ u2, succ u1, succ u2, succ u1} (List.{u2} α) (List.{u1} β) α β (List.Forall₂.{u2, u1} α β R) R (List.prod.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α _inst_1)) (Monoid.toOne.{u2} α _inst_1)) (List.prod.{u1} β (MulOneClass.toMul.{u1} β (Monoid.toMulOneClass.{u1} β _inst_2)) (Monoid.toOne.{u1} β _inst_2)))
Case conversion may be inaccurate. Consider using '#align list.rel_prod List.rel_prodₓ'. -/
@[to_additive]
theorem rel_prod [Monoid α] [Monoid β] (h : R 1 1) (hf : (R ⇒ R ⇒ R) (· * ·) (· * ·)) :
    (Forall₂ R ⇒ R) prod prod :=
  rel_foldl hf h
#align list.rel_prod List.rel_prod
#align list.rel_sum List.rel_sum

#print List.SublistForall₂ /-
/-- Given a relation `R`, `sublist_forall₂ r l₁ l₂` indicates that there is a sublist of `l₂` such
  that `forall₂ r l₁ l₂`. -/
inductive SublistForall₂ (R : α → β → Prop) : List α → List β → Prop
  | nil {l} : sublist_forall₂ [] l
  | cons {a₁ a₂ l₁ l₂} : R a₁ a₂ → sublist_forall₂ l₁ l₂ → sublist_forall₂ (a₁ :: l₁) (a₂ :: l₂)
  | cons_right {a l₁ l₂} : sublist_forall₂ l₁ l₂ → sublist_forall₂ l₁ (a :: l₂)
#align list.sublist_forall₂ List.SublistForall₂
-/

/- warning: list.sublist_forall₂_iff -> List.sublistForall₂_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {R : α -> β -> Prop} {l₁ : List.{u1} α} {l₂ : List.{u2} β}, Iff (List.SublistForall₂.{u1, u2} α β R l₁ l₂) (Exists.{succ u2} (List.{u2} β) (fun (l : List.{u2} β) => And (List.Forall₂.{u1, u2} α β R l₁ l) (List.Sublist.{u2} β l l₂)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {R : α -> β -> Prop} {l₁ : List.{u2} α} {l₂ : List.{u1} β}, Iff (List.SublistForall₂.{u2, u1} α β R l₁ l₂) (Exists.{succ u1} (List.{u1} β) (fun (l : List.{u1} β) => And (List.Forall₂.{u2, u1} α β R l₁ l) (List.Sublist.{u1} β l l₂)))
Case conversion may be inaccurate. Consider using '#align list.sublist_forall₂_iff List.sublistForall₂_iffₓ'. -/
theorem sublistForall₂_iff {l₁ : List α} {l₂ : List β} :
    SublistForall₂ R l₁ l₂ ↔ ∃ l, Forall₂ R l₁ l ∧ l <+ l₂ :=
  by
  constructor <;> intro h
  · induction' h with _ a b l1 l2 rab rll ih b l1 l2 hl ih
    · exact ⟨nil, forall₂.nil, nil_sublist _⟩
    · obtain ⟨l, hl1, hl2⟩ := ih
      refine' ⟨b :: l, forall₂.cons rab hl1, hl2.cons_cons b⟩
    · obtain ⟨l, hl1, hl2⟩ := ih
      exact ⟨l, hl1, hl2.trans (sublist.cons _ _ _ (sublist.refl _))⟩
  · obtain ⟨l, hl1, hl2⟩ := h
    revert l₁
    induction' hl2 with _ _ _ _ ih _ _ _ _ ih <;> intro l₁ hl1
    · rw [forall₂_nil_right_iff.1 hl1]
      exact sublist_forall₂.nil
    · exact sublist_forall₂.cons_right (ih hl1)
    · cases' hl1 with _ _ _ _ hr hl _
      exact sublist_forall₂.cons hr (ih hl)
#align list.sublist_forall₂_iff List.sublistForall₂_iff

#print List.SublistForall₂.is_refl /-
instance SublistForall₂.is_refl [IsRefl α Rₐ] : IsRefl (List α) (SublistForall₂ Rₐ) :=
  ⟨fun l => sublistForall₂_iff.2 ⟨l, forall₂_refl l, Sublist.refl l⟩⟩
#align list.sublist_forall₂.is_refl List.SublistForall₂.is_refl
-/

#print List.SublistForall₂.is_trans /-
instance SublistForall₂.is_trans [IsTrans α Rₐ] : IsTrans (List α) (SublistForall₂ Rₐ) :=
  ⟨fun a b c => by
    revert a b
    induction' c with _ _ ih
    · rintro _ _ h1 (_ | _ | _)
      exact h1
    · rintro a b h1 h2
      cases' h2 with _ _ _ _ _ hbc tbc _ _ y1 btc
      · cases h1
        exact sublist_forall₂.nil
      · cases' h1 with _ _ _ _ _ hab tab _ _ _ atb
        · exact sublist_forall₂.nil
        · exact sublist_forall₂.cons (trans hab hbc) (ih _ _ tab tbc)
        · exact sublist_forall₂.cons_right (ih _ _ atb tbc)
      · exact sublist_forall₂.cons_right (ih _ _ h1 btc)⟩
#align list.sublist_forall₂.is_trans List.SublistForall₂.is_trans
-/

#print List.Sublist.sublistForall₂ /-
theorem Sublist.sublistForall₂ {l₁ l₂ : List α} (h : l₁ <+ l₂) [IsRefl α Rₐ] :
    SublistForall₂ Rₐ l₁ l₂ :=
  sublistForall₂_iff.2 ⟨l₁, forall₂_refl l₁, h⟩
#align list.sublist.sublist_forall₂ List.Sublist.sublistForall₂
-/

#print List.tail_sublistForall₂_self /-
theorem tail_sublistForall₂_self [IsRefl α Rₐ] (l : List α) : SublistForall₂ Rₐ l.tail l :=
  l.tail_sublist.SublistForall₂
#align list.tail_sublist_forall₂_self List.tail_sublistForall₂_self
-/

end List


/- induction tactic -/

example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
  induction as with
  | nil => rfl
  | cons _ xs ih => simp [List.concat, ih]

example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
  induction as <;> simp! [*]


import classes.context_free.basics.toolbox
import utilities.list_utils
import utilities.written_by_others.trim_assoc

private def a_ : fin 3 := 0
private def a : symbol (fin 3) (fin 2) := symbol.terminal a_

private def b_ : fin 3 := 1
private def b : symbol (fin 3) (fin 2) := symbol.terminal b_

private def c_ : fin 3 := 2
private def c : symbol (fin 3) (fin 2) := symbol.terminal c_

private def S_ : fin 2 := 0
private def S : symbol (fin 3) (fin 2) := symbol.nonterminal S_

private def R_ : fin 2 := 1
private def R : symbol (fin 3) (fin 2) := symbol.nonterminal R_

private def gr_add : CF_grammar (fin 3) :=
CF_grammar.mk (fin 2) S_ [
  (S_, [a, S, c]),
  (S_, [R]),
  (R_, [b, R, c]),
  (R_, [])
]

example : CF_generates gr_add [a_, a_, b_, c_, c_, c_] :=
begin
  unfold gr_add,

  apply CF_deri_of_tran_deri,
  {
    use (S_, [a, S, c]),
    split_ile,
    use [[], []],
    simp,
  },

  apply CF_deri_of_tran_deri,
  {
    use (S_, [a, S, c]),
    split_ile,
    use [[a], [c]],
    rw S,
    simp,
  },

  apply CF_deri_of_tran_deri,
  {
    use (S_, [R]),
    split_ile,
    use [[a, a], [c, c]],
    simp,
  },

  apply CF_deri_of_tran_deri,
  {
    use (R_, [b, R, c]),
    split_ile,
    use [[a, a], [c, c]],
    rw R,
    simp,
  },

  apply CF_deri_of_tran,
  {
    use (R_, []),
    split_ile,
    use [[a, a, b], [c, c, c]],
    repeat { try {split}, try {refl} },
  },
end

private def anbmcnm (n m : ℕ) : list (fin 3) :=
list.repeat a_ n ++ list.repeat b_ m ++ list.repeat c_ (n + m)

private def language_add : language (fin 3) :=
λ x, ∃ n m : ℕ, x = anbmcnm n m

example : [a_, a_, b_, c_, c_, c_] ∈ language_add :=
begin
  use 2,
  use 1,
  refl,
end

example : CF_language gr_add = language_add :=
begin
  ext,

  split,
  {
    -- prove `x ∈ CF_language gr_add → x ∈ language_add` here
    intro ass,
    change CF_derives gr_add [S] (list.map symbol.terminal x) at ass,

    have possib : ∀ w : list (symbol (fin 3) gr_add.nt),
      CF_derives gr_add [S] w →
        (∃ i : ℕ, w = list.repeat a i ++ [S] ++ list.repeat c i) ∨
        (∃ i j : ℕ, w = list.repeat a i ++ list.repeat b j ++ [R] ++ list.repeat c (i + j)) ∨
        (∃ i j : ℕ, w = list.repeat a i ++ list.repeat b j ++ list.repeat c (i + j)),
    {
      intros w h,
      induction h with y z irr step ih,
      {
        left,
        use 0,
        refl,
      },
      cases step with rule foo,
      cases foo with rule_in bar,
      cases bar with u baz,
      cases baz with v hyp,
      cases hyp with hyp_bef hyp_aft,

      cases ih with case₁ caseᵣ,
      {
        -- the case `list.repeat a i ++ [S] ++ list.repeat c i` matches 1st and 2nd rule
        cases rule_in,
        {
          left,
          cases case₁ with i the_case,
          rw list.append_assoc at the_case,
          use i + 1,
          have almost : z = list.repeat a i ++ [a, S, c] ++ list.repeat c i,
          {
            rw hyp_bef at the_case,
            rw hyp_aft,
            rw rule_in at *,
            have u_must : u = list.repeat a i,
            {
              dsimp only at *,
              rw ←S at *,
              have indexS: (u ++ [S] ++ v).nth u.length = some S,
              {
                rw list.append_assoc,
                rw list.nth_append_right (le_of_eq rfl),
                rw tsub_self,
                refl,
              },
              cases @trichotomous ℕ (<) _ (list.length u) i with hlt hge,
              {
                exfalso,
                rw the_case at indexS,
                rw ←list.nth_take hlt at indexS,
                rw list.take_append_of_le_length (le_of_eq (list.length_repeat a i).symm) at indexS,
                rw list.take_repeat at indexS,
                rw min_self at indexS,
                rw ←list.length_repeat a i at hlt,
                have please : (list.repeat a i).nth_le u.length hlt = S,
                {
                  rw list.nth_le_nth hlt at indexS,
                  injection indexS,
                },
                rw list.nth_le_repeat a hlt at please,
                injection please,
              },
              cases hge.symm with hgt heq,
              {
                exfalso,
                rw the_case at indexS,
                have rightend : u.length < (list.repeat a i ++ [S] ++ list.repeat c i).length,
                {
                  have thelength := congr_arg list.length the_case,
                  rw list.append_assoc at thelength,
                  rw list.length_append at thelength,
                  rw list.append_assoc,
                  rw ←thelength,
                  rw list.length_append,
                  rw list.length_singleton,
                  rw ←add_assoc,
                  apply lt_of_lt_of_le,
                  {
                    exact lt_add_one u.length,
                  },
                  exact le_self_add,
                },
                rw ←list.append_assoc at indexS,
                rw list.nth_le_nth rightend at indexS,
                injection indexS with continue,
                have mala : (list.repeat a i ++ [S]).length ≤ u.length,
                {
                  rw list.length_append,
                  rw list.length_singleton,
                  rw list.length_repeat a i,
                  rw ←nat.succ_le_iff at hgt,
                  apply hgt,
                },
                rw list.nth_le_append_right mala at continue,
                finish,
              },
              rw list.append_assoc at the_case,
              apply list.append_inj_left the_case,
              rw heq,
              finish,
            },
            have v_must : v = list.repeat c i,
            {
              rw u_must at the_case,
              rw list.append_assoc at the_case,
              rw list.append_right_inj (list.repeat a i) at the_case,
              rw ←S at the_case,
              rw list.append_right_inj [S] at the_case,
              exact the_case,
            },
            rw u_must,
            rw v_must,
          },
          rw list.repeat_add,
          change z = list.repeat a i ++ [a] ++ [S] ++ list.repeat c (i + 1),
          rw add_comm,
          rw list.repeat_add,
          change z = list.repeat a i ++ [a] ++ [S] ++ ([c] ++ list.repeat c i),
          rw ←list.append_assoc,
          rw list.append_assoc (list.repeat a i) [a],
          rw list.append_assoc (list.repeat a i) ([a] ++ [S]),
          convert almost,
        },
        cases rule_in,
        {
          right,
          left,
          cases case₁ with i the_case,
          use i,
          use 0,
          simp,
          have u_must : u = list.repeat a i,
          {
            have indexS: (u ++ [S] ++ v).nth u.length = some S,
            {
              rw list.append_assoc,
              rw list.nth_append_right (le_of_eq rfl),
              rw tsub_self,
              refl,
            },
            cases @trichotomous ℕ (<) _ (list.length u) i with hlt hge,
            {
              exfalso,
              rw hyp_bef at the_case,
              rw rule_in at *,
              rw ←list.nth_take hlt at indexS,
              simp at the_case,
              change u ++ ([S] ++ v) = list.repeat a i ++ ([S] ++ list.repeat c i) at the_case,
              rw list.append_assoc at indexS,
              rw the_case at indexS,
              rw list.take_append_of_le_length (le_of_eq (list.length_repeat a i).symm) at indexS,
              rw list.take_repeat at indexS,
              rw min_self at indexS,
              rw ←list.length_repeat a i at hlt,
              have please : (list.repeat a i).nth_le u.length hlt = S,
              {
                rw list.nth_le_nth hlt at indexS,
                injection indexS,
              },
              rw list.nth_le_repeat a hlt at please,
              injection please,
            },
            cases hge.symm with hgt heq,
            {
              exfalso,
              rw list.append_assoc at the_case,
              rw hyp_bef at the_case,
              rw rule_in at *,
              change u ++ [S] ++ v = list.repeat a i ++ S :: list.repeat c i at the_case,
              rw the_case at indexS,
              have rightend : u.length < (list.repeat a i ++ [S] ++ list.repeat c i).length,
              {
                have thelength := congr_arg list.length the_case,
                rw list.append_assoc at thelength,
                rw list.length_append at thelength,
                rw list.append_assoc,
                change u.length < (list.repeat a i ++ S :: list.repeat c i).length,
                rw ←thelength,
                rw list.length_append,
                rw list.length_singleton,
                rw ←add_assoc,
                apply lt_of_lt_of_le,
                {
                  exact lt_add_one u.length,
                },
                exact le_self_add,
              },
              change (list.repeat a i ++ ([S] ++ list.repeat c i)).nth u.length = some S at indexS,
              rw ←list.append_assoc at indexS,
              rw list.nth_le_nth rightend at indexS,
              injection indexS with continue,
              have mala : (list.repeat a i ++ [S]).length ≤ u.length,
              {
                rw list.length_append,
                rw list.length_singleton,
                rw list.length_repeat a i,
                rw ←nat.succ_le_iff at hgt,
                apply hgt,
              },
              rw list.nth_le_append_right mala at continue,
              finish,
            },
            rw list.append_assoc at the_case,
            rw hyp_bef at the_case,
            rw list.append_assoc at the_case,
            apply list.append_inj_left the_case,
            rw heq,
            rw list.length_repeat a i,
          },
          have v_must : v = list.repeat c i,
          {
            rw list.append_assoc at the_case,
            rw hyp_bef at the_case,
            rw list.append_assoc at the_case,
            rw u_must at the_case,
            rw list.append_right_inj (list.repeat a i) at the_case,
            rw rule_in at the_case,
            change [S] ++ v = [S] ++ list.repeat c i at the_case,
            rw list.append_right_inj [S] at the_case,
            exact the_case,
          },
          rw hyp_aft,
          rw u_must,
          rw v_must,
          rw rule_in,
          simp,
        },
        cases rule_in,
        any_goals { try { cases rule_in },
          exfalso,
          rw rule_in at hyp_bef,
          simp at hyp_bef,
          cases case₁ with i the_case,
          rw the_case at hyp_bef,
          have contra := congr_arg (λ lis, R ∈ lis) hyp_bef,
          rw ←R at contra,
          simp at contra,
          cases contra,
          {
            rw list.mem_repeat at contra,
            have triv : R ≠ a,
            {
              apply symbol.no_confusion,
            },
            exact triv contra.right,
          },
          cases contra,
          {
            injection contra with contr,
            injection contr with cont,
            simp at cont,
            exact cont,
          },
          {
            rw list.mem_repeat at contra,
            have triv : R ≠ c,
            {
              apply symbol.no_confusion,
            },
            exact triv contra.right,
          },
        },
        exfalso,
        exact (list.mem_nil_iff rule).1 rule_in,
      },
      cases caseᵣ with case₂ case₃,
      {
        -- the case `list.repeat a i ++ list.repeat b j ++ [R] ++ list.repeat c (i + j)` matches 3rd and 4th rule
        cases rule_in,
        any_goals { try { cases rule_in },
          exfalso,
          rw rule_in at hyp_bef,
          simp at hyp_bef,
          cases case₂ with i foo,
          cases foo with j y_eq,
          rw y_eq at hyp_bef,
          have contra := congr_arg (λ lis, S ∈ lis) hyp_bef,
          rw ←S at contra,
          simp at contra,
          cases contra,
          {
            rw list.mem_repeat at contra,
            have triv : S ≠ a,
            {
              apply symbol.no_confusion,
            },
            exact triv contra.right,
          },
          cases contra,
          {
            rw list.mem_repeat at contra,
            have triv : S ≠ b,
            {
              apply symbol.no_confusion,
            },
            exact triv contra.right,
          },
          cases contra,
          {
            injection contra with contr,
            injection contr with cont,
            simp at cont,
            exact cont,
          },
          {
            rw list.mem_repeat at contra,
            have triv : S ≠ c,
            {
              apply symbol.no_confusion,
            },
            exact triv contra.right,
          },
        },

        cases rule_in,
        any_goals { try { cases rule_in },
          rw rule_in at *,
          simp at *,
          cases case₂ with i foo,
          cases foo with j y_form,
          rw y_form at hyp_bef,

          have indexR: (u ++ [R] ++ v).nth u.length = some R,
          {
            rw list.append_assoc,
            rw list.nth_append_right (le_of_eq rfl),
            rw tsub_self,
            refl,
          },
          have u_eq : u = list.repeat a i ++ list.repeat b j,
          {
            cases @trichotomous ℕ (<) _ u.length (i + j) with hlt rest,
            {
              exfalso,
              rw ←list.nth_take hlt at indexR,
              have h_len : u.length < (list.repeat a i ++ list.repeat b j).length,
              {
                rw list.length_append,
                rw list.length_repeat,
                rw list.length_repeat,
                exact hlt,
              },
              have propos : (list.repeat a i ++ list.repeat b j).nth_le u.length h_len = R,
              {
                change list.repeat a i ++ (list.repeat b j ++ R :: list.repeat c (i + j)) = u ++ ([R] ++ v) at hyp_bef,
                rw ←list.append_assoc u [R] v at hyp_bef,
                rw ←hyp_bef at indexR,
                rw ←list.append_assoc at indexR,
                have take_beg :
                  list.take (i + j) (list.repeat a i ++ list.repeat b j ++ R :: list.repeat c (i + j)) =
                  (list.repeat a i ++ list.repeat b j),
                {
                  have len_ij : (list.repeat a i ++ list.repeat b j).length = i + j,
                  {
                    rw list.length_append,
                    rw list.length_repeat,
                    rw list.length_repeat,
                  },
                  rw list.take_append_of_le_length (le_of_eq len_ij.symm),
                  rw list.take_all_of_le,
                  exact le_of_eq len_ij,
                },
                rw take_beg at indexR,
                rw list.nth_le_nth h_len at indexR,
                injection indexR,
              },
              have yes_R : R ∈ (list.repeat a i ++ list.repeat b j),
              {
                let positive := list.nth_le_mem (list.repeat a i ++ list.repeat b j) u.length h_len,
                rw propos at positive,
                exact positive,
              },
              have not_R : R ∉ (list.repeat a i ++ list.repeat b j),
              {
                rw list.mem_append,
                push_neg,
                split,
                {
                  have nidRa : R ≠ a,
                  {
                    apply symbol.no_confusion,
                  },
                  by_contradiction,
                  rw list.mem_repeat at h,
                  exact nidRa h.right,
                },
                {
                  have nidRb : R ≠ b,
                  {
                    apply symbol.no_confusion,
                  },
                  by_contradiction,
                  rw list.mem_repeat at h,
                  exact nidRb h.right,
                },
              },
              exact not_R yes_R,
            },
            cases rest.symm with hgt heq,
            {
              exfalso,
              have yes_Rc : R ∈ list.repeat c (i + j),
              {
                change
                  list.repeat a i ++ (list.repeat b j ++ R :: list.repeat c (i + j)) = u ++ ([R] ++ v)
                  at hyp_bef,
                rw ←list.append_assoc u [R] v at hyp_bef,
                rw ←hyp_bef at indexR,
                rw ←list.append_assoc at indexR,
                change
                  (list.repeat a i ++ list.repeat b j ++ ([R] ++ list.repeat c (i + j))).nth u.length = some R
                  at indexR,
                rw ←list.append_assoc at indexR,
                rw list.nth_append_right at indexR,
                {
                  simp at indexR,
                  have trouble_len : (u.length - (i + (j + 1))) < (list.repeat c (i + j)).length,
                  {
                    rw list.length_repeat,
                    have lengths_sum : u.length ≤ i + j + i + j,
                    {
                      let lengs := congr_arg list.length hyp_bef,
                      repeat {
                        rw list.length_append at lengs,
                      },
                      rw list.length_repeat at lengs,
                      rw list.length_repeat at lengs,
                      simp at lengs,
                      linarith,
                    },
                    linarith,
                  },
                  rw list.nth_le_nth trouble_len at indexR,
                  {
                    finish,
                  },
                },
                rw list.length_append,
                rw list.length_append,
                rw list.length_repeat,
                rw list.length_repeat,
                convert hgt,
              },
              have not_Rc : R ∉ list.repeat c (i + j),
              {
                by_contradiction,
                rw list.mem_repeat at h,
                have nidRc : R ≠ c,
                {
                  apply symbol.no_confusion,
                },
                exact nidRc h.right,
              },
              exact not_Rc yes_Rc,
            },
            rw ←list.append_assoc at hyp_bef,
            have lenlen : (list.repeat a i ++ list.repeat b j).length = u.length,
            {
              rw list.length_append,
              rw list.length_repeat,
              rw list.length_repeat,
              exact heq.symm,
            },
            symmetry,
            exact list.append_inj_left hyp_bef lenlen,
          },
          have v_eq : v = list.repeat c (i + j),
          {
            rw u_eq at hyp_bef,
            rw ←list.append_assoc at hyp_bef,
            rw list.append_right_inj (list.repeat a i ++ list.repeat b j) at hyp_bef,
            symmetry,
            exact list.tail_eq_of_cons_eq hyp_bef,
          },
          rw u_eq at hyp_aft,
          rw v_eq at hyp_aft,
          rw hyp_aft,
        },

        {
          right,
          left,
          use i,
          use j + 1,
          have bpp : list.repeat b (j + 1) = list.repeat b j ++ [b],
          {
            rw list.repeat_add b j 1,
            refl,
          },
          have cpp : list.repeat c (i + (j + 1)) = [c] ++ list.repeat c (i + j),
          {
            rw ←add_assoc,
            rw add_comm,
            rw list.repeat_add c 1 (i + j),
            refl,
          },
          rw bpp,
          rw cpp,
          rw list.append_assoc,
          apply congr_arg (λ lis, list.repeat a i ++ lis),
          rw list.append_assoc,
          apply congr_arg (λ lis, list.repeat b j ++ lis),
          rw list.singleton_append,
          rw list.singleton_append,
        },

        cases rule_in,
        {
          right,
          right,
          use i,
          use j,
          rw list.append_assoc,
        },

        exfalso,
        exact (list.mem_nil_iff rule).1 rule_in,
      },
      {
        -- the remaining case `list.repeat a i ++ list.repeat b j ++ list.repeat c (i + j)` does not match any rule
        exfalso,
        rw hyp_bef at case₃,
        cases case₃ with i foo,
        cases foo with j the_case,
        have contra := congr_arg (λ lis, symbol.nonterminal rule.fst ∈ lis) the_case,
        simp at contra,
        cases contra,
        {
          have neq : symbol.nonterminal rule.fst ≠ a,
          {
            apply symbol.no_confusion,
          },
          rw list.mem_repeat at contra,
          apply neq,
          exact contra.right,
        },
        cases contra,
        {
          have neq : symbol.nonterminal rule.fst ≠ b,
          {
            apply symbol.no_confusion,
          },
          rw list.mem_repeat at contra,
          apply neq,
          exact contra.right,
        },
        {
          have neq : symbol.nonterminal rule.fst ≠ c,
          {
            apply symbol.no_confusion,
          },
          rw list.mem_repeat at contra,
          apply neq,
          exact contra.right,
        },
      },
    },

    specialize possib (list.map symbol.terminal x) ass,
    cases possib with imposs rest,
    {
      exfalso,
      cases imposs with i hyp,
      have contra := congr_arg (λ xs, S ∈ xs) hyp,
      simp at contra,
      finish,
    },
    cases rest with imposs' necess,
    {
      exfalso,
      cases imposs' with i rest,
      cases rest with j hyp,
      have contra := congr_arg (λ xs, R ∈ xs) hyp,
      simp at contra,
      finish,
    },
    cases necess with n foobar,
    use n,
    cases foobar with m keyprop,
    use m,
    unfold anbmcnm,
    unfold a b c at keyprop,
    rw ←list.map_repeat symbol.terminal a_ n at keyprop,
    rw ←list.map_repeat symbol.terminal b_ m at keyprop,
    rw ←list.map_repeat symbol.terminal c_ (n + m) at keyprop,
    rw ←list.map_append at keyprop,
    rw ←list.map_append at keyprop,

    ext1 k,
    have kprop := congr_fun (congr_arg list.nth keyprop) k,
    rw list.nth_map at kprop,
    rw list.nth_map at kprop,

    cases x.nth k,
    {
      cases (list.repeat a_ n ++ list.repeat b_ m ++ list.repeat c_ (n + m)).nth k,
      {
        refl,
      },
      {
        exfalso,
        injection kprop,
      },
    },
    {
      cases (list.repeat a_ n ++ list.repeat b_ m ++ list.repeat c_ (n + m)).nth k,
      {
        exfalso,
        injection kprop,
      },
      {
        injection kprop,
        injection h_1,
        rw h_2,
      },
    },
  },
  {
    -- prove `x ∈ CF_language gr_add ← x ∈ language_add` here
    rintro ⟨n, m, hyp⟩,
    rw hyp,
    have epoch_a : ∀ i : ℕ, CF_derives gr_add [S] ((list.repeat a i) ++ [S] ++ (list.repeat c i)),
    {
      intro i,
      induction i with n' ih,
      {
        apply CF_deri_self,
      },
      apply CF_deri_of_deri_tran ih,

      use (S_, [a, S, c]),
      split_ile,
      use [list.repeat a n', list.repeat c n'],
      split,
      {
        refl,
      },
      rw [
        list.repeat_succ_eq_append_singleton a,
        list.repeat_succ_eq_singleton_append c,
        list.append_assoc,
        list.append_assoc,
        ←list.append_assoc [S],
        ←list.append_assoc [a],
        ←list.append_assoc (list.repeat a n')
      ],
      refl,
    },
    have epoch_b : ∀ j : ℕ, CF_derives gr_add [R] ((list.repeat b j) ++ [R] ++ (list.repeat c j)),
    {
      intro j,
      induction j with m' jh,
      {
        apply CF_deri_self,
      },
      apply CF_deri_of_deri_tran jh,

      use (R_, [b, R, c]),
      split_ile,
      use [list.repeat b m', list.repeat c m'],
      split,
      {
        refl,
      },
      rw [
        list.repeat_succ_eq_append_singleton b,
        list.repeat_succ_eq_singleton_append c,
        list.append_assoc,
        list.append_assoc,
        ←list.append_assoc [R],
        ←list.append_assoc [b],
        ←list.append_assoc (list.repeat b m')
      ],
      refl,
    },
    unfold CF_language,
    rw set.mem_set_of_eq,
    unfold CF_generates,
    unfold CF_generates_str,
    have obtain_sum :
      CF_derives gr_add
        [symbol.nonterminal gr_add.initial]
        ((list.repeat a n) ++ (list.repeat b m) ++ [R] ++ (list.repeat c (n + m))),
    {
      have middle_step : CF_derives gr_add
        ((list.repeat a n) ++ [S] ++ (list.repeat c n))
        ((list.repeat a n) ++ [R] ++ (list.repeat c n)),
      {
        apply CF_deri_with_prefix_and_postfix,
        apply CF_deri_of_tran,
        use (S_, [R]),
        split_ile,
        use [[], []],
        split;
        refl,
      },
      apply CF_deri_of_deri_deri,
      {
        specialize epoch_a n,
        finish,
      },
      apply CF_deri_of_deri_deri,
      {
        finish,
      },
      change
        CF_derives gr_add
          (list.repeat a n ++ ([R] ++ list.repeat c n))
          (list.repeat a n ++ list.repeat b m ++ [R] ++ list.repeat c (n + m)),
      rw ←list.append_assoc,
      have cnm : list.repeat c (n + m) = list.repeat c m ++ list.repeat c n,
      {
        rw add_comm,
        apply list.repeat_add,
      },
      rw cnm,
      have rebra : (list.repeat a n ++ list.repeat b m ++ [R] ++ (list.repeat c m ++ list.repeat c n)) =
                   (list.repeat a n ++ (list.repeat b m ++ [R] ++ list.repeat c m) ++ list.repeat c n),
      {
        simp only [list.append_assoc],
      },
      rw rebra,
      apply CF_deri_with_prefix_and_postfix,
      exact epoch_b m,
    },
    apply CF_deri_of_deri_tran obtain_sum,
    use (R_, []),
    split_ile,
    use (list.repeat a n ++ list.repeat b m),
    use list.repeat c (n + m),
    split,
    {
      refl,
    },
    unfold anbmcnm,
    rw list.map_append_append,
    rw list.append_nil,
    repeat {
      rw list.map_repeat,
    },
    refl,
  },
end


import ring_theory.principal_ideal_domain algebra.euclidean_domain

variables {V : Type*} [integral_domain V] [is_principal_ideal_ring V]

lemma test (a b: V) (gcd_a a b : V) : a * (euclidean_domain.gcd_a a b) + b * (euclidean_domain.gcd_b a b) := sorry


/-
Copyright (c) 2020 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import algebra.group.hom
import category_theory.limits.shapes.kernels
import algebra.big_operators.basic
import algebra.module.basic
import category_theory.endomorphism

/-!
# Preadditive categories

A preadditive category is a category in which `X ⟶ Y` is an abelian group in such a way that
composition of morphisms is linear in both variables.

This file contains a definition of preadditive category that directly encodes the definition given
above. The definition could also be phrased as follows: A preadditive category is a category
enriched over the category of Abelian groups. Once the general framework to state this in Lean is
available, the contents of this file should become obsolete.

## Main results

* Definition of preadditive categories and basic properties
* In a preadditive category, `f : Q ⟶ R` is mono if and only if `g ≫ f = 0 → g = 0` for all
  composable `g`.
* A preadditive category with kernels has equalizers.

## Implementation notes

The simp normal form for negation and composition is to push negations as far as possible to
the outside. For example, `f ≫ (-g)` and `(-f) ≫ g` both become `-(f ≫ g)`, and `(-f) ≫ (-g)`
is simplified to `f ≫ g`.

## References

* [F. Borceux, *Handbook of Categorical Algebra 2*][borceux-vol2]

## Tags

additive, preadditive, Hom group, Ab-category, Ab-enriched
-/

universes v u

open category_theory.limits

open_locale big_operators

namespace category_theory

variables (C : Type u) [category.{v} C]

/-- A category is called preadditive if `P ⟶ Q` is an abelian group such that composition is
    linear in both variables. -/
class preadditive :=
(hom_group : Π P Q : C, add_comm_group (P ⟶ Q) . tactic.apply_instance)
(add_comp' : ∀ (P Q R : C) (f f' : P ⟶ Q) (g : Q ⟶ R),
  (f + f') ≫ g = f ≫ g + f' ≫ g . obviously)
(comp_add' : ∀ (P Q R : C) (f : P ⟶ Q) (g g' : Q ⟶ R),
  f ≫ (g + g') = f ≫ g + f ≫ g' . obviously)

attribute [instance] preadditive.hom_group
restate_axiom preadditive.add_comp'
restate_axiom preadditive.comp_add'
attribute [simp,reassoc] preadditive.add_comp
attribute [reassoc] preadditive.comp_add -- (the linter doesn't like `simp` on this lemma)
attribute [simp] preadditive.comp_add

end category_theory

open category_theory

namespace category_theory
namespace preadditive

section preadditive
open add_monoid_hom
variables {C : Type u} [category.{v} C] [preadditive C]

section induced_category
universes u'
variables {C} {D : Type u'} (F : D → C)

instance induced_category.category : preadditive.{v} (induced_category C F) :=
{ hom_group := λ P Q, @preadditive.hom_group C _ _ (F P) (F Q),
  add_comp' := λ P Q R f f' g, add_comp' _ _ _ _ _ _,
  comp_add' := λ P Q R f g g', comp_add' _ _ _ _ _ _, }

end induced_category

instance (X : C) : add_comm_group (End X) := by { dsimp [End], apply_instance, }

instance (X : C) : ring (End X) :=
{ left_distrib := λ f g h, preadditive.add_comp X X X g h f,
  right_distrib := λ f g h, preadditive.comp_add X X X h f g,
  ..(infer_instance : add_comm_group (End X)),
  ..(infer_instance : monoid (End X)) }

/-- Composition by a fixed left argument as a group homomorphism -/
def left_comp {P Q : C} (R : C) (f : P ⟶ Q) : (Q ⟶ R) →+ (P ⟶ R) :=
mk' (λ g, f ≫ g) $ λ g g', by simp

/-- Composition by a fixed right argument as a group homomorphism -/
def right_comp (P : C) {Q R : C} (g : Q ⟶ R) : (P ⟶ Q) →+ (P ⟶ R) :=
mk' (λ f, f ≫ g) $ λ f f', by simp

variables {P Q R : C} (f f' : P ⟶ Q) (g g' : Q ⟶ R)

/-- Composition as a bilinear group homomorphism -/
def comp_hom : (P ⟶ Q) →+ (Q ⟶ R) →+ (P ⟶ R) :=
add_monoid_hom.mk' (λ f, left_comp _ f) $
  λ f₁ f₂, add_monoid_hom.ext $ λ g, (right_comp _ g).map_add f₁ f₂

@[simp, reassoc] lemma sub_comp :
  (f - f') ≫ g = f ≫ g - f' ≫ g :=
map_sub (right_comp P g) f f'

-- The redundant simp lemma linter says that simp can prove the reassoc version of this lemma.
@[reassoc, simp] lemma comp_sub :
  f ≫ (g - g') = f ≫ g - f ≫ g' :=
map_sub (left_comp R f) g g'

@[simp, reassoc] lemma neg_comp : (-f) ≫ g = -(f ≫ g) :=
map_neg (right_comp _ _) _

/- The redundant simp lemma linter says that simp can prove the reassoc version of this lemma. -/
@[reassoc, simp] lemma comp_neg : f ≫ (-g) = -(f ≫ g) :=
map_neg (left_comp _ _) _

@[reassoc] lemma neg_comp_neg : (-f) ≫ (-g) = f ≫ g :=
by simp

lemma nsmul_comp (n : ℕ) : (n • f) ≫ g = n • (f ≫ g) :=
map_nsmul (right_comp _ _) _ _

lemma comp_nsmul (n : ℕ) : f ≫ (n • g) = n • (f ≫ g) :=
map_nsmul (left_comp _ _) _ _

lemma zsmul_comp (n : ℤ) : (n • f) ≫ g = n • (f ≫ g) :=
map_zsmul (right_comp _ _) _ _

lemma comp_zsmul (n : ℤ) : f ≫ (n • g) = n • (f ≫ g) :=
map_zsmul (left_comp _ _) _ _

@[reassoc] lemma comp_sum {P Q R : C} {J : Type*} (s : finset J) (f : P ⟶ Q) (g : J → (Q ⟶ R)) :
  f ≫ ∑ j in s, g j = ∑ j in s, f ≫ g j :=
map_sum (left_comp R f) _ _

@[reassoc] lemma sum_comp {P Q R : C} {J : Type*} (s : finset J) (f : J → (P ⟶ Q)) (g : Q ⟶ R) :
  (∑ j in s, f j) ≫ g  = ∑ j in s, f j ≫ g :=
map_sum (right_comp P g) _ _

instance {P Q : C} {f : P ⟶ Q} [epi f] : epi (-f) :=
⟨λ R g g' H, by rwa [neg_comp, neg_comp, ←comp_neg, ←comp_neg, cancel_epi, neg_inj] at H⟩

instance {P Q : C} {f : P ⟶ Q} [mono f] : mono (-f) :=
⟨λ R g g' H, by rwa [comp_neg, comp_neg, ←neg_comp, ←neg_comp, cancel_mono, neg_inj] at H⟩

@[priority 100]
instance preadditive_has_zero_morphisms : has_zero_morphisms C :=
{ has_zero := infer_instance,
  comp_zero' := λ P Q f R, map_zero $ left_comp R f,
  zero_comp' := λ P Q R f, map_zero $ right_comp P f }

lemma mono_of_cancel_zero {Q R : C} (f : Q ⟶ R) (h : ∀ {P : C} (g : P ⟶ Q), g ≫ f = 0 → g = 0) :
  mono f :=
⟨λ P g g' hg, sub_eq_zero.1 $ h _ $ (map_sub (right_comp P f) g g').trans $ sub_eq_zero.2 hg⟩

lemma mono_iff_cancel_zero {Q R : C} (f : Q ⟶ R) :
  mono f ↔ ∀ (P : C) (g : P ⟶ Q), g ≫ f = 0 → g = 0 :=
⟨λ m P g, by exactI zero_of_comp_mono _, mono_of_cancel_zero f⟩

lemma mono_of_kernel_zero {X Y : C} {f : X ⟶ Y} [has_limit (parallel_pair f 0)]
  (w : kernel.ι f = 0) : mono f :=
mono_of_cancel_zero f (λ P g h, by rw [←kernel.lift_ι f g h, w, limits.comp_zero])

lemma epi_of_cancel_zero {P Q : C} (f : P ⟶ Q) (h : ∀ {R : C} (g : Q ⟶ R), f ≫ g = 0 → g = 0) :
  epi f :=
⟨λ R g g' hg, sub_eq_zero.1 $ h _ $ (map_sub (left_comp R f) g g').trans $ sub_eq_zero.2 hg⟩

lemma epi_iff_cancel_zero {P Q : C} (f : P ⟶ Q) :
  epi f ↔ ∀ (R : C) (g : Q ⟶ R), f ≫ g = 0 → g = 0 :=
⟨λ e R g, by exactI zero_of_epi_comp _, epi_of_cancel_zero f⟩

lemma epi_of_cokernel_zero {X Y : C} {f : X ⟶ Y} [has_colimit (parallel_pair f 0 )]
  (w : cokernel.π f = 0) : epi f :=
epi_of_cancel_zero f (λ P g h, by rw [←cokernel.π_desc f g h, w, limits.zero_comp])

open_locale zero_object
variables [has_zero_object C]

lemma mono_of_kernel_iso_zero {X Y : C} {f : X ⟶ Y} [has_limit (parallel_pair f 0)]
  (w : kernel f ≅ 0) : mono f :=
mono_of_kernel_zero (zero_of_source_iso_zero _ w)

lemma epi_of_cokernel_iso_zero {X Y : C} {f : X ⟶ Y} [has_colimit (parallel_pair f 0)]
  (w : cokernel f ≅ 0) : epi f :=
epi_of_cokernel_zero (zero_of_target_iso_zero _ w)

end preadditive

section equalizers
variables {C : Type u} [category.{v} C] [preadditive C]

section
variables {X Y : C} (f : X ⟶ Y) (g : X ⟶ Y)

/-- A kernel of `f - g` is an equalizer of `f` and `g`. -/
lemma has_limit_parallel_pair [has_kernel (f - g)] :
  has_limit (parallel_pair f g) :=
has_limit.mk { cone := fork.of_ι (kernel.ι (f - g)) (sub_eq_zero.1 $
    by { rw ←comp_sub, exact kernel.condition _ }),
  is_limit := fork.is_limit.mk _
    (λ s, kernel.lift (f - g) (fork.ι s) $
      by { rw comp_sub, apply sub_eq_zero.2, exact fork.condition _ })
    (λ s, by simp)
    (λ s m h, by { ext, simpa using h walking_parallel_pair.zero }) }

end

section

/-- If a preadditive category has all kernels, then it also has all equalizers. -/
lemma has_equalizers_of_has_kernels [has_kernels C] : has_equalizers C :=
@has_equalizers_of_has_limit_parallel_pair _ _ (λ _ _ f g, has_limit_parallel_pair f g)

end

section
variables {X Y : C} (f : X ⟶ Y) (g : X ⟶ Y)

/-- A cokernel of `f - g` is a coequalizer of `f` and `g`. -/
lemma has_colimit_parallel_pair [has_cokernel (f - g)] :
  has_colimit (parallel_pair f g) :=
has_colimit.mk { cocone := cofork.of_π (cokernel.π (f - g)) (sub_eq_zero.1 $
    by { rw ←sub_comp, exact cokernel.condition _ }),
  is_colimit := cofork.is_colimit.mk _
    (λ s, cokernel.desc (f - g) (cofork.π s) $
      by { rw sub_comp, apply sub_eq_zero.2, exact cofork.condition _ })
    (λ s, by simp)
    (λ s m h, by { ext, simpa using h walking_parallel_pair.one }) }

end

section

/-- If a preadditive category has all cokernels, then it also has all coequalizers. -/
lemma has_coequalizers_of_has_cokernels [has_cokernels C] : has_coequalizers C :=
@has_coequalizers_of_has_colimit_parallel_pair _ _ (λ _ _ f g, has_colimit_parallel_pair f g)

end

end equalizers

end preadditive

end category_theory


/-
Copyright (c) 2023 Wojciech Nawrocki. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Nawrocki
-/

import ProofChecker.Model.PropForm

/-! The Lindenbaum-Tarski algebra on propositional logic. We show that it is a Boolean algebra with
ordering given by semantic entailment. -/

open PropForm in
instance PropTerm.setoid (ν : Type) : Setoid (PropForm ν) where
  r := equivalent
  iseqv := {
    refl  := equivalent_refl
    symm  := equivalent.symm
    trans := equivalent.trans
  }

/-- A propositional term in the algebra is a propositional formula up to semantic equivalence. -/
-- PropFun ν
def PropTerm ν := Quotient (PropTerm.setoid ν)

namespace PropTerm

-- TODO Explain "generalized rewriting with quotient"

theorem exact {φ₁ φ₂ : PropForm ν} : @Eq (PropTerm ν) ⟦φ₁⟧ ⟦φ₂⟧ → PropForm.equivalent φ₁ φ₂ :=
  Quotient.exact

theorem sound {φ₁ φ₂ : PropForm ν} : PropForm.equivalent φ₁ φ₂ → @Eq (PropTerm ν) ⟦φ₁⟧ ⟦φ₂⟧ :=
  @Quotient.sound _ (PropTerm.setoid ν) _ _

def var (x : ν) : PropTerm ν := ⟦.var x⟧

def tr : PropTerm ν := ⟦.tr⟧

def fls : PropTerm ν := ⟦.fls⟧

def neg : PropTerm ν → PropTerm ν :=
  Quotient.map (.neg ·) (by
    intro _ _ h τ
    simp [h τ])

def conj : PropTerm ν → PropTerm ν → PropTerm ν :=
  Quotient.map₂ (.conj · ·) (by
    intro _ _ h₁ _ _ h₂ τ
    simp [h₁ τ, h₂ τ])

def disj : PropTerm ν → PropTerm ν → PropTerm ν :=
  Quotient.map₂ (.disj · ·) (by
    intro _ _ h₁ _ _ h₂ τ
    simp [h₁ τ, h₂ τ])

def impl : PropTerm ν → PropTerm ν → PropTerm ν :=
  Quotient.map₂ (.impl · ·) (by
    intro _ _ h₁ _ _ h₂ τ
    simp [h₁ τ, h₂ τ])

def biImpl : PropTerm ν → PropTerm ν → PropTerm ν :=
  Quotient.map₂ (.biImpl · ·) (by
    intro _ _ h₁ _ _ h₂ τ
    simp [h₁ τ, h₂ τ])

/-! Evaluation lifted to the lattice structure. -/

-- NOTE: It could be defined directly using surjectivity of ⟦-⟧ instead.
def eval (τ : PropAssignment ν) : PropTerm ν → Bool :=
  Quotient.lift (PropForm.eval τ) (fun _ _ h => h τ)

@[simp]
theorem eval_mk (τ : PropAssignment ν) (φ : PropForm ν) :
    eval τ ⟦φ⟧ = φ.eval τ :=
  rfl

@[simp]
theorem eval_var (τ : PropAssignment ν) (x : ν) : eval τ (var x) = τ x := by
  simp [eval, var]

@[simp]
theorem eval_tr (τ : PropAssignment ν) : eval τ tr = true := by
  simp [eval, tr]

@[simp]
theorem eval_fls (τ : PropAssignment ν) : eval τ fls = false := by
  simp [eval, fls]

@[simp]
theorem eval_neg (τ : PropAssignment ν) (φ : PropTerm ν) : eval τ (neg φ) = !(eval τ φ) := by
  have ⟨φ, h⟩ := Quotient.exists_rep φ
  simp [← h, eval, neg]

@[simp]
theorem eval_conj (τ : PropAssignment ν) (φ₁ φ₂ : PropTerm ν) :
    eval τ (conj φ₁ φ₂) = (eval τ φ₁ && eval τ φ₂) := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp [← h₁, ← h₂, conj, eval]

@[simp]
theorem eval_disj (τ : PropAssignment ν) (φ₁ φ₂ : PropTerm ν) :
    eval τ (disj φ₁ φ₂) = (eval τ φ₁ || eval τ φ₂) := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp [← h₁, ← h₂, eval, disj]

@[simp]
theorem eval_impl (τ : PropAssignment ν) (φ₁ φ₂ : PropTerm ν) :
    eval τ (impl φ₁ φ₂) = (eval τ φ₁) ⇨ (eval τ φ₂) := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp [← h₁, ← h₂, eval, impl]

@[simp]
theorem eval_biImpl (τ : PropAssignment ν) (φ₁ φ₂ : PropTerm ν) :
    eval τ (biImpl φ₁ φ₂) = (eval τ φ₁ = eval τ φ₂) := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp [← h₁, ← h₂, eval, biImpl]

/-! Satisfying assignments -/

def satisfies (τ : PropAssignment ν) (φ : PropTerm ν) : Prop :=
  φ.eval τ = true

/-- This instance is scoped so that when `PropTerm` is open, `τ ⊨ φ` implies `φ : PropTerm _`
via the `outParam`. -/
scoped instance : SemanticEntails (PropAssignment ν) (PropTerm ν) where
  entails := PropTerm.satisfies

@[simp]
theorem satisfies_mk {τ : PropAssignment ν} {φ : PropForm ν} : τ ⊨ ⟦φ⟧ ↔ PropForm.satisfies τ φ :=
  ⟨id, id⟩

open SemanticEntails renaming entails → sEntails

@[ext]
theorem ext : (∀ (τ : PropAssignment ν), τ ⊨ φ₁ ↔ τ ⊨ φ₂) → φ₁ = φ₂ := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp only [← h₁, ← h₂]
  intro h
  apply Quotient.sound ∘ PropForm.equivalent_ext.mpr
  apply h

/-! Semantic entailment. -/

def entails (φ₁ φ₂ : PropTerm ν) : Prop :=
  ∀ (τ : PropAssignment ν), φ₁.eval τ ≤ φ₂.eval τ

@[simp]
theorem entails_mk {φ₁ φ₂ : PropForm ν} : entails ⟦φ₁⟧ ⟦φ₂⟧ ↔ PropForm.entails φ₁ φ₂ :=
  ⟨id, id⟩

theorem entails_ext {φ₁ φ₂ : PropTerm ν} :
    entails φ₁ φ₂ ↔ (∀ (τ : PropAssignment ν), τ ⊨ φ₁ → τ ⊨ φ₂) := by
  have ⟨φ₁, h₁⟩ := Quotient.exists_rep φ₁
  have ⟨φ₂, h₂⟩ := Quotient.exists_rep φ₂
  simp only [← h₁, ← h₂, entails_mk]
  exact PropForm.entails_ext

theorem entails_refl (φ : PropTerm ν) : entails φ φ :=
  fun _ => le_rfl
theorem entails.trans : entails φ₁ φ₂ → entails φ₂ φ₃ → entails φ₁ φ₃ :=
  fun h₁ h₂ τ => le_trans (h₁ τ) (h₂ τ)

theorem entails_tr (φ : PropTerm ν) : entails φ tr :=
  fun _ => le_top
theorem fls_entails (φ : PropTerm ν) : entails fls φ :=
  fun _ => bot_le

theorem entails_disj_left (φ₁ φ₂ : PropTerm ν) : entails φ₁ (disj φ₁ φ₂) :=
  fun _ => by simp only [eval_disj]; exact le_sup_left
theorem entails_disj_right (φ₁ φ₂ : PropTerm ν) : entails φ₂ (disj φ₁ φ₂) :=
  fun _ => by simp only [eval_disj]; exact le_sup_right
theorem disj_entails : entails φ₁ φ₃ → entails φ₂ φ₃ → entails (disj φ₁ φ₂) φ₃ :=
  fun h₁ h₂ τ => by simp only [eval_disj]; exact sup_le (h₁ τ) (h₂ τ)

theorem conj_entails_left (φ₁ φ₂ : PropTerm ν) : entails (conj φ₁ φ₂) φ₁ :=
  fun _ => by simp only [eval_conj]; exact inf_le_left
theorem conj_entails_right (φ₁ φ₂ : PropTerm ν) : entails (conj φ₁ φ₂) φ₂ :=
  fun _ => by simp only [eval_conj]; exact inf_le_right
theorem entails_conj : entails φ₁ φ₂ → entails φ₁ φ₃ → entails φ₁ (conj φ₂ φ₃) :=
  fun h₁ h₂ τ => by simp only [eval_conj]; exact le_inf (h₁ τ) (h₂ τ)

theorem entails_disj_conj (φ₁ φ₂ φ₃ : PropTerm ν) :
    entails (conj (disj φ₁ φ₂) (disj φ₁ φ₃)) (disj φ₁ (conj φ₂ φ₃)) :=
  fun _ => by simp only [eval_conj, eval_disj]; exact le_sup_inf

theorem conj_neg_entails_fls (φ : PropTerm ν) : entails (conj φ (neg φ)) fls :=
  fun τ => by simp only [eval_conj, eval_neg]; exact BooleanAlgebra.inf_compl_le_bot (eval τ φ)

theorem tr_entails_disj_neg (φ : PropTerm ν) : entails tr (disj φ (neg φ)) :=
  fun τ => by simp only [eval_disj, eval_neg]; exact BooleanAlgebra.top_le_sup_compl (eval τ φ)

theorem entails.antisymm : entails φ ψ → entails ψ φ → φ = ψ := by
  intro h₁ h₂
  ext τ
  exact ⟨entails_ext.mp h₁ τ, entails_ext.mp h₂ τ⟩

theorem impl_eq (φ ψ : PropTerm ν) : impl φ ψ = disj ψ (neg φ) := by
  ext τ
  simp only [sEntails, satisfies, eval_impl, eval_disj, eval_neg]
  rfl

/-! From this point onwards we use lattice notation for `PropTerm`s in order to get all the laws
for free. -/

instance : BooleanAlgebra (PropTerm ν) where
  le := entails
  top := tr
  bot := fls
  compl := neg
  sup := disj
  inf := conj
  himp := impl
  le_refl := entails_refl
  le_trans := @entails.trans _
  le_antisymm := @entails.antisymm _
  le_top := entails_tr
  bot_le := fls_entails
  le_sup_left := entails_disj_left
  le_sup_right := entails_disj_right
  sup_le _ _ _ := disj_entails
  inf_le_left := conj_entails_left
  inf_le_right := conj_entails_right
  le_inf _ _ _ := entails_conj
  le_sup_inf := entails_disj_conj
  inf_compl_le_bot := conj_neg_entails_fls
  top_le_sup_compl := tr_entails_disj_neg
  himp_eq := impl_eq

@[simp]
theorem satisfies_var {τ : PropAssignment ν} {x : ν} : τ ⊨ var x ↔ τ x := by
  simp [sEntails, satisfies]

@[simp]
theorem satisfies_set {τ : PropAssignment ν} [DecidableEq ν] : τ.set x ⊤ ⊨ var x := by
  simp

@[simp]
theorem satisfies_tr {τ : PropAssignment ν} : τ ⊨ ⊤ :=
  by simp [sEntails, satisfies, Top.top]

@[simp]
theorem not_satisfies_fls {τ : PropAssignment ν} : τ ⊭ ⊥ :=
  fun h => nomatch h

@[simp]
theorem satisfies_neg {τ : PropAssignment ν} : τ ⊨ (φᶜ) ↔ τ ⊭ φ := by
  simp [sEntails, satisfies, HasCompl.compl]

@[simp]
theorem satisfies_conj {τ : PropAssignment ν} : τ ⊨ φ₁ ⊓ φ₂ ↔ τ ⊨ φ₁ ∧ τ ⊨ φ₂ := by
  simp [sEntails, satisfies, HasInf.inf]

@[simp]
theorem satisfies_disj {τ : PropAssignment ν} : τ ⊨ φ₁ ⊔ φ₂ ↔ τ ⊨ φ₁ ∨ τ ⊨ φ₂ := by
  simp [sEntails, satisfies, HasSup.sup]

@[simp]
theorem satisfies_impl {τ : PropAssignment ν} : τ ⊨ φ₁ ⇨ φ₂ ↔ (τ ⊨ φ₁ → τ ⊨ φ₂) := by
  simp only [sEntails, satisfies, eval_impl, HImp.himp]
  cases (eval τ φ₁) <;> simp [himp_eq]

theorem satisfies_impl' {τ : PropAssignment ν} : τ ⊨ φ₁ ⇨ φ₂ ↔ τ ⊭ φ₁ ∨ τ ⊨ φ₂ := by
  simp only [sEntails, satisfies, eval_impl, HImp.himp]
  cases (eval τ φ₁) <;> simp [himp_eq]

@[simp]
theorem satisfies_biImpl {τ : PropAssignment ν} : τ ⊨ biImpl φ₁ φ₂ ↔ (τ ⊨ φ₁ ↔ τ ⊨ φ₂) := by
  simp [sEntails, satisfies]

instance : Nontrivial (PropTerm ν) where
  exists_pair_ne := by
    use ⊤, ⊥
    intro h
    have : ∀ (τ : PropAssignment ν), τ ⊨ ⊥ ↔ τ ⊨ ⊤ := fun _ => h ▸ Iff.rfl
    simp only [satisfies_tr, not_satisfies_fls] at this
    apply this (fun _ => true)

theorem eq_top_iff {φ : PropTerm ν} : φ = ⊤ ↔ ∀ (τ : PropAssignment ν), τ ⊨ φ :=
  ⟨fun h => by simp [h], fun h => by ext; simp [h]⟩

theorem eq_bot_iff {φ : PropTerm ν} : φ = ⊥ ↔ ∀ (τ : PropAssignment ν), τ ⊭ φ :=
  ⟨fun h => by simp [h], fun h => by ext; simp [h]⟩

theorem biImpl_eq_impls (φ ψ : PropTerm ν) : biImpl φ ψ = (φ ⇨ ψ) ⊓ (ψ ⇨ φ) := by
  ext τ
  aesop

/-! Quotient helpers -/

-- TODO: custom simp set?

attribute [-simp] Quotient.eq

@[simp]
theorem mk_var (x : ν) : ⟦.var x⟧ = var x := rfl

@[simp]
theorem mk_tr : @Eq (PropTerm ν) ⟦.tr⟧ ⊤ := rfl

@[simp]
theorem mk_fls : @Eq (PropTerm ν) ⟦.fls⟧ ⊥ := rfl

@[simp]
theorem mk_neg (φ : PropForm ν) : @Eq (PropTerm ν) ⟦.neg φ⟧ (⟦φ⟧ᶜ) := rfl

@[simp]
theorem mk_conj (φ₁ φ₂ : PropForm ν) : @Eq (PropTerm ν) ⟦.conj φ₁ φ₂⟧ (⟦φ₁⟧ ⊓ ⟦φ₂⟧) := rfl

@[simp]
theorem mk_disj (φ₁ φ₂ : PropForm ν) : @Eq (PropTerm ν) ⟦.disj φ₁ φ₂⟧ (⟦φ₁⟧ ⊔ ⟦φ₂⟧) := rfl

@[simp]
theorem mk_impl (φ₁ φ₂ : PropForm ν) : @Eq (PropTerm ν) ⟦.impl φ₁ φ₂⟧ (⟦φ₁⟧ ⇨ ⟦φ₂⟧) := rfl

end PropTerm