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

import avlnode.init
import tactic.linarith

/-!
# AVL node basic theorems

This file follows from `avlnode.init.lean` and specifies a series of lemmas which form the basis
of a proof that the set of AVL trees (defined by the `well_balanced`) is closed under the `ins`
operation defined in the init file. In this file if a node n is referred to it is referencing an
object with type avlnode α.

## Main definitions

* `avlnode.neg_1_le_dep_n` is a proof that the minimum depth of an avlnode is -1 (in the case it is the
empty tree).

* `avlnode.dep_n_eq_neg_1_iff_n_nil` is a proof that the depth of a tree is -1 iff the tree is the
empty tree.

* `avlnode.dep_n_eq_0_iff_n_leaf` is a proof that the depth of a tree is 0 iff the tree is a leaf node.

* `avlnode.dep_l_v_nil_eq_dep_l_add_1` is a proof that the the depth of a tree with just one left subtree
is the depth of that subtree add 1.

* `avlnode.dep_nil_v_r_eq_dep_r_add_1` is a proof that the the depth of a tree with just one right subtree
is the depth of that subtree add 1.

* `avlnode.dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1` is a proof that given a node n, if the depth of
the left subtree is less than or equal to the depth of the right subtree, then the depth of n is
equal to the depth of the right subtree add 1.

* `avlnode.dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1` is a proof that given a node n, if the depth of
the right subtree is less than or equal to the depth of the left subtree, then the depth of n is
equal to the depth of the left subtree add 1.

* `avlnode.dep_l_add_1_le_dep_l_v_r` is a proof that given a node n, the depth of the left subtree add 1
is less than the depth of n.

* `avlnode.dep_r_add_1_le_dep_l_v_r` is a proof that given a node n, the depth of the right subtree add 1
is less than the depth of n.

* `avlnode.dep_bal_n_le_dep_n` is a proof that the balance operation does not increase the depth of a node
n.

* `avlnode.wb_n_then_wb_ins_n_val` is a proof that the well balanced property is closed under the defined
`ins` operation.

* `avlnode.wb_nil` is a proof that the empty tree is well_balanced (so we can construct avl trees using
this and an ins operation).
-/

open classical
open avlnode

universe u

namespace avlnode
variables {α : Type u} [linear_order α]

/--
Proof that the minimum depth of an avlnode is -1 (in the case it is the empty tree).
-/
lemma neg_1_le_dep_n (n : avlnode α) : -1 ≤ depth n :=
begin
  induction n with l val r hl hr,
  rw depth,
  rw depth,
  cases classical.em (l.depth ≤ r.depth),
  rw if_pos,
  have ha := int.add_le_add hr (int.coe_zero_le 1),
  rw int.add_zero at ha,
  exact ha,
  exact h,
  rw if_neg,
  have ha := int.add_le_add hl (int.coe_zero_le 1),
  rw int.add_zero at ha,
  exact ha,
  exact h,
end

/--
Proof that the depth of a tree is -1 iff the tree is the empty tree.
-/
@[simp] lemma dep_n_eq_neg_1_iff_n_nil (n : avlnode α)
  : depth n = -1 ↔ n = nil :=
begin
  rw iff_def,
  split,
  intro h,
  cases n,
  refl,
  exfalso,
  cases classical.em (n_left.depth ≤ n_right.depth),
  rw [depth, if_pos] at h,
  have h2 := neg_1_le_dep_n n_right,
  linarith,
  exact h_1,
  rw [depth, if_neg] at h,
  have h2 := neg_1_le_dep_n n_left,
  linarith,
  exact h_1,
  intro h,
  rw [h, depth],
end

/--
Proof that the depth of a tree is 0 iff the tree is a leaf node.
-/
lemma dep_n_eq_0_iff_n_leaf (n : avlnode α)
  : depth n = 0 ↔ ∃ (val : α), n = (node nil val nil) :=
begin
  cases n,
  rw iff_def,
  split,
  intro h,
  rw depth at h,
  exfalso,
  linarith,
  intro h,
  cases h with val h,
  exfalso,
  trivial,

  rw iff_def,
  split,
  intro h,
  have hl1 := neg_1_le_dep_n n_left,
  have hr1 := neg_1_le_dep_n n_right,
  cases classical.em (n_left.depth ≤ n_right.depth),
  rw [depth, if_pos] at h,
  have hl2 : n_left.depth = -1 := begin linarith end,
  have hr2 : n_right.depth = -1 := begin linarith end,
  rw dep_n_eq_neg_1_iff_n_nil at hl2,
  rw dep_n_eq_neg_1_iff_n_nil at hr2,
  rw [hl2, hr2],
  use n_val,
  exact h_1,
  rw [depth, if_neg] at h,
  have hr2 : n_right.depth = -1 := begin linarith end,
  have hl2 : n_left.depth = -1 := begin linarith end,
  rw dep_n_eq_neg_1_iff_n_nil at hl2,
  rw dep_n_eq_neg_1_iff_n_nil at hr2,
  rw [hl2, hr2],
  use n_val,
  exact h_1,
  intro h,
  cases h with val h,
  rw [h, depth, if_pos, depth],
  simp,
end

/--
Proof that the the depth of a tree with just one left subtree is the depth of that subtree add 1.
-/
@[simp] lemma dep_l_v_nil_eq_dep_l_add_1 (l : avlnode α) (v : α)
  : (node l v nil).depth = l.depth + 1 :=
begin
  rw depth,
  cases classical.em (l.depth ≤ nil.depth),
  rw [if_pos, depth],
  rw depth at h,
  have h1 := neg_1_le_dep_n l,
  have h2 := le_antisymm h1 h,
  rw h2,
  exact h,
  rw if_neg,
  exact h,
end

/--
Proof that the the depth of a tree with just one right subtree is the depth of that subtree add 1.
-/
@[simp] lemma dep_nil_v_r_eq_dep_r_add_1 (r : avlnode α) (v : α)
  : (node nil v r).depth = r.depth + 1 :=
begin
  rw [depth, if_pos],
  rw depth,
  exact neg_1_le_dep_n r,
end

/--
Proof that given a node n, if the depth of the left subtree is less than or equal to the depth of
the right subtree, then the depth of n is equal to the depth of the right subtree add 1.
-/
lemma dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (l r : avlnode α) (v : α) (h : depth l ≤ depth r)
  : depth (node l v r) = depth r + 1 :=
begin
  rw [depth, if_pos],
  exact h,
end

/--
Proof that given a node n, if the depth of the right subtree is less than or equal to the depth of
the left subtree, then the depth of n is equal to the depth of the left subtree add 1.
-/
lemma dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (l r : avlnode α) (v : α) (h : depth r ≤ depth l)
  : depth (node l v r) = depth l + 1 :=
begin
  rw depth,
  cases classical.em (l.depth ≤ r.depth),
  rw if_pos,
  linarith,
  exact h_1,
  rw if_neg,
  exact h_1,
end

/--
Proof that given a node n, the depth of the left subtree add 1 is less than the depth of n.
-/
lemma dep_l_add_1_le_dep_l_v_r
  : ∀ (l r : avlnode α) (v : α), l.depth + 1 ≤ (node l v r).depth
| nil r _ :=
  begin
    rw [depth, if_pos, depth],
    have h := int.add_le_add (neg_1_le_dep_n r) (le_refl 1),
    exact h,
    rw depth,
    exact neg_1_le_dep_n r,
  end
| (node l_l _ l_r) r _ :=
  begin
    rw depth,
    cases classical.em (l_l.depth ≤ l_r.depth),
    rw [if_pos, depth],
    cases classical.em ((l_l.node _x l_r).depth ≤ r.depth),
    rw if_pos,
    rw [depth, if_pos] at h_1,
    have h2 := int.add_le_add h_1 (le_refl 1),
    exact h2,
    exact h,
    exact h_1,
    rw [if_neg, depth, if_pos],
    exact h,
    exact h_1,
    exact h,
    rw [if_neg, depth],
    cases classical.em ((l_l.node _x l_r).depth ≤ r.depth),
    rw if_pos,
    rw [depth, if_neg] at h_1,
    have h2 := int.add_le_add h_1 (le_refl 1),
    exact h2,
    exact h,
    exact h_1,
    rw [if_neg, depth, if_neg],
    exact h,
    exact h_1,
    exact h,
  end

/--
Proof that given a node n, the depth of the right subtree add 1 is less than the depth of n.
-/
lemma dep_r_add_1_le_dep_l_v_r
  : ∀ (l r : avlnode α) (v : α), r.depth + 1 ≤ (node l v r).depth
| l nil _              :=
  begin
    rw depth,
    cases classical.em (l.depth ≤ nil.depth),
    rw if_pos,
    exact h,
    rw if_neg,
    exact (int.add_le_add (neg_1_le_dep_n l) (le_refl 1)),
    exact h,
  end
| l (node r_l _ r_r) _ :=
  begin
    rw depth,
    cases classical.em (r_l.depth ≤ r_r.depth),
    rw [if_pos, depth],
    cases classical.em (l.depth ≤ (r_l.node _x r_r).depth),
    rw [if_pos, depth, if_pos],
    exact h,
    rw [depth, if_pos],
    rw [depth, if_pos] at h_1,
    exact h_1,
    exact h,
    exact h,
    rw if_neg,
    rw [depth, if_pos, not_le] at h_1,
    have h2 := int.add_le_add (le_of_lt h_1) (le_refl 1),
    exact h2,
    exact h,
    exact h_1,
    exact h,
    rw [if_neg, depth],
    cases classical.em (l.depth ≤ (r_l.node _x r_r).depth),
    rw [if_pos, depth, if_neg],
    exact h,
    exact h_1,
    rw if_neg,
    rw [depth, if_neg, not_le] at h_1,
    have h2 := int.add_le_add (le_of_lt h_1) (le_refl 1),
    exact h2,
    exact h,
    exact h_1,
    exact h,
  end

private lemma bf_n_eq_2_then_bf_bal_n_le_1
  : ∀ (n : avlnode α), balance_factor n = 2 → balance_factor (balance n) ≤ 1
| nil :=
  begin
    intro h,
    rw balance_factor at h,
    exfalso,
    have h2 : ¬(0 : int) = (2 : int) := begin simp end,
    exact h2 h,
  end
| (node nil valX nil) :=
  begin
    intro h,
    rw [balance_factor, depth] at h,
    have h2 : ¬((-1 : int) - (-1 : int) = 2) := begin simp end,
    exfalso,
    exact h2 h,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    intro h,
    rw [balance, if_neg, balance_factor, depth, depth],
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw if_pos,
    have h1 := neg_1_le_dep_n rZ,
    linarith,
    exact h_1,
    rw if_neg,
    have h1 := neg_1_le_dep_n lZ,
    linarith,
    exact h_1,
    linarith,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance, if_pos],
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw [if_pos, rotate_left, balance_factor, depth, if_pos],
    rw [balance_factor, depth, if_pos, depth] at h,
    have h2 := neg_1_le_dep_n lZ,
    linarith,
    exact h_1,
    rw depth,
    exact neg_1_le_dep_n lZ,
    linarith,
    rw if_neg,
    cases lZ,
    rw depth at h_1,
    have h2 := neg_1_le_dep_n rZ,
    linarith,
    rw [rotate_right, rotate_left, balance_factor, depth],
    rw [balance_factor, depth, if_neg, depth, depth] at h,
    cases classical.em (lZ_left.depth ≤ rZ.depth),
    rw [if_pos, depth, if_pos],
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    rw if_pos at h,
    have h2 : lZ_right.depth = -1 := begin linarith end,
    rw h2 at h_3,
    have h3 := le_antisymm (neg_1_le_dep_n lZ_left) h_3,
    rw [depth, if_pos, not_le, int.lt_iff_add_one_le] at h_1,
    rw ← h3,
    rw h2 at h_1,
    linarith,
    rw [h2, ← h3],
    exact h_3,
    rw if_neg at h,
    have h2 : lZ_left.depth = -1 := begin linarith end,
    rw [depth, if_neg, not_le, int.lt_iff_add_one_le] at h_1,
    rw h2,
    rw h2 at h_1,
    linarith,
    exact h_3,
    exact h_3,
    rw depth,
    exact neg_1_le_dep_n lZ_left,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    rw if_pos at h,
    have h2 : lZ_right.depth = -1 := begin linarith end,
    rw h2,
    exact neg_1_le_dep_n rZ,
    exact h_3,
    have h2 := le_of_not_le h_3,
    exact le_trans h2 h_2,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    rw [if_pos, depth, if_pos],
    linarith,
    rw depth,
    exact neg_1_le_dep_n lZ_left,
    exact h_3,
    rw [if_neg, depth, if_pos],
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    rw if_pos at h,
    have h2 : lZ_right.depth = -1 := begin linarith end,
    rw h2 at h_4,
    have h3 := le_antisymm h_4 (neg_1_le_dep_n lZ_left),
    rw [h2, h3],
    linarith,
    exact h_4,
    have h2 := le_of_not_le h_4,
    linarith,
    rw depth,
    exact neg_1_le_dep_n lZ_left,
    exact h_3,
    exact h_1,
    linarith,
    linarith,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance, if_pos],
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw [if_pos, rotate_left, balance_factor, depth],
    rw [balance_factor, depth, if_pos] at h,
    cases classical.em ((lY.node valY rY).depth ≤ lZ.depth),
    rw if_pos,
    linarith,
    exact h_2,
    rw if_neg,
    linarith,
    exact h_2,
    exact h_1,
    linarith,
    rw if_neg,
    cases lZ,
    rw [balance_factor, depth, if_neg, depth, depth] at h,
    have h2 := neg_1_le_dep_n (lY.node valY rY),
    rw depth at h2,
    linarith,
    exact h_1,
    rw [rotate_right, rotate_left, balance_factor, depth],
    rw [balance_factor, depth, if_neg] at h,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    rw [if_pos, depth],
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    rw if_pos,
    have h2 := dep_l_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    linarith,
    exact h_3,
    rw if_neg,
    linarith,
    exact h_3,
    exact h_2,
    rw [if_neg, depth],
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    rw if_pos,
    cases classical.em (lZ_right.depth ≤ lZ_left.depth + 1),
    linarith,
    have h2 := dep_r_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    linarith,
    exact h_3,
    rw if_neg,
    have h2 := dep_r_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    linarith,
    exact h_3,
    exact h_2,
    exact h_1,
    linarith,
    linarith,
  end

