  def sub : Nat → Nat → Nat
    | 0, m => 0
    | n, 0 => n
    | n+1, m+1 =>  sub n m

#eval sub 10 1

variable (p q r : Prop)

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p :=
  Iff.intro
    (fun hpq => And.intro hpq.right hpq.left)
    (fun hqp => And.intro hqp.right hqp.left)

example : p ∨ q ↔ q ∨ p :=
  Iff.intro
    (fun hpq =>
      Or.elim hpq
        (fun hp : p =>
          show q ∨ p from Or.inr hp)
        (fun hq : q =>
          show q ∨ p from Or.inl hq))
    (fun hqp =>
      Or.elim hqp
        (fun hq : q =>
          show p ∨ q from Or.inr hq)
        (fun hp : p =>
          show p ∨ q from Or.inl hp))

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
  Iff.intro
    (fun h₁ =>
      have h₃: p ∧ q := h₁.left
      And.intro h₃.left (And.intro h₃.right h₁.right))
    (fun h₂ =>
      have h₄: q ∧ r := h₂.right
      And.intro (And.intro h₂.left h₄.left) h₄.right)

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
  Iff.intro
    (fun h₁ =>
      Or.elim h₁
        (fun h₂: p ∨ q =>
          Or.elim h₂
            (fun h₅: p =>
              show p ∨ (q ∨ r) from Or.inl h₅)
            (fun h₆: q =>
              have h₇: q ∨ r := Or.inl h₆
              show p ∨ (q ∨ r) from Or.inr h₇))
        (fun h₃: r =>
          have h₄: q ∨ r := Or.inr h₃
          show p ∨ (q ∨ r) from Or.inr h₄))
    (fun h₂ =>
      Or.elim h₂
        (fun h₃: p =>
          have h₅: p ∨ q := Or.inl h₃
          show (p ∨ q) ∨ r from Or.inl h₅)
        (fun h₄: q ∨ r =>
          Or.elim h₄
            (fun h₅: q =>
              have h₇: p ∨ q := Or.inr h₅
              show (p ∨ q) ∨ r from Or.inl h₇)
            (fun h₆: r =>
              show (p ∨ q) ∨ r from Or.inr h₆)))

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
  Iff.intro
    (fun h₁ =>
      have h₂: q ∨ r := h₁.right
      Or.elim h₂
        (fun h₃: q =>
          show (p ∧ q) ∨ (p ∧ r) from Or.inl (And.intro h₁.left h₃))
        (fun h₄: r =>
          show (p ∧ q) ∨ (p ∧ r) from Or.inr (And.intro h₁.left h₄)))
    (fun h₁ =>
      Or.elim h₁
        (fun h₂: p ∧ q =>
          And.intro h₂.left (Or.inl h₂.right))
        (fun h₃: p ∧ r =>
          And.intro h₃.left (Or.inr h₃.right)))

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
  Iff.intro
    (fun h₁ =>
      Or.elim h₁
        (fun h₂: p =>
          show (p ∨ q) ∧ (p ∨ r) from And.intro (Or.inl h₂) (Or.inl h₂))
        (fun h₂: q ∧ r =>
          show (p ∨ q) ∧ (p ∨ r) from And.intro (Or.inr h₂.left) (Or.inr h₂.right)))
    (fun h₁ =>
      have h₂: p ∨ q := h₁.left
      have h₃: p ∨ r := h₁.right
      Or.elim h₂
        (fun h₄: p =>
          Or.inl h₄)
        (fun h₄: q =>
          Or.elim h₃
            (fun h₅: p =>
              Or.inl h₅)
            (fun h₅: r =>
              Or.inr (And.intro h₄ h₅))))

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) :=
  Iff.intro
    (fun h₁ =>
      fun h₂ =>
        h₁ h₂.left h₂.right)
    (fun h₁ =>
      fun h₂ =>
        fun h₃ =>
          have h₄: p ∧ q := And.intro h₂ h₃
          h₁ h₄)

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := sorry
example : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  Iff.intro
    (fun h₁ =>
      fun h₂: p ∨ q =>
      Or.elim h₂
        (fun h₃: p =>
          )
        (fun h3: q =>
          ))
    (fun h₁ =>
      _)
example : ¬p ∨ ¬q → ¬(p ∧ q) :=
  fun h₁ => show ¬(p ∧ q) from (
    fun h₂: p ∧ q => show False from (
      Or.elim h₁
        (fun h₃: ¬p =>
          show False from h₃ h₂.left)
        (fun h₃: ¬q =>
          show False from h₃ h₂.right)
    )
  )
example : ¬(p ∧ ¬p) :=
  fun h₁ : p ∧ ¬p =>
  show False from (
    have h₂: p := h₁.left
    have h₃: ¬p := h₁.right
    h₃ h₂
  )
example : p ∧ ¬q → ¬(p → q) :=
  fun h₁ =>
    fun h₂: p → q => show False from (
      have h₃: ¬q := h₁.right
      have h₄: p := h₁.left
      h₃ (h₂ h₄)
    )
example : ¬p → (p → q) :=
  fun h₁ =>
    fun h₂ =>
      absurd h₂ h₁
