import Mathlib

/-!
# Propositions as Types

* We can represent propositions as types, or interpret (slightly restricted) types as propositions.
* A proof of a proposition is a term of the corresponding type.
* Defining and proving are essentially the same thing.
* The same foundational rules let us construct propostions and proofs as well as terms and types.
-/

/-!
## Main correspondence

* `A → B` is functions from `A` to `B` as well as `A` implies `B`.
* Function application is the same as *modus ponens*.
* So if we have propositions `A` and `B`, we can construct a proposition `A → B` corresponding to implication.
-/

universe u

def functionApplication {A B : Type u} (f : A → B) (a : A) : B := f a

def modus_ponens {A B : Prop} (h₁ : A → B) (h₂ : A) : B := h₁ h₂

/-!
## Other Combination

* `A ∧ B` is the conjunction of `A` and `B`.
* `A ∨ B` is the disjunction of `A` and `B`.
* `¬ A` is the negation of `A`.

All these can be constructed by our rules.

-/

#check And
#check Prod

/-!
## Conjuction and Product

```lean
structure And (a b : Prop) : Prop where
  /-- `And.intro : a → b → a ∧ b` is the constructor for the And operation. -/
  intro ::
  /-- Extract the left conjunct from a conjunction. `h : a ∧ b` then
  `h.left`, also notated as `h.1`, is a proof of `a`. -/
  left : a
  /-- Extract the right conjunct from a conjunction. `h : a ∧ b` then
  `h.right`, also notated as `h.2`, is a proof of `b`. -/
  right : b
```

This is the same as the product type:

```lean
structure Prod (α : Type u) (β : Type v) where
  /-- The first projection out of a pair. if `p : α × β` then `p.1 : α`. -/
  fst : α
  /-- The second projection out of a pair. if `p : α × β` then `p.2 : β`. -/
  snd : β
```
-/

inductive MyOr (A B : Prop) : Prop
| inl : A → MyOr A B
| inr : B → MyOr A B

#check Or
#check Sum

/-!
# Disjunction and Sum

```lean
inductive Or (a b : Prop) : Prop where
  /-- `Or.inl` is "left injection" into an `Or`. If `h : a` then `Or.inl h : a ∨ b`. -/
  | inl (h : a) : Or a b
  /-- `Or.inr` is "right injection" into an `Or`. If `h : b` then `Or.inr h : a ∨ b`. -/
  | inr (h : b) : Or a b
```

This is the same as the sum type:

```lean
inductive Sum (α : Type u) (β : Type v) where
  /-- Left injection into the sum type `α ⊕ β`. If `a : α` then `.inl a : α ⊕ β`. -/
  | inl (val : α) : Sum α β
  /-- Right injection into the sum type `α ⊕ β`. If `b : β` then `.inr b : α ⊕ β`. -/
  | inr (val : β) : Sum α β
```
-/

#check True
#check False

/-!
## True and False

These are analogues of `Unit` and `Empty`:

```lean
inductive True : Prop where
  /-- `True` is true, and `True.intro` (or more commonly, `trivial`)
  is the proof. -/
  | intro : True

inductive False : Prop
```
-/

/-!
## Negation

The negation of a type (or proposition) `A` is `¬ A`, which is the type (or proposition) `A → False`.

-/

theorem everything_implies_true (A : Prop) : A → True := fun _ => True.intro

theorem false_implies_everything (A : Prop) : False → A := fun h => nomatch h

#check False.rec -- False.rec.{u} (motive : False → Sort u) (t : False) : motive t

#check False.rec (motive := fun _ => 1 = 2) -- False → 1 = 2

example {A B : Prop}: A → B → A :=
  fun a _ ↦ a

#check Iff

/-!
## If and only if

```lean
structure Iff (a b : Prop) : Prop where
  /-- If `a → b` and `b → a` then `a` and `b` are equivalent. -/
  intro ::
  /-- Modus ponens for if and only if. If `a ↔ b` and `a`, then `b`. -/
  mp : a → b
  /-- Modus ponens for if and only if, reversed. If `a ↔ b` and `b`, then `a`. -/
  mpr : b → a
```
-/

example {A B : Prop}: (A ↔  B) =
      ((A → B) ∧ (B → A)) := by
  apply propext
  apply Iff.intro
  case a.mp =>
    intro h
    apply And.intro
    · exact h.mp
    · exact h.mpr
  case a.mpr =>
    intro h
    apply Iff.intro
    · exact h.left
    · exact h.right

/-!
## Universal Quantifiers

* A proposition associated to each term of a type `A` is a function `A → Prop`.
* The propostion `∀ x : A, P x` is the proposition that `P x` is true for all `x : A`, which is the dependent function type
`(x: A) → P x`.

-/

#check Exists

/-!
## Existential Quantifiers

```lean
inductive Exists {α : Sort u} (p : α → Prop) : Prop where
  /-- Existential introduction. If `a : α` and `h : p a`,
  then `⟨a, h⟩` is a proof that `∃ x : α, p x`. -/
  | intro (w : α) (h : p w) : Exists p
```
-/

#check Nat.le
#check Eq

/-!
## Basic types

We have seen `≤` can be constructed as indexed inductive type:

```lean
inductive Nat.le (n : Nat) : Nat → Prop
  /-- Less-equal is reflexive: `n ≤ n` -/
  | refl     : Nat.le n n
  /-- If `n ≤ m`, then `n ≤ m + 1`. -/
  | step {m} : Nat.le n m → Nat.le n (succ m)
```

The equality type is also indexed inductive type:

```lean
inductive Eq : α → α → Prop where
  /-- `Eq.refl a : a = a` is reflexivity, the unique constructor of the
  equality type. See also `rfl`, which is usually used instead. -/
  | refl (a : α) : Eq a a
```
-/

example {α β : Type}{f g : α → β} (x y : α ):
  f = g → x = y → f x = g y := by
  intro hfg hxy
  match f, g, hfg, x, y, hxy with
  | f, .(f), rfl, x, .(x), rfl => rfl


/-
Copyright (c) 2021 Alex Zhao. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alex Zhao
-/
import data.nat.modeq
import group_theory.submonoid.basic
import group_theory.submonoid.membership

/-!
# Frobenius Number in Two Variables

In this file we first define a predicate for Frobenius numbers, then solve the 2-variable variant
of this problem.

## Theorem Statement

Given a finite set of relatively prime integers all greater than 1, their Frobenius number is the
largest positive integer that cannot be expressed as a sum of nonnegative multiples of these
integers. Here we show the Frobenius number of two relatively prime integers `m` and `n` greater
than 1 is `m * n - m - n`. This result is also known as the Chicken McNugget Theorem.

## Implementation Notes

First we define Frobenius numbers in general using `is_greatest` and `add_submonoid.closure`. Then
we proceed to compute the Frobenius number of `m` and `n`.

For the upper bound, we begin with an auxiliary lemma showing `m * n` is not attainable, then show
`m * n - m - n` is not attainable. Then for the construction, we create a `k_1` which is `k mod n`
and `0 mod m`, then show it is at most `k`. Then `k_1` is a multiple of `m`, so `(k-k_1)`
is a multiple of n, and we're done.

## Tags

frobenius number, chicken mcnugget, chinese remainder theorem, add_submonoid.closure
-/

open nat

/-- A natural number `n` is the **Frobenius number** of a set of natural numbers `s` if it is an
upper bound on the complement of the additive submonoid generated by `s`.
In other words, it is the largest number that can not be expressed as a sum of numbers in `s`. -/
def is_frobenius_number (n : ℕ) (s : set ℕ) : Prop :=
is_greatest {k | k ∉ add_submonoid.closure (s)} n

variables {m n : ℕ}

/-- The **Chicken Mcnugget theorem** stating that the Frobenius number
  of positive numbers `m` and `n` is `m * n - m - n`. -/
theorem is_frobenius_number_pair (cop : coprime m n) (hm : 1 < m) (hn : 1 < n) :
  is_frobenius_number (m * n - m - n) {m, n} :=