private lemma bf_n_eq_neg_2_then_neg_1_le_bf_bal_n
  : ∀ (n : avlnode α), balance_factor n = -2 → -1 ≤ balance_factor (balance n)
| nil :=
  begin
    intro h,
    rw [balance, balance_factor],
    simp,
  end
| (node nil valX nil) :=
  begin
    intro h,
    rw [balance, balance_factor, depth],
    simp,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    intro h,
    cases classical.em (((lZ.node valZ rZ).node valX nil).balance_factor ≤ -2),
    rw [balance, if_pos],
    rw balance_factor at h,
    cases classical.em (rZ.depth ≤ lZ.depth),
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ h_2,
    rw [h2, depth] at h,
    rw [if_pos, rotate_right, balance_factor],
    have h3 := neg_1_le_dep_n (rZ.node valX nil),
    linarith,
    linarith,
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ (le_of_not_le h_2),
    rw [h2, depth] at h,
    cases rZ,
    rw depth at h_2,
    have h3 := neg_1_le_dep_n lZ,
    linarith,
    rw [if_neg, rotate_left, rotate_right, balance_factor],
    have h3 := dep_l_v_nil_eq_dep_l_add_1 rZ_right valX,
    have h4 := neg_1_le_dep_n rZ_left,
    have h5 := neg_1_le_dep_n rZ_right,
    have h6 := dep_l_add_1_le_dep_l_v_r rZ_left rZ_right rZ_val,
    have h7 := dep_r_add_1_le_dep_l_v_r rZ_left rZ_right rZ_val,
    cases classical.em (lZ.depth ≤ rZ_left.depth),
    have h8 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ_left valZ h_3,
    linarith,
    have h8 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ_left valZ (le_of_not_le h_3),
    linarith,
    linarith,
    exact h_1,
    rw [balance, if_neg],
    linarith,
    exact h_1,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    intro h,
    have h2 := neg_1_le_dep_n lZ,
    have h3 := dep_l_add_1_le_dep_l_v_r lZ rZ valZ,
    rw [balance_factor, depth, depth] at h,
    rw depth at h3,
    linarith,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance, if_neg, if_pos],
    rw balance_factor at h,
    cases classical.em (rY.depth ≤ lY.depth),
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY valY h_1,
    rw [if_pos, rotate_right, balance_factor],
    cases classical.em (rY.depth ≤ (lZ.node valZ rZ).depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY (lZ.node valZ rZ) valX h_2,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY (lZ.node valZ rZ) valX (le_of_not_le h_2),
    linarith,
    linarith,
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY valY (le_of_not_le h_1),
    cases rY,
    rw depth at h_1,
    have h3 := neg_1_le_dep_n lY,
    linarith,
    rw [if_neg, rotate_left, rotate_right, balance_factor],
    cases classical.em (rY_right.depth ≤ (lZ.node valZ rZ).depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_right (lZ.node valZ rZ) valX h_2,
    cases classical.em (lY.depth ≤ rY_left.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY_left valY h_3,
    have h5 := dep_l_add_1_le_dep_l_v_r rY_left rY_right rY_val,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY_left valY (le_of_not_le h_3),
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_right (lZ.node valZ rZ) valX (le_of_not_le h_2),
    cases classical.em (lY.depth ≤ rY_left.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY_left valY h_3,
    have h5 := dep_l_add_1_le_dep_l_v_r rY_left rY_right rY_val,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY_left valY (le_of_not_le h_3),
    cases classical.em (rY_left.depth ≤ rY_right.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_left rY_right rY_val h_4,
    linarith,
    linarith,
    linarith,
    linarith,
    linarith,
  end

private lemma children_wb_and_bf_n_le_1_and_neg_2_le_bf_n_then_bf_bal_b_le_1
  : ∀ (l r : avlnode α) (val : α), well_balanced l ∧ well_balanced r → balance_factor (node l val r) ≤ 1 → -2 ≤ balance_factor (node l val r) → balance_factor (balance (node l val r)) ≤ 1
| nil nil val :=
  begin
    intros h1 h2 h3,
    rw [balance, balance_factor, depth],
    linarith,
  end
| (node lZ valZ rZ) nil val :=
  begin
    intros h1 h2 h3,
    cases classical.em (((lZ.node valZ rZ).node val nil).balance_factor ≤ -2),
    cases classical.em (rZ.depth ≤ lZ.depth),
    rw [balance, if_pos, if_pos, rotate_right, balance_factor],
    have h3 := dep_l_v_nil_eq_dep_l_add_1 rZ val,
    linarith,
    linarith,
    exact h,
    rw [balance, if_pos, if_neg],
    cases rZ,
    have h3 := neg_1_le_dep_n lZ,
    rw depth at h_1,
    linarith,
    rw [rotate_left, rotate_right, balance_factor],
    rw [balance_factor, depth, if_pos] at h3,
    cases classical.em (rZ_left.depth ≤ rZ_right.depth),
    rw [depth, if_pos, depth] at h3,
    have h4 := dep_l_v_nil_eq_dep_l_add_1 rZ_right val,
    have h5 := neg_1_le_dep_n (lZ.node valZ rZ_left),
    linarith,
    exact h_2,
    rw [depth, if_neg, depth] at h3,
    have h4 := neg_1_le_dep_n rZ_right,
    linarith,
    exact h_2,
    linarith,
    linarith,
    exact h,
    rw [balance, if_neg],
    exact h2,
    exact h,
  end
| nil (node lZ valZ rZ) val :=
  begin
    intros h1 h2 h3,
    rw [balance, if_neg],
    exact h2,
    linarith,
  end
| (node lY valY rY) (node lZ valZ rZ) val :=
  begin
    intros h1 h2 h3,
    rw [balance, if_neg],
    cases classical.em (((lY.node valY rY).node val (lZ.node valZ rZ)).balance_factor = -2),
    rw if_pos,
    cases classical.em (0 ≤ lY.depth - rY.depth),
    rw [if_pos, rotate_right, balance_factor, depth],
    cases classical.em (rY.depth ≤ (lZ.node valZ rZ).depth),
    rw if_pos,
    rw balance_factor at h,
    have h3 : rY.depth ≤ lY.depth := begin linarith end,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY valY h3,
    linarith,
    exact h_2,
    rw if_neg,
    linarith,
    exact h_2,
    exact h_1,
    rw if_neg,
    cases rY,
    have h3 := neg_1_le_dep_n lY,
    rw depth at h_1,
    linarith,
    have h_6 := and.elim_left h1,
    rw well_balanced at h_6,
    have h_7 := and.elim_right (and.elim_right (and.elim_right h_6)),
    rw well_balanced at h_7,
    have h_8 := and.elim_left h_7,
    rw balance_factor at h_8,
    rw [rotate_left, rotate_right, balance_factor, depth],
    cases classical.em (rY_right.depth ≤ (lZ.node valZ rZ).depth),
    rw if_pos,
    cases classical.em (lY.depth ≤ rY_left.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY_left valY h_3,
    have h4 : lY.depth ≤ (rY_left.node rY_val rY_right).depth := begin linarith end,
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY (rY_left.node rY_val rY_right) valY h4,
    cases classical.em (rY_right.depth ≤ rY_left.depth),
    rw [balance_factor, h5] at h,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_left rY_right rY_val h_4,
    linarith,
    rw [balance_factor, h5] at h,
    have h9 : rY_left.depth ≤ rY_right.depth := begin linarith end,
    have h10 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_left rY_right rY_val h9,
    linarith,
    have h4 : rY_left.depth ≤ lY.depth := begin linarith end,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY_left valY h4,
    have h6 : lY.depth ≤ (rY_left.node rY_val rY_right).depth := begin linarith end,
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY (rY_left.node rY_val rY_right) valY h6,
    rw [balance_factor, h7] at h,
    cases classical.em (rY_left.depth ≤ rY_right.depth),
    have h8 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_left rY_right rY_val h_4,
    linarith,
    have h8 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_left rY_right rY_val (le_of_not_le h_4),
    linarith,
    exact h_2,
    rw if_neg,
    have h9 := dep_r_add_1_le_dep_l_v_r lY rY_left valY,
    linarith,
    exact h_2,
    exact h_1,
    linarith,
    rw if_neg,
    exact h2,
    have h4 : -2 = ((lY.node valY rY).node val (lZ.node valZ rZ)).balance_factor → -2 < ((lY.node valY rY).node val (lZ.node valZ rZ)).balance_factor :=
      begin
        intro h4,
        have h5 := eq.symm h4,
        exfalso,
        exact h h5,
      end,
    have h5 : -2 < ((lY.node valY rY).node val (lZ.node valZ rZ)).balance_factor → -2 < ((lY.node valY rY).node val (lZ.node valZ rZ)).balance_factor :=
      begin
        intro h5,
        exact h5,
      end,
    have h6 := or.elim (eq_or_lt_of_le h3) h4 h5,
    linarith,
    linarith,
  end

private lemma children_wb_and_neg_1_le_bf_n_and_bf_n_le_2_then_neg_1_le_bf_bal_n
  : ∀ (l r : avlnode α) (val : α), well_balanced l ∧ well_balanced r → -1 ≤ balance_factor (node l val r) → balance_factor (node l val r) ≤ 2 → -1 ≤ balance_factor (balance (node l val r))
| nil nil val :=
  begin
    intros h1 h2 h3,
    rw [balance, balance_factor, depth],
    simp,
  end
| (node lZ valZ rZ) nil val :=
  begin
    intros h1 h2 h3,
    rw [balance, if_neg],
    exact h2,
    linarith,
  end
| nil (node lZ valZ rZ) val :=
  begin
    intros h1 h2 h3,
    rw le_iff_lt_or_eq at h3,
    cases h3,
    rw [balance, if_neg],
    exact h2,
    linarith,
    rw [balance, if_pos],
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw [if_pos, rotate_left, balance_factor],
    have h4 := dep_nil_v_r_eq_dep_r_add_1 lZ val,
    linarith,
    linarith,
    cases lZ,
    rw depth at h,
    have h4 := neg_1_le_dep_n rZ,
    linarith,
    rw [if_neg, rotate_right, rotate_left, balance_factor],
    have h4 := dep_nil_v_r_eq_dep_r_add_1 lZ_left val,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_1,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_2),
    rw [well_balanced, balance_factor] at h1,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_1),
    rw [well_balanced, well_balanced, balance_factor, balance_factor] at h1,
    linarith,
    linarith,
    linarith,
  end
| (node lY valY rY) (node lZ valZ rZ) val :=
  begin
    intros h1 h2 h3,
    rw le_iff_lt_or_eq at h3,
    cases h3,
    rw [balance, if_neg, if_neg],
    exact h2,
    linarith,
    linarith,
    rw [balance, if_pos],
    cases classical.em (lZ.depth ≤ rZ.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ h,
    rw [if_pos, rotate_left, balance_factor],
    cases classical.em ((lY.node valY rY).depth ≤ lZ.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ val h_1,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ val (le_of_not_le h_1),
    rw balance_factor at h3,
    linarith,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ (le_of_not_le h),
    cases lZ,
    rw depth at h,
    have h5 := neg_1_le_dep_n rZ,
    linarith,
    rw [if_neg, rotate_right, rotate_left, balance_factor],
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_1,
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left val h_2,
    rw [well_balanced, well_balanced, balance_factor, balance_factor] at h1,
    have h7 := dep_l_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ_left val (le_of_not_le h_2),
    rw balance_factor at h3,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_3,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_3),
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_1),
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left val h_2,
    rw [well_balanced, well_balanced, well_balanced, balance_factor, balance_factor, balance_factor] at h1,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ_left val (le_of_not_le h_2),
    rw balance_factor at h3,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_3,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_3),
    linarith,
    linarith,
    linarith,
  end

private lemma bf_n_eq_2_then_dep_n_eq_dep_bal_n_add_1
  : ∀ (n : avlnode α), balance_factor n = 2 → depth n ≤ depth (balance n) + 1
| nil :=
  begin
    intro h,
    rw balance_factor at h,
    linarith,
  end
| (node nil valX nil) :=
  begin
    intro h,
    rw [balance_factor, depth] at h,
    linarith,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    intro h,
    rw [balance_factor, depth, depth] at h,
    have h2 := neg_1_le_dep_n (node lZ valZ rZ),
    rw depth at h2,
    linarith,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    intro h,
    cases classical.em (lZ.depth ≤ rZ.depth),
    have h_copy := h,
    rw [balance_factor, depth, depth, if_pos] at h,
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ h_1,
    have h3 := dep_nil_v_r_eq_dep_r_add_1 (lZ.node valZ rZ) valX,
    rw [balance, if_pos, if_pos, rotate_left],
    cases classical.em ((nil.node valX lZ).depth ≤ rZ.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (nil.node valX lZ) rZ valZ h_2,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (nil.node valX lZ) rZ valZ (le_of_not_le h_2),
    have h5 := dep_nil_v_r_eq_dep_r_add_1 lZ valX,
    have h6 := neg_1_le_dep_n lZ,
    linarith,
    linarith,
    linarith,
    exact h_1,
    rw [balance, if_pos, if_neg],
    cases lZ,
    have h2 := neg_1_le_dep_n rZ,
    rw depth at h_1,
    linarith,
    rw [rotate_right, rotate_left],
    have h2 := dep_nil_v_r_eq_dep_r_add_1 ((lZ_left.node lZ_val lZ_right).node valZ rZ) valX,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lZ_left.node lZ_val lZ_right) rZ valZ (le_of_not_le h_1),
    rw [balance_factor, h3] at h,
    cases classical.em ((nil.node valX lZ_left).depth ≤ (lZ_right.node valZ rZ).depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (nil.node valX lZ_left) (lZ_right.node valZ rZ) lZ_val h_2,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_3,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_4,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_4),
    have h7 := dep_nil_v_r_eq_dep_r_add_1 lZ_left valX,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_3),
    have h6 := dep_nil_v_r_eq_dep_r_add_1 lZ_left valX,
    have h7 : lZ_left.depth ≤ lZ_right.depth := begin linarith end,
    have h8 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h7,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (nil.node valX lZ_left) (lZ_right.node valZ rZ) lZ_val (le_of_not_le h_2),
    have h5 := dep_nil_v_r_eq_dep_r_add_1 lZ_left valX,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_3,
    have h7 : rZ.depth ≤ lZ_right.depth := begin linarith end,
    have h8 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ h7,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_3),
    linarith,
    linarith,
    linarith,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance, if_pos],
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw balance_factor at h,
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ h_1,
    have h3 : (lY.node valY rY).depth ≤ (lZ.node valZ rZ).depth := begin linarith end,
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) (lZ.node valZ rZ) valX h3,
    rw [if_pos, rotate_left],
    cases classical.em (((lY.node valY rY).node valX lZ).depth ≤ rZ.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 ((lY.node valY rY).node valX lZ) rZ valZ h_2,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 ((lY.node valY rY).node valX lZ) rZ valZ (le_of_not_le h_2),
    cases classical.em ((lY.node valY rY).depth ≤ lZ.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ valX h_3,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ valX (le_of_not_le h_3),
    linarith,
    linarith,
    rw balance_factor at h,
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ (le_of_not_le h_1),
    have h3 : (lY.node valY rY).depth ≤ (lZ.node valZ rZ).depth := begin linarith end,
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) (lZ.node valZ rZ) valX h3,
    cases lZ,
    have h2 := neg_1_le_dep_n rZ,
    rw depth at h_1,
    linarith,
    rw [if_neg, rotate_right, rotate_left, h4, h2],
    cases classical.em (((lY.node valY rY).node valX lZ_left).depth ≤ (lZ_right.node valZ rZ).depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 ((lY.node valY rY).node valX lZ_left) (lZ_right.node valZ rZ) lZ_val h_2,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_3,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_4,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_4),
    have h8 : (lY.node valY rY).depth ≤ lZ_left.depth := begin linarith end,
    have h9 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left valX h8,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_3),
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_4,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_4),
    have h8 : (lY.node valY rY).depth ≤ lZ_left.depth := begin linarith end,
    have h9 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left valX h8,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 ((lY.node valY rY).node valX lZ_left) (lZ_right.node valZ rZ) lZ_val (le_of_not_le h_2),
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left valX h_3,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_4,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_4),
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ_left valX (le_of_not_le h_3),
    linarith,
    linarith,
    linarith,
  end

