import .love03_forward_proofs_demo
import .love04_functional_programming_demo

/-! # LoVe Demo 5: Inductive Predicates

__Inductive predicates__, or inductively defined propositions, are a convenient
way to specify functions of type `⋯ → Prop`. They are reminiscent of formal
systems and of the Horn clauses of Prolog, the logic programming language par
excellence.

A possible view of Lean:

    Lean = functional programming + logic programming + more logic -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Introductory Examples

### Even Numbers

Mathematicians often define sets as the smallest that meets some criteria. For
example:

    The set `E` of even natural numbers is defined as the smallest set closed
    under the following rules: (1) `0 ∈ E` and (2) for every `k ∈ ℕ`, if
    `k ∈ E`, then `k + 2 ∈ E`.

In Lean, we can define the corresponding "is even" predicate as follows: -/

inductive even : ℕ → Prop
| zero    : even 0
| add_two : ∀k : ℕ, even k → even (k + 2)

/-! This should look familiar. We have used the same syntax, except with `Type`
instead of `Prop`, for inductive types.

The above command introduces a new unary predicate `even` as well as two
constructors, `even.zero` and `even.add_two`, which can be used to build proof
terms. Thanks to the "no junk" guarantee of inductive definitions, `even.zero`
and `even.add_two` are the only two ways to construct proofs of `even`.

By the PAT principle, `even` can be seen as an inductive type, the values being
the proof terms. -/

lemma even_4 :
  even 4 :=
have even_0 : even 0 :=
  even.zero,
have even_2 : even 2 :=
  even.add_two _ even_0,
show even 4, from
  even.add_two _ even_2

/-! Why cannot we simply define `even` recursively? Indeed, why not? -/

def even₂ : ℕ → bool
| 0       := tt
| 1       := ff
| (k + 2) := even₂ k

/-! There are advantages and disadvantages to both styles.

The recursive version requires us to specify a false case (1), and it requires
us to worry about termination. On the other hand, because it is computational,
it works well with `refl`, `simp`, `#reduce`, and `#eval`.

The inductive version is often considered more abstract and elegant. Each rule
is stated independently of the others.

Yet another way to define `even` is as a nonrecursive definition: -/

def even₃ (k : ℕ) : bool :=
k % 2 = 0

/-! Mathematicians would probably find this the most satisfactory definition.
But the inductive version is a convenient, intuitive example that is typical of
many realistic inductive definitions.

### Tennis Games

Transition systems consists of transition rules, which together specify a
binary predicate connecting a "before" and an "after" state. As a simple
specimen of a transition system, we consider the possible transitions, in a game
of tennis, starting from 0–0. -/

inductive score : Type
| vs       : ℕ → ℕ → score
| adv_srv  : score
| adv_rcv  : score
| game_srv : score
| game_rcv : score

infixr ` – ` : 10 := score.vs

inductive step : score → score → Prop
| srv_0_15     : ∀n, step (0–n) (15–n)
| srv_15_30    : ∀n, step (15–n) (30–n)
| srv_30_40    : ∀n, step (30–n) (40–n)
| srv_40_game  : ∀n, n < 40 → step (40–n) score.game_srv
| srv_40_adv   : step (40–40) score.adv_srv
| srv_adv_40   : step score.adv_srv (40–40)
| srv_adv_game : step score.adv_srv score.game_srv
| rcv_0_15     : ∀n, step (n–0) (n–15)
| rcv_15_30    : ∀n, step (n–15) (n–30)
| rcv_30_40    : ∀n, step (n–30) (n–40)
| rcv_40_game  : ∀n, n < 40 → step (n–40) score.game_rcv
| rcv_40_adv   : step (40–40) score.adv_rcv
| rcv_adv_40   : step score.adv_rcv (40–40)
| rcv_adv_game : step score.adv_rcv score.game_rcv

infixr ` ⇒ ` := step

/-! We can ask, and formally answer, questions such as: Can the score ever
return to 0–0? -/

lemma no_step_to_0_0 (s : score) :
  ¬ step s (0–0) :=
begin
  intro h,
  cases' h
end

/-! ### Reflexive Transitive Closure

Our last introductory example is the reflexive transitive closure of a
relation `r`, modeled as a binary predicate `star r`. -/

inductive star {α : Type} (r : α → α → Prop) : α → α → Prop
| base (a b : α)    : r a b → star a b
| refl (a : α)      : star a a
| trans (a b c : α) : star a b → star b c → star a c

/-! The first rule embeds `r` into `star r`. The second rule achieves the
reflexive closure. The third rule achieves the transitive closure.

The definition is truly elegant. If you doubt this, try implementing `star` as a
recursive function: -/

def star_rec {α : Type} (r : α → α → bool) :
  α → α → bool :=
sorry

/-! ### A Nonexample

Not all inductive definitions are legal. -/

-- fails
inductive illegal₂ : Prop
| intro : ¬ illegal₂ → illegal₂

/-! ## Logical Symbols

The truth values `false` and `true`, the connectives `∧` and `∨`, the
`∃`-quantifier, and the equality predicate `=` are all defined as inductive
propositions or predicates. In contrast, `∀` (= `Π`) and `→` are built into
the logic.

Syntactic sugar:

    `∃x : α, p` := `Exists (λx : α, p)`
    `x = y`     := `eq x y` -/

namespace logical_symbols

inductive and (a b : Prop) : Prop
| intro : a → b → and

inductive or (a b : Prop) : Prop
| intro_left  : a → or
| intro_right : b → or

inductive iff (a b : Prop) : Prop
| intro : (a → b) → (b → a) → iff

inductive Exists {α : Type} (p : α → Prop) : Prop
| intro : ∀a : α, p a → Exists

inductive true : Prop
| intro : true

inductive false : Prop

inductive eq {α : Type} : α → α → Prop
| refl : ∀a : α, eq a a

end logical_symbols

#print and
#print or
#print iff
#print Exists
#print true
#print false
#print eq

/-! ## Rule Induction

Just as we can perform induction on a term, we can perform induction on a proof
term.

This is called __rule induction__, because the induction is on the introduction
rules (i.e., the constructors of the proof term). Thanks to the PAT principle,
this works as expected. -/

lemma mod_two_eq_zero_of_even (n : ℕ) (h : even n) :
  n % 2 = 0 :=
begin
  induction' h,
  case zero {
    refl },
  case add_two : k hk ih {
    simp [ih] }
end

lemma not_even_two_mul_add_one (n : ℕ) :
  ¬ even (2 * n + 1) :=
begin
  intro h,
  induction' h,
  apply ih (n - 1),
  cases' n,
  case zero {
    linarith },
  case succ {
    simp [nat.succ_eq_add_one] at *,
    linarith }
end

/-! `linarith` proves goals involving linear arithmetic equalities or
inequalities. "Linear" means it works only with `+` and `-`, not `*` and `/`
(but multiplication by a constant is supported). -/

lemma linarith_example (i : ℤ) (hi : i > 5) :
  2 * i + 3 > 11 :=
by linarith

lemma star_star_iff_star {α : Type} (r : α → α → Prop)
    (a b : α) :
  star (star r) a b ↔ star r a b :=
begin
  apply iff.intro,
  { intro h,
    induction' h,
    case base : a b hab {
      exact hab },
    case refl : a {
      apply star.refl },
    case trans : a b c hab hbc ihab ihbc {
      apply star.trans a b,
      { exact ihab },
      { exact ihbc } } },
  { intro h,
    apply star.base,
    exact h }
end

@[simp] lemma star_star_eq_star {α : Type}
    (r : α → α → Prop) :
  star (star r) = star r :=
begin
  apply funext,
  intro a,
  apply funext,
  intro b,
  apply propext,
  apply star_star_iff_star
end

#check funext
#check propext

/-! ## Elimination

Given an inductive predicate `p`, its introduction rules typically have the form
`∀…, ⋯ → p …` and can be used to prove goals of the form `⊢ p …`.

Elimination works the other way around: It extracts information from a lemma or
hypothesis of the form `p …`. Elimination takes various forms: pattern matching,
the `cases'` and `induction'` tactics, and custom elimination rules (e.g.,
`and.elim_left`).

* `cases'` works like `induction'` but without induction hypothesis.

* `match` is available as well, but it corresponds to dependently typed pattern
  matching (cf. `vector` in lecture 4).

Now we can finally analyze how `cases' h`, where `h : l = r`, and how
`cases' classical.em h` work. -/

#print eq

lemma cases_eq_example {α : Type} (l r : α) (h : l = r)
    (p : α → α → Prop) :
  p l r :=
begin
  cases' h,
  sorry
end

#check classical.em
#print or

lemma cases_classical_em_example {α : Type} (a : α)
    (p q : α → Prop) :
  q a :=
begin
  have h : p a ∨ ¬ p a :=
    classical.em (p a),
  cases' h,
  case inl {
    sorry },
  case inr {
    sorry }
end

/-! Often it is convenient to rewrite concrete terms of the form `p (c …)`,
where `c` is typically a constructor. We can state and prove an
__inversion rule__ to support such eliminative reasoning.

Typical inversion rule:

    `∀x y, p (c x y) → (∃…, ⋯ ∧ ⋯) ∨ ⋯ ∨ (∃…, ⋯ ∧ ⋯)`

It can be useful to combine introduction and elimination into a single lemma,
which can be used for rewriting both the hypotheses and conclusions of goals:

    `∀x y, p (c x y) ↔ (∃…, ⋯ ∧ ⋯) ∨ ⋯ ∨ (∃…, ⋯ ∧ ⋯)` -/

lemma even_iff (n : ℕ) :
  even n ↔ n = 0 ∨ (∃m : ℕ, n = m + 2 ∧ even m) :=
begin
  apply iff.intro,
  { intro hn,
    cases' hn,
    case zero {
      simp },
    case add_two : k hk {
      apply or.intro_right,
      apply exists.intro k,
      simp [hk] } },
  { intro hor,
    cases' hor,
    case inl : heq {
      simp [heq, even.zero] },
    case inr : hex {
      cases' hex with k hand,
      cases' hand with heq hk,
      simp [heq, even.add_two _ hk] } }
end

lemma even_iff₂ (n : ℕ) :
  even n ↔ n = 0 ∨ (∃m : ℕ, n = m + 2 ∧ even m) :=
iff.intro
  (assume hn : even n,
   match n, hn with
   | _, even.zero         :=
     show 0 = 0 ∨ _, from
       by simp
   | _, even.add_two k hk :=
     show _ ∨ (∃m, k + 2 = m + 2 ∧ even m), from
       or.intro_right _ (exists.intro k (by simp [*]))
   end)
  (assume hor : n = 0 ∨ (∃m, n = m + 2 ∧ even m),
   match hor with
   | or.inl heq :=
     show even n, from
       by simp [heq, even.zero]
   | or.inr hex :=
     match hex with
     | Exists.intro m hand :=
       match hand with
       | and.intro heq hm :=
         show even n, from
           by simp [heq, even.add_two _ hm]
       end
     end
   end)

/-! ## Further Examples

### Sorted Lists -/

inductive sorted : list ℕ → Prop
| nil : sorted []
| single {x : ℕ} : sorted [x]
| two_or_more {x y : ℕ} {zs : list ℕ} (hle : x ≤ y)
    (hsorted : sorted (y :: zs)) :
  sorted (x :: y :: zs)

lemma sorted_nil :
  sorted [] :=
sorted.nil

lemma sorted_2 :
  sorted [2] :=
sorted.single

lemma sorted_3_5 :
  sorted [3, 5] :=
begin
  apply sorted.two_or_more,
  { linarith },
  { exact sorted.single }
end

lemma sorted_3_5₂ :
  sorted [3, 5] :=
sorted.two_or_more (by linarith) sorted.single

lemma sorted_7_9_9_11 :
  sorted [7, 9, 9, 11] :=
sorted.two_or_more (by linarith)
  (sorted.two_or_more (by linarith)
     (sorted.two_or_more (by linarith)
        sorted.single))

lemma not_sorted_17_13 :
  ¬ sorted [17, 13] :=
begin
  intro h,
  cases' h,
  linarith
end

/-! ### Palindromes -/

inductive palindrome {α : Type} : list α → Prop
| nil : palindrome []
| single (x : α) : palindrome [x]
| sandwich (x : α) (xs : list α) (hxs : palindrome xs) :
  palindrome ([x] ++ xs ++ [x])

-- fails
def palindrome₂ {α : Type} : list α → Prop
| []                 := true
| [_]                := true
| ([x] ++ xs ++ [x]) := palindrome₂ xs
| _                  := false

lemma palindrome_aa {α : Type} (a : α) :
  palindrome [a, a] :=
palindrome.sandwich a _ palindrome.nil

lemma palindrome_aba {α : Type} (a b : α) :
  palindrome [a, b, a] :=
palindrome.sandwich a _ (palindrome.single b)

lemma reverse_palindrome {α : Type} (xs : list α)
    (hxs : palindrome xs) :
  palindrome (reverse xs) :=
begin
  induction' hxs,
  case nil {
    exact palindrome.nil },
  case single {
    exact palindrome.single x },
  case sandwich {
    simp [reverse, reverse_append],
    exact palindrome.sandwich _ _ ih }
end

/-! ### Full Binary Trees -/

#check btree

inductive is_full {α : Type} : btree α → Prop
| empty : is_full btree.empty
| node (a : α) (l r : btree α)
    (hl : is_full l) (hr : is_full r)
    (hiff : l = btree.empty ↔ r = btree.empty) :
  is_full (btree.node a l r)

lemma is_full_singleton {α : Type} (a : α) :
  is_full (btree.node a btree.empty btree.empty) :=
begin
  apply is_full.node,
  { exact is_full.empty },
  { exact is_full.empty },
  { refl }
end

lemma is_full_mirror {α : Type} (t : btree α)
    (ht : is_full t) :
  is_full (mirror t) :=