example : (¬p ∨ q) → (p → q) :=
  fun h₁ =>
    fun h₂ =>
      Or.elim h₁
      (fun h₃: ¬p =>
        show q from absurd h₂ h₃)
      (fun h₃: q =>
        h₃)
example : p ∨ False ↔ p :=
  Iff.intro
  (fun h₁ =>
    Or.elim h₁
    (fun h₂: p =>
      h₂)
    (fun h₂: False =>
      False.elim h₂))
  (fun h₁ =>
    Or.inl h₁)
example : p ∧ False ↔ False :=
  Iff.intro
  (fun h₁ =>
    h₁.right)
  (fun h₂ =>
    have h₃: p := False.elim h₂
    And.intro h₃ h₂)
example : (p → q) → (¬q → ¬p) :=
  fun h₁ =>
    fun h₂ =>
      fun h₃: p => show False from(
        have h₄: q := h₁ h₃
        h₂ h₄
      )

variable (α : Type) (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
  Iff.intro
  (fun h₁ =>
    And.intro
    (fun y: α =>
      (h₁ y).left)
    (fun u: α =>
      (h₁ u).right))
  (fun h₁ =>
    fun y: α =>
      have h₂: p y := h₁.left y
      have h₃: q y := h₁.right y
      And.intro h₂ h₃)
example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
  fun h₁ =>
    fun h₂ =>
      fun y: α =>
        (h₁ y) (h₂ y)
example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
  fun h₁ =>
    Or.elim h₁
    (fun h₂: ∀ x, p x =>
      fun y: α =>
        Or.inl (h₂ y))
    (fun h₂: ∀ x, q x =>
      fun y: α =>
        Or.inr (h₂ y))

variable (α : Type) (p q : α → Prop)
variable (r : Prop)

example : α → ((∀ x : α, r) ↔ r) :=
  fun h₁ =>
    Iff.intro
    (fun h₂ =>
      h₂ h₁)
    (fun h₂ =>
      fun h₃ =>
        h₂)

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  Iff.intro
  (fun h₁ =>
    fun h₂ =>
    fun y: α =>
      have h₃ : r → p y := h₁ y
      h₃ h₂)
  (fun h₁ =>
    fun y: α =>
      fun h₂ =>
      have h₃: ∀ (x : α), p x := h₁ h₂
      h₃ y)

variable (men : Type) (barber : men)
variable  (shaves : men → men → Prop)

example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : False :=
    have h₁: shaves barber barber ↔ ¬ shaves barber barber := h barber
    have h₂: shaves barber barber → ¬ shaves barber barber := h₁.mp
    show False from(
      fun h₃: shaves barber barber =>
        have h₄: ¬ shaves barber barber := h₂ h₃
        h₄ h₃
    )

open Classical

variable (α : Type) (p q : α → Prop)
variable (r : Prop)

example : (∃ x : α, r) → r :=
  fun h₁ =>
    Exists.elim h₁
    (fun w =>
      fun hr: r =>
      hr)

example (a : α) : r → (∃ x : α, r) :=
  fun h₁ =>
    Exists.intro a h₁

example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  Iff.intro
  (fun h₁: ∃ x, p x ∧ r =>
    Exists.elim h₁
    (fun y: α =>
      fun h₂: p y ∧ r =>
        have h₃: ∃ x, p x := Exists.intro y h₂.left
        And.intro h₃ h₂.right))
  (fun h₁: (∃ x, p x) ∧ r =>
    have h₂: ∃ x, p x := h₁.left
    Exists.elim h₂
    (fun w: α =>
      fun h₃: p w =>
      have h₄: p w ∧ r := And.intro h₃ h₁.right
      Exists.intro w h₄))

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  Iff.intro
  (fun h₁ =>
    Exists.elim h₁
    (fun w: α =>
      fun h₂: p w ∨ q w =>
        Or.elim h₂
        (fun h₃: p w =>
          have h₄: ∃ x, p x := Exists.intro w h₃
          Or.inl h₄)
        (fun h₃: q w =>
          have h₄: ∃ x, q x := Exists.intro w h₃
          Or.inr h₄)))
  (fun h₁ =>
    Or.elim h₁
    (fun h₂: ∃ x, p x =>
      Exists.elim h₂
      (fun w: α =>
        fun h₃: p w =>
          have h₄: p w ∨ q w := Or.inl h₃
          Exists.intro w h₄))
    (fun h₂: ∃ x, q x =>
      Exists.elim h₂
      (fun w: α =>
        fun h₃: q w =>
          have h₄: p w ∨ q w := Or.inr h₃
          Exists.intro w h₄))

example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
  Iff.intro
  (fun h₁ =>
    fun y: α =>
      have h₂: p y := h₁ y
      have h₃: ∃ x, p x := Exists.intro y h₂
      byContradiction
      (fun h₄: ∃ x, ¬ p x =>
        show False from h₄ h₃))
  (fun h₁ =>
    )
example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
  Iff.intro
  (fun h₁ =>
    Exists.elim h₁
    (fun y: α =>
      fun h₂: p y =>
        byContradiction
        (fun h₃: ∀ x, ¬ p x =>
          h₃ h₂)))
  (fun h₁ =>
    )

variable (p q r : Prop)

example : p ∧ q ↔ q ∧ p := by
apply Iff.intro
. intro h
  apply And.intro
  . exact h.right
  . exact h.left
. intro h
  apply And.intro
  . exact h.right
  . exact h.left

example : p ∨ q ↔ q ∨ p := by
apply Iff.intro
. intro h
  apply Or.elim h
  . intro hp
    apply Or.inr hp
  . intro hq
    apply Or.inl hq
. intro h
  apply Or.elim h
  . intro hq
    apply Or.inr hq
  . intro hp
    apply Or.inl hp

example : ∀ a b c : Nat, a = b → a = c → c = b := by
  intros
  apply Eq.trans
  apply Eq.symm
  assumption
  assumption

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := by
apply Iff.intro
. intro h
  cases h with
  | intro hpq hr => constructor; exact hpq.left; constructor; exact hpq.right; assumption
. intro h
  cases h with
  | intro hp hqr => constructor; constructor; assumption; exact hqr.left; exact hqr.right

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := by
apply Iff.intro
. intro h
  cases h with
  | inl hpq =>
    apply Or.elim hpq
    . intro hp
      apply Or.inl
      assumption
    . intro hq
      apply Or.inr
      apply Or.inl
      assumption
  | inr hr =>
    apply Or.inr
    apply Or.inr
    assumption
. intro h
  cases h with
  | inl hp =>
    apply Or.inl
    apply Or.inl
    assumption
  | inr hqr =>
    apply Or.elim hqr
    . intro hq
      apply Or.inl
      apply Or.inr
      assumption
    . intro hr
      apply Or.inr
      assumption

example (p q : Prop) : p ∧ q → q ∧ p := by
  intro h
  cases h with
  | intro hp hq => apply And.intro; exact hq; exact hp

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
apply Iff.intro
. intro h
  have hp := h.left
  have hqr := h.right
  apply Or.elim hqr
  . intro hq
    apply Or.inl
    constructor
    repeat assumption
  . intro hr
    apply Or.inr
    constructor
    repeat assumption
. intro h
  apply Or.elim h
  . intro hpq
    constructor
    exact hpq.left
    apply Or.inl
    exact hpq.right
  . intro hpr
    constructor
    exact hpr.left
    apply Or.inr
    exact hpr.right

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := by
apply Iff.intro
. intro h
  cases h with
  | inl hp => constructor; apply Or.inl; exact hp; apply Or.inl; exact hp
  | inr hqr => constructor; apply Or.inr; exact hqr.left; apply Or.inr; exact hqr.right
. intro h
  match h with
  | ⟨ Or.inl hp, Or.inl hp2 ⟩ => apply Or.inl; assumption
  | ⟨ Or.inl hp, Or.inr hr ⟩ => apply Or.inl; assumption
  | ⟨ Or.inr hq, Or.inl hp ⟩ => apply Or.inl; assumption
  | ⟨ Or.inr hq, Or.inr hr ⟩ => apply Or.inr; constructor; repeat assumption

def f (x y z : Nat) : Nat :=
  match x, y, z with
  | 5, _, _ => y
  | _, 5, _ => y
  | _, _, 5 => y
  | _, _, _ => 1

example (x y z : Nat) : x ≠ 5 → y ≠ 5 → z ≠ 5 → z = w → f x y w = 1 := by
  intros
  simp [f]
  split
  . contradiction
  . contradiction
  . contradiction
  . rfl

theorem append_nil (as : List α) : append as nil = as :=
rfl

theorem append_assoc (as bs cs : List α)
        : append (append as bs) cs = append as (append bs cs) :=
sorry

open List

def length (t: List α) : Nat :=
  match t with
  | nil => 0
  | cons a as =>  1 + (length as)

example : p ∨ q → q ∨ p := by
  intro h
  match h with
  | Or.inl h1  => apply Or.inr; exact h1
  | Or.inr h2 => apply Or.inl; exact h2


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov

! This file was ported from Lean 3 source module measure_theory.measure.complex_lebesgue
! leanprover-community/mathlib commit 468b141b14016d54b479eb7a0fff1e360b7e3cf6
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.MeasureTheory.Measure.Lebesgue

/-!
# Lebesgue measure on `ℂ`

In this file we define Lebesgue measure on `ℂ`. Since `ℂ` is defined as a `structure` as the
push-forward of the volume on `ℝ²` under the natural isomorphism. There are (at least) two
frequently used ways to represent `ℝ²` in `mathlib`: `ℝ × ℝ` and `fin 2 → ℝ`. We define measurable
equivalences (`measurable_equiv`) to both types and prove that both of them are volume preserving
(in the sense of `measure_theory.measure_preserving`).
-/

open MeasureTheory

noncomputable section

namespace Complex

/-- Lebesgue measure on `ℂ`. -/
instance measureSpace : MeasureSpace ℂ :=
  ⟨Measure.map basisOneI.equivFun.symm volume⟩
#align complex.measure_space Complex.measureSpace

/-- Measurable equivalence between `ℂ` and `ℝ² = fin 2 → ℝ`. -/
def measurableEquivPi : ℂ ≃ᵐ (Fin 2 → ℝ) :=
  basisOneI.equivFun.toContinuousLinearEquiv.toHomeomorph.toMeasurableEquiv
#align complex.measurable_equiv_pi Complex.measurableEquivPi

/-- Measurable equivalence between `ℂ` and `ℝ × ℝ`. -/
def measurableEquivRealProd : ℂ ≃ᵐ ℝ × ℝ :=
  equivRealProdClm.toHomeomorph.toMeasurableEquiv
#align complex.measurable_equiv_real_prod Complex.measurableEquivRealProd

theorem volumePreservingEquivPi : MeasurePreserving measurableEquivPi :=
  (measurableEquivPi.symm.Measurable.MeasurePreserving _).symm _
#align complex.volume_preserving_equiv_pi Complex.volumePreservingEquivPi

theorem volumePreservingEquivRealProd : MeasurePreserving measurableEquivRealProd :=
  (volumePreservingFinTwoArrow ℝ).comp volumePreservingEquivPi
#align complex.volume_preserving_equiv_real_prod Complex.volumePreservingEquivRealProd

end Complex


import analysis.normed_space.int

import polyhedral_lattice.basic

/-!

# The integers are a polyhedral lattice.

The integers, with its usual norm, are a polyhedral lattice.

-/
noncomputable theory

open_locale big_operators

lemma int.sum_units_to_nat_smul (n : ℤ) :
  ∑ (i : units ℤ), int.to_nat (i * n) • (i : ℤ) = n :=
begin
  rw [units_int.univ, finset.sum_insert], swap, dec_trivial,
  simp only [neg_mul, mul_one, one_mul, nsmul_eq_mul, units.coe_one, units.coe_neg_one, mul_neg,
    finset.sum_singleton, ←sub_eq_add_neg, int.to_nat_sub_to_nat_neg],
end

instance int.polyhedral_lattice : polyhedral_lattice ℤ :=
{ polyhedral' :=
  begin
    refine ⟨units ℤ, infer_instance, coe, _⟩,
    intro n,
    refine ⟨λ e, int.to_nat (e * n), (int.sum_units_to_nat_smul _).symm, _⟩,
    simp only [int.norm_coe_units, mul_one, nat.cast_one, one_mul, units_int.univ],
    show ∥n∥ = ((1 * n).to_nat) + (↑(((-1) * n).to_nat) + 0),
    simp only [neg_mul, add_zero, one_mul],
    exact (int.to_nat_add_to_nat_neg_eq_norm _).symm,
  end }


import Mathlib.Tactic.Basic
import Mathlib.Tactic.LeftRight

lemma v1 (P Q R : Prop) : P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) := by
(constructor;
  (intro h;
    cases h with
      | intro hP hQR =>
        cases hQR with
          | inl hQ =>
            left; constructor;
              assumption;
              assumption
          | inr hR =>
            right; constructor;
                assumption;
              assumption
  );
  (intro h;
    cases h with
      | inl hPQ =>
        cases hPQ with
          | intro hP hQ =>
            constructor;
              assumption;
              left; assumption
      | inr hPR =>
        cases hPR with
          | intro hP hR =>
            constructor;
              assumption;
              right; assumption
  )
)

lemma v2 (P Q R : Prop) : P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) :=
  ((Iff.intro
    ((λ h : P ∧ (Q ∨ R) =>
      (match h with
        | And.intro hP hQR => (match hQR with
            | Or.inl hQ => Or.inl (And.intro hP hQ : P ∧ Q)
            | Or.inr hR => Or.inr (And.intro hP hR : P ∧ R)
        )
      )
    ) : P ∧ (Q ∨ R) -> (P ∧ Q) ∨ (P ∧ R))
    ((λ h : P ∧ Q ∨ P ∧ R =>
      (And.intro
        ((match h with
          | Or.inl (And.intro hP hQ) => hP
          | Or.inr (And.intro hP hR) => hP
        ) : P)
        ((match h with
          | Or.inl (And.intro hP hQ) => Or.inl hQ
          | Or.inr (And.intro hP hR) => Or.inr hR
        ) : Q ∨ R)
      )
    ) : (P ∧ Q) ∨ (P ∧ R) -> P ∧ (Q ∨ R))
  ) : P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R))