private lemma bf_n_eq_neg_2_then_dep_n_eq_dep_bal_n_add_1
  : ∀ (n : avlnode α), balance_factor n = -2 → depth n ≤ depth (balance n) + 1
| nil :=
  begin
    intro h,
    rw balance_factor at h,
    linarith,
  end
| (node nil valX nil) :=
  begin
    intro h,
    rw [balance_factor, depth] at h,
    linarith,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    intro h,
    have h2 := dep_l_v_nil_eq_dep_l_add_1 (lZ.node valZ rZ) valX,
    rw [balance, if_pos],
    cases classical.em (rZ.depth ≤ lZ.depth),
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ h_1,
    rw [if_pos, rotate_right],
    have h4 := dep_l_v_nil_eq_dep_l_add_1 rZ valX,
    cases classical.em (lZ.depth ≤ (rZ.node valX nil).depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ (rZ.node valX nil) valZ h_2,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ (rZ.node valX nil) valZ (le_of_not_le h_2),
    linarith,
    linarith,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ (le_of_not_le h_1),
    cases rZ,
    rw depth at h_1,
    have h3 := neg_1_le_dep_n lZ,
    linarith,
    rw [balance_factor, h3, depth, depth] at h,
    have h4 := neg_1_le_dep_n rZ_left,
    have h5 := neg_1_le_dep_n rZ_right,
    have h6 := dep_l_add_1_le_dep_l_v_r rZ_left rZ_right rZ_val,
    have h7 := dep_r_add_1_le_dep_l_v_r rZ_left rZ_right rZ_val,
    rw depth at h6,
    rw depth at h7,
    have h8 : rZ_left.depth = -1 := begin linarith end,
    have h9 : rZ_right.depth = -1 := begin linarith end,
    have h10 : rZ_left.depth ≤ rZ_right.depth := begin linarith end,
    have h11 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rZ_left rZ_right rZ_val h10,
    rw [if_neg, rotate_left, rotate_right],
    cases classical.em ((lZ.node valZ rZ_left).depth ≤ (rZ_right.node valX nil).depth),
    have h12 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lZ.node valZ rZ_left) (rZ_right.node valX nil) rZ_val h_2,
    have h13 := dep_l_v_nil_eq_dep_l_add_1 rZ_right valX,
    linarith,
    have h12 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lZ.node valZ rZ_left) (rZ_right.node valX nil) rZ_val (le_of_not_le h_2),
    have h13 : lZ.depth ≤ rZ_left.depth := begin linarith end,
    have h14 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ_left valZ h13,
    linarith,
    linarith,
    linarith,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance_factor, depth, depth] at h,
    have h2 := neg_1_le_dep_n (lZ.node valZ rZ),
    rw depth at h2,
    linarith,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw balance_factor at h,
    have h2 : (lZ.node valZ rZ).depth ≤ (lY.node valY rY).depth := begin linarith end,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) (lZ.node valZ rZ) valX h2,
    rw [balance, if_neg, if_pos],
    cases classical.em (rY.depth ≤ lY.depth),
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY valY h_1,
    rw [if_pos, rotate_right],
    cases classical.em (lY.depth ≤ (rY.node valX (lZ.node valZ rZ)).depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY (rY.node valX (lZ.node valZ rZ)) valY h_2,
    linarith,
    cases classical.em (rY.depth ≤ (lZ.node valZ rZ).depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY (lZ.node valZ rZ) valX h_3,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY (lZ.node valZ rZ) valX (le_of_not_le h_3),
    linarith,
    linarith,
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY valY (le_of_not_le h_1),
    cases rY,
    have h5 := neg_1_le_dep_n lY,
    rw depth at h_1,
    linarith,
    rw [if_neg, rotate_left, rotate_right],
    cases classical.em ((lY.node valY rY_left).depth ≤ (rY_right.node valX (lZ.node valZ rZ)).depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY_left) (rY_right.node valX (lZ.node valZ rZ)) rY_val h_2,
    cases classical.em (rY_right.depth ≤ (lZ.node valZ rZ).depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_right (lZ.node valZ rZ) valX h_3,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_right (lZ.node valZ rZ) valX (le_of_not_le h_3),
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY_left) (rY_right.node valX (lZ.node valZ rZ)) rY_val (le_of_not_le h_2),
    cases classical.em (lY.depth ≤ rY_left.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY_left valY h_3,
    cases classical.em (rY_left.depth ≤ rY_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_left rY_right rY_val h_4,
    have h8 := dep_l_add_1_le_dep_l_v_r rY_right (lZ.node valZ rZ) valX,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_left rY_right rY_val (le_of_not_le h_4),
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY_left valY (le_of_not_le h_3),
    cases classical.em (rY_left.depth ≤ rY_right.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_left rY_right rY_val h_4,
    have h8 := dep_l_add_1_le_dep_l_v_r rY_right (lZ.node valZ rZ) valX,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_left rY_right rY_val (le_of_not_le h_4),
    linarith,
    linarith,
    rw balance_factor,
    linarith,
    rw balance_factor,
    linarith,
  end

private lemma bf_n_le_1_and_neg_1_le_bf_n_then_n_eq_bal_n
  : ∀ (n : avlnode α), balance_factor n ≤ 1 ∧ -1 ≤ balance_factor n → n = balance n
| nil :=
  begin
    intro h,
    rw balance,
  end
| (node nil valX nil) :=
  begin
    intro h,
    rw balance,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    intro h,
    rw [balance, if_neg],
    rw balance_factor,
    rw balance_factor at h,
    linarith,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw [balance, if_neg],
    rw balance_factor,
    rw balance_factor at h,
    linarith,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    intro h,
    rw balance_factor at h,
    rw [balance, if_neg, if_neg],
    rw balance_factor,
    linarith,
    rw balance_factor,
    linarith,
  end

/--
Proof that the balance operation does not increase the depth of a node n.
-/
lemma dep_bal_n_le_dep_n
  : ∀ (n : avlnode α), depth (balance n) ≤ depth n
| nil :=
  begin
    rw balance,
  end
| (node nil valX nil) :=
  begin
    rw balance,
  end
| (node (node lZ valZ rZ) valX nil) :=
  begin
    rw balance,
    cases classical.em (((lZ.node valZ rZ).node valX nil).balance_factor ≤ -2),
    rw if_pos,
    cases classical.em (rZ.depth ≤ lZ.depth),
    rw [if_pos, rotate_right, depth],
    cases classical.em (lZ.depth ≤ (rZ.node valX nil).depth),
    rw if_pos,
    have h3 := dep_l_v_nil_eq_dep_l_add_1 (lZ.node valZ rZ) valX,
    have h4 := dep_l_v_nil_eq_dep_l_add_1 rZ valX,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ h_1,
    linarith,
    exact h_2,
    rw if_neg,
    have h3 := dep_l_v_nil_eq_dep_l_add_1 (lZ.node valZ rZ) valX,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ rZ valZ h_1,
    linarith,
    exact h_2,
    linarith,
    cases rZ,
    have h2 := neg_1_le_dep_n lZ,
    rw depth at h_1,
    linarith,
    rw [if_neg, rotate_left, rotate_right, depth],
    cases classical.em (rZ_left.depth ≤ rZ_right.depth),
    rw if_pos,
    have h2 := dep_l_v_nil_eq_dep_l_add_1 (lZ.node valZ (rZ_left.node rZ_val rZ_right)) valX,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ (rZ_left.node rZ_val rZ_right) valZ (le_of_not_le h_1),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rZ_left rZ_right rZ_val h_2,
    have h5 := dep_l_v_nil_eq_dep_l_add_1 rZ_right valX,
    linarith,
    rw [depth, if_pos] at h_1,
    have h2 := dep_l_v_nil_eq_dep_l_add_1 rZ_right valX,
    cases classical.em (lZ.depth ≤ rZ_left.depth),
    rw [depth, if_pos],
    linarith,
    exact h_3,
    rw [depth, if_neg],
    linarith,
    exact h_3,
    exact h_2,
    rw if_neg,
    have h2 := dep_l_v_nil_eq_dep_l_add_1 (lZ.node valZ (rZ_left.node rZ_val rZ_right)) valX,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ (rZ_left.node rZ_val rZ_right) valZ (le_of_not_le h_1),
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rZ_left rZ_right rZ_val (le_of_not_le h_2),
    rw h4 at h_1,
    have h5 : lZ.depth ≤ rZ_left.depth := begin linarith end,
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ_left valZ h5,
    linarith,
    rw [depth, if_neg] at h_1,
    have h2 : lZ.depth ≤ rZ_left.depth := begin linarith end,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ_left valZ h2,
    have h4 := dep_l_v_nil_eq_dep_l_add_1 rZ_right valX,
    linarith,
    exact h_2,
    linarith,
    exact h,
    rw if_neg,
    exact h,
  end
| (node nil valX (node lZ valZ rZ)) :=
  begin
    rw balance,
    cases classical.em (2 ≤ (nil.node valX (lZ.node valZ rZ)).balance_factor),
    rw if_pos,
    cases classical.em (lZ.depth ≤ rZ.depth),
    have h2 := dep_nil_v_r_eq_dep_r_add_1 (lZ.node valZ rZ) valX,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ h_1,
    rw [if_pos, rotate_left],
    cases classical.em ((nil.node valX lZ).depth ≤ rZ.depth),
    rw [depth, if_pos],
    linarith,
    exact h_2,
    rw [depth, if_neg],
    have h4 := dep_nil_v_r_eq_dep_r_add_1 lZ valX,
    linarith,
    exact h_2,
    linarith,
    cases lZ,
    have h2 := neg_1_le_dep_n rZ,
    rw depth at h_1,
    linarith,
    rw [if_neg, rotate_right, rotate_left, depth],
    have h2 := dep_nil_v_r_eq_dep_r_add_1 ((lZ_left.node lZ_val lZ_right).node valZ rZ) valX,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lZ_left.node lZ_val lZ_right) rZ valZ (le_of_not_le h_1),
    have h8 := dep_nil_v_r_eq_dep_r_add_1 lZ_left valX,
    cases classical.em (lZ_left.depth ≤ lZ_right.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_2,
    have h5 : rZ.depth ≤ lZ_right.depth := begin linarith end,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ h5,
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_left lZ_right lZ_val h_2,
    rw if_pos,
    linarith,
    linarith,
    have h4 := dep_nil_v_r_eq_dep_r_add_1 lZ_left valX,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_left lZ_right lZ_val (le_of_not_le h_2),
    cases classical.em (lZ_left.depth ≤ rZ.depth),
    have h6 : lZ_right.depth ≤ rZ.depth := begin linarith end,
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h6,
    rw if_pos,
    linarith,
    linarith,
    rw if_neg,
    linarith,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h8 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_4,
    linarith,
    have h8 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_4),
    linarith,
    linarith,
    exact h,
    rw if_neg,
    exact h,
  end
| (node (node lY valY rY) valX (node lZ valZ rZ)) :=
  begin
    rw balance,
    cases classical.em (2 ≤ ((lY.node valY rY).node valX (lZ.node valZ rZ)).balance_factor),
    cases classical.em (lZ.depth ≤ rZ.depth),
    rw [if_pos, if_pos, rotate_left],
    cases classical.em (((lY.node valY rY).node valX lZ).depth ≤ rZ.depth),
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 ((lY.node valY rY).node valX lZ) rZ valZ h_2,
    have h3 := dep_r_add_1_le_dep_l_v_r lZ rZ valZ,
    have h4 := dep_r_add_1_le_dep_l_v_r (lY.node valY rY) (lZ.node valZ rZ) valX,
    linarith,
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 ((lY.node valY rY).node valX lZ) rZ valZ (le_of_not_le h_2),
    cases classical.em ((lY.node valY rY).depth ≤ lZ.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ valX h_3,
    have h4 := dep_l_add_1_le_dep_l_v_r lZ rZ valZ,
    have h5 := dep_r_add_1_le_dep_l_v_r (lY.node valY rY) (lZ.node valZ rZ) valX,
    linarith,
    rw balance_factor at h,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ rZ valZ h_1,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ valX (le_of_not_le h_3),
    linarith,
    linarith,
    exact h,
    rw [if_pos, if_neg],
    cases lZ,
    have h2 := neg_1_le_dep_n rZ,
    rw depth at h_1,
    linarith,
    rw [rotate_right, rotate_left],
    cases classical.em (((lY.node valY rY).node valX lZ_left).depth ≤ (lZ_right.node valZ rZ).depth),
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 ((lY.node valY rY).node valX lZ_left) (lZ_right.node valZ rZ) lZ_val h_2,
    cases classical.em (lZ_right.depth ≤ rZ.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lZ_right rZ valZ h_3,
    have h4 := dep_r_add_1_le_dep_l_v_r (lZ_left.node lZ_val lZ_right) rZ valZ,
    have h5 := dep_r_add_1_le_dep_l_v_r (node lY valY rY) ((lZ_left.node lZ_val lZ_right).node valZ rZ) valX,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lZ_right rZ valZ (le_of_not_le h_3),
    have h4 := dep_r_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    have h5 := dep_l_add_1_le_dep_l_v_r (lZ_left.node lZ_val lZ_right) rZ valZ,
    have h6 := dep_r_add_1_le_dep_l_v_r (node lY valY rY) ((lZ_left.node lZ_val lZ_right).node valZ rZ) valX,
    linarith,
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 ((lY.node valY rY).node valX lZ_left) (lZ_right.node valZ rZ) lZ_val (le_of_not_le h_2),
    cases classical.em ((lY.node valY rY).depth ≤ lZ_left.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) lZ_left valX h_3,
    have h4 := dep_l_add_1_le_dep_l_v_r lZ_left lZ_right lZ_val,
    have h5 := dep_l_add_1_le_dep_l_v_r (lZ_left.node lZ_val lZ_right) rZ valZ,
    have h6 := dep_r_add_1_le_dep_l_v_r (node lY valY rY) (node (lZ_left.node lZ_val lZ_right) valZ rZ) valX,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY) lZ_left valX (le_of_not_le h_3),
    rw balance_factor at h,
    have h4 : (lY.node valY rY).depth ≤ (node (lZ_left.node lZ_val lZ_right) valZ rZ).depth := begin linarith end,
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY) (node (lZ_left.node lZ_val lZ_right) valZ rZ) valX h4,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lZ_left.node lZ_val lZ_right) rZ valZ (le_of_not_le h_1),
    linarith,
    linarith,
    exact h,
    cases classical.em (((lY.node valY rY).node valX (lZ.node valZ rZ)).balance_factor ≤ -2),
    cases classical.em (rY.depth ≤ lY.depth),
    rw [if_neg, if_pos, if_pos, rotate_right],
    cases classical.em (lY.depth ≤ (node rY valX (lZ.node valY rZ)).depth),
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY (node rY valX (lZ.node valZ rZ)) valY h_3,
    cases classical.em (rY.depth ≤ (lZ.node valY rZ).depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY (lZ.node valZ rZ) valX h_4,
    rw balance_factor at h_1,
    have h4 := dep_l_add_1_le_dep_l_v_r (lY.node valY rY) (lZ.node valZ rZ) valX,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY (lZ.node valZ rZ) valX (le_of_not_le h_4),
    have h4 := dep_r_add_1_le_dep_l_v_r lY rY valY,
    have h5 := dep_l_add_1_le_dep_l_v_r (node lY valY rY) (node lZ valZ rZ) valX,
    linarith,
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY (node rY valX (lZ.node valZ rZ)) valY (le_of_not_le h_3),
    have h3 := dep_l_add_1_le_dep_l_v_r lY rY valY,
    have h4 := dep_l_add_1_le_dep_l_v_r (node lY valY rY) (node lZ valZ rZ) valX,
    linarith,
    linarith,
    exact h_1,
    exact h,
    rw [if_neg, if_pos, if_neg],
    cases rY,
    have h2 := neg_1_le_dep_n lY,
    rw depth at h_2,
    linarith,
    rw [rotate_left, rotate_right],
    cases classical.em ((lY.node valY rY_left).depth ≤ (rY_right.node valX (lZ.node valZ rZ)).depth),
    have h2 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (lY.node valY rY_left) (rY_right.node valX (lZ.node valZ rZ)) rY_val h_3,
    cases classical.em (rY_right.depth ≤ (lZ.node valZ rZ).depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 rY_right (lZ.node valZ rZ) valX h_4,
    rw balance_factor at h_1,
    have h4 := dep_l_add_1_le_dep_l_v_r (lY.node valY (rY_left.node rY_val rY_right)) (lZ.node valZ rZ) valX,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 rY_right (lZ.node valZ rZ) valX (le_of_not_le h_4),
    have h4 := dep_r_add_1_le_dep_l_v_r rY_left rY_right rY_val,
    have h5 := dep_r_add_1_le_dep_l_v_r lY (rY_left.node rY_val rY_right) valY,
    have h6 := dep_l_add_1_le_dep_l_v_r (lY.node valY (rY_left.node rY_val rY_right)) (node lZ valZ rZ) valX,
    linarith,
    have h2 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (lY.node valY rY_left) (rY_right.node valX (lZ.node valZ rZ)) rY_val (le_of_not_le h_3),
    cases classical.em (lY.depth ≤ rY_left.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 lY rY_left valY h_4,
    have h4 := dep_l_add_1_le_dep_l_v_r rY_left rY_right rY_val,
    have h5 := dep_r_add_1_le_dep_l_v_r lY (rY_left.node rY_val rY_right) valY,
    have h6 := dep_l_add_1_le_dep_l_v_r (lY.node valY (rY_left.node rY_val rY_right)) (node lZ valZ rZ) valX,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 lY rY_left valY (le_of_not_le h_4),
    have h4 := dep_l_add_1_le_dep_l_v_r lY (rY_left.node rY_val rY_right) valY,
    have h5 := dep_l_add_1_le_dep_l_v_r (lY.node valY (rY_left.node rY_val rY_right)) (node lZ valZ rZ) valX,
    linarith,
    linarith,
    exact h_1,
    exact h,
    rw [if_neg, if_neg],
    exact h_1,
    exact h,
  end

private lemma dep_ins_n_val_le_dep_n_add_1
  : ∀ (n : avlnode α) (val : α), depth (ins n val) ≤ depth n + 1
| nil val2 :=
  begin
    rw [ins, depth, if_pos],
    refl,
  end
| (node l val1 r) val2 :=
  begin
    cases classical.em (val2 ≤ val1),
    rw [ins, if_pos],
    have h2 := dep_bal_n_le_dep_n ((l.ins val2).node val1 r),
    cases classical.em ((l.ins val2).depth ≤ r.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (l.ins val2) r val1 h_1,
    have h4 := dep_r_add_1_le_dep_l_v_r l r val1,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (l.ins val2) r val1 (le_of_not_le h_1),
    have h4 := dep_ins_n_val_le_dep_n_add_1 l val2,
    have h5 := dep_l_add_1_le_dep_l_v_r l r val1,
    linarith,
    exact h,
    rw [ins, if_neg],
    have h2 := dep_bal_n_le_dep_n (l.node val1 (r.ins val2)),
    cases classical.em (l.depth ≤ (r.ins val2).depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l (r.ins val2) val1 h_1,
    have h4 := dep_ins_n_val_le_dep_n_add_1 r val2,
    have h5 := dep_r_add_1_le_dep_l_v_r l r val1,
    linarith,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l (r.ins val2) val1 (le_of_not_le h_1),
    have h4 := dep_l_add_1_le_dep_l_v_r l r val1,
    linarith,
    exact h,
  end

private lemma children_n_wb_and_bf_n_eq_neg_2_then_bal_n_wb
  : ∀ (l r : avlnode α) (val : α), well_balanced l ∧ well_balanced r → (node l val r).balance_factor = -2 → well_balanced (node l val r).balance
| nil nil val :=
  begin
    intros h1 h2,
    rw [balance, well_balanced, well_balanced, balance_factor, depth],
    simp,
  end
| nil (node l2 val2 r2) val :=
  begin
    intros h1 h2,
    rw [balance_factor, depth, depth] at h2,
    have h3 := neg_1_le_dep_n (node l2 val2 r2),
    rw depth at h3,
    linarith,
  end
| (node l1 val1 r1) nil val :=
  begin
    intros h1 h2,
    rw [balance, if_pos],
    cases classical.em (r1.depth ≤ l1.depth),
    rw [if_pos, rotate_right, well_balanced],
    have h3 := dep_l_v_nil_eq_dep_l_add_1 r1 val,
    have h4 := and.elim_left h1,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1 val1 h,
    have h6 := neg_1_le_dep_n r1,
    rw [balance_factor, h5, depth] at h2,
    rw well_balanced at h4,
    repeat {split},
    rw balance_factor,
    linarith,
    rw balance_factor,
    linarith,
    exact and.elim_left (and.elim_right (and.elim_right h4)),
    rw [balance_factor, depth],
    linarith,
    rw [balance_factor, depth],
    linarith,
    exact and.elim_right (and.elim_right (and.elim_right h4)),
    linarith,
    rw [balance_factor, depth, depth] at h2,
    cases r1 with r1l r1val r1r,
    have h3 := neg_1_le_dep_n l1,
    rw depth at h,
    linarith,
    rw [if_neg, rotate_left, rotate_right, well_balanced],
    have h3 := dep_l_v_nil_eq_dep_l_add_1 r1r val,
    have h4 := and.elim_left h1,
    rw well_balanced at h4,
    have h5 := and.elim_right (and.elim_right (and.elim_right h4)),
    rw balance_factor at h4,
    rw well_balanced at h5,
    rw balance_factor at h5,
    repeat {split},
    cases classical.em (l1.depth ≤ r1l.depth),
    rw balance_factor,
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_1,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_1),
    rw balance_factor,
    linarith,
    cases classical.em (l1.depth ≤ r1l.depth),
    rw balance_factor,
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_1,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_1),
    rw balance_factor,
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_2,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_2),
    linarith,
    any_goals {
      rw balance_factor,
      cases classical.em (r1l.depth ≤ r1r.depth),
      have h7 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_1,
      linarith,
      have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_1),
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right h4)),
    exact and.elim_left (and.elim_right (and.elim_right h5)),
    have h6 := neg_1_le_dep_n r1r,
    rw [balance_factor, depth],
    linarith,
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 (r1l.node r1val r1r) val1 (le_of_not_le h),
    rw depth at h6,
    rw h6 at h2,
    have h7 := dep_r_add_1_le_dep_l_v_r r1l r1r r1val,
    rw [balance_factor, depth],
    linarith,
    exact and.elim_right (and.elim_right (and.elim_right h5)),
    linarith,
    linarith,
  end
| (node l1 val1 r1) (node l2 val2 r2) val :=
  begin
    intros h1 h2,
    rw [balance, if_neg, if_pos],
    cases classical.em (r1.depth ≤ l1.depth),
    rw [if_pos, rotate_right, well_balanced],
    rw balance_factor at h2,
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1 val1 h,
    repeat {rw well_balanced at h1},
    repeat {split},
    any_goals {
      rw balance_factor,
      cases classical.em (r1.depth ≤ (l2.node val2 r2).depth),
      have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1 (l2.node val2 r2) val h_1,
      linarith,
      have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1 (l2.node val2 r2) val (le_of_not_le h_1),
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left h1))),
    rw balance_factor,
    have h4 := and.elim_left (and.elim_right (and.elim_left h1)),
    rw balance_factor at h4,
    linarith,
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    exact and.elim_left (and.elim_right h1),
    exact and.elim_left (and.elim_right (and.elim_right h1)),
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_right h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
    linarith,
    rw balance_factor at h2,
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1 val1 (le_of_not_le h),
    repeat {rw well_balanced at h1},
    cases r1 with r1l r1val r1r,
    have h2 := neg_1_le_dep_n l1,
    rw depth at h,
    linarith,
    rw [if_neg, rotate_left, rotate_right, well_balanced],
    repeat {split},
    rw balance_factor,
    cases classical.em (r1r.depth ≤ (l2.node val2 r2).depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1r (l2.node val2 r2) val h_1,
    cases classical.em (l1.depth ≤ r1l.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_2,
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    have h7 := and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    rw [well_balanced, balance_factor] at h7,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_3),
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_2),
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    have h7 := and.elim_left (and.elim_left h1),
    rw balance_factor at h7,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_3),
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1r (l2.node val2 r2) val (le_of_not_le h_1),
    cases classical.em (l1.depth ≤ r1l.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_2,
    have h6 := and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    rw [well_balanced, balance_factor] at h6,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_2),
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    linarith,
    linarith,
    rw balance_factor,
    cases classical.em (r1r.depth ≤ (l2.node val2 r2).depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1r (l2.node val2 r2) val h_1,
    cases classical.em (l1.depth ≤ r1l.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_2,
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    have h7 := and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    rw [well_balanced, balance_factor] at h7,
    linarith,
    have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_3),
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_2),
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    have h7 := and.elim_left (and.elim_left h1),
    rw balance_factor at h7,
    linarith,
    have h7 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_3),
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1r (l2.node val2 r2) val (le_of_not_le h_1),
    cases classical.em (l1.depth ≤ r1l.depth),
    have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l1 r1l val1 h_2,
    have h6 := and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    rw [well_balanced, balance_factor] at h6,
    linarith,
    have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l1 r1l val1 (le_of_not_le h_2),
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_3,
    linarith,
    linarith,
    rw balance_factor,
    rw balance_factor at h1,
    have h4 := dep_l_add_1_le_dep_l_v_r r1l r1r r1val,
    linarith,
    rw balance_factor,
    rw [well_balanced, balance_factor, balance_factor] at h1,
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_1,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_1),
    linarith,
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left h1))),
    rw well_balanced at h1,
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_right (and.elim_right (and.elim_right (and.elim_left h1)))))),
    rw balance_factor,
    rw [well_balanced, balance_factor, balance_factor] at h1,
    cases classical.em (r1l.depth ≤ r1r.depth),
    have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 r1l r1r r1val h_1,
    linarith,
    have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 r1l r1r r1val (le_of_not_le h_1),
    linarith,
    rw balance_factor,
    rw balance_factor at h1,
    have h4 := dep_r_add_1_le_dep_l_v_r r1l r1r r1val,
    linarith,
    rw well_balanced at h1,
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right (and.elim_right (and.elim_right (and.elim_left h1)))))),
    exact and.elim_left (and.elim_right h1),
    exact and.elim_left (and.elim_right (and.elim_right h1)),
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_right h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
    linarith,
    linarith,
    linarith,
  end