begin
  simp_rw [is_frobenius_number, add_submonoid.mem_closure_pair],
  have hmn : m + n ≤ m * n := add_le_mul hm hn,
  split,
  { push_neg,
    intros a b h,
    apply cop.mul_add_mul_ne_mul (add_one_ne_zero a) (add_one_ne_zero b),
    simp only [nat.sub_sub, smul_eq_mul] at h, zify at h ⊢,
    rw [← sub_eq_zero] at h ⊢, rw ← h,
    ring, },
  { intros k hk, dsimp at hk, contrapose! hk,
    let x := chinese_remainder cop 0 k,
    have hx : x.val < m * n := chinese_remainder_lt_mul cop 0 k (ne_bot_of_gt hm) (ne_bot_of_gt hn),
    suffices key : x.1 ≤ k,
    { obtain ⟨a, ha⟩ := modeq_zero_iff_dvd.mp x.2.1,
      obtain ⟨b, hb⟩ := (modeq_iff_dvd' key).mp x.2.2,
      exact ⟨a, b, by rw [mul_comm, ←ha, mul_comm, ←hb, nat.add_sub_of_le key]⟩, },
    refine modeq.le_of_lt_add x.2.2 (lt_of_le_of_lt _ (add_lt_add_right hk n)),
    rw nat.sub_add_cancel (le_tsub_of_add_le_left hmn),
    exact modeq.le_of_lt_add
      (x.2.1.trans (modeq_zero_iff_dvd.mpr (nat.dvd_sub' (dvd_mul_right m n) dvd_rfl)).symm)
      (lt_of_lt_of_le hx le_tsub_add), },
end


import init.meta.well_founded_tactics
import tactic.norm_num
open well_founded
open nat
def euclid_n : nat → nat → nat
| 0        b:= 0
| (succ c) b := have b % succ c < succ c, from mod_lt _ $ succ_pos _,
                succ (euclid_n (b % succ c) (succ c))
                --copied from mathlib gcd definition
def euclid_r: ℕ→ℕ→ℕ→ℕ
    | b c 0        := b
    | b c (succ n) := euclid_r b c n
theorem r_gcd: ∀ b c n:ℕ,euclid_n c b= succ n→euclid_r b c n = gcd c b:=begin
    assume b c n,
    induction c with c1 Hi,
    cases n with n1,unfold euclid_r,
end
theorem decreasingd: ∀ b c n:ℕ ,euclid_n c b =n → euclid_r b c n=gcd b c
| b 0 0                 :=by {unfold euclid_r euclid_n,simp}
| b (succ c) 0          :=by {unfold euclid_n,simp [succ_ne_zero]}
| b 0 (succ n)          :=by {unfold euclid_n,rw eq_comm,simp [succ_ne_zero]}
| b (succ c) (succ n)   :=by{unfold euclid_r euclid_n,intro, rw gcd exact decreasingd (succ c) (b%succ c) n (succ_inj a)}
def euclid_q: ℕ→ℕ→ℕ→ℕ
    | b c 0     := 0
    | b c (succ n) := euclid_r c (b%c) n / euclid_r b c n
def euclid_x: ℕ→ℕ→ℕ→ℤ
    | b c 0     := 0
    | b c (succ n) := euclid_x c (b%c) n - euclid_q b c (succ n) * euclid_x b c n

theorem bezout: ∀ b c:ℕ,∃x y:ℤ, ↑b*x+↑c*y = ↑(gcd b c):=begin
    assume b c,
    apply gcd.induction b c,
    unfold gcd,intro n,existsi (10909301:int),existsi (1:int),simp,
    assume m n Hm,assume Hmn,cases Hmn with x1 Hx,cases Hx with y1 Hxy,
    cases m with m,simp [lt_irrefl] at Hm,revert Hm,cc,
    unfold gcd,rw ←Hxy,existsi -↑(n/succ m)*x1+y1,existsi x1,
    have H: n%succ m=n-(succ m)*(n/(succ m)):=begin rw [eq_comm,nat.sub_eq_iff_eq_add,mod_add_div n (succ m)],rw mul_comm,exact div_mul_le_self n (succ m) end,
    rw H,rw [int.coe_nat_sub,mul_add,add_assoc,add_comm (↑(succ m) * y1) (↑n * x1),←add_assoc,add_right_cancel_iff,mul_sub_right_distrib,int.coe_nat_mul],norm_num,
    rw mul_comm,exact div_mul_le_self n (succ m),
end
#print gcd.induction


import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level5 -- one_eq_succ_zero
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 2: `mul_one`

In this level we'll need to use

* `one_eq_succ_zero : 1 = succ 0`

which was mentioned back in [Addition World Level 5](../AdditionWorld/Level5.lean.md) and which will
be a useful thing to rewrite right now, as we begin to prove a couple of lemmas about how `1`
behaves with respect to multiplication.

## Lemma

For any natural number `m`, we have `m * 1 = m.`
-/
lemma mul_one (m : MyNat) : m * 1 = m := by
  rw [one_eq_succ_zero]
  rw [mul_succ]
  rw [mul_zero]
  rw [zero_add]

/-!
Notice how all our theorems are nicely building on each other.

Next up is [Multiplication Level 3](./Level3.lean.md).
-/


/-
| REESE ALLEN (rga2uz)
| CS2102 S20 Sullivan
| Final Exam
-/

/-
1. You know from our study of the Boolean
satisfiability problem that there are 2^n
possible combinations of Boolean values for
n Boolean variables.

Let's make the property of
natural numbers that is at issue clear by
defining "P n" to be proposition that "the
number of possible combinations of values
for *n* Boolean variables is *2^n*."

What your are to prove is the proposition
that this is true for any value of n. That
is, you are to prove ∀ n, P n.

Mext, recall that a proof of a universal
generalization (such as ∀ n, P n) *by induction*
is based on the application of the *induction
principle* for for the given data type. Here
the data type is ℕ, and the induction rule for
ℕ is as follows:

∀ {P : Prop},
P 0 →
(∀ n', P n' → P (n' + 1)) →
∀ n, P n.

Reading backwards, this says that if you
want to prove ∀ n, P n, it will *suffice*
to prove P 0 and ∀ n', P n' → P (n' + 1)).
The reason is that you can then apply the
rule to deduce that ∀ n, P n must be true.
In other words, you can reduce the task of
proving ∀ n, P n to the tasks of proving
the two antecedents of this conclusion in
the induction rule.
-/

/-
*ANSWER*

Theorem: For any natural number, n, the
number of combinations of values for n
Boolean variables is 2^n.

Proof: By induction. To prove ∀ n, P n,
where P is defined to be the proposition,
"the number of possible combinations of
values for n Boolean variables is 2^n,"
it will suffice to prove P 0 and
∀ n', P n' → (n' + 1)

Here is a more complete quasi-formal version
of the proof with algebra:

Our aim is to prove the predicate [P(n)], which
says that for any ℕ n, the number of possible
combinations of values, i.e. interpretations, for
n Boolean variables is 2^n. We do so inductively
and must first prove / show two things: [1] The
proposition P is true for n = 0, our base case.
[2] If we assume the number of combinations for n'
Boolean variables is 2^n' for some arbitrary but
specific ℕ n', our induction hypothesis, then the
number of combinations of (n' + 1) Boolean
variables is 2^(n' + 1).

By the principle of induction, through assuming
P(n') is true and using it to prove that P(n' + 1)
is true, we can conclude that P(n) is true for
all ℕs n.

[1] This is easy. Our base case says that for zero
Boolean variables, there is one combination.
We treat P(0) = 1 almost like an axiom. We have
defined the number of combinations of values for
0 Boolean variables to be 1. This is certainly true.

[2] We can prove this second step algebraically. I'm
going to use k here to represent n' because it is a
bit cleaner to read. Assuming P(k) = 2^k is true,
we want to show P(k + 1) = 2^(k + 1) is true.
2^(k + 1) is equivalent to (2^k) + (2^1) by our
rules of exponents, which is equivalent to
(2^k) * 2; it follows that P(k + 1) = P(k) * 2.
Because P(k) is true for any k, it must be that
P(n) is true for any n.
-/

/-  2  -/

def aProp :=    ∀ (α : Type), ∀ (Heavy Charmed: α → Prop),
                (∃ (a : α), Heavy a ∧ Charmed a) →
                (∃ (a : α), Charmed a)

/-
2a. English language rendition of this proposition:

The proposition aProp says suppose that you have
ojects of some type α and that Heavy and Charmed
are properties of type α. If there exists an
object a of type α that is both Heavy and Charmed,
then there exists some object that is only Charmed.
-/

/- 2b. Give a formal proof of it. -/

example : aProp :=
λ α : Type,
    λ H C : α → Prop,
        λ hac : ∃ (a : α), H a ∧ C a,
        match hac with
        | exists.intro obj pf := let Cobj := pf.right in
            exists.intro obj Cobj
        end

/-
2c. Quasi-formal, English-language proof:

To prove the proposition aProp, we suppose that objects
of type α exist and that Heavy and Charmed are predicates
that take an object of type α as an argument and return a
proposition, which could be true or false. We will just
say that heavy and charmed are properties of these
objects. Denoting an object of type α as a and the
predicates Heavy and Charmed as H and C, respectively,
when we say that there is a proof of heavy and charmed,
for example, what we mean is that there is a proof that
the conjunction of H(a) and C(a) is true.

In this context, we want to prove an implication by
showing that if there is an object that is both heavy
and charmed, then there is an object that is charmed.
Given a proof that objects of type α exist that are both
heavy and charmed, which we call hac, we can deduce that
there is an object that has the properties of heavy and
charmed. We assume this by reasoning through case analysis
and with existential instantiation, i.e. the exists
introduction rule. We call our single arbitrary but
specific object of type α obj. The only case in which our
proof hac could have been constructed is by applying the
exists introduction rule to obj and a proof that heavy
and charmed is true, i.e. that obj has the properties of
heavy and charmed, which we call pf. The proof pf is
constructed through conjunction introduction, another rule
of inference. We can destructure pf with the right
elimination rule; given a proof of heavy and charmed, we
can derive a proof of just charmed, which we call Cobj.
We can then use instantiation again by applying the exists
introduction rule to our object obj and our proof of
charmed Cobj. By showing that a proof that an object of
type α can be constructed from a proof that objects of
type α exist with the properties of heavy and charmed,
we have proved aProp.
-/

/-  3  -/

def aProp2 :=   ∀ (α : Type), ∀ (Heavy Charmed: α → Prop),
                (∃ (a : α), Heavy a ∨ Charmed a) →
                (∃ (a : α), Heavy a) ∨ (∃ (a : α), Charmed a)

/-
3a. English language rendition of this proposition:

The proposition aProp2 says suppose that you have
objects of some type α and that Heavy and Charmed
are properties of type α. If there exists an
object a of type α that is Heavy or Charmed, then
there exists an object that is just Heavy or there
exists an object that is just Charmed.
-/

/- 3b. Give a formal proof of it. -/

example : aProp2 :=
λ α : Type,
    λ H C : α → Prop,
        λ hoc : ∃ (a : α), H a ∨ C a,
        match hoc with
        | exists.intro obj pf :=
            match pf with
            | or.inl h := or.inl (exists.intro obj h)
            | or.inr c := or.inr (exists.intro obj c)
            end

        end

/-
3c. Quasi-formal, English-language proof:

To prove the proposition aProp2, we suppose that objects
of type α exist and that Heavy and Charmed are predicates
that take an object of type α as an argument and return a
proposition, which could be true or false. We will just
say that heavy and charmed are properties of these objects.

When we say that there is a proof of heavy or charmed,
for example, what we mean is that there is a proof that
the disjunction of the predicates Heavy and Charmed, when
each is given an object of type α, is true. This is
analogous to saying that the object has the property of
heavy, the property of charmed, or both.

In this context, we want to prove an implication
by showing that if there is an object that is heavy or
charmed, then there is an object that is heavy or there
is an object that is charmed. Given a proof that objects of
type α exist that are heavy or charmed, which we call hoc,
we can deduce that there is an object that has the properties
of heavy or charmed. We assume this by reasoning through
case analysis and with existential instantiation, i.e. the
exists introduction rule. We call our single arbitrary but
specific object of type α obj. The only case in which our
proof hoc could have been constructed is by applying the
exists introduction rule of inference to obj and a proof
that obj is heavy or charmed, which we call pf. The proof
pf is constructed through disjunction introduction. Using
case analysis again, we can destructure pf to show the two
ways in which it could have been formed. A proof of a
disjunction is constructed either from a proof of the left
disjunct or a proof of the right disjunct. We assume that
our object obj has the property of heavy in the first case,
the proof of which we denote as h, and that obj has the
property of charmed in the second case, which we denote as c.
To prove the first case, we use the left elimination rule
applied to a proof that there exists an object that is heavy,
which is given through instantiation by applying the exists
introduction rule to our object obj and our proof h. Similarly,
to prove the second case, we use the right elimination rule
applied to a proof that there exists an object that is charmed,
which is given through instantiation by applying the exists
introduction rule to our object obj and our proof c. By showing
that a proof that an object of type α has the property of heavy
and a proof that an object of type α has the property of
charmed can be constructed from a proof that objects of type
α exists that are heavy or charmed, we have proved aProp2.
-/

/- 4.
Formally specify the syntax and semantics
of a language of *arithmetic* expressions that
can include variables, where the meaning of
an expression is (reduces to) a natural number.

Hint: model your answer on our specification
of the syntax and semantics of *propositional
logic* expressions.
-/

/- In this language, an interpretation will
map variables to natural numbers rather than
to Boolean values. We'll give you a start on
a solution by defining (1) a type of variables
that are distinguished from one another by a
ℕ-valued index, (2) specifying the type of an
interpretation, and (3) giving an example of
an interpretation in which all variables have
the value zero.
-/

-- our variable type
structure a_var : Type := mk :: (index : ℕ)

-- friendly names for a few variables
def X_var := a_var.mk 0
def Y_var := a_var.mk 1
def Z_var := a_var.mk 2

-- the type of an interpretation
def interp := a_var → ℕ

-- one possible interpretation, "all zero"
def all_zero_interp : interp := λ v, 0

/-
4a. Define an interpretation in which X has
value 3, Y has value 7, and Z has value 1,
and all other variables have value 0.
-/

def an_interp : interp := λ v,
match v with
| a_var.mk 0 := 3
| a_var.mk 1:= 7
| a_var.mk 2 := 1
| _ := 0
end

/-
4b. Define the syntax of your language to have
the following kinds of expressions.

- ℕ literal expression
- ℕ variable expression
- expression + expression
- expression * expression

Do this by defining an inductive type, aexp,
the values of which are arithmetic expressions
in our language. Call your constructors lit,
var, add, and mul. When you succeed, the test
expressions we give you should type check.
-/

inductive aexp : Type
| lit : ℕ → aexp
| var : a_var → aexp
| add : aexp → aexp → aexp
| mul : aexp → aexp → aexp

open aexp

-- These expressions should type-check
def X := aexp.var X_var
def Y := aexp.var Y_var
def Z := aexp.var Z_var
def l6 := aexp.lit 6
def e1 := aexp.add X Y
def e2 := aexp.mul X Z
def e3 := aexp.mul e1 l6

/-
4c. Define a semantics for your
language so that expressions evaluate
to natural numbers as they would using
the standard notions of addition and
multiplication. Furthermore, literal
expressions should evaluate to their
natural numbers arguments, and variable
expressions should evaluate the natural
numbers according to an interpretation
given to the evaluation function (call
it aEval). Remember to put constructor
expressions in parentheses when using
pattern matching / destructuring. When
you've succeeded, the test cases we've
provided should all pass.
-/

-- Your answer here
def aEval : aexp → interp → ℕ
| (lit n) _ := n
| (var av) i  := i av
| (add ae1 ae2) i := (aEval ae1 i) + (aEval ae2 i)
| (mul ae1 ae2) i := (aEval ae1 i) * (aEval ae2 i )

-- Test cases that should pass when you've succeeded
example : aEval X an_interp = 3 := rfl
example : aEval Y an_interp = 7 := rfl
example : aEval Z an_interp = 1 := rfl
example : aEval l6 all_zero_interp = 6 := rfl
example : aEval e1 all_zero_interp = 0  := rfl
example : aEval e2 an_interp = 3 := rfl
example : aEval e3 an_interp = 60 := rfl

/-
5. Explain concisely but also precisely how a
proof of a proposition, P, "by contradiction"
would be carried out. Be sure to point out
exactly where negation elimination is involved.

Then explain concisely and precisely how a
proof of a proposition, ¬ P, would be carried
out.

To carry out a proof of a proposition P by contradiction,
we assume ¬P and show that this leads to a contradiction.
Negation elimination essentially allows us to delete
double-negatives and is given by the law of the excluded
middle, which we treat as an axiom in Lean. Specifically,
classical.em : ∀ (P : Prop), P ∨ ¬ P. It allows us to
assume that for any proposition, either that proposition
is true or its negation is true

In order to carry out a proof of a proposition ¬ P, we
use proof by negation. We start by assuming that there is
a proof that the proposition is true and show that this
enables us to construct a proof of false. A proof of false
does not exist, so if assuming P allows us to construct one,
then the assumption that P is true must be wrong, i.e. ¬ P
is true. ¬ P is equivalent to P → false. To prove ¬P, we just
show that if P is true then false is true. An example would
be to use case analysis to prove the implication P → false.
Assuming P, we match the proof of P with a constructor, which
is an empty match statement (there are no cases). This shows
that P implies false, so we have proved that ¬ P, the negation
of P, must be true.

-/

/- 6. A simplish proof. Give a formal proof
of the following proposition. Then explain
briefly in English why the proposition must
be true no matter what propositions P and Q
are.
-/

example : ∀ (P Q R : Prop), ¬ ((P ∧ ¬ Q) ∧ (Q ∧ ¬ R)) :=
λ P Q R,
    λ h,
    match h with
    | and.intro panq qanr :=
        let nq := panq.right in
        let q := qanr.left in
        nq q
    end

/-
Our goal is to prove that for any propositions P, Q, and R,
it is not the case that the conjunction of P and not Q, the
left conjunct, and Q and not R, the right conjunct, is true.
This is to show that (P ∧ ¬ Q) ∧ (Q ∧ ¬ R) implies false. We
start by assuming that we have propositions P, Q, and R and
a proof, which we call h, that (P ∧ ¬ Q) ∧ (Q ∧ ¬ R) is true.
We reason by case analysis and show that to construct h, the
and introduction rule is applied to the left and right conjuncts.
Using the left and right elimination rules applied to the
left and right conjuncts of h respectively, we arrive at a proof,
or a construction, of ¬ Q ∧ Q, which is false. By showing that
(P ∧ ¬ Q) ∧ (Q ∧ ¬ R) leads to a contradiction, we have proved
that h implies false, i.e. ¬ ((P ∧ ¬ Q) ∧ (Q ∧ ¬ R)) is true.

-/


/-
Proof strategies.

- DIRECT PROOF: from established facts

- BY NEGATION: to prove ¬ P, assume P;
  show that this yields a contradiction.
  From this a proof of false can then
  be derived. This shows P → false. And
  that is the definition of ¬P.

  Example: Prove that the square root of
  two is irrational.

  It sounds like we're trying to prove P,
  where P := "sqrt(2) is irrational". But
  it's better to take P to be the proposition
  that "sqrt(2) is rational", and to view
  the goal as to prove ¬ P.

  Prove that the square root two is NOT
  rational.

  Prove "¬ sqrt(2) is rational".

  Proof: BY NEGATION. assume sqrt(2) is
  rational. In this case, we can write
  sqrt(2) as some fraction, a/b (for that
  is what it means for a number to be
  rational). We now want to show that
  this leads to a contradiction so that
  we can conclude that our assumption is
  false, thus ¬ P is true. Details left
  as an exercise.

  - BY CONTRADICTION : to prove P, assume
    ¬ P and show that this leads to a
    contradiction, from which a proof of
    false can be derived. This proves
    ¬ P → false, which is equivalent to
    ¬ ¬ P. The apply the *classical* rule
    of negation elimination to deduce P.

    Classical rule of negation elimination:
    ∀ (P : Prop), ¬ ¬ P → P.

    Prove 0 = 0 by contradiction.

    We want to prove P (0 = 0). Assume
    ¬ (0 = 0), and show that this leads
    to a contradiction. But by the
    reflexive property of equality, which
    says everything is equal to itself,
    we know immediate that 0 = 0. That
    gives us a direct contradiction,
    between ¬ (0=0) and (0=0). From
    such a contradiction we can derive
    a proof of false, showing that
    ¬ (0=0) → false. And this just means
    ¬ (¬ 0=0). The by classical negation
    elimination, this implies 0 = 0.

    BY CASE ANALYSIS. Show that for any
    possible form that an assumed value
    can take (e.g., nat.zero or nat.succ
    n' for some n'), i.e., for each of
    its possible constructors, that the
    goal must be true. Then in must be
    true in ALL cases, which proves that
    it is valid.

    - Today: proof BY INDUCTION.
-/

-- Answer to question about why contradictions imply false
axiom P : Prop
axiom p : P
axiom np : ¬ P  /- P → false -/
#check (np p)

/-
Prove this: ∀ (m : ℕ), my_add 0 m = m
-/

def my_add : ℕ → ℕ → ℕ
| nat.zero m := m
| (nat.succ n') m := nat.succ (my_add n' m)

-- 0 + m = m
-- (1 + n') + m = 1 + (n' + m)

/-
Proof: By the definition of addition,
and specifically by the first of the
two cases, which tells us that for any
m, 0 + m = m.
-/

/-
Many proofs are accomplished by mere
simplification of both sides of some
equation *using function definitions*
that are already known.
-/

/-
If one is being precise, however, there
are some unexpected consequences. One is
that sometimes something that looks easy
turns out to be a bit more complicated.

For example, try to prove this (using)
only what we know so far.
-/

example : ∀ (n : ℕ), my_add n 0 = n := _

/-
We have no rule (yet) for adding zero
on the right, so simplifying using the
definition of my_add doesn't work.
Instead, we need to try a whole different
proof strategy.
-/

/-
Here's the idea:

- every inductively defined type has a
  corresponding induction rule, which  is
  a rule for showing that some predicate
  is true for *every* value of that type

- Here's induction principle for the ℕ type:
  ∀ P : (ℕ → Prop),
  P 0 →
  ((∀ n' : ℕ), P n' → P (nat.succ n')) →
  ∀ n, P n

In other words, for any property of natural
numbers (a predicate that takes a natural
number as an argument), *if* 0 satisfies
the predicate and whenever any n' satisfies
it, so does n'+1, then the predicate is
true/satisfied by *all* natural numbers.

Think of the a proof of (P 0) as "showing that
the first domino falls", and (P n' → P (n' + 1))
as showing that "whenever *any* domino falls, so
does the next one."" Together these proofs show
that all the dominos fall.

[THIS IS THE KEY THING TO REMEMBER: The
induction principle. Mathematical induction
is nothing but the induction principle for
ℕ. There is a corresponding induction rule
for every inductively defined type.]

In other words, for any predicate/property
P, to show that ∀ (n : ℕ), P n, it suffices
to show the following:
    (1) P 0,
    (2) (∀ n' : ℕ), P n' → P (nat.succ n')
-/

/-
The next key idea in a proof by induction
is that when proving (2), you get to *assume*
that P holds for some arbitrary n' and all you
have to do is to show that in this context it
must also hold for n'+1.
-/

/-
Example: We want to prove ∀ n, n + 0 = n.
Proof by induction. If suffices to show that
(1) Case: n = 0. We need to show 0 + 0 = 0.
    This is trivially proved by applying the first rule of my_add.
    So we have now proved (P 0).
(2) Case: n = nat.succ n':
    Show P n' → P (nat.succ n').
    In other words, show n'+ 0 =n' → (n' + 1) + 0 = (n' +1).
    Assume P n'? I.e., assume that: my_add n' 0 = n'
    Now show P (nat.succ n'): that my_add (nat.succ n') 0 = (nat.succ n').
    *** simplify: my_add (nat.succ n' 0) =
                  nat.succ (my_add n' 0) = --2nd rule of my_add
    *** apply induction hypothesis, that my_add n' 0 = n'.
    Now we have my_add (nat.succ n' 0) = nat.succ n'.
    We have thus now proved (2), that P n' → P (nat.succ n').
    Applying the induction principle to these proofs yields a proof
    of ∀ n, n + 0 = n.
    QED.

def my_add : ℕ → ℕ → ℕ
| nat.zero m := m
| (nat.succ n') m := nat.succ (my_add n' m)
-/

/-
∀ n, Sum numbers from 0 to n = n * (succ n) / 2.

Proof by induction. We will apply the principle of
induction for the natural numbers to two smaller
proofs: one for n = 0, and one that shows that if
the formula is true for some n' > 0, then it must
also be true succ n'.

Base case: prove (P 0): show sum from 0 to 0 = (0 * 1)/2 = 0.
Inductive case: Show  P n' → P (n' + 1)
Assume P n'. The sum from 0 to n' = n' * (n' + 1) / 2.
Show (P (n' + 1)): The sum from 0 to n'+1 = (n'+1)((n'+1)+1)/2.
Do some algebra!

--- intuition
1+2+3+4+5 if we assume this is 5*6/2
(1+2+3+4+5)+6 show this is 6*7/2
sum 0 to 5 + 6! = 5*6/2 + 6

The sum from 0 to (n' + 1) = sum from 0 to n' + (n' + 1)
                           = (n'*(n'+1)/2) + (n' + 1)
                           ...
                           = (n'+1)((n'+1)+1)/2.

-/


/-
If P is a proposition then so is ¬P.
-/

axiom excl_middle : ∀ (P : Prop), P ∨ ¬ P

axiom KevinIsFromCville : Prop

example : KevinIsFromCville ∨ ¬ KevinIsFromCville :=
excl_middle KevinIsFromCville

/-
To construct a proof of Π for some proposition P,
we need to show that there can be no proof of P.
To show there can be no proof of P we assume that
there is a proof of P and show that this leads to
a contradition.

What do we mean by a contradiction.
-/

#check false

namespace hidden

def not (P : Prop) : Prop := P → false

end hidden

#check not

example : ¬ 0 = 1 :=
begin
  show not (0 = 1),
  -- the result of this application
  -- is the proposition (0=1) → false.
  assume h,
  cases h,
end

example : false → KevinIsFromCville :=
begin
  assume f,
  cases f,
end

/-
For any proposition at all, if false is
true then that proposition, P, is true.
One way to understand this is that if false
is true, then anything is true, because if
it's true, it's true, and if it's false it's
also true.
-/
theorem exfalso : ∀ (P : Prop), false → P :=
begin
  intro P,
  assume f,
  cases f,
end

theorem non_contradition : ∀ (P : Prop), ¬ (P ∧ ¬P) :=
begin
  intro P,
  assume h,
  cases h with p np,
  have f := _,
end

/-
Is the following proposition valid in Lean
-/

example : ∀ (P : Prop), ¬ ¬ P → P :=
begin
  intro P,
  assume h,
  -- no use of classical reasoning allowed.
  -- stuck! this is not a theorem in
  -- the constructive logic of Lean
  -- or Coq, or ...
end

#check classical.em

/-
∀ (p : Prop), p ∨ ¬p
-/

example : ∀ (P : Prop), ¬¬ P → P :=
begin
  intro P,
  assume h,
  /-
  At this point we're stuck! All we
  have to work with is our assumed
  proof of ¬¬P, but all that tells
  us is that *if* we have a proof
  of ¬P we can derive a proof of
  false (which which we could deduce
  P); but we have no proof of ¬P to
  use. The bottom line is that ¬¬P→ P
  is *not* valid in the constructive
  logic of Lean. On the other hand, as
  we now show, if we accept the law of
  the excluded middle, we can finish
  the proof. The steps are (1)use em
  to derive a proof of P ∨ ¬P, then
  (2) show that in either case, P is
  true, in the first case by assumption
  and in the second case by contradiction.
  -/
  have o := classical.em P,
  cases o,
  assumption,     -- case #1: P is true
  contradiction,  -- case #2: ¬P is true
  /-
  If you're not sure what "contradiction
  is doing here, comment it out and then
  uncomment the following two lines, which
  make clear that from a contradiciton we
  can derive a proof of false, and then we
  can use false elimination (case analysis
  where to prove the conclusion follows in
  all cases requires no proofs at all as
  there are no possible cases for a proof
  of false to consider)."

  have f := h o,    -- <<< understand this!
  cases f,
  -/
end

/-
If we assume the law of the excluded middle,
then negation elimination, ¬¬P → P, is valid.

That ¬¬P → P is valid is the essence of proof
by contradiction.

Let's start by noting that we have now seen
two different proof techniques involving
contradictions.

In "proof by negation," we prove ¬P by
proving P → false, assuming P and showing
that from that assumption we can derive
a proof false. That is, to prove ¬P we
prove P → false.

By contrast, proof by contradiction seeks
to prove P (rather than ¬ P) by assuming
¬P and showing that *that* assumption leads
to a contradiction. That is, we seek to
show ¬P → false, which is just what we mean
by ¬¬P. *THEN*, having shown ¬¬P, we apply
(classical) negation elimination (¬¬P → P)
to derive a proof of P, our overall goal.

What we've seen today, then, is that while
proof by negation is "constructively valid"
proof by contradiction is *not*. However it
is valid if we assume the classical axiom
of the excluded middle. That axiom is what
allows us to infer P from ¬¬P, which is the
last step in a proof by contradiction, as
we have just seen.
-/


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

import data.int.basic
import algebra.field

/-!
# Euclidean domains

This file introduces Euclidean domains and provides the extended Euclidean algorithm. To be precise,
a slightly more general version is provided which is sometimes called a transfinite Euclidean domain
and differs in the fact that the degree function need not take values in `ℕ` but can take values in
any well-ordered set. Transfinite Euclidean domains were introduced by Motzkin and examples which
don't satisfy the classical notion were provided independently by Hiblot and Nagata.

## Main definitions

* `euclidean_domain`: Defines Euclidean domain with functions `quotient` and `remainder`. Instances
  of `has_div` and `has_mod` are provided, so that one can write `a = b * (a / b) + a % b`.
* `gcd`: defines the greatest common divisors of two elements of a Euclidean domain.
* `xgcd`: given two elements `a b : R`, `xgcd a b` defines the pair `(x, y)` such that
  `x * a + y * b = gcd a b`.
* `lcm`: defines the lowest common multiple of two elements `a` and `b` of a Euclidean domain as
  `a * b / (gcd a b)`

## Main statements

* `gcd_eq_gcd_ab`: states Bézout's lemma for Euclidean domains.
* `int.euclidean_domain`: shows that `ℤ` is a Euclidean domain.
* `field.to_euclidean_domain`: shows that any field is a Euclidean domain.

## Notation

`≺` denotes the well founded relation on the Euclidean domain, e.g. in the example of the polynomial
ring over a field, `p ≺ q` for polynomials `p` and `q` if and only if the degree of `p` is less than
the degree of `q`.

## Implementation details

Instead of working with a valuation, `euclidean_domain` is implemented with the existence of a well
founded relation `r` on the integral domain `R`, which in the example of `ℤ` would correspond to
setting `i ≺ j` for integers `i` and `j` if the absolute value of `i` is smaller than the absolute
value of `j`.

## References

* [Th. Motzkin, *The Euclidean algorithm*][MR32592]
* [J.-J. Hiblot, *Des anneaux euclidiens dont le plus petit algorithme n'est pas à valeurs finies*]
  [MR399081]
* [M. Nagata, *On Euclid algorithm*][MR541021]

## Tags

Euclidean domain, transfinite Euclidean domain, Bézout's lemma
-/

universe u

section old_structure_cmd
set_option old_structure_cmd true

/-- A `euclidean_domain` is an `integral_domain` with a division and a remainder, satisfying
  `b * (a / b) + a % b = a`. The definition of a euclidean domain usually includes a valuation
  function `R → ℕ`. This definition is slightly generalised to include a well founded relation
  `r` with the property that `r (a % b) b`, instead of a valuation.  -/
@[protect_proj without mul_left_not_lt r_well_founded]
class euclidean_domain (R : Type u) extends comm_ring R, nontrivial R :=
(quotient : R → R → R)
(quotient_zero : ∀ a, quotient a 0 = 0)
(remainder : R → R → R)
(quotient_mul_add_remainder_eq : ∀ a b, b * quotient a b + remainder a b = a)
(r : R → R → Prop)
(r_well_founded : well_founded r)
(remainder_lt : ∀ a {b}, b ≠ 0 → r (remainder a b) b)
(mul_left_not_lt : ∀ a {b}, b ≠ 0 → ¬r (a * b) a)
end old_structure_cmd

namespace euclidean_domain
variable {R : Type u}
variables [euclidean_domain R]

local infix ` ≺ `:50 := euclidean_domain.r

@[priority 70] -- see Note [lower instance priority]
instance : has_div R := ⟨euclidean_domain.quotient⟩

@[priority 70] -- see Note [lower instance priority]
instance : has_mod R := ⟨euclidean_domain.remainder⟩

theorem div_add_mod (a b : R) : b * (a / b) + a % b = a :=
euclidean_domain.quotient_mul_add_remainder_eq _ _

lemma mod_add_div (a b : R) : a % b + b * (a / b) = a :=
(add_comm _ _).trans (div_add_mod _ _)

lemma mod_add_div' (m k : R) : m % k + (m / k) * k = m :=
by { rw mul_comm, exact mod_add_div _ _ }

lemma div_add_mod' (m k : R) : (m / k) * k + m % k = m :=
by { rw mul_comm, exact div_add_mod _ _ }

lemma mod_eq_sub_mul_div {R : Type*} [euclidean_domain R] (a b : R) :
  a % b = a - b * (a / b) :=
calc a % b = b * (a / b) + a % b - b * (a / b) : (add_sub_cancel' _ _).symm
... = a - b * (a / b) : by rw div_add_mod

theorem mod_lt : ∀ a {b : R}, b ≠ 0 → (a % b) ≺ b :=
euclidean_domain.remainder_lt

theorem mul_right_not_lt {a : R} (b) (h : a ≠ 0) : ¬(a * b) ≺ b :=
by rw mul_comm; exact mul_left_not_lt b h

lemma mul_div_cancel_left {a : R} (b) (a0 : a ≠ 0) : a * b / a = b :=
eq.symm $ eq_of_sub_eq_zero $ classical.by_contradiction $ λ h,
begin
  have := mul_left_not_lt a h,
  rw [mul_sub, sub_eq_iff_eq_add'.2 (div_add_mod (a*b) a).symm] at this,
  exact this (mod_lt _ a0)
end

lemma mul_div_cancel (a) {b : R} (b0 : b ≠ 0) : a * b / b = a :=
by rw mul_comm; exact mul_div_cancel_left a b0

@[simp] lemma mod_zero (a : R) : a % 0 = a :=
by simpa only [zero_mul, zero_add] using div_add_mod a 0

@[simp] lemma mod_eq_zero {a b : R} : a % b = 0 ↔ b ∣ a :=
⟨λ h, by rw [← div_add_mod a b, h, add_zero]; exact dvd_mul_right _ _,
 λ ⟨c, e⟩, begin
  rw [e, ← add_left_cancel_iff, div_add_mod, add_zero],
  haveI := classical.dec,
  by_cases b0 : b = 0,
  { simp only [b0, zero_mul] },
  { rw [mul_div_cancel_left _ b0] }
 end⟩

@[simp] lemma mod_self (a : R) : a % a = 0 :=
mod_eq_zero.2 (dvd_refl _)

lemma dvd_mod_iff {a b c : R} (h : c ∣ b) : c ∣ a % b ↔ c ∣ a :=
by rw [dvd_add_iff_right (dvd_mul_of_dvd_left h _), div_add_mod]

lemma lt_one (a : R) : a ≺ (1:R) → a = 0 :=
by haveI := classical.dec; exact
not_imp_not.1 (λ h, by simpa only [one_mul] using mul_left_not_lt 1 h)

lemma val_dvd_le : ∀ a b : R, b ∣ a → a ≠ 0 → ¬a ≺ b
| _ b ⟨d, rfl⟩ ha := mul_left_not_lt b (mt (by rintro rfl; exact mul_zero _) ha)

@[simp] lemma mod_one (a : R) : a % 1 = 0 :=
mod_eq_zero.2 (one_dvd _)

@[simp] lemma zero_mod (b : R) : 0 % b = 0 :=
mod_eq_zero.2 (dvd_zero _)

@[simp, priority 900] lemma div_zero (a : R) : a / 0 = 0 :=
euclidean_domain.quotient_zero a

@[simp, priority 900] lemma zero_div {a : R} : 0 / a = 0 :=
classical.by_cases
  (λ a0 : a = 0, a0.symm ▸ div_zero 0)
  (λ a0, by simpa only [zero_mul] using mul_div_cancel 0 a0)

@[simp, priority 900] lemma div_self {a : R} (a0 : a ≠ 0) : a / a = 1 :=
by simpa only [one_mul] using mul_div_cancel 1 a0

lemma eq_div_of_mul_eq_left {a b c : R} (hb : b ≠ 0) (h : a * b = c) : a = c / b :=
by rw [← h, mul_div_cancel _ hb]

lemma eq_div_of_mul_eq_right {a b c : R} (ha : a ≠ 0) (h : a * b = c) : b = c / a :=
by rw [← h, mul_div_cancel_left _ ha]

theorem mul_div_assoc (x : R) {y z : R} (h : z ∣ y) : x * y / z = x * (y / z) :=
begin
  classical, by_cases hz : z = 0,
  { subst hz, rw [div_zero, div_zero, mul_zero] },
  rcases h with ⟨p, rfl⟩,
  rw [mul_div_cancel_left _ hz, mul_left_comm, mul_div_cancel_left _ hz]
end

section
open_locale classical

@[elab_as_eliminator]
theorem gcd.induction {P : R → R → Prop} : ∀ a b : R,
  (∀ x, P 0 x) →
  (∀ a b, a ≠ 0 → P (b % a) a → P a b) →
  P a b
| a := λ b H0 H1, if a0 : a = 0 then by rw [a0]; apply H0 else
  have h:_ := mod_lt b a0,
  H1 _ _ a0 (gcd.induction (b%a) a H0 H1)
using_well_founded {dec_tac := tactic.assumption,
  rel_tac := λ _ _, `[exact ⟨_, r_well_founded⟩]}

end

section gcd
variable [decidable_eq R]

/-- `gcd a b` is a (non-unique) element such that `gcd a b ∣ a` `gcd a b ∣ b`, and for
  any element `c` such that `c ∣ a` and `c ∣ b`, then `c ∣ gcd a b` -/
def gcd : R → R → R
| a := λ b, if a0 : a = 0 then b else
  have h:_ := mod_lt b a0,
  gcd (b%a) a
using_well_founded {dec_tac := tactic.assumption,
  rel_tac := λ _ _, `[exact ⟨_, r_well_founded⟩]}

@[simp] theorem gcd_zero_left (a : R) : gcd 0 a = a :=
by rw gcd; exact if_pos rfl

@[simp] theorem gcd_zero_right (a : R) : gcd a 0 = a :=
by rw gcd; split_ifs; simp only [h, zero_mod, gcd_zero_left]

theorem gcd_val (a b : R) : gcd a b = gcd (b % a) a :=
by rw gcd; split_ifs; [simp only [h, mod_zero, gcd_zero_right], refl]

theorem gcd_dvd (a b : R) : gcd a b ∣ a ∧ gcd a b ∣ b :=
gcd.induction a b
  (λ b, by rw [gcd_zero_left]; exact ⟨dvd_zero _, dvd_refl _⟩)
  (λ a b aneq ⟨IH₁, IH₂⟩, by rw gcd_val;
    exact ⟨IH₂, (dvd_mod_iff IH₂).1 IH₁⟩)

theorem gcd_dvd_left (a b : R) : gcd a b ∣ a := (gcd_dvd a b).left

theorem gcd_dvd_right (a b : R) : gcd a b ∣ b := (gcd_dvd a b).right

protected theorem gcd_eq_zero_iff {a b : R} :
  gcd a b = 0 ↔ a = 0 ∧ b = 0 :=
⟨λ h, by simpa [h] using gcd_dvd a b,
 by rintro ⟨rfl, rfl⟩; simp⟩

theorem dvd_gcd {a b c : R} : c ∣ a → c ∣ b → c ∣ gcd a b :=
gcd.induction a b
  (λ _ _ H, by simpa only [gcd_zero_left] using H)
  (λ a b a0 IH ca cb, by rw gcd_val;
    exact IH ((dvd_mod_iff ca).2 cb) ca)

theorem gcd_eq_left {a b : R} : gcd a b = a ↔ a ∣ b :=
⟨λ h, by rw ← h; apply gcd_dvd_right,
 λ h, by rw [gcd_val, mod_eq_zero.2 h, gcd_zero_left]⟩

@[simp] theorem gcd_one_left (a : R) : gcd 1 a = 1 :=
gcd_eq_left.2 (one_dvd _)

@[simp] theorem gcd_self (a : R) : gcd a a = a :=
gcd_eq_left.2 (dvd_refl _)

/--
An implementation of the extended GCD algorithm.
At each step we are computing a triple `(r, s, t)`, where `r` is the next value of the GCD
algorithm, to compute the greatest common divisor of the input (say `x` and `y`), and `s` and `t`
are the coefficients in front of `x` and `y` to obtain `r` (i.e. `r = s * x + t * y`).
The function `xgcd_aux` takes in two triples, and from these recursively computes the next triple:
```
xgcd_aux (r, s, t) (r', s', t') = xgcd_aux (r' % r, s' - (r' / r) * s, t' - (r' / r) * t) (r, s, t)
```
-/
def xgcd_aux : R → R → R → R → R → R → R × R × R
| r := λ s t r' s' t',
if hr : r = 0 then (r', s', t')
  else
  have r' % r ≺ r, from mod_lt _ hr,
  let q := r' / r in xgcd_aux (r' % r) (s' - q * s) (t' - q * t) r s t
using_well_founded {dec_tac := tactic.assumption,
  rel_tac := λ _ _, `[exact ⟨_, r_well_founded⟩]}

@[simp] theorem xgcd_zero_left {s t r' s' t' : R} : xgcd_aux 0 s t r' s' t' = (r', s', t') :=
by unfold xgcd_aux; exact if_pos rfl

theorem xgcd_aux_rec {r s t r' s' t' : R} (h : r ≠ 0) :
  xgcd_aux r s t r' s' t' = xgcd_aux (r' % r) (s' - (r' / r) * s) (t' - (r' / r) * t) r s t :=
by conv {to_lhs, rw [xgcd_aux]}; exact if_neg h

/-- Use the extended GCD algorithm to generate the `a` and `b` values
  satisfying `gcd x y = x * a + y * b`. -/
def xgcd (x y : R) : R × R := (xgcd_aux x 1 0 y 0 1).2

/-- The extended GCD `a` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_a (x y : R) : R := (xgcd x y).1

/-- The extended GCD `b` value in the equation `gcd x y = x * a + y * b`. -/
def gcd_b (x y : R) : R := (xgcd x y).2

@[simp] theorem gcd_a_zero_left {s : R} : gcd_a 0 s = 0 :=
by { unfold gcd_a, rw [xgcd, xgcd_zero_left] }

@[simp] theorem gcd_b_zero_left {s : R} : gcd_b 0 s = 1 :=
by { unfold gcd_b, rw [xgcd, xgcd_zero_left] }

@[simp] theorem xgcd_aux_fst (x y : R) : ∀ s t s' t',
  (xgcd_aux x s t y s' t').1 = gcd x y :=
gcd.induction x y (by intros; rw [xgcd_zero_left, gcd_zero_left])
(λ x y h IH s t s' t', by simp only [xgcd_aux_rec h, if_neg h, IH]; rw ← gcd_val)

theorem xgcd_aux_val (x y : R) : xgcd_aux x 1 0 y 0 1 = (gcd x y, xgcd x y) :=
by rw [xgcd, ← xgcd_aux_fst x y 1 0 0 1, prod.mk.eta]

theorem xgcd_val (x y : R) : xgcd x y = (gcd_a x y, gcd_b x y) :=
prod.mk.eta.symm

private def P (a b : R) : R × R × R → Prop | (r, s, t) := (r : R) = a * s + b * t

theorem xgcd_aux_P (a b : R) {r r' : R} : ∀ {s t s' t'}, P a b (r, s, t) →
  P a b (r', s', t') → P a b (xgcd_aux r s t r' s' t') :=
gcd.induction r r' (by intros; simpa only [xgcd_zero_left]) $ λ x y h IH s t s' t' p p', begin
  rw [xgcd_aux_rec h], refine IH _ p, unfold P at p p' ⊢,
  rw [mul_sub, mul_sub, add_sub, sub_add_eq_add_sub, ← p', sub_sub,
    mul_comm _ s, ← mul_assoc, mul_comm _ t, ← mul_assoc, ← add_mul, ← p,
    mod_eq_sub_mul_div]
end

/-- An explicit version of Bézout's lemma for Euclidean domains. -/
theorem gcd_eq_gcd_ab (a b : R) : (gcd a b : R) = a * gcd_a a b + b * gcd_b a b :=
by have := @xgcd_aux_P _ _ _ a b a b 1 0 0 1
  (by rw [P, mul_one, mul_zero, add_zero]) (by rw [P, mul_one, mul_zero, zero_add]);
rwa [xgcd_aux_val, xgcd_val] at this

@[priority 70] -- see Note [lower instance priority]
instance (R : Type*) [e : euclidean_domain R] : integral_domain R :=
by haveI := classical.dec_eq R; exact
{ eq_zero_or_eq_zero_of_mul_eq_zero :=
    λ a b h, (or_iff_not_and_not.2 $ λ h0,
      h0.1 $ by rw [← mul_div_cancel a h0.2, h, zero_div]),
  zero := 0, add := (+), mul := (*), ..e }

end gcd

section lcm
variables [decidable_eq R]

/-- `lcm a b` is a (non-unique) element such that `a ∣ lcm a b` `b ∣ lcm a b`, and for
  any element `c` such that `a ∣ c` and `b ∣ c`, then `lcm a b ∣ c` -/
def lcm (x y : R) : R :=
x * y / gcd x y

theorem dvd_lcm_left (x y : R) : x ∣ lcm x y :=
classical.by_cases
  (assume hxy : gcd x y = 0, by rw [lcm, hxy, div_zero]; exact dvd_zero _)
  (λ hxy, let ⟨z, hz⟩ := (gcd_dvd x y).2 in ⟨z, eq.symm $ eq_div_of_mul_eq_left hxy $
    by rw [mul_right_comm, mul_assoc, ← hz]⟩)

theorem dvd_lcm_right (x y : R) : y ∣ lcm x y :=
classical.by_cases
  (assume hxy : gcd x y = 0, by rw [lcm, hxy, div_zero]; exact dvd_zero _)
  (λ hxy, let ⟨z, hz⟩ := (gcd_dvd x y).1 in ⟨z, eq.symm $ eq_div_of_mul_eq_right hxy $
    by rw [← mul_assoc, mul_right_comm, ← hz]⟩)

theorem lcm_dvd {x y z : R} (hxz : x ∣ z) (hyz : y ∣ z) : lcm x y ∣ z :=
begin
  rw lcm, by_cases hxy : gcd x y = 0,
  { rw [hxy, div_zero], rw euclidean_domain.gcd_eq_zero_iff at hxy, rwa hxy.1 at hxz },
  rcases gcd_dvd x y with ⟨⟨r, hr⟩, ⟨s, hs⟩⟩,
  suffices : x * y ∣ z * gcd x y,
  { cases this with p hp, use p,
    generalize_hyp : gcd x y = g at hxy hs hp ⊢, subst hs,
    rw [mul_left_comm, mul_div_cancel_left _ hxy, ← mul_left_inj' hxy, hp],
    rw [← mul_assoc], simp only [mul_right_comm] },
  rw [gcd_eq_gcd_ab, mul_add], apply dvd_add,
  { rw mul_left_comm, exact mul_dvd_mul_left _ (dvd_mul_of_dvd_left hyz _) },
  { rw [mul_left_comm, mul_comm], exact mul_dvd_mul_left _ (dvd_mul_of_dvd_left hxz _) }
end

@[simp] lemma lcm_dvd_iff {x y z : R} : lcm x y ∣ z ↔ x ∣ z ∧ y ∣ z :=
⟨λ hz, ⟨dvd_trans (dvd_lcm_left _ _) hz, dvd_trans (dvd_lcm_right _ _) hz⟩,
λ ⟨hxz, hyz⟩, lcm_dvd hxz hyz⟩

@[simp] lemma lcm_zero_left (x : R) : lcm 0 x = 0 :=
by rw [lcm, zero_mul, zero_div]

@[simp] lemma lcm_zero_right (x : R) : lcm x 0 = 0 :=
by rw [lcm, mul_zero, zero_div]

@[simp] lemma lcm_eq_zero_iff {x y : R} : lcm x y = 0 ↔ x = 0 ∨ y = 0 :=
begin
  split,
  { intro hxy, rw [lcm, mul_div_assoc _ (gcd_dvd_right _ _), mul_eq_zero] at hxy,
    apply or_of_or_of_imp_right hxy, intro hy,
    by_cases hgxy : gcd x y = 0,
    { rw euclidean_domain.gcd_eq_zero_iff at hgxy, exact hgxy.2 },
    { rcases gcd_dvd x y with ⟨⟨r, hr⟩, ⟨s, hs⟩⟩,
      generalize_hyp : gcd x y = g at hr hs hy hgxy ⊢, subst hs,
      rw [mul_div_cancel_left _ hgxy] at hy, rw [hy, mul_zero] } },
  rintro (hx | hy),
  { rw [hx, lcm_zero_left] },
  { rw [hy, lcm_zero_right] }
end

@[simp] lemma gcd_mul_lcm (x y : R) : gcd x y * lcm x y = x * y :=
begin
  rw lcm, by_cases h : gcd x y = 0,
  { rw [h, zero_mul], rw euclidean_domain.gcd_eq_zero_iff at h, rw [h.1, zero_mul] },
  rcases gcd_dvd x y with ⟨⟨r, hr⟩, ⟨s, hs⟩⟩,
  generalize_hyp : gcd x y = g at h hr ⊢, subst hr,
  rw [mul_assoc, mul_div_cancel_left _ h]
end

end lcm

end euclidean_domain

instance int.euclidean_domain : euclidean_domain ℤ :=
{ add := (+),
  mul := (*),
  one := 1,
  zero := 0,
  neg := has_neg.neg,
  quotient := (/),
  quotient_zero := int.div_zero,
  remainder := (%),
  quotient_mul_add_remainder_eq := λ a b, int.div_add_mod _ _,
  r := λ a b, a.nat_abs < b.nat_abs,
  r_well_founded := measure_wf (λ a, int.nat_abs a),
  remainder_lt := λ a b b0, int.coe_nat_lt.1 $
    by rw [int.nat_abs_of_nonneg (int.mod_nonneg _ b0), ← int.abs_eq_nat_abs];
    exact int.mod_lt _ b0,
  mul_left_not_lt := λ a b b0, not_lt_of_ge $
    by rw [← mul_one a.nat_abs, int.nat_abs_mul];
    exact mul_le_mul_of_nonneg_left (int.nat_abs_pos_of_ne_zero b0) (nat.zero_le _),
  .. int.comm_ring,
  .. int.nontrivial }

@[priority 100] -- see Note [lower instance priority]
instance field.to_euclidean_domain {K : Type u} [field K] : euclidean_domain K :=
{ add := (+),
  mul := (*),
  one := 1,
  zero := 0,
  neg := has_neg.neg,
  quotient := (/),
  remainder := λ a b, a - a * b / b,
  quotient_zero := div_zero,
  quotient_mul_add_remainder_eq := λ a b,
    by classical; by_cases b = 0; simp [h, mul_div_cancel'],
  r := λ a b, a = 0 ∧ b ≠ 0,
  r_well_founded := well_founded.intro $ λ a, acc.intro _ $ λ b ⟨hb, hna⟩,
    acc.intro _ $ λ c ⟨hc, hnb⟩, false.elim $ hnb hb,
  remainder_lt := λ a b hnb, by simp [hnb],
  mul_left_not_lt := λ a b hnb ⟨hab, hna⟩, or.cases_on (mul_eq_zero.1 hab) hna hnb,
  .. ‹field K› }


import .aux.ncard
import .aux.helpers

-- noncomputable theory
open_locale classical
open_locale big_operators

open set

variables {E : Type*}

/-- A predicate `P` on sets satisfies the exchange property if, for all `X` and `Y` satisfying `P`
  and all `a ∈ X \ Y`, there exists `b ∈ Y \ X` so that swapping `a` for `b` in `X` maintains `P`.-/
def exchange_property (P : set E → Prop) : Prop :=
  ∀ X Y, P X → P Y → ∀ a ∈ X \ Y, ∃ b ∈ Y \ X, P (insert b (X \ {a}))

/-- A `matroid` is a nonempty collection of sets satisfying the exchange property. Each such set
  is called a `base` of the matroid. -/

@[ext] structure matroid (E : Type*) :=
  (base : set E → Prop)
  (exists_base' : ∃ B, base B)
  (base_exchange' : exchange_property base)

instance {E : Type*} [finite E] :
  finite (matroid E) :=
finite.of_injective (λ M, M.base) (λ M₁ M₂ h, (by {ext, dsimp only at h, rw h}))

instance {E : Type*} : nonempty (matroid E) :=
  ⟨⟨λ B, B = univ, ⟨_,rfl⟩, λ B B' hB hB' a ha, (ha.2 (by convert mem_univ a)).elim⟩⟩

namespace matroid
/- None of these definitions require finiteness -/

section defs

/-- A set is independent if it is contained in a base.  -/
def indep (M : matroid E) (I : set E) : Prop :=
  ∃ B, M.base B ∧ I ⊆ B

/-- A basis for a set `X` is a maximal independent subset of `X`
  (Often in the literature, the word 'basis' is used to refer to what we call a 'base')-/
def basis (M : matroid E) (I X : set E) : Prop :=
  M.indep I ∧ I ⊆ X ∧ ∀ J, M.indep J → I ⊆ J → J ⊆ X → I = J

/-- A circuit is a minimal dependent set -/
def circuit (M : matroid E) (C : set E) : Prop :=
  ¬M.indep C ∧ ∀ I ⊂ C, M.indep I

/-- A flat is a maximal set having a given basis  -/
def flat (M : matroid E) (F : set E) : Prop :=
  ∀ I X, M.basis I F → M.basis I X → X ⊆ F

/-- The closure of a set is the intersection of the flats containing it -/
def cl (M : matroid E) (X : set E) : set E :=
  ⋂₀ {F | M.flat F ∧ X ⊆ F}

/-- A hyperplane is a maximal proper subflat -/
def hyperplane (M : matroid E) (H : set E) : Prop :=
  M.flat H ∧ H ⊂ univ ∧ (∀ F, H ⊂ F → M.flat F → F = univ)

/-- A cocircuit is the complement of a hyperplane -/
def cocircuit (M : matroid E) (K : set E) : Prop :=
  M.hyperplane Kᶜ

/-- A coindependent set is one that contains no cocircuit -/
def coindep (M : matroid E) (I : set E) : Prop :=
  ¬ ∃ K ⊆ I, M.cocircuit K

/-- A loop is a singleton circuit -/
def loop (M : matroid E) (e : E) : Prop :=
  M.circuit {e}

/-- A coloop is an element contained in every basis -/
def coloop (M : matroid E) (e : E) : Prop :=
  ∀ B, M.base B → e ∈ B

/-- The set of nonloops of `M` is the complement of the set `M.cl ∅` of loops -/
def nonloops (M : matroid E) : set E :=
  (M.cl ∅)ᶜ

end defs

section base

variables {B B₁ B₂ I : set E} {M : matroid E} {e f x y : E}

lemma exists_base (M : matroid E) : ∃ B, M.base B := M.exists_base'

lemma base.exchange (hB₁ : M.base B₁) (hB₂ : M.base B₂) (hx : x ∈ B₁ \ B₂) :
  ∃ y ∈ B₂ \ B₁, M.base (insert y (B₁ \ {x}))  :=
M.base_exchange' B₁ B₂ hB₁ hB₂ _ hx

lemma base.exchange_mem (hB₁ : M.base B₁) (hB₂ : M.base B₂) (hxB₁ : x ∈ B₁) (hxB₂ : x ∉ B₂) :
  ∃ y, (y ∈ B₂ ∧ y ∉ B₁) ∧ M.base (insert y (B₁ \ {x})) :=
by simpa using hB₁.exchange hB₂ ⟨hxB₁, hxB₂⟩

variables [finite E]

lemma base.card_eq_card_of_base (hB₁ : M.base B₁) (hB₂ : M.base B₂) :
  B₁.ncard = B₂.ncard :=
begin
  suffices h : ∀ i B B', M.base B → M.base B' → (B' \ B).ncard ≤ i →
    B'.ncard ≤ B.ncard, from
      (h _ _ _ hB₂ hB₁ rfl.le).antisymm (h _ _ _ hB₁ hB₂ rfl.le),
  clear hB₁ B₁ hB₂ B₂,
  intro i,
  induction i with i IH,
  { rintros B B' - - h,
    rw [le_zero_iff, ncard_eq_zero, diff_eq_empty] at h,
    exact ncard_le_of_subset h, },
  refine λ B B' hB hB' hcard, le_of_not_lt (λ hlt, _ ) ,
  obtain ⟨x, hxB', hxB⟩ := exists_mem_not_mem_of_ncard_lt_ncard hlt,
  have := hB'.exchange hB ⟨hxB',hxB⟩,
  obtain ⟨y, hy, hB''⟩ := hB'.exchange hB ⟨hxB',hxB⟩,

  have hcard := IH B (insert y (B' \ {x}))  hB (by simpa using hB'') _,
  { apply hlt.not_le,
    rwa [ncard_insert_of_not_mem, ncard_diff_singleton_add_one hxB'] at hcard,
    simpa using hy.2},

  suffices hss : (insert y (B' \ {x})) \ B ⊂ B' \ B,
  { exact nat.le_of_lt_succ ((ncard_lt_ncard hss).trans_le hcard)},

  refine (ssubset_iff_of_subset (λ a, _) ).mpr ⟨x,  _⟩,
  { rw [mem_diff, mem_insert_iff, and_imp, mem_diff_singleton],
    rintro (rfl | ⟨haB',hax⟩) haB,
    { exact (haB hy.1).elim},
    exact ⟨haB',haB⟩},

  rw [exists_prop, mem_diff, mem_diff, not_and, not_not_mem, mem_insert_iff, mem_diff,
    mem_singleton_iff, ne_self_iff_false, and_false, or_false],
  exact ⟨⟨hxB', hxB⟩, by {rintro rfl, exact hy.1}⟩,
end

lemma base.eq_of_subset_base (hB₁ : M.base B₁) (hB₂ : M.base B₂) (hB₁B₂ : B₁ ⊆ B₂) :
  B₁ = B₂ :=
begin
  suffices : B₂ \ B₁ = ∅, from hB₁B₂.antisymm (diff_eq_empty.mp this),
  by_contra' h,
  obtain ⟨e,he⟩ := set.nonempty_iff_ne_empty.mpr h,
  obtain ⟨y,hy,-⟩:=  hB₂.exchange hB₁ he,
  exact hy.2 (hB₁B₂ hy.1),
end

end base

end matroid

-- TODO : prove strong basis exchange (and hence define duality) in this file.

-- lemma base.indep (hB : M.base B) :
--   M.indep B :=
-- sorry

-- lemma base.insert_dep (hB : M.base B) (h : e ∉ B) :
--   ¬M.indep (insert e B) := sorry

-- lemma base_iff_maximal_indep :
--   M.base B ↔ M.indep B ∧ ∀ I, M.indep I → B ⊆ I → B = I :=
-- sorry

-- lemma indep.unique_circuit_of_insert {e : E} (hI : M.indep I) (hI' : ¬M.indep (insert e I)) :
--   ∃! C, C ⊆ insert e I ∧ M.circuit C ∧ e ∈ C := sorry

-- lemma subset_cl (M : matroid E) (X : set E) :
--   X ⊆ M.cl X := sorry

-- -- lemma base_iff_indep_card :
-- --   M.base B ↔ M.indep B ∧ B.ncard =


import data.set
import algebra.pi_instances

import .topology

section negative_sets

def negative_set (S: set ℝ): set ℝ := { x : ℝ | -x ∈ S}

lemma negative_set.negative_set_eq_set
  (S: set ℝ): negative_set (negative_set S) = S :=
  begin
  rw negative_set,
  rw negative_set,
  simp,
  end

lemma negative_set.mem_iff {S: set ℝ} (x: ℝ):
  x ∈ S ↔ -x ∈ negative_set S := begin
  split,
  intro h,
  rw negative_set,
  finish,
  intro h,
  rw negative_set at h,
  finish,
end

lemma negative_set.nonempty {S: set ℝ}:
  S.nonempty → (negative_set S).nonempty := begin
  intro hne,
  obtain ⟨ x, h ⟩ := hne,
  use (-x),
  rw negative_set,
  finish,
end

lemma negative_set.bdd_above {S: set ℝ}:
  bdd_below S → bdd_above (negative_set S) := begin
  intro bdd,
  obtain ⟨ M, hm ⟩ := bdd,
  rw lower_bounds at hm,
  simp at hm,
  use (-M),
  rw negative_set,
  rw upper_bounds,
  simp,
  intros a ha,
  apply le_neg.2,
  exact hm ha,
end

lemma negative_set.bdd_below {S: set ℝ}:
  bdd_above S → bdd_below (negative_set S) :=
begin
  intro bdd,
  obtain ⟨ M, hm ⟩ := bdd,
  rw upper_bounds at hm,
  simp at hm,
  use (-M),
  rw negative_set,
  rw lower_bounds,
  simp,
  intros a ha,
  apply neg_le.1,
  exact hm ha,
end

lemma negative_set.bdd_above_iff {S: set ℝ}:
  bdd_below S ↔ bdd_above (negative_set S) := begin
  split,
  exact negative_set.bdd_above,
  set S' := negative_set S with hs,
  have Seq := negative_set.negative_set_eq_set S,
  rw ← Seq,
  rw ← hs,
  exact negative_set.bdd_below,
end

lemma negative_set.bdd_below_iff {S: set ℝ}:
  bdd_above S ↔ bdd_below (negative_set S) := begin
  split,
  exact negative_set.bdd_below,
  set S' := negative_set S with hs,
  have Seq := negative_set.negative_set_eq_set S,
  rw ← Seq,
  rw ← hs,
  exact negative_set.bdd_above,
end

lemma negative_set.adhere_ens {S: set ℝ} {l: ℝ}:
  adhere_ens S l → adhere_ens (negative_set S) (-l) :=
  begin
  intro adh,
  rw adhere_ens at adh,
  obtain ⟨ x, hx ⟩ := adh,
  use (-x),
  split,
  simp,
  intro n,
  exact (negative_set.mem_iff (x n)).1 (hx.1 n),
  exact neg_converge hx.2,
  end

lemma negative_set.adhere_ens_iff {S: set ℝ} {l: ℝ}:
  adhere_ens S l ↔ adhere_ens (negative_set S) (-l) :=
begin
  split,
  exact negative_set.adhere_ens,
  set S' := negative_set S with hs,
  have Seq := negative_set.negative_set_eq_set S,
  have leq: l = - -l := by simp,
  rw ← Seq,
  rw ← hs,
  set l' := -l with hl,
  rw leq,
  exact negative_set.adhere_ens,
end

end negative_sets


/- Definition of a Group -/

class Group (G : Type u) :=
  mul : G → G → G
  mul_assoc : ∀ a b c : G, mul (mul a b) c = mul a (mul b c)
  one : G
  one_mul : ∀ a : G, mul one a = a
  mul_one : ∀ a : G, mul a one = a
  inv : G → G
  inv_mul : ∀ a : G, mul (inv a) a = one
  mul_inv : ∀ a : G, mul a (inv a) = one

class Abelian_Group (G : Type u) extends Group G :=
  mul_com : ∀ a b : G, mul a b = mul b a

infixl:70    " * "   => Group.mul
notation:max " e "   => Group.one
postfix:max  " ⁻¹ "  => Group.inv

/- Some Examples of Groups -/

namespace Examples

inductive pm : Type :=
  | p : pm
  | m : pm

def pm.mul : pm → pm → pm
  | p, p => p
  | p, m => m
  | m, p => m
  | m, m => p

theorem pm.mul_assoc : ∀ a b c : pm, mul (mul a b) c = mul a (mul b c)
  | p, p, p => by simp [pm.mul]
  | p, p, m => by simp [pm.mul]
  | p, m, p => by simp [pm.mul]
  | p, m, m => by simp [pm.mul]
  | m, p, p => by simp [pm.mul]
  | m, p, m => by simp [pm.mul]
  | m, m, p => by simp [pm.mul]
  | m, m, m => by simp [pm.mul]

theorem pm.one_mul : ∀ a : pm, mul p a = a
  | p => by simp [pm.mul]
  | m => by simp [pm.mul]

theorem pm.mul_one : ∀ a : pm, mul a p = a
  | p => by simp [pm.mul]
  | m => by simp [pm.mul]

def pm.inv : pm → pm
  | p => p
  | m => m

theorem pm.inv_mul : ∀ a : pm, mul (inv a) a = p
  | p => by simp [pm.inv, pm.mul]
  | m => by simp [pm.inv, pm.mul]

theorem pm.mul_inv : ∀ a : pm, mul a (inv a) = p
  | p => by simp [pm.inv, pm.mul]
  | m => by simp [pm.inv, pm.mul]

theorem pm.mul_com : ∀ a b : pm, mul a b = mul b a
  | p, p => by simp [pm.mul]
  | p, m => by simp [pm.mul]
  | m, p => by simp [pm.mul]
  | m, m => by simp [pm.mul]

instance : Abelian_Group pm where
    mul := pm.mul
    mul_assoc := pm.mul_assoc
    one := pm.p
    one_mul := pm.one_mul
    mul_one := pm.mul_one
    inv := pm.inv
    inv_mul := pm.inv_mul
    mul_inv := pm.mul_inv
    mul_com := pm.mul_com

end Examples

namespace Examples

inductive nat : Type :=
  | zero : nat
  | succ : nat → nat

def nat.add : nat → nat → nat
  | a, nat.zero   => a
  | a, nat.succ b => nat.succ (add a b)

theorem nat.add_assoc : ∀ a b c : nat, add (add a b) c = add a (add b c)
  | a, b, zero   => by rw [add, add]
  | a, b, succ n => by simp [add, add_assoc]

theorem nat.zero_add : ∀ a : nat, add zero a = a
  | zero   => by simp [add]
  | succ n => by simp [add, zero_add]

theorem nat.succ_add : ∀ a b : nat, add (succ a) b = succ (add a b)
  | a, zero   => by simp [add]
  | a, succ n => by simp [add, succ_add]

theorem nat.add_com : ∀ a b : nat, add a b = add b a
  | a, zero => by simp [add, zero_add]
  | a, succ n => by simp [add, succ_add, add_com a n]

def nat.pred : nat → nat
  | nat.zero   => nat.zero
  | nat.succ n => n

def nat.sub : nat → nat → nat
  | a, nat.zero   => a
  | a, nat.succ b => pred (sub a b)

inductive int : Type :=
  | ofnat : nat → int
  | nsucc : nat → int

def int.sub_nat (m n : nat) : int :=
  match (nat.sub n m : nat) with
  | nat.zero   => int.ofnat (nat.sub m n)
  | nat.succ k => int.nsucc k

def int.add : int → int → int
  | ofnat m, ofnat n => int.ofnat (nat.add m n)
  | ofnat m, nsucc n => int.sub_nat m (nat.succ n)
  | nsucc m, ofnat n => int.sub_nat n (nat.succ m)
  | nsucc m, nsucc n => int.nsucc (nat.add m n)

end Examples

/- Some Preliminary Lemmas -/

namespace Group
variable {G : Type u} [Group G]

theorem unique_one_l {e' : G} (h : ∀ x : G, e' * x = x) : e' = e := calc
  e' = e' * e := by rw [mul_one]
  _  = e      := by rw [h]

theorem unique_one_r {e' : G} (h : ∀ x : G, x * e' = x) : e' = e := calc
  e' = e * e' := by rw [one_mul]
  _  = e      := by rw [h]

theorem unique_inv_l {a b : G} (h : b * a = e) : b = a⁻¹ := calc
  b = b * e         := by rw [mul_one]
  _ = b * (a * a⁻¹) := by rw [mul_inv]
  _ = (b * a) * a⁻¹ := by rw [mul_assoc]
  _ = e * a⁻¹       := by rw [h]
  _ = a⁻¹           := by rw [one_mul]

theorem unique_inv_r {a b : G} (h : a * b = e) : b = a⁻¹ := calc
  b = e * b         := by rw [one_mul]
  _ = (a⁻¹ * a) * b := by rw [inv_mul]
  _ = a⁻¹ * (a * b) := by rw [mul_assoc]
  _ = a⁻¹ * e       := by rw [h]
  _ = a⁻¹           := by rw [mul_one]

theorem inv_inv (a : G) : (a⁻¹)⁻¹ = a :=
  have h₁ : a * a⁻¹ = e := mul_inv a
  have h₂ : a = (a⁻¹)⁻¹ := unique_inv_l h₁
  show (a⁻¹)⁻¹ = a from h₂.symm

theorem prod_inv (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
  have h₁ : (a * b) * (b⁻¹ * a⁻¹) = e := calc
    (a * b) * (b⁻¹ * a⁻¹) = a * (b * b⁻¹) * a⁻¹ := by simp [mul_assoc]
    _                     = e                   := by simp [mul_inv, mul_one, mul_inv]
  have h₂ : (b⁻¹ * a⁻¹) = (a * b)⁻¹ := unique_inv_r h₁
  show (a * b)⁻¹ = b⁻¹ * a⁻¹ from h₂.symm

theorem left_cancel {a b x : G} (h : a * x = b) : x = a⁻¹ * b := calc
  x = e * x         := by rw [one_mul]
  _ = (a⁻¹ * a) * x := by rw [inv_mul]
  _ = a⁻¹ * (a * x) := by rw [mul_assoc]
  _ = a⁻¹ * b       := by rw [h]

theorem right_cancel {a b x : G} (h : x * a = b) : x = b * a⁻¹ := calc
  x = x * e         := by rw [mul_one]
  _ = x * (a * a⁻¹) := by rw [mul_inv]
  _ = (x * a) * a⁻¹ := by rw [mul_assoc]
  _ = b * a⁻¹       := by rw [h]

end Group


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

! This file was ported from Lean 3 source module algebra.regular.smul
! leanprover-community/mathlib commit 550b58538991c8977703fdeb7c9d51a5aa27df11
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.SMulWithZero
import Mathlib.Algebra.Regular.Basic

/-!
# Action of regular elements on a module

We introduce `M`-regular elements, in the context of an `R`-module `M`.  The corresponding
predicate is called `IsSMulRegular`.

There are very limited typeclass assumptions on `R` and `M`, but the "mathematical" case of interest
is a commutative ring `R` acting an a module `M`. Since the properties are "multiplicative", there
is no actual requirement of having an addition, but there is a zero in both `R` and `M`.
SMultiplications involving `0` are, of course, all trivial.

The defining property is that an element `a ∈ R` is `M`-regular if the smultiplication map
`M → M`, defined by `m ↦ a • m`, is injective.

This property is the direct generalization to modules of the property `IsLeftRegular` defined in
`Algebra/Regular`.  Lemma `isLeftRegular_iff` shows that indeed the two notions
coincide.
-/

variable {R S : Type _} (M : Type _) {a b : R} {s : S}

/-- An `M`-regular element is an element `c` such that multiplication on the left by `c` is an
injective map `M → M`. -/
def IsSMulRegular [SMul R M] (c : R) :=
  Function.Injective ((c • ·) : M → M)
#align is_smul_regular IsSMulRegular

theorem IsLeftRegular.isSMulRegular [Mul R] {c : R} (h : IsLeftRegular c) : IsSMulRegular R c :=
  h
#align is_left_regular.is_smul_regular IsLeftRegular.isSMulRegular

/-- Left-regular multiplication on `R` is equivalent to `R`-regularity of `R` itself. -/
theorem isLeftRegular_iff [Mul R] {a : R} : IsLeftRegular a ↔ IsSMulRegular R a :=
  Iff.rfl
#align is_left_regular_iff isLeftRegular_iff

theorem IsRightRegular.isSMulRegular [Mul R] {c : R} (h : IsRightRegular c) :
    IsSMulRegular R (MulOpposite.op c) :=
  h
#align is_right_regular.is_smul_regular IsRightRegular.isSMulRegular

/-- Right-regular multiplication on `R` is equivalent to `Rᵐᵒᵖ`-regularity of `R` itself. -/
theorem isRightRegular_iff [Mul R] {a : R} :
    IsRightRegular a ↔ IsSMulRegular R (MulOpposite.op a) :=
  Iff.rfl
#align is_right_regular_iff isRightRegular_iff

namespace IsSMulRegular

variable {M}

section SMul

variable [SMul R M] [SMul R S] [SMul S M] [IsScalarTower R S M]

/-- The product of `M`-regular elements is `M`-regular. -/
theorem smul (ra : IsSMulRegular M a) (rs : IsSMulRegular M s) : IsSMulRegular M (a • s) :=
  fun _ _ ab => rs (ra ((smul_assoc _ _ _).symm.trans (ab.trans (smul_assoc _ _ _))))
#align is_smul_regular.smul IsSMulRegular.smul

/-- If an element `b` becomes `M`-regular after multiplying it on the left by an `M`-regular
element, then `b` is `M`-regular. -/
theorem of_smul (a : R) (ab : IsSMulRegular M (a • s)) : IsSMulRegular M s :=
  @Function.Injective.of_comp _ _ _ (fun m : M => a • m) _ fun c d cd => by
  dsimp only [Function.comp] at cd
  rw [←smul_assoc, ←smul_assoc] at cd
  exact ab cd
#align is_smul_regular.of_smul IsSMulRegular.of_smul

/-- An element is `M`-regular if and only if multiplying it on the left by an `M`-regular element
is `M`-regular. -/
@[simp]
theorem smul_iff (b : S) (ha : IsSMulRegular M a) : IsSMulRegular M (a • b) ↔ IsSMulRegular M b :=
  ⟨of_smul _, ha.smul⟩
#align is_smul_regular.smul_iff IsSMulRegular.smul_iff

theorem isLeftRegular [Mul R] {a : R} (h : IsSMulRegular R a) : IsLeftRegular a :=
  h
#align is_smul_regular.is_left_regular IsSMulRegular.isLeftRegular

theorem isRightRegular [Mul R] {a : R} (h : IsSMulRegular R (MulOpposite.op a)) :
    IsRightRegular a :=
  h
#align is_smul_regular.is_right_regular IsSMulRegular.isRightRegular

theorem mul [Mul R] [IsScalarTower R R M] (ra : IsSMulRegular M a) (rb : IsSMulRegular M b) :
    IsSMulRegular M (a * b) :=
  ra.smul rb
#align is_smul_regular.mul IsSMulRegular.mul

theorem of_mul [Mul R] [IsScalarTower R R M] (ab : IsSMulRegular M (a * b)) :
    IsSMulRegular M b := by
  rw [← smul_eq_mul] at ab
  exact ab.of_smul _
#align is_smul_regular.of_mul IsSMulRegular.of_mul

@[simp]
theorem mul_iff_right [Mul R] [IsScalarTower R R M] (ha : IsSMulRegular M a) :
    IsSMulRegular M (a * b) ↔ IsSMulRegular M b :=
  ⟨of_mul, ha.mul⟩
#align is_smul_regular.mul_iff_right IsSMulRegular.mul_iff_right

/-- Two elements `a` and `b` are `M`-regular if and only if both products `a * b` and `b * a`
are `M`-regular. -/
theorem mul_and_mul_iff [Mul R] [IsScalarTower R R M] :
    IsSMulRegular M (a * b) ∧ IsSMulRegular M (b * a) ↔ IsSMulRegular M a ∧ IsSMulRegular M b := by
  refine' ⟨_, _⟩
  · rintro ⟨ab, ba⟩
    refine' ⟨ba.of_mul, ab.of_mul⟩
  · rintro ⟨ha, hb⟩
    exact ⟨ha.mul hb, hb.mul ha⟩
#align is_smul_regular.mul_and_mul_iff IsSMulRegular.mul_and_mul_iff

end SMul

section Monoid

variable [Monoid R] [MulAction R M]

variable (M)

/-- One is always `M`-regular. -/
@[simp]
theorem one : IsSMulRegular M (1 : R) := fun a b ab => by
  dsimp only [Function.comp] at ab
  rw [one_smul, one_smul] at ab
  assumption
#align is_smul_regular.one IsSMulRegular.one

variable {M}

/-- An element of `R` admitting a left inverse is `M`-regular. -/
theorem of_mul_eq_one (h : a * b = 1) : IsSMulRegular M b :=
  of_mul
    (by
      rw [h]
      exact one M)
#align is_smul_regular.of_mul_eq_one IsSMulRegular.of_mul_eq_one

/-- Any power of an `M`-regular element is `M`-regular. -/
theorem pow (n : ℕ) (ra : IsSMulRegular M a) : IsSMulRegular M (a ^ n) := by
  induction' n with n hn
  · rw [pow_zero]; simp only [one]
  · rw [pow_succ]
    exact (ra.smul_iff (a ^ n)).mpr hn
#align is_smul_regular.pow IsSMulRegular.pow

/-- An element `a` is `M`-regular if and only if a positive power of `a` is `M`-regular. -/
theorem pow_iff {n : ℕ} (n0 : 0 < n) : IsSMulRegular M (a ^ n) ↔ IsSMulRegular M a := by
  refine' ⟨_, pow n⟩
  rw [← Nat.succ_pred_eq_of_pos n0, pow_succ', ← smul_eq_mul]
  exact of_smul _
#align is_smul_regular.pow_iff IsSMulRegular.pow_iff

end Monoid

section MonoidSMul

variable [Monoid S] [SMul R M] [SMul R S] [MulAction S M] [IsScalarTower R S M]

/-- An element of `S` admitting a left inverse in `R` is `M`-regular. -/
theorem of_smul_eq_one (h : a • s = 1) : IsSMulRegular M s :=
  of_smul a
    (by
      rw [h]
      exact one M)
#align is_smul_regular.of_smul_eq_one IsSMulRegular.of_smul_eq_one

end MonoidSMul

section MonoidWithZero

variable [MonoidWithZero R] [MonoidWithZero S] [Zero M] [MulActionWithZero R M]
  [MulActionWithZero R S] [MulActionWithZero S M] [IsScalarTower R S M]

/-- The element `0` is `M`-regular if and only if `M` is trivial. -/
protected theorem subsingleton (h : IsSMulRegular M (0 : R)) : Subsingleton M :=
  ⟨fun a b => h (by dsimp only [Function.comp]; repeat' rw [MulActionWithZero.zero_smul])⟩
#align is_smul_regular.subsingleton IsSMulRegular.subsingleton

/-- The element `0` is `M`-regular if and only if `M` is trivial. -/

/-- The `0` element is not `M`-regular, on a non-trivial module. -/
theorem not_zero_iff : ¬IsSMulRegular M (0 : R) ↔ Nontrivial M := by
  rw [nontrivial_iff, not_iff_comm, zero_iff_subsingleton, subsingleton_iff]
  push_neg
  exact Iff.rfl
#align is_smul_regular.not_zero_iff IsSMulRegular.not_zero_iff

/-- The element `0` is `M`-regular when `M` is trivial. -/
theorem zero [sM : Subsingleton M] : IsSMulRegular M (0 : R) :=
  zero_iff_subsingleton.mpr sM
#align is_smul_regular.zero IsSMulRegular.zero

/-- The `0` element is not `M`-regular, on a non-trivial module. -/
theorem not_zero [nM : Nontrivial M] : ¬IsSMulRegular M (0 : R) :=
  not_zero_iff.mpr nM
#align is_smul_regular.not_zero IsSMulRegular.not_zero

end MonoidWithZero

section CommSemigroup

variable [CommSemigroup R] [SMul R M] [IsScalarTower R R M]

/-- A product is `M`-regular if and only if the factors are. -/
theorem mul_iff : IsSMulRegular M (a * b) ↔ IsSMulRegular M a ∧ IsSMulRegular M b := by
  rw [← mul_and_mul_iff]
  exact ⟨fun ab => ⟨ab, by rwa [mul_comm]⟩, fun rab => rab.1⟩
#align is_smul_regular.mul_iff IsSMulRegular.mul_iff

end CommSemigroup

end IsSMulRegular

section Group

variable {G : Type _} [Group G]

/-- An element of a group acting on a Type is regular. This relies on the availability
of the inverse given by groups, since there is no `LeftCancelSMul` typeclass. -/
theorem isSMulRegular_of_group [MulAction G R] (g : G) : IsSMulRegular R g := by
  intro x y h
  convert congr_arg (g⁻¹ • ·) h using 1 <;> simp [← smul_assoc]
#align is_smul_regular_of_group isSMulRegular_of_group

end Group

section Units

variable [Monoid R] [MulAction R M]

/-- Any element in `Rˣ` is `M`-regular. -/
theorem Units.isSMulRegular (a : Rˣ) : IsSMulRegular M (a : R) :=
  IsSMulRegular.of_mul_eq_one a.inv_val
#align units.is_smul_regular Units.isSMulRegular

/-- A unit is `M`-regular. -/
theorem IsUnit.isSMulRegular (ua : IsUnit a) : IsSMulRegular M a := by
  rcases ua with ⟨a, rfl⟩
  exact a.isSMulRegular M
#align is_unit.is_smul_regular IsUnit.isSMulRegular

end Units


/-
Copyright (c) 2022 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 4 : "and" (`∧`)

We learn about how to manipulate `P ∧ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics:

* `cases`
* `split`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

example : P ∧ Q → P :=
begin
  intro q,cases q,assumption,
end

example : P ∧ Q → Q :=
begin
  intro q,cases q,assumption,
end

example : (P → Q → R) → (P ∧ Q → R) :=
begin
  intros q w,apply q,cases w,assumption,cases w, assumption,
end

example : P → Q → P ∧ Q :=
begin
  intros q w, split, assumption,assumption,
end

/-- `∧` is symmetric -/
example : P ∧ Q → Q ∧ P :=
begin
  intro q,cases q,split ,assumption, assumption,
end

example : P → P ∧ true :=
begin
  intro q,split,assumption,triv,
end

example : false → P ∧ false :=
begin
  intro q,split ,by_contra,triv,triv,
end

/-- `∧` is transitive -/
example : (P ∧ Q) → (Q ∧ R) → (P ∧ R) :=
begin
  intros q w,split, cases q,assumption,cases w,assumption,
end

example : ((P ∧ Q) → R) → (P → Q → R) :=
begin
  intros q w e,apply q,split, assumption ,assumption,
end


import data.nat data.set
open nat set

variable {T : Type}

/-
Lemma 2: A Δ B ⊆ (A ∪ B) – (A ∩ B)
Proof of Lemma 2: We will show that for any x ∈ A Δ B, x ∈ (A ∪ B) – (A ∩ B). Consider any
x ∈ A Δ B. Then either x ∈ A and x ∉ B, or x ∈ B and x ∉ A. Assume without loss of generality that
x ∈ A and x ∉ B. Since x ∈ A, x ∈ A ∪ B. Since x ∉ B, x ∉ A ∩ B, so x ∈ (A ∪ B) – (A ∩ B). Since our
choice of x was arbitrary, we have that A Δ B ⊆ (A ∪ B) – (A ∩ B). ■
-/

definition set_minus [reducible] (A B : set T) :=
  λ x, x ∈ A ∧ ¬ x ∈ B

notation a - b := set_minus a b

example : ∀ (A B : set T) (x : T), (x ∈ A ∧ x ∉ B) ∨ (x ∈ B ∧ x ∉ A) → x ∈ A ∪ B - A ∩ B := sorry

/-
Theorem: If m and n have opposite parity, m + n is odd
Proof: Without loss of generality, assume that m is odd and n is even. Since m is odd, there exists an
integer r such that m = 2r + 1. Since n is even, there exists an integer s such that n = 2s. Then
m + n = 2r + 1 + 2s = 2(r + s) + 1. Consequently, m + n is odd. ■
-/

definition even (x : nat) := ∃ k, x = 2 * k.
definition odd (x : nat) := ∃ k, x = 2 * k + 1.

example : ∀ (m n : nat), (odd m ∧ even n) ∨ (odd n ∧ even n) → odd (m + n) := sorry

/-
Theorem: Let G be an undirected graph and let C1 and C2 be connected components of G. If C1 ≠ C2,
then C1 ∩ C2 = Ø.
Proof: By contradiction. Suppose that C1 and C2 are connected components of some undirected
graph G, that C1 ≠ C2, but that C1 ∩ C2 ≠ Ø. Since C1 ∩ C2 ≠ Ø, there must be some node v such that
v ∈ C1 and v ∈ C2. Furthermore, since C1 ≠ C2, there must be some node u that either u ∈ C1 or
u ∈ C2, but not both. Without loss of generality, assume that u ∈ C1 and u ∉ C2.
By the definition of a connected component, since u ∈ C1 and v ∈ C1, we know u ↔ v. Similarly, by
the definition of a connected component, since v ∈ C2 and u ∉ C2, we know that u ↮ v, contradicting
our previous assertion. We have reached a contradiction, so our assumption must have been wrong.
Thus C1 ∩ C2 = Ø, as required. ■
-/

-- similar to the first one
example : ∀ (C1 C2 : set T) (u : T), (u ∈ C1 ∧ u ∉ C2) ∨ (u ∈ C2 ∧ u ∉ C1) → true := sorry

/-
Theorem: Let G = (V, E) be any graph containing a simple cycle C. Let u, v ∈ V be nodes in G. If
u ↔ v, then after deleting any single edge in C from graph G, it is still the case that u ↔ v.
Proof: Consider any graph G = (V, E) with a simple cycle C = (x1, x2, …, xn, x1). Consider any u, v ∈
V such that u ↔ v. This means that there must be some simple path (u, y1, y2, …, ym, v) from u to v.
*
Now, suppose that we remove the edge {xi, xi+1} from G.
†
 We need to show that u ↔ v in this modified
graph. We consider two cases. First, it might be the case that the edge {xi, xi+1} does not appear
on the path (u, y1, …, ym, v). In that case, the path (u, y1, …, ym, v) is a valid path from u to v in the
new graph, so u ↔ v still holds.
Second, it might be the case that the edge {xi, xi+1} appears somewhere in our original path (u, y1, …,
ym, v). Since the graph is undirected, the edge might appear as {xi, xi+1} or as {xi+1, xi} when it occurs
in the path. Assume without loss of generality that it appears as {xi, xi+1} (otherwise, we can just reverse
the ordering of the nodes in the original cycle so as to relabel the edges). This means that we
can split the original path into three smaller paths – a path from u to xi, then the edge {xi, xi+1}, and finally
a path from xi+1 to v. Thus u ↔ xi and xi+1 ↔ v. Now, since the edge {xi, xi+1} lies on the cycle
C, after deleting the edge from the cycle, there is still a path from xi to xi+1. Specifically, we can follow
the edges of the cycle in reverse from xi until we reach xi+1. In other words, in this new graph, we
must have that xi ↔ xi+1.
Since in this new graph u ↔ xi, xi ↔ xi+1, and xi+1 ↔ v, we thus have that u ↔ v in the new graph, as
required. ■

* We have not formally proven that u ↔ v iff there is a simple path from u to v. It's a good exercise to try to prove this result. As a hint, try using
the well-ordering principle by considering the shortest path from u to v and proving that it must be a simple path.
† It might be the case that this edge is the last edge on the cycle, which goes from xn to x1. In proofs such as this one, it is typical to allow for a
slight abuse of notation by letting xi+1 mean “the next node on the cycle,” which might not necessarily have the next index.
-/

-- Not sure why he needs a directionality of the edge at all...weird

/-
Theorem: Let G be a tree with at least one edge. If any edge is removed from G, the resulting graph
consists of two connected components that are each trees over their respective nodes.
Proof: Let G = (V, E) be a tree with at least one edge, and let {u, v} ∈ E be an arbitrary edge of G.
By our lemma, if we remove {u, v} from G, we are left with two connected components; call them Cu
and Cv, with u ∈ Cu and v ∈ Cv. Since Cu and Cv are connected components, they are connected. Consequently,
if we can show that Cu and Cv are acyclic, then we can conclude that Cu and Cv are trees.
To show that Cu and Cv are acyclic, assume for the sake of contradiction that at least one of them is
not; without loss of generality, let it be Cu. This means that there is a simple cycle contained purely
within Cu. But since all of the edges in Cu are also present in G, this means that there is a simple cycle
in G, contradicting the fact that G is a tree. We have reached a contradiction, so our assumption
must have been wrong. Thus Cu and Cv must be acyclic, and therefore are trees. ■
-/

-- example acyclic Cu ∨ acyclic Cv → false

/-
Theorem: Let T = (V, E) be a tree. If |V| = 1, then V has exactly one leaf node. Otherwise, V has at
least two leaf nodes.
Proof: By induction. Let P(n) be “any tree with n nodes has exactly one leaf if n = 1 and at least two
leaves if n ≥ 2.” We prove P(n) is true for all n ∈ ℕ⁺ by induction on n.
Assume that for some n ∈ ℕ⁺, that for all n' ∈ ℕ⁺ with n' < n, that P(n') holds and any tree with n'
nodes either has one node and exactly one leaf, or has at least two nodes and at least two leaves. We
will prove that P(n) holds in this case.
First, if n = 1, then the only tree with n nodes is one with a single isolated node. This node has no
edges connected to it, so it is a leaf. Thus P(n) holds.
Otherwise, assume that n ≥ 2 and consider any tree T with n nodes. Choose any edge of T and remove
it; this splits T into two subtrees T1 and T2. We now consider three cases about the relative
sizes of T1 and T2.
Case 1: T1 and T2 each have one node. This means that the tree T has exactly two nodes, so it has exactly
one edge. Thus each of the nodes in T are leaves, since they are incident to just one edge each,
and so T has at least two leaves.
Case 2: Both T1 and T2 have at least two nodes. By the inductive hypothesis, this means that T1 and
T2 each have two leaf nodes, meaning that there are at least four nodes in the graph that have at most
one edge touching them. When we add back to T the initial edge that we deleted, this new edge can
be incident to at most two of these nodes. Consequently, the other two nodes must still have at most
one edge incident to them, and so they are still leaves in the overall graph T. Thus T has at least two
leaves.
Case 3: One of T1 and T2 has exactly one node, and the other has at least two. Without loss of generality,
assume that T1 has one node u, and that T2 has at least two. By the inductive hypothesis, T2 has
at least two leaves. Also by the inductive hypothesis, T1's sole node u must be a leaf, and moreover it
must have no edges incident to it, because any one-node graph must have no edges. When we add
back to T the initial edge that we deleted, this edge will be incident to u and incident to at most one
of the at least two leaves from T2. This means that there are now at least two leaves in T: first, the
node u, which now has exactly one edge incident to it, and second, one of the leaves from T2 that is
not incident to the new edge. Thus T has at least two leaves.
Thus in all three cases T has at least two leaves, so P(n) holds, completing the induction. ■
-/

-- Another simple binary disjunction, binary commutative relation one.

/-
Lemma: Let R be an equivalence relation over A, and X = { [x]R | x ∈ A }. Then for any two sets [x]R,
[y]R ∈ X, if [x]R ≠ [y]R, then [x]R ∩ [y]R = Ø.
Proof: Let R be an equivalence relation over A, and X = { [x]R | x ∈ A }. We proceed by contrapositive
and show that for any [x]R, [y]R ∈ X, that if [x]R ∩ [y]R ≠ Ø, then [x]R = [y]R.
Consider any [x]R, [y]R ∈ X such that [x]R ∩ [y]R ≠ Ø. Then there must be some element w such that
w ∈ [x]R and w ∈ [y]R. By definition, this means w ∈ { z ∈ A | xRz } and w ∈ { z ∈ A | yRz }. Consequently,
xRw and yRw. Since R is symmetric, this means that xRw and wRy. Since R is transitive,
this means that xRy. By symmetry, we also have that yRx.
We will now use this fact to show that [x]R ⊆ [y]R. Without loss of generality, we can use this same
argument to show that [y]R ⊆ [x]R, from which we can conclude that [x]R = [y]R, as required.
To show that [x]R ⊆ [y]R, consider any z ∈ [x]R. This means that xRz. Since yRx and xRz, by transitivity
we have that yRz. Consequently, z ∈ [y]R. Since our choice of z was arbitrary, we have that any
z ∈ [x]R satisfies z ∈ [y]R. Thus [x]R ⊆ [y]R, as required. ■
-/

-- This one is a bit more subtle.

--Goal: (R x y → P x y) ∧ (R y x → P y x)
--no special knowledge of x and y
--wlog assume R x y
-- this is a weird one

/-
Theorem: | is a partial order over ℕ.
Proof: We will show that | is reflexive, antisymmetric, and transitive. To see that | is reflexive, we will
prove that for any n ∈ ℕ, that n | n (that there exists some q ∈ ℕ such that n = nq). So let n be any
natural number, and take q = 1. Then nq = n · 1 = n, so n | n.
To see that | is antisymmetric, we will prove that for any m, n ∈ ℕ, that if m | n and n | m, that m = n.
Consider any m, n ∈ ℕ where m | n and n | m. This means that there exists q, r ∈ ℕ such that n = mq
and m = nr. Consequently:
m = nr = (mq)r = mqr
n = mq = (nr)q = nqr
We now consider two cases. First, if m = n = 0, then we are done, since m = n. Otherwise, at least
one of m or n is nonzero; without loss of generality, assume m ≠ 0. Then since m = mqr, we know
that 1 = qr. Since q, r ∈ ℕ, this is only possible if q = r = 1. Consequently, m = nr = n · 1 = n, so
m = n, as required.
To see that | is transitive, we will prove that for any m, n, p ∈ ℕ, that if m | n and n | p, then m | p.
Consider any m, n, p ∈ ℕ where m | n and n | p; then there must exist q, r ∈ ℕ such that n = qm and
p = rn. Consequently, p = rn = r(qm) = qrm = (qr)m. Since qr ∈ ℕ, this means that there is some
k ∈ ℕ (namely, qr) such that p = km. Thus m | p, as required.
Since | is reflexive, antisymmetric, and transitive over ℕ, | is a partial order over ℕ.
-/

--standard case:
--m = n vs n = m

------------------------------------

--Either:
--have an OR (wlog, assume _)
--or want to show an AND (wlog, we'll show that _) -- this one is sketchier, I am okay ignoring it for now

example : ∀ (A B : set T) (x : T), (x ∈ A ∧ x ∉ B) ∨ (x ∈ B ∧ x ∉ A) → x ∈ A ∪ B - A ∩ B :=
  assume A B x x_or_and,
  wlog x_or_and
       (assume x_in_A_nin_B : x ∈ A ∧ x ∉ B,
        show x ∈ A ∪ B - A ∩ B, from sorry)

open prod.ops

example : ∀ (A B : set T) (x : T),
  let R := λ UV, x ∈ UV.1 ∧ x ∉ UV.2 in
  let Goal := λ
  let Pi := _ in -- permutations on pairs
  have can_assume_generic : R (A,B) ∨ R (B,A) ↔ ∀ (pi : Pi), R (pi (A,B)),
  have suffices : ∀ pi, Goal (pi (A,B)) → Goal (A,B),
  -- replace assumption,
  assume pi RpiAB,
  show Goal (pi (A,B)),
  show Goal (A,B)

-- Binary wlog
check @or.elim
check @bool.rec

-- or.elim : ∀ {a b c : Prop}, a ∨ b → (a → c) → (b → c) → c
-- bool.rec : Π {C : bool → Type}, C bool.ff → C bool.tt → (Π (n : bool), C n)
-- or.rec : ∀ {a b C : Prop}, (a → C) → (b → C) → a ∨ b → C

-- wlog : Π (disj : ?A ∨ ?B) (?A → ?G) : ?G
-- wlog : Π (disj : ?A ∨ ?B) (a_to_G : @?F ?T ?A) {Aok : ?F ?A = G} {Bok : ?F ?B → G} :=
  or.elim disj (eq.rec a_to_G Aok)
               (Bok ?)

-- hmm...this is confusing
-- I provide a proof of (A → C), which we can "generalize" to a proof of (∀ {?T}, ?T → ?G)
-- and then `Aok` and `Bok` can handle the conversions.

       (assume x_in_A_nin_B : x ∈ A ∧ x ∉ B,
        show x ∈ A ∪ B - A ∩ B, from sorry)
wlog := or.elim disj (F

definition wlog : ∀ {A B C : Prop} { MysteryType1 MysteryType2 : Type } { F : Π {M1 : MysteryType1}  (M2 : M1), M2 → C }
  (disj : A ∨ B) (pf1 : F A) : C :=
  or.rec pf1 (λ b,
      @or.elim a b c disj (@
  C a → c :=

----------------
lemma opposite_parity_sum_odd : ∀ (m n : nat), opposite_parity m n → odd (m + n) :=
  assume m n : nat,

  -- Without loss of generality, assume that m is odd and n is even.
  suffices_to_show (∀ (m n : nat), odd m ∧ even n → odd (m + n))

    (assume (lem : ∀ (m n : nat), odd m ∧ even n → odd (m + n))
            (m n : nat) (opp_mn : opposite_parity m n),
      or.elim opp_mn (assume H : odd m ∧ even n, lem m n H) (assume H : odd n ∧ even m, (add_comm n m) ▸ (lem n m H)))

    (take (m n : nat),


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

/-!
# Cardinality of continuum

In this file we define `cardinal.continuum` (notation: `𝔠`, localized in `cardinal`) to be `2 ^ ω`.
We also prove some `simp` lemmas about cardinal arithmetic involving `𝔠`.

## Notation

- `𝔠` : notation for `cardinal.continuum` in locale `cardinal`.
-/

namespace cardinal

universes u v

open_locale cardinal

/-- Cardinality of continuum. -/
def continuum : cardinal.{u} := 2 ^ omega.{u}

localized "notation `𝔠` := cardinal.continuum" in cardinal

@[simp] lemma two_power_omega : (2 ^ omega.{u} : cardinal.{u}) = 𝔠 := rfl

@[simp] lemma lift_continuum : lift.{v} continuum.{u} = 𝔠 :=
by rw [← two_power_omega, lift_two_power, lift_omega, two_power_omega]

/-!
### Inequalities
-/

lemma omega_lt_continuum : ω < 𝔠 := cantor ω

lemma omega_le_continuum : ω ≤ 𝔠 := omega_lt_continuum.le

lemma nat_lt_continuum (n : ℕ) : ↑n < 𝔠 := (nat_lt_omega n).trans omega_lt_continuum

lemma mk_set_nat : #(set ℕ) = 𝔠 := by simp

lemma continuum_pos : 0 < 𝔠 := nat_lt_continuum 0

lemma continuum_ne_zero : 𝔠 ≠ 0 := continuum_pos.ne'

/-!
### Addition
-/

@[simp] lemma omega_add_continuum : ω + 𝔠 = 𝔠 :=
add_eq_right omega_le_continuum omega_le_continuum

@[simp] lemma continuum_add_omega : 𝔠 + ω = 𝔠 :=
(add_comm _ _).trans omega_add_continuum

@[simp] lemma continuum_add_self : 𝔠 + 𝔠 = 𝔠 :=
add_eq_right omega_le_continuum le_rfl

@[simp] lemma nat_add_continuum (n : ℕ) : ↑n + 𝔠 = 𝔠 :=
add_eq_right omega_le_continuum (nat_lt_continuum n).le

@[simp] lemma continuum_add_nat (n : ℕ) : 𝔠 + n = 𝔠 :=
(add_comm _ _).trans (nat_add_continuum n)

/-!
### Multiplication
-/

@[simp] lemma continuum_mul_self : 𝔠 * 𝔠 = 𝔠 :=
mul_eq_left omega_le_continuum le_rfl continuum_ne_zero

@[simp] lemma continuum_mul_omega : 𝔠 * ω = 𝔠 :=
mul_eq_left omega_le_continuum omega_le_continuum omega_ne_zero

@[simp] lemma omega_mul_continuum : ω * 𝔠 = 𝔠 :=
(mul_comm _ _).trans continuum_mul_omega

@[simp] lemma nat_mul_continuum {n : ℕ} (hn : n ≠ 0) :
  ↑n * 𝔠 = 𝔠 :=
mul_eq_right omega_le_continuum (nat_lt_continuum n).le (nat.cast_ne_zero.2 hn)

@[simp] lemma continuum_mul_nat {n : ℕ} (hn : n ≠ 0) :
  𝔠 * n = 𝔠 :=
(mul_comm _ _).trans (nat_mul_continuum hn)

/-!
### Power
-/

@[simp] lemma omega_power_omega : omega.{u} ^ omega.{u} = 𝔠 :=
power_self_eq le_rfl

@[simp] lemma nat_power_omega {n : ℕ} (hn : 2 ≤ n) : (n ^ omega.{u} : cardinal.{u}) = 𝔠 :=
nat_power_eq le_rfl hn

@[simp] lemma continuum_power_omega : continuum.{u} ^ omega.{u} = 𝔠 :=
by rw [← two_power_omega, ← power_mul, mul_eq_left le_rfl le_rfl omega_ne_zero]

end cardinal


import tactic
variables (P Q R S : Prop)

/-
Lean has the logical connectives  `and`, `or`, `not` built-in.

Lean has special tactics to help us deal with them.

New tactics: `cases`, `split`, `left` and `right`

# And (conjunction)

 `P and Q` is written  `P ∧ Q`. This is true iff P and Q are both true.

# cases

 Given `h : P ∧ Q` in the local context we can use `cases h with hp hq`
 to get the two parts so `hp : P` and `hq : Q` replace `h : P ∧ Q` -/

-- 01
example (h : P ∧ Q) : P:=
begin
  cases h with hp hq,
  -- Now have
  -- hp : P,
  -- hq : Q
  exact hp,
end

/- If our goal is `⊢ P ∧ Q` then we can use `split` to create two new goals
one `⊢ P` and the other `⊢ Q` -/

-- 02
example (hp : P) (hq : Q) : P ∧ Q :=
begin
  split,
/-
2 goals
PQ: Prop
hp: P
hq: Q
⊢ P
PQ: Prop
hp: P
hq: Q
⊢ Q-/
  { -- Inside `{ }` we only see the 1st goal
    exact hp,
  },
  { -- Inside `{ }` we only see the 2nd goal
    exact hq,
  },
end

/-
Dot notation: if we have `(hpq : P ∧ Q)` then `hpq.1 : P` and `hpq.2 : Q`

(This `dot` notation is very common in Lean.)

Conversely if we have `(hp : P)` and `(hq : Q)` then `⟨hp,hq⟩ : P ∧ Q`
i.e. `⟨hp,hq⟩` is a term of type `P ∧ Q`.

The angled brackets are typed `\<` and `\>` -/
-- 03
example (hpq : P ∧ Q) : Q ∧ P :=
begin
  cases hpq with hp hq,
  split,
  {
    exact hq,
  },
  {
    exact hp,
  },
  -- or more simply `exact ⟨hpq.2,hpq.1⟩,`
end

-- Having introduced `→` and `∧` we get `iff` (written `↔`) for free
-- `P ↔ Q` is:  `P → Q` and `Q → P`
-- 04
example : P ∧ Q ↔ Q ∧ P :=
begin
  split,
  {
    intro hpq, cases hpq with hp hq,
    split,
    {
      exact hq,
    },
    {
      exact hp,
    },
  },
  {
    sorry,
  }
end

-- 05
example : P ∧ Q → Q :=
begin
  sorry,
end

-- in Lean `P ∧ Q ∧ R` means `P ∧ (Q ∧ R)` (∧ is right-associative)
-- Find a single line `exact ...` solution to the following.
-- (Hint: what are h.1 and h.2 in this example?)
-- 06
example  (h: P ∧ Q ∧ R ∧ S ) : R :=
begin
  sorry,
end

-- 07
example : P → Q → P ∧ Q :=
begin
  sorry,
end

-- 08
example : P ∧ Q → Q ∧ R → P ∧ R :=
begin
  sorry,
end

-- 09
example :  P ∧ R ∧ Q → Q ∧ P ∧ R :=
begin
  sorry,
end

/-
   # Or (disjunction)

The proposition `P or Q` is written  `P ∨ Q`.

 `P ∨ Q` is true iff P is true or Q is true.

If our goal is `⊢ P ∨ Q` then we can accomplish this by either giving a term
of type `P` or a term of type `Q`. We indicate which by using `left` for `P`
and `right` for Q.
 -/

-- 10
example (hp : P) : Q ∨ P :=
begin
  right, -- Goal is now `⊢ P`
  exact hp,
end

/-
Given `(hpq : P ∨ Q)` in the local context we can use `cases hpq with hp hq`
to split our goal into two subgoals, one in which `hp : P` and the other
in which `hq : Q`
-/
-- 11
example (hpq : P ∨ Q) : Q ∨ P :=
begin
  cases hpq with hp hq,
  /-
2 goals
case or.inl
PQ: Prop
hp: P
⊢ Q ∨ P
case or.inr
PQ: Prop
hq: Q
⊢ Q ∨ P-/
{
  right, exact hp,
},
{
  left, exact hq,
}
end

-- 12
example : (P ∨ Q) ∧ (P → Q) → Q :=
begin
  sorry,
end

-- 13
example : (P ∨ Q) ∧ (P ∨ R) → P ∨ (Q ∧ R):=
begin
  sorry,
end

-- 14
example : (P ∨ Q) ∧ (R ∨ S) → (P ∧ R) ∨ (P ∧ S) ∨ (Q ∧ R) ∨ (Q ∧ S):=
begin
  sorry,
end
/-
There are two special propositions : `false` and `true`

To prove `true` we use `triv`

You shouldn't be able to prove `false`.

If you have `false` in the local context then you have a `contradiction`
-/

-- 15
example : true :=
begin
  triv,
end

-- 16
example: P → true :=
begin
  sorry
end

-- 17
example : false → P:=
begin
  intro f,
  contradiction,
end

-- 18
example : false → true:=
begin
  sorry,
end

-- 19
example : true → false → true → false → true :=
begin
  sorry,
end

/-
# Negation

If `P : Prop` then `not P` is denoted `¬ P` this is defined to be `P → false`  -/
-- 20
example : ¬ P ↔ (P → false):=
begin
  refl,
end

-- 21
example : ¬ true → false :=
begin
  sorry,
end

-- 22
example : ¬ false → true :=
begin
  sorry,
end

-- 23
example : P → ¬¬P :=
begin
  sorry,
end

-- 24
example (hp : P) (hnp : ¬ P) : Q ∧ R → ¬ Q  :=
begin
  sorry,
end

-- Can you explain how the following proof works?
-- 25
example (hp : P) (hnp : ¬ P) : Q :=
begin
  cases (hnp hp), -- Hint: what is `(hnp hp)`?
end