class MyAdd (a : Type) where
  add : a -> a -> a

#check @MyAdd.add

instance : MyAdd Nat where
  add := Nat.add

instance : MyAdd Int where
  add := Int.add

instance : MyAdd Float where
  add := Float.add

def double [inst : Add a] (x : a) : a :=
  inst.add x x

#check @double
-- @double : {a : Type} → [inst : Add a] → a → a

-- #eval double 10
-- 20

#check Nat.ne_of_lt

example : ¬ 0 < 0 :=
  ((fun h =>
    let x := (Nat.ne_of_lt h)
    let y := (absurd rfl x)
    y
  ) : ¬ 0 < 0)

#check @absurd
example (n : Nat) (h : n ≠ 0) : .succ (.pred n) = n :=
  ((match n with
    | Nat.zero => absurd rfl h
    | Nat.succ m => rfl
  ) : .succ (.pred n) = n)

example (n : Nat) (h : n ≠ 0) : .succ (.pred n) = n := by
  cases n with
  | zero =>
    -- goal: h : 0 ≠ 0 ⊢ succ (pred 0) = 0
    apply absurd rfl h
  | succ m =>
    -- second goal: h : succ m ≠ 0 ⊢ succ (pred (succ m)) = succ m
    rfl

-- induction with term and tactics
def add : Nat → Nat → Nat
  | m, .zero   => m
  | m, .succ n => .succ (add m n)