private lemma children_n_wb_and_bf_n_eq_2_then_bal_n_wb
  : ∀ (l r : avlnode α) (val : α), well_balanced l ∧ well_balanced r → (node l val r).balance_factor = 2 → well_balanced (node l val r).balance
| nil nil val :=
  begin
    intros h1 h2,
    rw [balance, well_balanced, balance_factor, depth, well_balanced],
    simp,
  end
| nil (node l2 val2 r2) val :=
  begin
    intros h1 h2,
    rw [balance, if_pos],
    rw [well_balanced, balance_factor] at h1,
    cases classical.em (l2.depth ≤ r2.depth),
    rw [if_pos, rotate_left, well_balanced],
    rw [balance_factor, depth, depth, if_pos] at h2,
    have h3 := dep_nil_v_r_eq_dep_r_add_1 l2 val,
    repeat {split},
    any_goals {
      rw balance_factor,
      linarith,
    },
    any_goals {
      rw [balance_factor, depth],
      linarith,
    },
    any_goals {
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_right h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
    rw [balance_factor, depth, depth, if_neg] at h2,
    cases l2,
    have h3 := neg_1_le_dep_n r2,
    rw depth at h,
    linarith,
    have h3 := dep_nil_v_r_eq_dep_r_add_1 l2_left val,
    have h4 := neg_1_le_dep_n l2_left,
    have h5 := neg_1_le_dep_n l2_right,
    have h6 := dep_l_add_1_le_dep_l_v_r l2_left l2_right l2_val,
    have h7 := dep_r_add_1_le_dep_l_v_r l2_left l2_right l2_val,
    rw [if_neg, rotate_right, rotate_left, well_balanced],
    rw well_balanced at h1,
    repeat {split},
    any_goals {
      rw balance_factor,
      cases classical.em (l2_right.depth ≤ r2.depth),
      have h8 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l2_right r2 val2 h_1,
      linarith,
      linarith,
    },
    any_goals {
      linarith,
    },
    any_goals {
      rw [balance_factor, depth],
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left (and.elim_right (and.elim_right (and.elim_right h1)))))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_left (and.elim_right (and.elim_right (and.elim_right h1)))))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
  end
