-- ----------------------------------------------------
-- Ejercicio. Demostrar
--    ⊢ ¬(p ∧ ¬p)
-- ----------------------------------------------------

import tactic
variables (p q : Prop)

-- 1ª demostración
example :
  ¬(p ∧ ¬p) :=
begin
  intro H,
  apply H.right,
  exact H.left,
end

-- 2ª demostración
example :
  ¬(p ∧ ¬p) :=
begin
  intro H,
  exact H.right (H.left),
end

-- 3ª demostración
example :
  ¬(p ∧ ¬p) :=
λ H, H.right (H.left)

-- 4ª demostración
example :
  ¬(p ∧ ¬p) :=
begin
  rintro ⟨H1, H2⟩,
  exact H2 H1,
end

-- 5ª demostración
example :
  ¬(p ∧ ¬p) :=
λ ⟨H1, H2⟩, H2 H1

-- 6ª demostración
example :
  ¬(p ∧ ¬p) :=
-- by suggest
(and_not_self p).mp

-- 7ª demostración
example :
  ¬(p ∧ ¬p) :=
assume H : p ∧ ¬p,
have H1 : p,
  from and.left H,
have H2 : ¬p,
  from and.right H,
show false,
  from H2 H1

-- 8ª demostración
example :
  ¬(p ∧ ¬p) :=
-- by hint
by tauto

-- 9ª demostración
example :
  ¬(p ∧ ¬p) :=
by finish

-- 10ª demostración
example :
  ¬(p ∧ ¬p) :=
by simp


/-
Copyright (c) 2018 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison

Case bashing:
* on `x ∈ A`, for `A : finset α` or `A : list α`, or
* on `x : A`, with `[fintype A]`.
-/
import data.fintype.basic
import tactic.norm_num

namespace tactic
open lean.parser
open interactive interactive.types expr
open conv.interactive

/-- Checks that the expression looks like `x ∈ A` for `A : finset α`, `multiset α` or `A : list α`,
    and returns the type α. -/
meta def guard_mem_fin (e : expr) : tactic expr :=
do t ← infer_type e,
   α ← mk_mvar,
   to_expr ``(_ ∈ (_ : finset %%α))   tt ff >>= unify t <|>
   to_expr ``(_ ∈ (_ : multiset %%α)) tt ff >>= unify t <|>
   to_expr ``(_ ∈ (_ : list %%α))     tt ff >>= unify t,
   instantiate_mvars α

/--
`expr_list_to_list_expr` converts an `expr` of type `list α`
to a list of `expr`s each with type `α`.

TODO: this should be moved, and possibly duplicates an existing definition.
-/
meta def expr_list_to_list_expr : Π (e : expr), tactic (list expr)
| `(list.cons %%h %%t) := list.cons h <$> expr_list_to_list_expr t
| `([]) := return []
| _ := failed

private meta def fin_cases_at_aux : Π (with_list : list expr) (e : expr), tactic unit
| with_list e :=
(do
  result ← cases_core e,
  match result with
  -- We have a goal with an equation `s`, and a second goal with a smaller `e : x ∈ _`.
  | [(_, [s], _), (_, [e], _)] :=
    do let sn := local_pp_name s,
        ng ← num_goals,
        -- tidy up the new value
        match with_list.nth 0 with
        -- If an explicit value was specified via the `with` keyword, use that.
        | (some h) := tactic.interactive.conv (some sn) none
                        (to_rhs >> conv.interactive.change (to_pexpr h))
        -- Otherwise, call `norm_num`. We let `norm_num` unfold `max` and `min`
        -- because it's helpful for the `interval_cases` tactic.
        | _ := try $ tactic.interactive.conv (some sn) none $
               to_rhs >> conv.interactive.norm_num
                 [simp_arg_type.expr ``(max), simp_arg_type.expr ``(min)]
        end,
        s ← get_local sn,
        try `[subst %%s],
        ng' ← num_goals,
        when (ng = ng') (rotate_left 1),
        fin_cases_at_aux with_list.tail e
  -- No cases; we're done.
  | [] := skip
  | _ := failed
  end)

/--
`fin_cases_at with_list e` performs case analysis on `e : α`, where `α` is a fintype.
The optional list of expressions `with_list` provides descriptions for the cases of `e`,
for example, to display nats as `n.succ` instead of `n+1`.
These should be defeq to and in the same order as the terms in the enumeration of `α`.
-/
meta def fin_cases_at : Π (with_list : option pexpr) (e : expr), tactic unit
| with_list e :=
do ty ← try_core $ guard_mem_fin e,
    match ty with
    | none := -- Deal with `x : A`, where `[fintype A]` is available:
      (do
        ty ← infer_type e,
        i ← to_expr ``(fintype %%ty) >>= mk_instance <|> fail "Failed to find `fintype` instance.",
        t ← to_expr ``(%%e ∈ @fintype.elems %%ty %%i),
        v ← to_expr ``(@fintype.complete %%ty %%i %%e),
        h ← assertv `h t v,
        fin_cases_at with_list h)
    | (some ty) := -- Deal with `x ∈ A` hypotheses:
      (do
        with_list ← match with_list with
        | (some e) := do e ← to_expr ``(%%e : list %%ty), expr_list_to_list_expr e
        | none := return []
        end,
        fin_cases_at_aux with_list e)
    end

namespace interactive
private meta def hyp := tk "*" *> return none <|> some <$> ident
local postfix `?`:9001 := optional

/--
`fin_cases h` performs case analysis on a hypothesis of the form
`h : A`, where `[fintype A]` is available, or
`h ∈ A`, where `A : finset X`, `A : multiset X` or `A : list X`.

`fin_cases *` performs case analysis on all suitable hypotheses.

As an example, in
```
example (f : ℕ → Prop) (p : fin 3) (h0 : f 0) (h1 : f 1) (h2 : f 2) : f p.val :=
begin
  fin_cases *; simp,
  all_goals { assumption }
end
```
after `fin_cases p; simp`, there are three goals, `f 0`, `f 1`, and `f 2`.
-/
meta def fin_cases : parse hyp → parse (tk "with" *> texpr)? → tactic unit
| none none := focus1 $ do
    ctx ← local_context,
    ctx.mfirst (fin_cases_at none) <|>
      fail "No hypothesis of the forms `x ∈ A`, where `A : finset X`, `A : list X`, or `A : multiset X`, or `x : A`, with `[fintype A]`."
| none (some _) := fail "Specify a single hypothesis when using a `with` argument."
| (some n) with_list :=
  do
    h ← get_local n,
    focus1 $ fin_cases_at with_list h

end interactive