theorem add_zero (m : Nat)   : add m .zero = m := rfl
theorem add_succ (m n : Nat) : add m (.succ n) = .succ (add m n) := rfl

theorem zero_add : ∀ n, add .zero n = n
  | .zero   => rfl
  | .succ n => congrArg Nat.succ (zero_add n)

#check @congrArg
example : ∀ n, add .zero n = n := by
  (intro n;
    (induction n with
      | zero => apply rfl
      | succ n IH => apply congrArg Nat.succ IH
    )
  )

theorem not_lt_zero (n : Nat) : ¬ n < 0 :=
  (fun h =>
    let h : n + 1 ≤ 0 := h
    match n with
      | 0 =>
        let z := Nat.ne_of_lt h
        absurd rfl z
      | n' + 1 =>
        let IH := not_lt_zero n'
        let z : n' + 1 ≤ 0 := Nat.lt_of_succ_lt h
        (IH z)
  )


import binomial
import WZtheorem

open int
open binomial

/-
Proof of ∑ₖ (binomial n k) / (2^n) = 1

The `sorry`s indicate places where ideally the theorem prover should be able to carry the proof to its completion.
-/

def extendedbinomial : ℤ → ℤ → ℚ
  | (of_nat n) (of_nat k) := binomial n k
  | -[1+n] _ := 0
  | _ -[1+k] := 0