| (node l1 val1 r1) nil val :=
  begin
    intros h1 h2,
    rw [balance_factor, depth, depth] at h2,
    have h3 := neg_1_le_dep_n (node l1 val1 r1),
    rw depth at h3,
    linarith,
  end
| (node l1 val1 r1) (node l2 val2 r2) val :=
  begin
    intros h1 h2,
    rw [balance, if_pos],
    rw [well_balanced, well_balanced, balance_factor, balance_factor] at h1,
    rw balance_factor at h2,
    cases classical.em (l2.depth ≤ r2.depth),
    have h3 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l2 r2 val2 h,
    rw [if_pos, rotate_left, well_balanced],
    repeat {split},
    any_goals {
      rw balance_factor,
      cases classical.em ((l1.node val1 r1).depth ≤ l2.depth),
      have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (l1.node val1 r1) l2 val h_1,
      linarith,
      have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (l1.node val1 r1) l2 val (le_of_not_le h_1),
      linarith,
    },
    any_goals {
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_right h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
    have h3 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l2 r2 val2 (le_of_not_le h),
    cases l2,
    have h4 := neg_1_le_dep_n r2,
    rw depth at h,
    linarith,
    rw [well_balanced, balance_factor] at h1,
    rw [if_neg, rotate_right, rotate_left, well_balanced],
    repeat {split},
    any_goals {
      rw balance_factor,
      cases classical.em (l2_right.depth ≤ r2.depth),
      have h5 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l2_right r2 val2 h_1,
      cases classical.em ((l1.node val1 r1).depth ≤ l2_left.depth),
      have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (l1.node val1 r1) l2_left val h_2,
      linarith,
      have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (l1.node val1 r1) l2_left val (le_of_not_le h_2),
      linarith,
      have h5 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l2_right r2 val2 (le_of_not_le h_1),
      cases classical.em ((l1.node val1 r1).depth ≤ l2_left.depth),
      have h6 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 (l1.node val1 r1) l2_left val h_2,
      linarith,
      have h6 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 (l1.node val1 r1) l2_left val (le_of_not_le h_2),
      linarith,
    },
    any_goals {
      rw balance_factor,
      have h4 := dep_l_add_1_le_dep_l_v_r l2_left l2_right l2_val,
      have h5 := dep_l_add_1_le_dep_l_v_r (l2_left.node l2_val l2_right) r2 val2,
      linarith,
    },
    any_goals {
      rw balance_factor,
      cases classical.em (l2_left.depth ≤ l2_right.depth),
      have h4 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l2_left l2_right l2_val h_1,
      linarith,
      have h4 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l2_left l2_right l2_val (le_of_not_le h_1),
      linarith,
    },
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left h1))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_left h1))),
    exact and.elim_left (and.elim_right (and.elim_right (and.elim_left (and.elim_right (and.elim_right (and.elim_right h1)))))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_left (and.elim_right (and.elim_right (and.elim_right h1)))))),
    exact and.elim_right (and.elim_right (and.elim_right (and.elim_right h1))),
    linarith,
  end

private lemma wb_n_then_dep_n_le_dep_ins_n_val
  : ∀ (n : avlnode α) (val : α), well_balanced n → depth n ≤ depth (ins n val)
| nil val2 :=
  begin
    intro h,
    rw [ins, depth, if_pos, depth],
    linarith,
    refl,
  end