begin
  induction' ht,
  case empty {
    exact is_full.empty },
  case node : a l r hl hr hiff ih_l ih_r {
    rw mirror,
    apply is_full.node,
    { exact ih_r },
    { exact ih_l },
    { simp [mirror_eq_empty_iff, *] } }
end

lemma is_full_mirror₂ {α : Type} (t : btree α) :
  is_full t → is_full (mirror t) :=
begin
  induction' t,
  case empty {
    intro ht,
    exact ht },
  case node : a l r ih_l ih_r {
    intro ht,
    cases' ht with _ _ _ hl hr hiff,
    rw mirror,
    apply is_full.node,
    { exact ih_r hr },
    { apply ih_l hl },
    { simp [mirror_eq_empty_iff, *] } }
end

/-! ### First-Order Terms -/

inductive term (α β : Type) : Type
| var {} : β → term
| fn     : α → list term → term

inductive well_formed {α β : Type} (arity : α → ℕ) :
  term α β → Prop
| var (x : β) : well_formed (term.var x)
| fn (f : α) (ts : list (term α β))
    (hargs : ∀t ∈ ts, well_formed t)
    (hlen : list.length ts = arity f) :
  well_formed (term.fn f ts)

inductive variable_free {α β : Type} : term α β → Prop
| fn (f : α) (ts : list (term α β))
    (hargs : ∀t ∈ ts, variable_free t) :
  variable_free (term.fn f ts)

end LoVe


import tuto_lib
import data.int.parity
/-
Negations, proof by contradiction and contraposition.

This file introduces the logical rules and tactics related to negation:
exfalso, by_contradiction, contrapose, by_cases and push_neg.

There is a special statement denoted by `false` which, by definition,
has no proof.

So `false` implies everything. Indeed `false → P` means any proof of
`false` could be turned into a proof of P.
This fact is known by its latin name
"ex falso quod libet" (from false follows whatever you want).
Hence Lean's tactic to invoke this is called `exfalso`.
-/

example : false → 0 = 1 :=
begin
  intro h,
  exfalso,
  exact h,
end

/-
The preceding example suggests that this definition of `false` isn't very useful.
But actually it allows us to define the negation of a statement P as
"P implies false" that we can read as "if P were true, we would get
a contradiction". Lean denotes this by `¬ P`.

One can prove that (¬ P) ↔ (P ↔ false). But in practice we directly
use the definition of `¬ P`.
-/

example {x : ℝ} : ¬ x < x :=
begin
  intro hyp,
  rw lt_iff_le_and_ne at hyp,
  cases hyp with hyp_inf hyp_non,
  clear hyp_inf, -- we won't use that one, so let's discard it
  change x = x → false at hyp_non, -- Lean doesn't need this psychological line
  apply hyp_non,
  refl,
end

open int

-- 0045
example (n : ℤ) (h_even : even n) (h_not_even : ¬ even n) : 0 = 1 :=
begin
  exfalso, exact h_not_even h_even,
end

-- 0046
example (P Q : Prop) (h₁ : P ∨ Q) (h₂ : ¬ (P ∧ Q)) : ¬ P ↔ Q :=
begin
  split, intro h, cases h₁ with p q, exfalso, exact h p, exact q,
  push_neg at h₂, intros q p, exact h₂ p q,
end

/-
The definition of negation easily implies that, for every statement P,
P → ¬ ¬ P

The excluded middle axiom, which asserts P ∨ ¬ P allows us to
prove the converse implication.

Together those two implications form the principle of double negation elimination.
  not_not {P : Prop} : (¬ ¬ P) ↔ P

The implication `¬ ¬ P → P` is the basis for proofs by contradiction:
in order to prove P, it suffices to prove ¬¬ P, ie `¬ P → false`.

Of course there is no need to keep explaining all this. The tactic
`by_contradiction Hyp` will transform any goal P into `false` and
add Hyp : ¬ P to the local context.

Let's return to a proof from the 5th file: uniqueness of limits for a sequence.
This cannot be proved without using some version of the excluded middle
axiom. We used it secretely in

eq_of_abs_sub_le_all (x y : ℝ) : (∀ ε > 0, |x - y| ≤ ε) → x = y

(we'll prove a variation on this lemma below).

In the proof below, we also take the opportunity to introduce the `let` tactic
which creates a local definition. If needed, it can be unfolded using `dsimp` which
takes a list of definitions to unfold. For instance after using `let N₀ := max N N'`,
you could write `dsimp [N₀] at h` to replace `N₀` by its definition in some
local assumption `h`.
-/
example (u : ℕ → ℝ) (l l' : ℝ) : seq_limit u l → seq_limit u l' → l = l' :=
begin
  intros hl hl',
  by_contradiction H,
  change l ≠ l' at H, -- Lean does not need this line
  have ineg : |l-l'| > 0,
    exact abs_pos.mpr (sub_ne_zero_of_ne H), -- details about that line are not important
  cases hl ( |l-l'|/4 ) (by linarith) with N hN,
  cases hl' ( |l-l'|/4 ) (by linarith) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  have clef : |l-l'| < |l-l'|,
    calc
    |l - l'| = |(l-u N₀) + (u N₀ -l')|   : by ring_nf
         ... ≤ |l - u N₀| + |u N₀ - l'|  : by apply abs_add
         ... = |u N₀ - l| + |u N₀ - l'|  : by rw abs_sub_comm
         ... < |l-l'|                    : by linarith,
  linarith, -- linarith can also find simple numerical contradictions
end

/-
Another incarnation of the excluded middle axiom is the principle of
contraposition: in order to prove P ⇒ Q, it suffices to prove
non Q ⇒ non P.
-/

-- Using a proof by contradiction, let's prove the contraposition principle
-- 0047
example (P Q : Prop) (h : ¬ Q → ¬ P) : P → Q :=
begin
  by_contradiction hyp,
  push_neg at hyp,
  cases hyp with hp hq, exact h hq hp,
end

/-
Again Lean doesn't need this principle explained to it. We can use the
`contrapose` tactic.
-/

example (P Q : Prop) (h : ¬ Q → ¬ P) : P → Q :=
begin
  contrapose,
  exact h,
end

/-
In the next exercise, we'll use
 odd n : ∃ k, n = 2*k + 1
 int.odd_iff_not_even {n : ℤ} : odd n ↔ ¬ even n
-/
-- 0048
example (n : ℤ) : even (n^2) ↔ even n :=
begin
  split, contrapose, repeat {rw ← int.odd_iff_not_even},
  intro oddn, cases oddn with k hk, use (2*k^2 + 2*k), rw hk, ring,
  intro h, cases h with k hk, use (2*k^2), rw hk, ring,
end
/-
As a last step on our law of the excluded middle tour, let's notice that, especially
in pure logic exercises, it can sometimes be useful to use the
excluded middle axiom in its original form:
  classical.em : ∀ P, P ∨ ¬ P

Instead of applying this lemma and then using the `cases` tactic, we
have the shortcut
 by_cases h : P,

combining both steps to create two proof branches: one assuming
h : P, and the other assuming h : ¬ P

For instance, let's prove a reformulation of this implication relation,
which is sometimes used as a definition in other logical foundations,
especially those based on truth tables (hence very strongly using
excluded middle from the very beginning).
-/

variables (P Q : Prop)

example : (P → Q) ↔ (¬ P ∨ Q) :=
begin
  split,
  { intro h,
    by_cases hP : P,
    { right,
      exact h hP },
    { left,
      exact hP } },
  { intros h hP,
    cases h with hnP hQ,
    { exfalso,
      exact hnP hP },
    { exact hQ } },
end

-- 0049
example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  split,
  {intro h, push_neg at h, by_cases hP:P,
    right, exact h hP, left, exact hP,},
  {intro h, push_neg, intro p, cases h with hp hq, exfalso, exact hp p, exact hq,},
end

/-
It is crucial to understand negation of quantifiers.
Let's do it by hand for a little while.
In the first exercise, only the definition of negation is needed.
-/

-- 0050
example (n : ℤ) : ¬ (∃ k, n = 2*k) ↔ ∀ k, n ≠ 2*k :=
begin
  split, intro h, intro k, intro hyp,
  exact h ⟨k,hyp⟩,
  intro h, intro hyp, cases hyp with k hk, exact h k hk,
end

/-
Contrary to negation of the existential quantifier, negation of the
universal quantifier requires excluded middle for the first implication.
In order to prove this, we can use either
* a double proof by contradiction
* a contraposition, not_not : (¬ ¬ P) ↔ P) and a proof by contradiction.
-/

def even_fun (f : ℝ → ℝ) := ∀ x, f (-x) = f x

-- 0051
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split, contrapose, intro h, rw not_not, intro x, by_contradiction hyp, exact h ⟨x,hyp⟩,
  contrapose, rw not_not, intros h hyp, cases hyp with x hx, exact hx (h x),
end

/-
Of course we can't keep repeating the above proofs, especially the second one.
So we use the `push_neg` tactic.
-/

example : ¬ even_fun (λ x, 2*x) :=
begin
  unfold even_fun, -- Here unfolding is important because push_neg won't do it.
  push_neg,
  use 42,
  linarith,
end

-- 0052
example (f : ℝ → ℝ) : ¬ even_fun f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split, intro h, unfold even_fun at h, push_neg at h, exact h,
  intro h, unfold even_fun, push_neg, exact h,
end

def bounded_above (f : ℝ → ℝ) := ∃ M, ∀ x, f x ≤ M

example : ¬ bounded_above (λ x, x) :=
begin
  unfold bounded_above,
  push_neg,
  intro M,
  use M + 1,
  linarith,
end

-- Let's contrapose
-- 0053
example (x : ℝ) : (∀ ε > 0, x ≤ ε) → x ≤ 0 :=
begin
  contrapose, intro h, push_neg at h, push_neg, use (x/2), split ; linarith,
end

/-
The "contrapose, push_neg" combo is so common that we can abreviate it to
`contrapose!`

Let's use this trick, together with:
  eq_or_lt_of_le : a ≤ b → a = b ∨ a < b
-/

-- 0054
example (f : ℝ → ℝ) : (∀ x y, x < y → f x < f y) ↔ (∀ x y, (x ≤ y ↔ f x ≤ f y)) :=
begin
  split, intro h, intros x y, split, intro j, have k := eq_or_lt_of_le j,
  cases k with keq kl, rw keq, have k := h x y kl, linarith, contrapose!,
  exact h y x, intros h x y hyp, by_contradiction k, push_neg at k,
  have j := (h x y).1 (by linarith), have e : f x = f y, linarith,
  clear k j, specialize h y x, have j := h.2, have  k : f y ≤ f x, linarith, have l := j k,
  linarith,
end


import data.nat.basic
import data.nat.modeq
import data.nat.gcd
import data.zmod.basic
import tactic
import algebra.euclidean_domain
import data.int.basic
import data.equiv.ring
import lemmas2
noncomputable theory

open nat nat.modeq zmod euclidean_domain lemmas2
namespace CRT2

/- CHINESE REMAINDER THEOREM WITH 2 CONGRUENCE RELATIONS -/

/--
Given two coprime moduli M1 and M2 (nonzero) and natural numbers a1 and a1,
there is a natural number x such that x ≡ a1 [MOD M1]  and  x ≡ a2 [MOD M2].
-/
theorem CRTwith2exist (a1 a2 M1 M2 : ℕ ) (M1pos : 0 < M1) (M2pos : 0 < M2) (H : coprime M1 M2) :
                         ∃ x : ℕ , modeq M1 x a1 ∧ modeq M2 x a2 :=
begin
    -- get modulo inveres from lemma above
    cases nat_inv M1 M2 M1pos M2pos H with b1 Hb1,
    cases nat_inv M2 M1 M2pos M1pos (coprime.symm H) with b2 Hb2,
    --solution x = a1 b1 m2 + a2 b1 m2
    use (a1*b1*M2 + a2*b2*M1),
    split,
    --show this is a solution mod M1
        { rw ← add_zero a1, --conv {to_rhs, skip, rw ← add_zero a1}, --applies to just what we see as RHS
        apply modeq_add,

        simp only [add_zero],
        rw ← mul_one a1,
        rw mul_assoc,
        apply modeq_mul,
        simp only [mul_one],
        exact Hb1,

        rw modeq_iff_dvd,
        simp only [dvd_mul_left, zero_sub, int.coe_nat_zero, dvd_neg, int.coe_nat_mul], },

      --show this is a solution mod M2
        { rw ← zero_add a2,
        apply modeq_add,

        rw modeq_iff_dvd,
        simp only [dvd_mul_left, zero_sub, int.coe_nat_zero, dvd_neg, int.coe_nat_mul],

        simp only [zero_add],
        rw ← mul_one a2,
        rw mul_assoc,
        apply modeq_mul,
        simp only [mul_one],
        exact Hb2, }
end

/--
Given two solutions to a pair of congruence relations modulo M1 and M2,
the two solutions will be congruent modulo M1*M2
-/
theorem CRTwith2unique (x1 x2 a1 a2 M1 M2 : ℕ)  (M1pos : 0 < M1) (M2pos : 0 < M2)
            (H : coprime M1 M2) (H1 : modeq M1 x1 a1 ∧ modeq M2 x1 a2)
            (H2 : modeq M1 x2 a1 ∧ modeq M2 x2 a2) : modeq (M1 * M2) x1 x2 :=
begin
    --cosntruct separate modular equations x1 = x2 mod Mi
    have H3 := modeq.trans H1.left (modeq.symm H2.left),
    have H4 := modeq.trans H1.right (modeq.symm H2.right),
    -- combine modular equations using coprime
    rw ← modeq_and_modeq_iff_modeq_mul,
    exact ⟨H3, H4⟩,
    exact H,
end

end CRT2


/-  Math40001 : Introduction to university mathematics.

Problem Sheet 1, October 2019.