def F : ℤ → ℤ → ℚ := λ n k, (extendedbinomial n k) / (2 ^ n)

def G : ℤ → ℤ → ℚ := λ n k, - (extendedbinomial n (k-1)) / (2 ^ (n+1))

theorem WZ_FG : WZ_pair F G :=
begin
  unfold WZ_pair,
  intros n k,
  rw F at *,
  rw G at *,
  simp,
  ring_nf,
  cases n,
  sorry,
  sorry,
end

theorem Gvanishes : ∀ n, vanishes (G n) :=
begin
  intro n,
  unfold vanishes,
  intro ε, intro hεpos,
  existsi (int.to_nat (n+1)),
  intro k,
  intro hkrange,
  sorry,
end

theorem basecase : summation.equals_indefinite_sum (F 0) 1 :=
begin
  unfold summation.equals_indefinite_sum,
  intro ε, intro hε,
  use 1,
  intro m, intro hmgt1,
  split,
  {
    rw F,
    induction m with d hd,
    {
      rw summation.summation,
      simp,
      linarith,
    },
    {
      rw summation.summation,
      simp,
      simp at hd,
      sorry,
    }
  },
  {
    induction m with d hd,
    linarith,
    rw summation.summation,
    rw F,
    sorry,
  }
end

-- the final theorem
theorem binomial_id : ∀ (n : ℤ), summation.equals_indefinite_sum (F n) 1 := WZ F G WZ_FG Gvanishes 1 basecase


import mynat.add

lemma add_succ_zero (a : mynat) : a + succ(0) = succ(a) :=
begin
rw add_succ,
rw add_zero,
refl,
end


-- import the definition of the gcd maze
import mazes.gcd_maze.definition
import data.int.gcd
open maze direction

/-

# Bezout's Theorem Example maze.

You are in a maze of integers, all distinct.

You can go north, south east or west.

North adds 8 to your integer, South subtracts 8.
East adds 13 to your integer, West subtracts 13

The exit is at 1, the gcd of 8 and 5.
When you're at the exit, type `out`.

Solver remark : there are infinitely many rooms.
-/

/- Lemma : no-side-bar
Can you prove this case of Bezout's Theorem?
-/
example : can_escape 8 13 0 :=
begin
  iterate 5 {n},
  iterate 3 {w},
  out
end

-- #eval nat.xgcd 8 13 -- (2, -3) -- hide


/-
Copyright (c) 2022 Moritz Doll. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Moritz Doll
-/
import linear_algebra.basis
import linear_algebra.bilinear_map

/-!
# Lemmas about bilinear maps with a basis over each argument

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

variables {ι₁ ι₂ : Type*}
variables {R R₂ S S₂ M N P : Type*}
variables {Mₗ Nₗ Pₗ : Type*}
variables [comm_semiring R] [comm_semiring S] [comm_semiring R₂] [comm_semiring S₂]

section add_comm_monoid

