-- Section 3.1: Propositions as Types
namespace s31
    constant and : Prop → Prop → Prop
    constant or : Prop → Prop → Prop
    constant not : Prop → Prop
    constant implies : Prop → Prop → Prop

    variables p q r A B: Prop
    #check and p q                      -- Prop
    #check or (and p q) r               -- Prop
    #check implies (and p q) (and q p)  -- Prop

    constant Proof : Prop → Type

    constant and_comm : Π p q : Prop,
    Proof (implies (and p q) (and q p))

    #check and_comm p q      -- Proof (implies (and p q) (and q p))

    constant modus_ponens :
    Π p q : Prop, Proof (implies p q) →  Proof p → Proof q

    constant implies_intro :
    Π p q : Prop, (Proof p → Proof q) → Proof (implies p q).

end s31

-- Section 3.2: Working with P. as T.
namespace s32

    constants p q r s: Prop
    -- Similar to 'def' but for Prop, not Type
    theorem t1_ : p → q → p := λ hp : p, λ hq : q, hp
    #print t1_

    -- Written with other syntax for readability
    theorem t1' : p → q → p :=
    assume hp : p,
    assume hq : q,
    show p, from hp

    -- Can also say "lemma" instead of theorem
    lemma t1'' : p → q → p := λ hp : p, λ hq : q, hp

    -- Can also move arguments to left of colon, like with defs
    lemma t1'''  (hp : p) (hq : q):  p := hp

    axiom hp : p -- same as constant

    theorem t2' : q → p := t1' hp
    #print t2'

    -- Want our theorem to be true for any p q, not just those particular constants

    theorem t1 (p q : Prop) (hp : p) (hq : q) : p := hp
    -- Apply to other constants
    #check t1 p q                -- p → q → p
    #check t1 r s                -- r → s → r
    #check t1 (r → s) (s → r)    -- (r → s) → (s → r) → r → s

    variable h : r → s
    #check t1 (r → s) (s → r) h  -- (s → r) → r → s

    -- Function composition says that "implies" is transitive
    theorem t2 (h₁ : q → r) (h₂ : p → q) : p → r :=
    assume h₃ : p,
    show r, from h₁ (h₂ h₃)

end s32

-- Section 3.3: Propositional logic
namespace s33
    variables p q r : Prop

    #check p → q → p ∧ q
    #check ¬p → p ↔ false
    #check p ∨ q → q ∨ p

    -- Conjunction
    --------------

    -- 'example' states a theorem without naming/storing it. Essentially is just a typecheck
    example (hp : p) (hq : q) : p ∧ q := and.intro hp hq

    #check assume (hp : p) (hq : q), and.intro hp hq

    example (h : p ∧ q) : p := and.elim_left h
    example (h : p ∧ q) : q := and.elim_right h

    -- Common abbreviation for elim_left/right
    example (h : p ∧ q) : q ∧ p :=
    and.intro (and.right h) (and.left h)

    -- Anonymous constructor

    variables  (hp : p) (hq : q)
    #check (⟨hp, hq⟩ : p ∧ q)
    example : p ∧ q := (|hp, hq|) -- ascii version

    -- Dot syntax
    variable l : list ℕ

    #check list.head l
    #check l.head -- equivalent, easier to write

    -- Dot syntax for propositions
    example (h : p ∧ q) : q ∧ p :=
    ⟨h.right, h.left⟩

    -- Disjunction
    --------------
    --intro
    example (hp : p) : p ∨ q := or.intro_left q hp
    example (hq : q) : p ∨ q := or.intro_right p hq
    --elim
    example (h : p ∨ q) : q ∨ p :=
    or.elim h
    (λ hp : p,
        show q ∨ p, from or.intro_right q hp)
    (assume hq : q,
        show q ∨ p, from or.intro_left p hq)

    -- intro
    example (h : p ∨ q) : q ∨ p :=
    or.elim h (λ hp, or.inr hp) (λ hq, or.inl hq)

    -- or has two constructors,so can't use anonymous constructor
    -- still write h.elim instead of or.elim h:
    example (h : p ∨ q) : q ∨ p :=
    h.elim
    (assume hp : p, or.inr hp)
    (assume hq : q, or.inl hq)

    -- Negation/falsity
    example (hpq : p → q) (hnq : ¬q) : ¬p :=
    assume hp : p,
    show false, from hnq (hpq hp)
    -- explosion (q is implicit argument in false.elim)
    example (hp : p) (hnp : ¬p) : q := false.elim (hnp hp)
    -- Shorthand for explosion
    example (hp : p) (hnp : ¬p) : q := absurd hp hnp

    -- proof of ¬p → q → (q → p) → r:

    example (hnp : ¬p) (hq : q) (hqp : q → p) : r :=
    absurd (hqp hq) hnp

    -- Logical equivalence
    -----------------------
    theorem and_swap : p ∧ q ↔ q ∧ p :=
    iff.intro
    (assume h : p ∧ q,
        show q ∧ p, from and.intro (and.right h) (and.left h))
    (assume h : q ∧ p,
        show p ∧ q, from and.intro (and.right h) (and.left h))

    #check and_swap p q    -- p ∧ q ↔ q ∧ p

    -- Modus ponens
    variable h : p ∧ q
    example : q ∧ p := iff.mp (and_swap p q) h

end s33

-- Section 3.4: Auxiliary Subgoals
namespace s34
    variables p q : Prop

    example (h : p ∧ q) : q ∧ p :=
    have hp : p, from and.left h,
    have hq : q, from and.right h,
    show q ∧ p, from and.intro hq hp

    -- suffices for backwards reasoning from a goal
    example (h : p ∧ q) : q ∧ p :=
    have hp : p, from and.left h,
    suffices hq : q, from and.intro hq hp,
    show q, from and.right h
    -- suffices hq : q leaves us with two goals.
    --- show that it indeed suffices to show q, by proving the original goal of q ∧ p with the additional hypothesis hq : q.
    --- show q.

end s34

-- Section 3.5: Classical logic
namespace s35
    open classical
    -- Excluded middle
    variables p q : Prop
    #check em p
    -- Double negation as consequence
    theorem dne {p : Prop} (h : ¬¬p) : p :=
    or.elim (em p)
    (assume hp : p, hp)
    (assume hnp : ¬p, absurd hnp h)
    -- This allows us to construct proofs by contradiction
    example (h : ¬¬p) : p :=
    by_cases
    (assume h1 : p, h1)
    (assume h1 : ¬p, absurd h1 h)
    example (h : ¬¬p) : p :=
    by_contradiction
    (assume h1 : ¬p,
        show false, from h h1)

    example (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
    or.elim (em p)
    (assume hp : p,
        or.inr
        (show ¬q, from
            assume hq : q,
            h ⟨hp, hq⟩))
    (assume hp : ¬p,
        or.inl hp)

end s35

-- Section 3.6: Examples of propositional validities
namespace s36
    open classical
    -- We can use sorry or an underscore to allow us to incrementally build up a proof (with warnings, not errors) to make sure the large structure is correct before tackling subgoals.
    variables p q r : Prop

    -- distributivity
    example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
    iff.intro
    (assume h : p ∧ (q ∨ r),
        have hp : p, from h.left,
        or.elim (h.right)
        (assume hq : q,
            show (p ∧ q) ∨ (p ∧ r), from or.inl ⟨hp, hq⟩)
        (assume hr : r,
            show (p ∧ q) ∨ (p ∧ r), from or.inr ⟨hp, hr⟩))
    (assume h : (p ∧ q) ∨ (p ∧ r),
        or.elim h
        (assume hpq : p ∧ q,
            have hp : p, from hpq.left,
            have hq : q, from hpq.right,
            show p ∧ (q ∨ r), from ⟨hp, or.inl hq⟩)
        (assume hpr : p ∧ r,
            have hp : p, from hpr.left,
            have hr : r, from hpr.right,
            show p ∧ (q ∨ r), from ⟨hp, or.inr hr⟩))

    -- an example that requires classical reasoning
    example : ¬(p ∧ ¬q) → (p → q) :=
    assume h : ¬(p ∧ ¬q),
    assume hp : p,
    show q, from
    or.elim (em q)
        (assume hq : q, hq)
        (assume hnq : ¬q, absurd (and.intro hp hnq) h)

end s36

-- Exercises
namespace s3x
-- 1

variables p q r s : Prop

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p := iff.intro
    (λ hpq, and.intro hpq.right hpq.left)
    (λ hpq, and.intro hpq.right hpq.left)

example : p ∨ q ↔ q ∨ p := iff.intro
    (λ hporq, or.elim hporq
        (assume hp: p, or.inr hp)
        (assume hq: q, or.inl hq))
    (λ hqorp, or.elim hqorp
        (assume hq: q, or.inr hq)
        (assume hp: p, or.inl hp))

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := iff.intro
    (assume hpq_r : (p ∧ q) ∧ r,
        and.intro hpq_r.left.left (and.intro hpq_r.left.right hpq_r.right))
    (assume hp_qr : p ∧ (q ∧ r),
        and.intro (and.intro hp_qr.left hp_qr.right.left) hp_qr.right.right)

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := iff.intro
    (assume hpq_r : (p ∨ q) ∨ r, or.elim hpq_r
        (assume hpq: (p ∨ q), or.elim hpq
            (assume hp : p, (or.inl hp))
            (assume hq : q, or.inr (or.inl hq)))
        (assume hr : r, or.inr (or.inr hr)))
    (assume hp_qr : p ∨ (q ∨ r), or.elim hp_qr
        (assume hp : p, or.inl (or.inl hp))
        (assume hqr: (q ∨ r), or.elim hqr
            (assume hq : q, or.inl (or.inr hq))
            (assume hr : r, or.inr hr)))

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := iff.intro
(assume hpqr : p ∧ (q ∨ r),
    have hp:p, from and.left hpqr,
        show (p ∧ q) ∨ (p ∧ r), from or.elim
            (and.right hpqr)
            (assume hq : q, or.inl (and.intro hp hq))
            (assume hr : r, or.inr (and.intro hp hr)))
(assume hpqpr : (p ∧ q) ∨ (p ∧ r), or.elim hpqpr
    (assume hpq : p ∧ q, and.intro
        hpq.left
        (or.inl hpq.right))
    (assume hpr : p ∧ r, and.intro
        hpr.left
        (or.inr hpr.right)))

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := iff.intro
(assume hpqr : p ∨ (q ∧ r), or.elim hpqr
    (assume hp : p, and.intro
        (or.inl hp)
        (or.inl hp))
    (assume hqr : q ∧ r, and.intro
        (or.inr hqr.left)
        (or.inr hqr.right)))
(assume hpqpr: (p ∨ q) ∧ (p ∨ r),
    show p ∨ (q ∧ r), from or.elim hpqpr.left
    (assume hp: p, or.inl hp)
    (assume hq: q,
        or.elim hpqpr.right
        (assume hp: p, or.inl hp)
        (assume hr: r, or.inr (and.intro hq hr))))

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) := iff.intro
(assume hpqr : p → (q → r),
    (assume hpr : p ∧ q, hpqr hpr.left hpr.right))
(assume hpqr : p ∧ q → r,
    (assume hp : p,
        (assume hq : q,
            have hpq : p ∧ q, from and.intro hp hq,
            hpqr hpq)))

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := iff.intro
(assume hpqr : (p ∨ q) → r,
    have hpr : p → r, from
        (assume hp: p, hpqr (or.inl hp)),
    have hqr : q → r, from
        (assume hq : q, hpqr (or.inr hq)),
    and.intro hpr hqr)
(assume hprqr : (p → r) ∧ (q → r),
    (assume hpq : p ∨ q, or.elim hpq
     (assume hp: p, hprqr.left hp)
     (assume hq: q, hprqr.right hq)))

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := iff.intro
(assume hnpq : ¬(p ∨ q),
    have hnp: ¬p, from
        (assume hp: p, hnpq (or.inl hp)),
    have hnq: ¬q, from
        (assume hq: q, hnpq (or.inr hq)),
    and.intro hnp hnq)
(assume hnpnq : ¬p ∧ ¬q,
    (assume hpq : p ∨ q, or.elim hpq
        (assume hp :p, hnpnq.left hp)
        (assume hq: q, hnpnq.right hq)))

example : ¬p ∨ ¬q → ¬(p ∧ q) :=
    (assume hnpnq : ¬p ∨ ¬q,
        (assume hpq : p ∧ q, or.elim hnpnq
            (assume hnp :¬p, hnp hpq.left)
            (assume hnq :¬q, hnq hpq.right)))

example : ¬(p ∧ ¬p) :=
    (assume hpnp: p ∧ ¬p, hpnp.right hpnp.left)

example : p ∧ ¬q → ¬(p → q) :=
    assume hpnq : p ∧ ¬q,
        assume hpq : p → q,
            have hp : p, from hpnq.left,
            have hq : q, from hpq hp,
            have hnq : ¬ q, from hpnq.right,
            hnq hq

example : ¬p → (p → q) :=
    assume hnp : ¬p,
    assume hp : p,
    absurd hp hnp

example : (¬p ∨ q) → (p → q) :=
    assume hnpq : ¬p ∨ q,
        assume hp : p,
        or.elim hnpq
            (assume hnp : ¬p, absurd hp hnp)
            id

example : p ∨ false ↔ p := iff.intro
(assume hpf : p ∨ false, or.elim hpf
    (assume hp : p, hp)
    (assume f: false,
    false.elim f))
(assume hp : p, or.inl hp)

example : p ∧ false ↔ false := iff.intro
(assume hpf: p ∧ false, hpf.right)
(assume hf : false, false.elim hf)

example : (p → q) → (¬q → ¬p) :=
    assume hpq : p → q,
    assume hnq : ¬q,
    assume hp : p,
    absurd (hpq hp) hnq

-- 3

--2 (use classical reasoning)
open classical

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
    assume hprs: p → r ∨ s,
    have hpnp : p ∨ ¬ p, from em p,
    or.elim hpnp
    (assume hp : p,
        have hrs : r ∨ s, from hprs hp,
        or.elim hrs
        (assume hr : r, or.inl (assume _ : p, hr))
        (assume hs: s, or.inr (assume _ : p, hs)))
    (assume hnp : ¬ p, or.inl
        (assume hp : p, absurd hp hnp))

example : ¬(p ∧ q) → ¬p ∨ ¬q :=
    assume hnpq : ¬(p ∧ q) ,
    have emp : p ∨ ¬p , from em p ,
    or.elim emp
    (assume hp : p,
        have emq : q ∨ ¬q , from em q ,
        or.elim emq
        (assume hq : q, absurd (and.intro hp hq) hnpq)
        (assume hnq : ¬q, or.inr hnq))
    (assume hnp : ¬p, or.inl hnp)

example : ¬(p → q) → p ∧ ¬q :=
    assume h : ¬(p → q),
    have emp : p ∨ ¬p , from em p ,
    or.elim emp
        (assume hp : p,
            have emq : q ∨ ¬q , from em q ,
            or.elim emq
                (assume hq : q,
                    have imp: p → q, from (λ x: p, hq ),
                    absurd imp h)
                (assume hnq : ¬q, and.intro hp hnq))
        (assume hnp : ¬p,
            have imp : p → q, from (λ x: p, absurd x hnp),
            absurd imp h)

example : (p → q) → (¬p ∨ q) :=
    assume hpq : p → q,
    have emp : p ∨ ¬p , from em p ,
    or.elim emp
    (assume hp : p, or.inr (hpq hp))
    (assume hnp : ¬p, or.inl hnp)

example : (¬q → ¬p) → (p → q) :=
    assume hnqnp : ¬q → ¬p,
    assume hp: p,
    have emq : q ∨ ¬q , from em q ,
    or.elim emq
    id
    (assume hnq : ¬q, absurd hp (hnqnp hnq))

example : p ∨ ¬p := em p

example : (((p → q) → p) → p) :=
    assume hpqp : (p → q) → p,
    have hem : p ∨ ¬p, from em p,
    or.elim hem
        (assume hp: p, id hp)
        (assume hnp: ¬p,
            absurd
            (have hpq: p → q, from
                (assume hp: p, absurd hp hnp),
            hpqp hpq)
            hnp)

end s3x


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc b a c,
end

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  -- sorry
  rw mul_comm c b,
  rw mul_assoc b c a,
  rw mul_comm c a,
  -- sorry
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  -- sorry
  rw ← mul_assoc a b c,
  rw mul_comm a b,
  rw mul_assoc b a c,
  -- sorry
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  -- sorry
  rw ← mul_assoc,
  -- "rw mul_comm," doesn't do what we want.
  rw mul_comm a b,
  rw mul_assoc,
  -- sorry
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  -- sorry
  rw hyp' at hyp,
  rw mul_comm b a at hyp,
  rw sub_self (a*b) at hyp,
  exact hyp,
  -- sorry
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  -- sorry
  calc c = b*a - d : by { rw hyp }
  ... = b*a - a*b : by { rw hyp' }
  ... = a*b - a*b : by { rw mul_comm a b }
  ... = 0 : by { rw sub_self (a*b) },
  -- sorry
end

/-
The preceding proofs have exhausted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particular commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  -- sorry
  ring,
  -- sorry
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  -- sorry
  ring,
  -- sorry
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  -- sorry
  rw pow_two a,
  rw pow_two b,
  rw mul_sub (a+b) a b,
  rw add_mul a b a,
  rw add_mul a b b,
  rw mul_comm b a,
  rw ← sub_sub,
  rw ← add_sub,
  rw sub_self,
  rw add_zero,
  -- sorry
end

/- Let's stick to ring in the end. -/


import data.real.basic
import data.int.parity

/-
In this file, we learn how to handle the ∃ quantifier.

In order to prove `∃ x, P x`, we give some x₀ using tactic `use x₀` and
then prove `P x₀`. This x₀ can be an object from the local context
or a more complicated expression.
-/
example : ∃ n : ℕ, 8 = 2*n :=
begin
  use 4,
  refl,  -- this is the tactic analogue of the rfl proof term
end

/-
In order to use `h : ∃ x, P x`, we use the `cases` tactic to fix
one x₀ that works.

Again h can come straight from the local context or can be a more
complicated expression.
-/
example (n : ℕ) (h : ∃ k : ℕ, n = k + 1) : n > 0 :=
begin
  -- Let's fix k₀ such that n = k₀ + 1.
  cases h with k₀ hk₀,
  -- It now suffices to prove k₀ + 1 > 0.
  rw hk₀,
  -- and we have a lemma about this
  exact nat.succ_pos k₀,
end

/-
The next exercises use divisibility in ℤ (beware the ∣ symbol which is
not ASCII).

By definition, a ∣ b ↔ ∃ k, b = a*k, so you can prove a ∣ b using the
`use` tactic.
-/

-- Until the end of this file, a, b and c will denote integers, unless
-- explicitly stated otherwise
variables (a b c : ℤ)

-- 0029
example (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
begin
  cases h₁ with k₁ hk₁,
  cases h₂ with k₂ hk₂,
  use k₁ * k₂,
  rw hk₁ at hk₂,
  linarith,
end

/-
A very common pattern is to have an assumption or lemma asserting
  h : ∃ x, y = ...
and this is used through the combo:
  cases h with x hx,
  rw hx at ...
The tactic `rcases` allows us to do recursive `cases`, as indicated by its name,
and also simplifies the above combo when the name hx is replaced by the special
name `rfl`, as in the following example.
It uses the anonymous constructor angle brackets syntax.
-/

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  rcases h1 with ⟨k, rfl⟩,
  rcases h2 with ⟨l, rfl⟩,
  use k+l,
  ring,
end

/-
You can use the same `rfl` trick with the `rintros` tactic.
-/

example : a ∣ b → a ∣ c → a ∣ b+c :=
begin
  rintros ⟨k, rfl⟩ ⟨l, rfl⟩,
  use k+l,
  ring,
end

-- 0030
example : 0 ∣ a ↔ a = 0 :=
begin
  split,
  {
    -- intros h, cases h with k hk, -- k:Z, hk:a=0*k, |- a=0
    rintros ⟨k, rfl⟩, -- k:Z, |- 0*k=0
    ring,
  },
  {
    intros h,
    rw h,
  }
end

/-
We can now start combining quantifiers, using the definition

  surjective (f : X → Y) := ∀ y, ∃ x, f x = y

-/
open function

-- In the remaining of this file, f and g will denote functions from
-- ℝ to ℝ.
variables (f g : ℝ → ℝ)

-- 0031
example (h : surjective (g ∘ f)) : surjective g :=
begin
  -- intros c,
  -- specialize h c,
  -- cases h with a ha,
  -- use (f a),
  -- linarith,

  intros c,
  cases (h c) with a ha,
  use (f a),
  linarith,
end

/-
The above exercise can be done in three lines. Try to do the
next exercise in four lines.
-/

-- 0032
example (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  intro c,
  rcases (hg c) with ⟨b, rfl⟩,
  rcases (hf b) with ⟨a, rfl⟩,
  use a,
end


/-
Copyright (c) 2020 Heather Macbeth, Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Heather Macbeth, Patrick Massot
-/
import group_theory.subgroup
import algebra.archimedean

/-!
# Archimedean groups

This file proves a few facts about ordered groups which satisfy the `archimedean` property, that is:
`class archimedean (α) [ordered_add_comm_monoid α] : Prop :=`
`(arch : ∀ (x : α) {y}, 0 < y → ∃ n : ℕ, x ≤ n • y)`

They are placed here in a separate file (rather than incorporated as a continuation of
`algebra.archimedean`) because they rely on some imports from `group_theory` -- bundled subgroups
in particular.

The main result is `add_subgroup.cyclic_of_min`:  a subgroup of a decidable archimedean abelian
group is cyclic, if its set of positive elements has a minimal element.

This result is used in this file to deduce `int.subgroup_cyclic`, proving that every subgroup of `ℤ`
is cyclic.  (There are several other methods one could use to prove this fact, including more purely
algebraic methods, but none seem to exist in mathlib as of writing.  The closest is
`subgroup.is_cyclic`, but that has not been transferred to `add_subgroup`.)

The result is also used in `topology.instances.real` as an ingredient in the classification of
subgroups of `ℝ`.
-/

variables {G : Type*} [linear_ordered_add_comm_group G] [archimedean G]
open linear_ordered_add_comm_group

/-- Given a subgroup `H` of a decidable linearly ordered archimedean abelian group `G`, if there
exists a minimal element `a` of `H ∩ G_{>0}` then `H` is generated by `a`. -/
lemma add_subgroup.cyclic_of_min {H : add_subgroup G} {a : G}
  (ha : is_least {g : G | g ∈ H ∧ 0 < g} a) : H = add_subgroup.closure {a} :=
begin
  obtain ⟨⟨a_in, a_pos⟩, a_min⟩ := ha,
  refine le_antisymm _ (H.closure_le.mpr $ by simp [a_in]),
  intros g g_in,
  obtain ⟨k, nonneg, lt⟩ : ∃ k, 0 ≤ g - k • a ∧ g - k • a < a :=
    exists_int_smul_near_of_pos' a_pos g,
  have h_zero : g - k • a = 0,
  { by_contra h,
    have h : a ≤ g - k • a,
    { refine a_min ⟨_, _⟩,
      { exact add_subgroup.sub_mem H g_in (add_subgroup.gsmul_mem H a_in k) },
      { exact lt_of_le_of_ne nonneg (ne.symm h) } },
    have h' : ¬ (a ≤ g - k • a) := not_le.mpr lt,
    contradiction },
  simp [sub_eq_zero.mp h_zero, add_subgroup.mem_closure_singleton],
end

/-- Every subgroup of `ℤ` is cyclic. -/
lemma int.subgroup_cyclic (H : add_subgroup ℤ) : ∃ a, H = add_subgroup.closure {a} :=
begin
  cases add_subgroup.bot_or_exists_ne_zero H with h h,
  { use 0,
    rw h,
    exact add_subgroup.closure_singleton_zero.symm },
  let s := {g : ℤ | g ∈ H ∧ 0 < g},
  have h_bdd : ∀ g ∈ s, (0 : ℤ) ≤ g := λ _ h, le_of_lt h.2,
  obtain ⟨g₀, g₀_in, g₀_ne⟩ := h,
  obtain ⟨g₁, g₁_in, g₁_pos⟩ : ∃ g₁ : ℤ, g₁ ∈ H ∧ 0 < g₁,
  { cases lt_or_gt_of_ne g₀_ne with Hg₀ Hg₀,
    { exact ⟨-g₀, H.neg_mem g₀_in, neg_pos.mpr Hg₀⟩ },
    { exact ⟨g₀, g₀_in, Hg₀⟩ } },
  obtain ⟨a, ha, ha'⟩ := int.exists_least_of_bdd ⟨(0 : ℤ), h_bdd⟩ ⟨g₁, g₁_in, g₁_pos⟩,
  exact ⟨a, add_subgroup.cyclic_of_min ⟨ha, ha'⟩⟩,
end


/-
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 1 : "implies" (`→`)

We learn about propositions, and implications `P → Q` between them. You can get
this arrow by typing `\to` or `\r`. Mathematicians usually write the
implication arrow as `P ⇒ Q` but Lean prefers a single arrow.

## The absolute basics

`P : Prop` means that `P` is a true-false statement. `h : P` means
that `h` is a proof that `P` is true, or you can regard `h` as an
assumption that `P` is true; logically these are the same. Stuff above
the `⊢` symbol is your assumptions. The statement to the right of it is
the goal. Your job is to prove the goal from the assumptions.

## Tactics you will need

To solve the levels on this sheet you will need to know how to use the
following tactics:

* `intro`
* `exact`
* `apply`

You can read the descriptions of these tactics in Part C of the course
notes.

## Worked examples

Click around in the proofs to see the tactic state (on the right) change.
The tactic is implemented and the state changes just before the comma.
I will use the following conventions: variables with capital
letters like `P`, `Q`, `R` denote propositions
(i.e. true/false statements) and variables whose names begin
with `h` like `h1` or `hP` are proofs or hypotheses.

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.
variables (P Q R : Prop)

-- Here are some examples of `intro`, `exact` and `apply` being used.

-- Assume that `P` and `Q` and `R` are all true. Deduce that `P` is true.
example (hP : P) (hQ : Q) (hR : R) : P :=
begin
  -- note that `exact P` does *not* work. `P` is the proposition, `hP` is the proof.
  exact hP,
end

-- Assume `Q` is true. Prove that `P → Q`.
example (hQ : Q) : P → Q :=
begin
  -- The goal is of the form `X → Y` so we can use `intro`
  intro h,
  -- now `h` is the hypothesis that `P` is true.
  -- Our goal is now the same as a hypothesis so we can use `exact`
  exact hQ,
  -- note `exact Q` doesn't work: `exact` takes the *term*, not the type.
end

-- Assume `P → Q` and `P` is true. Deduce `Q`.
example (h : P → Q) (hP : P) : Q :=
begin
  -- our goal is `⊢ Q` which matches with the conclusion of `h` so `apply` works
  apply h,
  -- now our goal has changed to `P` which is an assumption
  exact hP,
end

/-

## Examples for you to try

Delete the `sorry`s and replace them with comma-separated tactic proofs
using `intro`, `exact` and `apply`.

-/

/-- Every proposition implies itself. -/
example : P → P :=
begin
  sorry
end

/-

Note that `→` is not associative: in general `P → (Q → R)` and `(P → Q) → R`
might not be equivalent. This is like subtraction on numbers -- in general
`a - (b - c)` and `(a - b) - c` might not be equal.

So if we write `P → Q → R` then we'd better know what this means.
The convention in Lean is that it means `P → (Q → R)`. If you think
about it, this means that to deduce `R` you will need to prove both `P`
and `Q`. In general to prove `P1 → P2 → P3 → ... Pn` you can assume
`P1`, `P2`,...,`P(n-1)` and then you have to prove `Pn`.

So the next level is asking you prove that `P → (Q → P)`.

-/
example : P → Q → P :=
begin
  sorry
end

/-- If we know `P`, and we also know `P → Q`, we can deduce `Q`.
This is called "Modus Ponens" by logicians. -/
example : P → (P → Q) → Q :=
begin
  sorry
end

/-- `→` is transitive. That is, if `P → Q` and `Q → R` are true, then
  so is `P → R`. -/
example : (P → Q) → (Q → R) → (P → R) :=
begin
  sorry,
end

-- If `h : P → Q → R` with goal `⊢ R` and you `apply h`, you'll get
-- two goals! Note that tactics operate on only the first goal.
example : (P → Q → R) → (P → Q) → (P → R) :=
begin
  sorry
end

/-

Here are some harder puzzles. If you're not into logic puzzles
and you feel like you understand `intro`, `exact` and `apply`
then you can just skip these and move onto the next sheet
in this section, where you'll learn some more tactics.

-/

variables (S T : Prop)

example : (P → R) → (S → Q) → (R → T) → (Q → R) → S → T :=
begin
  sorry
end

example : (P → Q) → ((P → Q) → P) → Q :=
begin
  sorry
end

example : ((P → Q) → R) → ((Q → R) → P) → ((R → P) → Q) → P :=
begin
  sorry
end

example : ((Q → P) → P) → (Q → R) → (R → P) → P :=
begin
  sorry
end

example : (((P → Q) → Q) → Q) → (P → Q) :=
begin
  sorry
end

example :
  (((P → Q → Q) → ((P → Q) → Q)) → R) →
  ((((P → P) → Q) → (P → P → Q)) → R) →
  (((P → P → Q) → ((P → P) → Q)) → R) → R :=
begin
  sorry
end


constants a b c: int
constants  x y z m: nat
constant modus_ponens {p q : Prop} : implies p q → p → q

-- a congruent to b modulo m
def cong (a:int) (b: int) (m: int): Prop := ∃ x: int, a-b = m*x

-- p is prime
def is_prime (p:nat): Prop := ∀ x y: int, cong (x*y) 0 p → cong x 0 p ∨ cong y 0 p

-- f is the floor of a/b
def floor (a:int) (b:int) (f:int): Prop  := (f*b ≤ a ∧ ∀  y: int, y*b ≤ a → y≤f)

-- g is the gcd of a and b
def gcd (a:int) (b:int) (g:int): Prop := g > 0 ∧ cong a 0 g ∧ cong b 0 g ∧
    (∀ y:nat, cong a 0 y ∧ cong b 0 y → g ≥ y)

-- Solution to linear diophantine equation with constants a b k
def LDE (a:int) (b:int) (k:int): Prop := ∃ x y: int, a*x + b*y = k

-- existence of floor, gcd, and a solution to LDEs.
axiom Efloor (a: int) (b:int): ∃ x:int, floor a b x

axiom Egcd (a:int) (b: int): ∃ x: nat, gcd a b x

axiom ELDE (a:int) (b: int): ∃ k: int, LDE a b k ∧ k > 0 ∧ (∀ y: int, LDE a b y → y ≥ k)

-- All of the above can be proven from Wop, but are temporarily axiomatised

axiom WOP {k: nat} (p:nat → Prop) (H: p k): ∃ n: nat, p n ∧ (∀ y:nat, p y → y ≥ x)

--

-- Division algorithm

theorem DivAlgo (a : int) (b : int): ∃ q r : int, a = b*q + r ∧ 0 ≤ r ∧ b > r :=
begin
have Q: ∃ q r: int, a = b*q + r ∧ 0 ≤ r ∧ b > r,
    from exists.elim (Efloor a b) (fun (x:int) (H: x*b ≤ a ∧ ∀  y: int, y*b ≤ a → y≤x),
have Hl: x*b ≤ a, from and.elim_left H,
have Hr: ∀  y: int, y*b ≤ a → y≤x, from and.elim_right H,
have L1: 0 = b*x - b*x, from eq.symm (sub_eq_zero_of_eq (eq.refl (b*x))),
have G1: a = b*x + (a-x*b), from calc
     a = 0 + a: eq.symm (zero_add a)
    ... = b*x - b*x + a: by rw [L1]
    ... = b*x - x*b + a: by rw [mul_comm]
    ... = b*x + a - x*b: sub_add_eq_add_sub (b*x) (x*b) a
    ... = b*x + (a - x*b): add_sub_assoc (b*x) a (x*b),
have G2: 0 ≤ (a - x*b), from sub_nonneg_of_le Hl,
have Trich: b ≤ (a - x*b) ∨ b > (a - x*b), from le_or_gt b (a - x*b),
have G3: b > (a - x*b), from or.cases_on Trich
(assume ass: b ≤ (a - x*b),
have  L2: a ≥ b + x*b, from add_le_of_le_sub_right ass,
have L3:  b + (x*b) = 1*b + (x*b), from by rw [one_mul],
have L4: (x+1)*b = (1+x)*b, from by rw [add_comm],
have C: (x+1)*b ≤ a, from calc
 a ≥ b + (x*b) : L2
 ... ≥ 1*b + (x*b): le_of_eq (eq.symm L3)
 ... ≥ (1+x)*b : le_of_eq (add_mul 1 x b)
 ... ≥ (x+1)*b : le_of_eq L4,
 have F: x+1 ≤ x, from Hr (x+1) C,
 have T: x+1 > x, from int.lt_add_one_of_le (le_of_eq (eq.refl x)),
 have will: b > (a - x*b), from absurd F (not_le_of_gt T),
 will)
 (assume ass: b > (a - x*b),
 ass),
    have final: ∃ q r: int, a = b*q + r ∧ 0 ≤ r ∧ b > r,
        from exists.intro x (exists.intro (a - x*b) (and.intro G1 (and.intro G2 G3))),
    final),
    exact Q
end

--Lemma for proving that Z is a principal ideal domain later on

lemma LDEsimp {a b p :int} (W1: (LDE a b p ∧ p > 0) ∧ (∀ (q: int), LDE a b q → q ≥ p)) : cong a 0 p:=
begin
exact exists.elim (and.elim_left (and.elim_left W1))
        (fun (x : int) (W2: ∃ y:int, a*x + b*y  = p),
            exists.elim W2
            (fun (y : int) (W3: a*x + b*y = p),
                have App: ∃ m n: int, a = p*m + n ∧ 0 ≤ n ∧ p > n, from DivAlgo a p,
                exists.elim (App)
                (fun (m : int) (D1: ∃ n: int, a = p*m + n ∧ 0 ≤ n ∧ p > n),
                    exists.elim D1
                    (fun (n : int) (D: a = p*m + n ∧ (0 ≤ n ∧ p > n)),

                        have Ntest: 0 ≤ n, from and.elim_left (and.elim_right D),
                        have UB: p > n, from and.elim_right (and.elim_right D),

                        have A1: a = p*m + n, from and.elim_left D,
                        have A2: a = (a*x + b*y)*m + n, from eq.subst (eq.symm W3) A1,
                        have A3: n = a - (a*x + b*y)*m, from eq_sub_of_add_eq' (eq.symm A2),
                        have A: a*(1-x*m) + b*(-(y*m))  = n, from eq.symm (calc
                            n = a - (a*x + b*y)*m : A3
                            ... = a - (a*x*m + b*y*m) : by rw [add_mul (a*x) (b*y) m]
                            ... = a - a*x*m - b*y*m : sub_add_eq_sub_sub a (a*x*m) (b*y*m)
                            ... = a*1 - a*x*m - b*y*m : by rw [mul_one]
                            ... = a*1 - a*(x*m) - b*y*m : by rw [mul_assoc]
                            ... = a*(1 - x*m) - (b*y*m) : by rw [eq.symm (mul_sub a 1 (x*m))]
                            ... = a*(1-x*m) + -(b*y*m) : by rw [sub_eq_add_neg]
                            ... = a*(1-x*m) + -(b*(y*m)) : by rw [mul_assoc]
                            ... = a*(1-x*m) + b*(-(y*m)) : by rw [neg_mul_eq_mul_neg]
                        ),
                        have Z1: n = 0, from or.by_cases (lt_or_eq_of_le Ntest)
                        (assume H1: n > 0,
                        have C0: LDE a b n, from exists.intro (1-x*m) (exists.intro (-(y*m)) A),
                        have C1: ∀ q: int, LDE a b q → q ≥ p, from  (and.elim_right W1),
                        have C2: n ≥ p, from C1 n C0,
                        have C4: ¬ n < p, from not_lt_of_ge C2,
                        have E: n = 0, from absurd UB C4,
                        E)
                        (assume H: 0 = n,
                        eq.symm H),
                        have P1: a = (p*m) + 0, from eq.subst Z1 A1,
                        have P2: a = (p*m), from calc a = (p*m) + 0 : P1 ... = (p*m) : by rw [add_zero],
                        have P3: a - 0 = p*m, from eq.trans (sub_zero a) P2,
                        have P4: cong a 0 p, from exists.intro m P3,
                        P4
                    )
                )
            )
        )
end

--WIP

lemma LDEcomm (a: int) (b: int) (p: int) : LDE a b p ↔ LDE b a p :=
begin
have H:LDE a b p ↔ LDE b a p, from sorry,
exact H
end

theorem IntegersFormPID (a : int) (b : int): ∃ g:int, LDE a b g ∧ gcd a b g :=
begin
have H1: ∃ g:int, LDE a b g ∧ gcd a b g, from exists.elim (ELDE a b)
    (fun (p:int) (W1: (LDE a b p ∧ p > 0) ∧  (∀ q: int, LDE a b q → q ≥ p)),
    have H: cong a 0 p, from LDEsimp W1,
    have W11: LDE a b p, from (and.elim_left (and.elim_left W1)),
    have W12: LDE b a p, from iff.elim_left (LDEcomm a b p) W11,
    have W2: p > 0, from and.elim_right (and.elim_left W1),
    have W31: LDE a b p → LDE b a p, from iff.elim_left (LDEcomm a b p),
    have W3: (∀ q: int, LDE b a q → q ≥ p), from iff_subst (LDEcomm a b p) (and.elim_right (and.elim_right W1)),
    have W2: (LDE b a p ∧ p > 0) ∧  (∀ q: int, LDE b a q → q ≥ p), from iff_subst (LDEcomm a b p) W1,
    have Hb: cong b 0 p, from LDEsimp W1,
    )
end


/-
Copyright (c) 2022 Jon Eugster. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jon Eugster
-/
import algebra.char_p.basic
import data.nat.factorization.prime_pow
import ring_theory.ideal.local_ring

/-!
# Characteristics of local rings

## Main result

- `char_p_zero_or_prime_power`: In a commutative local ring the characteristics is either
  zero or a prime power.

-/

/-- In a local ring the characteristics is either zero or a prime power. -/
theorem char_p_zero_or_prime_power (R : Type*) [comm_ring R] [local_ring R] (q : ℕ)
  [char_R_q : char_p R q] : q = 0 ∨ is_prime_pow q :=
begin
  /- Assume `q := char(R)` is not zero. -/
  apply or_iff_not_imp_left.2,
  intro q_pos,
  let K := local_ring.residue_field R,
  haveI RM_char := ring_char.char_p K,

  let r := ring_char K,
  let n := (q.factorization) r,
  /- `r := char(R/m)` is either prime or zero: -/
  cases char_p.char_is_prime_or_zero K r with r_prime r_zero,
  { let a := q / (r ^ n),
    /- If `r` is prime, we can write it as `r = a * q^n` ... -/
    have q_eq_a_mul_rn : q = r ^ n * a := by rw nat.mul_div_cancel' (nat.pow_factorization_dvd q r),
    have r_ne_dvd_a := nat.not_dvd_div_pow_factorization r_prime q_pos,

    have rn_dvd_q: r ^ n ∣ q := ⟨a, q_eq_a_mul_rn⟩,
    rw mul_comm at q_eq_a_mul_rn,
    have a_dvd_q : a ∣ q := ⟨r ^ n, q_eq_a_mul_rn⟩,
    /- ... where `a` is a unit. -/
    have a_unit : is_unit (a : R) :=
    begin
      by_contradiction g,
      rw ←mem_nonunits_iff at g,
      rw ←local_ring.mem_maximal_ideal at g,
      have a_cast_zero := (ideal.quotient.eq_zero_iff_mem).2 g,
      rw map_nat_cast at a_cast_zero,
      have r_dvd_a := (ring_char.spec K a).1 a_cast_zero,
      exact absurd r_dvd_a r_ne_dvd_a,
    end,
    /- Let `b` be the inverse of `a`. -/
    cases a_unit.exists_left_inv with a_inv h_inv_mul_a,
    have rn_cast_zero : ↑(r ^ n) = (0 : R) :=
    begin
      rw [nat.cast_pow, ←@mul_one R _ (r ^ n), mul_comm,
        ←(classical.some_spec a_unit.exists_left_inv), mul_assoc, ←nat.cast_pow, ←nat.cast_mul,
        ←q_eq_a_mul_rn, char_p.cast_eq_zero R q],
      simp,
    end,
    have q_eq_rn := nat.dvd_antisymm ((char_p.cast_eq_zero_iff R q (r ^ n)).mp rn_cast_zero)
      rn_dvd_q,
    have n_pos : n ≠ 0 :=
    begin
      by_contradiction n_zero,
      simp [n_zero] at q_eq_rn,
      exact absurd q_eq_rn (char_p.char_ne_one R q),
    end,

    /- Definition of prime power: `∃ r n, prime r ∧ 0 < n ∧ r ^ n = q`. -/
    exact ⟨r, ⟨n, ⟨nat.prime_iff.mp r_prime, ⟨pos_iff_ne_zero.mpr n_pos, q_eq_rn.symm⟩⟩⟩⟩},
  { haveI K_char_p_0 := ring_char.of_eq r_zero,
    haveI K_char_zero: char_zero K := char_p.char_p_to_char_zero K,
    haveI R_char_zero := ring_hom.char_zero (local_ring.residue R),
    /- Finally, `r = 0` would lead to a contradiction: -/
    have q_zero := char_p.eq R char_R_q (char_p.of_char_zero R),
    exact absurd q_zero q_pos}
end


/-
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

/-!

# Sets in Lean, example sheet 2 : "the" empty set and the "universal set".

Lean notation for the empty subset of `X` is `∅`. Unlike in
set theory, there is more than one empty set in Lean! Every
type has an empty subset, and it *doesn't make sense*
to ask if `∅ : set ℕ` and `∅ : set ℤ` are equal, because
they have different types.

At the other extreme, the subset of `X` containing all the terms of type `X`
is...well...mathematicians would just call it `X`, but `X` is a type not a subset.
The subset of `X` consisting of every element of `X` is called `set.univ : set X`,
or just `univ : set X` if we have opened the `set` namespace. Let's do that now.

-/

open set

/-

## Definition of ∅ and univ

`x ∈ ∅` is *by definition* equal to `false` and `x ∈ univ` is *by definition*
equal to `true`. You can use the `change` tactic to make these changes
if you like. But you don't have to. Remember that `triv` proves `true`
and `cases h` will solve a goal if `h : false`.

-/

-- set up variables
variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

open set

example : x ∈ (univ : set X) :=
begin
  sorry
end

example : x ∈ (∅ : set X) → false :=
begin
  sorry
end

example : A ⊆ univ :=
begin
  sorry
end

example : ∅ ⊆ A :=
begin
  sorry
end


import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
import AdvancedAdditionWorld.Level8 -- eq_zero_of_add_right_eq_self
import AdvancedAdditionWorld.Level11 -- add_right_eq_zero
namespace MyNat
open MyNat
/-!

# Inequality world.

## Level 6: `le_antisymm`

In Advanced Addition World you proved

`eq_zero_of_add_right_eq_self (a b : MyNat) : a + b = a → b = 0`.

This might be useful in this level.

Another tip: if you want to create a new hypothesis, you can use the `have` tactic.
For example, if you have a hypothesis `hd : a + (c + d) = a` and you want
a hypothesis `h : c + d = 0` then you can write

`have h := eq_zero_of_add_right_eq_self hd`

## Lemma : le_antisymm
`≤` is antisymmetric. In other words, if `a ≤ b` and `b ≤ a` then `a = b`.
-/
theorem le_antisymm (a b : MyNat) (hab : a ≤ b) (hba : b ≤ a) : a = b := by
  cases hab with
  | _ c hc =>
    cases hba with
    | _ d hd =>
      rw [hc, add_assoc] at hd
      have hd := hd.symm
      have h := eq_zero_of_add_right_eq_self hd
      have h2 := add_right_eq_zero h
      rw [h2] at hc
      rw [hc]
      exact add_zero a

/-!
This proved that the natural numbers are a partial order!
-/
-- BUGBUG : collectibles
-- instance : partial_order MyNat := by structure_helper

/-!
Next up [Level 7](./Level7.lean.md)
-/


-- Natural numbers

-- data type
#eval nat.zero
#eval (nat.succ nat.zero)
#eval nat.succ (nat.succ nat.zero)

-- notation
#eval 0
#eval 1
#eval 2

-- operators
#eval nat.add 1 2   -- addition
#eval nat.mul 2 3   -- multiplication
#eval nat.pow 2 3   -- exponentiation
#eval nat.sub 4 1   -- subtraction
#eval nat.div 4 3   -- quotient
#eval nat.mod 4 3   -- remainder

-- notation
#eval 1 + 2
#eval 2 * 3
#eval 4 - 1
#eval 4 / 3
#eval 4 % 3

-- a theorem and its already Lean-defined proof
def add_commutes : ∀ (n m : ℕ), n + m = m + n := nat.add_comm


import data.real.basic

variables{u v: ℕ → ℝ }{l a b: ℝ}

notation`|`x`|` := abs x

def limite (u : ℕ → ℝ) (l : ℝ) : Prop :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε

def sucesion_cauchy (u : ℕ → ℝ) :=
∀ ε > 0, ∃ N, ∀ p q, p ≥ N → q ≥ N → |u p - u q| ≤ ε

/-
DEMOSTRACIÓN 1
-/

example : (∃ l, limite u l) → sucesion_cauchy u :=
begin
  intro h,
  unfold sucesion_cauchy,
  intros ε hε,
  have hε2 : 0 < ε/2,
    { linarith },
  cases h with l hl,
  cases hl (ε /2) hε2 with N hN,
  clear hε hl hε2,
  use N,
  intros p q hp hq,
  calc |u p - u q| = |(u p - l)+(l - u q)| : by ring_nf
               ... ≤ |u p - l| + |l - u q| : abs_add (u p - l) (l - u q)
               ... = |u p - l| + |u q - l| : by rw abs_sub l (u q)
               ... ≤ ε /2 + ε /2           : add_le_add (hN p hp) (hN q hq)
               ... = ε                     : add_halves ε,
end

/-
DEMOSTRACIÓN 2
-/

example : (∃ l, limite u l) → sucesion_cauchy u :=
begin
  intro h,
  intros ε hε,
  cases h with l hl,
  cases hl (ε /2) (by linarith) with N hN,
  clear hε hl,
  use N,
  intros p q hp hq,
  calc |u p - u q| = |(u p - l)+(l - u q)| : by ring_nf
               ... ≤ |u p - l| + |l - u q| : abs_add (u p - l) (l - u q)
               ... = |u p - l| + |u q - l| : by rw abs_sub l (u q)
               ... ≤ ε /2 + ε /2           : add_le_add (hN p hp) (hN q hq)
               ... = ε                     : add_halves ε,
end

/-
DEMOSTRACIÓN 3
-/

example : (∃ l, limite u l) → sucesion_cauchy u :=
begin
  intros h ε hε,
  cases h with l hl,
  cases hl (ε /2) (by linarith) with N hN,
  clear hε hl,
  use N,
  intros p q hp hq,
  have cota1 : |u p - l| ≤ ε / 2 := hN p hp,
  have cota2 : |u q - l| ≤ ε / 2 := hN q hq,
  clear hN hp hq,
  calc |u p - u q| = |(u p - l)+(l - u q)| : by ring_nf
               ... ≤ |u p - l| + |l - u q| : abs_add (u p - l) (l - u q)
               ... = |u p - l| + |u q - l| : by rw abs_sub l (u q)
               ... ≤ ε                     : by linarith,
end

/-
DEMOSTRACIÓN 4
-/

example : (∃ l, limite u l) → sucesion_cauchy u :=
begin
  intros h ε hε,
  cases h with l hl,
  cases hl (ε /2) (by linarith) with N hN,
  clear hε hl,
  use N,
  intros p q hp hq,
  calc |u p - u q| = |(u p - l)+(l - u q)| : by ring_nf
               ... ≤ |u p - l| + |l - u q| : abs_add (u p - l) (l - u q)
               ... = |u p - l| + |u q - l| : by rw abs_sub l (u q)
               ... ≤ ε                     : by linarith [hN p (by linarith), hN q (by linarith)],
end


import lib.m154

/-
# Disjonctions

Ce fichier est consacré au connecteur logique de disjonction, noté simplement « ou »
sur papier et `∨` dans Lean ainsi que dans les livres de logique pure (attention
ce symbole n'est pas la lettre v).

Pour *démontrer* un but de la forme `P ∨ Q`, on utilise
`Montrons que P` et on démontre P ou `Montrons que Q` et
on démontre Q (il faut donc faire un choix stratégique).
-/

example : 2^3 = 3 ∨ 2^3 = 8 :=
begin
  Montrons que 2^3 = 8,
  On calcule,
end

example : 1 + 1 = 2 ∨ 1 + 3 = 17 :=
begin
  sorry
end

/-
Pour *utiliser* une hypothèse de la forme `hyp : P ∨ Q`
lors de la démonstration d'un but `R`, on tape
`On discute en utilisant hyp,`
(ou bien `On discute en utilisant (hyp : P ∨ Q),` pour plus de clarté)
qui crée deux branches dans la démonstration :
une branche où il faut démontrer `P → R`
et l'autre où il faut démontrer `Q → R`.
-/

example {a : ℝ} (h : a = 2 ∨ a = -2) : a^2 = 4 :=
begin
  On discute en utilisant h,
  Supposons ha : a = 2,
    calc
      a^2 = 2^2 : by On réécrit via ha
      ... = 4   : by On calcule,
  Supposons ha : a = -2,
    calc
      a^2 = (-2)^2 : by On réécrit via ha
      ... = 4   : by On calcule,
end

example {a : ℝ} (h : a = 2 ∨ a = -3) : a^2 + a = 6 :=
begin
  sorry
end

/-
Voyons maintenant des énoncés qui successivement utilisent et démontrent
une disjonction. L'utilisation d'une disjonction crée un embranchement
dans la démonstration et la décision stratégique pour démontrer la disjonction
but dépend de la branche courante.

Dans l'exemple suivant, on utilise aussi
la commande `On combine` suivie d'une liste d'hypothèses,
qui cherche à démontrer une égalité ou une inégalité
par combinaison linéaire de ces hypothèses.
Si la liste ne consiste qu'en une seule hypothèse,
il est plus naturel d'utiliser `On conclut par`.
Cette commande permet aussi de déduire `a ≤ b` de `a < b`
ou d'autres trivialités de ce genre.

Enfin on utilise le lemme
`mul_eq_zero : a*b = 0 ↔ a = 0 ∨ b = 0`
qui servira aussi dans l'exercice suivant.
-/

example (a b : ℝ) : a = a*b → a = 0 ∨ b = 1 :=
begin
  Supposons hyp : a = a*b,
  Fait H : a*(1 - b) = 0,
    calc a*(1 - b) = a - a*b : by On calcule
               ... = 0       : by On conclut par hyp,
  On réécrit via mul_eq_zero dans H qui devient a = 0 ∨ 1 - b = 0,
  On discute en utilisant (H : a = 0 ∨ 1 - b = 0),
    Supposons H : a = 0,
    Montrons que a = 0,
    On conclut par H,
  Supposons H : 1 - b = 0,
  Montrons que b = 1,
  On conclut par H,
end

example (x y : ℝ) : x^2 = y^2 → x = y ∨ x = -y :=
begin
  sorry
end

/-
On va maintenant combiner le quantificateur universel
et la disjonction dans deux exercices plus intéressants.

On rappelle la définition de fonction croissante :
-/

def croissante (f : ℝ → ℝ) := ∀ x₁, (∀ x₂, x₁ ≤ x₂ → f x₁ ≤ f x₂)

/-
Dans l'exercice suivant, on pourra utiliser le lemme
`eq_or_lt_of_le : x ≤ y → x = y ∨ x < y`
-/
example (f : ℝ → ℝ) : croissante f ↔ ∀ x y, x < y → f x ≤ f y :=
begin
  sorry
end

/-
Dans l'exercice suivant, on pourra utiliser le lemme
`le_total x y : x ≤ y ∨ y ≤ x`

Cet exercice nécessite de réfléchir un peu plus que les
précédents, l'énoncé n'étant pas complètement évident.
-/
example (f : ℝ → ℝ) (h : croissante f) (h' : ∀ x, f (f x) = x) : ∀ x, f x = x :=
begin
  sorry
end


-- ----------------------------------------------------
-- Ejercicio 2. Demostrar
--    ∃ x, ¬(P x) ⊢ ¬(∀ x, P x)
-- ----------------------------------------------------

import tactic

variable (U : Type)
variable (P : U -> Prop)

-- 1ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
begin
  intro h1,
  cases h with a h2,
  apply h2,
  exact h1 a,
end

-- 2ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
begin
  intro h1,
  cases h with a h2,
  exact h2 (h1 a),
end

-- 3ª demostración
example :
  (∃ x, ¬(P x)) → ¬(∀ x, P x) :=
begin
  rintro ⟨a, h2⟩ h1,
  exact h2 (h1 a),
end

-- 4ª demostración
example :
  (∃ x, ¬(P x)) → ¬(∀ x, P x) :=
λ ⟨a, h2⟩ h1, h2 (h1 a)

-- 5ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
assume h1 : ∀ x, P x,
exists.elim h
  ( assume a,
    assume h2 : ¬(P a),
    have h3 : P a,
      from h1 a,
    show false,
      from h2 h3 )

-- 6ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
assume h1 : ∀ x, P x,
exists.elim h
  ( assume a,
    assume h2 : ¬(P a),
    h2 (h1 a) )

-- 7ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
assume h1 : ∀ x, P x,
exists.elim h
  (λ a h2, h2 (h1 a) )

-- 8ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
λ h1, exists.elim h (λ a h2, h2 (h1 a) )

-- 9ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
-- by library_search
not_forall.mpr h

-- 10ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
-- by hint
by tauto

-- 11ª demostración
example
  (h: ∃ x, ¬(P x))
  : ¬(∀ x, P x) :=
by finish


/-
Copyright (c) 2022 James Gallicchio.

Authors: James Gallicchio
-/

import Mathlib.Data.Nat.Basic
import Mathlib.Init.Data.Int.Basic
import Mathlib.Order.Basic
import Mathlib.Data.UInt

namespace Nat
  theorem sub_dist (x y z : Nat) : x - (y + z) = x - y - z := by
    induction z
    simp
    case succ z z_ih =>
    simp [Nat.sub_succ, Nat.add_succ, z_ih]

  theorem sub_lt_of_lt_add {x y z : Nat}
    : x < y + z → x ≥ z → x - z < y
    := by
    intro h h_z
    apply Nat.le_of_add_le_add_right
    rw [succ_add, Nat.sub_add_cancel h_z]
    assumption

  theorem add_mul_div {x y z : Nat} (h_x : 0 < x)
    : (x * y + z) / x = y + z / x
    := by
    induction y generalizing z with
    | zero => simp
    | succ y ih =>
      simp [mul_succ, Nat.add_assoc]
      rw [ih]
      simp [HDiv.hDiv, Div.div]
      rw [Nat.div]
      simp [h_x, Nat.le_add_right]
      rw [Nat.add_comm x z, Nat.add_sub_cancel,
        Nat.add_comm _ 1, ←Nat.add_assoc, Nat.add_one]

  theorem mul_div_with_rem_cancel (x : Nat) {q r : Nat} (h_r : r < q)
    : (x * q + r) / q = x
    := by
    induction x with
    | zero =>
      rw [div_eq]
      have : 0 < q := zero_lt_of_lt h_r
      simp [this, Nat.not_le_of_gt h_r]
    | succ x ih =>
      rw [div_eq]
      simp [zero_lt_of_lt h_r]
      have : q ≤ (x + 1) * q + r := by
        simp [succ_mul]
        rw [Nat.add_comm _ q, Nat.add_assoc]
        apply Nat.le_add_right
      simp [this]
      rw [succ_mul, Nat.add_assoc, Nat.add_comm q,
          ←Nat.add_assoc, Nat.add_sub_cancel]
      assumption

  theorem le_of_mul_of_div { x y : Nat }
    : x * (y / x) ≤ y
    := by
    apply Nat.le_of_add_le_add_right (b := y % x)
    rw [div_add_mod]
    apply Nat.le_add_right

  /-
  theorem lt_of_mul_lt {x y z : Nat} (h_z : 0 < z)
    : x < y * z → x / z < y
    := by
    intro h
    by_cases x / z < y
    case pos h_res =>
      assumption
    case neg h_res =>
      rw [←div_add_mod x z] at h
      apply False.elim $ Nat.not_le_of_gt h _
      clear h h_z
      rw [Nat.mul_comm]
      suffices z * y ≤ z * (x / z) from
        Nat.le_trans this $ Nat.le_add_right _ (x % z)
      apply Nat.mul_le_mul_left z
      exact Nat.ge_of_not_lt h_res
  -/

  theorem lt_of_lt_le {x y z : Nat} : x < y → y ≤ z → x < z := by
    intro h h'
    induction h'
    assumption
    apply Nat.le_step
    assumption

  theorem min_le_right {x y : Nat} : min x y ≤ y := by
    simp [min]
    split
    assumption
    simp

  theorem min_symm (x y : Nat) : min x y = min y x := by
    simp [min]
    split
    case inl h =>
      cases h <;> simp
      case step h => simp [Nat.not_le.mpr (Nat.succ_le_succ h)]
    case inr h =>
      simp [Nat.le_of_lt <| Nat.gt_of_not_le h]

  @[simp]
  theorem min_zero_left {x} : min 0 x = 0 := by
    rw [←Nat.le_zero_eq]
    exact min_le_left _ _

  @[simp]
  theorem min_zero_right {x} : min x 0 = 0 := by
    rw [←Nat.le_zero_eq]
    exact min_le_right

  def toUSize! (n : Nat) : USize :=
    if n < USize.size then
      n.toUSize
    else
      panic! s!"Integer {n} is to larget for usize"

  theorem mod_lt_of_lt {x y z : Nat}
    : x < y → z > 0 → x % z < y
    := by
    intro h h_z
    match h_y : decide (y ≤ z) with
    | true =>
      have := of_decide_eq_true h_y
      rw [Nat.mod_eq_of_lt (Nat.lt_of_lt_le h this)]
      assumption
    | false =>
      have := of_decide_eq_false h_y
      apply Nat.lt_trans _ (Nat.gt_of_not_le this)
      apply Nat.mod_lt
      assumption

  @[inline]
  def square (n : Nat) := n * n

  def sqrt (n : Nat) : Nat :=
    let guess := n / 2
    if guess = 0 then n else
    let rec iter (guess : Nat) : Nat :=
      let next := (guess + n / guess) / 2
      if h : guess ≤ next then
        guess
      else
        have : next < guess := Nat.gt_of_not_le h
        iter next
    iter guess
  termination_by iter guess => guess

  theorem square_sqrt_le (n)
    : square (sqrt n) ≤ n
    :=
    match n with
    | 0 | 1 => by simp
    | n+2 =>
    let rec iter n (guess) (g_pos : guess > 0) (g_le_n : guess ≤ n)
      : square (sqrt.iter n guess) ≤ n
      :=
      let next := (guess + n / guess) / 2
      if h : guess ≤ next then by
        unfold sqrt.iter
        simp [h, square]
        apply (le_div_iff_mul_le g_pos).mp
        apply Nat.le_of_add_le_add_left (a := guess)
        rw [(by simp [succ_mul] : guess + guess = 2 * guess)]
        rw [mul_comm]
        apply (le_div_iff_mul_le (by decide)).mp
        exact h
      else
        have : next < guess := Nat.gt_of_not_le h
        have next_pos : next > 0 := by
          have : n / guess = succ _ := by
            conv => lhs; simp [Div.div, HDiv.hDiv]; unfold Nat.div
            simp [g_pos, g_le_n]
            rfl
          cases guess <;> simp at this
          simp [this, succ_add, add_succ]
          simp [succ_eq_add_one, add_assoc]
          rw [(by decide : 1 + 1 = 2)]
          simp [←add_assoc]
          apply succ_le_succ (zero_le _)
        have next_le_n : next ≤ n := by
          simp
          apply Nat.div_le_of_le_mul
          simp [succ_mul]
          apply Nat.add_le_add
          assumption
          apply Nat.div_le_self
        have := iter n next next_pos next_le_n
        by unfold sqrt.iter; simp [h, this]
    have := iter (n+2) ((n+2)/2)
      (by simp; apply succ_le_succ; simp)
      (Nat.div_le_self _ _)
    by simp [sqrt] at this ⊢; exact this
  termination_by iter guess _ _ => guess

  theorem square_succ_sqrt_gt (n)
    : n < square ((sqrt n)+1)
    :=
    match n with
    | 0 | 1 => by simp
    | n+2 =>
    let rec iter n (guess) (g_succ_gt_n : square (guess+1) > n)
      : n < square (sqrt.iter n guess + 1)
      :=
      let next := (guess + n / guess) / 2
      if h : guess ≤ next then by
        unfold sqrt.iter
        simp [h, g_succ_gt_n]
      else
        have : next < guess := Nat.gt_of_not_le h
        have : square (next + 1) > n := by
          have : square (next + 1) ≤ square guess := by sorry
          have : n < square guess := by sorry
          sorry
        have := iter n next this
        by unfold sqrt.iter; simp [h, this]
    have := iter (n+2) ((n+2)/2) (by
      simp [square]
      have : n ≤ 2 * (n / 2) + 1 := by
        apply Nat.le_trans (m := 2 * (n / 2) + n % 2)
        rw [Nat.div_add_mod]; apply Nat.le_refl
        apply Nat.add_le_add; apply Nat.le_refl
        apply Nat.le_of_succ_le_succ
        simp [(by decide : succ 1 = 2)]
        apply Nat.mod_lt
        decide
      simp [succ_mul, mul_succ] at this ⊢
      simp [succ_add, add_succ]
      apply succ_le_succ; apply succ_le_succ; apply succ_le_succ
      apply Nat.le_trans this
      apply succ_le_succ
      simp
      apply Nat.add_le_add_right
      apply Nat.le_add_left)
    by simp [sqrt] at this ⊢; exact this
  termination_by iter guess _ _ => guess

#eval sqrt 0
#eval (7 + 15 / 7) / 2

end Nat

namespace Fin

@[simp]
def embed_add_right : Fin n → Fin (n+m)
| ⟨i, h⟩ => ⟨i, Nat.lt_of_lt_le h (Nat.le_add_right _ _)⟩

@[simp]
def embed_add_left : Fin n → Fin (m+n)
| ⟨i, h⟩ => ⟨i, Nat.lt_of_lt_le h (Nat.le_add_left _ _)⟩

@[simp]
def embed_succ : Fin n → Fin n.succ
| ⟨i, h⟩ => ⟨i, Nat.lt_of_lt_le h (Nat.le_step $ Nat.le_refl _)⟩

@[simp]
def last (n : Nat) : Fin n.succ := ⟨n, Nat.lt_succ_self _⟩

end Fin

namespace Int

theorem ofNat_natAbs_eq_neg_of_nonpos (a : Int) (h : a ≤ 0)
  : ofNat (natAbs a) = -a
  := by
  rw [←Int.natAbs_neg]
  apply Int.ofNat_natAbs_eq_of_nonneg
  apply Int.neg_le_neg h

end Int

namespace USize

theorem usize_bounded : USize.size ≤ UInt64.size := by
  cases usize_size_eq <;> (
    rw [(by assumption : USize.size = _)]
    decide
  )

end USize

namespace List
  def front? : List τ → Option (τ × List τ)
  | [] => none
  | t::ts => some (t,ts)

  @[simp]
  theorem front_cons (ts : List τ)
    : (ts.cons t).front? = some (t,ts)
    := by
    cases ts
    repeat {simp [front?]}

  def back? : List τ → Option (List τ × τ)
  | [] => none
  | t::ts => some (match ts.back? with | none => ([],t) | some (ts',t') => (t::ts',t'))

  @[simp]
  theorem back_concat (ts : List τ)
    : (ts.concat t).back? = some (ts,t)
    := by
    induction ts
    simp [back?]
    case cons t' ts' ih =>
    simp at ih ⊢
    simp [back?, ih]

  theorem concat_nonempty (L : List τ)
    : L.concat x ≠ []
    := by
    induction L
    simp [concat]
    simp [concat]

  theorem back_some_iff_concat (L : List τ)
    : L.back? = some (ts,t) ↔ L = ts.concat t
    := by
    apply Iff.intro
    case mpr =>
      intro h; rw [h, back_concat]
    case mp =>
    induction ts generalizing L
    case nil =>
      simp [concat]
      intro h
      match L with
      | [] => contradiction
      | [t] => simp [back?] at h; simp [h]
      | _::_::_ => simp [back?] at h
    case cons head tail ih =>
      simp [concat]
      intro h
      match L with
      | [] => contradiction
      | [x] => simp [back?] at h
      | x::y::z =>
        have ihh : back? (y::z) = some (tail, t) := by
          simp [back?] at h
          cases h; case intro l r =>
          simp [back?]
          match h:back? z with
          | none =>
            simp [h] at l r
            simp [l,r]
          | some (ts',t') =>
            simp [h] at l r ⊢
            simp [l,r]
        have := ih _ ihh
        rw [this]; simp; clear this
        unfold back? at h
        simp [ihh] at h
        assumption

  @[simp]
  theorem concat_append (L₁ L₂ : List τ)
    : (L₁ ++ L₂).concat x = L₁ ++ L₂.concat x
    := by
    induction L₁ <;> simp

  @[simp]
  theorem map_concat (L : List τ) (f : τ → α)
    : (L.concat x).map f = (L.map f).concat (f x)
    := by
    induction L <;> simp [concat,map]

  @[simp]
  theorem join_concat (L : List (List τ))
    : (L.concat x).join = L.join ++ x
    := by
    simp [concat]
    induction L <;> simp [concat,join]
    assumption

  @[simp]
  theorem get_of_set_eq (L : List τ) (i : Fin L.length) (x : τ)
    : (L.set i x).get ⟨i, by rw [length_set]; exact i.isLt⟩ = x
    := by
      induction L
      simp at i; exact Fin.elim0 i
      case cons hd tl ih =>
      cases i; case mk i h_i =>
      cases i
      simp [set,get]
      case succ i =>
      simp at h_i
      have h_i := Nat.lt_of_succ_lt_succ h_i
      have := ih ⟨i,h_i⟩
      simp at this
      simp [set,get]

  @[simp]
  theorem get_of_set_ne (L : List τ) (i : Nat) (x : τ) (j : Fin L.length)
    : i ≠ j → (L.set i x).get ⟨j, by rw [length_set]; exact j.isLt⟩ = L.get j
    := by
      intro h
      induction L generalizing i
      simp at j; exact Fin.elim0 j
      case cons hd tl ih =>
      cases j; case mk j h_j =>
      cases j
      simp at h
      cases i; contradiction
      simp [set,get]
      case succ j =>
      simp at h_j
      simp
      cases i
      simp [set,get]
      case succ i =>
      simp [set,get]
      simp at h
      have h_j := Nat.lt_of_succ_lt_succ h_j
      have := ih i ⟨j,h_j⟩ h
      exact this

  theorem set_of_ge_length (L : List α) (i x) (h : ¬i < L.length)
    : L.set i x = L
    := by
    induction L generalizing i
    simp
    case cons hd tl ih =>
    cases i
    simp at h
    simp at h
    simp
    apply ih
    apply Nat.not_lt_of_le
    apply Nat.le_of_succ_le_succ h

  theorem set_append_left (L1 L2 : List α) (i x) (h : i < L1.length)
    : (L1 ++ L2).set i x = L1.set i x ++ L2
    := by
    induction L1 generalizing i with
    | nil => simp at h; contradiction
    | cons hd tl ih =>
    match i with
    | 0 =>
      simp at h
      simp [set]
    | i+1 =>
      simp at h
      simp [set]
      apply ih
      apply Nat.le_of_succ_le_succ h

  theorem set_append_right (L1 L2 : List α) (i x) (h : L1.length ≤ i)
    : (L1 ++ L2).set i x = L1 ++ L2.set (i - L1.length) x
    := by
    induction L1 generalizing i with
    | nil => simp [set]
    | cons hd tl ih =>
    match i with
    | 0 =>
      simp at h
    | i+1 =>
      simp at h
      simp [set]
      rw [Nat.succ_sub_succ]
      apply ih
      apply Nat.le_of_succ_le_succ h

  theorem map_set {L : List τ} {i x} {f : τ → τ'}
    : (L.set i x).map f = (L.map f).set i (f x)
    := by
    induction L generalizing i with
    | nil => simp
    | cons y ys ih =>
      cases i
      simp [set]
      simp [ih]

  theorem set_map {L : List τ} (x') (h : x = f x')
    : (L.map f).set i x = (L.set i x').map f
    := by
    induction L generalizing i with
    | nil => simp
    | cons y ys ih =>
      cases i
      simp [set]; assumption
      simp [set, ih]

  def subtypeByMem (L : List α) : List {a // a ∈ L} :=
    let rec aux (rest : List α) (h : ∀ a, a ∈ rest → a ∈ L)
      : List {a // a ∈ L} :=
      match rest, h with
      | [], _ => []
      | (x::xs), h =>
        ⟨x, h _ (List.Mem.head _ _)⟩ ::
        aux xs (by intros; apply h; apply List.Mem.tail; assumption)
    aux L (by intros; assumption)

  theorem length_subtypeByMemAux (L rest : List α) (h)
    : (List.subtypeByMem.aux L rest h).length = rest.length
    := by
      induction rest
      simp [subtypeByMem.aux]
      case cons hd tl ih =>
      simp [subtypeByMem.aux]
      apply ih

  @[simp]
  theorem length_subtypeByMem (L : List α)
    : L.subtypeByMem.length = L.length
    := by apply length_subtypeByMemAux

  @[simp]
  theorem get_subtypeByMem (L : List α) (i : Fin L.subtypeByMem.length)
    : L.subtypeByMem.get i = L.get ⟨i, by cases i; case mk _ h => simp at h; assumption⟩
    := by
    simp [subtypeByMem]
    suffices ∀ L' hL' i (hi : i < length L'),
      (get (subtypeByMem.aux L L' hL') ⟨i, by
        simp [length_subtypeByMemAux] at hi ⊢
        exact hi⟩).val =
      get L' ⟨i, hi⟩
      from this L (by simp) i (by cases i; case mk _ h => simp at h; assumption)
    intro L' hL' i hi
    induction L' generalizing i with
    | nil =>
      simp [subtypeByMem.aux]
      unfold get
      split <;> split <;> contradiction
    | cons x xs ih =>
      simp [subtypeByMem.aux]
      unfold get
      split
      case h_1 a b c d e f g =>
        simp at f
        cases f; case intro f1 f2 =>
        cases f1
        cases f2
        simp at g
        split
        case h_1 h i j k l m n o =>
          cases n
          rfl
        case h_2 h i j k l m n o =>
          cases n
          cases o
          contradiction
      case h_2 a b c d e f g =>
        simp at f
        cases f; case intro f1 f2 =>
        cases f1
        cases f2
        simp at g
        split
        case h_1 h i j k l m n o =>
          cases n
          cases o
          contradiction
        case h_2 h i j k l m n o =>
          cases n
          cases o
          cases g
          simp at e
          apply ih

  def index_of_mem (L : List α) (x) (h : x ∈ L) : ∃ i, L.get i = x := by
    induction L
    cases h <;> contradiction
    case cons hd tl ih =>
    cases h
    apply Exists.intro ⟨0,by apply Nat.succ_le_succ; exact Nat.zero_le _⟩
    simp [get]
    cases ih (by assumption)
    case intro w h =>
    apply Exists.intro w.succ
    simp [get]
    exact h

  theorem get_of_take (L : List α) (n i) (h : n ≤ L.length)
    : (L.take n).get i = L.get ⟨i.val, by
        apply Nat.le_trans i.isLt
        simp [length_take]
        rw [Nat.min_symm]
        exact Nat.min_le_left _ _
      ⟩
    := by
    induction L generalizing n
    case nil =>
      cases n <;> (
        simp [length, take] at i
        exact Fin.elim0 i
      )
    case cons hd tl ih =>
    cases n
    case zero =>
      simp [length, take] at i
      exact Fin.elim0 i
    case succ n =>
      cases i; case mk i h_i =>
      cases i
      case zero =>
        simp [get, take]
      case succ i =>
        simp [get, take]
        apply ih
        simp [length] at h
        exact Nat.le_of_succ_le_succ h

  theorem get_map_reverse (f : α → β) {l n}
    : f (get l n) = get (map f l) ⟨n, by simp [n.isLt]⟩
    := by simp

  @[simp]
  theorem length_rangeAux : (rangeAux n L).length = L.length + n := by
    induction n generalizing L <;> simp [length, rangeAux, *]
    case succ n ih =>
    rw [←Nat.add_one, Nat.add_comm n 1, Nat.add_assoc]

  @[simp]
  theorem length_range : (range n).length = n := by
    simp [range]

  theorem rangeAux_eq_append
    : rangeAux n (x :: L) = rangeAux n [] ++ (x :: L)
    := by
    suffices ∀ L, rangeAux n L = rangeAux n [] ++ L from
      this (cons x L)
    intro L
    induction n generalizing L
    simp [rangeAux]
    case succ n ih =>
    simp [get, rangeAux]
    rw [@ih (n :: L), @ih [n]]
    simp [List.append_assoc]

  theorem get?_range (n) (i : Nat) (h : i < n) : (range n).get? i = some i := by
    induction n with
    | zero => cases h
    | succ n ih =>
      unfold range
      simp [rangeAux]
      rw [rangeAux_eq_append]
      have : i < length (rangeAux n [] ++ [n]) := by
        simp; assumption
      rw [get?_eq_get this, Option.some_inj]
      match h_i:Nat.beq i n with
      | true =>
        simp at h_i
        rw [List.get_append_right]
        simp [get, h_i]
        simp [h_i]
        simp [h_i]
      | false =>
        have h := Nat.le_of_succ_le_succ h
        have h_i := Nat.ne_of_beq_eq_false h_i
        have : i < n := Nat.lt_of_le_of_ne h h_i
        rw [List.get_append_left]
        case h => simp [this]
        rw [←Option.some_inj]
        rw [←get?_eq_get]
        exact ih this

  theorem get_range' (n) (i : Nat) (h) : (range n).get ⟨i, h⟩ = i := by
    rw [←Option.some_inj, ← get?_eq_get]
    exact get?_range _ _ (by simp at h; exact h)

  @[simp]
  theorem get_range (i : Fin n) : (range n).get (cast (by simp) i) = i := by
    have : ∀ m (h : m = n) h', (cast (h ▸ rfl) i : Fin m) = ⟨i, h'⟩ := by
      intros m h h'; cases h; rfl
    rw [this]; apply get_range'; simp; simp [i.2]

  theorem foldl_acc_cons (L : List τ) (f : _ → _) (x') (acc : List τ')
    : L.foldl (fun acc x => acc ++ f x) (x' :: acc)
      = x' :: L.foldl (fun acc x => acc ++ f x) acc
    := by
    induction L generalizing acc with
    | nil => simp [foldl]
    | cons x xs ih =>
      unfold foldl
      rw [List.cons_append, ih]

  theorem foldl_eq_reverseAux (L : List τ) (acc)
    : L.foldl (fun acc x => x :: acc) acc = L.reverseAux acc
    := by
    induction L generalizing acc with
    | nil => simp [foldl]
    | cons x xs ih =>
      unfold foldl
      apply ih

  theorem foldl_eq_map (L : List τ) (f : τ → τ')
    : L.foldl (fun acc x => acc ++ [f x]) [] = L.map f
    := by
    induction L with
    | nil => simp [foldl]
    | cons x xs ih =>
      unfold foldl
      simp [foldl_acc_cons]
      apply ih

  theorem foldl_eq_filter (L : List τ) (f : τ → Bool)
    : L.foldl (fun acc x => acc ++ if f x then [x] else []) [] = L.filter f
    := by
    induction L with
    | nil => simp [filter, foldl]
    | cons x xs ih =>
      unfold foldl
      apply Eq.symm
      simp [filter]
      split <;> (
        simp [(by assumption : f x = _)]
        simp [foldl_acc_cons]
        apply ih.symm
      )

  theorem foldl_filter (L : List τ) (f : τ → Bool) (foldF) (foldAcc : β)
    : (L.filter f).foldl foldF foldAcc =
      L.foldl (fun acc x => if f x then foldF acc x else acc) foldAcc
    := by
    induction L generalizing foldAcc with
    | nil => simp [foldl]
    | cons x xs ih =>
      unfold filter
      split
      case cons.h_1 h =>
        simp [h, foldl, ih]
      case cons.h_2 h =>
        simp [h, foldl, ih]

  theorem foldr_eq_map (L : List τ) (f : τ → τ')
    : L.foldr (f · :: ·) [] = L.map f
    := by induction L <;> simp; assumption

  theorem foldr_eq_filter (L : List τ) (f : τ → Bool)
    : L.foldr (fun x acc => if f x then x :: acc else acc) [] = L.filter f
    := by
      induction L <;> simp [filter]
      split <;> split
      case h_1 =>
        simp; assumption
      case h_2 h _ h' =>
        rw [h] at h'; contradiction
      case h_1 =>
        contradiction
      case h_2 =>
        simp; assumption

  theorem foldr_cons_eq_foldl_append (L : List τ) (f : _ → β)
    : L.foldr (f · :: ·) [] = L.foldl (· ++ [f ·]) []
    := by rw [foldr_eq_map, foldl_eq_map]

  theorem mem_of_map_iff (L : List τ) (f : τ → τ')
    : ∀ y, y ∈ L.map f ↔ ∃ x, x ∈ L ∧ f x = y
    := by
    intro y
    induction L with
    | nil => simp
    | cons x xs ih =>
      simp
      constructor
      case mp =>
        intro h; cases h
        case inl h =>
          exact .inl h.symm
        case inr h =>
          cases ih.mp h; case intro x' h =>
          exact .inr ⟨x', h.1, h.2⟩
      case mpr =>
        intro h; cases h
        case inl h' =>
          exact .inl h'.symm
        case inr h' =>
          exact Or.inr (ih.mpr h')

end List

inductive Vector (α : Type u) : Nat → Type u where
  | nil  : Vector α 0
  | cons : α → Vector α n → Vector α (n+1)

namespace Vector
  def ofList : (L : List τ) → Vector τ L.length
  | [] => nil
  | x::xs => cons x (ofList xs)

  def toList : (V : Vector τ n) → List τ
  | nil => []
  | cons x xs => x :: toList xs

  theorem length_toList (V : Vector τ n)
  : V.toList.length = n
  := by induction V <;> simp [toList]; assumption
end Vector

namespace Function
  def update' {α α' : Sort u} {β : α → Sort u} (f : (a : α) → β a) (i : α) (x : α') [D : DecidableEq α]
    : (a : α) → update β i α' a
    := λ a =>
    if h:a = i
    then cast (by simp [h, update]) x
    else cast (by simp [h]) (f a)
end Function

def Cached {α : Type _} (a : α) := { b // b = a }

namespace Cached

instance {a : α} : DecidableEq (Cached a) :=
  fun ⟨x, hx⟩ ⟨y, hy⟩ => Decidable.isTrue (by cases hx; cases hy; rfl)

instance {a : α} [Repr α] : Repr (Cached a) where
  reprPrec x := Repr.addAppParen <| "cached " ++ repr x.val

instance {a : α} : Subsingleton (Cached a) :=
  ⟨by intro ⟨x, h⟩; cases h; intro ⟨y, h⟩; cases h; rfl⟩

instance {a : α} : CoeHead (Cached a) α where
  coe x := x.1

def cached (a : α) : Cached a :=
  ⟨a, rfl⟩

def cached' (a : α) (h : a = b) : Cached b :=
  ⟨a, h⟩

instance {a : α} : Inhabited (Cached a) where
  default := cached a

@[simp] theorem cached_val (a : α) (b : Cached a) : (b : α) = a := b.2

end Cached

export Cached (cached)
export Cached (cached')

def time (f : IO α) : IO (Nat × α) := do
  let pre ← IO.monoMsNow
  let ret ← f
  let post ← IO.monoMsNow
  pure (post-pre, ret)


import tactic

variables (p q r : Prop)

-- 1ª demostración
example : ((p ∨ q) → r) ↔ ((p → r) ∧ (q → r)) :=
or_imp_distrib

-- 2ª demostración
example : ((p ∨ q) → r) ↔ ((p → r) ∧ (q → r)) :=
⟨λ h, ⟨λ hp, h (or.inl hp),
       λ hq, h (or.inr hq)⟩,
 λ ⟨hpr, hqr⟩ hpq, hpq.elim hpr hqr⟩

-- 3ª demostración
example : ((p ∨ q) → r) ↔ ((p → r) ∧ (q → r)) :=
begin
  split,
  { intro h,
    split,
    { intro hp,
      exact h (or.inl hp), },
    { intro hq,
      exact h (or.inr hq), }},
  { rintros ⟨hpr, hqr⟩ hpq,
    cases hpq with hp hq,
    { exact hpr hp, },
    { exact hqr hq, }},
end


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Yury Kudryashov, Neil Strickland

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

/-!
# Lemmas about regular elements in rings.
-/

variable {α : Type _}

/-- Left `Mul` by a `k : α` over `[Ring α]` is injective, if `k` is not a zero divisor.
The typeclass that restricts all terms of `α` to have this property is `NoZeroDivisors`. -/
theorem isLeftRegular_of_non_zero_divisor [NonUnitalNonAssocRing α] (k : α)
    (h : ∀ x : α, k * x = 0 → x = 0) : IsLeftRegular k := by
  refine' fun x y (h' : k * x = k * y) => sub_eq_zero.mp (h _ _)
  rw [mul_sub, sub_eq_zero, h']
#align is_left_regular_of_non_zero_divisor isLeftRegular_of_non_zero_divisor

/-- Right `Mul` by a `k : α` over `[Ring α]` is injective, if `k` is not a zero divisor.
The typeclass that restricts all terms of `α` to have this property is `NoZeroDivisors`. -/
theorem isRightRegular_of_non_zero_divisor [NonUnitalNonAssocRing α] (k : α)
    (h : ∀ x : α, x * k = 0 → x = 0) : IsRightRegular k := by
  refine' fun x y (h' : x * k = y * k) => sub_eq_zero.mp (h _ _)
  rw [sub_mul, sub_eq_zero, h']
#align is_right_regular_of_non_zero_divisor isRightRegular_of_non_zero_divisor

theorem isRegular_of_ne_zero' [NonUnitalNonAssocRing α] [NoZeroDivisors α] {k : α} (hk : k ≠ 0) :
    IsRegular k :=
  ⟨isLeftRegular_of_non_zero_divisor k fun _ h =>
      (NoZeroDivisors.eq_zero_or_eq_zero_of_mul_eq_zero h).resolve_left hk,
    isRightRegular_of_non_zero_divisor k fun _ h =>
      (NoZeroDivisors.eq_zero_or_eq_zero_of_mul_eq_zero h).resolve_right hk⟩
#align is_regular_of_ne_zero' isRegular_of_ne_zero'

theorem isRegular_iff_ne_zero' [Nontrivial α] [NonUnitalNonAssocRing α] [NoZeroDivisors α]
    {k : α} : IsRegular k ↔ k ≠ 0 :=
  ⟨fun h => by
    rintro rfl
    exact not_not.mpr h.left not_isLeftRegular_zero, isRegular_of_ne_zero'⟩
#align is_regular_iff_ne_zero' isRegular_iff_ne_zero'

/-- A ring with no zero divisors is a `CancelMonoidWithZero`.

Note this is not an instance as it forms a typeclass loop. -/
@[reducible]
def NoZeroDivisors.toCancelMonoidWithZero [Ring α] [NoZeroDivisors α] : CancelMonoidWithZero α :=
  { (by infer_instance : MonoidWithZero α) with
    mul_left_cancel_of_ne_zero := fun ha =>
      @IsRegular.left _ _ _ (isRegular_of_ne_zero' ha) _ _,
    mul_right_cancel_of_ne_zero := fun hb =>
      @IsRegular.right _ _ _ (isRegular_of_ne_zero' hb) _ _ }
#align no_zero_divisors.to_cancel_monoid_with_zero NoZeroDivisors.toCancelMonoidWithZero

/-- A commutative ring with no zero divisors is a `CancelCommMonoidWithZero`.

Note this is not an instance as it forms a typeclass loop. -/
@[reducible]
def NoZeroDivisors.toCancelCommMonoidWithZero [CommRing α] [NoZeroDivisors α] :
    CancelCommMonoidWithZero α :=
  { NoZeroDivisors.toCancelMonoidWithZero, ‹CommRing α› with }
#align no_zero_divisors.to_cancel_comm_monoid_with_zero NoZeroDivisors.toCancelCommMonoidWithZero

section IsDomain

-- see Note [lower instance priority]
instance (priority := 100) IsDomain.toCancelMonoidWithZero [Semiring α] [IsDomain α] :
    CancelMonoidWithZero α :=
  { }
#align is_domain.to_cancel_monoid_with_zero IsDomain.toCancelMonoidWithZero

variable [CommSemiring α] [IsDomain α]

-- see Note [lower instance priority]
instance (priority := 100) IsDomain.toCancelCommMonoidWithZero : CancelCommMonoidWithZero α :=
  { mul_left_cancel_of_ne_zero := IsLeftCancelMulZero.mul_left_cancel_of_ne_zero }
#align is_domain.to_cancel_comm_monoid_with_zero IsDomain.toCancelCommMonoidWithZero

end IsDomain


import ..lectures.love10_denotational_semantics_demo

/-! # LoVe Exercise 10: Denotational Semantics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Question 1: Monotonicity

1.1. Prove the following lemma from the lecture. -/

lemma monotone_comp {α β : Type} [partial_order α] (f g : α → set (β × β))
    (hf : monotone f) (hg : monotone g) :
  monotone (λa, f a ◯ g a) :=
sorry

/-! 1.2. Prove its cousin. -/

lemma monotone_restrict {α β : Type} [partial_order α] (f : α → set (β × β))
    (p : β → Prop) (hf : monotone f) :
  monotone (λa, f a ⇃ p) :=
sorry

/-! ## Question 2: Regular Expressions

__Regular expressions__, or __regexes__, are a highly popular tool for software
development, to analyze textual inputs. Regexes are generated by the following
grammar:

    R ::= ∅
        | ε
        | a
        | R ⬝ R
        | R + R
        | R*

Informally, the semantics of regular expressions is as follows:

* `∅` accepts nothing;
* `ε` accepts the empty string;
* `a` accepts the atom `a`;
* `R ⬝ R` accepts the concatenation of two regexes;
* `R + R` accepts either of two regexes;
* `R*` accepts arbitrary many repetitions of a regex.

Notice the rough correspondence with a WHILE language:

    `∅` ~ diverging statement (e.g., `while true do skip`)
    `ε` ~ `skip`
    `a` ~ `:=`
    `⬝` ~ `;`
    `+` ~ `if then else`
    `*` ~ `while` loop -/

inductive regex (α : Type) : Type
| nothing : regex
| empty   : regex
| atom    : α → regex
| concat  : regex → regex → regex
| alt     : regex → regex → regex
| star    : regex → regex

/-! In this exercise, we explore an alternative semantics of regular
expressions. Namely, we can imagine that the atoms represent binary relations,
instead of letters or symbols. Concatenation corresponds to composition of
relations, and alternation is union. Mathematically, regexes and binary
relations are both instances of Kleene algebras.

2.1. Complete the following translation of regular expressions to relations.

Hint: Exploit the correspondence with the WHILE language. -/

def rel_of_regex {α : Type} : regex (set (α × α)) → set (α × α)
| regex.nothing        := ∅
| regex.empty          := Id
| (regex.atom s)       := s
-- enter the missing cases here

/-! 2.2. Prove the following recursive equation about your definition. -/

lemma rel_of_regex_star {α : Type} (r : regex (set (α × α))) :
  rel_of_regex (regex.star r) =
  rel_of_regex (regex.alt (regex.concat r (regex.star r)) regex.empty) :=
sorry

end LoVe