This is a Lean file. It can be read with the Lean theorem prover.

You can work on this file online at
https://tinyurl.com/Lean-M40001-Sheet1

or you can install Lean and its maths library following the
instructions at
https://github.com/leanprover-community/mathlib

There are advantages to installing Lean on your own computer
(for example it's faster), but it's a lot more hassle than
just using it online.

In the below, delete "sorry" and replace it with some
tactics which prove the result.

-/

/- Question 1.

Let P and Q be Propositions (that is, true/false statements).
Prove that P ∧ Q → Q ∧ P.

-/

lemma question_one (P Q : Prop) : P ∧ Q → Q ∧ P :=
begin
  sorry
end
/-

For question 2, comment out one option (or just delete it)
and prove the other one.
-/

-- Part (a): is → symmetric?

lemma question_2a_true : ∀ P Q : Prop, (P → Q) → (Q → P) :=
begin
  sorry
end

lemma question_2a_false : ¬ (∀ P Q : Prop, (P → Q) → (Q → P)) :=
begin
  sorry
end

-- Part (b) : is ↔ symmetric?

lemma question_2b_true (P Q : Prop) : (P ↔ Q) → (Q ↔ P) :=
begin
  sorry
end

lemma question_2b_false : ¬ (∀ P Q : Prop, (P ↔ Q) → (Q ↔ P)) :=
begin
  sorry
end

/- Question 3.

Say P, Q and R are propositions, and we know:
1) if Q is true then P is true
2) If Q is false then R is false.

Can we deduce that R implies P? Comment out one
option and prove the other. Hint: if you're stuck,
"apply classical.by_contradiction" sometimes helps.
-/

lemma question_3_true (P Q R : Prop)
  (h1 : Q → P)
  (h2 : ¬ Q → ¬ R) :
R → P :=
begin
  sorry
end

lemma question_3_false : ¬ (∀ P Q R : Prop,
  (Q → P) →
  (¬ Q → ¬ R) →
  R → P) :=
begin
  sorry
end

/- Question 4.

   Your friend is thinking of three true-false statements P, Q and R,
   and they tell you the following facts:
   a) P → (Q ∧ R)
   b) Q → (R ∧ ¬ P)
   c) R → (P ∧ ¬ Q)

   What can you deduce?

In this question you must *change the conclusion* to explain
   what you deduced.
-/

lemma question_4 (P Q R : Prop)
  (h1 : P → (Q ∧ R))
  (h2 : Q → (R ∧ ¬ P))
  (h3 : R → (P ∧ ¬ Q)) :
P ∨ Q  -- replace this line with your conclusion
:=
begin
  sorry
end

/- Question 5.

  Say that for every integer n we have a proposition P n.
  Say we know P n → P (n + 8) for all n, and
  P n → P (n -3) for all n. Prove that the P n are either
  all true, or all false.

This question is harder than the others.
-/
lemma question_5 (P : ℤ → Prop) (h8 : ∀ n, P n → P (n + 8)) (h3 : ∀ n, P n → P (n - 3)) :
(∀ n, P n) ∨ (∀ n, ¬ (P n)) :=
begin
  sorry
end

/-
The first four of these questions can be solved using only the following
tactics:

intro
apply (or, better, refine)
left, right, cases, split
assumption (or, better, exact)
have,
simp
contradiction (or, better, false.elim)

The fifth question is harder.
-/


import tactic

-- 1.2. Implications in Lean

/--------------------------------------------------------------------------

``exact``

  If ``P`` is the target of the current goal and
  ``hp`` is a term of type ``P``, then
  ``exact hp,`` will close the goal.

``intro``

  If the target of the current goal is a function ``P → Q``, then
  ``intro hp,`` will produce a hypothesis
  ``hp : P`` and change the target to  ``Q``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

--------------------------------------------------------------------------/

theorem tautology (P : Prop) (hp : P) : P :=
begin
  exact hp,
end

theorem tautology' (P : Prop) : P → P :=
begin
  intro hp,
  exact hp,
end

example (P Q : Prop): (P → (Q → P)) :=
begin
  intro hP,
  intro hQ,
  exact hP,
end

-- Can you find two different ways of proving the following?

-- First way
example (P Q : Prop) : ((Q → P) → (Q → P)) :=
begin
  intro hQP,
  exact hQP,
end

-- Second way - using the tautology' proof above
example (P Q : Prop) : ((Q → P) → (Q → P)) :=
begin
  exact tautology' _,
end

/--------------------------------------------------------------------------

``have``

  If ``f`` is a term of type ``P → Q`` and
  ``hp`` is a term of type ``P``, then
  ``have hq := f(hp),`` creates the hypothesis ``hq : Q`` .

``apply``

  If the target of the current goal is ``Q`` and
  ``f`` is a term of type ``P → Q``, then
  ``apply f,`` changes target to ``P``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

--------------------------------------------------------------------------/

example (P Q R : Prop) (hp : P) (f : P → Q) (g : Q → R) : R :=
begin
  -- apply is reasoning backward
  apply g,
  apply f,
  exact hp,
end

example (P Q R : Prop) (hp : P) (f : P → Q) (g : Q → R) : R :=
begin
  -- have is reasoning forward
  have hq := f(hp),
  have hr := g(hq),
  exact hr,
end

example (P Q R S T U: Type)
  (hpq : P → Q)
  (hqr : Q → R)
  (hqt : Q → T)
  (hst : S → T)
  (htu : T → U)
  : P → U :=
begin
  intro hp,
  apply htu,
  apply hqt,
  apply hpq,
  exact hp,
end

example (P Q R S T U: Type)
  (hpq : P → Q)
  (hqr : Q → R)
  (hqt : Q → T)
  (hst : S → T)
  (htu : T → U)
  : P → U :=
begin
  intro hp,
  have hQ := hpq(hp),
  have hT := hqt(hQ),
  have hU := htu(hT),
  exact hU,
end

/--------------------------------------------------------------------------

Recall that
  ``¬ P`` is ``P → false``,
  ``¬ (¬ P)`` is ``(P → false) → false``, and so on.

Delete the ``sorry,`` below and replace them with a legitimate proof.

--------------------------------------------------------------------------/

-- Hint
-- If two of your hypotheses are hp : Q and hnp : ¬ Q, then the term
-- hnp (hp)
-- has the type false.

theorem self_imp_not_not_self (P : Prop) : P → ¬ (¬ P) :=
begin
  intro hP,
  intro hnP,
  have x := hnP(hP),
  exact x,
end

theorem contrapositive (P Q : Prop) : (P → Q) → (¬Q → ¬P) :=
begin
  intro hPQ,
  intro hnQ,
  intro hP,
  have hQ := hPQ(hP),
  have x := hnQ(hQ),
  exact x,
end

-- Hint
-- If your target is false, and one of your assumptions is hnq : ¬ Q then try
-- apply hnq,

example (P : Prop) : ¬ (¬ (¬ P)) → ¬ P :=
begin
  intro hnnnP,
  intro hP,
  apply hnnnP,
  intro hnP,
  apply hnP,
  exact hP,
end

-- 1.3. Proof by contradiction

/--------------------------------------------------------------------------

You can prove exactly one of the following three using just
``exact``, ``intro``, ``have``, and ``apply``.

Can you find which one?

--------------------------------------------------------------------------/

theorem not_not_self_imp_self (P : Prop) : ¬ ¬ P → P:=
begin
  sorry,
end

theorem contrapositive_converse (P Q : Prop) : (¬Q → ¬P) → (P → Q) :=
begin
  sorry,
end

example (P : Prop) : ¬ P → ¬ ¬ ¬ P :=
begin
  intro hP,
  intro hnnP,
  apply hnnP,
  exact hP,
end

/--
  This is because it is not true that ¬ ¬ P = P by definition, after all,
     ¬ ¬ P is (P → false) → false
  which is drastically different from P.
  There is an extra axiom called the law of excluded middle which says that either P is inhabited
  or ¬ P is inhabited (and there is no middle option) and so P ↔ ¬ ¬ P.
  This is the axiom that allows for proofs by contradiction.
  Lean provides us the following tactics to use it.
--/

/--------------------------------------------------------------------------

``exfalso``

  Changes the target of the current goal to ``false``.

``push_neg``

  ``push_neg,`` simplifies negations in the target.
  You can push negations across a hypothesis ``hp : P`` using
  ``push_neg at hp,``.

``contrapose!``

  If the target of the current goal is  ``P → Q``,
  then ``contrapose!,`` changes the target to  ``¬ Q → ¬ P``.

  If the target of the current goal is ``Q`` and
  one of the hypotheses is ``hp : P``, then
  ``contrapose! hp,`` changes the target to  ``¬ P`` and
  changes the hypothesis to ``hp : ¬ Q``.

Delete the ``sorry,`` below and replace them with a legitimate proof.

--------------------------------------------------------------------------/

theorem not_not_self_imp_self' (P : Prop) : ¬ ¬ P → P:=
begin
  push_neg,
  intro hP,
  exact hP,
end

theorem contrapositive_converse' (P Q : Prop) : (¬Q → ¬P) → (P → Q) :=
begin
  intro hnQnP,
  -- goal is now P → Q, so use contrapose! following hints above
  contrapose,
  exact hnQnP, -- and we are left with hnQnP
end

example (P : Prop) : ¬ P → ¬ ¬ ¬ P :=
begin
  push_neg,
  exact tautology' _, -- goal was ¬ P → ¬ P, so use earlier proof of P → P.
end

theorem principle_of_explosion (P Q : Prop) : P → (¬ P → Q) :=
begin
  intro hP,   -- gives hP : P
  intro hnP,  -- gives hnP : ¬ P
  exfalso,    -- changes goal of Q to false
  apply hnP,  -- recall ¬ P is P → false and so this makes goal P
  exact hP,
end


-- Math 52: Quiz 5
-- Open this file in a folder that contains 'utils'.

import utils

definition divides (a b : ℤ) : Prop := ∃ (k : ℤ), b = a * k
local infix ∣ := divides

axiom not_3_divides : ∀ (m : ℤ), ¬ (3 ∣ m) ↔ 3 ∣ m - 1 ∨ 3 ∣ m + 1

theorem main : ∀ (n : ℤ), ¬ (3 ∣ n) → 3 ∣ n * n - 1 :=
begin
intro n,
rw not_3_divides,
intro H,
cases H,

    {unfold divides at H ⊢,
    cases H with a H,
    existsi(a*(n+1):ℤ ),
    rw int.distrib_left,
    rw int.distrib_left,
    rw mul_one,
    symmetry,
    calc 3 * (a * n) + 3 * a
    = 3*a*n + 3*a :by int_refl[a,n]...
    = 3*a*(n+1): by int_refl[a,n]...
    = (n-1)*(n+1): by rw H...
    = n*n - 1: by int_refl[n],
    },

    {unfold divides at H ⊢,
    cases H with a H,
    existsi(a*(n-1):ℤ ),
    --it didn't like it when I copied and pasted lines 21-
    --23 here, so I'm trying something different
    symmetry,
    calc 3 * (a * (n-1))
    = 3*a* (n - 1) :by int_refl[a,n]...
    = (n+1) * (n-1) :by rw H ...
    = n*n - 1 : by int_refl[n],
    },

end

--Proof: Let n be an artibrary integer. By the lemma,
--3 does not divide n is logically equivalent to the
--statement that either 3 divides n-1 or 3 divides n+1.
--Assume that either 3 divides n-1 or 3 divides n+1. The
--goal is now to show that 3 divides n*n - 1. We consider
--two cases:

--First, the case that 3 divides n-1. By the definition
--of divides, there exists some integer a for which
--n-1=3*a, and we want to show that there exists some
--integer k such that n*n-1 = 3k. Consider k = a(n+1).
--By closure under subtraction and multiplication, k
--must be an integer. Also, 3*k=3*a*(n+1)=(n-1)(n+1)
--  =n*n-1.

--Second, the case that 3 divides n+1. By the definition
--of divides, there exists some integer a for which
--n+1=3*a, and we want to show that there exists some
--integer k such that n*n-1 = 3k. Consider k = a(n-1).
--By closure under subtraction and multiplication, k
--must be an integer. Also, 3*k=3*a*(n-1)=(n-1)(n+1)
--  =n*n-1.

--Since both cases lead us to the conclusion that 3
--divides n*n-1, it is proven that, if 3 does not
--divide n, then 3 divides n*n-1. □


import data.real.basic

section

-- Notaciones.
variables  (u : ℕ → ℝ)
variables  (f : ℝ → ℝ)
variables  (x₀ l : ℝ)

-- ----------------------------------------------------
-- Ejercicio. Definir la notación |x| para el valor
-- absoluto de x.
-- ----------------------------------------------------

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

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    limite : (ℕ → ℝ) → ℝ → Prop
-- tal que (limite u c) expresa que c es el límite de
-- la sucesión u.
-- ----------------------------------------------------

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

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ¬ limite u l ↔ ∃ ε > 0, ∀ N, ∃ n ≥ N, |u n - l| > ε
-- ----------------------------------------------------------------------

example :
  ¬ limite u l ↔ ∃ ε > 0, ∀ N, ∃ n ≥ N, |u n - l| > ε :=
begin
  unfold limite,
  push_neg,
  simp,
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    continua : (ℝ → ℝ) → ℝ → Prop
-- tal que (continua f x₀) se verifica si f es continua
-- en x₀.
-- ----------------------------------------------------

def continua : (ℝ → ℝ) → ℝ → Prop :=
λ f x₀, ∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ →  |f x - f x₀| ≤ ε

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--   ¬ (continua f x₀) ↔
--   ∃ ε > 0, ∀ δ > 0, ∃ x, |x - x₀| ≤ δ ∧ |f x - f x₀| > ε
-- ----------------------------------------------------------------------