variables [add_comm_monoid M] [add_comm_monoid N] [add_comm_monoid P]
variables [add_comm_monoid Mₗ] [add_comm_monoid Nₗ] [add_comm_monoid Pₗ]
variables [module R M] [module S N] [module R₂ P] [module S₂ P]
variables [module R Mₗ] [module R Nₗ] [module R Pₗ]
variables [smul_comm_class S₂ R₂ P]
variables {ρ₁₂ : R →+* R₂} {σ₁₂ : S →+* S₂}
variables (b₁ : basis ι₁ R M) (b₂ : basis ι₂ S N) (b₁' : basis ι₁ R Mₗ) (b₂' : basis ι₂ R Nₗ)

/-- Two bilinear maps are equal when they are equal on all basis vectors. -/
lemma ext_basis {B B' : M →ₛₗ[ρ₁₂] N →ₛₗ[σ₁₂] P}
  (h : ∀ i j, B (b₁ i) (b₂ j) = B' (b₁ i) (b₂ j)) : B = B' :=
b₁.ext $ λ i, b₂.ext $ λ j, h i j

/-- Write out `B x y` as a sum over `B (b i) (b j)` if `b` is a basis.

Version for semi-bilinear maps, see `sum_repr_mul_repr_mul` for the bilinear version. -/
lemma sum_repr_mul_repr_mulₛₗ {B : M →ₛₗ[ρ₁₂] N →ₛₗ[σ₁₂] P} (x y) :
  (b₁.repr x).sum (λ i xi, (b₂.repr y).sum (λ j yj, (ρ₁₂ xi) • (σ₁₂ yj) • B (b₁ i) (b₂ j))) =
  B x y :=
begin
  conv_rhs { rw [← b₁.total_repr x, ← b₂.total_repr y] },
  simp_rw [finsupp.total_apply, finsupp.sum, map_sum₂, map_sum,
    linear_map.map_smulₛₗ₂, linear_map.map_smulₛₗ],
end

/-- Write out `B x y` as a sum over `B (b i) (b j)` if `b` is a basis.

Version for bilinear maps, see `sum_repr_mul_repr_mulₛₗ` for the semi-bilinear version. -/
lemma sum_repr_mul_repr_mul {B : Mₗ →ₗ[R] Nₗ →ₗ[R] Pₗ} (x y) :
  (b₁'.repr x).sum (λ i xi, (b₂'.repr y).sum (λ j yj, xi • yj • B (b₁' i) (b₂' j))) =
  B x y :=
begin
  conv_rhs { rw [← b₁'.total_repr x, ← b₂'.total_repr y] },
  simp_rw [finsupp.total_apply, finsupp.sum, map_sum₂, map_sum,
    linear_map.map_smul₂, linear_map.map_smul],
end

end add_comm_monoid

end linear_map


import data.finset.basic
import data.mv_polynomial.basic
import data.mv_polynomial.comm_ring

namespace finsupp

open set function finsupp add_monoid_algebra
open_locale big_operators

lemma sum_single'' {M : Type*} [semiring M] {α : Type*} {s : finset α} {j : α} (h : j ∈ s)
 (a : M) : ∑ x in s , (single j a) x  = a :=
begin
  revert h,
  apply finset.cons_induction_on s,
  { simp },
  { clear s,
    intros j' s h' h h'',
    rw finset.sum_cons,
    rw finset.mem_cons at h'',
    cases h'' with j_eq_j' j_in_s,
    { suffices h: s.sum ⇑(single j' a) = 0,
      by simp [j_eq_j', h],
      suffices z : ∀ (x : α), x ∈ s → (λ x, (single j' a) x) x = (λ x, 0) x,
      { rw finset.sum_congr (by refl) z,
        simp },
      intros x hx,
      suffices j'_ne_x : j' ≠ x,
      by simp [j'_ne_x],
      by_contra c,
      rw c at h',
      exact h' hx },
    { suffices j_ne_j' : j ≠ j',
      by simp [h j_in_s, j_ne_j'],
      by_contra c,
      rw c at j_in_s,
      exact h' j_in_s } },
end

lemma sum_single' {M σ : Type*} [semiring M] [fintype σ] (j : σ) (a : M) :
  ∑ ( x : σ) , single j a x  = a := by simp only [sum_single'', finset.mem_univ]

end finsupp


import data.set.basic -- hide
import data.set.prod --hide
open set -- hide

variables {X Y : Type} -- hide

/- Tactic : split

## Summary:

If the goal is `P ∧ Q` or `P ↔ Q` then `split` will break it into two goals.

## Details

If `P Q : Prop` and the goal is `⊢ P ∧ Q`, then `split` will change it into
two goals, namely `⊢ P` and `⊢ Q`.

If `P Q : Prop` and the goal is `⊢ P ↔ Q`, then `split` will change it into
two goals, namely `⊢ P → Q` and `⊢ Q → P`.

## Example:

If your local context (the top right window) looks like this
```
X : Type
A B : set X
x : X
⊢ x ∈ A ↔ x ∈ B
```

then after

`split,`

it will look like this:

```
2 goals
X : Type
A B : set X
x : X
⊢ x ∈ A → x ∈ B

X : Type
A B : set X
x : X
⊢ x ∈ B → x ∈ A
```
-/

/-
In this level we will learn the `split` tactic. It breaks a goal `P ∧ Q` into two goals (proving `P`, and then proving `Q`),
and also breaks goals of the form `P ↔ Q` into proving each of the implications separately.

You will need to use both features to accomplish this proof.
-/
/- Lemma : no-side-bar
Giving an element `p` of a product type `X × Y` and two subsets `A ⊆ X` `B ⊆ Y`. The element
`p` is the set `A × B` (as sets) if only if the first component of `p` is in `A` and the second in `B`.
-/
lemma mem_prod_iff {p : X × Y} (A: set X) (B : set Y): p ∈ A ×ˢ B ↔ p.1 ∈ A ∧ p.2 ∈ B :=
begin
  split;
  intro h;
  exact ⟨h.1, h.2⟩

end


-- Pruebas de la equivalencia entre definiciones de inversa
-- ========================================================

import data.list.basic
open list

variable  {α : Type*}
variable  (x : α)
variables (xs ys : list α)

-- ----------------------------------------------------
-- Nota. Se usará la función inversa y sus propiedades
-- estudiadas anteriormente.
-- ----------------------------------------------------

def inversa : list α → list α
| []        := []
| (x :: xs) := inversa xs ++ [x]

@[simp]
lemma inversa_nil :
  inversa ([] : list α) = [] :=
rfl

@[simp]
lemma inversa_cons :
  inversa (x :: xs) = inversa xs ++ [x] :=
rfl

-- ----------------------------------------------------
-- Ejercicio 1. Definir la función
--    inversaAc : list α → list α
-- tal que (inversaAc xs) es a inversa de xs calculada
-- usando acumuladores. Por ejemplo,
--    inversaAc [1,3,2,5] = [5,3,2,1]
-- ----------------------------------------------------

def inversaAcAux : list α → list α → list α
| []        ys := ys
| (x :: xs) ys := inversaAcAux xs (x :: ys)

@[simp]
def inversaAc : list α → list α :=
λ xs, inversaAcAux xs []

-- #eval inversaAc [1,3,2,5]

-- ----------------------------------------------------
-- Ejercicio 2.Demostrar los siguientes lemas
-- + inversaAcAux_nil :
--      inversaAcAux [] ys = ys
-- + inversaAcAux_cons :
--      inversaAcAux (x :: xs) ys =
--      inversaAcAux xs (x :: ys)
-- ----------------------------------------------------

@[simp]
lemma inversaAcAux_nil :
  inversaAcAux [] ys = ys :=
rfl

@[simp]
lemma inversaAcAux_cons :
  inversaAcAux (x :: xs) ys = inversaAcAux xs (x :: ys) :=
rfl

-- ----------------------------------------------------
-- Ejercicio 3. Demostrar que
--    inversaAc [1,2,3] = inversa [1,2,3]
-- ----------------------------------------------------

example : inversaAc [1,2,3] = inversa [1,2,3] :=
rfl

-- ----------------------------------------------------
-- Ejercicio 4. (p. 44) Demostrar que
--    inversaAcAux xs ys = (inversa xs) ++ ys
-- ----------------------------------------------------

-- 1ª demostración
example :
  ∀ ys, inversaAcAux xs ys = (inversa xs) ++ ys :=
begin
  induction xs with a as HI,
  { intro,
    rw inversaAcAux_nil,
    rw inversa_nil,
    rw nil_append, },
  { intro,
    rw inversaAcAux_cons,
    rw (HI (a :: ys)),
    rw inversa_cons,
    rw append_assoc,
    rw singleton_append, },
end

-- 2ª demostración
example :
  ∀ ys, inversaAcAux xs ys = (inversa xs) ++ ys :=
begin
  induction xs with a as HI,
  { intro,
    calc inversaAcAux [] ys
         = ys               : by rw inversaAcAux_nil
     ... = [] ++ ys         : by rw nil_append
     ... = inversa [] ++ ys : by rw inversa_nil },
  { intro,
    calc inversaAcAux (a :: as) ys
         = inversaAcAux as (a :: ys) : by rw inversaAcAux_cons
     ... = inversa as ++ (a :: ys)   : by rw (HI (a :: ys))
     ... = inversa as ++ ([a] ++ ys) : by rw singleton_append
     ... = (inversa as ++ [a]) ++ ys : by rw append_assoc
     ... = inversa (a :: as) ++ ys   : by rw inversa_cons },
end

-- 3ª demostración
example :
  inversaAcAux xs ys = (inversa xs) ++ ys :=
begin
  induction xs with a as HI generalizing ys,
  { rw inversaAcAux_nil,
    rw inversa_nil,
    rw nil_append, },
  { rw inversaAcAux_cons,
    rw (HI (a :: ys)),
    rw inversa_cons,
    rw append_assoc,
    rw singleton_append, },
end

-- 4ª demostración
example :
  inversaAcAux xs ys = (inversa xs) ++ ys :=
begin
  induction xs with a as HI generalizing ys,
  { calc inversaAcAux [] ys
         = ys               : by rw inversaAcAux_nil
     ... = [] ++ ys         : by rw nil_append
     ... = inversa [] ++ ys : by rw inversa_nil },
  { calc inversaAcAux (a :: as) ys
         = inversaAcAux as (a :: ys) : by rw inversaAcAux_cons
     ... = inversa as ++ (a :: ys)   : by rw (HI (a :: ys))
     ... = inversa as ++ ([a] ++ ys) : by rw singleton_append
     ... = (inversa as ++ [a]) ++ ys : by rw append_assoc
     ... = inversa (a :: as) ++ ys   : by rw inversa_cons },
end

-- 5ª demostración
example :
  inversaAcAux xs ys = (inversa xs) ++ ys :=
begin
  induction xs with a as HI generalizing ys,
  { simp, },
  { simp [HI (a :: ys)], },
end

-- 6ª demostración
example :
  inversaAcAux xs ys = (inversa xs) ++ ys :=
by induction xs generalizing ys ; simp [*]

-- 7ª demostración
@[simp]
lemma inversa_equiv :
  ∀ xs : list α, ∀ ys, inversaAcAux xs ys = (inversa xs) ++ ys
| []         := by simp
| (a :: as)  := by simp [inversa_equiv as]

-- ----------------------------------------------------
-- Ejercicio 5. (p. 43) Demostrar que
--    inversaAc xs = inversa xs
-- ----------------------------------------------------

-- 1ª demostración
example : inversaAc xs = inversa xs :=
calc inversaAc xs
     = inversaAcAux xs [] : rfl
 ... = inversa xs ++ []   : by rw inversa_equiv
 ... = inversa xs         : by rw append_nil

-- 2ª demostración
example : inversaAc xs = inversa xs :=
by simp [inversa_equiv]

-- 3ª demostración
example : inversaAc xs = inversa xs :=
by simp


-- Boilerplate:
import algebra.group.basic
import algebra.pointwise
import data.int.basic
open has_add has_neg
namespace examples

/-
Let us start with the following two Lean declarations, not found in \mathlib, showing the main forms that parameters can take in Lean.
-/
def sub {A : Type*} [add_group A] (a b : A) : A :=
add a (neg b)

lemma sub_eq_add_neg {A : Type*} [add_group A] (a b : A) :
  sub a b = add a (neg b) := by refl

/-
For example, defining `add_group ℤ` instance allows us to subtract two integers using the `sub` operator we defined above:
-/
instance : add_group ℤ := by apply_instance
lemma subtraction_example : (sub 42 37 : ℤ) = 5 := by refl
/-
Instance parameters are considered a form of implicit parameters, and can thus be made explicit using the `@` operator:
-/
#check sub
#check @sub
/-
Here `?M_1` stands for a free metavariable that the elaborator could not (yet) fill in.

Instance declarations can themselves have their own instance parameters.
For example, the subsets of a monoid form a monoid under pointwise operations, which we can express as
-/
open set

-- Adapted from `algebra/pointwise.lean:241`
instance pointwise_monoid {A : Type*} [monoid A] : monoid (set A) :=
{ mul := image2 has_mul.mul,
  mul_assoc := λ _ _ _, set.image2_assoc mul_assoc,
  one := {1},
  mul_one := λ s, by simp,
  one_mul := λ s, by simp }

/-!--------------
Class definitions
-----------------/

/-
Thus, a definition for `add_group` could look like:
-/
-- Adapted from `algebra/group/defs.lean:607`
class add_group (A : Type*) :=
(zero : A) (neg : A → A) (add : A → A → A)
(add_assoc : ∀ (x y z : A), add x (add y z) = add (add x y) z)
(zero_add : ∀ (x : A), add zero x = x)
(neg_add : ∀ (x : A), add (neg x) x = zero)
/-
The projections of a class automatically use instance parameters, generating the declarations:
-/
#print add_group.zero
#print add_group.neg
#print add_group.add
#print add_group.add_assoc
-- and so on.

/-!--------
Subclassing
----------/

/-
To define abelian groups as a subclass of additive groups, we write
-/
namespace unbundled
open examples.add_group

-- Adapted from `algebra/group/defs.lean:668`
class add_comm_group (A : Type*) [add_group A] :=
(add_comm : ∀ (x y : A), add x y = add y x)
end unbundled

namespace bundled
class add_comm_group (A : Type*) extends add_group A :=
(add_comm : ∀ (x y : A), add x y = add y x)
end bundled
/-
This has analogous effects to writing
-/
namespace bundled_expanded
class add_comm_group (A : Type*) :=
(to_add_group : add_group A)
(add_comm : ∀ (x y : A),
  @add_group.add A to_add_group x y = @add_group.add A to_add_group y x)
-- Register the projection as an instance:
attribute [instance] add_comm_group.to_add_group
end bundled_expanded

/-!--------------------------------
Extensions of the typeclass pattern
----------------------------------/

/-
For example, \mathlib uses a typeclass parametrized over a natural number $p$ to express the characteristic of a ring:
-/
-- Adapted from `algebra/char_p/basic.lean:21`
class char_p (R : Type*) [semiring R] (p : ℕ) : Prop :=
(cast_eq_zero_iff : ∀ (x : ℕ), (coe x : R) = 0 ↔ p ∣ x)

end examples