| (node l val r) val2 :=
  begin
    intro h,
    cases classical.em (val2 ≤ val),
    rw [ins, if_pos],
    rw well_balanced at h,
    have h2 := and.elim_left (and.elim_right h),
    rw balance_factor at h2,
    have h3 := dep_ins_n_val_le_dep_n_add_1 l val2,
    have h4 : -2 ≤ r.depth - (l.ins val2).depth := begin linarith end,
    have h5 := and.elim_left h,
    rw balance_factor at h5,
    have h6 := wb_n_then_dep_n_le_dep_ins_n_val l val2 (and.elim_left (and.elim_right (and.elim_right h))),
    have h7 : r.depth - (l.ins val2).depth ≤ 1 := begin linarith end,
    rw le_iff_eq_or_lt at h4,
    cases h4,
    have h8 := bf_n_eq_neg_2_then_dep_n_eq_dep_bal_n_add_1 ((l.ins val2).node val r) (eq.symm h4),
    rw [depth, if_neg],
    rw [depth, if_neg] at h8,
    linarith,
    linarith,
    linarith,
    have h8 : -1 < (r.depth - (l.ins val2).depth) + 1 := begin linarith end,
    rw int.lt_add_one_iff at h8,
    have h9 := bf_n_le_1_and_neg_1_le_bf_n_then_n_eq_bal_n ((l.ins val2).node val r) (and.intro h7 h8),
    rw ← h9,
    cases classical.em (l.depth ≤ r.depth),
    have h10 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l r val h_2,
    have h11 := dep_r_add_1_le_dep_l_v_r (l.ins val2) r val,
    linarith,
    have h10 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l r val (le_of_not_le h_2),
    have h11 := dep_l_add_1_le_dep_l_v_r (l.ins val2) r val,
    linarith,
    exact h_1,
    rw [ins, if_neg],
    rw well_balanced at h,
    have h2 := and.elim_left h,
    rw balance_factor at h2,
    have h3 := dep_ins_n_val_le_dep_n_add_1 r val2,
    have h4 : (r.ins val2).depth - l.depth ≤ 2 := begin linarith end,
    have h5 := and.elim_left (and.elim_right h),
    rw balance_factor at h5,
    have h6 := wb_n_then_dep_n_le_dep_ins_n_val r val2 (and.elim_right (and.elim_right (and.elim_right h))),
    have h7 : -1 ≤ (r.ins val2).depth - l.depth := begin linarith end,
    rw le_iff_eq_or_lt at h4,
    cases h4,
    have h8 := bf_n_eq_2_then_dep_n_eq_dep_bal_n_add_1 (l.node val (r.ins val2)) h4,
    rw [depth, if_pos],
    rw [depth, if_pos] at h8,
    linarith,
    linarith,
    linarith,
    have h8 : ((r.ins val2).depth - l.depth) < 1 + 1 := begin linarith end,
    rw int.lt_add_one_iff at h8,
    have h9 := bf_n_le_1_and_neg_1_le_bf_n_then_n_eq_bal_n (l.node val (r.ins val2)) (and.intro h8 h7),
    rw ← h9,
    cases classical.em (l.depth ≤ r.depth),
    have h10 := dep_l_le_dep_r_then_dep_l_v_r_eq_dep_r_add_1 l r val h_2,
    have h11 := dep_r_add_1_le_dep_l_v_r l (r.ins val2) val,
    linarith,
    have h10 := dep_r_le_dep_l_then_dep_l_v_r_eq_dep_l_add_1 l r val (le_of_not_le h_2),
    have h11 := dep_l_add_1_le_dep_l_v_r l (r.ins val2) val,
    linarith,
    exact h_1,
  end

/--
Proof that the well balanced property is closed under the defined `ins` operation.
-/
lemma wb_n_then_wb_ins_n_val
  : ∀ (n : avlnode α) (val : α), well_balanced n → well_balanced (ins n val)
| nil ins_val :=
  begin
    intro h,
    rw [ins, well_balanced, well_balanced, balance_factor, depth],
    simp,
  end
| (node l val r) ins_val :=
  begin
    intro h,
    rw ins,
    cases classical.em (ins_val ≤ val),
    rw if_pos,
    rw well_balanced at h,
    have h2 := and.elim_left (and.elim_right h),
    rw balance_factor at h2,
    have h3 := (dep_ins_n_val_le_dep_n_add_1 l ins_val),
    have h4 : -2 ≤ r.depth - (l.ins ins_val).depth := begin linarith end,
    have h4_copy := h4,
    have h5 := and.elim_right (and.elim_right (and.elim_right h)),
    have h6 := wb_n_then_wb_ins_n_val l ins_val (and.elim_left (and.elim_right (and.elim_right h))),
    have h7 := wb_n_then_dep_n_le_dep_ins_n_val l ins_val (and.elim_left (and.elim_right (and.elim_right h))),
    have h8 := and.elim_left h,
    rw balance_factor at h8,
    have h9 : r.depth - (l.ins ins_val).depth ≤ 1 := begin linarith end,
    rw le_iff_eq_or_lt at h4,
    cases h4,
    have h10 := bf_n_eq_neg_2_then_neg_1_le_bf_bal_n ((l.ins ins_val).node val r) (eq.symm h4),
    have h11 := children_wb_and_bf_n_le_1_and_neg_2_le_bf_n_then_bf_bal_b_le_1 (l.ins ins_val) r val (and.intro h6 h5) h9 h4_copy,
    exact children_n_wb_and_bf_n_eq_neg_2_then_bal_n_wb (l.ins ins_val) r val (and.intro h6 h5) (eq.symm h4),
    have h10 : -1 ≤ r.depth - (l.ins ins_val).depth := begin linarith end,
    have h11 := bf_n_le_1_and_neg_1_le_bf_n_then_n_eq_bal_n ((l.ins ins_val).node val r) (and.intro h9 h10),
    rw [← h11, well_balanced],
    exact and.intro h9 (and.intro h10 (and.intro h6 h5)),
    exact h_1,
    rw if_neg,
    rw well_balanced at h,
    have h2 := and.elim_left h,
    rw balance_factor at h2,
    have h3 := (dep_ins_n_val_le_dep_n_add_1 r ins_val),
    have h4 : (r.ins ins_val).depth - l.depth ≤ 2 := begin linarith end,
    have h4_copy := h4,
    have h5 := and.elim_left (and.elim_right (and.elim_right h)),
    have h6 := wb_n_then_wb_ins_n_val r ins_val (and.elim_right (and.elim_right (and.elim_right h))),
    have h7 := wb_n_then_dep_n_le_dep_ins_n_val r ins_val (and.elim_right (and.elim_right (and.elim_right h))),
    have h8 := and.elim_left (and.elim_right h),
    rw balance_factor at h8,
    have h9 : -1 ≤ (r.ins ins_val).depth - l.depth := begin linarith end,
    rw le_iff_eq_or_lt at h4,
    cases h4,
    have h10 := bf_n_eq_2_then_bf_bal_n_le_1 (l.node val (r.ins ins_val)) h4,
    have h11 := children_wb_and_neg_1_le_bf_n_and_bf_n_le_2_then_neg_1_le_bf_bal_n l (r.ins ins_val) val (and.intro h5 h6) h9 h4_copy,
    exact children_n_wb_and_bf_n_eq_2_then_bal_n_wb l (r.ins ins_val) val (and.intro h5 h6) h4,
    have h10 : (r.ins ins_val).depth - l.depth ≤ 1 := begin linarith end,
    have h11 := bf_n_le_1_and_neg_1_le_bf_n_then_n_eq_bal_n (l.node val (r.ins ins_val)) (and.intro h10 h9),
    rw [← h11, well_balanced],
    exact and.intro h10 (and.intro h9 (and.intro h5 h6)),
    exact h_1,
  end

/--
Proof that the empty tree is well_balanced.
-/
lemma wb_nil
  : well_balanced (nil : avlnode α) :=
begin
  rw well_balanced,
  trivial,
end

end avlnode


/-
Copyright (c) 2021 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.real.ennreal
import Mathlib.order.filter.countable_Inter
import Mathlib.order.liminf_limsup
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Order properties of extended non-negative reals

This file compiles filter-related results about `ennreal` (see data/real/ennreal.lean).
-/

namespace ennreal

theorem eventually_le_limsup {α : Type u_1} {f : filter α} [countable_Inter_filter f] (u : α → ennreal) : filter.eventually (fun (y : α) => u y ≤ filter.limsup f u) f := sorry

theorem limsup_eq_zero_iff {α : Type u_1} {f : filter α} [countable_Inter_filter f] {u : α → ennreal} : filter.limsup f u = 0 ↔ filter.eventually_eq f u 0 := sorry

theorem limsup_const_mul_of_ne_top {α : Type u_1} {f : filter α} {u : α → ennreal} {a : ennreal} (ha_top : a ≠ ⊤) : (filter.limsup f fun (x : α) => a * u x) = a * filter.limsup f u := sorry

theorem limsup_const_mul {α : Type u_1} {f : filter α} [countable_Inter_filter f] {u : α → ennreal} {a : ennreal} : (filter.limsup f fun (x : α) => a * u x) = a * filter.limsup f u := sorry

theorem limsup_add_le {α : Type u_1} {f : filter α} [countable_Inter_filter f] (u : α → ennreal) (v : α → ennreal) : filter.limsup f (u + v) ≤ filter.limsup f u + filter.limsup f v := sorry


/-
Copyright (c) 2021 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne, Kexing Ying
-/

import probability_theory.stopping
import probability_theory.notation

/-!
# Martingales

A family of functions `f : ι → α → E` is a martingale with respect to a filtration `ℱ` if every
`f i` is integrable, `f` is adapted with respect to `ℱ` and for all `i ≤ j`,
`μ[f j | ℱ.le i] =ᵐ[μ] f i`. On the other hand, `f : ι → α → E` is said to be a supermartingale
with respect to the filtration `ℱ` if `f i` is integrable, `f` is adapted with resepct to `ℱ`
and for all `i ≤ j`, `μ[f j | ℱ.le i] ≤ᵐ[μ] f i`. Finally, `f : ι → α → E` is said to be a
submartingale with respect to the filtration `ℱ` if `f i` is integrable, `f` is adapted with
resepct to `ℱ` and for all `i ≤ j`, `f i ≤ᵐ[μ] μ[f j | ℱ.le i]`.

The definitions of filtration and adapted can be found in `probability_theory.stopping`.

### Definitions

* `measure_theory.martingale f ℱ μ`: `f` is a martingale with respect to filtration `ℱ` and
  measure `μ`.
* `measure_theory.supermartingale f ℱ μ`: `f` is a supermartingale with respect to
  filtration `ℱ` and measure `μ`.
* `measure_theory.submartingale f ℱ μ`: `f` is a submartingale with respect to filtration `ℱ` and
  measure `μ`.

### Results

* `measure_theory.martingale_condexp f ℱ μ`: the sequence `λ i, μ[f | ℱ i, ℱ.le i])` is a
  martingale with respect to `ℱ` and `μ`.

-/

open topological_space filter
open_locale nnreal ennreal measure_theory probability_theory big_operators

namespace measure_theory

variables {α E ι : Type*} [preorder ι] [measurable_space E]
  {m0 : measurable_space α} {μ : measure α}
  [normed_group E] [normed_space ℝ E] [complete_space E] [borel_space E]
  [second_countable_topology E]
  {f g : ι → α → E} {ℱ : filtration ι m0} [sigma_finite_filtration μ ℱ]

/-- A family of functions `f : ι → α → E` is a martingale with respect to a filtration `ℱ` if `f`
is adapted with respect to `ℱ` and for all `i ≤ j`, `μ[f j | ℱ.le i] =ᵐ[μ] f i`. -/
def martingale (f : ι → α → E) (ℱ : filtration ι m0) (μ : measure α)
  [sigma_finite_filtration μ ℱ] : Prop :=
adapted ℱ f ∧ ∀ i j, i ≤ j → μ[f j | ℱ i, ℱ.le i] =ᵐ[μ] f i

/-- A family of integrable functions `f : ι → α → E` is a supermartingale with respect to a
filtration `ℱ` if `f` is adapted with respect to `ℱ` and for all `i ≤ j`,
`μ[f j | ℱ.le i] ≤ᵐ[μ] f i`. -/
def supermartingale [has_le E] (f : ι → α → E) (ℱ : filtration ι m0) (μ : measure α)
  [sigma_finite_filtration μ ℱ] : Prop :=
adapted ℱ f ∧ (∀ i j, i ≤ j → μ[f j | ℱ i, ℱ.le i] ≤ᵐ[μ] f i) ∧ ∀ i, integrable (f i) μ

/-- A family of integrable functions `f : ι → α → E` is a submartingale with respect to a
filtration `ℱ` if `f` is adapted with respect to `ℱ` and for all `i ≤ j`,
`f i ≤ᵐ[μ] μ[f j | ℱ.le i]`. -/
def submartingale [has_le E] (f : ι → α → E) (ℱ : filtration ι m0) (μ : measure α)
  [sigma_finite_filtration μ ℱ] : Prop :=
adapted ℱ f ∧ (∀ i j, i ≤ j → f i ≤ᵐ[μ] μ[f j | ℱ i, ℱ.le i]) ∧ ∀ i, integrable (f i) μ

variables (E)
lemma martingale_zero (ℱ : filtration ι m0) (μ : measure α) [sigma_finite_filtration μ ℱ] :
  martingale (0 : ι → α → E) ℱ μ :=
⟨adapted_zero E ℱ, λ i j hij, by { rw [pi.zero_apply, condexp_zero], simp, }⟩
variables {E}

namespace martingale

@[protected]
lemma adapted (hf : martingale f ℱ μ) : adapted ℱ f := hf.1

@[protected]
lemma measurable (hf : martingale f ℱ μ) (i : ι) : measurable[ℱ i] (f i) := hf.adapted i

lemma condexp_ae_eq (hf : martingale f ℱ μ) {i j : ι} (hij : i ≤ j) :
  μ[f j | ℱ i, ℱ.le i] =ᵐ[μ] f i :=
hf.2 i j hij

@[protected]
lemma integrable (hf : martingale f ℱ μ) (i : ι) : integrable (f i) μ :=
integrable_condexp.congr (hf.condexp_ae_eq (le_refl i))

lemma set_integral_eq (hf : martingale f ℱ μ) {i j : ι} (hij : i ≤ j) {s : set α}
  (hs : measurable_set[ℱ i] s) :
  ∫ x in s, f i x ∂μ = ∫ x in s, f j x ∂μ :=