example :
  ¬ (continua f x₀) ↔
  ∃ ε > 0, ∀ δ > 0, ∃ x, |x - x₀| ≤ δ ∧ |f x - f x₀| > ε :=
begin
  unfold continua,
  push_neg,
  simp,
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    uniformemente_continua : (ℝ → ℝ) → Prop
-- tal que (uniformemente_continua f) se verifica si f
-- es uniformemente continua.
-- ----------------------------------------------------

def uniformemente_continua : (ℝ → ℝ) → Prop :=
λ f, ∀ ε > 0, ∃ δ > 0, ∀ x x', |x' - x| ≤ δ →  |f x' - f x| ≤ ε

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ¬ (uniformemente_continua f) ↔
--    ∃ ε > 0, ∀ δ > 0, ∃ x x', |x' - x| ≤ δ ∧ |f x' - f x| > ε :=
-- ----------------------------------------------------------------------

example :
  ¬ (uniformemente_continua f) ↔
  ∃ ε > 0, ∀ δ > 0, ∃ x x', |x' - x| ≤ δ ∧ |f x' - f x| > ε :=
begin
  unfold uniformemente_continua,
  push_neg,
  simp,
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    secuencialmente_continua : (ℝ → ℝ) → Prop
-- tal que (secuencialmente_continua f x₀) se verifica
-- si f es secuencialmente continua en x₀.
-- ----------------------------------------------------

def secuencialmente_continua : (ℝ → ℝ) → ℝ → Prop :=
λ f x₀, ∀ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) →
                     (∀ ε > 0, ∃ N, ∀ n ≥ N, |(f ∘ u) n - f x₀| ≤ ε)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ¬ secuencialmente_continua f x₀  ↔
--    ∃ u : ℕ → ℝ,
--      (∀ δ > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ δ) ∧
--      (∃ ε > 0,  ∀ N, ∃ n ≥ N, |f (u n) - f x₀| > ε)
-- ----------------------------------------------------------------------

example :
  ¬ secuencialmente_continua f x₀  ↔
  ∃ u : ℕ → ℝ,
    (∀ δ > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ δ) ∧
    (∃ ε > 0,  ∀ N, ∃ n ≥ N, |f (u n) - f x₀| > ε) :=
begin
  unfold secuencialmente_continua,
  push_neg,
  simp,
end
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
-- ----------------------------------------------------

def tiende_a_infinito : (ℕ → ℝ) → Prop :=
λ u, ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    tiende_a_infinito u → ∀ l, ¬ limite u l
-- ----------------------------------------------------------------------

example
  {u : ℕ → ℝ}
  : tiende_a_infinito u → ∀ l, ¬ limite u l :=
begin
  intros h l hl,
  cases hl 1 (by linarith) with N hN,
  cases h (l+2) with N' hN',
  let N₀ := max N N',
  specialize hN N₀ (le_max_left _ _),
  specialize hN' N₀ (le_max_right _ _),
  rw abs_le at hN,
  linarith,
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    no_decreciente : (ℕ → ℝ) → Prop
-- tal que (no_dreciente u) expresa que la sucesión u
-- es no decreciente.
-- ----------------------------------------------------

def no_decreciente : (ℕ → ℝ) → Prop :=
λ u, ∀ n m, n ≤ m → u n ≤ u m

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si u es una sucesión no decreciente y su
-- límite es l, entonces
--    ∀ n, u n ≤ l
-- ----------------------------------------------------------------------