add_tactic_doc
{ name       := "fin_cases",
  category   := doc_category.tactic,
  decl_names := [`tactic.interactive.fin_cases],
  tags       := ["case bashing"] }

end tactic


/-
Copyright (c) 2020 Google LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Wong
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.list.basic
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Palindromes

This module defines *palindromes*, lists which are equal to their reverse.

The main result is the `palindrome` inductive type, and its associated `palindrome.rec_on` induction
principle. Also provided are conversions to and from other equivalent definitions.

## References

* [Pierre Castéran, *On palindromes*][casteran]

[casteran]: https://www.labri.fr/perso/casteran/CoqArt/inductive-prop-chap/palindrome.html

## Tags

palindrome, reverse, induction
-/

/--
`palindrome l` asserts that `l` is a palindrome. This is defined inductively:

* The empty list is a palindrome;
* A list with one element is a palindrome;
* Adding the same element to both ends of a palindrome results in a bigger palindrome.
-/
inductive palindrome {α : Type u_1} : List α → Prop where
| nil : palindrome []
| singleton : ∀ (x : α), palindrome [x]
| cons_concat : ∀ (x : α) {l : List α}, palindrome l → palindrome (x :: (l ++ [x]))

namespace palindrome

theorem reverse_eq {α : Type u_1} {l : List α} (p : palindrome l) : list.reverse l = l := sorry

theorem of_reverse_eq {α : Type u_1} {l : List α} : list.reverse l = l → palindrome l := sorry

theorem iff_reverse_eq {α : Type u_1} {l : List α} : palindrome l ↔ list.reverse l = l :=
  { mp := reverse_eq, mpr := of_reverse_eq }

theorem append_reverse {α : Type u_1} (l : List α) : palindrome (l ++ list.reverse l) := sorry

protected instance decidable {α : Type u_1} [DecidableEq α] (l : List α) :
    Decidable (palindrome l) :=
  decidable_of_iff' (list.reverse l = l) iff_reverse_eq

end Mathlib


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

/-

# More on functions

Another question on the Imperial introduction to proof problem sheet on functions
is "If `f : X → Y` and `g : Y → Z` and `g ∘ f` is injective, is it true that `g` is injective?"
This is not true. A counterexample could be made by letting `X` and `Z` have one element,
and letting `Y` have two elements; `f` and `g` are then not hard to write down. Let's
see how to do this in Lean by making inductive types `X`, `Y` and `Z` and functions
`f` and `g` which give an explicit counterexample.

-/

-- Let X be {a}
inductive X : Type
| a : X

-- in fact the term of type X is called `X.a`.

-- Let Y be {b,c}
inductive Y : Type
| b : Y
| c : Y

inductive Z : Type
| d : Z

-- Define f by f(X.a)=Y.b
def f : X → Y
| X.a := Y.b

-- define g by g(Y.b)=g(Y.c)=Z.d
def g : Y → Z
| Y.b := Z.d
| Y.c := Z.d

-- Here `Z` only has one term, so if `z : Z` then `cases z` only produces one goal,
-- namely "what you get if you change `z` to `Z.d`".
example (z : Z) : z = Z.d :=
begin
  cases z,
  refl,
end

lemma Yb_ne_Yc : Y.b ≠ Y.c :=
begin
  intro h, -- x ≠ y is definitionally equal to (x = y) → false
  cases h, -- no cases when they're equal!
end

lemma gYb_eq_gYc : g Y.b = g Y.c :=
begin
  -- they're both definitionally `Z.d`
  refl,
end

open function

lemma gf_injective : injective (g ∘ f) :=
begin
  -- use `rintro` trick to do `intro, cases` at the same time
  rintro ⟨_⟩ ⟨_⟩ h,
  refl,
end

-- This is a question on the IUM (Imperial introduction to proof course) function problem sheet.
-- Recall that if you have a hypothesis of the form `h : ∀ A, ...`, then `specialize h X`
-- will specialize `h` to the specific case `A = X`.
example : ¬ (∀ A B C : Type, ∀ (φ : A → B) (ψ : B → C), injective (ψ ∘ φ) → injective ψ) :=
begin
  intro h,
  specialize h X Y Z f g gf_injective gYb_eq_gYc,
  cases h,
end

-- You might want to make some sublemmas first.
lemma gf_surjective : surjective (g ∘ f) :=
begin
  intro z,
  use X.a,
  cases z,
  refl,
end

-- This is another one.
example : ¬ (∀ A B C : Type, ∀ (φ : A → B) (ψ : B → C), surjective (ψ ∘ φ) → surjective φ) :=
begin
  intro h,
  specialize h X Y Z f g gf_surjective Y.c,
  rcases h with ⟨⟨_⟩, ⟨⟩⟩, -- this line does three `cases` at once.
end


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

import tactic
import data.real.basic
import analysis.calculus.parametric_integral

/-

# Basic calculus

-/

-- Thanks to Moritz Doll on the Zulip for writing this one!
/-- If `f : ℝ → ℝ` is differentiable at `x`, then the obvious induced function `ℝ → ℂ` is
also differentiable at `x`. -/
lemma complex.differentiable_at_coe {f : ℝ → ℝ} {x : ℝ } (hf : differentiable_at ℝ f x) :
  differentiable_at ℝ (λ y, (f y : ℂ)) x :=
begin
  apply complex.of_real_clm.differentiable_at.comp _ hf,
end

-- Here's a harder example
example (a : ℂ) (x : ℝ) : differentiable_at ℝ (λ (y : ℝ), complex.exp (-(a * ↑y ^ 2))) x :=
begin
  apply differentiable_at.comp,
  { apply differentiable_at.cexp,
    apply differentiable_at_id', },
  { apply differentiable_at.neg,
    apply differentiable_at.mul,
    { apply differentiable_at_const, },
    { norm_cast,
      apply complex.differentiable_at_coe,
      apply differentiable_at.pow,
      apply differentiable_at_id', } },
end

noncomputable def φ₁ : ℝ → ℝ × ℝ :=
λ x, (real.cos x, real.sin x)

example : cont_diff_on ℝ ⊤ (λ x, (real.cos x, real.sin x)) (set.Icc 0 1) :=
begin
  sorry,
end


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

! This file was ported from Lean 3 source module linear_algebra.tensor_product_basis
! leanprover-community/mathlib commit 4977fd9da637b6e0a805c1cf460c3a6b8df3f556
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.LinearAlgebra.DirectSum.Finsupp
import Mathbin.LinearAlgebra.FinsuppVectorSpace

/-!
# Bases and dimensionality of tensor products of modules

These can not go into `linear_algebra.tensor_product` since they depend on
`linear_algebra.finsupp_vector_space` which in turn imports `linear_algebra.tensor_product`.

-/

noncomputable section

open Set LinearMap Submodule

section CommRing

variable {R : Type _} {M : Type _} {N : Type _} {ι : Type _} {κ : Type _}

variable [CommRing R] [AddCommGroup M] [Module R M] [AddCommGroup N] [Module R N]

/-- If b : ι → M and c : κ → N are bases then so is λ i, b i.1 ⊗ₜ c i.2 : ι × κ → M ⊗ N. -/
def Basis.tensorProduct (b : Basis ι R M) (c : Basis κ R N) :
    Basis (ι × κ) R (TensorProduct R M N) :=
  Finsupp.basisSingleOne.map
    ((TensorProduct.congr b.repr c.repr).trans <|
        (finsuppTensorFinsupp R _ _ _ _).trans <|
          Finsupp.lcongr (Equiv.refl _) (TensorProduct.lid R R)).symm
#align basis.tensor_product Basis.tensorProduct

@[simp]
theorem Basis.tensorProduct_apply (b : Basis ι R M) (c : Basis κ R N) (i : ι) (j : κ) :
    Basis.tensorProduct b c (i, j) = b i ⊗ₜ c j := by simp [Basis.tensorProduct]
#align basis.tensor_product_apply Basis.tensorProduct_apply

theorem Basis.tensorProduct_apply' (b : Basis ι R M) (c : Basis κ R N) (i : ι × κ) :
    Basis.tensorProduct b c i = b i.1 ⊗ₜ c i.2 := by simp [Basis.tensorProduct]
#align basis.tensor_product_apply' Basis.tensorProduct_apply'

end CommRing


/-
Copyright (c) 2022 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser

! This file was ported from Lean 3 source module data.int.log
! leanprover-community/mathlib commit 1f0096e6caa61e9c849ec2adbd227e960e9dff58
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Order.Floor
import Mathlib.Algebra.Order.Field.Power
import Mathlib.Data.Nat.Log

/-!
# Integer logarithms in a field with respect to a natural base

This file defines two `ℤ`-valued analogs of the logarithm of `r : R` with base `b : ℕ`:

* `Int.log b r`: Lower logarithm, or floor **log**. Greatest `k` such that `↑b^k ≤ r`.
* `Int.clog b r`: Upper logarithm, or **c**eil **log**. Least `k` such that `r ≤ ↑b^k`.

Note that `Int.log` gives the position of the left-most non-zero digit:
```lean
#eval (Int.log 10 (0.09 : ℚ), Int.log 10 (0.10 : ℚ), Int.log 10 (0.11 : ℚ))
--    (-2,                    -1,                    -1)
#eval (Int.log 10 (9 : ℚ),    Int.log 10 (10 : ℚ),   Int.log 10 (11 : ℚ))
--    (0,                     1,                     1)
```
which means it can be used for computing digit expansions
```lean
import Data.Fin.VecNotation
import Mathlib.Data.Rat.Floor

def digits (b : ℕ) (q : ℚ) (n : ℕ) : ℕ :=
⌊q * ((b : ℚ) ^ (n - Int.log b q))⌋₊ % b

#eval digits 10 (1/7) ∘ ((↑) : Fin 8 → ℕ)
-- ![1, 4, 2, 8, 5, 7, 1, 4]
```

## Main results

* For `Int.log`:
  * `Int.zpow_log_le_self`, `Int.lt_zpow_succ_log_self`: the bounds formed by `Int.log`,
    `(b : R) ^ log b r ≤ r < (b : R) ^ (log b r + 1)`.
  * `Int.zpow_log_gi`: the galois coinsertion between `zpow` and `Int.log`.
* For `Int.clog`:
  * `Int.zpow_pred_clog_lt_self`, `Int.self_le_zpow_clog`: the bounds formed by `Int.clog`,
    `(b : R) ^ (clog b r - 1) < r ≤ (b : R) ^ clog b r`.
  * `Int.clog_zpow_gi`:  the galois insertion between `Int.clog` and `zpow`.
* `Int.neg_log_inv_eq_clog`, `Int.neg_clog_inv_eq_log`: the link between the two definitions.
-/

variable {R : Type _} [LinearOrderedSemifield R] [FloorSemiring R]

namespace Int

/-- The greatest power of `b` such that `b ^ log b r ≤ r`. -/
def log (b : ℕ) (r : R) : ℤ :=
  if 1 ≤ r then Nat.log b ⌊r⌋₊ else -Nat.clog b ⌈r⁻¹⌉₊
#align int.log Int.log

theorem log_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : log b r = Nat.log b ⌊r⌋₊ :=
  if_pos hr
#align int.log_of_one_le_right Int.log_of_one_le_right

theorem log_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : log b r = -Nat.clog b ⌈r⁻¹⌉₊ := by
  obtain rfl | hr := hr.eq_or_lt
  · rw [log, if_pos hr, inv_one, Nat.ceil_one, Nat.floor_one, Nat.log_one_right, Nat.clog_one_right,
      Int.ofNat_zero, neg_zero]
  · exact if_neg hr.not_le
#align int.log_of_right_le_one Int.log_of_right_le_one

@[simp, norm_cast]
theorem log_natCast (b : ℕ) (n : ℕ) : log b (n : R) = Nat.log b n := by
  cases n
  · simp [log_of_right_le_one]
  · rw [log_of_one_le_right, Nat.floor_coe]
    simp
#align int.log_nat_cast Int.log_natCast

theorem log_of_left_le_one {b : ℕ} (hb : b ≤ 1) (r : R) : log b r = 0 := by
  cases' le_total 1 r with h h
  · rw [log_of_one_le_right _ h, Nat.log_of_left_le_one hb, Int.ofNat_zero]
  · rw [log_of_right_le_one _ h, Nat.clog_of_left_le_one hb, Int.ofNat_zero, neg_zero]
#align int.log_of_left_le_one Int.log_of_left_le_one

theorem log_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : log b r = 0 := by
  rw [log_of_right_le_one _ (hr.trans zero_le_one),
    Nat.clog_of_right_le_one ((Nat.ceil_eq_zero.mpr <| inv_nonpos.2 hr).trans_le zero_le_one),
    Int.ofNat_zero, neg_zero]
#align int.log_of_right_le_zero Int.log_of_right_le_zero

theorem zpow_log_le_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) : (b : R) ^ log b r ≤ r := by
  cases' le_total 1 r with hr1 hr1
  · rw [log_of_one_le_right _ hr1]
    rw [zpow_ofNat, ← Nat.cast_pow, ← Nat.le_floor_iff hr.le]
    exact Nat.pow_log_le_self b (Nat.floor_pos.mpr hr1).ne'
  · rw [log_of_right_le_one _ hr1, zpow_neg, zpow_ofNat, ← Nat.cast_pow]
    exact inv_le_of_inv_le hr (Nat.ceil_le.1 <| Nat.le_pow_clog hb _)
#align int.zpow_log_le_self Int.zpow_log_le_self

theorem lt_zpow_succ_log_self {b : ℕ} (hb : 1 < b) (r : R) : r < (b : R) ^ (log b r + 1) := by
  cases' le_or_lt r 0 with hr hr
  · rw [log_of_right_le_zero _ hr, zero_add, zpow_one]
    exact hr.trans_lt (zero_lt_one.trans_le <| by exact_mod_cast hb.le)
  cases' le_or_lt 1 r with hr1 hr1
  · rw [log_of_one_le_right _ hr1]
    rw [Int.ofNat_add_one_out, zpow_ofNat, ← Nat.cast_pow]
    apply Nat.lt_of_floor_lt
    exact Nat.lt_pow_succ_log_self hb _
  · rw [log_of_right_le_one _ hr1.le]
    have hcri : 1 < r⁻¹ := one_lt_inv hr hr1
    have : 1 ≤ Nat.clog b ⌈r⁻¹⌉₊ :=
      Nat.succ_le_of_lt (Nat.clog_pos hb <| Nat.one_lt_cast.1 <| hcri.trans_le (Nat.le_ceil _))
    rw [neg_add_eq_sub, ← neg_sub, ← Int.ofNat_one, ← Int.ofNat_sub this, zpow_neg, zpow_ofNat,
      lt_inv hr (pow_pos (Nat.cast_pos.mpr <| zero_lt_one.trans hb) _), ← Nat.cast_pow]
    refine' Nat.lt_ceil.1 _
    exact Nat.pow_pred_clog_lt_self hb <| Nat.one_lt_cast.1 <| hcri.trans_le <| Nat.le_ceil _
#align int.lt_zpow_succ_log_self Int.lt_zpow_succ_log_self

@[simp]
theorem log_zero_right (b : ℕ) : log b (0 : R) = 0 :=
  log_of_right_le_zero b le_rfl
#align int.log_zero_right Int.log_zero_right

@[simp]
theorem log_one_right (b : ℕ) : log b (1 : R) = 0 := by
  rw [log_of_one_le_right _ le_rfl, Nat.floor_one, Nat.log_one_right, Int.ofNat_zero]
#align int.log_one_right Int.log_one_right

-- Porting note: needed to replace b ^ z with (b : R) ^ z in the below
theorem log_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : log b ((b : R) ^ z : R) = z := by
  obtain ⟨n, rfl | rfl⟩ := Int.eq_nat_or_neg z
  · rw [log_of_one_le_right _ (one_le_zpow_of_nonneg _ <| Int.coe_nat_nonneg _), zpow_ofNat, ←
      Nat.cast_pow, Nat.floor_coe, Nat.log_pow hb]
    exact_mod_cast hb.le
  · rw [log_of_right_le_one _ (zpow_le_one_of_nonpos _ <| neg_nonpos.mpr (Int.coe_nat_nonneg _)),
      zpow_neg, inv_inv, zpow_ofNat, ← Nat.cast_pow, Nat.ceil_natCast, Nat.clog_pow _ _ hb]
    exact_mod_cast hb.le
#align int.log_zpow Int.log_zpow

@[mono]
theorem log_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) : log b r₁ ≤ log b r₂ := by
  cases' le_or_lt b 1 with hb hb
  · rw [log_of_left_le_one hb, log_of_left_le_one hb]
  cases' le_total r₁ 1 with h₁ h₁ <;> cases' le_total r₂ 1 with h₂ h₂
  · rw [log_of_right_le_one _ h₁, log_of_right_le_one _ h₂, neg_le_neg_iff, Int.ofNat_le]
    exact Nat.clog_mono_right _ (Nat.ceil_mono <| inv_le_inv_of_le h₀ h)
  · rw [log_of_right_le_one _ h₁, log_of_one_le_right _ h₂]
    exact (neg_nonpos.mpr (Int.coe_nat_nonneg _)).trans (Int.coe_nat_nonneg _)
  · obtain rfl := le_antisymm h (h₂.trans h₁)
    rfl
  · rw [log_of_one_le_right _ h₁, log_of_one_le_right _ h₂, Int.ofNat_le]
    exact Nat.log_mono_right (Nat.floor_mono h)
#align int.log_mono_right Int.log_mono_right

variable (R)

/-- Over suitable subtypes, `zpow` and `Int.log` form a galois coinsertion -/
def zpowLogGi {b : ℕ} (hb : 1 < b) :
    GaloisCoinsertion
      (fun z : ℤ =>
        Subtype.mk ((b : R) ^ z) <| zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z)
      fun r : Set.Ioi (0 : R) => Int.log b (r : R) :=
  GaloisCoinsertion.monotoneIntro (fun r₁ r₂ => log_mono_right r₁.2)
    (fun z₁ z₂ hz => Subtype.coe_le_coe.mp <| (zpow_strictMono <| by exact_mod_cast hb).monotone hz)
    (fun r => Subtype.coe_le_coe.mp <| zpow_log_le_self hb r.2) fun _ => log_zpow (R := R) hb _
#align int.zpow_log_gi Int.zpowLogGi

variable {R}

/-- `zpow b` and `Int.log b` (almost) form a Galois connection. -/
theorem lt_zpow_iff_log_lt {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
    r < (b : R) ^ x ↔ log b r < x :=
  @GaloisConnection.lt_iff_lt _ _ _ _ _ _ (zpowLogGi R hb).gc x ⟨r, hr⟩
#align int.lt_zpow_iff_log_lt Int.lt_zpow_iff_log_lt

/-- `zpow b` and `Int.log b` (almost) form a Galois connection. -/
theorem zpow_le_iff_le_log {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
    (b : R) ^ x ≤ r ↔ x ≤ log b r :=
  @GaloisConnection.le_iff_le _ _ _ _ _ _ (zpowLogGi R hb).gc x ⟨r, hr⟩
#align int.zpow_le_iff_le_log Int.zpow_le_iff_le_log

/-- The least power of `b` such that `r ≤ b ^ log b r`. -/
def clog (b : ℕ) (r : R) : ℤ :=
  if 1 ≤ r then Nat.clog b ⌈r⌉₊ else -Nat.log b ⌊r⁻¹⌋₊
#align int.clog Int.clog

theorem clog_of_one_le_right (b : ℕ) {r : R} (hr : 1 ≤ r) : clog b r = Nat.clog b ⌈r⌉₊ :=
  if_pos hr
#align int.clog_of_one_le_right Int.clog_of_one_le_right

theorem clog_of_right_le_one (b : ℕ) {r : R} (hr : r ≤ 1) : clog b r = -Nat.log b ⌊r⁻¹⌋₊ := by
  obtain rfl | hr := hr.eq_or_lt
  · rw [clog, if_pos hr, inv_one, Nat.ceil_one, Nat.floor_one, Nat.log_one_right,
      Nat.clog_one_right, Int.ofNat_zero, neg_zero]
  · exact if_neg hr.not_le
#align int.clog_of_right_le_one Int.clog_of_right_le_one

theorem clog_of_right_le_zero (b : ℕ) {r : R} (hr : r ≤ 0) : clog b r = 0 := by
  rw [clog, if_neg (hr.trans_lt zero_lt_one).not_le, neg_eq_zero, Int.coe_nat_eq_zero,
    Nat.log_eq_zero_iff]
  cases' le_or_lt b 1 with hb hb
  · exact Or.inr hb
  · refine' Or.inl (lt_of_le_of_lt _ hb)
    exact Nat.floor_le_one_of_le_one ((inv_nonpos.2 hr).trans zero_le_one)
#align int.clog_of_right_le_zero Int.clog_of_right_le_zero

@[simp]
theorem clog_inv (b : ℕ) (r : R) : clog b r⁻¹ = -log b r := by
  cases' lt_or_le 0 r with hrp hrp
  · obtain hr | hr := le_total 1 r
    · rw [clog_of_right_le_one _ (inv_le_one hr), log_of_one_le_right _ hr, inv_inv]
    · rw [clog_of_one_le_right _ (one_le_inv hrp hr), log_of_right_le_one _ hr, neg_neg]
  · rw [clog_of_right_le_zero _ (inv_nonpos.mpr hrp), log_of_right_le_zero _ hrp, neg_zero]
#align int.clog_inv Int.clog_inv

@[simp]
theorem log_inv (b : ℕ) (r : R) : log b r⁻¹ = -clog b r := by
  rw [← inv_inv r, clog_inv, neg_neg, inv_inv]
#align int.log_inv Int.log_inv

-- note this is useful for writing in reverse
theorem neg_log_inv_eq_clog (b : ℕ) (r : R) : -log b r⁻¹ = clog b r := by rw [log_inv, neg_neg]
#align int.neg_log_inv_eq_clog Int.neg_log_inv_eq_clog

theorem neg_clog_inv_eq_log (b : ℕ) (r : R) : -clog b r⁻¹ = log b r := by rw [clog_inv, neg_neg]
#align int.neg_clog_inv_eq_log Int.neg_clog_inv_eq_log

@[simp, norm_cast]
theorem clog_natCast (b : ℕ) (n : ℕ) : clog b (n : R) = Nat.clog b n := by
  cases' n with n
  · simp [clog_of_right_le_one]
  · rw [clog_of_one_le_right, (Nat.ceil_eq_iff (Nat.succ_ne_zero n)).mpr] <;> simp
#align int.clog_nat_cast Int.clog_natCast

theorem self_le_zpow_clog {b : ℕ} (hb : 1 < b) (r : R) : r ≤ (b : R) ^ clog b r := by
  cases' le_or_lt r 0 with hr hr
  · rw [clog_of_right_le_zero _ hr, zpow_zero]
    exact hr.trans zero_le_one
  rw [← neg_log_inv_eq_clog, zpow_neg, le_inv hr (zpow_pos_of_pos _ _)]
  · exact zpow_log_le_self hb (inv_pos.mpr hr)
  · exact Nat.cast_pos.mpr (zero_le_one.trans_lt hb)
#align int.self_le_zpow_clog Int.self_le_zpow_clog

theorem zpow_pred_clog_lt_self {b : ℕ} {r : R} (hb : 1 < b) (hr : 0 < r) :
    (b : R) ^ (clog b r - 1) < r := by
  rw [← neg_log_inv_eq_clog, ← neg_add', zpow_neg, inv_lt _ hr]
  · exact lt_zpow_succ_log_self hb _
  · exact zpow_pos_of_pos (Nat.cast_pos.mpr <| zero_le_one.trans_lt hb) _
#align int.zpow_pred_clog_lt_self Int.zpow_pred_clog_lt_self

@[simp]
theorem clog_zero_right (b : ℕ) : clog b (0 : R) = 0 :=
  clog_of_right_le_zero _ le_rfl
#align int.clog_zero_right Int.clog_zero_right

@[simp]
theorem clog_one_right (b : ℕ) : clog b (1 : R) = 0 := by
  rw [clog_of_one_le_right _ le_rfl, Nat.ceil_one, Nat.clog_one_right, Int.ofNat_zero]
#align int.clog_one_right Int.clog_one_right

-- Porting note: needed to replace b ^ z with (b : R) ^ z in the below
theorem clog_zpow {b : ℕ} (hb : 1 < b) (z : ℤ) : clog b ((b : R) ^ z : R) = z := by
  rw [← neg_log_inv_eq_clog, ← zpow_neg, log_zpow hb, neg_neg]
#align int.clog_zpow Int.clog_zpow

@[mono]
theorem clog_mono_right {b : ℕ} {r₁ r₂ : R} (h₀ : 0 < r₁) (h : r₁ ≤ r₂) :
    clog b r₁ ≤ clog b r₂ := by
  rw [← neg_log_inv_eq_clog, ← neg_log_inv_eq_clog, neg_le_neg_iff]
  exact log_mono_right (inv_pos.mpr <| h₀.trans_le h) (inv_le_inv_of_le h₀ h)
#align int.clog_mono_right Int.clog_mono_right

variable (R)

/-- Over suitable subtypes, `Int.clog` and `zpow` form a galois insertion -/
def clogZpowGi {b : ℕ} (hb : 1 < b) :
    GaloisInsertion (fun r : Set.Ioi (0 : R) => Int.clog b (r : R)) fun z : ℤ =>
      ⟨(b : R) ^ z, zpow_pos_of_pos (by exact_mod_cast zero_lt_one.trans hb) z⟩ :=
  GaloisInsertion.monotoneIntro
    (fun z₁ z₂ hz => Subtype.coe_le_coe.mp <| (zpow_strictMono <| by exact_mod_cast hb).monotone hz)
    (fun r₁ r₂ => clog_mono_right r₁.2)
    (fun r => Subtype.coe_le_coe.mp <| self_le_zpow_clog hb _) fun _ => clog_zpow (R := R) hb _
#align int.clog_zpow_gi Int.clogZpowGi

variable {R}

/-- `Int.clog b` and `zpow b` (almost) form a Galois connection. -/
theorem zpow_lt_iff_lt_clog {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
    (b : R) ^ x < r ↔ x < clog b r :=
  (@GaloisConnection.lt_iff_lt _ _ _ _ _ _ (clogZpowGi R hb).gc ⟨r, hr⟩ x).symm
#align int.zpow_lt_iff_lt_clog Int.zpow_lt_iff_lt_clog

/-- `Int.clog b` and `zpow b` (almost) form a Galois connection. -/
theorem le_zpow_iff_clog_le {b : ℕ} (hb : 1 < b) {x : ℤ} {r : R} (hr : 0 < r) :
    r ≤ (b : R) ^ x ↔ clog b r ≤ x :=
  (@GaloisConnection.le_iff_le _ _ _ _ _ _ (clogZpowGi R hb).gc ⟨r, hr⟩ x).symm
#align int.le_zpow_iff_clog_le Int.le_zpow_iff_clog_le

end Int


import Playground.Data.BinaryTree.BST

section
  namespace List
  variable {type} [LinearOrder type]

  def toBST (list : List type) : Data.BinaryTree.BST type
  := match list with
  | [] => .nil
  | a :: list => list.toBST.insert a

  def bstSort (list : List type) : List type := list.toBST.val.infixList

  theorem bstSort_isSorted (list : List type) : list.bstSort.IsSorted
  := (list.toBST.val.IsBST_iff_infixList_IsSorted).mp list.toBST.property

  theorem bstSort_Perm (list : List type) : list.bstSort ~ list
  := match list with
  | [] => Perm.nil
  | a :: list =>
    (list.toBST.val.insertAsInBST_infixList_Perm_cons_infixList a).trans
      (list.bstSort_Perm.cons a)

  end List
end

#eval [3, 6, 3, 2, 10, 1].bstSort


/-
Copyright (c) 2022 Jireh Loreaux. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jireh Loreaux
-/
import analysis.normed_space.units
import algebra.algebra.spectrum
import topology.continuous_function.algebra

/-!
# Units of continuous functions

This file concerns itself with `C(X, M)ˣ` and `C(X, Mˣ)` when `X` is a topological space
and `M` has some monoid structure compatible with its topology.
-/

variables {X M R 𝕜 : Type*} [topological_space X]

namespace continuous_map

section monoid

variables [monoid M] [topological_space M] [has_continuous_mul M]

/-- Equivalence between continuous maps into the units of a monoid with continuous multiplication
and the units of the monoid of continuous maps. -/
@[to_additive "Equivalence between continuous maps into the additive units of an additive monoid
with continuous addition and the additive units of the additive monoid of continuous maps.", simps]
def units_lift : C(X, Mˣ) ≃ C(X, M)ˣ :=
{ to_fun := λ f,
  { val := ⟨λ x, f x, units.continuous_coe.comp f.continuous⟩,
    inv := ⟨λ x, ↑(f x)⁻¹, units.continuous_coe.comp (continuous_inv.comp f.continuous)⟩,
    val_inv := ext $ λ x, units.mul_inv _,
    inv_val := ext $ λ x, units.inv_mul _ },
  inv_fun := λ f,
  { to_fun := λ x, ⟨f x, f⁻¹ x, continuous_map.congr_fun f.mul_inv x,
                                continuous_map.congr_fun f.inv_mul x⟩,
    continuous_to_fun := continuous_induced_rng.2 $ continuous.prod_mk (f : C(X, M)).continuous
      $ mul_opposite.continuous_op.comp (↑f⁻¹ : C(X, M)).continuous },
  left_inv := λ f, by { ext, refl },
  right_inv := λ f, by { ext, refl } }

end monoid

section normed_ring

variables [normed_ring R] [complete_space R]

lemma _root_.normed_ring.is_unit_unit_continuous {f : C(X, R)} (h : ∀ x, is_unit (f x)) :
  continuous (λ x, (h x).unit) :=
begin
  refine continuous_induced_rng.2 (continuous.prod_mk f.continuous
    (mul_opposite.continuous_op.comp (continuous_iff_continuous_at.mpr (λ x, _)))),
  have := normed_ring.inverse_continuous_at (h x).unit,
  simp only [←ring.inverse_unit, is_unit.unit_spec, ←function.comp_apply] at this ⊢,
  exact this.comp (f.continuous_at x),
end

/-- Construct a continuous map into the group of units of a normed ring from a function into the
normed ring and a proof that every element of the range is a unit. -/
@[simps]
noncomputable def units_of_forall_is_unit {f : C(X, R)} (h : ∀ x, is_unit (f x)) : C(X, Rˣ) :=
{ to_fun := λ x, (h x).unit,
  continuous_to_fun :=  normed_ring.is_unit_unit_continuous h }

instance can_lift : can_lift C(X, R) C(X, Rˣ)
  (λ f, ⟨λ x, f x, units.continuous_coe.comp f.continuous⟩) (λ f, ∀ x, is_unit (f x)) :=
{ prf := λ f h, ⟨units_of_forall_is_unit h, by { ext, refl }⟩ }

lemma is_unit_iff_forall_is_unit (f : C(X, R)) :
  is_unit f ↔ ∀ x, is_unit (f x) :=
iff.intro (λ h, λ x, ⟨units_lift.symm h.unit x, rfl⟩)
  (λ h, ⟨(units_of_forall_is_unit h).units_lift, by { ext, refl }⟩)

end normed_ring

section normed_field

variables [normed_field 𝕜] [complete_space 𝕜]

lemma is_unit_iff_forall_ne_zero (f : C(X, 𝕜)) :
  is_unit f ↔ ∀ x, f x ≠ 0 :=
by simp_rw [f.is_unit_iff_forall_is_unit, is_unit_iff_ne_zero]

lemma spectrum_eq_range (f : C(X, 𝕜)) :
  spectrum 𝕜 f = set.range f :=
begin
  ext,
  simp only [spectrum.mem_iff, is_unit_iff_forall_ne_zero, not_forall, coe_sub,
    pi.sub_apply, algebra_map_apply, algebra.id.smul_eq_mul, mul_one, not_not, set.mem_range,
    sub_eq_zero, @eq_comm _ x _]
end

end normed_field

end continuous_map


import analysis.topology.topological_space
import analysis.topology.continuity
import data.set.basic
import data.bool
import logic.basic

open set filter lattice classical
local attribute [instance] prop_decidable

universe u
variables {α : Type u} {β : Type u} {γ : Type u} {δ : Type u}

def is_clopen [t : topological_space α] (s : set α) : Prop := is_open s ∧ is_closed s

/- For subsets, connected def needs to
consider open sets in the subspace topology. -/

def is_open_in_subspace [t : topological_space α] (A : set α) (V : set α) : Prop := ∃ U, V = A ∩ U ∧ is_open U

def is_connected [t : topological_space α] (A : set α) : Prop := ∀ U V : set α,
is_open_in_subspace A U ∧ is_open_in_subspace A V → ¬( U ∪ V = A ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)

def is_separated [topological_space α] (s t : set α) : Prop := (closure s) ∩ t = ∅ ∧ s ∩ (closure t) = ∅

class connected_space (α : Type u) extends topological_space α :=
    (clopen_trivial : (∀ s : set α, is_clopen s → (s = univ ∨ s = ∅)))

class discrete_space α extends topological_space α :=
(discreteness : ∀ U : set α, is_open U)

def X : topological_space bool := by apply_instance

class indiscrete_space α extends topological_space α :=
(indiscreteness : ∀ U : set α, ¬is_open U)

class discrete_connected_space α extends connected_space α :=
(discreteness : ∀ U : set α, is_open U)

class indiscrete_connected_space α extends connected_space α :=
(indiscreteness : ∀ U : set α, ¬is_open U)

-----------------------------------------------------------------
-- Some useful lemmas

lemma eq_compl_iff_compl_eq {A B : set α} : A = -B ↔ B = -A :=
by {apply iff.intro, intro H1, rw [H1,compl_compl], intro H2, rw [H2,compl_compl]}

lemma disjoint_and_union_univ_imp_compl {A B : set α} (hU : A ∪ B = univ)
(hE : A ∩ B = ∅) : A = -B :=
  set.subset.antisymm
    (subset_compl_iff_disjoint.2 hE : A ⊆ -B)
    (compl_subset_iff_union.2 $ set.union_comm A B ▸ hU : -B ⊆ A)

lemma neq_empty_imp_empty_to_union {A B : set α} (H : ¬A = ∅ → B = ∅) :
A = ∅ ∨ B = ∅ :=
begin
  by_contradiction hc, rw [not_or_distrib] at hc,
  by exact absurd (H hc.1) hc.2,
end

lemma in_right_union_univ {A B : set α} {x : α} (H : A ∪ B = univ) : x ∉ A → x ∈ B :=
begin
  intro hx,
  by exact or.elim ((mem_union _ _ _).mp (eq_univ_iff_forall.mp H x))
    (assume a1, by exact absurd a1 hx)
    (assume a2, by exact a2),
end

lemma in_left_union_univ {A B : set α} {x : α} (H : A ∪ B = univ) : x ∉ B → x ∈ A :=
begin
  intro hx,
  by exact or.elim ((mem_union _ _ _).mp (eq_univ_iff_forall.mp H x))
    (assume a2, by exact a2)
    (assume a1, by exact absurd a1 hx),
end

lemma subset_def_iff {A B : set α} : (A ⊆ B) ↔ ∀ x, x ∈ A → x ∈ B :=
by {apply iff.intro, intro h1, rwa [←subset_def], intro h2, rwa [subset_def]}

lemma nmem_compl_iff (s : set α) (x : α) : x ∉ -s ↔ x ∈ s := by simp

lemma not_in_right_union_empty {A B : set α} {x : α} (H : A ∩ B = ∅) : x ∈ A → x ∉ B :=
by {intros hx hc, exact absurd H (ne_empty_of_mem (mem_inter hx hc))}

lemma not_in_left_union_empty {A B : set α} {x : α} (H : A ∩ B = ∅) : x ∈ B → x ∉ A :=
by {intros hx hc, exact absurd H (ne_empty_of_mem (mem_inter hc hx))}

lemma mem_inter_empty_left {A B : set α} {x : α} (H1 : x ∈ A) (H2 : A ∩ B = ∅) :
x ∉ B := by {by_contradiction HC, exact absurd H2 (ne_empty_of_mem (mem_inter H1 HC))}

lemma mem_inter_empty_right {A B : set α} {x : α} (H1 : x ∈ B) (H2 : A ∩ B = ∅) :
x ∉ A := by {by_contradiction HC, exact absurd H2 (ne_empty_of_mem (mem_inter HC H1))}

lemma disjoint_compl_imp_subset {A B : set α} (H : A ∩ -B = ∅) : A ⊆ B :=
subset_def_iff.mpr (assume (x : α) (hA : x ∈ A), (nmem_compl_iff _ _ ).mp (mem_inter_empty_left hA H))

lemma eq_union_of_inter_if_subseteq {A B C : set α} (H1 : A ⊆ B ∪ C) : A = (A ∩ B) ∪ (A ∩ C)
:= by {rw [←inter_distrib_left,inter_eq_self_of_subset_left H1]}

lemma inter_empty_distrib {A B C : set α} (H1 : B ∩ C = ∅) : (A ∩ B) ∩ (A ∩ C) = ∅
:= by {rw [inter_left_comm,←inter_assoc,←inter_assoc], simp, rw [inter_assoc,H1], simp}

lemma union_left_inter_distrib {A B C D : set α} (H : A = (B ∪ C) ∩ D) : B ∩ A = B ∩ D
:= by {rw [H,←inter_assoc, inter_eq_self_of_subset_left (subset_union_left _ _ )]}

lemma union_right_inter_distrib {A B C D : set α} (H : A = (B ∪ C) ∩ D) : C ∩ A = C ∩ D
:= by {rw [H,←inter_assoc, inter_eq_self_of_subset_left (subset_union_right _ _ )]}

@[simp] lemma inter_union_self_left {A B : set α} : A ∩ (A ∪ B) = A :=
ext (assume x, iff.intro (assume h1, mem_of_mem_inter_left h1) (assume h2, mem_inter h2 (mem_union_left _ h2)))

@[simp] lemma inter_union_self_right {A B : set α} : B ∩ (A ∪ B) = B :=
ext (assume x, iff.intro (assume h1, mem_of_mem_inter_left h1) (assume h2, mem_inter h2 (mem_union_right _ h2)))

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

theorem open_imp_inter_open_in_subspace [topological_space α] {s t v : set α} :
is_open t → is_open_in_subspace s (t ∩ s) :=
begin
  intro h1,
  show is_open_in_subspace s (t ∩ s),
    {rw is_open_in_subspace, by exact exists.intro t ⟨inter_comm t s, h1⟩},
end

lemma sub_union_open_to_open_inter_left [topological_space α] {A B C : set α}
(H1 : is_open_in_subspace (A ∪ B) C) : is_open_in_subspace A (A ∩ C) :=
by {cases H1 with T HT, exact ⟨T, ⟨union_left_inter_distrib HT.1, HT.2⟩⟩}

lemma sub_union_open_to_open_inter_right [topological_space α] {A B C : set α}
(H1 : is_open_in_subspace (A ∪ B) C) : is_open_in_subspace B (B ∩ C) :=
by {cases H1 with T HT, exact ⟨T, ⟨union_right_inter_distrib HT.1, HT.2⟩⟩}

lemma closure_subset_imp_eq [topological_space α] {s : set α}
(H : closure s ⊆ s) : closure s = s := by exact subset.antisymm H subset_closure

lemma subset_interior_imp_eq [topological_space α] {s : set α}
(H : s ⊆ interior s) : interior s = s := by exact subset.antisymm interior_subset H

theorem closure_eq_interior_iff_clopen [topological_space α] :
(∀ s : set α, closure s = interior s ↔ is_clopen s) :=
begin
  intro s,
    have A := subset_closure, have B := interior_subset,
  apply iff.intro, assume h1, rw [h1] at A, rw [←h1] at B,
    rw is_clopen, exact ⟨interior_eq_iff_open.mp (subset_interior_imp_eq A),
    closure_eq_iff_is_closed.mp (closure_subset_imp_eq B)⟩,
  assume H1, rw [closure_eq_iff_is_closed.mpr H1.2, interior_eq_iff_open.mpr H1.1],
end

theorem empty_frontier_iff_clopen [topological_space α] :
(∀ s : set α, frontier s = ∅ ↔ is_clopen s) :=
begin
  intro s, apply iff.intro,
    rw [frontier_eq_closure_inter_closure],
    intro h, simp at h,
    by exact (closure_eq_interior_iff_clopen _ ).mp (subset.antisymm (disjoint_compl_imp_subset h) interior_subset_closure),
  intro H1, rw [frontier, closure_eq_iff_is_closed.mpr H1.2, interior_eq_iff_open.mpr H1.1, diff_eq], simp,
end

lemma components_of_separation_clopen [topological_space α]
{U1 U2 : set α} (hu1 : is_open U1) (hu2 : is_open U2) : (U1 ∪ U2 = univ ∧ U1 ∩ U2 = ∅ ∧ U1 ≠ ∅ ∧ U2 ≠ ∅) →
is_clopen U1 ∧ is_clopen U2 :=
begin
  intro H,
  exact ⟨⟨hu1, by {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl H.1 H.2.1))] at hu2, rwa is_closed}⟩,
    ⟨hu2, by {rw disjoint_and_union_univ_imp_compl H.1 H.2.1 at hu1, rwa is_closed}⟩⟩,
end

lemma components_of_separation_clopen2 [topological_space α]
{U1 U2 : set α} (hu1 : is_open U1) (hu2 : is_open U2) (hunion : U1 ∪ U2 = univ)
(hinter : U1 ∩ U2 = ∅) (hnon1 : U1 ≠ ∅) (hnon2 : U2 ≠ ∅) : is_clopen U1 ∧ is_clopen U2 :=
⟨⟨hu1, by {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl hunion hinter))] at hu2, rwa is_closed}⟩,
  ⟨hu2, by {rw disjoint_and_union_univ_imp_compl hunion hinter at hu1, rwa is_closed}⟩⟩

lemma open_separation_to_closed [topological_space α]
{U1 U2 : set α} (hu1 : is_open U1) (hu2 : is_open U2) : (U1 ∪ U2 = univ ∧ U1 ∩ U2 = ∅ ∧ U1 ≠ ∅ ∧ U2 ≠ ∅) →
is_closed U1 ∧ is_closed U2 :=
begin
  intro H,
  exact ⟨by {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl H.1 H.2.1))] at hu2, rwa is_closed},
  by {rw disjoint_and_union_univ_imp_compl H.1 H.2.1 at hu1, rwa is_closed}⟩
end

lemma open_separation_to_closed2 [topological_space α]
  {U1 U2 : set α} (hu1 : is_open U1) (hu2 : is_open U2) (hunion : U1 ∪ U2 = univ)
  (hinter : U1 ∩ U2 = ∅) (hnon1 : U1 ≠ ∅) (hnon2 :U2 ≠ ∅) : is_closed U1 ∧ is_closed U2 :=
begin
  have hu1c : is_closed U1, {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl hunion hinter))] at hu2, rwa is_closed},
  have hu2c : is_closed U2, {rw disjoint_and_union_univ_imp_compl hunion hinter at hu1, rwa is_closed},
  exact ⟨hu1c,hu2c⟩,
end

lemma closed_separation_to_open [topological_space α]
{U1 U2 : set α} (hu1 : is_closed U1) (hu2 : is_closed U2) : (U1 ∪ U2 = univ ∧ U1 ∩ U2 = ∅ ∧ U1 ≠ ∅ ∧ U2 ≠ ∅) →
is_open U1 ∧ is_open U2 :=
begin
  intro H,
    exact ⟨by {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl H.1 H.2.1))] at hu2, rwa is_closed at hu2, simp at hu2, assumption},
      by {rw disjoint_and_union_univ_imp_compl H.1 H.2.1 at hu1, rwa is_closed at hu1, simp at hu1, assumption}⟩,
end

lemma closed_separation_to_open2 [topological_space α]
{U1 U2 : set α} (hu1 : is_closed U1) (hu2 : is_closed U2) (hunion : U1 ∪ U2 = univ)
(hinter : U1 ∩ U2 = ∅) (hnon1 : U1 ≠ ∅) (hnon2 :U2 ≠ ∅) : is_open U1 ∧ is_open U2 :=
begin
  exact ⟨by {rw [←eq_comm.mp (eq_compl_iff_compl_eq.mp (disjoint_and_union_univ_imp_compl hunion hinter))] at hu2, rwa is_closed at hu2, simp at hu2, assumption},
    by {rw disjoint_and_union_univ_imp_compl hunion hinter at hu1, rwa is_closed at hu1, simp at hu1, assumption}⟩,
end

lemma no_open_sep_iff_no_closed_sep [topological_space α] :
(∀ U1 U2: set α, is_open U1 ∧ is_open U2 → ¬( U1 ∪ U2 = univ ∧ U1 ∩ U2 = ∅ ∧ U1 ≠ ∅ ∧ U2 ≠ ∅))
↔ (∀ V1 V2 : set α, is_closed V1 ∧ is_closed V2 → ¬( V1 ∪ V2 = univ ∧ V1 ∩ V2 = ∅ ∧ V1 ≠ ∅ ∧ V2 ≠ ∅)) :=
begin
  apply iff.intro,
    intros H1 V1 V2 h1 c1,
    apply absurd c1 (H1 V1 V2 (closed_separation_to_open h1.1 h1.2 c1)),
  intros H1 U1 U2 h1 c1,
  apply absurd c1 (H1 U1 U2 (open_separation_to_closed h1.1 h1.2 c1)),
end

lemma closure_union_closure_compl_eq_univ [topological_space α] {A : set α} :
closure A ∪ closure (-A) = univ :=
  by {simp, rw [←compl_subset_iff_union], rw [compl_subset_comm, compl_compl],
  exact interior_subset_closure}

lemma trivial_imp_empty_frontier [topological_space α] {A : set α} :
A = univ ∨ A = ∅ → frontier A = ∅ :=
begin
  intro H,
  by exact or.elim H
    (assume a1, show closure A \ interior A = ∅, {rw a1, simp, rw [←compl_eq_univ_diff _], simp})
    (assume a1, show closure A \ interior A = ∅, {rw a1, by simp}),
end

lemma trivial_imp_empty_frontier2 [topological_space α] {A : set α}
(H : A = univ ∨ A = ∅) : frontier A = ∅ :=
  or.elim H
    (assume a1, show closure A \ interior A = ∅, {rw a1, simp, rw [←compl_eq_univ_diff _], simp})
    (assume a1, show closure A \ interior A = ∅, {rw a1, by simp})

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

-- (1) → (2)
lemma one_implies_two [topological_space α] : (∀ U V : set α, is_open U ∧ is_open V →
¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) → (∀ U V : set α,
is_closed U ∧ is_closed V → ¬( U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅ )) :=
no_open_sep_iff_no_closed_sep.mp

-- (2) → (3)
lemma two_implies_three [topological_space α] : (∀ U V : set α, is_closed U ∧ is_closed V →
¬( U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅ )) → (∀ s : set α, frontier s = ∅ ↔ s = univ ∨ s = ∅) :=
begin
  intros h1 s, apply iff.intro, assume h2,
    have h3 : closure s ∩ closure (- s) = ∅, {rwa frontier_eq_closure_inter_closure at h2},
    have h4 := closure_union_closure_compl_eq_univ,
    have h5 := h1 (closure s) (closure (-s)) ⟨by simp, by simp⟩, simp at h5 h4 h3,
    by exact or.elim (neq_empty_imp_empty_to_union (h5 h4 h3))
      (assume a1, have a2 : s ⊆ ∅, {rw [←a1], by exact subset_closure},
        by exact or.inr (eq_empty_of_subset_empty a2))
      (assume a1,
        have a2 : -s = ∅ := eq_empty_of_subset_empty
          (by {rw [←closure_compl_eq] at a1, rw [←a1], by exact subset_closure}),
        by exact or.inl
          (by {have b1 : -(-s) = -∅, {rw [←a2]}, simp at b1, assumption})),
  by exact trivial_imp_empty_frontier,
end

-- (3) → (4)
lemma three_implies_four [topological_space α] : (∀ s : set α, frontier s = ∅ ↔ s = univ ∨ s = ∅)
→  (∀ s : set α, is_clopen s → (s = univ ∨ s = ∅)) :=
by {intros H s, rw [←empty_frontier_iff_clopen], by exact (H s).mp}

-- (4) → (5)
lemma four_implies_five [topological_space α] : (∀ s : set α, is_clopen s → (s = univ ∨ s = ∅))
→ (∀ U V : set α, is_separated U V → ¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) :=
begin
  intros H U V hsep hc, rw is_separated at hsep,
  have hU := hsep.1,
  have hV := hsep.2,

  have h1 : V = -(closure U),
    {have a1 : closure U ∪ V = univ,
      {have b1 : U ⊆ closure U ∪ V,
        {have c1 : U ⊆ closure U := subset_closure,
        have c2 : closure U ⊆ closure U ∪ V := subset_union_left (closure U) V,
        by exact subset.trans c1 c2},
      have b2 : U ∪ V ⊆ closure U ∪ V :=
        (union_subset_iff.mpr ⟨b1, subset_union_right (closure U) V⟩),
      rwa [hc.1] at b2,
      by exact eq_univ_of_univ_subset b2},
    {rw [union_comm _ _] at a1, rw [inter_comm _ _] at hU,
     by exact disjoint_and_union_univ_imp_compl a1 hU}},

  have h2 : U = -(closure V),
    {have a1 : U ∪ closure V = univ,
      {have b1 : V ⊆ U ∪ closure V,
        {have c1 : V ⊆ closure V := subset_closure,
        have c2 : closure V ⊆ U ∪ closure V := subset_union_right U (closure V),
        by exact subset.trans c1 c2},
      have b2 : U ∪ V ⊆ U ∪ closure V :=
        (union_subset_iff.mpr ⟨subset_union_left U (closure V), b1⟩),
      rwa [hc.1] at b2, by exact eq_univ_of_univ_subset b2},
    by exact disjoint_and_union_univ_imp_compl a1 hV},

  have h3 : is_open U,
    {have a1 : is_closed (closure V) := is_closed_closure, rw h2, rwa is_closed at a1},
  have h4 : is_open V,
    {have a1 : is_closed (closure U) := is_closed_closure, rw h1, rwa is_closed at a1},

  have h5 : U = -V := disjoint_and_union_univ_imp_compl hc.1 hc.2.1,
  have h6 : V = -U := eq_compl_iff_compl_eq.mp h5,

  have h7 : is_closed U, {rwa [is_closed,←h6]},
  have h8 : is_closed V, {rwa [is_closed,←h5]},

  have g1 : is_clopen U := ⟨h3,h7⟩, have g2 : is_clopen V := ⟨h4,h8⟩,

  have g3, from H U g1,
  have g4, from H V g2,

  have UneqV : U ≠ V,
    {by_contradiction ac, simp at ac, have a1 := hc.2.1,
    rw ac at a1, simp at a1, by exact absurd a1 hc.2.2.2},

  have toUeqV : U = univ → V = univ → U = V, {intros a1 a2, rwa [a2]},

  have g5 : U = univ → false,
    {intro a1, by exact or.elim g4
      (assume b1, by exact absurd (toUeqV a1 b1) UneqV)
      (assume b2, by exact absurd b2 hc.2.2.2)},

  have g6 : U = ∅ → false, {intro a1, by exact absurd a1 hc.2.2.1},

  by exact or.elim g3 (assume a1, by exact g5 a1) (assume a2, by exact g6 a2),

end

-- (5) → (6)
lemma five_implies_six [topological_space α] : (∀ U V : set α, is_separated U V →
¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) → (∀ f : α → bool, continuous f → ¬function.surjective f) :=
begin
  intros H1 f cf, by_contradiction h,
  rw function.surjective at h, rw continuous at cf,

  have h1 : X.is_open {ff}, {unfold X},

  have hff' : is_open (f ⁻¹' {ff}),
    from cf {ff} (show X.is_open {ff}, {unfold X}),
  have htt' : is_open (f ⁻¹' {tt}),
    from cf {tt} (show X.is_open {tt}, {unfold X}),

  have hs : (f ⁻¹' {ff}) ∪ (f ⁻¹' {tt}) = @univ α,
    begin
      have a1 : ∀ x, x ∈ (f ⁻¹' {ff}) ∪ (f ⁻¹' {tt}),
        begin
          intro x, rw [mem_union], by_contradiction hx,
          simp at hx, rw [not_or_distrib] at hx,
          have hy, from hx.1, simp at hx,
          by exact absurd hx.2 hy,
        end,
      rwa [eq_univ_iff_forall],
    end,

  have he : (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}) = ∅,
    {have a1 : ∀ x, x ∉ (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}),
    by simp, by exact eq_empty_iff_forall_not_mem.mpr a1},

  have hc1 : (f ⁻¹' {ff}) = -(f ⁻¹' {tt}),
    {by exact disjoint_and_union_univ_imp_compl hs he},

  have hc2 : (f ⁻¹' {tt}) = -(f ⁻¹' {ff}),
    {by exact eq_compl_iff_compl_eq.mp hc1},

  have Hc1 : closure (f ⁻¹' {ff}) = (f ⁻¹' {ff}),
    {have hclff : is_closed (f ⁻¹' {ff}), {rwa [is_closed,←hc2]},
    by exact closure_eq_of_is_closed hclff},
  have Hc2 : closure (f ⁻¹' {tt}) = (f ⁻¹' {tt}),
    {have hcltt : is_closed (f ⁻¹' {tt}), {rwa [is_closed,←hc1]},
    by exact closure_eq_of_is_closed hcltt},

  have Hs : is_separated (f ⁻¹' {ff}) (f ⁻¹' {tt}),
    {rw is_separated,
    have a1 : closure (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}) = ∅, {rwa [Hc1]},
    have a2 : f ⁻¹' {ff} ∩ closure (f ⁻¹' {tt}) = ∅, {rwa [Hc2]},
    by exact ⟨a1,a2⟩},

  have HS := H1 (f ⁻¹' {ff}) (f ⁻¹' {tt}) Hs,
  simp at HS, have HS2 := neq_empty_imp_empty_to_union (HS hs he),

  have P : ∀ b : bool, f ⁻¹' {b} = ∅ → false,
    {intros b a1,
    have a2, from h b,
    have a3 : ∀ a, f a = b → a ∈ f ⁻¹' {b},
      {intros a b1, rw [mem_preimage_eq,b1], simp},
    have a4 : ∃ x, x ∈ f ⁻¹' {b}, from exists.elim a2
      (assume x, assume hx : f x = b,
        have hy : x ∈ f ⁻¹' {b} := a3 x hx,
        by exact ⟨x,hy⟩),
    by exact absurd a1 (ne_empty_iff_exists_mem.mpr a4)},

  by exact or.elim HS2
    (assume a1, by exact P ff a1)
    (assume a2, by exact P tt a2),
end

-- (6) → (1)
def char_map (A : set α) (x : α) : Prop := x ∈ A

noncomputable def char_map_to_bool (A : set α) (x : α) : bool := to_bool (char_map A x)

lemma six_implies_one [topological_space α] : (∀ f : α → bool,
continuous f → ¬function.surjective f) → (∀ U V : set α, is_open U ∧ is_open V →
¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) :=
begin
  intros H1 U V H2 HC,
  have hcompl : U = -V := disjoint_and_union_univ_imp_compl HC.1 HC.2.1,
  have h1 : (char_map_to_bool V) ⁻¹' ∅ = ∅ := rfl,
  have h2 : (char_map_to_bool V) ⁻¹' univ = univ := rfl,

  have h3 : (char_map_to_bool V) ⁻¹' {ff} = U,
    {have a1 : ∀ x ∈ U, x ∈ (char_map_to_bool V) ⁻¹' {ff},
      {simp, intros x hx, have b1 : x ∉ V, {rwa hcompl at hx},
      rw [char_map_to_bool,char_map], simp, assumption},
    have a2 : ∀ x ∈ (char_map_to_bool V) ⁻¹' {ff}, x ∈ U,
      {simp, intros x hx, rw char_map_to_bool at hx, simp at hx,
      rw char_map at hx, by exact in_left_union_univ HC.1 hx},
    rw [←subset_def] at a1 a2, by exact subset.antisymm a2 a1},

  have h4 : (char_map_to_bool V) ⁻¹' {tt} = V,
    {have a1 : ∀ x ∈ V, x ∈ (char_map_to_bool V) ⁻¹' {tt},
      {simp, intros x hx,
      rw [char_map_to_bool,char_map], simp, assumption},
    have a2 : ∀ x ∈ (char_map_to_bool V) ⁻¹' {tt}, x ∈ V,
      {simp, intros x hx, rw char_map_to_bool at hx,
      simp at hx, rwa char_map at hx},
    rw [←subset_def] at a1 a2, by exact subset.antisymm a2 a1},

  have g1 : is_open (char_map_to_bool V ⁻¹' ∅), {rw h1, simp},
  have g2 : is_open (char_map_to_bool V ⁻¹' univ), {rw h2, simp},
  have g3 : is_open (char_map_to_bool V ⁻¹' {ff}), {rw h3, exact H2.1},
  have g4 : is_open (char_map_to_bool V ⁻¹' {tt}), {rw h4, exact H2.2},

  suffices c1 : continuous (char_map_to_bool V),
    have j1 := H1 (char_map_to_bool V) c1,
    rw [function.surjective, not_forall] at j1, simp at j1,

    by exact exists.elim j1
      (assume b a1,
      have a2 : b = ff ∨ b = tt := bool.dichotomy b,
      by exact or.elim a2
        (assume bf : b = ff,
          have c1 : ∀ (x : α), ¬char_map_to_bool V x = ff, {rwa bf at a1},
          have c2 : ∀ (x : α), ¬ x ∈ (char_map_to_bool V ⁻¹' {ff}), {simp, simp at c1, assumption},
          have c3 : ∀ (x : α), ¬ x ∈ U, {rwa h3 at c2},
          have c4 : U = ∅ := eq_empty_iff_forall_not_mem.mpr c3,
          by exact absurd c4 HC.2.2.1)
        (assume bt : b = tt,
          have c1 : ∀ (x : α), ¬char_map_to_bool V x = tt, {rwa bt at a1},
          have c2 : ∀ (x : α), ¬ x ∈ (char_map_to_bool V ⁻¹' {tt}), {simp, simp at c1, assumption},
          have c3 : ∀ (x : α), ¬ x ∈ V, {rwa h4 at c2},
          have c4 : V = ∅ := eq_empty_iff_forall_not_mem.mpr c3,
          by exact absurd c4 HC.2.2.2)),

  rw continuous,
  have A1 : {ff} ∪ {tt} = @univ bool,
    {rw eq_univ_iff_forall, intro b, simp, exact bool.dichotomy b},
  have A2 : ∀ y : bool, y ∉ {ff} → y ∈ {tt}, {intro y, exact in_right_union_univ A1},
  have A3 : ∀ y : bool, y ∉ {tt} → y ∈ {ff}, {intro y, exact in_left_union_univ A1},

  have k1 : ∀ s : set bool, s = ∅ ∨ s = univ ∨ s = {ff} ∨ s = {tt},
    begin
      intro s, by_contradiction hc, repeat {rw not_or_distrib at hc},
      have a1 : ∃ b1 : bool, b1 ∈ s := not_eq_empty_iff_exists.mp hc.1,
      have a2 : ∃ b2 : bool, b2 ∉ s, {by_contradiction bc,
        simp at bc, rw [←eq_univ_iff_forall] at bc, exact absurd bc hc.2.1},

      have a3 : ff ∈ s → tt ∈ s,
        {intros c1, have c2 := hc.2.2.1,
        have hy : ∃ y, y ∈ s ∧ y ≠ ff, by_contradiction cy, simp at cy,
          have hf : s = {ff},
            {have hy1 : ∀ y : bool, y ∈ s → y ∈ {ff},
              {intros y d1,
                have d2 : y ∈ {ff} ∨ y ∈ {tt},
                  {rw [←mem_union _ _ _,A1], simp},
                by exact or.elim d2 (assume e1, by exact e1)
                (assume e2, show y ∈ {ff}, {simp at e2, rw e2 at d1, exact absurd d1 cy})},
            have hy2 : ∀ y : bool, y ∈ {ff} → y ∈ s, {simp, assumption},
            by exact subset.antisymm hy1 hy2},
          by exact absurd hf c2,
        by exact exists.elim hy
          (assume y d1,
          have d2 : y ∉ {ff} → y ∈ {tt} := A2 y,
          have d3 : y ≠ ff → y = tt, {rwa [mem_singleton_iff,mem_singleton_iff] at d2},
          have d4 : y = tt := d3 d1.2,
          have d5 : y ∈ s := d1.1,
          show tt ∈ s, {rwa d4 at d5})},

      have a4 : tt ∈ s → ff ∈ s,
        {intros c1, have c2 := hc.2.2.2,
        have hy : ∃ y, y ∈ s ∧ y ≠ tt, by_contradiction cy, simp at cy,
          have hf : s = {tt},
            {have hy1 : ∀ y : bool, y ∈ s → y ∈ {tt},
              {intros y d1,
                have d2 : y ∈ {tt} ∨ y ∈ {ff},
                  {rw [or_comm,←mem_union _ _ _,A1], simp},
                by exact or.elim d2 (assume e1, by exact e1)
                (assume e2, show y ∈ {tt}, {simp at e2, rw e2 at d1, exact absurd d1 cy})},
            have hy2 : ∀ y : bool, y ∈ {tt} → y ∈ s, {simp, assumption},
            by exact subset.antisymm hy1 hy2},
          by exact absurd hf c2,
        by exact exists.elim hy
          (assume y d1,
          have d2 : y ∉ {tt} → y ∈ {ff} := A3 y,
          have d3 : y ≠ tt → y = ff, {rwa [mem_singleton_iff,mem_singleton_iff] at d2},
          have d4 : y = ff := d3 d1.2,
          have d5 : y ∈ s := d1.1,
          show ff ∈ s, {rwa d4 at d5})},

      have hX : ∀ b : bool, b = ff ∨ b = tt, {intro b, exact (bool.dichotomy b)},

      have a5 : ff ∈ s,
        {by exact exists.elim a1
          (assume b : bool, assume Hb : b ∈ s,
          by exact or.elim (hX b)
            (assume s1, show ff ∈ s, by {rwa s1 at Hb})
            (assume s1, show ff ∈ s, {rw s1 at Hb, by exact a4 Hb}))},

      have a6 : tt ∈ s,
        {by exact exists.elim a1
          (assume b : bool, assume Hb : b ∈ s,
          by exact or.elim ( (or_comm _ _).mp (hX b))
            (assume s1, show tt ∈ s, by {rwa s1 at Hb})
            (assume s1, show tt ∈ s, {rw s1 at Hb, by exact a3 Hb}))},

      have a7 : ∀ b : bool, b ∈ s,
        {intro b, by exact or.elim (hX b)
          (assume k1, by {rwa [←k1] at a5})
          (assume k2, by {rwa [←k2] at a6})},

      rw [←eq_univ_iff_forall] at a7, by exact absurd a7 hc.2.1,

    end,

  intros s Hs,

  have B1 : s = ∅ → is_open (char_map_to_bool V ⁻¹' s), {intro h, rwa h},
  have B2 : s = univ → is_open (char_map_to_bool V ⁻¹' s), {intro h, rwa h},
  have B3 : s = {ff} → is_open (char_map_to_bool V ⁻¹' s), {intro h, rwa h},
  have B4 : s = {tt} → is_open (char_map_to_bool V ⁻¹' s), {intro h, rwa h},

  by exact or.elim (k1 s)
    (assume b1 : s = ∅, by exact B1 b1)
    (assume b2 : s = univ ∨ s = {ff} ∨ s = {tt},
      by exact or.elim b2
        (assume c1 : s = univ, by exact B2 c1)
        (assume c2 : s = {ff} ∨ s = {tt},
          by exact or.elim c2
            (assume d1 : s = {ff}, by exact B3 d1)
            (assume d2 : s = {tt}, by exact B4 d2))),
end

------------------------------------------------------------
/- Alternate definitions of connected, proved to be
equivalent to the chosen clopen_trivial definition. -/

-- 4.
theorem connected_def_empty_frontier_iff_trivial [c : connected_space α] :
∀ s : set α, frontier s = ∅ ↔ s = univ ∨ s = ∅ :=
begin
  intro s, apply iff.intro,
  assume h1,
    have A : is_clopen s, from (empty_frontier_iff_clopen s).mp h1,
    exact connected_space.clopen_trivial _ A,
  assume h2,
    have B : is_clopen s,

    have G : s = univ → is_clopen s,
      assume B1 : s = univ, rw B1, exact ⟨is_open_univ,is_closed_univ⟩,
    have H : s = ∅ → is_clopen s,
      assume B1 : s = ∅, rw B1, exact ⟨is_open_empty,is_closed_empty⟩,
    exact or.elim h2 G H,
  rwa [empty_frontier_iff_clopen],
end

theorem empty_frontier_iff_trivial_to_connected [topological_space α]
(hs : ∀ s : set α, frontier s = ∅ ↔ s = univ ∨ s = ∅) : connected_space α :=
begin
  have h1 : ∀ s : set α, is_clopen s → s = univ ∨ s = ∅,
    {assume s, by exact (iff.trans (iff.symm (empty_frontier_iff_clopen s)) (hs s)).mp},
    by exact connected_space.mk h1,
end

-- 1.
theorem connected_def_no_open_separation [c : connected_space α] : ∀ U V : set α,
is_open U ∧ is_open V → ¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅) :=
begin
  intros U V H,
  by_contradiction h,
  have A, from components_of_separation_clopen H.1 H.2 h,
  have A1, from A.1, have A2, from A.2,
  rw [←empty_frontier_iff_clopen] at A1 A2,
  rw [connected_def_empty_frontier_iff_trivial] at A1 A2,
  have h2, from h.2.1,
  have H1 : U = univ → false,
    begin
      assume B1,
      rw [B1, univ_inter] at h2,
      apply absurd h2 h.2.2.2,
    end,
   exact or.elim A1 H1 (assume B2 : U = ∅, absurd B2 h.2.2.1),
end

theorem connected_def_no_open_separation2 [c : connected_space α] (U V : set α)
(hU : is_open U) (hV : is_open V) (hunion : U ∪ V = univ) (hinter : U ∩ V = ∅)
: (U = ∅ ∨ V = ∅) :=
begin
  by_contradiction h,
  have A, from components_of_separation_clopen2 hU hV hunion hinter (not_or_distrib.mp h).1 (not_or_distrib.mp h).2,
  have H1 : U = univ → false,
    {assume B, rw [B, univ_inter] at hinter, apply absurd hinter (not_or_distrib.mp h).2},
  exact or.elim ((connected_def_empty_frontier_iff_trivial _ ).mp ((empty_frontier_iff_clopen _ ).mpr A.1))
    H1 (assume B : U = ∅, absurd B (not_or_distrib.mp h).1),
end

theorem no_open_separation_to_connected [topological_space α] (H : ∀ U V : set α,
is_open U ∧ is_open V → ¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) : connected_space α :=
begin
  by exact empty_frontier_iff_trivial_to_connected (two_implies_three (one_implies_two H)),
end

-- 2.
theorem connected_def_no_closed_separation [c : connected_space α] : ∀ U V : set α,
is_closed U ∧ is_closed V → ¬( U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅ ) :=
begin
  exact no_open_sep_iff_no_closed_sep.mp connected_def_no_open_separation,
end

theorem no_closed_separation_to_connected [topological_space α] (H : ∀ U V : set α,
is_closed U ∧ is_closed V → ¬( U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅ )) :
connected_space α :=
begin
  by exact empty_frontier_iff_trivial_to_connected (two_implies_three H),
end

-- 3. clopen_trivial, chosen to be the definition of connected

-- 5.
theorem connected_def_separated_sets [c : connected_space α] : ∀ U V : set α,
is_separated U V → ¬( U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅ ) :=
begin
  intros U V hsep hc, rw is_separated at hsep,
  have hU := hsep.1,
  have hV := hsep.2,

  have h1 : V = -(closure U),
    {have a1 : closure U ∪ V = univ,
      {have b1 : U ⊆ closure U ∪ V,
        {have c1 : U ⊆ closure U := subset_closure,
        have c2 : closure U ⊆ closure U ∪ V := subset_union_left (closure U) V,
        by exact subset.trans c1 c2},
      have b2 : U ∪ V ⊆ closure U ∪ V :=
        (union_subset_iff.mpr ⟨b1, subset_union_right (closure U) V⟩),
      rwa [hc.1] at b2,
      by exact eq_univ_of_univ_subset b2},
    {rw [union_comm _ _] at a1, rw [inter_comm _ _] at hU,
     by exact disjoint_and_union_univ_imp_compl a1 hU}},

  have h2 : U = -(closure V),
    {have a1 : U ∪ closure V = univ,
      {have b1 : V ⊆ U ∪ closure V,
        {have c1 : V ⊆ closure V := subset_closure,
        have c2 : closure V ⊆ U ∪ closure V := subset_union_right U (closure V),
        by exact subset.trans c1 c2},
      have b2 : U ∪ V ⊆ U ∪ closure V :=
        (union_subset_iff.mpr ⟨subset_union_left U (closure V), b1⟩),
      rwa [hc.1] at b2, by exact eq_univ_of_univ_subset b2},
    by exact disjoint_and_union_univ_imp_compl a1 hV},

  have h3 : is_open U,
    {have a1 : is_closed (closure V) := is_closed_closure, rw h2, rwa is_closed at a1},
  have h4 : is_open V,
    {have a1 : is_closed (closure U) := is_closed_closure, rw h1, rwa is_closed at a1},

  have h5 : U = -V := disjoint_and_union_univ_imp_compl hc.1 hc.2.1,
  have h6 : V = -U := eq_compl_iff_compl_eq.mp h5,

  have h7 : is_closed U, {rwa [is_closed,←h6]},
  have h8 : is_closed V, {rwa [is_closed,←h5]},

  have g1 : is_clopen U := ⟨h3,h7⟩, have g2 : is_clopen V := ⟨h4,h8⟩,

  have g3, from connected_space.clopen_trivial U g1,
  have g4, from connected_space.clopen_trivial V g2,

  have UneqV : U ≠ V,
    {by_contradiction ac, simp at ac, have a1 := hc.2.1,
    rw ac at a1, simp at a1, by exact absurd a1 hc.2.2.2},

  have toUeqV : U = univ → V = univ → U = V, {intros a1 a2, rwa [a2]},

  have g5 : U = univ → false,
    {intro a1, by exact or.elim g4
      (assume b1, by exact absurd (toUeqV a1 b1) UneqV)
      (assume b2, by exact absurd b2 hc.2.2.2)},

  have g6 : U = ∅ → false, {intro a1, by exact absurd a1 hc.2.2.1},

  by exact or.elim g3 (assume a1, by exact g5 a1) (assume a2, by exact g6 a2),

end

theorem separated_sets_to_connected [topological_space α]
(H : ∀ U V : set α, is_separated U V → ¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅)) :
connected_space α :=
begin
  by exact empty_frontier_iff_trivial_to_connected
  (two_implies_three (one_implies_two (six_implies_one (five_implies_six H)))),
end

-- 6.
theorem connected_def_cts_to_discrete [connected_space α] :
∀ f : α → bool, continuous f → ¬function.surjective f :=
begin
  intros f cf, by_contradiction h,
  rw function.surjective at h, rw continuous at cf,

  have s1 : X.is_open {ff}, {unfold X},

  have hff' : is_open (f ⁻¹' {ff}),
    from cf {ff} (show X.is_open {ff}, {unfold X}),
  have htt' : is_open (f ⁻¹' {tt}),
    from cf {tt} (show X.is_open {tt}, {unfold X}),

  have hs : (f ⁻¹' {ff}) ∪ (f ⁻¹' {tt}) = @univ α,
    begin
      have a1 : ∀ x, x ∈ (f ⁻¹' {ff}) ∪ (f ⁻¹' {tt}),
        begin
          intro x, rw [mem_union], by_contradiction hx,
          simp at hx, rw [not_or_distrib] at hx,
          have hy, from hx.1, simp at hx,
          by exact absurd hx.2 hy,
        end,
      rwa [eq_univ_iff_forall],
    end,

  have he : (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}) = ∅,
    {have a1 : ∀ x, x ∉ (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}),
    by simp, by exact eq_empty_iff_forall_not_mem.mpr a1},

  have hc1 : (f ⁻¹' {ff}) = -(f ⁻¹' {tt}),
    {by exact disjoint_and_union_univ_imp_compl hs he},

  have hc2 : (f ⁻¹' {tt}) = -(f ⁻¹' {ff}),
    {by exact eq_compl_iff_compl_eq.mp hc1},

  have Hc1 : closure (f ⁻¹' {ff}) = (f ⁻¹' {ff}),
    {have hclff : is_closed (f ⁻¹' {ff}), {rwa [is_closed,←hc2]},
    by exact closure_eq_of_is_closed hclff},
  have Hc2 : closure (f ⁻¹' {tt}) = (f ⁻¹' {tt}),
    {have hcltt : is_closed (f ⁻¹' {tt}), {rwa [is_closed,←hc1]},
    by exact closure_eq_of_is_closed hcltt},

  have Hs : is_separated (f ⁻¹' {ff}) (f ⁻¹' {tt}),
    {rw is_separated,
    have a1 : closure (f ⁻¹' {ff}) ∩ (f ⁻¹' {tt}) = ∅, {rwa [Hc1]},
    have a2 : f ⁻¹' {ff} ∩ closure (f ⁻¹' {tt}) = ∅, {rwa [Hc2]},
    by exact ⟨a1,a2⟩},

  have HS := connected_def_separated_sets (f ⁻¹' {ff}) (f ⁻¹' {tt}) Hs,
  simp at HS, have HS2 := neq_empty_imp_empty_to_union (HS hs he),

  have P : ∀ b : bool, f ⁻¹' {b} = ∅ → false,
    {intros b a1,
    have a2, from h b,
    have a3 : ∀ a, f a = b → a ∈ f ⁻¹' {b},
      {intros a b1, rw [mem_preimage_eq,b1], simp},
    have a4 : ∃ x, x ∈ f ⁻¹' {b}, from exists.elim a2
      (assume x, assume hx : f x = b,
        have hy : x ∈ f ⁻¹' {b} := a3 x hx,
        by exact ⟨x,hy⟩),
    by exact absurd a1 (ne_empty_iff_exists_mem.mpr a4)},

  by exact or.elim HS2
    (assume a1, by exact P ff a1)
    (assume a2, by exact P tt a2),
end

theorem cts_to_discrete_to_connected [topological_space α]
(H : ∀ f : α → bool, continuous f → ¬function.surjective f) : connected_space α :=
begin
  by exact empty_frontier_iff_trivial_to_connected
  (two_implies_three (one_implies_two (six_implies_one H))),
end

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

lemma subset_inter_empty_right {A B C : set α} (H1 : A ⊆ B ∪ C) (H2 : A ∩ B = ∅) :
A ⊆ C :=
begin
  rw [subset_def] at H1, rw subset_def, intros x hx,
  by exact or.elim (mem_or_mem_of_mem_union (H1 x hx))
    (assume a1, by exact absurd a1 (not_in_right_union_empty H2 hx)) (by simp)
end

lemma subset_inter_empty_left {A B C : set α} (H1 : A ⊆ B ∪ C) (H2 : A ∩ C = ∅) :
A ⊆ B :=
begin
  rw [subset_def] at H1, rw subset_def, intros x hx,
  by exact or.elim (mem_or_mem_of_mem_union (H1 x hx))
    (by simp) (assume a1, by exact absurd a1 (not_in_right_union_empty H2 hx))
end

lemma subsets_of_disjoint {A B C D : set α} (H1 : A ⊆ C) (H2 : B ⊆ D) (H3 : C ∩ D = ∅) :
A ∩ B = ∅ :=
by {have H4 := inter_subset_inter H1 H2, rw [H3] at H4, exact eq_empty_of_subset_empty H4}

lemma inter_union_empty_eq_union_empty_left {A B C D : set α} (H1 : A ∩ (C ∪ D) = ∅) (H2 : A ∪ B = C ∪ D) :
A = ∅ := by {rw [←H2] at H1, simp at H1, assumption}

lemma inter_union_empty_eq_union_empty_right {A B C D : set α} (H1 : B ∩ (C ∪ D) = ∅) (H2 : A ∪ B = C ∪ D) :
B = ∅ := by {rw [←H2] at H1, simp at H1, assumption}

lemma inter_union_lemma_1 {A B C : set α} (H1 : A ∩ B = ∅) (H2 : A ∩ C = ∅) :
A ∩ (B ∪ C) = ∅ := by {rw [inter_distrib_left,H1,H2], simp}

lemma inter_eq_comm {A B C : set α} (H1 : A ∩ B = C) : B ∩ A = C := by {rwa inter_comm}

theorem is_connected_pairwise_union [topological_space α] {A B : set α} :
is_connected A ∧ is_connected B → (A ∩ B ≠ ∅) → is_connected (A ∪ B) :=
begin
  intros hc hn, rw is_connected, intros U' V' huv h,

  have T1 : A ⊆ U' ∪ V', {rw h.1, simp},
  have T2 : B ⊆ U' ∪ V', {rw h.1, simp},

  have hA := eq_union_of_inter_if_subseteq T1,
  have hB := eq_union_of_inter_if_subseteq T2,

  have pA2, from hc.1 (A ∩ U') (A ∩ V')
    ⟨sub_union_open_to_open_inter_left huv.1,sub_union_open_to_open_inter_left huv.2⟩,
  have pB2, from hc.2 (B ∩ U') (B ∩ V')
    ⟨sub_union_open_to_open_inter_right huv.1,sub_union_open_to_open_inter_right huv.2⟩,

  simp at pA2, simp at pB2, rw eq_comm at hA hB,

  have wA1 : ¬A ∩ U' = ∅ → A ∩ V' = ∅, from pA2 hA (inter_empty_distrib h.2.1),
  have wB1 : ¬B ∩ U' = ∅ → B ∩ V' = ∅, from pB2 hB (inter_empty_distrib h.2.1),
  rw [inter_comm] at wA1 wB1,
  have v1 : ¬U' ∩ A = ∅ → V' ∩ A = ∅, {intro hv, have hv2, from wA1 hv, rwa [inter_comm]},
  have v2 : ¬U' ∩ B = ∅ → V' ∩ B = ∅, {intro hv, have hv2, from wB1 hv, rwa [inter_comm]},

  have zA : A ⊆ U' ∨ A ⊆ V', exact or.elim (neq_empty_imp_empty_to_union v1)
    (assume z1, by {rw inter_comm at z1, exact or.inr (subset_inter_empty_right T1 z1)})
    (assume z2, by {rw inter_comm at z2, exact or.inl (subset_inter_empty_left T1 z2)}),

  have zB : B ⊆ U' ∨ B ⊆ V', exact or.elim (neq_empty_imp_empty_to_union v2)
    (assume z1, by {rw inter_comm at z1, exact or.inr (subset_inter_empty_right T2 z1)})
    (assume z2, by {rw inter_comm at z2, exact or.inl (subset_inter_empty_left T2 z2)}),

  have H1 := neq_empty_imp_empty_to_union v1,
  have H2 := neq_empty_imp_empty_to_union v2,

  cases H1 with P1 P2,
    cases H2 with P3 P4,
      exact absurd (inter_union_empty_eq_union_empty_left (inter_union_lemma_1 P1 P3) h.1) h.2.2.1,
      exact absurd (inter_eq_comm (subsets_of_disjoint (subset_inter_empty_left T2 (inter_eq_comm P4))
      (subset_inter_empty_right T1 (inter_eq_comm P1)) h.2.1)) hn,
    cases H2 with P5 P6,
      exact absurd (subsets_of_disjoint (subset_inter_empty_left T1 (inter_eq_comm P2))
      (subset_inter_empty_right T2 (inter_eq_comm P5)) h.2.1) hn,
      exact absurd (inter_union_empty_eq_union_empty_right (inter_union_lemma_1 P2 P6) h.1) h.2.2.2,
end

theorem open_in_univ_iff_open [topological_space α] {U : set α} :
is_open U ↔ is_open_in_subspace univ U :=
begin
  apply iff.intro,
    intro H, rw is_open_in_subspace,
    have h1 : U = univ ∩ U, {simp},
    by exact exists.intro U ⟨h1,H⟩,
  intro H, rw is_open_in_subspace at H,
  by exact exists.elim H
  (assume V, assume hV, show is_open U,
  {have b1, from is_open_inter is_open_univ hV.2, rwa [hV.1]}),
end

theorem connected_if_univ_connected [topological_space α] :
is_connected (@univ α) → connected_space α :=
begin
  rw is_connected, intros H,
  have h1 : ∀ U V : set α, is_open U ∧ is_open V → ¬(U ∪ V = univ ∧ U ∩ V = ∅ ∧ U ≠ ∅ ∧ V ≠ ∅),
    {intros U V, repeat {rw [open_in_univ_iff_open]}, by exact H U V},
  by exact no_open_separation_to_connected h1,
end

theorem is_connected_univ [connected_space α] : is_connected (@univ α) :=
begin
  rw is_connected, intros U V h1, repeat {rw [←open_in_univ_iff_open] at h1},
  by exact connected_def_no_open_separation U V h1,
end

theorem is_connected_empty [connected_space α] : is_connected (∅ : set α) :=
begin
  rw is_connected, intros U V h1 hc, repeat {rw is_open_in_subspace at h1},
  have h2 : ∀ V : set α, ∅ ∩ V = ∅, {intro V, by exact empty_inter V},
  show false, from exists.elim h1.1 (assume U1, assume hU1, have a1 : U = ∅,
    {rw [h2 U1] at hU1, by exact hU1.1}, by exact absurd a1 hc.2.2.1),
end

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


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

import algebra.hom.iterate
import data.list.cycle
import data.nat.prime
import dynamics.fixed_points.basic

/-!
# Periodic points

A point `x : α` is a periodic point of `f : α → α` of period `n` if `f^[n] x = x`.

## Main definitions

* `is_periodic_pt f n x` : `x` is a periodic point of `f` of period `n`, i.e. `f^[n] x = x`.
  We do not require `n > 0` in the definition.
* `pts_of_period f n` : the set `{x | is_periodic_pt f n x}`. Note that `n` is not required to
  be the minimal period of `x`.
* `periodic_pts f` : the set of all periodic points of `f`.
* `minimal_period f x` : the minimal period of a point `x` under an endomorphism `f` or zero
  if `x` is not a periodic point of `f`.
* `orbit f x`: the cycle `[x, f x, f (f x), ...]` for a periodic point.

## Main statements

We provide “dot syntax”-style operations on terms of the form `h : is_periodic_pt f n x` including
arithmetic operations on `n` and `h.map (hg : semiconj_by g f f')`. We also prove that `f`
is bijective on each set `pts_of_period f n` and on `periodic_pts f`. Finally, we prove that `x`
is a periodic point of `f` of period `n` if and only if `minimal_period f x | n`.

## References

* https://en.wikipedia.org/wiki/Periodic_point

-/

open set

namespace function

variables {α : Type*} {β : Type*} {f fa : α → α} {fb : β → β} {x y : α} {m n : ℕ}

/-- A point `x` is a periodic point of `f : α → α` of period `n` if `f^[n] x = x`.
Note that we do not require `0 < n` in this definition. Many theorems about periodic points
need this assumption. -/
def is_periodic_pt (f : α → α) (n : ℕ) (x : α) := is_fixed_pt (f^[n]) x

/-- A fixed point of `f` is a periodic point of `f` of any prescribed period. -/
lemma is_fixed_pt.is_periodic_pt (hf : is_fixed_pt f x) (n : ℕ) : is_periodic_pt f n x :=
hf.iterate n

/-- For the identity map, all points are periodic. -/
lemma is_periodic_id (n : ℕ) (x : α) : is_periodic_pt id n x := (is_fixed_pt_id x).is_periodic_pt n

/-- Any point is a periodic point of period `0`. -/
lemma is_periodic_pt_zero (f : α → α) (x : α) : is_periodic_pt f 0 x := is_fixed_pt_id x

namespace is_periodic_pt

instance [decidable_eq α] {f : α → α} {n : ℕ} {x : α} : decidable (is_periodic_pt f n x) :=
is_fixed_pt.decidable

protected lemma is_fixed_pt (hf : is_periodic_pt f n x) : is_fixed_pt (f^[n]) x := hf

protected lemma map (hx : is_periodic_pt fa n x) {g : α → β} (hg : semiconj g fa fb) :
  is_periodic_pt fb n (g x) :=
hx.map (hg.iterate_right n)

lemma apply_iterate (hx : is_periodic_pt f n x) (m : ℕ) : is_periodic_pt f n (f^[m] x) :=
hx.map $ commute.iterate_self f m

protected lemma apply (hx : is_periodic_pt f n x) : is_periodic_pt f n (f x) :=
hx.apply_iterate 1

protected lemma add (hn : is_periodic_pt f n x) (hm : is_periodic_pt f m x) :
  is_periodic_pt f (n + m) x :=
by { rw [is_periodic_pt, iterate_add], exact hn.comp hm }

lemma left_of_add (hn : is_periodic_pt f (n + m) x) (hm : is_periodic_pt f m x) :
  is_periodic_pt f n x :=
by { rw [is_periodic_pt, iterate_add] at hn, exact hn.left_of_comp hm }

lemma right_of_add (hn : is_periodic_pt f (n + m) x) (hm : is_periodic_pt f n x) :
  is_periodic_pt f m x :=
by { rw add_comm at hn, exact hn.left_of_add hm }

protected lemma sub (hm : is_periodic_pt f m x) (hn : is_periodic_pt f n x) :
  is_periodic_pt f (m - n) x :=
begin
  cases le_total n m with h h,
  { refine left_of_add _ hn,
    rwa [tsub_add_cancel_of_le h] },
  { rw [tsub_eq_zero_iff_le.mpr h],
    apply is_periodic_pt_zero }
end

protected lemma mul_const (hm : is_periodic_pt f m x) (n : ℕ) : is_periodic_pt f (m * n) x :=
by simp only [is_periodic_pt, iterate_mul, hm.is_fixed_pt.iterate n]

protected lemma const_mul (hm : is_periodic_pt f m x) (n : ℕ) : is_periodic_pt f (n * m) x :=
by simp only [mul_comm n, hm.mul_const n]

lemma trans_dvd (hm : is_periodic_pt f m x) {n : ℕ} (hn : m ∣ n) : is_periodic_pt f n x :=
let ⟨k, hk⟩ := hn in hk.symm ▸ hm.mul_const k

protected lemma iterate (hf : is_periodic_pt f n x) (m : ℕ) : is_periodic_pt (f^[m]) n x :=
begin
  rw [is_periodic_pt, ← iterate_mul, mul_comm, iterate_mul],
  exact hf.is_fixed_pt.iterate m
end

lemma comp {g : α → α} (hco : commute f g) (hf : is_periodic_pt f n x) (hg : is_periodic_pt g n x) :
  is_periodic_pt (f ∘ g) n x :=
by { rw [is_periodic_pt, hco.comp_iterate], exact hf.comp hg }

lemma comp_lcm {g : α → α} (hco : commute f g) (hf : is_periodic_pt f m x)
  (hg : is_periodic_pt g n x) :
  is_periodic_pt (f ∘ g) (nat.lcm m n) x :=
(hf.trans_dvd $ nat.dvd_lcm_left _ _).comp hco (hg.trans_dvd $ nat.dvd_lcm_right _ _)

lemma left_of_comp {g : α → α} (hco : commute f g) (hfg : is_periodic_pt (f ∘ g) n x)
  (hg : is_periodic_pt g n x) : is_periodic_pt f n x :=
begin
  rw [is_periodic_pt, hco.comp_iterate] at hfg,
  exact hfg.left_of_comp hg
end

lemma iterate_mod_apply (h : is_periodic_pt f n x) (m : ℕ) :
  f^[m % n] x = (f^[m] x) :=
by conv_rhs { rw [← nat.mod_add_div m n, iterate_add_apply, (h.mul_const _).eq] }

protected lemma mod (hm : is_periodic_pt f m x) (hn : is_periodic_pt f n x) :
  is_periodic_pt f (m % n) x :=
(hn.iterate_mod_apply m).trans hm

protected lemma gcd (hm : is_periodic_pt f m x) (hn : is_periodic_pt f n x) :
  is_periodic_pt f (m.gcd n) x :=
begin
  revert hm hn,
  refine nat.gcd.induction m n (λ n h0 hn, _) (λ m n hm ih hm hn, _),
  { rwa [nat.gcd_zero_left], },
  { rw [nat.gcd_rec],
    exact ih (hn.mod hm) hm }
end

/-- If `f` sends two periodic points `x` and `y` of the same positive period to the same point,
then `x = y`. For a similar statement about points of different periods see `eq_of_apply_eq`. -/
lemma eq_of_apply_eq_same (hx : is_periodic_pt f n x) (hy : is_periodic_pt f n y) (hn : 0 < n)
  (h : f x = f y) :
  x = y :=
by rw [← hx.eq, ← hy.eq, ← iterate_pred_comp_of_pos f hn, comp_app, h]

/-- If `f` sends two periodic points `x` and `y` of positive periods to the same point,
then `x = y`. -/
lemma eq_of_apply_eq (hx : is_periodic_pt f m x) (hy : is_periodic_pt f n y) (hm : 0 < m)
  (hn : 0 < n) (h : f x = f y) :
  x = y :=
(hx.mul_const n).eq_of_apply_eq_same (hy.const_mul m) (mul_pos hm hn) h

end is_periodic_pt

/-- The set of periodic points of a given (possibly non-minimal) period. -/
def pts_of_period (f : α → α) (n : ℕ) : set α := {x : α | is_periodic_pt f n x}

@[simp] lemma mem_pts_of_period : x ∈ pts_of_period f n ↔ is_periodic_pt f n x :=
iff.rfl

lemma semiconj.maps_to_pts_of_period {g : α → β} (h : semiconj g fa fb) (n : ℕ) :
  maps_to g (pts_of_period fa n) (pts_of_period fb n) :=
(h.iterate_right n).maps_to_fixed_pts

lemma bij_on_pts_of_period (f : α → α) {n : ℕ} (hn : 0 < n) :
  bij_on f (pts_of_period f n) (pts_of_period f n) :=
⟨(commute.refl f).maps_to_pts_of_period n,
  λ x hx y hy hxy, hx.eq_of_apply_eq_same hy hn hxy,
  λ x hx, ⟨f^[n.pred] x, hx.apply_iterate _,
    by rw [← comp_app f, comp_iterate_pred_of_pos f hn, hx.eq]⟩⟩

lemma directed_pts_of_period_pnat (f : α → α) : directed (⊆) (λ n : ℕ+, pts_of_period f n) :=
λ m n, ⟨m * n, λ x hx, hx.mul_const n, λ x hx, hx.const_mul m⟩

/-- The set of periodic points of a map `f : α → α`. -/
def periodic_pts (f : α → α) : set α := {x : α | ∃ n > 0, is_periodic_pt f n x}

lemma mk_mem_periodic_pts (hn : 0 < n) (hx : is_periodic_pt f n x) :
  x ∈ periodic_pts f :=
⟨n, hn, hx⟩

lemma mem_periodic_pts : x ∈ periodic_pts f ↔ ∃ n > 0, is_periodic_pt f n x := iff.rfl

lemma is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate (hx : x ∈ periodic_pts f)
  (hm : is_periodic_pt f m (f^[n] x)) : is_periodic_pt f m x :=
begin
  rcases hx with ⟨r, hr, hr'⟩,
  convert (hm.apply_iterate ((n / r + 1) * r - n)).eq,
  suffices : n ≤ (n / r + 1) * r,
  { rw [←iterate_add_apply, nat.sub_add_cancel this, iterate_mul, (hr'.iterate _).eq] },
  rw [add_mul, one_mul],
  exact (nat.lt_div_mul_add hr).le
end

variable (f)

lemma bUnion_pts_of_period : (⋃ n > 0, pts_of_period f n) = periodic_pts f :=
set.ext $ λ x, by simp [mem_periodic_pts]

lemma Union_pnat_pts_of_period : (⋃ n : ℕ+, pts_of_period f n) = periodic_pts f :=
supr_subtype.trans $ bUnion_pts_of_period f

lemma bij_on_periodic_pts : bij_on f (periodic_pts f) (periodic_pts f) :=
Union_pnat_pts_of_period f ▸
  bij_on_Union_of_directed (directed_pts_of_period_pnat f) (λ i, bij_on_pts_of_period f i.pos)

variable {f}

lemma semiconj.maps_to_periodic_pts {g : α → β} (h : semiconj g fa fb) :
  maps_to g (periodic_pts fa) (periodic_pts fb) :=
λ x ⟨n, hn, hx⟩, ⟨n, hn, hx.map h⟩

open_locale classical

noncomputable theory

/-- Minimal period of a point `x` under an endomorphism `f`. If `x` is not a periodic point of `f`,
then `minimal_period f x = 0`. -/
def minimal_period (f : α → α) (x : α) :=
if h : x ∈ periodic_pts f then nat.find h else 0

lemma is_periodic_pt_minimal_period (f : α → α) (x : α) : is_periodic_pt f (minimal_period f x) x :=
begin
  delta minimal_period,
  split_ifs with hx,
  { exact (nat.find_spec hx).snd },
  { exact is_periodic_pt_zero f x }
end

@[simp] lemma iterate_minimal_period : f^[minimal_period f x] x = x :=
is_periodic_pt_minimal_period f x

@[simp] lemma iterate_add_minimal_period_eq : f^[n + minimal_period f x] x = (f^[n] x) :=
by { rw iterate_add_apply, congr, exact is_periodic_pt_minimal_period f x }

@[simp] lemma iterate_mod_minimal_period_eq : f^[n % minimal_period f x] x = (f^[n] x) :=
(is_periodic_pt_minimal_period f x).iterate_mod_apply n

lemma minimal_period_pos_of_mem_periodic_pts (hx : x ∈ periodic_pts f) :
  0 < minimal_period f x :=
by simp only [minimal_period, dif_pos hx, (nat.find_spec hx).fst.lt]

lemma minimal_period_eq_zero_of_nmem_periodic_pts (hx : x ∉ periodic_pts f) :
  minimal_period f x = 0 :=
by simp only [minimal_period, dif_neg hx]

lemma is_periodic_pt.minimal_period_pos (hn : 0 < n) (hx : is_periodic_pt f n x) :
  0 < minimal_period f x :=
minimal_period_pos_of_mem_periodic_pts $ mk_mem_periodic_pts hn hx

lemma minimal_period_pos_iff_mem_periodic_pts :
  0 < minimal_period f x ↔ x ∈ periodic_pts f :=
⟨not_imp_not.1 $ λ h,
  by simp only [minimal_period, dif_neg h, lt_irrefl 0, not_false_iff],
  minimal_period_pos_of_mem_periodic_pts⟩

lemma minimal_period_eq_zero_iff_nmem_periodic_pts : minimal_period f x = 0 ↔ x ∉ periodic_pts f :=
by rw [←minimal_period_pos_iff_mem_periodic_pts, not_lt, nonpos_iff_eq_zero]

lemma is_periodic_pt.minimal_period_le (hn : 0 < n) (hx : is_periodic_pt f n x) :
  minimal_period f x ≤ n :=
begin
  rw [minimal_period, dif_pos (mk_mem_periodic_pts hn hx)],
  exact nat.find_min' (mk_mem_periodic_pts hn hx) ⟨hn, hx⟩
end

lemma minimal_period_apply_iterate (hx : x ∈ periodic_pts f) (n : ℕ) :
  minimal_period f (f^[n] x) = minimal_period f x :=
begin
  apply (is_periodic_pt.minimal_period_le (minimal_period_pos_of_mem_periodic_pts hx) _).antisymm
    ((is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate hx
      (is_periodic_pt_minimal_period f _)).minimal_period_le
    (minimal_period_pos_of_mem_periodic_pts _)),
  { exact (is_periodic_pt_minimal_period f x).apply_iterate n, },
  { rcases hx with ⟨m, hm, hx⟩,
    exact ⟨m, hm, hx.apply_iterate n⟩ }
end

lemma minimal_period_apply (hx : x ∈ periodic_pts f) :
  minimal_period f (f x) = minimal_period f x :=
minimal_period_apply_iterate hx 1

lemma le_of_lt_minimal_period_of_iterate_eq {m n : ℕ} (hm : m < minimal_period f x)
  (hmn : f^[m] x = (f^[n] x)) : m ≤ n :=
begin
  by_contra' hmn',
  rw [←nat.add_sub_of_le hmn'.le, add_comm, iterate_add_apply] at hmn,
  exact ((is_periodic_pt.minimal_period_le (tsub_pos_of_lt hmn')
    (is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate
    (minimal_period_pos_iff_mem_periodic_pts.1 ((zero_le m).trans_lt hm)) hmn)).trans
    (nat.sub_le m n)).not_lt hm
end

lemma eq_of_lt_minimal_period_of_iterate_eq {m n : ℕ} (hm : m < minimal_period f x)
  (hn : n < minimal_period f x) (hmn : f^[m] x = (f^[n] x)) : m = n :=
(le_of_lt_minimal_period_of_iterate_eq hm hmn).antisymm
  (le_of_lt_minimal_period_of_iterate_eq hn hmn.symm)

lemma eq_iff_lt_minimal_period_of_iterate_eq {m n : ℕ} (hm : m < minimal_period f x)
  (hn : n < minimal_period f x) : f^[m] x = (f^[n] x) ↔ m = n :=
⟨eq_of_lt_minimal_period_of_iterate_eq hm hn, congr_arg _⟩

lemma minimal_period_id : minimal_period id x = 1 :=
((is_periodic_id _ _ ).minimal_period_le nat.one_pos).antisymm
  (nat.succ_le_of_lt ((is_periodic_id _ _ ).minimal_period_pos nat.one_pos))

lemma is_fixed_point_iff_minimal_period_eq_one : minimal_period f x = 1 ↔ is_fixed_pt f x :=
begin
  refine ⟨λ h, _, λ h, _⟩,
  { rw ← iterate_one f,
    refine function.is_periodic_pt.is_fixed_pt _,
    rw ← h,
    exact is_periodic_pt_minimal_period f x },
  { exact ((h.is_periodic_pt 1).minimal_period_le nat.one_pos).antisymm
      (nat.succ_le_of_lt ((h.is_periodic_pt 1).minimal_period_pos nat.one_pos)) }
end

lemma is_periodic_pt.eq_zero_of_lt_minimal_period (hx : is_periodic_pt f n x)
  (hn : n < minimal_period f x) : n = 0 :=
eq.symm $ (eq_or_lt_of_le $ n.zero_le).resolve_right $ λ hn0,
not_lt.2 (hx.minimal_period_le hn0) hn

lemma not_is_periodic_pt_of_pos_of_lt_minimal_period :
  ∀ {n : ℕ} (n0 : n ≠ 0) (hn : n < minimal_period f x), ¬ is_periodic_pt f n x
| 0 n0 _ := (n0 rfl).elim
| (n + 1) _ hn := λ hp, nat.succ_ne_zero _ (hp.eq_zero_of_lt_minimal_period hn)

lemma is_periodic_pt.minimal_period_dvd (hx : is_periodic_pt f n x) : minimal_period f x ∣ n :=
(eq_or_lt_of_le $ n.zero_le).elim (λ hn0, hn0 ▸ dvd_zero _) $ λ hn0,
nat.dvd_iff_mod_eq_zero.2 $
(hx.mod $ is_periodic_pt_minimal_period f x).eq_zero_of_lt_minimal_period $
nat.mod_lt _ $ hx.minimal_period_pos hn0

lemma is_periodic_pt_iff_minimal_period_dvd : is_periodic_pt f n x ↔ minimal_period f x ∣ n :=
⟨is_periodic_pt.minimal_period_dvd, λ h, (is_periodic_pt_minimal_period f x).trans_dvd h⟩

open nat

lemma minimal_period_eq_minimal_period_iff {g : β → β} {y : β} :
  minimal_period f x = minimal_period g y ↔ ∀ n, is_periodic_pt f n x ↔ is_periodic_pt g n y :=
by simp_rw [is_periodic_pt_iff_minimal_period_dvd, dvd_right_iff_eq]

lemma minimal_period_eq_prime {p : ℕ} [hp : fact p.prime] (hper : is_periodic_pt f p x)
  (hfix : ¬ is_fixed_pt f x) : minimal_period f x = p :=
(hp.out.eq_one_or_self_of_dvd _ (hper.minimal_period_dvd)).resolve_left
  (mt is_fixed_point_iff_minimal_period_eq_one.1 hfix)

lemma minimal_period_eq_prime_pow {p k : ℕ} [hp : fact p.prime] (hk : ¬ is_periodic_pt f (p ^ k) x)
(hk1 : is_periodic_pt f (p ^ (k + 1)) x) : minimal_period f x = p ^ (k + 1) :=
begin
  apply nat.eq_prime_pow_of_dvd_least_prime_pow hp.out;
  rwa ← is_periodic_pt_iff_minimal_period_dvd
end

lemma commute.minimal_period_of_comp_dvd_lcm {g : α → α} (h : function.commute f g) :
  minimal_period (f ∘ g) x ∣ nat.lcm (minimal_period f x) (minimal_period g x) :=
begin
  rw [← is_periodic_pt_iff_minimal_period_dvd],
  exact (is_periodic_pt_minimal_period f x).comp_lcm h (is_periodic_pt_minimal_period g x)
end

lemma commute.minimal_period_of_comp_dvd_mul {g : α → α} (h : function.commute f g) :
  minimal_period (f ∘ g) x ∣ (minimal_period f x) * (minimal_period g x) :=
dvd_trans h.minimal_period_of_comp_dvd_lcm (lcm_dvd_mul _ _)

lemma commute.minimal_period_of_comp_eq_mul_of_coprime {g : α → α} (h : function.commute f g)
  (hco : coprime (minimal_period f x) (minimal_period g x)) :
  minimal_period (f ∘ g) x = (minimal_period f x) * (minimal_period g x) :=
begin
  apply dvd_antisymm (h.minimal_period_of_comp_dvd_mul),
  suffices : ∀ {f g : α → α}, commute f g → coprime (minimal_period f x) (minimal_period g x) →
    minimal_period f x ∣ minimal_period (f ∘ g) x,
    from hco.mul_dvd_of_dvd_of_dvd (this h hco) (h.comp_eq.symm ▸ this h.symm hco.symm),
  clear hco h f g,
  intros f g h hco,
  refine hco.dvd_of_dvd_mul_left (is_periodic_pt.left_of_comp h _ _).minimal_period_dvd,
  { exact (is_periodic_pt_minimal_period _ _).const_mul _ },
  { exact (is_periodic_pt_minimal_period _ _).mul_const _ }
end

private lemma minimal_period_iterate_eq_div_gcd_aux (h : 0 < gcd (minimal_period f x) n) :
  minimal_period (f ^[n]) x = minimal_period f x / nat.gcd (minimal_period f x) n :=
begin
  apply nat.dvd_antisymm,
  { apply is_periodic_pt.minimal_period_dvd,
    rw [is_periodic_pt, is_fixed_pt, ← iterate_mul, ← nat.mul_div_assoc _ (gcd_dvd_left _ _),
        mul_comm, nat.mul_div_assoc _ (gcd_dvd_right _ _), mul_comm, iterate_mul],
    exact (is_periodic_pt_minimal_period f x).iterate _ },
  { apply coprime.dvd_of_dvd_mul_right (coprime_div_gcd_div_gcd h),
    apply dvd_of_mul_dvd_mul_right h,
    rw [nat.div_mul_cancel (gcd_dvd_left _ _), mul_assoc, nat.div_mul_cancel (gcd_dvd_right _ _),
        mul_comm],
    apply is_periodic_pt.minimal_period_dvd,
    rw [is_periodic_pt, is_fixed_pt, iterate_mul],
    exact is_periodic_pt_minimal_period _ _ }
end

lemma minimal_period_iterate_eq_div_gcd (h : n ≠ 0) :
  minimal_period (f ^[n]) x = minimal_period f x / nat.gcd (minimal_period f x) n :=
minimal_period_iterate_eq_div_gcd_aux $ gcd_pos_of_pos_right _ (nat.pos_of_ne_zero h)

lemma minimal_period_iterate_eq_div_gcd' (h : x ∈ periodic_pts f) :
  minimal_period (f ^[n]) x = minimal_period f x / nat.gcd (minimal_period f x) n :=
minimal_period_iterate_eq_div_gcd_aux $
  gcd_pos_of_pos_left n (minimal_period_pos_iff_mem_periodic_pts.mpr h)

/-- The orbit of a periodic point `x` of `f` is the cycle `[x, f x, f (f x), ...]`. Its length is
the minimal period of `x`.

If `x` is not a periodic point, then this is the empty (aka nil) cycle. -/
def periodic_orbit (f : α → α) (x : α) : cycle α :=
(list.range (minimal_period f x)).map (λ n, f^[n] x)

/-- The definition of a periodic orbit, in terms of `list.map`. -/
lemma periodic_orbit_def (f : α → α) (x : α) :
  periodic_orbit f x = (list.range (minimal_period f x)).map (λ n, f^[n] x) :=
rfl

/-- The definition of a periodic orbit, in terms of `cycle.map`. -/

@[simp] lemma periodic_orbit_length : (periodic_orbit f x).length = minimal_period f x :=
by rw [periodic_orbit, cycle.length_coe, list.length_map, list.length_range]

@[simp] lemma periodic_orbit_eq_nil_iff_not_periodic_pt :
  periodic_orbit f x = cycle.nil ↔ x ∉ periodic_pts f :=
by { simp [periodic_orbit], exact minimal_period_eq_zero_iff_nmem_periodic_pts }

lemma periodic_orbit_eq_nil_of_not_periodic_pt (h : x ∉ periodic_pts f) :
  periodic_orbit f x = cycle.nil :=
periodic_orbit_eq_nil_iff_not_periodic_pt.2 h

@[simp] lemma mem_periodic_orbit_iff (hx : x ∈ periodic_pts f) :
  y ∈ periodic_orbit f x ↔ ∃ n, f^[n] x = y :=
begin
  simp only [periodic_orbit, cycle.mem_coe_iff, list.mem_map, list.mem_range],
  use λ ⟨a, ha, ha'⟩, ⟨a, ha'⟩,
  rintro ⟨n, rfl⟩,
  use [n % minimal_period f x, mod_lt _ (minimal_period_pos_of_mem_periodic_pts hx)],
  rw iterate_mod_minimal_period_eq
end

@[simp] lemma iterate_mem_periodic_orbit (hx : x ∈ periodic_pts f) (n : ℕ) :
  f^[n] x ∈ periodic_orbit f x :=
(mem_periodic_orbit_iff hx).2 ⟨n, rfl⟩

@[simp] lemma self_mem_periodic_orbit (hx : x ∈ periodic_pts f) : x ∈ periodic_orbit f x :=
iterate_mem_periodic_orbit hx 0

lemma nodup_periodic_orbit : (periodic_orbit f x).nodup :=
begin
  rw [periodic_orbit, cycle.nodup_coe_iff, list.nodup_map_iff_inj_on (list.nodup_range _)],
  intros m hm n hn hmn,
  rw list.mem_range at hm hn,
  rwa eq_iff_lt_minimal_period_of_iterate_eq hm hn at hmn
end

lemma periodic_orbit_apply_iterate_eq (hx : x ∈ periodic_pts f) (n : ℕ) :
  periodic_orbit f (f^[n] x) = periodic_orbit f x :=
eq.symm $ cycle.coe_eq_coe.2 $ ⟨n, begin
  apply list.ext_le _ (λ m _ _, _),
  { simp [minimal_period_apply_iterate hx] },
  { rw list.nth_le_rotate _ n m,
    simp [iterate_add_apply] }
end⟩

lemma periodic_orbit_apply_eq (hx : x ∈ periodic_pts f) :
  periodic_orbit f (f x) = periodic_orbit f x :=
periodic_orbit_apply_iterate_eq hx 1

theorem periodic_orbit_chain (r : α → α → Prop) {f : α → α} {x : α} :
  (periodic_orbit f x).chain r ↔ ∀ n < minimal_period f x, r (f^[n] x) (f^[n+1] x) :=
begin
  by_cases hx : x ∈ periodic_pts f,
  { have hx' := minimal_period_pos_of_mem_periodic_pts hx,
    have hM := nat.sub_add_cancel (succ_le_iff.2 hx'),
    rw [periodic_orbit, ←cycle.map_coe, cycle.chain_map, ←hM, cycle.chain_range_succ],
    refine ⟨_, λ H, ⟨_, λ m hm, H _ (hm.trans (nat.lt_succ_self _))⟩⟩,
    { rintro ⟨hr, H⟩ n hn,
      cases eq_or_lt_of_le (lt_succ_iff.1 hn) with hM' hM',
      { rwa [hM', hM, iterate_minimal_period] },
      { exact H _ hM' } },
    { rw iterate_zero_apply,
      nth_rewrite 2 ←@iterate_minimal_period α f x,
      nth_rewrite 1 ←hM,
      exact H _ (nat.lt_succ_self _) } },
  { rw [periodic_orbit_eq_nil_of_not_periodic_pt hx,
      minimal_period_eq_zero_of_nmem_periodic_pts hx],
    simp }
end

theorem periodic_orbit_chain' (r : α → α → Prop) {f : α → α} {x : α} (hx : x ∈ periodic_pts f) :
  (periodic_orbit f x).chain r ↔ ∀ n, r (f^[n] x) (f^[n+1] x) :=
begin
  rw periodic_orbit_chain r,
  refine ⟨λ H n, _, λ H n _, H n⟩,
  rw [iterate_succ_apply, ←iterate_mod_minimal_period_eq],
  nth_rewrite 1 ←iterate_mod_minimal_period_eq,
  rw [←iterate_succ_apply, minimal_period_apply hx],
  exact H _ (mod_lt _ (minimal_period_pos_of_mem_periodic_pts hx))
end

end function

namespace mul_action

open function

variables {α β : Type*} [group α] [mul_action α β] {a : α} {b : β}

@[to_additive] lemma pow_smul_eq_iff_minimal_period_dvd {n : ℕ} :
  a ^ n • b = b ↔ function.minimal_period ((•) a) b ∣ n :=
by rw [←is_periodic_pt_iff_minimal_period_dvd, is_periodic_pt, is_fixed_pt, smul_iterate]

@[to_additive] lemma zpow_smul_eq_iff_minimal_period_dvd {n : ℤ} :
  a ^ n • b = b ↔ (function.minimal_period ((•) a) b : ℤ) ∣ n :=
begin
  cases n,
  { rw [int.of_nat_eq_coe, zpow_coe_nat, int.coe_nat_dvd, pow_smul_eq_iff_minimal_period_dvd] },
  { rw [int.neg_succ_of_nat_coe, zpow_neg, zpow_coe_nat, inv_smul_eq_iff, eq_comm,
        dvd_neg, int.coe_nat_dvd, pow_smul_eq_iff_minimal_period_dvd] },
end

variables (a b)

@[simp, to_additive] lemma pow_smul_mod_minimal_period (n : ℕ) :
  a ^ (n % function.minimal_period ((•) a) b) • b = a ^ n • b :=
by conv_rhs { rw [← nat.mod_add_div n (minimal_period ((•) a) b), pow_add, mul_smul,
    pow_smul_eq_iff_minimal_period_dvd.mpr (dvd_mul_right _ _)] }

@[simp, to_additive] lemma zpow_smul_mod_minimal_period (n : ℤ) :
  a ^ (n % (function.minimal_period ((•) a) b : ℤ)) • b = a ^ n • b :=
by conv_rhs { rw [← int.mod_add_div n (minimal_period ((•) a) b), zpow_add, mul_smul,
    zpow_smul_eq_iff_minimal_period_dvd.mpr (dvd_mul_right _ _)] }

end mul_action


import tuto_lib

section
/-
The first part of this file makes sure you can negate quantified statements
in your head without the help of `push_neg`.

You need to complete the statement and then use the `check_me` tactic
to check your answer. This tactic exists only for those exercises,
it mostly calls `push_neg` and then cleans up a bit.

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

-- In this section, u denotes a sequence of real numbers
-- f is a function from ℝ to ℝ
-- x₀ and l are real numbers
variables (u : ℕ → ℝ) (f : ℝ → ℝ) (x₀ l : ℝ)

/- Negation of "u tends to l" -/
-- 0062
example : ¬ (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - l| ≤ ε) ↔ (∃ ε > 0, ∀ N, ∃ n ≥ N, |u n - l| > ε)
:=
begin
  check_me,
end

/- Negation of "f is continuous at x₀" -/
-- 0063
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x, |x - x₀| ≤ δ →  |f x - f x₀| ≤ ε) ↔ (∃ ε > 0, ∀ δ > 0, ∃ x, |x - x₀| ≤ δ ∧ |f x - f x₀| > ε)
:=
begin
  check_me,
end

/-
In the next exercise, we need to keep in mind that
`∀ x x', ...` is the abbreviation of
`∀ x, ∀ x', ... `.

Also, `∃ x x', ...` is the abbreviation of `∃ x, ∃ x', ...`.
-/

/- Negation of "f is uniformly continuous on ℝ" -/
-- 0064
example : ¬ (∀ ε > 0, ∃ δ > 0, ∀ x x', |x' - x| ≤ δ →  |f x' - f x| ≤ ε) ↔ (∃ ε > 0, ∀ δ > 0, ∃ x x', |x' - x| ≤ δ ∧ |f x' - f x| > ε)
:=
begin
  check_me,
end

/- Negation of "f is sequentially continuous at x₀" -/
-- 0065
example : ¬ (∀ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) → (∀ ε > 0, ∃ N, ∀ n ≥ N, |(f ∘ u) n - f x₀| ≤ ε))  ↔ (∃ u : ℕ → ℝ, (∀ ε > 0, ∃ N, ∀ n ≥ N, |u n - x₀| ≤ ε) ∧ (∃ ε > 0, ∀ N, ∃ n ≥ N, |(f ∘ u) n - f x₀| > ε))
:=
begin
  check_me,
end
end

/-
We now turn to elementary applications of negations to limits of sequences.
Remember that `linarith` can find easy numerical contradictions.

Also recall the following lemmas:

abs_le {x y : ℝ} : |x| ≤ y ↔ -y ≤ x ∧ x ≤ y

ge_max_iff (p q r) : r ≥ max p q  ↔ r ≥ p ∧ r ≥ q

le_max_left p q : p ≤ max p q

le_max_right p q : q ≤ max p q

/-- The sequence `u` tends to `+∞`. -/
def tendsto_infinity (u : ℕ → ℝ) := ∀ A, ∃ N, ∀ n ≥ N, u n ≥ A
-/

-- 0066
example {u : ℕ → ℝ} : tendsto_infinity u → ∀ l, ¬ seq_limit u l :=
begin
  intros ha l hc,
  specialize hc 1 (by linarith),
  specialize ha (2+l),
  cases hc with N hN,
  cases ha with N' hN',
  specialize hN (max N N') (le_max_left N N'),
  specialize hN' (max N N') (le_max_right N N'),
  rw abs_le at hN,
  linarith,
end

def nondecreasing_seq (u : ℕ → ℝ) := ∀ n m, n ≤ m → u n ≤ u m

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

/-
In the following exercises, `A : set ℝ` means that A is a set of real numbers.
We can use the usual notation x ∈ A.

The notation `∀ x ∈ A, ...` is the abbreviation of `∀ x, x ∈ A → ... `

The notation `∃ x ∈ A, ...` is the abbreviation of `∃ x, x ∈ A ∧ ... `.
More precisely it is the abbreviation of `∃ x (H : x ∈ A), ...`
which is Lean's strange way of saying `∃ x, x ∈ A ∧ ... `.
You can convert between these forms using the lemma
  exists_prop {p q : Prop} : (∃ (h : p), q) ↔ p ∧ q

We'll work with upper bounds and supremums.
Again we'll introduce specialized definitions for the sake of exercises, but mathlib
has more general versions.

def upper_bound (A : set ℝ) (x : ℝ) := ∀ a ∈ A, a ≤ x

def is_sup (A : set ℝ) (x : ℝ) := upper_bound A x ∧ ∀ y, upper_bound A y → x ≤ y

Remark: one can easily show that a set of real numbers has at most one sup,
but we won't need this.
-/

-- 0068
example {A : set ℝ} {x : ℝ} (hx : is_sup A x) :
∀ y, y < x → ∃ a ∈ A, y < a :=
begin
  intros y hy,
  cases hx with hxa hxb,
  by_contradiction H,
  push_neg at H,
  specialize hxb y,
  have key : x ≤ y,
  { apply hxb,
  exact H,},
  linarith,
end

/-
Let's do a variation on an example from file 07 that will be useful in the last
exercise below.
-/

-- 0069
lemma le_of_le_add_all' {x y : ℝ} :
  (∀ ε > 0, y ≤ x + ε) →  y ≤ x :=
begin
  intro h,
  by_contradiction H,
  push_neg at H,
  specialize h ((y-x)/2) (by linarith),
  linarith,
end

-- 0070
example {x y : ℝ} {u : ℕ → ℝ} (hu : seq_limit u x)
  (ineg : ∀ n, u n ≤ y) : x ≤ y :=
begin
  unfold seq_limit at hu,
  apply le_of_le_add_all',
  intros ε ε_pos,
  specialize hu ε ε_pos,
  cases hu with N hN,
  specialize ineg N,
  specialize hN N (by linarith),
  rw abs_le at hN,
  cases hN with lef righ,
  linarith,
end


import data.nat.gcd

namespace nat

-- lemma gcd_eq_zero_iff {m n : ℕ} :
-- 	m.gcd n = 0 ↔ m = 0 ∧ n = 0 :=
-- ⟨λ h, ⟨eq_zero_of_gcd_eq_zero_left h, eq_zero_of_gcd_eq_zero_right h⟩,
-- 	λ ⟨hm, hn⟩, hn ▸ hm.symm ▸ gcd_zero_left n⟩

lemma lcm_eq_zero_iff {m n : ℕ} :
	m.lcm n = 0 ↔ m = 0 ∨ n = 0 :=
begin
	split, swap,
	{	rintro (rfl | rfl), { rw lcm_zero_left }, rw lcm_zero_right },
	unfold lcm, intro h,
	by_contradiction hmn,
	push_neg at hmn,
	rw nat.div_eq_zero_iff at h, swap,
	{ apply nat.pos_of_ne_zero, simp [gcd_eq_zero_iff], intro, exact hmn.2 },
	apply (not_lt_of_ge _) h,
	rw ← nat.one_mul (m.gcd n),
	apply mul_le_mul (succ_le_of_lt $ nat.pos_of_ne_zero hmn.1) _ (zero_le _) (zero_le _),
	apply nat.le_of_dvd (nat.pos_of_ne_zero hmn.2) (gcd_dvd_right _ _),
end

end nat