begin
  rw ← @set_integral_condexp _ _ _ _ _ _ _ _ (ℱ i) m0 _ (ℱ.le i) _ _ _ (hf.integrable j) hs,
  refine set_integral_congr_ae (ℱ.le i s hs) _,
  filter_upwards [hf.2 i j hij] with _ heq _ using heq.symm,
end

lemma add (hf : martingale f ℱ μ) (hg : martingale g ℱ μ) : martingale (f + g) ℱ μ :=
begin
  refine ⟨hf.adapted.add hg.adapted, λ i j hij, _⟩,
  exact (condexp_add (hf.integrable j) (hg.integrable j)).trans
    ((hf.2 i j hij).add (hg.2 i j hij)),
end

lemma neg (hf : martingale f ℱ μ) : martingale (-f) ℱ μ :=
⟨hf.adapted.neg, λ i j hij, (condexp_neg (f j)).trans ((hf.2 i j hij).neg)⟩

lemma sub (hf : martingale f ℱ μ) (hg : martingale g ℱ μ) : martingale (f - g) ℱ μ :=
by { rw sub_eq_add_neg, exact hf.add hg.neg, }

lemma smul (c : ℝ) (hf : martingale f ℱ μ) : martingale (c • f) ℱ μ :=
begin
  refine ⟨hf.adapted.smul c, λ i j hij, _⟩,
  refine (condexp_smul c (f j)).trans ((hf.2 i j hij).mono (λ x hx, _)),
  rw [pi.smul_apply, hx, pi.smul_apply, pi.smul_apply],
end

lemma supermartingale [preorder E] (hf : martingale f ℱ μ) : supermartingale f ℱ μ :=
⟨hf.1, λ i j hij, (hf.2 i j hij).le, λ i, hf.integrable i⟩

lemma submartingale [preorder E] (hf : martingale f ℱ μ) : submartingale f ℱ μ :=
⟨hf.1, λ i j hij, (hf.2 i j hij).symm.le, λ i, hf.integrable i⟩

end martingale

lemma martingale_iff [partial_order E] : martingale f ℱ μ ↔
  supermartingale f ℱ μ ∧ submartingale f ℱ μ :=
⟨λ hf, ⟨hf.supermartingale, hf.submartingale⟩,
 λ ⟨hf₁, hf₂⟩, ⟨hf₁.1, λ i j hij, (hf₁.2.1 i j hij).antisymm (hf₂.2.1 i j hij)⟩⟩

lemma martingale_condexp (f : α → E) (ℱ : filtration ι m0) (μ : measure α)
  [sigma_finite_filtration μ ℱ] :
  martingale (λ i, μ[f | ℱ i, ℱ.le i]) ℱ μ :=
⟨λ i, measurable_condexp, λ i j hij, condexp_condexp_of_le (ℱ.mono hij) _⟩

namespace supermartingale

@[protected]
lemma adapted [has_le E] (hf : supermartingale f ℱ μ) : adapted ℱ f := hf.1

@[protected]
lemma measurable [has_le E] (hf : supermartingale f ℱ μ) (i : ι) : measurable[ℱ i] (f i) :=
hf.adapted i

@[protected]
lemma integrable [has_le E] (hf : supermartingale f ℱ μ) (i : ι) : integrable (f i) μ := hf.2.2 i

lemma condexp_ae_le [has_le E] (hf : supermartingale f ℱ μ) {i j : ι} (hij : i ≤ j) :
  μ[f j | ℱ i, ℱ.le i] ≤ᵐ[μ] f i :=
hf.2.1 i j hij

lemma set_integral_le {f : ι → α → ℝ} (hf : supermartingale f ℱ μ)
  {i j : ι} (hij : i ≤ j) {s : set α} (hs : measurable_set[ℱ i] s) :
  ∫ x in s, f j x ∂μ ≤ ∫ x in s, f i x ∂μ :=
begin
  rw ← set_integral_condexp (ℱ.le i) (hf.integrable j) hs,
  refine set_integral_mono_ae integrable_condexp.integrable_on (hf.integrable i).integrable_on _,
  filter_upwards [hf.2.1 i j hij] with _ heq using heq,
end

lemma add [preorder E] [covariant_class E E (+) (≤)]
  (hf : supermartingale f ℱ μ) (hg : supermartingale g ℱ μ) : supermartingale (f + g) ℱ μ :=
begin
  refine ⟨hf.1.add hg.1, λ i j hij, _, λ i, (hf.2.2 i).add (hg.2.2 i)⟩,
  refine (condexp_add (hf.integrable j) (hg.integrable j)).le.trans _,
  filter_upwards [hf.2.1 i j hij, hg.2.1 i j hij],
  intros,
  refine add_le_add _ _; assumption,
end

lemma add_martingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : supermartingale f ℱ μ) (hg : martingale g ℱ μ) : supermartingale (f + g) ℱ μ :=
hf.add hg.supermartingale

lemma neg [preorder E] [covariant_class E E (+) (≤)]
  (hf : supermartingale f ℱ μ) : submartingale (-f) ℱ μ :=
begin
  refine ⟨hf.1.neg, λ i j hij, _, λ i, (hf.2.2 i).neg⟩,
  refine eventually_le.trans _ (condexp_neg (f j)).symm.le,
  filter_upwards [hf.2.1 i j hij] with _ _,
  simpa,
end

end supermartingale

namespace submartingale

@[protected]
lemma adapted [has_le E] (hf : submartingale f ℱ μ) : adapted ℱ f := hf.1

@[protected]
lemma measurable [has_le E] (hf : submartingale f ℱ μ) (i : ι) : measurable[ℱ i] (f i) :=
hf.adapted i

@[protected]
lemma integrable [has_le E] (hf : submartingale f ℱ μ) (i : ι) : integrable (f i) μ := hf.2.2 i

lemma ae_le_condexp [has_le E] (hf : submartingale f ℱ μ) {i j : ι} (hij : i ≤ j) :
  f i ≤ᵐ[μ] μ[f j | ℱ i, ℱ.le i] :=
hf.2.1 i j hij

lemma add [preorder E] [covariant_class E E (+) (≤)]
  (hf : submartingale f ℱ μ) (hg : submartingale g ℱ μ) : submartingale (f + g) ℱ μ :=
begin
  refine ⟨hf.1.add hg.1, λ i j hij, _, λ i, (hf.2.2 i).add (hg.2.2 i)⟩,
  refine eventually_le.trans _ (condexp_add (hf.integrable j) (hg.integrable j)).symm.le,
  filter_upwards [hf.2.1 i j hij, hg.2.1 i j hij],
  intros,
  refine add_le_add _ _; assumption,
end

lemma add_martingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : submartingale f ℱ μ) (hg : martingale g ℱ μ) : submartingale (f + g) ℱ μ :=
hf.add hg.submartingale

lemma neg [preorder E] [covariant_class E E (+) (≤)]
  (hf : submartingale f ℱ μ) : supermartingale (-f) ℱ μ :=
begin
  refine ⟨hf.1.neg, λ i j hij, (condexp_neg (f j)).le.trans _, λ i, (hf.2.2 i).neg⟩,
  filter_upwards [hf.2.1 i j hij] with _ _,
  simpa,
end

lemma set_integral_le {f : ι → α → ℝ} (hf : submartingale f ℱ μ)
  {i j : ι} (hij : i ≤ j) {s : set α} (hs : measurable_set[ℱ i] s) :
  ∫ x in s, f i x ∂μ ≤ ∫ x in s, f j x ∂μ :=
begin
  rw [← neg_le_neg_iff, ← integral_neg, ← integral_neg],
  exact supermartingale.set_integral_le hf.neg hij hs,
end

lemma sub_supermartingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : submartingale f ℱ μ) (hg : supermartingale g ℱ μ) : submartingale (f - g) ℱ μ :=
by { rw sub_eq_add_neg, exact hf.add hg.neg }

lemma sub_martingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : submartingale f ℱ μ) (hg : martingale g ℱ μ) : submartingale (f - g) ℱ μ :=
hf.sub_supermartingale hg.supermartingale

end submartingale

namespace supermartingale

lemma sub_submartingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : supermartingale f ℱ μ) (hg : submartingale g ℱ μ) : supermartingale (f - g) ℱ μ :=
by { rw sub_eq_add_neg, exact hf.add hg.neg }

lemma sub_martingale [preorder E] [covariant_class E E (+) (≤)]
  (hf : supermartingale f ℱ μ) (hg : martingale g ℱ μ) : supermartingale (f - g) ℱ μ :=
hf.sub_submartingale hg.submartingale

section

variables {F : Type*} [measurable_space F] [normed_lattice_add_comm_group F]
  [normed_space ℝ F] [complete_space F] [borel_space F] [second_countable_topology F]
  [ordered_smul ℝ F]

lemma smul_nonneg {f : ι → α → F}
  {c : ℝ} (hc : 0 ≤ c) (hf : supermartingale f ℱ μ) :
  supermartingale (c • f) ℱ μ :=
begin
  refine ⟨hf.1.smul c, λ i j hij, _, λ i, (hf.2.2 i).smul c⟩,
  refine (condexp_smul c (f j)).le.trans _,
  filter_upwards [hf.2.1 i j hij] with _ hle,
  simp,
  exact smul_le_smul_of_nonneg hle hc,
end

lemma smul_nonpos {f : ι → α → F}
  {c : ℝ} (hc : c ≤ 0) (hf : supermartingale f ℱ μ) :
  submartingale (c • f) ℱ μ :=
begin
  rw [← neg_neg c, (by { ext i x, simp } : - -c • f = -(-c • f))],
  exact (hf.smul_nonneg $ neg_nonneg.2 hc).neg,
end

end

end supermartingale

namespace submartingale

section

variables {F : Type*} [measurable_space F] [normed_lattice_add_comm_group F]
  [normed_space ℝ F] [complete_space F] [borel_space F] [second_countable_topology F]
  [ordered_smul ℝ F]

lemma smul_nonneg {f : ι → α → F}
  {c : ℝ} (hc : 0 ≤ c) (hf : submartingale f ℱ μ) :
  submartingale (c • f) ℱ μ :=
begin
  rw [← neg_neg c, (by { ext i x, simp } : - -c • f = -(c • -f))],
  exact supermartingale.neg (hf.neg.smul_nonneg hc),
end

lemma smul_nonpos {f : ι → α → F}
  {c : ℝ} (hc : c ≤ 0) (hf : submartingale f ℱ μ) :
  supermartingale (c • f) ℱ μ :=
begin
  rw [← neg_neg c, (by { ext i x, simp } : - -c • f = -(-c • f))],
  exact (hf.smul_nonneg $ neg_nonneg.2 hc).neg,
end

end

end submartingale

section nat

variables {𝒢 : filtration ℕ m0} [sigma_finite_filtration μ 𝒢]

namespace submartingale

lemma integrable_stopped_value [has_le E] {f : ℕ → α → E} (hf : submartingale f 𝒢 μ) {τ : α → ℕ}
  (hτ : is_stopping_time 𝒢 τ) {N : ℕ} (hbdd : ∀ x, τ x ≤ N) :
  integrable (stopped_value f τ) μ :=
integrable_stopped_value hτ hf.integrable hbdd

-- We may generalize the below lemma to functions taking value in a `normed_lattice_add_comm_group`.
-- Similarly, generalize `(super/)submartingale.set_integral_le`.

/-- Given a submartingale `f` and bounded stopping times `τ` and `π` such that `τ ≤ π`, the
expectation of `stopped_value f τ` is less or equal to the expectation of `stopped_value f π`.
This is the forward direction of the optional stopping theorem. -/
lemma expected_stopped_value_mono {f : ℕ → α → ℝ} (hf : submartingale f 𝒢 μ) {τ π : α → ℕ}
  (hτ : is_stopping_time 𝒢 τ) (hπ : is_stopping_time 𝒢 π) (hle : τ ≤ π)
  {N : ℕ} (hbdd : ∀ x, π x ≤ N) :
  μ[stopped_value f τ] ≤ μ[stopped_value f π] :=