example
  (u : ℕ → ℝ)
  (l : ℝ)
  (h : limite u l)
  (h' : no_decreciente u)
  : ∀ n, u n ≤ l :=
begin
  intro n,
  by_contradiction H,
  push_neg at H,
  cases h ((u n - l)/2) (by linarith) with N hN,
  specialize hN (max n N) (le_max_right _ _),
  specialize h' n (max n N) (le_max_left _ _),
  rw abs_le at hN,
  linarith,
end

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    cota_superior : set ℝ → ℝ → Prop
-- tal que (cota_superior A x) expresa que x es una
-- cota superior de A.
-- ----------------------------------------------------

def cota_superior : set ℝ → ℝ → Prop :=
λ A x, ∀ a ∈ A, a ≤ x

-- ----------------------------------------------------
-- Ejercicio. Definir la función
--    es_supremo : set ℝ → ℝ → Prop
-- tal que (es_supremos A x) expresa que x es el
-- supremo de A.
-- ----------------------------------------------------

def es_supremo : set ℝ → ℝ → Prop :=
λ A x, cota_superior A x ∧ ∀ y, cota_superior A y → x ≤ y

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si x es el supremo de A, entonces
--    ∀ y, y < x → ∃ a ∈ A, y < a
-- ----------------------------------------------------------------------

example
  {A : set ℝ}
  {x : ℝ}
  (hx : es_supremo A x)
  : ∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intro y,
  contrapose!,
  exact hx.right y,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    (∀ ε > 0, y ≤ x + ε) →  y ≤ x
-- ----------------------------------------------------------------------

lemma le_of_le_add_all'
  {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  contrapose!,
  intro h,
  use (y-x)/2,
  split ; linarith,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si x es el límite de u e y es una cota
-- superior de u, entonces x ≤ y.
-- ----------------------------------------------------------------------

example
  {x y : ℝ}
  {u : ℕ → ℝ}
  (hu : limite u x)
  (h : ∀ n, u n ≤ y)
  : x ≤ y :=
begin
  apply le_of_le_add_all',
  intros ε hε,
  cases hu ε hε with N hN,
  specialize hN N (by linarith),
  rw abs_le at hN,
  linarith [h N],
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
import solutions.section03groups.sheet1 -- imports our definition of `mygroup`

/-!

# Challenge sheet

This is an optional "puzzle" sheet, which won't teach you any
new Lean concepts but will give you some practice in rewriteology,
and will teach you pretty much all there is to know about the
question "which axioms of a group can I safely drop?" If you're
not into puzzles like this, just move on to sheet 3.

It turns out that two of the axioms in our definition of a group
are not needed; they can be deduced from the others. Let's define
a "weak group" class, where we only have three of the group axioms.
The question is: can you prove that a weak group is a group?
The last def, `to_mygroup`, does this, but you need to fill in the
sorrys first. Note that the simplifier is less use to you now; we've
trained it to solve problems about `mygroup`s but it doesn't
know anything about `myweakgroup`.

-/

-- removing `mul_one` and `mul_inv_self`
class myweakgroup (G : Type)
  extends has_one G, has_mul G, has_inv G : Type :=
(mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c))
(one_mul : ∀ a : G, 1 * a = a)
(inv_mul_self : ∀ a : G, a⁻¹ * a = 1)

namespace myweakgroup

variables {G : Type} [myweakgroup G] (a b c : G)

attribute [simp] mul_assoc one_mul inv_mul_self

/-

One way of doing it: try proving

`mul_left_cancel : a * b = a * c → b = c`

and then

`mul_eq_of_eq_inv_mul : b = a⁻¹ * c → a * b = c`

first.

-/

lemma mul_left_cancel (h: a * b = a * c) :
 b=c :=
begin
 calc
      b=1*b:by rw one_mul
...     = a⁻¹*a*b: by rw inv_mul_self
...     = a⁻¹*(a*b):by rw mul_assoc
...     = a⁻¹*(a*c): by rw h
...     =a⁻¹*a*c: by rw mul_assoc
...      =1*c:by rw inv_mul_self
...      =c:by rw one_mul,
end

lemma mul_eq_of_eq_inv_mul (h:b=a⁻¹*c):a*b=c :=
begin
  apply mul_left_cancel a⁻¹,
  rw [<- mul_assoc, inv_mul_self, one_mul, h],
end

lemma mul_one (a : G) : a * 1 = a :=
begin
  apply mul_eq_of_eq_inv_mul,rw inv_mul_self,
end

lemma mul_inv_self (a : G) : a * a⁻¹ = 1 :=
begin
  apply mul_left_cancel a⁻¹,
  rw [<- mul_assoc, inv_mul_self, one_mul, mul_one]
end

def to_mygroup (G : Type) [myweakgroup G] : mygroup G :=
{ mul_assoc := mul_assoc,
  one_mul := one_mul,
  mul_one := mul_one,
  inv_mul_self := inv_mul_self,
  mul_inv_self := mul_inv_self }

end myweakgroup

/-
If you want to take this further: prove that if we make
a new class `my_even_weaker_group` by replacing
`one_mul` by `mul_one` in the definition of `myweakgroup`
then it is no longer true that you can prove `mul_inv_self`;
there are structures which satisfy `mul_assoc`, `mul_one`
and `inv_mul_self` but which really are not groups.
Can you find an example? Try it on paper first.
-/

-- claim: not a group in general
class my_even_weaker_group (G : Type)
  extends has_one G, has_mul G, has_inv G : Type :=
(mul_assoc : ∀ a b c : G, (a * b) * c = a * (b * c))
(mul_one : ∀ a : G, a * 1 = a)
(inv_mul_self : ∀ a : G, a⁻¹ * a = 1)


-- Lean Tactics
-- https://leanprover.github.io/theorem_proving_in_lean4/tactics.html
-- Lean List Proofs
-- https://github.com/leanprover/std4/blob/main/Std/Data/List/Lemmas.lean
-- Coq List Proofs
-- https://github.com/katydid/proofs/blob/old-coq/src/CoqStock/List.v
-- List of Lean Tactics
-- https://github.com/leanprover/lean4/blob/master/src/Init/Tactics.lean

open Nat
open List

theorem nat_succ_le_succ_iff (x y: Nat):
  succ x ≤ succ y <-> x ≤ y := by
  apply Iff.intro
  case mp =>
    apply Nat.le_of_succ_le_succ
  case mpr =>
    apply Nat.succ_le_succ

theorem nat_succ_eq_plus_one : succ n = n + 1 := by
  simp

theorem nat_pred_le_succ : {n m : Nat} -> Nat.le n (succ m) -> Nat.le (pred n) m
  | zero, zero, _ => Nat.le.refl
  | _, _, Nat.le.refl => Nat.le.refl
  | zero, succ _, Nat.le.step h => h
  | succ _, succ _, Nat.le.step h => Nat.le_trans (le_succ _) h

theorem nat_pred_le_succ' : {n m : Nat} -> Nat.le n (succ m) -> Nat.le (pred n) m := by
  intro n m h
  cases h with
  | refl =>
    constructor
  | step h =>
    cases n with
    | zero =>
      dsimp
      exact h
    | succ n =>
      dsimp
      have h_n_le_succ_n := Nat.le_succ n
      exact (Nat.le_trans h_n_le_succ_n h)

theorem nat_min_zero {n: Nat}: min 0 n = 0 := by
  unfold min
  unfold instMinNat
  unfold minOfLe
  simp

theorem nat_zero_min {n: Nat}: min n 0 = 0 := by
  cases n with
  | zero =>
    simp
  | succ n =>
    unfold min
    unfold instMinNat
    unfold minOfLe
    simp

theorem nat_add_succ_is_succ_add (n m: Nat): succ n + m = succ (n + m) := by
  cases n with
  | zero =>
    rewrite [Nat.add_comm]
    simp
  | succ n =>
    rewrite [Nat.add_comm]
    rewrite [Nat.add_comm (succ n)]
    repeat rewrite [Nat.add_succ]
    rfl

theorem nat_pred_le_pred : {n m : Nat} → LE.le n m → LE.le (pred n) (pred m) := by
  intro n m h
  cases h with
  | refl => constructor
  | step h =>
    rename_i m
    cases n with
    | zero =>
      dsimp
      exact h
    | succ n =>
      dsimp
      have h_n_le_succ_n := Nat.le_succ n
      exact (Nat.le_trans h_n_le_succ_n h)

theorem list_cons_ne_nil (x : α) (xs : List α):
  x :: xs ≠ [] := by
  intro h'
  contradiction

theorem list_cons_nil_ne (x : α) (xs : List α):
  [] ≠ x :: xs := by
  intro h'
  contradiction

theorem list_append_ne_nil (x : α) (xs : List α):
  [x] ++ xs ≠ [] := by
  intro h'
  contradiction

theorem list_append_nil_ne (x : α) (xs : List α):
  xs ++ [x] ≠ [] := by
  intro h'
  cases xs with
  | nil =>
    contradiction
  | cons head tail =>
    contradiction

theorem list_length_zero_is_empty (xs: List α):
  length xs = 0 -> xs = [] := by
  cases xs
  · intro _
    rfl
  · intro h'
    simp [length] at h'
    contradiction

theorem list_app_nil_l (xs: List α):
  [] ++ xs = xs := by
  rfl

theorem list_app_nil_r (xs: List α):
  xs ++ [] = xs := by
  induction xs with
  | nil =>
    rfl
  | cons head tail ih =>
    apply (congrArg (cons head))
    assumption

theorem list_app_assoc (xs ys zs: List α):
  xs ++ (ys ++ zs) = (xs ++ ys) ++ zs := by
  induction xs with
  | nil => rfl
  | cons head tail ih =>
    apply (congrArg (cons head))
    exact ih

theorem list_app_assoc_reverse (xs ys zs: List α):
  (xs ++ ys) ++ zs = xs ++ (ys ++ zs) := by
  apply Eq.symm -- same as symmetry tactic in Coq and Lean3
  apply list_app_assoc

theorem list_app_comm_cons (x: α) (xs ys: List α):
  x :: (xs ++ ys) = (x :: xs) ++ ys := by
  apply (congrArg (cons x))
  rfl

theorem list_app_cons_not_nil (x: α) (xs ys: List α):
  [] ≠ xs ++ (y :: ys) := by
  cases xs <;> { intro h' ; contradiction }

theorem list_app_nil_nil (xs ys: List α):
  xs ++ ys = [] <-> xs = [] /\ ys = [] := by
  apply Iff.intro
  case mp =>
    cases xs with
    | nil =>
      simp
      intro h'
      assumption
    | cons head tail =>
      intro h'
      contradiction
  case mpr =>
    intro h'
    cases h' with
    | intro h1 h2 =>
      rw [h1, h2]
      rfl

theorem list_app_eq_unit (a: α) (xs ys: List α):
  xs ++ ys = [a] -> (xs = [] /\ ys = [a]) \/ (xs = [a] /\ ys = []) := by
  cases xs with
  | nil =>
    intro hy
    simp at hy
    apply Or.intro_left
    apply And.intro
    case left => rfl
    case right => assumption
  | cons head tail =>
    intro hy
    simp at hy
    apply Or.intro_right
    cases hy with
    | intro h1 h2 =>
      rw [h1]
      have h3: tail = [] /\ ys = [] := (Iff.mp (list_app_nil_nil tail ys)) h2
      cases h3 with
      | intro h4 h5 =>
        rw [h4, h5]
        apply And.intro <;> rfl

theorem list_app_inj_tail (xs ys: List α) (x y: α):
  xs ++ [x] = ys ++ [y] -> xs = ys /\ x = y := by
  revert ys -- same as generalize dependent ys in Coq
  induction xs with
  | nil =>
    intro ys h'
    simp at h'
    have h3: (ys = [] /\ [y] = [x]) \/ (ys = [x] /\ [y] = []) := (list_app_eq_unit x ys [y]) (Eq.symm h')
    cases h3 with -- Or
    | inl left =>
      cases left with
      | intro empty xy =>
        simp at xy
        rw [empty, xy]
        apply And.intro <;> rfl
    | inr right =>
      cases right with
      | intro empty ysx =>
        contradiction
  | cons headx tailx ihx =>
    intro ys
    cases ys with
    | nil =>
      intro h'
      simp at h'
      cases h' with
      | intro _ hfalse =>
        simp [List.append] at hfalse
        have h: tailx ++ [x] ≠ [] := list_append_nil_ne x tailx
        contradiction
    | cons heady taily =>
      intro h'
      simp at h'
      cases h' with
      | intro heads tails =>
        rw [heads]
        have h: tailx = taily ∧ x = y := (ihx taily tails)
        cases h with
        | intro tailxy xy =>
          rw [tailxy]
          rw [xy]
          apply And.intro <;> rfl

theorem list_app_nil_end (xs: List α):
  xs = xs ++ [] := by
  cases xs with
  | nil => rfl
  | cons head tail => simp [List.append]

theorem list_app_length (xs ys: List α):
  length (xs ++ ys) = length xs + length ys := by
  induction xs with
  | nil => simp
  | cons _ tailx ih => simp [ih, Nat.succ_add]

theorem list_last_length (xs: List α):
  length (xs ++ x :: nil) = (length xs) + 1 := by
  induction xs with
  | nil => rfl
  | cons _ tail ih => simp [ih]

theorem list_cons_eq (x y: α) (xs ys: List α):
  x :: xs = y :: ys <-> x = y /\ xs = ys := by
  apply Iff.intro
  · intro h
    apply And.intro
    · injections
    · injections
  · intro h
    congr
    · exact h.left
    · exact h.right

theorem list_length_zero_or_smaller_is_empty (xs: List α):
  length xs <= 0 -> xs = [] := by
  cases xs with
  | nil => intro; rfl
  | cons head tail => intro; contradiction

theorem list_take_nil {α: Type u} (n: Nat):
  take n ([] : List α) = [] := by
  cases n with
  | zero => rfl
  | succ => rfl

theorem list_take_cons (n: Nat) (x: α) (xs: List α):
  take (n + 1) (x::xs) = x :: (take n xs) := by
  rw [take]

theorem list_take_all (xs: List α):
  take (length xs) xs = xs := by
  induction xs with
  | nil => rfl
  | cons head tail ih =>
    have h : take (length (head::tail)) (head::tail) =
        head :: (take (length tail) tail) := by
      apply list_take_cons
    rw [ih] at h
    trivial

theorem list_take_zero (xs : List α) :
  take zero xs = [] := by rw [take]

theorem list_rev_empty (xs : List α) :
  reverse xs = [] -> xs = [] := by
  cases xs with
  | nil => simp
  | cons head tail =>
    intro h
    have h' : (reverse (reverse (head :: tail)) = []) := congrArg reverse h
    simp at h'

theorem list_rev_empty2 :
  reverse ([] : List α) = [] := by trivial

theorem list_rev_eq (n : Nat) (xs ys : List α) :
  reverse xs = reverse ys -> xs = ys := by
  intro h
  have h' : reverse (reverse xs) = reverse (reverse ys) :=
    congrArg reverse h
  simp at h'
  assumption

theorem take_one_nil : take 1 ([] : List α) = [] := by
  rw [take]

private theorem list_length_cons_le_succ (head : α) (tail : List α) (k : Nat):
  length (head :: tail) ≤ succ k -> length tail ≤ k := by
  rw [length]
  apply Nat.le_of_succ_le_succ

theorem list_take_all2 (n: Nat) (xs: List α):
  (length xs) <= n -> take n xs = xs := by
  revert xs
  induction n with
  | zero =>
    intro xs h
    rw [list_length_zero_or_smaller_is_empty xs h]
    rw [take]
  | succ k ih =>
    intro xs
    cases xs with
    | nil =>
      intro _
      apply list_take_nil
    | cons head tail =>
      intro h
      rw [take]
      apply (congrArg (cons head))
      apply ih tail (list_length_cons_le_succ head tail k h)

theorem list_take_O (xs: List α):
  take 0 xs = [] := by
  rw [take]

theorem list_take_le_length (n: Nat) (xs: List α):
  length (take n xs) <= n := by
  revert n
  induction xs with
  | nil =>
    intro n
    rw [list_take_nil]
    simp
  | cons head tail ih =>
    intro n
    cases n with
    | zero  =>
      rw [take]
      simp
    | succ k =>
      rw [take]
      rw [length]
      apply Nat.succ_le_succ
      apply (ih k)

theorem list_length_succ_le_cons  (head : α) (tail : List α) (k : Nat):
  k ≤ length tail -> succ k ≤ length (head :: tail) := by
  rw [length, <-nat_succ_eq_plus_one]
  apply Nat.succ_le_succ

theorem list_length_pred_le_cons (head : α) (tail : List α) (k : Nat):
  k ≤ length (head :: tail) -> pred k ≤ length tail := by
  rw [length, <-nat_succ_eq_plus_one]
  apply nat_pred_le_succ

theorem list_length_cons_succ : length (x :: xs) = succ (length xs) := by simp

theorem list_take_length_le (n: Nat) (xs: List α):
  n <= length xs -> length (take n xs) = n := by
  revert n
  induction xs with
  | nil =>
    intro n
    rw [list_take_nil]
    rw [length]
    cases n with
    | zero =>
      intro _
      rfl
    | succ n' =>
      intro h
      contradiction
  | cons head tail ih =>
    intro n
    intro h
    have h' : _ := ih (pred n)
    have h₂ : _ := h' ((list_length_pred_le_cons head tail n) h)
    cases n with
    | zero => rw [list_take_zero, length]
    | succ n' =>
      simp at h₂
      rw [list_take_cons, list_length_cons_succ]
      have h₃ : _ := congrArg succ h₂
      exact h₃

theorem list_take_app (n: Nat) (xs ys: List α):
  take n (xs ++ ys) = (take n xs) ++ (take (n - length xs) ys) := by
  revert xs ys
  induction n with
  | zero =>
    intro xs ys
    rw [Nat.zero_sub]
    repeat rw [take]
    simp
  | succ n ih =>
    intro xs ys
    cases xs with
    | nil =>
      repeat rw [take]
      simp
    | cons x xs =>
      rw [take]
      rw [length]
      rw [succ_sub_succ]
      rw [<- list_app_comm_cons]
      rw [take]
      apply (congrArg (cons x))
      apply ih

theorem list_take_app_2 (n: Nat) (xs ys: List α):
  take ((length xs) + n) (xs ++ ys) = xs ++ take n ys := by
  induction xs with
  | nil =>
    simp
  | cons x xs ih =>
    rw [<- list_app_comm_cons]
    rw [length]
    have hcomm: length xs + 1 + n = succ (length xs + n) := by
      rw [Nat.add_comm (length xs) 1]
      rw [Nat.add_assoc]
      rw [Nat.add_comm]
      rfl
    rw [hcomm]
    rw [take]
    apply (congrArg (cons x))
    apply ih

theorem list_take_take (n n: Nat) (xs: List α):
  take n (take m xs) = take (min n m) xs := by
  revert m xs
  induction n with
  | zero =>
    intro m xs
    rw [nat_min_zero]
    repeat rw [take]
  | succ n ihn =>
    intro m xs
    cases m with
    | zero =>
      unfold min
      unfold instMinNat
      unfold minOfLe
      rw [take]
      simp
      rw [take]
      apply list_take_nil
    | succ m =>
      unfold min
      unfold instMinNat
      unfold minOfLe
      simp
      split
      case succ.succ.inl h =>
        rw [nat_succ_le_succ_iff] at h
        cases xs with
        | nil =>
          rw [take]
        | cons x xs =>
          repeat rw [take]
          apply (congrArg (cons x))
          have hmin : min n m = n := by
            unfold min
            unfold instMinNat
            unfold minOfLe
            simp
            split
            · rfl
            · contradiction
          have ihn' : _ := @ihn m xs
          rw [hmin] at ihn'
          exact ihn'
      case succ.succ.inr h =>
        rw [nat_succ_le_succ_iff] at h
        cases xs with
        | nil =>
          rw [take]
          apply list_take_nil
        | cons x xs =>
          repeat rw [take]
          apply (congrArg (cons x))
          have hmin : min n m = m := by
            unfold min
            unfold instMinNat
            unfold minOfLe
            simp
            split
            · contradiction
            · rfl
          have ihn' : _ := @ihn m xs
          rw [hmin] at ihn'
          exact ihn'

theorem list_drop_O (xs: List α):
  drop 0 xs = xs := by
  rw [drop]

theorem list_drop_zero (xs: List α):
  drop zero xs = xs := by
  rw [drop]

theorem list_drop_nil {α: Type u} (n: Nat):
  drop n ([] : List α) = [] := by
  cases n with
  | zero => rw [drop]
  | succ n' => rw [drop]

theorem list_take_drop_comm (n m: Nat) (xs: List α):
  take m (drop n xs) = drop n (take (n + m) xs) := by
  revert m xs
  induction n with
  | zero =>
    intro m xs
    rw [Nat.zero_add]
    repeat rw [list_drop_zero]
  | succ n ih =>
    intro m xs
    cases xs with
    | nil =>
      rw [list_drop_nil, list_take_nil, list_take_nil, list_drop_nil]
    | cons x xs =>
      rw [drop]
      rw [nat_add_succ_is_succ_add]
      rw [take]
      rw [drop]
      apply ih

theorem list_drop_take_comm (n m: Nat) (xs: List α):
  drop m (take n xs) = take (n - m) (drop m xs) := by
  revert m xs
  induction n with
  | zero =>
    intro m xs
    rw [Nat.zero_sub]
    repeat rw [list_take_zero]
    rw [list_drop_nil]
  | succ n ih =>
    intro m xs
    cases m with
    | zero =>
      rw [Nat.sub_zero]
      repeat rw [list_drop_zero]
    | succ m =>
      cases xs with
      | nil =>
        rw [list_take_nil, list_drop_nil, list_take_nil]
      | cons x xs =>
        rw [take]
        rw [drop]
        rw [Nat.succ_sub_succ]
        rw [drop]
        apply ih

theorem list_drop_cons (n: Nat) (x: α) (xs: List α):
  drop (n + 1) (x::xs) = drop n xs := by
  rw [drop]

theorem list_drop_all (xs: List α):
  drop (length xs) xs = nil := by
  induction xs with
  | nil =>
    rw [list_drop_nil]
  | cons head tail ih =>
    rw [length]
    rw [drop]
    exact ih

theorem list_drop_all2 (n: Nat) (xs: List α):
  length xs <= n -> drop n xs = [] := by
  revert xs
  induction n with
  | zero =>
    intro xs
    intro h
    have empty_list := list_length_zero_or_smaller_is_empty xs h
    rw [empty_list]
    rw [drop]
  | succ n ih =>
    intro xs
    cases xs with
    | nil =>
      rw [length]
      intro _
      rw [drop]
    | cons x xs =>
      rw [length]
      intro h
      rw [drop]
      rw [nat_succ_le_succ_iff] at h
      exact ih xs h

theorem list_take_drop (n: Nat) (xs: List α):
  take n xs ++ drop n xs = xs := by
  revert xs
  induction n with
  | zero =>
    intro xs
    rw [take]
    rw [drop]
    rw [list_app_nil_l]
  | succ n ih =>
    intro xs
    cases xs with
    | nil =>
      rw [take, drop]
      simp
    | cons x xs =>
      rw [take, drop]
      apply (congrArg (cons x))
      apply ih

theorem nat_succ_gt_succ {n m: Nat}:
  succ n > succ m -> n > m := by
  intro h
  cases h with
  | refl =>
    constructor
  | step s =>
    apply Nat.le_of_succ_le_succ
    exact (Nat.le_succ_of_le s)

theorem nat_succ_gt_succ' {n m: Nat}:
  succ n > succ m -> n > m := by
  apply Nat.le_of_succ_le_succ

theorem list_take_large_length {n: Nat} {xs: List α}:
  n > length xs -> length (take n xs) = length xs := by
  revert xs
  induction n with
  | zero =>
    intro xs
    intro h
    contradiction
  | succ n ih =>
    intro xs
    intro h
    cases xs with
    | nil =>
      rw [take]
    | cons x xs =>
      rw [take]
      repeat rw [length]
      apply congrArg succ
      rw [length] at h
      have h' := nat_succ_gt_succ h
      exact ih h'

theorem list_take_length (n: Nat) (xs: List α):
  length (take n xs) = min n (length xs) := by
  unfold min
  unfold instMinNat
  unfold minOfLe
  simp
  split
  case inl =>
    rename_i c
    apply (list_take_length_le _ _ c)
  case inr =>
    rename_i c
    have c' := gt_of_not_le c
    exact (list_take_large_length c')

theorem list_drop_length (n: Nat) (xs: List α):
  length (drop n xs) = length xs - n := by
  revert xs
  induction n with
  | zero =>
    intro xs
    rw [drop]
    rw [Nat.sub_zero]
  | succ n ih =>
    intro xs
    cases xs with
    | nil =>
      rw [drop]
      rw [length]
      rw [Nat.zero_sub]
    | cons x xs =>
      rw [drop]
      rw [length]
      rw [Nat.succ_sub_succ]
      apply ih

theorem list_drop_app (n: Nat) (xs ys: List α):
  drop n (xs ++ ys) = (drop n xs) ++ (drop (n - length xs) ys) := by
  -- TODO
  sorry

theorem list_take_app_length (xs ys: List α):
  take (length xs) (xs ++ ys) = xs := by
  revert ys
  induction xs with
  | nil =>
    intro ys
    simp
    apply list_take_zero
  | cons x xs ih =>
    intro ys
    rw [length]
    rw [<- list_app_comm_cons]
    rw [take]
    apply (congrArg (cons x))
    apply ih

theorem list_drop_app_length (xs ys: List α):
  drop (length xs) (xs ++ ys) = ys := by
  -- TODO
  sorry

theorem list_split_list (xs: List α) (n : Nat): ∀ (ys zs: List α),
  length ys = n ->
  xs = ys ++ zs ->
  ys = take n xs /\
  zs = drop n xs := by
  revert xs
  induction n with
  | zero =>
    intro xs ys zs hl ha
    have he := list_length_zero_is_empty _ hl
    rw [he]
    rw [he] at ha
    rw [take]
    rw [drop]
    rw [list_app_nil_l] at ha
    have ha' := Eq.symm ha
    constructor
    case left =>
      rfl
    case right =>
      exact ha'
  | succ n ih =>
    intro xs ys zs hl ha
    cases xs with
    | nil =>
      rw [take]
      rw [drop]
      have ha' := Eq.symm ha
      rw [list_app_nil_nil] at ha'
      exact ha'
    | cons x xs =>
      rw [take, drop]
      cases ys with
      | nil =>
        rw [length] at hl
        contradiction
      | cons y ys =>
        simp at hl
        have hzs : y :: ys ++ zs = y :: (ys ++ zs) := by
          simp
        rw [hzs] at ha
        rw [list_cons_eq] at ha
        cases ha with
        | intro hxy ha =>
          rw [hxy]
          have ih' := ih xs ys zs hl ha
          cases ih' with
          | intro hys hzs =>
            rw [<- hys]
            rw [<- hzs]
            simp

theorem list_prefix_leq_length (xs ys zs: List α):
  xs = ys ++ zs -> length ys <= length xs := by
  -- TODO
  sorry

theorem list_drop_length_prefix_is_suffix (xs ys: List α):
  drop (length xs) (xs ++ ys) = ys := by
  -- TODO
  sorry

theorem list_take_length_prefix_is_prefix (xs ys: List α):
  take (length xs) (xs ++ ys) = xs := by
  -- TODO
  sorry

theorem list_prefix_length_leq: ∀ (xs ys zs: List α),
  xs ++ ys = zs -> length xs <= length zs := by
  -- TODO
  sorry

theorem list_length_gt_zero: ∀ (xs: List α),
  xs ≠ [] -> 0 < length xs := by
  intro xs
  cases xs with
  | nil =>
    intro h
    contradiction
  | cons x xs =>
    intro _
    rw [length]
    apply Nat.zero_lt_succ

theorem list_prefix_is_gt_zero_and_leq: ∀ (xs ys zs: List α),
  (xs ≠ []) ->
  xs ++ ys = zs ->
  (0 < length xs /\ length xs <= length zs) := by
  intro xs ys zs
  revert xs ys
  cases zs with
  | nil =>
    intro xs ys hneq ha
    rw [list_app_nil_nil] at ha
    cases ha with
    | intro hxs hys =>
      contradiction
  | cons z zs =>
    intro xs ys hneq ha
    have hl := list_length_gt_zero _ hneq
    rw [<- ha]
    apply And.intro
    case left =>
      exact hl
    case right =>
      rw [list_app_length]
      apply Nat.le_add_right

theorem list_prefix_is_not_empty_with_index_gt_zero: ∀ (xs: List α) (n: Nat)
  (range: 0 < n /\ n <= length xs),
  take index xs ≠ [] :=
  -- TODO
  sorry

theorem list_app_uncons: ∀ (x: α) (xs ys zs: List α),
  ys ++ zs = x :: xs ->
  (ys = [] /\ zs = x :: xs)
  \/ (∃
     (ys': List α)
     (pys: ys = x :: ys'),
     ys' ++ zs = xs
  ) := by
  -- TODO
  sorry


/-
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
import data.real.basic -- imports the real numbers

/-

# Limits of sequences in Lean

We give the standard `ε`, `N` definition of the limit of a sequence
and prove some theorems about them.

## lambda (λ) notation for functions

Here's how we define the functions from the naturals to the reals
sending n to n^2 + 3:

-/

def f : ℕ → ℝ := λ n, n^2 + 3

/-

Mathematicians might write `n ↦ n^2+3` for this functions; indeed `λ` is
just prefix notation for the infix notation `↦` (i.e. you write `λ` at
the front and `↦` in the middle but they mean the same thing). You can
read more about function types in the "three kinds of types" section
of Part B of the course book.

Sometimes you might find yourself with a lambda-defined function
evaluated at a number. For example, you might see something like
`(λ n, n^2 + 3) 37`, which means "take the function sending
`n` to `n^2+3` and then evaluate it at 37". You can use the `dsimp`
(or `dsimp only`) tactic to simplify this to `37^2+3`.

The reason we need to know about function notation for this sheet
is that a sequence `x₀, x₁, x₂, …` of reals on this sheet will
be encoded as a function from `ℕ` to `ℝ` sending `0` to `x₀`, `1` to `x₁`
and so on.

## Limit of a sequence.

Here's the definition of the limit of a sequence.
-/

/-- If `a(n)` is a sequence of reals and `t` is a real, `tends_to a t`
is the assertion that the limit of `a(n)` as `n → ∞` is `t`. -/
def tends_to (a : ℕ → ℝ) (t : ℝ) : Prop :=
∀ ε > 0, ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε

/-

We've made a definition, so it's our job to now make the API
for the definition, i.e. prove some basic theorems about it.
-/

-- If your goal is `tends_to a t` and you want to replace it with
-- `∀ ε > 0, ∃ B, …` then you can do this with `rw tends_to_def`.
theorem tends_to_def {a : ℕ → ℝ} {t : ℝ} :
  tends_to a t ↔ ∀ ε, 0 < ε → ∃ B : ℕ, ∀ n, B ≤ n → |a n - t| < ε :=
begin
  -- true by definition
  refl
end

-- the eagle-eyed viewers amongst you might have spotted
-- that `∀ ε > 0, ...` and `∀ ε, ε > 0 → ...` and `∀ ε, 0 < ε → ...`
-- are all definitionally equal, so `refl` sees through them.

/-

## The questions

Here are some basic results about limits of sequences.
See if you can fill in the `sorry`s with Lean proofs.
Note that `norm_num` can work with `|x|` if `x` is a numeral like 37,
but it can't do anything with it if it's a variable.
-/

/-- The limit of the constant sequence with value 37 is 37. -/
theorem tends_to_thirtyseven : tends_to (λ n, 37) 37 :=
begin
  sorry,
end

/-- The limit of the constant sequence with value `c` is `c`. -/
theorem tends_to_const (c : ℝ) : tends_to (λ n, c) c :=
begin
  sorry,
end

/-- If `a(n)` tends to `t` then `a(n) + c` tends to `t + c` -/
theorem tends_to_add_const {a : ℕ → ℝ} {t : ℝ} (c : ℝ)
  (h : tends_to a t) :
  tends_to (λ n, a n + c) (t + c) :=
begin
  sorry,
  -- hints: make sure you know the maths proof!
  -- use `cases` to deconstruct an `exists`
  -- hypothesis, and `specialize` to specialize
  -- a `forall` hypothesis to specific values.
  -- Look up the explanations of these tactics in Part C
  -- of the course notes.
end

-- you're not quite ready for this one yet though.
/-- If `a(n)` tends to `t` then `-a(n)` tends to `-t`.  -/
example {a : ℕ → ℝ} {t : ℝ} (ha : tends_to a t) :
  tends_to (λ n, - a n) (-t) :=
begin
  sorry,
  -- Try this one. You don't know enough material to do it yet!
  -- Where do you get stuck? The problem is that I didn't teach you
  -- any "API" for (a.k.a. theorems about) the absolute value function |.|.
  -- We need to figure out how to prove |(-x)| = |x|,
  -- or |a - b| = |b - a| or something like that.
  -- Leave this for now and try sheet 4, where you'll learn how to discover these things.
  -- We'll come back to this example on sheet 5.
end


import Mathlib.Data.Real.Basic

/-
 Russian Mathematical Olympiad 1998, problem 42

 A binary operation ⋆ on real numbers has the property that
 (a ⋆ b) ⋆ c = a + b + c.

 Prove that a ⋆ b = a + b.

-/

variable (star : ℝ → ℝ → ℝ)
local infixl:80 " ⋆ " => star

theorem russia1998_q42
  (stardef : ∀ a b c, a ⋆ b ⋆ c = a + b + c) :
  (∀ a b, a ⋆ b = a + b) :=
by
  have lemma2 : ∀ a b d, a ⋆ b = d ⋆ b → a = d := by
    intros a b d hab
    have := calc a + b + a
        = a ⋆ b ⋆ a := (stardef _ _ _).symm
      _ = d ⋆ b ⋆ a := by rw [hab]
      _ = d + b + a := stardef _ _ _
    have : a + b = d + b := (add_left_inj a).mp this
    have : a = d := (add_left_inj b).mp this
    exact this

  have lemma3 : ∀ a b, a ⋆ b = b ⋆ a := by
    intros a b
    let d1 := a ⋆ b
    let d2 := b ⋆ a
    have h1 := calc d1 ⋆ 1 = a + b + 1 := stardef _ _ _
                    _ = b + a + 1 := by rw [add_comm a b]
                    _ = d2 ⋆ 1 := (stardef _ _ _).symm
    exact lemma2 d1 1 d2 h1

  have lemma4 : ∀ a, a ⋆ 0 = a := by
    intro a
    let x := a ⋆ 0
    have h1 := calc x ⋆ 0
        = a + 0 + 0 := stardef a 0 0
      _ = a := by rw [add_zero, add_zero]

    have h2 := calc 2 * x
        = x + x := two_mul x
      _ = x + 0 + x := by rw [add_zero]
      _ = x ⋆ 0 ⋆ x := (stardef _ _ _).symm
      _ = a ⋆ x := by rw [h1]
      _ = x ⋆ a := lemma3 _ _
      _ = a ⋆ 0 ⋆ a := rfl
      _ = a + 0 + a := stardef _ _ _
      _ = a + a := by rw [add_zero]
      _ = 2 * a := (two_mul a).symm
    have h3 : (2:ℝ) ≠ 0 := two_ne_zero
    have h4 : x = a := (mul_right_inj' h3).mp h2
    exact h4

  intros a b

  have := calc a + b = a + b + 0 := by rw [add_zero]
                   _ = a ⋆ b ⋆ 0 := (stardef _ _ _).symm
                   _ = a ⋆ b := lemma4 _
  exact this.symm


/- OVERVIEW

To readily understand the introduction rule and the
elimination rule for proofs of existential propositions
(starting with ∃), we first need a review of predicates.
Predicate in turn are thought of as functions from the
arguments to propositions "about" those arguments. So
we also discuss function definitions, more generally,
and how we can specify them in various ways in Lean.
Finally we come back to the inference rules for ∃.
-/

/- PREDICATES

A predicate is a proposition with one or more parameters.
A proposition is a predicate with no remaining parameters!

You can think of a predicate it as a function that takes
one or more arguments and that reduces to a proposition
*about those particular values*.

Here, for example, we define a predicate, called isEven,
that takes a natural number, n, as an argument and that
reduces to ("returns") the proposition, n % 2 = 0, *for
that particular n*.
-/

def isEven : ℕ → Prop :=
begin
assume n,
exact (n%2 = 0)
end

/-
In fact, in Lean and similar logical programming systems,
a predicate *is* a function, and can thus be applied to an
argument of the specified type.
-/

#reduce isEven 0      -- 0 = 0
#reduce isEven 1      -- 1 = 0
#reduce isEven 2      -- 0 = 0
#reduce isEven 3      -- 1 = 0

/-
Note that the n%2 expression is evaluated automatically.
-/

/-
We will say that one or more values "satisfy" a predicate
when the corresponding proposition is true. In constructive
logic, that means when there's a proof of that proposition.
-/

example : isEven 0 :=
begin
simp [isEven],  -- new tactic: simplify by def'n of isEven
exact rfl,      -- forces reduction, tests equality
-- Yay! 0 is even
end

-- The rfl tactic does some simplification automatically
example : isEven 0 :=
begin
exact rfl,      -- forces reduction, tests equality
-- Yay! 0 is even
end

example : isEven 1 :=
begin
exact rfl,      -- there's no proof of 1=0
-- Ooooh noooo, 1 is not even
end

-- In fact we can prove the negation
example : ¬isEven 1 :=
begin
assume h,
simp [isEven] at h, -- more tactic fun (optional)
cases h,            -- no proofs of h so done
-- Yay! 1 is *not* even (proof by negation)
end

-- Proof that 2 is even
example : isEven 2 :=
begin
exact rfl,
-- Yay! 2 is even.
end

/-
A predicate expresses a *property* of the objects
it takes as arguments. Here the predicate expresses
the property of a natural number *being even* (or not).
Every natural number, n, that satisfies the predicate
(for which there's a proof) has the property expressed
by isEven,  while every number lacks this property.

We speak of values that "satisfy" a predicate as
having the property that it expresses. Again, as an
example, evenness is a property of natural numbers.
Some have it, some don't. Every n for which n%2=0
has it, and no other number does (in ℕ).

A predicate can also be understood as specifying a
*set* of values: in this example, the set of all
even natural numbers: all those n where n % 2 = 0.
-/


import Mathlib.Data.Nat.Basic -- for ℕ notation

/-
We have covered the basics of propositional logic. The main limitation
of propositional logic is that all statements have to be propositions.

For example, we are not allowed to talk about a statement like: `n` is odd.
This is not a proposition because `n` does not actually have a value. It
is a placeholder which will be instantiated at some point by an actual
value.

Propositions that depend on variables are called _predicates_. They are
implemented in Lean quite easily.
-/

variable {α β : Type}
variable (P P': α → Prop) (Q : α → β → Prop)

/-
Here `P` is a predicate on one variable by type `α` while `Q` is a
predicate on two variables.
-/

def leSquare : ℕ → ℕ → Prop := fun n m ↦ n ≤ m^2

/-
We can convert predicates into propositions by _quantifying_ over them.
In our previous example, we can go from `n` is odd to for all `n`, `n` is
odd. This is now a proposition.

Instead of asking that the predicate is true for all values, we can ask
that it is true for at least one value: there exists `n` such that `n` is
odd.
-/

def allLESquare : Prop := ∀ n m, leSquare n m

def someLESquare : Prop := ∃ n m, leSquare n m

/-
Since we have multiple variables, we use different quantifiers.
-/

def allLESomeSquare : Prop := ∀ n, ∃ m, leSquare n m

def someLEAllSquares : Prop := ∃ n, ∀ m, leSquare n m

/-
Notice that the order of quantifiers is important in general.
-/

def someSquareGEAll : Prop := ∃ m, ∀ n, leSquare n m

/-
Like the connectives, quantified statements have introduction and elimination
rules.

The universal quantifier is essentially a function that take as input
any value of the variables and gives as output a proof the proposition
with that value. In Lean it acts as such.
-/

example : ∀ (n : ℕ), n = n  := by
  intro n
  rfl

example : ∀ (n : ℕ), n = n  := by
  exact fun n => rfl

example (h : allLESquare) : 5 ≤ 4 := by
  exact h 5 2

/-
The existentional quantifier in contrast is implemented as its own inductive
type.
/--
Existential quantification. If `p : α → Prop` is a predicate, then `∃ x : α, p x`
asserts that there is some `x` of type `α` such that `p x` holds.
To create an existential proof, use the `exists` tactic,
or the anonymous constructor notation `⟨x, h⟩`.
To unpack an existential, use `cases h` where `h` is a proof of `∃ x : α, p x`,
or `let ⟨x, hx⟩ := h` where `.
-/
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
-/

example : ∃ n, ∀ m, leSquare n m := by
  apply Exists.intro 0
  intro m
  sorry

example : ∃ n, ∀ m, leSquare n m := by
  exact ⟨0, sorry⟩

example (h : ∃ m, ∀ n, leSquare n m) : False := by
  cases h with
  | intro w h => sorry

/-
We can commute quantifiers of the same type.
-/

example : (∀ a, ∀ b, Q a b) ↔ ∀ b, ∀ a, Q a b := by
  apply Iff.intro
  · intro h
    intro b a
    exact h a b
  · intro h
    intro a b
    exact h b a

example : (∃ a, ∃ b, Q a b) ↔ ∃ b, ∃ a, Q a b := by
  apply Iff.intro
  · intro h
    apply Exists.elim h
    intro a
    intro h'
    apply Exists.elim h'
    intro b h''
    apply Exists.intro b
    apply Exists.intro a
    exact h''
  · intro h
    apply Exists.elim h
    intro b
    intro h'
    apply Exists.elim h'
    intro a h''
    apply Exists.intro a
    apply Exists.intro b
    exact h''

/-
Some other examples
-/

example : (∃ a, ¬ P a) → ¬ ∀ a, P a := by
  intro h n
  apply Exists.elim h
  intro a₀ h'
  apply h'
  exact n a₀

example (a : α) (h : ¬ ∀ a, P a) : ∃ a, ¬ P a := sorry

example (h : ∀ a, P a) (h' : ∀ a, P' a) : ∀ a, P a ∧ P' a := sorry

example (h : ∃ a, P a) : ∃ a, P a ∨ P' a := sorry


-- induccion.lean
-- Inducción.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 5-julio-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Importar la librerías de tácticas y la teoría de grupos.
-- ---------------------------------------------------------------------

import tactic
import algebra.group.basic

-- ---------------------------------------------------------------------
-- Ejercicio. Habilitar la lógica clásica.
-- ---------------------------------------------------------------------

open classical

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    suma_potencias_de_dos : nat → nat
-- tal que (suma_potencias_de_dos n) = 2^0 + 2^1 +···+ 2^n
-- ---------------------------------------------------------------------

def suma_potencias_de_dos : nat → nat
| 0            := 1
| (nat.succ n) := suma_potencias_de_dos n + 2^(n+1)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    suma_potencias_de_dos n = 2^(n+1) - 1
-- ---------------------------------------------------------------------

lemma valor_suma_potencias_de_dos
  (n : nat)
  : suma_potencias_de_dos n = 2^(n+1) - 1 :=
begin
  induction n with m HI,
  { simp [suma_potencias_de_dos], },
  { calc suma_potencias_de_dos (m+1)
         = suma_potencias_de_dos m + 2^(m+1)
           : by simp [suma_potencias_de_dos]
    ...  = 2^(m+1) - 1 + 2^(m+1)
           : by simp [HI]
    ...  = 2^(m+1) + 2^(m+1) - 1
           : by omega
    ...  = 2 * 2^(m+1) - 1
           : by ring_nf
    ...  = 2^(m+2) - 1
           : by ring, },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular la suma de las 5 primeras potencias de dos.
-- ---------------------------------------------------------------------

-- Se descomenta la siguiente línea y al situar el cursor en #eval se
-- obtiene el valor (que es 63).
--
-- #eval suma_potencias_de_dos 5

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir la teoría de conjuntos finitos.
-- ---------------------------------------------------------------------

open finset

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir localmente la teoría de los grandes operadores (para
-- usar ∑).
-- ---------------------------------------------------------------------

open_locale big_operators

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    suma_de_unos : nat → nat
-- tal que (suma_de_unos n) es la suma de n veces el número 1.
-- ---------------------------------------------------------------------

def suma_de_unos : nat → nat :=
λ n : nat, ∑ (i : nat) in range n, 1

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    suma_de_unos n = n
-- ---------------------------------------------------------------------

-- 1ª demostración
example
  (n : nat)
  : suma_de_unos n = n :=
begin
  induction n with m HI,
  { simp [suma_de_unos] },
  { calc suma_de_unos (m+1)
         = ∑ (i : nat) in range (m+1), 1  : by simp [suma_de_unos]
    ... = (∑ (i : nat) in range m, 1) + 1 : by simp
    ... = m + 1                           : by simp [HI] },
end

-- 2ª demostración
example
  (n : nat)
  : suma_de_unos n = n :=
begin
  unfold suma_de_unos,
  -- by library_search,
  exact sum_range_induction (λ (k : ℕ), 1) (λ (n : ℕ), n) rfl (congr_fun rfl) n,
end

-- 2ª demostración
example
  (n : nat)
  : suma_de_unos n = n :=
begin
  unfold suma_de_unos,
  apply sum_range_induction,
  { refl, },
  { intro n,
    refl, },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir, usando sumatorios, la función
--    suma_potencias_de_dos' : nat → nat
-- tal que (suma_potencias_de_dos' n) = 2^0 + 2^1 +···+ 2^n
-- ---------------------------------------------------------------------

def suma_potencias_de_dos' : nat → nat
:= λ n, ∑ (i : nat) in range n, 2^i

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    suma_potencias_de_dos' n = 2^(n+1) - 1
-- ---------------------------------------------------------------------

example
  (n : nat)
  : suma_potencias_de_dos' n =  2^n - 1 :=
begin
  induction n with m HI,
  { unfold suma_potencias_de_dos',
    ring},
  { calc suma_potencias_de_dos' (m+1)
        = ∑ i in range (m+1), 2^i
        : by unfold suma_potencias_de_dos'
    ... = ∑ i in range m, 2^i + 2^m
        : by exact sum_range_succ (λ x, 2^x) m
    ... = suma_potencias_de_dos' m + 2^m
        : by simp [suma_potencias_de_dos']
    ... = 2^m - 1 + 2^m
        : by simp [HI]
    ... = 2^m + 2^m - 1
        : by omega
    ... = 2^(m+1) - 1
        : by ring_nf, },
end

-- =====================================================================
-- § Ejercicios complementarios                                       --
-- =====================================================================

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la función
--    suma_primeros_pares : nat → nat
-- tal que (suma_primeros_pares n) es la suma
--    0 + 2 + 4 + 6 +···+ 2*n
-- ---------------------------------------------------------------------

def suma_primeros_pares : nat → nat
| 0            := 0
| (nat.succ n) := suma_primeros_pares n + 2 * (n + 1)

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    suma_primeros_pares n = n * (n+1)
-- ---------------------------------------------------------------------

example
  (n : nat)
  : suma_primeros_pares n = n * (n+1) :=
begin
  induction n with m HI,
  { simp [suma_primeros_pares], },
  { calc suma_primeros_pares (m+1)
         = suma_primeros_pares m + 2 * (m+1)
        : by simp [suma_primeros_pares]
    ... = m * (m+1) + 2 * (m+1)
        : by simp [HI]
    ... = (m+2) * (m+1)
        : by ring
    ... = (m+1) * (m+2)
        : by ring, },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir, usando sumatorio, la función
--    suma_primeros_pares' : nat → nat
-- tal que (suma_primeros_pares' n) es la suma
--    0 + 2 + 4 + 6 +···+ 2*n
-- ---------------------------------------------------------------------

def suma_primeros_pares' : nat → nat
:= λ n, 2 * ∑ (i : nat) in range (n+1), i

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    suma_primeros_pares' n = n * (n+1)
-- ---------------------------------------------------------------------

example
  (n : nat)
  : suma_primeros_pares' n = n * (n+1) :=
begin
  induction n with m HI,
  { simp [suma_primeros_pares'], },
  { calc suma_primeros_pares' (m+1)
        = 2 * ∑ (i : nat) in range (m+2), i
        : by unfold suma_primeros_pares'
    ... = 2 * ((∑ (i : nat) in range (m+1), i) + m + 1)
        : by {congr, simp only [sum_range_succ], ring}
    ... = 2 * ∑ (i : nat) in  range (m+1), i + 2 * (m+1)
        : by ring
    ... = m * (m+1) + 2 * (m+1)
        : by {unfold suma_primeros_pares' at HI, simp [HI]}
    ... = (m+2) * (m+1)
        : by ring
    ... = (m+1) * (m+2)
        : by ring, },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Sea G un grupo, n ∈ ℕ y a, b ∈ G. Demostrar que
--    (a * b * a⁻¹)^n = a * b^n * a⁻¹
-- ---------------------------------------------------------------------

example
  {G : Type*} [group G]
  (a : G)
  (b : G)
  (n : ℕ)
  : (a * b * a⁻¹)^n = a * b^n * a⁻¹ :=
begin
  induction n with m HI,
  { group },
  { calc (a * b * a⁻¹)^(m+1)
        = (a * b * a⁻¹)^m * (a * b * a⁻¹) : by simp [pow_succ' _ m]
    ... = (a * b^m * a⁻¹) * (a * b * a⁻¹) : by simp [HI]
    ... = a * b^m * b * a⁻¹               : by group
    ... = a * b^(m+1) * a⁻¹               : by group },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Sea G un grupo, n ∈ ℕ y a, b ∈ G. Demostrar que si
--    a * b = b * a
-- entonces
--    b^n * a = a * b^n
-- ---------------------------------------------------------------------

example
  {G : Type*} [group G]
  (a : G)
  (b : G)
  (hab : a * b = b * a)
  (n : nat)
  : b^n * a = a * b^n :=
begin
  induction n with m HI,
  { group },
  { calc b^(m+1) * a
        = b^m * b * a : by group
    ... = b^m * (b*a) : by group
    ... = b^m * (a*b) : by simp [hab]
    ... = b^m * a * b : by group
    ... = a * b^m * b : by simp [HI]
    ... = a * b^(m+1) : by group },
end

-- Referencia
-- ==========

-- Basado en induction.lean de Clara Löh que se encuentra en
-- https://bit.ly/3upWGTV


example (A B : Prop) : A ∨ B ∨ ¬ A ∨ B :=
begin
by_cases h:A,
left,
exact h,
right,
right,
left,
exact h,
end

theorem lawofassoc (A B C : Prop) : A∧(B∧C) ↔ (A∧B)∧C:=
begin
split,
intro h₁,
split,
cases h₁ with h₂ h₃,
cases h₃ with h₄ h₅,
split,
exact h₂,
exact h₄,
cases h₁ with h₂ h₃,
cases h₃ with h₄ h₅,
exact h₅,
intro h₁,
split,
cases h₁ with h₂ h₃,
cases h₂ with h₄ h₅,
exact h₄,
cases h₁ with h₂ h₃,
cases h₂ with h₄ h₅,
split,
exact h₅,
exact h₃,
end

theorem lawofdist (A B C : Prop) : A∧(B∨C) ↔ (A∧B)∨(A∧C):=
begin
split,
intro h₁,
cases h₁ with h₂ h₃,
cases h₃ with h₄ h₅,
left,
split,
exact h₂,
exact h₄,
right,
split,
exact h₂,
exact h₅,
intro h₁,
cases h₁ with h₂ h₃,
cases h₂ with h₄ h₅,
split,
exact h₄,
left,
exact h₅,
split,
cases h₃ with h₄ h₅,
exact h₄,
cases h₃ with h₄ h₅,
right,
exact h₅,
end

theorem de_morgan1 (A B : Prop) : ¬ (A∨B) ↔ (¬A) ∧ (¬B) :=
begin
split, --We consider each implication separately
intro h₁, --We introduce ¬(A ∨ B) as the hypothesis h₁
split, --Instead of showing ¬A ∧ ¬B, we will show ¬A and ¬B separately
intro h₂, --The goal ¬A changes to false, and we the hypothesis h₂:A
apply h₁,
left,
exact h₂,
intro h₂,
apply h₁,
right,
exact h₂,
intros h₁ h₂,
cases h₁ with h₃ h₄,
cases h₂ with h₅ h₆,
apply h₃,
exact h₅,
apply h₄,
exact h₆,
end

theorem de_morgan2 (A B : Prop) : ¬(A∧B) ↔ (¬A)∨(¬B):=
begin
split,
intro h₁,
by_cases h:A,
right,
intro h₂,
apply h₁,
split,
exact h,
exact h₂,
left,
exact h,
intros h₁ h₂,
cases h₂ with h₃ h₄,
cases h₁ with h₅ h₆,
apply h₅,
exact h₃,
apply h₆,
apply h₄,
end


-- Estudante: Lucas Emanuel Resck Domingues

open nat

--1.a.
example : ∀ m n k : nat, m * (n + k) = m * n + m * k :=
    assume m n k,
        nat.rec_on k
            (show m * (n + 0) = m * n + m * 0, from calc
                m * (n + 0) = m * n         : by rw add_zero
                        ... = m * n + 0     : by rw add_zero
                        ... = m * n + m * 0 : by rw mul_zero)
            (assume p,
                assume ih: m * (n + p) = m * n + m * p,
                show m * (n + succ p) = m * n + m * (succ p), from
                    calc
                        m * (n + succ p) = m * (succ (n + p)) : by rw add_succ
                                     ... = m * (n + p) + m : by rw mul_succ
                                     ... = m * n + m * p + m : by rw ih
                                     ... = m * n + (m * p + m) : by rw add_assoc
                                     ... = m * n + m * (succ p) : by rw mul_succ)

--1.b.
example : ∀ n : nat, 0 * n = 0 :=
    assume n,
        nat.rec_on n
            (show 0 * 0 = 0, from rfl)
            (assume k,
                assume ih : 0 * k = 0,
                show 0 * (succ k) = 0, from
                    calc
                        0 * (succ k) = 0 * k + 0 : by rw mul_succ
                                ... = 0 * k : by rw add_zero
                                ... = 0 : by rw ih)

--1.c.
example : ∀ n : nat, 1 * n = n :=
    assume n,
        nat.rec_on n
            (show 1 * 0 = 0, by rw mul_zero)
            (assume k,
                assume ih : 1 * k = k,
                    calc
                        1 * (succ k) = 1 * k + 1 : by rw mul_succ
                                ... = k + 1 : by rw ih
                                ... = succ k : rfl)

--1.d.
example : ∀ m n k : nat, (m * n) * k = m * (n * k) :=
    assume m n k,
        nat.rec_on k
            (show (m * n) * 0 = m * (n * 0), from
                calc
                    (m * n) * 0 = 0 : by rw mul_zero
                            ... = m * 0 : by rw mul_zero
                            ... = 0 * m : by rw mul_comm
                            ... = (n * 0) * m : by rw mul_zero
                            ... = m * (n * 0) : by rw mul_comm)
            (assume i (ih : (m * n) * i = m * (n * i)),
            show (m * n) * succ i = m * (n * succ i), from
                begin
                    rw mul_succ,
                    rw ih,
                    rw ← left_distrib,
                    rw mul_succ
                end)

--1.e.
example : ∀ m n : nat, m * n = n * m :=
    assume m n,
        nat.rec_on n
            (begin
                rw mul_zero,
                apply eq.symm,
                rw zero_mul
            end)
            (begin
                intros k ih,
                rw mul_succ,
                rw ih,
                rw succ_mul
            end)

--2.a.
example : ∀ m n k : nat, n ≤ m → n + k ≤ m + k :=
    assume m n k (h1 : n ≤ m),
    show n + k ≤ m + k, from nat.rec_on k
        h1
        (begin
            intros k' ih,
            have h2, from succ_le_succ ih,
            rw add_succ,
            rw add_succ,
            exact h2
        end)

--2.b.
example : ∀ m n k : nat, n + k ≤ m + k → n ≤ m :=
    begin
        intros m n k,
        apply nat.rec_on k,
            intro h1,
            exact h1,
        intros k' h1 h2,
        apply h1,
        apply le_of_succ_le_succ,
        rw ← add_succ,
        rw ← add_succ,
        exact h2
    end

--2.c.
example : ∀ m n k : nat, n ≤ m → n * k ≤ m * k :=
    assume m n k (h1 : n ≤ m),
    nat.rec_on k
        (le_refl 0)
        (assume k' (ih : n * k' ≤ m * k'),
        show n * succ k' ≤ m * succ k', from calc
            n * succ k' = n * k' + n : by rw mul_succ
                    ... ≤ m * k' + n : add_le_add_right ih n
                    ... ≤ m * k' + m : add_le_add_left h1 (m * k')
                    ... = m * succ k' : by rw mul_succ
        )

--2.d.
example : ∀ m n : nat, m ≥ n → m = n ∨ m ≥ n + 1 :=
    begin
        intros m n,
        apply nat.rec_on n,
            intro h1,
            apply classical.by_contradiction,
            intro h2,
            have h3 : ¬(m = 0) ∧ ¬(m ≥ 0 + 1), from
                begin
                    apply and.intro,
                        intro h4,
                        have h5 : m = 0 ∨ m ≥ 0 + 1, from or.inl h4,
                        exact h2 h5,
                    intro h3,
                    have h4 : m = 0 ∨ m ≥ 0 + 1, from or.inr h3,
                    exact h2 h4
                end,
            cases h3 with h4 h5,
            have h6 : m ≠ 0, from h4,
            have h7 : 0 ≤ m, from h1,
            apply h5,
            have h8 : 0 < m, from nat.lt_of_le_and_ne h7 h6.symm,
            apply le_of_lt_succ,
            apply succ_lt_succ,
            exact h8,
        intros k ih h1,
        apply classical.by_contradiction,
        intro h2,
        have h3 : ¬(m = succ k) ∧ ¬(m ≥ succ k + 1), from
            begin
                apply and.intro,
                    intro h4,
                    have h5 : m = succ k ∨ m ≥ succ k + 1, from or.inl h4,
                    exact h2 h5,
                intro h3,
                have h4 : m = succ k ∨ m ≥ succ k + 1, from or.inr h3,
                exact h2 h4
            end,
        cases h3 with h4 h5,
        have h6 : m ≠ succ k, from h4,
        have h7 : succ k ≤ m, from h1,
        apply h5,
        have h8 : succ k < m, from nat.lt_of_le_and_ne h7 h6.symm,
        apply le_of_lt_succ,
        apply succ_lt_succ,
        exact h8
    end

--2.e.
example : ∀ n : nat, 0 ≤ n :=
    begin
        intro n,
        apply nat.rec_on n,
            exact zero_le 0,
        intros k ih,
        have h1 : k ≤ succ k, from le_succ k,
        exact le_trans ih h1
    end


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: María Inés de Frutos-Fernández
-/
import topology.instances.nnreal

/-!
# Limits of monotone and antitone sequences

We prove some auxiliary results about limits of `ℝ`-valued and `ℝ≥0`-valued sequences.

## Main Results

* `real.tendsto_of_is_bounded_antitone` : an antitone, bounded below sequence `f : ℕ → ℝ` has a
  finite limit.
* `nnreal.tendsto_of_is_bounded_antitone` : an antitone sequence `f : ℕ → ℝ≥0` has a finite limit.

## Tags

glb, monotone, antitone, tendsto
-/

open_locale filter topology

/-- A nonempty, bounded below set of real numbers has a greatest lower bound. -/
theorem real.exists_is_glb {S : set ℝ} (hne : S.nonempty) (hbdd : bdd_below S) :
  ∃ x, is_glb S x :=
begin
  set T := - S with hT,
  have hT_ne : T.nonempty := set.nonempty_neg.mpr hne,
  have hT_bdd : bdd_above T := bdd_above_neg.mpr hbdd,
  use -classical.some (real.exists_is_lub T hT_ne hT_bdd),
  simpa [← is_lub_neg] using (classical.some_spec (real.exists_is_lub T hT_ne hT_bdd)),
end

/-- An monotone, bounded above sequence `f : ℕ → ℝ` has a finite limit. -/
lemma filter.tendsto_of_is_bounded_monotone {f : ℕ → ℝ} (h_bdd : bdd_above (set.range f))
  (h_mon : monotone f) : ∃ r : ℝ, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  obtain ⟨B, hB⟩ := (real.exists_is_lub ((set.range f)) (set.range_nonempty f) h_bdd),
  exact ⟨B, tendsto_at_top_is_lub h_mon hB⟩
end

/-- An antitone, bounded below sequence `f : ℕ → ℝ` has a finite limit. -/
lemma real.tendsto_of_is_bounded_antitone {f : ℕ → ℝ} (h_bdd : bdd_below (set.range f))
  (h_ant : antitone f) : ∃ r : ℝ, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  obtain ⟨B, hB⟩ := (real.exists_is_glb (set.range_nonempty f) h_bdd),
  exact ⟨B, tendsto_at_top_is_glb h_ant hB⟩,
end

/-- An antitone sequence `f : ℕ → ℝ≥0` has a finite limit. -/
lemma nnreal.tendsto_of_is_bounded_antitone {f : ℕ → nnreal} (h_ant : antitone f) :
  ∃ r : nnreal, filter.tendsto f filter.at_top (𝓝 r) :=
begin
  have h_bdd_0 : (0 : ℝ) ∈ lower_bounds (set.range (λ (n : ℕ), (f n : ℝ))),
  { intros r hr,
    obtain ⟨n, hn⟩ := set.mem_range.mpr hr,
    simp_rw [← hn],
    exact nnreal.coe_nonneg _ },
  have h_bdd : bdd_below (set.range (λ n, (f n : ℝ))) := ⟨0, h_bdd_0⟩,
  obtain ⟨L, hL⟩ := real.tendsto_of_is_bounded_antitone h_bdd h_ant,
  have hL0 : 0 ≤ L,
  { have h_glb : is_glb (set.range (λ n, (f n : ℝ))) L := is_glb_of_tendsto_at_top h_ant hL,
    exact (le_is_glb_iff h_glb).mpr h_bdd_0 },
  use ⟨L, hL0⟩,
  rw ← nnreal.tendsto_coe,
  exact hL,
end


-- ---------------------------------------------------------------------
-- Ejercicio 1. Realizar la siguientes acciones:
--    1. Importar la teoría de mcd sobre los naturales.
--    2. Declarar x, y y z como variables sobre los naturales.
-- ----------------------------------------------------------------------

import data.nat.gcd   -- 1
variables x y z : ℕ   -- 2

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si
--    x ∣ y
--    y ∣ z
-- entonces
--    x ∣ z
-- ----------------------------------------------------------------------

example
  (h₀ : x ∣ y)
  (h₁ : y ∣ z)
  : x ∣ z :=
dvd_trans h₀ h₁

-- ---------------------------------------------------------------------
-- Ejercicio 3. Demostrar que
--    x ∣ y * x * z
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example : x ∣ y * x * z :=
begin
  have h1 : x ∣ y * x,
    { exact dvd_mul_left x y },
  have h2 : (y * x) ∣ (y * x * z),
    { exact dvd.intro z rfl},
  show x ∣ y * x * z,
    { exact dvd_trans h1 h2},
end

-- 2ª demostración
-- ===============

example : x ∣ y * x * z :=
dvd_trans (dvd_mul_left x y) (dvd.intro z rfl)

-- 3ª demostración
-- ===============

example : x ∣ y * x * z :=
begin
  apply dvd_mul_of_dvd_left,
  apply dvd_mul_left
end

-- Su desarrollo es
--
--    ⊢ x ∣ y * x * z
-- apply dvd_mul_of_dvd_left,
--    ⊢ x ∣ y * x
-- apply dvd_mul_left
--    no goals

-- ---------------------------------------------------------------------
-- Ejercicio 4. Demostrar que si x ∈ ℕ, entonces
--    x ∣ x^2
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example : x ∣ x^2 :=
begin
  rw pow_two,
  apply dvd_mul_right,
end

-- Su desarrollo es
--
--    ⊢ x ∣ x ^ 2
-- rw pow_two,
--    ⊢ x ∣ x * x
-- apply dvd_mul_left
--    no goals

-- 2ª demostración
-- ===============

example : x ∣ x^2 :=
by apply dvd_mul_right

-- Lemas usados
-- ============

-- #check (dvd_trans : x ∣ y → y ∣ z → x ∣ z)
-- #check (dvd_mul_of_dvd_left : x ∣ y → ∀ (c : ℕ), x ∣ y * c)
-- #check (dvd_mul_left : ∀ (a b : ℕ), a ∣ b * a)
-- #check (pow_two : ∀ (a : ℕ), a ^ 2 = a * a)