begin
  rw [← sub_nonneg, ← integral_sub', stopped_value_sub_eq_sum' hle hbdd],
  { simp only [finset.sum_apply],
    have : ∀ i, measurable_set[𝒢 i] {x : α | τ x ≤ i ∧ i < π x},
    { intro i,
      refine (hτ i).inter _,
      convert (hπ i).compl,
      ext x,
      simpa },
    rw integral_finset_sum,
    { refine finset.sum_nonneg (λ i hi, _),
      rw [integral_indicator (𝒢.le _ _ (this _)), integral_sub', sub_nonneg],
      { exact hf.set_integral_le (nat.le_succ i) (this _) },
      { exact (hf.integrable _).integrable_on },
      { exact (hf.integrable _).integrable_on } },
    intros i hi,
    exact integrable.indicator (integrable.sub (hf.integrable _) (hf.integrable _))
      (𝒢.le _ _ (this _)) },
  { exact hf.integrable_stopped_value hπ hbdd },
  { exact hf.integrable_stopped_value hτ (λ x, le_trans (hle x) (hbdd x)) }
end

end submartingale

end nat

end measure_theory


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

! This file was ported from Lean 3 source module number_theory.legendre_symbol.zmod_char
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Int.Range
import Mathbin.Data.Zmod.Basic
import Mathbin.NumberTheory.LegendreSymbol.MulCharacter

/-!
# Quadratic characters on ℤ/nℤ

This file defines some quadratic characters on the rings ℤ/4ℤ and ℤ/8ℤ.

We set them up to be of type `mul_char (zmod n) ℤ`, where `n` is `4` or `8`.

## Tags

quadratic character, zmod
-/

/-!
### Quadratic characters mod 4 and 8

We define the primitive quadratic characters `χ₄`on `zmod 4`
and `χ₈`, `χ₈'` on `zmod 8`.
-/

namespace ZMod

section QuadCharModP

/-- Define the nontrivial quadratic character on `zmod 4`, `χ₄`.
It corresponds to the extension `ℚ(√-1)/ℚ`. -/
@[simps]
def χ₄ : MulChar (ZMod 4) ℤ
    where
  toFun := (![0, 1, 0, -1] : ZMod 4 → ℤ)
  map_one' := rfl
  map_mul' := by decide
  map_nonunit' := by decide
#align zmod.χ₄ ZMod.χ₄

/-- `χ₄` takes values in `{0, 1, -1}` -/
theorem isQuadratic_χ₄ : χ₄.IsQuadratic := by
  intro a
  decide!
#align zmod.is_quadratic_χ₄ ZMod.isQuadratic_χ₄

/-- The value of `χ₄ n`, for `n : ℕ`, depends only on `n % 4`. -/
theorem χ₄_nat_mod_four (n : ℕ) : χ₄ n = χ₄ (n % 4 : ℕ) := by rw [← ZMod.nat_cast_mod n 4]
#align zmod.χ₄_nat_mod_four ZMod.χ₄_nat_mod_four

/-- The value of `χ₄ n`, for `n : ℤ`, depends only on `n % 4`. -/
theorem χ₄_int_mod_four (n : ℤ) : χ₄ n = χ₄ (n % 4 : ℤ) :=
  by
  rw [← ZMod.int_cast_mod n 4]
  norm_cast
#align zmod.χ₄_int_mod_four ZMod.χ₄_int_mod_four

/-- An explicit description of `χ₄` on integers / naturals -/
theorem χ₄_int_eq_if_mod_four (n : ℤ) :
    χ₄ n = if n % 2 = 0 then 0 else if n % 4 = 1 then 1 else -1 :=
  by
  have help : ∀ m : ℤ, 0 ≤ m → m < 4 → χ₄ m = if m % 2 = 0 then 0 else if m = 1 then 1 else -1 := by
    decide
  rw [← Int.emod_emod_of_dvd n (by norm_num : (2 : ℤ) ∣ 4), ← ZMod.int_cast_mod n 4]
  exact help (n % 4) (Int.emod_nonneg n (by norm_num)) (Int.emod_lt n (by norm_num))
#align zmod.χ₄_int_eq_if_mod_four ZMod.χ₄_int_eq_if_mod_four

theorem χ₄_nat_eq_if_mod_four (n : ℕ) :
    χ₄ n = if n % 2 = 0 then 0 else if n % 4 = 1 then 1 else -1 := by
  exact_mod_cast χ₄_int_eq_if_mod_four n
#align zmod.χ₄_nat_eq_if_mod_four ZMod.χ₄_nat_eq_if_mod_four

/-- Alternative description of `χ₄ n` for odd `n : ℕ` in terms of powers of `-1` -/
theorem χ₄_eq_neg_one_pow {n : ℕ} (hn : n % 2 = 1) : χ₄ n = (-1) ^ (n / 2) :=
  by
  rw [χ₄_nat_eq_if_mod_four]
  simp only [hn, Nat.one_ne_zero, if_false]
  nth_rw 1 [← Nat.div_add_mod n 4]
  nth_rw 1 [(by norm_num : 4 = 2 * 2)]
  rw [mul_assoc, add_comm, Nat.add_mul_div_left _ _ (by norm_num : 0 < 2), pow_add, pow_mul,
    neg_one_sq, one_pow, mul_one]
  have help : ∀ m : ℕ, m < 4 → m % 2 = 1 → ite (m = 1) (1 : ℤ) (-1) = (-1) ^ (m / 2) := by decide
  exact
    help (n % 4) (Nat.mod_lt n (by norm_num))
      ((Nat.mod_mod_of_dvd n (by norm_num : 2 ∣ 4)).trans hn)
#align zmod.χ₄_eq_neg_one_pow ZMod.χ₄_eq_neg_one_pow

/-- If `n % 4 = 1`, then `χ₄ n = 1`. -/
theorem χ₄_nat_one_mod_four {n : ℕ} (hn : n % 4 = 1) : χ₄ n = 1 :=
  by
  rw [χ₄_nat_mod_four, hn]
  rfl
#align zmod.χ₄_nat_one_mod_four ZMod.χ₄_nat_one_mod_four

/-- If `n % 4 = 3`, then `χ₄ n = -1`. -/
theorem χ₄_nat_three_mod_four {n : ℕ} (hn : n % 4 = 3) : χ₄ n = -1 :=
  by
  rw [χ₄_nat_mod_four, hn]
  rfl
#align zmod.χ₄_nat_three_mod_four ZMod.χ₄_nat_three_mod_four

/-- If `n % 4 = 1`, then `χ₄ n = 1`. -/
theorem χ₄_int_one_mod_four {n : ℤ} (hn : n % 4 = 1) : χ₄ n = 1 :=
  by
  rw [χ₄_int_mod_four, hn]
  rfl
#align zmod.χ₄_int_one_mod_four ZMod.χ₄_int_one_mod_four

/-- If `n % 4 = 3`, then `χ₄ n = -1`. -/
theorem χ₄_int_three_mod_four {n : ℤ} (hn : n % 4 = 3) : χ₄ n = -1 :=
  by
  rw [χ₄_int_mod_four, hn]
  rfl
#align zmod.χ₄_int_three_mod_four ZMod.χ₄_int_three_mod_four

/-- If `n % 4 = 1`, then `(-1)^(n/2) = 1`. -/
theorem neg_one_pow_div_two_of_one_mod_four {n : ℕ} (hn : n % 4 = 1) : (-1 : ℤ) ^ (n / 2) = 1 :=
  by
  rw [← χ₄_eq_neg_one_pow (Nat.odd_of_mod_four_eq_one hn), ← nat_cast_mod, hn]
  rfl
#align neg_one_pow_div_two_of_one_mod_four neg_one_pow_div_two_of_one_mod_four

/-- If `n % 4 = 3`, then `(-1)^(n/2) = -1`. -/
theorem neg_one_pow_div_two_of_three_mod_four {n : ℕ} (hn : n % 4 = 3) : (-1 : ℤ) ^ (n / 2) = -1 :=
  by
  rw [← χ₄_eq_neg_one_pow (Nat.odd_of_mod_four_eq_three hn), ← nat_cast_mod, hn]
  rfl
#align neg_one_pow_div_two_of_three_mod_four neg_one_pow_div_two_of_three_mod_four

/-- Define the first primitive quadratic character on `zmod 8`, `χ₈`.
It corresponds to the extension `ℚ(√2)/ℚ`. -/
@[simps]
def χ₈ : MulChar (ZMod 8) ℤ
    where
  toFun := (![0, 1, 0, -1, 0, -1, 0, 1] : ZMod 8 → ℤ)
  map_one' := rfl
  map_mul' := by decide
  map_nonunit' := by decide
#align zmod.χ₈ ZMod.χ₈

/-- `χ₈` takes values in `{0, 1, -1}` -/
theorem isQuadratic_χ₈ : χ₈.IsQuadratic := by
  intro a
  decide!
#align zmod.is_quadratic_χ₈ ZMod.isQuadratic_χ₈

/-- The value of `χ₈ n`, for `n : ℕ`, depends only on `n % 8`. -/
theorem χ₈_nat_mod_eight (n : ℕ) : χ₈ n = χ₈ (n % 8 : ℕ) := by rw [← ZMod.nat_cast_mod n 8]
#align zmod.χ₈_nat_mod_eight ZMod.χ₈_nat_mod_eight

/-- The value of `χ₈ n`, for `n : ℤ`, depends only on `n % 8`. -/
theorem χ₈_int_mod_eight (n : ℤ) : χ₈ n = χ₈ (n % 8 : ℤ) :=
  by
  rw [← ZMod.int_cast_mod n 8]
  norm_cast
#align zmod.χ₈_int_mod_eight ZMod.χ₈_int_mod_eight

/-- An explicit description of `χ₈` on integers / naturals -/
theorem χ₈_int_eq_if_mod_eight (n : ℤ) :
    χ₈ n = if n % 2 = 0 then 0 else if n % 8 = 1 ∨ n % 8 = 7 then 1 else -1 :=
  by
  have help :
    ∀ m : ℤ, 0 ≤ m → m < 8 → χ₈ m = if m % 2 = 0 then 0 else if m = 1 ∨ m = 7 then 1 else -1 := by
    decide
  rw [← Int.emod_emod_of_dvd n (by norm_num : (2 : ℤ) ∣ 8), ← ZMod.int_cast_mod n 8]
  exact help (n % 8) (Int.emod_nonneg n (by norm_num)) (Int.emod_lt n (by norm_num))
#align zmod.χ₈_int_eq_if_mod_eight ZMod.χ₈_int_eq_if_mod_eight

theorem χ₈_nat_eq_if_mod_eight (n : ℕ) :
    χ₈ n = if n % 2 = 0 then 0 else if n % 8 = 1 ∨ n % 8 = 7 then 1 else -1 := by
  exact_mod_cast χ₈_int_eq_if_mod_eight n
#align zmod.χ₈_nat_eq_if_mod_eight ZMod.χ₈_nat_eq_if_mod_eight

/-- Define the second primitive quadratic character on `zmod 8`, `χ₈'`.
It corresponds to the extension `ℚ(√-2)/ℚ`. -/
@[simps]
def χ₈' : MulChar (ZMod 8) ℤ
    where
  toFun := (![0, 1, 0, 1, 0, -1, 0, -1] : ZMod 8 → ℤ)
  map_one' := rfl
  map_mul' := by decide
  map_nonunit' := by decide
#align zmod.χ₈' ZMod.χ₈'

/-- `χ₈'` takes values in `{0, 1, -1}` -/
theorem isQuadratic_χ₈' : χ₈'.IsQuadratic := by
  intro a
  decide!
#align zmod.is_quadratic_χ₈' ZMod.isQuadratic_χ₈'

/-- An explicit description of `χ₈'` on integers / naturals -/
theorem χ₈'_int_eq_if_mod_eight (n : ℤ) :
    χ₈' n = if n % 2 = 0 then 0 else if n % 8 = 1 ∨ n % 8 = 3 then 1 else -1 :=
  by
  have help :
    ∀ m : ℤ, 0 ≤ m → m < 8 → χ₈' m = if m % 2 = 0 then 0 else if m = 1 ∨ m = 3 then 1 else -1 := by
    decide
  rw [← Int.emod_emod_of_dvd n (by norm_num : (2 : ℤ) ∣ 8), ← ZMod.int_cast_mod n 8]
  exact help (n % 8) (Int.emod_nonneg n (by norm_num)) (Int.emod_lt n (by norm_num))
#align zmod.χ₈'_int_eq_if_mod_eight ZMod.χ₈'_int_eq_if_mod_eight

theorem χ₈'_nat_eq_if_mod_eight (n : ℕ) :
    χ₈' n = if n % 2 = 0 then 0 else if n % 8 = 1 ∨ n % 8 = 3 then 1 else -1 := by
  exact_mod_cast χ₈'_int_eq_if_mod_eight n
#align zmod.χ₈'_nat_eq_if_mod_eight ZMod.χ₈'_nat_eq_if_mod_eight

/-- The relation between `χ₄`, `χ₈` and `χ₈'` -/
theorem χ₈'_eq_χ₄_mul_χ₈ (a : ZMod 8) : χ₈' a = χ₄ a * χ₈ a := by decide!
#align zmod.χ₈'_eq_χ₄_mul_χ₈ ZMod.χ₈'_eq_χ₄_mul_χ₈

theorem χ₈'_int_eq_χ₄_mul_χ₈ (a : ℤ) : χ₈' a = χ₄ a * χ₈ a :=
  by
  rw [← @cast_int_cast 8 (ZMod 4) _ 4 _ (by norm_num) a]
  exact χ₈'_eq_χ₄_mul_χ₈ a
#align zmod.χ₈'_int_eq_χ₄_mul_χ₈ ZMod.χ₈'_int_eq_χ₄_mul_χ₈

end QuadCharModP

end ZMod