import data.nat.choose
import data.nat.prime
import data.nat.gcd
import data.list
import tactic.linarith

import data.set.finite
import data.multiset
import data.finset
import data.finsupp

open classical

local attribute [instance] nat.decidable_prime_1

/-
    https://en.wikipedia.org/wiki/Proof_of_Bertrand%27s_postulate
-/

/- Misc lemmas -/

lemma prod_le_prod : ∀ (l : list ℕ) (f g : ℕ → ℕ) ,
    (∀ (x:ℕ) , x ∈ l → f x ≤ g x) →
    (l.map f).prod ≤ (l.map g).prod := sorry

lemma sum_le_sum : ∀ (l : list ℕ) (f g : ℕ → ℕ) ,
    (∀ (x:ℕ) , x ∈ l → f x ≤ g x) →
    (l.map f).sum ≤ (l.map g).sum := sorry

/- Primes dividing choose -/

/- Define a floor log function for natural numbers.
    TODO does this already exist somewhere? -/
def log : ℕ → ℕ → ℕ
| 0 _ := 0
| 1 _ := 0
| _ 0 := 0
| (p+2) (n+1) := if (n+1) < (p+2) then 0 else (
    have h : (n+1)/(p+2) < n+1, from begin
        calc (n + 1) / (p + 2) ≤ (n+1) / 2 : sorry
        ... < (n+1) / 1 : sorry
        ... = n+1 : by simp
    end,
    log (p+2) ((n+1)/(p+2)) + 1
)

theorem pow_log_le : ∀ (a b : ℕ) ,
    a ^ (log a b) ≤ b := sorry

/- This is a closed form for the largest exponent
    r such that p^r divides (choose n k). -/
def lpdc (n:ℕ) (k:ℕ) (p:ℕ) :=
    ((list.range' 1 (log p n)).map
        (λ (i:ℕ) , if (k % (p^i)) + ((n-k) % (p^i)) ≥ p^i then 1 else 0)).sum

theorem p_pow_lpdc_dvd_choose : ∀ (n k p : ℕ) ,
    p ^ lpdc n k p ∣ choose n k := sorry

theorem exp_le_lpdc_of_dvd_choose : ∀ (n k p r : ℕ) ,
    p ^ r ∣ choose n k → r ≤ lpdc n k p := sorry

-- Which has this corollary:

lemma exp_le_log_of_dvd_choose : ∀ (n k p r : ℕ) ,
    p ^ r ∣ choose n k → r ≤ log p n :=
begin
    intros ,
    have h : r ≤ lpdc n k p := exp_le_lpdc_of_dvd_choose n k p r a,
    rw [lpdc] at h ,
    have h' := sum_le_sum (list.range' 1 (log p n))
        (λ (i : ℕ), ite (k % p ^ i + (n - k) % p ^ i ≥ p ^ i) 1 0)
        (λ (i : ℕ), 1)
        (begin
            intros , intros , simp , split_ifs , linarith , linarith ,
        end),
    simp at h' ,
    linarith ,
end

/- "lemma 1" -/

lemma four_n_bound : ∀ (n : ℕ) ,
    4 ^ n ≤ 2 * n * (choose (2*n) n) := sorry

/- "lemma 2" -/

lemma p_to_R_bound : ∀ (p:ℕ) (r:ℕ) (n:ℕ) ,
    nat.prime p →
    p ^ r ∣ (choose (2*n) n) →
    p ^ r ≤ 2*n :=
begin
    intros ,
    have h : r ≤ log p (2*n) := exp_le_log_of_dvd_choose (2*n) n p r a_1,
    calc p ^ r ≤ p ^ (log p (2*n)) : begin
        apply nat.pow_le_pow_of_le_right ,
        cases p ,
        have q : ¬ nat.prime 0 := dec_trivial , contradiction ,
        rw nat.succ_eq_add_one , linarith ,
        assumption ,
    end
        ... ≤ 2*n : begin
        apply pow_log_le ,
    end
end

/- "lemma 3" -/

theorem log_p_eq_1 : ∀ (p n) ,
    p ≥ 3 → 2*n / 3 < p → p ≤ n →
    log p (2*n) = 1 := sorry

lemma central_primes_do_not_divide : ∀ (p n : ℕ) ,
    nat.prime p →
    p ≠ 2 →
    2*n / 3 < p →
    p ≤ n →
    ¬ (p ∣ (choose (2*n) n)) :=
begin
    intros , by_contradiction ,
    have h := exp_le_lpdc_of_dvd_choose (2*n) n p 1 (begin
        simp , assumption ,
    end) ,

    have log_1 : log p (2*n) = 1 := begin
        apply log_p_eq_1 ,
        cases p ,
        have h : ¬ (nat.prime 0) := dec_trivial ,
        contradiction ,
        cases p ,
        have h : ¬ (nat.prime 1) := dec_trivial ,
        contradiction ,
        cases p ,
        contradiction ,
        rw nat.succ_eq_add_one ,
        rw nat.succ_eq_add_one ,
        rw nat.succ_eq_add_one ,
        linarith , assumption, assumption ,
    end,

    have j : lpdc (2 * n) n p = 0 := begin
        rw [lpdc] , rw log_1 , rw [list.range'] ,
        simp ,
        have l : ¬(n % p + (2 * n - n) % p ≥ p) := begin
            simp , rw two_mul , rw nat.add_sub_cancel ,
            rw <- two_mul ,
            have t : n = (n - p) + p := begin
                rw nat.sub_add_cancel , assumption ,
            end,
            rw t , simp ,
            have s : ((n - p) % p) ≤ n-p := begin
                apply nat.mod_le ,
            end,
            have r : 2*n < 3*p := begin
                rw [@nat.mul_comm 3 p] ,
                rw <- nat.div_lt_iff_lt_mul' , assumption ,
                norm_num ,
            end ,
            have q : 2*(n-p) < p := begin
                rw nat.mul_sub_left_distrib ,
                calc 2*n - 2*p < 3*p - 2*p :
                    begin
                        rw nat.sub_lt_sub_right_iff , assumption , linarith ,
                    end
                ... = (3-2)*p : by rw nat.mul_sub_right_distrib
                ... = p : by norm_num
            end,
            linarith ,
        end,
        simp [l] ,
    end,
    rw j at h , linarith ,
end

/- range-related lemmas -/

def range_to (n:ℕ) (m:ℕ) := list.range' n (m-(n-1))

theorem range_to_append : ∀ (n m k : ℕ) ,
    n ≤ m+1 → m ≤ k →
    range_to n m ++ range_to (m+1) k = range_to n k := sorry

/- "lemma 4" -/

def primorial (n : ℕ) : ℕ :=
    (list.filter nat.prime (range_to 1 n)).prod

lemma primorial_induction : ∀ (n : ℕ) ,
    primorial (n+1) = (if nat.prime (n+1) then primorial n * (n+1) else primorial n) := sorry

lemma zero_lt_primorial : ∀ (n : ℕ) ,
    0 < primorial n :=
begin
    intros , induction n,
    simp [primorial, list.filter, range_to, list.prod] , linarith ,
    rw primorial_induction ,
    split_ifs , rw mul_add , simp , linarith ,
    assumption ,
end

lemma primorial_eq_primorial_mul_primes : ∀ (n m : ℕ) ,
    m ≤ n →
    primorial n = primorial m *
        (list.filter nat.prime (range_to (m+1) n)).prod :=
begin
    intros ,
    rw [primorial, primorial] ,
        rw <- list.prod_append,
        rw <- list.filter_append ,
        rw <- range_to_append ,
        linarith, linarith ,
end

lemma primorial_ratio_eq_prod : ∀ (n m : ℕ) ,
    m ≤ n →
    primorial n / primorial m =
        (list.filter nat.prime (range_to (m+1) n)).prod :=
begin
    intros ,
    have h : primorial n = primorial m * (list.filter nat.prime (range_to (m+1) n)).prod := primorial_eq_primorial_mul_primes n m a,
    rw h , rw nat.mul_div_cancel_left ,
    apply zero_lt_primorial ,
end

lemma coprime_list : ∀ (m n p : ℕ) ,
    n < p →
    nat.prime p →
    nat.coprime (list.prod (list.filter nat.prime (range_to (m + 1) n))) p :=
begin
    intros , rw [range_to] , rw [nat.add_sub_cancel] ,
    by_cases (n < m),
    {
        have q : n-m = 0 := begin rw nat.sub_eq_zero_iff_le , apply nat.le_of_lt , assumption end,
        rw q , simp ,
    },
    have j : n - m + m = n := begin
        rw nat.sub_add_cancel , simp at h , assumption ,
    end,
    rw <- j at a,
    revert a , induction (n-m) ,
    {
        intros , simp ,
    },
    {
        intros , rw nat.succ_eq_add_one ,
        have k : list.range' (m + 1) (n_1 + 1) =
            list.range' (m + 1) n_1 ++
            list.range' (m + 1 + n_1) 1 := begin
            rw list.range'_append , rw nat.add_comm ,
            rw [@nat.add_comm 1 n_1] ,
        end,
        have k' : list.range' (m + 1 + n_1) 1 = [m + 1 + n_1] := by simp ,
        rw k' at k , rw k , rw list.filter_append , rw list.prod_append ,
        apply nat.coprime.mul ,
        apply ih , rw nat.succ_eq_add_one at a , linarith ,
        rw [list.filter] , split_ifs , simp ,
        rw [@nat.add_comm n_1 1] , rw <- nat.add_assoc ,
        rw nat.coprime_primes , rw nat.succ_eq_add_one at a ,
        by_contradiction , simp at a_2 , linarith ,
        assumption , assumption ,
        simp ,
    }
end

lemma prime_list_dvd : ∀ (n m k : ℕ) ,
    (∀ (p:ℕ) , nat.prime p → m+1 ≤ p → p ≤ n → p ∣ k) →
    (list.filter nat.prime (range_to (m+1) n)).prod ∣ k :=
begin
    intros, induction n ,
    {
        rw [range_to] , simp ,
    },
    {
        by_cases (m ≤ n_n) ,
        {
            have h : range_to (m + 1) (nat.succ n_n) = range_to (m+1) n_n ++ range_to (n_n + 1) (n_n + 1) := begin
                rw range_to_append , linarith , linarith ,
            end,
            rw h , rw list.filter_append , rw list.prod_append ,
            have h1 : range_to (n_n + 1) (n_n + 1) = [n_n + 1] := by simp [range_to] ,
            rw h1 , simp [list.filter] ,
            split_ifs ,
            {
                 have h2 : list.prod [n_n + 1] = n_n + 1 := by simp ,
                 rw h2 ,

                 have n_n_plus_1_dvd_k := a (n_n + 1) h_1 (by linarith) (begin
                    have t : nat.succ n_n = n_n + 1 := rfl , rw t ,
                 end),
                 apply nat.coprime.mul_dvd_of_dvd_of_dvd ,
                 apply coprime_list , linarith , assumption ,
                 apply n_ih , intros , apply a , assumption, assumption ,
                 have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 , linarith ,
                 assumption ,
            },
            {
                simp , apply n_ih , intros , apply a , assumption, assumption, have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 , linarith ,
            }
        },
        {
            have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 ,
            have t : n_n + 1 - m = 0 := begin
                rw nat.sub_eq_zero_iff_le , simp at h ,
                apply nat.le_of_lt_succ ,
                have h_4 : nat.succ m = m + 1 := rfl , rw h_4 ,
                linarith ,
            end,
            have h : range_to (m + 1) (n_n + 1) = [] := begin
                rw [range_to] , simp at h , rw nat.add_sub_cancel ,
                rw t , simp ,
            end,
            rw h , simp ,
        }
    }
end

lemma p_dvd_choose_2m_plus_1_of_ge_m_plus_2 : ∀ (m p : ℕ) ,
    nat.prime p →
    m + 2 ≤ p →
    p ≤ 2*m + 1 →
    p ∣ choose (2*m+1) (m+1) :=
begin
    intros ,
    have q := @choose_mul_fact_mul_fact (2*m + 1) (m + 1) (by linarith) ,
    have r : p ∣ nat.fact (2 * m + 1) := begin
            rw nat.prime.dvd_fact , assumption , assumption ,
        end,
    have r1 : ¬ (p ∣ nat.fact (m + 1)) := begin
            rw nat.prime.dvd_fact , simp , linarith , assumption ,
        end,
    have r2 : ¬ (p ∣ nat.fact (2 * m + 1 - (m + 1))) := begin
            rw nat.prime.dvd_fact , simp ,
            have h1 : 1 + 2 * m = (m + (m+1)) := by ring ,
            rw h1 , rw nat.add_sub_cancel , linarith ,
            assumption ,
        end,
    rw <- q at r ,
    rw nat.prime.dvd_mul at r ,
    cases r ,
    rw nat.prime.dvd_mul at r ,
    cases r , assumption , contradiction , assumption , contradiction ,
    assumption ,
end

lemma primorial_ratio_le_choose : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) / primorial (m+1) ≤ (choose (2*m + 1) (m+1)) :=
begin
    intros ,
    rw primorial_ratio_eq_prod ,
    apply nat.le_of_dvd ,
    {
        apply choose_pos , linarith ,
    },
    {
        apply prime_list_dvd , intros , apply p_dvd_choose_2m_plus_1_of_ge_m_plus_2 , assumption, linarith , assumption ,
    },
    {
        linarith ,
    },

end

lemma primorial_2m_plus_1_le_choose : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) ≤ primorial (m+1) * (choose (2*m + 1) (m+1)) :=
begin
    intros ,
    calc primorial (2*m + 1) =
         primorial (m+1) * (primorial (2*m + 1) / primorial (m+1)) :
    begin
        rw nat.mul_div_cancel' ,
        rw [primorial_eq_primorial_mul_primes (2*m+1) (m+1)] , simp ,
        linarith ,
    end
    ... ≤ primorial (m+1) * (choose (2*m + 1) (m+1)) :
    begin
        apply nat.mul_le_mul_left , apply primorial_ratio_le_choose , assumption ,
    end
end

lemma finset_sum_finset_range_eq_sum_range : ∀ (f: ℕ → ℕ) (n : ℕ) ,
    finset.sum (finset.range n) f = ((list.range n).map f).sum :=
begin
    intros , induction n , simp , rw[list.range] , rw [list.range_core] ,
    simp ,

    have h1 : list.sum (list.map f (list.range (nat.succ n_n)))
            = list.sum (list.map f (list.range n_n)) + f n_n :=
        begin
            rw [list.range_concat] , simp ,
        end,
    have h2 : finset.sum (finset.range (nat.succ n_n)) f
            = finset.sum (finset.range n_n) f + f n_n :=
        begin
            rw [finset.sum_range_succ] , rw nat.add_comm ,
        end,
    rw h1 , rw h2 , rw n_ih ,
end

lemma choose_2m_plus_1_le_power_2 : ∀ (m : ℕ) ,
    choose (2*m + 1) (m+1) ≤ 2^(2*m) :=
begin
    intros ,
    have t := (
        calc 2 * 2^(2*m) = 2^(2*m+1) :
            begin
                rw nat.pow_add , norm_num , ring ,
            end
        ... = ((1:ℕ) + (1:ℕ))^(2*m+1) : by simp
    ),
    have h := add_pow 1 1 (2*m+1) , simp at h ,
    simp at t , rw h at t , clear h,

    rw finset_sum_finset_range_eq_sum_range at t,

    have l : list.range (nat.succ (1 + 2 * m)) =
        list.range' 0 m ++
        list.range' m 2 ++
        list.range' (m+2) m :=
        begin
            have t1 : list.range' m 2 = list.range' (0+m) 2 := by simp , rw t1 ,
            rw [list.range_eq_range'] , rw [list.range'_append] ,
            have t2 : list.range' (m + 2) m = list.range' (0 + (2 + m)) m := by simp , rw t2 ,
            rw [list.range'_append] , rw [nat.succ_eq_add_one] ,
            have t3 : (1 + 2 * m + 1) = (m + (2 + m)) := by ring,
            rw t3 ,
        end ,
    rw l at t ,
    rw [list.map_append, list.map_append, list.sum_append, list.sum_append] at t ,
    have t' : 2 * 2 ^ (2 * m) ≥
        list.sum (list.map (choose (1 + 2 * m)) (list.range' m 2)) := by linarith ,
    clear t,
    simp at t' ,

    have q : choose (1 + 2 * m) m = choose (1 + 2 * m) (m+1) :=
        begin
            rw choose_eq_fact_div_fact , rw choose_eq_fact_div_fact ,
            have t1 : (1 + 2 * m) = m + (m+1) := by ring,
            rw t1 , rw nat.add_sub_cancel ,
            rw [@nat.add_comm m (m+1)] , rw nat.add_sub_cancel ,
            rw [@nat.mul_comm (nat.fact m) (nat.fact (m + 1))] ,
            linarith , linarith ,
        end ,
    rw q at t' ,
    have q' : choose (1 + 2 * m) (m + 1) + choose (1 + 2 * m) (m + 1) = 2 * choose (1 + 2 * m) (m + 1) := by ring ,
    rw q' at t' ,
    simp , linarith ,
end

lemma primorial_2m_plus_1_le_power_2 : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) ≤ primorial (m+1) * 2^(2*m) :=
begin
    intros ,
    calc primorial (2*m + 1) ≤
         primorial (m+1) * (choose (2*m + 1) (m+1)) :
            primorial_2m_plus_1_le_choose m a
    ... ≤ primorial (m+1) * 2^(2*m) :
        begin
            apply nat.mul_le_mul_left ,
            apply choose_2m_plus_1_le_power_2 ,
        end
end

lemma case_by_parity : ∀ (n : ℕ) ,
    (∃ (m:ℕ) , n = 2*m) ∨ (∃ (m:ℕ) , n = 2*m + 1) :=
begin
    intros , induction n ,
    left , existsi 0, norm_num ,
    cases n_ih ,
    cases n_ih , right , existsi n_ih_w , rw n_ih_h ,
    cases n_ih , left , existsi (n_ih_w + 1) , rw n_ih_h ,
    rw nat.succ_eq_add_one , ring ,
end

lemma le_2_pow_2_mul_2_mul_plus_1 : ∀ (m : ℕ) ,
    2 ^ (2 * (2 * m + 1)) ≤ 2 ^ (2 * (2 * (m + 1))) :=
begin
    intros ,
    apply nat.pow_le_pow_of_le_right , linarith ,
    linarith ,
end

lemma primorial_bound_aux : ∀ (bound n : ℕ) ,
    n < bound →
    n ≥ 3 →
    8 * primorial n < 2 ^ (2*n)
| 0 n :=
    begin
        intros , linarith ,
    end
| (bound+1) 0 := begin intros , linarith end
| (bound+1) 1 := begin intros , linarith end
| (bound+1) 2 := begin intros , linarith end
| (bound+1) 3 :=
    begin
        intros ,
        have p1 : ¬ (nat.prime 1) := dec_trivial ,
        have p2 : (nat.prime 2) := dec_trivial ,
        have p3 : (nat.prime 3) := dec_trivial ,
        rw [primorial, range_to] , norm_num ,
        simp [list.filter, list.prod, p1, p2, p3],
        norm_num,
    end
| (bound+1) 4 :=
    begin
        intros ,
        have p1 : ¬ (nat.prime 1) := dec_trivial ,
        have p2 : (nat.prime 2) := dec_trivial ,
        have p3 : (nat.prime 3) := dec_trivial ,
        have p4 : ¬ (nat.prime 4) := dec_trivial ,
        rw [primorial, range_to] , norm_num ,
        simp [list.filter, list.prod, p1, p2, p3, p4],
        norm_num,
    end
| (bound+1) (n+5) := begin
    intros ,
    cases (case_by_parity (n+5)) ,
    {
        cases h , rename h_w m , rw h_h at * ,
        cases m , simp at a , linarith ,
        have q : (nat.succ m) = (m+1) := rfl , rw q at * , clear q ,
        have m_ge_1 : m ≥ 1 := by linarith ,
        rw primorial ,
        rw [<- @range_to_append 1 (2*m + 1) (2*(m+1))] ,
        {
        rw list.filter_append ,
        have r : range_to (2 * m + 1 + 1) (2 * (m + 1)) = [2 * (m+1)] :=
            begin
                rw [range_to] , rw nat.add_sub_cancel ,
                have q' : 2 * (m + 1) = 1 + (2 * m + 1) := by ring ,
                have q : 2 * (m + 1) - (2 * m + 1) = 1 := begin
                    rw q', rw nat.add_sub_cancel ,
                end,
                rw q ,
                rw [list.range', list.range'],
                have l : (2 * m + 1 + 1) = 2 * (m + 1) := by ring,
                rw l ,
            end,
        rw r ,
        have s : list.filter nat.prime [2 * (m + 1)] = [] :=
            begin
                rw [list.filter] , split_ifs ,
                have t : ¬ (nat.prime (2 * (m+1))) := begin
                    apply nat.not_prime_mul, linarith , linarith ,
                end,
                contradiction ,
                rw [list.filter] ,
            end ,
        rw s ,
        simp ,
        have ih_bound : (2*m + 1 < bound) := by linarith ,
        have ih := primorial_bound_aux
            bound (2*m + 1) ih_bound (by linarith) ,
        rw [<- primorial] ,
        rw nat.add_comm ,
        calc 8 * primorial (2 * m + 1) < 2 ^ (2 * (2 * m + 1)) : ih
        ... ≤ 2 ^ (2 * (2 * (m + 1))) : (le_2_pow_2_mul_2_mul_plus_1 m)
        },
        linarith , linarith ,
    },
    {
        cases h , rename h_w m , rw h_h at * ,
        have m_ge_2 : m ≥ 2 := by linarith ,
        have h1 := primorial_2m_plus_1_le_power_2 m m_ge_2 ,
        have ih_bound : (m+1 < bound) := by linarith ,
        have h2 := primorial_bound_aux bound (m+1) ih_bound (by linarith) ,
        calc 8 * primorial (2 * m + 1) ≤ 8 * primorial (m + 1) * 2 ^ (2*m) :        begin
                rw nat.mul_assoc ,
                apply nat.mul_le_mul_left , assumption ,
              end
        ... < 2 ^ (2 * (m+1)) * 2 ^ (2*m) :
            begin
                rw mul_lt_mul_right , assumption ,
                apply nat.pos_pow_of_pos , linarith ,
            end
        ... = 2 ^ (2 * (2*m+1)) : begin
            rw <- nat.pow_add , ring ,
        end
    },
end

lemma primorial_bound : ∀ (n : ℕ) ,
    n ≥ 3 →
    8 * primorial n < 2 ^ (2*n) :=
begin
    intros ,
    apply (primorial_bound_aux (n+1) n) ,
    linarith , assumption ,
end

/- prime factorization -/

theorem factorize : ∀ (n m : ℕ) ,
    (∀ p , nat.prime p → p ∣ n → p ≤ m) →
    (∃ (r : ℕ → ℕ) , (∀ (p:ℕ) , nat.prime p → p ^ r p ∣ n) ∧
        n = (((range_to 1 m).filter nat.prime).map
            (λ p , p ^ (r p))).prod
    ) := sorry

lemma p_le_of_p_dvd_choose : ∀ (p n k : ℕ) ,
    k ≤ n → nat.prime p → p ∣ (choose n k) → p ≤ n :=
begin
    intros ,
    have q := @choose_mul_fact_mul_fact n k a,
    have r : (p ∣ nat.fact n) := begin
        cases a_2 , rw [a_2_h] at q , rw nat.mul_assoc at q ,
        rw nat.mul_assoc at q , rw <- q , apply dvd_mul_right ,
    end ,
    rw [nat.prime.dvd_fact] at r , assumption ,
    assumption ,
end

/- main result (bertrand's postulate for n ≥ 432 case) -/

lemma two_thirds_n_bound : ∀ (n : ℕ) ,
    n ≥ 468 →
    2 * n / 3 < 2 →
    false :=
begin
    intros ,
    have t := (
        calc 2 < 2 * 468 / 3 : by norm_num
        ... ≤ 2 * n / 3 : begin
            apply nat.div_le_div_right , linarith ,
        end
        ... < 2 : a_1
    ),
    linarith ,
end

lemma le_of_sqr_le_sqr : ∀ (n m : ℕ) ,
    n*n ≤ m*m → n ≤ m :=
begin
    intros , by_contradiction , simp at a_1 ,
    have h : m*m < n*n := nat.mul_self_lt_mul_self a_1 ,
    linarith ,
end

lemma le_div_mul : ∀ (n m : ℕ) ,
    m > 0 →
    n ≤ (n / m + 1) * m := sorry

lemma sqrt_le_2_3 : ∀ (n : ℕ) ,
    468 ≤ n → nat.sqrt (2 * n) ≤ 2 * n / 3 :=
begin
    intros , apply le_of_sqr_le_sqr ,
    have h : (2*n / 3 ≥ 4) := begin
            calc 2*n / 3 ≥ 2*468 / 3 :
                begin
                    apply nat.div_le_div_right ,
                    apply nat.mul_le_mul_left , assumption ,
                end
            ... ≥ 4 : by norm_num
        end ,
    calc nat.sqrt (2 * n) * nat.sqrt (2 * n) ≤ (2*n) : nat.sqrt_le (2*n)
    ... ≤ (2*n / 3 + 1) * 3 : le_div_mul (2*n) 3 (by linarith)
    ... = (2*n / 3) * 3 + 3 : begin rw add_mul , simp , end
    ... ≤ (2*n / 3) * 3 + (2*n / 3) : by linarith
    ... = (2*n / 3) * 4 :
            begin
                have q : 4 = 3+1 := by norm_num , rw q ,
                rw mul_add, simp ,
            end
    ... ≤ (2 * n / 3) * (2 * n / 3) :
            begin
                apply nat.mul_le_mul_left , assumption ,
            end
end

lemma p_le_2n_over_3 : ∀ (n p : ℕ) ,
    (∀ (x : ℕ), nat.prime x → n < x → 2 * n < x) →
    n ≥ 468 →
    nat.prime p →
    p ∣ (choose (2*n) n) →
    p ≤ (2*n / 3) :=
begin
    intros ,
    by_cases (p ≤ 2*n / 3) ,
    {
        assumption ,
    },
    simp at h , rename h p_bound ,
    by_cases (p ≤ n) ,
    {
        have q := central_primes_do_not_divide p n a_2 (begin
            /- show p > 2 -/
            by_contradiction , simp at a_4 , rw a_4 at * ,
            apply two_thirds_n_bound , assumption, assumption,
        end) p_bound h ,
        contradiction ,
    },
    simp at h , clear p_bound , rename h p_bound ,
    {
        have q := a p a_2 p_bound ,
        have r := p_le_of_p_dvd_choose p (2*n) n (by linarith) a_2 a_3 ,
        linarith ,
    },
end

lemma log_le_1_of_gt_sqrt : ∀ (n p : ℕ) ,
    p > nat.sqrt n → log p n ≤ 1 := sorry

lemma p_at_most_1_in_choose_of_gt_sqrt : ∀ (p n r : ℕ) ,
    nat.prime p →
    p > nat.sqrt (2*n) →
    p ^ r ∣ choose (2*n) n →
    r ≤ 1 :=
begin
    intros ,
    have h : r ≤ log p (2*n) := exp_le_log_of_dvd_choose (2*n) n p r a_2 ,
    have j : log p (2*n) ≤ 1 := log_le_1_of_gt_sqrt (2*n) p a_1 ,
    exact (nat.le_trans h j) ,
end

lemma factorize_choose_2n_n : ∀ (n : ℕ) ,
    (∀ (x : ℕ), nat.prime x → n < x → 2 * n < x) →
    n ≥ 468 →
    ∃ (r : ℕ → ℕ) ,
    (∀ (p:ℕ) , nat.prime p → p ^ r p ∣ choose (2*n) n) ∧
    (choose (2*n) n =
        (((range_to 1 (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod) :=
begin
    intros ,
    have f := factorize (choose (2*n) n) (2*n/3) (begin
        intros , apply p_le_2n_over_3 , assumption, assumption, assumption,
        assumption ,
    end),
    cases f , rename f_w r , cases f_h , rename f_h_left dvd_condition ,
    rename f_h_right prod_condition ,
    existsi r ,
    split , assumption ,
    rw prod_condition ,
end

lemma prod_repeat_eq_pow : ∀ (a b : ℕ) ,
    list.prod (list.repeat a b) = a ^ b :=
begin
    intros ,
    induction b ,
    simp , simp ,
    rw nat.pow_succ , rw nat.mul_comm ,
end

lemma length_filter_le_length {α : Type} (p : α → Prop) [decidable_pred p] : ∀ (l : list α) ,
    list.length (l.filter p) ≤ list.length l :=
begin
    intros ,
    induction l , simp , simp , rw [list.filter] , split_ifs ,
    simp , assumption , linarith ,
end

lemma prime_bounds_1 : ∀ (n : ℕ) (r : ℕ → ℕ),
    (∀ p , nat.prime p → p ^ r p ∣ (choose (2*n) n)) →
    (((range_to 1 (nat.sqrt (2*n))).filter nat.prime).map
                (λ p , p ^ (r p))).prod
    ≤ (2*n) ^ (nat.sqrt (2*n)) :=
begin
    intros ,
    by_cases (n ≥ 1) ,
    {
        have h := prod_le_prod
            ((range_to 1 (nat.sqrt (2*n))).filter nat.prime)
            (λ p , p ^ (r p))
            (λ p , 2 * n)
            (begin
                intros, simp ,
                simp at a_1 , cases a_1 ,
                apply p_to_R_bound , assumption , apply a , assumption,
            end),
        exact (nat.le_trans h (begin
            rw list.map_const , rw prod_repeat_eq_pow ,
            apply nat.pow_le_pow_of_le_right,
            linarith ,
            have l : (nat.sqrt (2 * n)) = list.length (range_to 1 (nat.sqrt (2 * n))) := begin
                rw [range_to] , rw [list.length_range'] , ring ,
            end,
            have len_le := length_filter_le_length nat.prime (range_to 1 (nat.sqrt (2 * n))) ,
            rw <- l at len_le ,
            assumption ,
        end)),
    },
    {
        simp at h ,
        have t : (nat.sqrt 0 = 0) := rfl ,
        rw h , norm_num , rw [range_to] , rw t , simp ,
    }
end

lemma prime_bounds_2 : ∀ (n : ℕ) (r : ℕ → ℕ),
    n ≥ 468 →
    (∀ p , nat.prime p → p ^ r p ∣ (choose (2*n) n)) →
    (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod
    ≤ primorial (2*n / 3) :=
begin
    intros ,
    have h := prod_le_prod
        ((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime)
        (λ p , p ^ (r p))
        id
        (begin
            intros , simp at a_2, rw [range_to] at a_2 , simp at a_2 ,
            cases a_2 , cases a_2_left ,
            simp ,
            have t : x ^ r x ≤ x ^ 1 := begin
                apply nat.pow_le_pow_of_le_right , linarith ,
                apply p_at_most_1_in_choose_of_gt_sqrt x n (r x) ,
                assumption , linarith , apply a_1 , assumption ,
            end,
            simp at t,
            assumption ,
        end),
    exact (nat.le_trans h (begin
        rw list.map_id ,
        rw primorial ,
        have rsplit : range_to 1 (2 * n / 3) =
            range_to 1 (nat.sqrt (2 * n)) ++
            range_to (nat.sqrt (2 * n) + 1) (2 * n / 3) :=
                begin
                    rw range_to_append , linarith ,
                    apply sqrt_le_2_3 , assumption ,
                end,
        rw rsplit ,
        rw list.filter_append ,
        rw list.prod_append ,
        calc
            list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) =
                1 * list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    ring ,
                end
        ... ≤
                primorial (nat.sqrt (2 * n)) *
                list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    apply nat.mul_le_mul_right,
                    apply nat.le_of_lt_succ ,
                    have x : nat.succ (primorial (nat.sqrt (2 * n))) = primorial (nat.sqrt (2 * n)) + 1 := rfl ,
                    rw x ,
                    have t := zero_lt_primorial (nat.sqrt (2 * n)) ,
                    linarith ,
                end
        ... =
                list.prod (list.filter nat.prime (range_to 1 (nat.sqrt (2 * n)))) *
                list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    rw [primorial] ,
                end
    end))
end

lemma main_bound : ∀ (n : ℕ) ,
    n ≥ 468 →
    2*n * ((2*n) ^ (nat.sqrt (2*n))) * (4 ^ (2*n/3)) < 4^n := sorry

lemma b_lt_c_of_ab_lt_c : ∀ (a b c : ℕ) ,
    a > 0 →
    a*b < c → b < c :=
begin
    intros ,
    calc b = 1*b : by simp
    ... ≤ a*b :
        begin
            apply nat.mul_le_mul_right ,
            cases a , linarith , rw nat.succ_eq_add_one , linarith ,
        end
    ... < c : by assumption
end

/- copied from assignment 3, surprised I couldn't find this built-in? -/

theorem e1 {α : Type} : ∀ (p : α → Prop) , (¬ ∃ x, p x) → ∀ x, ¬ p x :=
  assume p : α → Prop ,
  assume h : (¬ ∃ x, p x) ,
  assume x : α ,
  not.intro (
    assume g : p x ,
    absurd (exists.intro x g) h
  )

theorem exists_of_not_forall {α : Type} : ∀ (p : α → Prop) , (¬ ∀ x, ¬ p x) → ∃ x, p x :=
  assume p : α → Prop ,
  assume g : (¬ ∀ x, ¬ p x) ,
  or.elim (em (∃ x, p x))
  (
    assume h : (∃ x, p x) ,
    h
  )
  (
    assume h : ¬ (∃ x, p x) ,
    absurd (e1 p h) g
  )

lemma bertrands_postulate_main : ∀ (n : ℕ) ,
    n ≥ 468 →
    ∃ p , nat.prime p ∧ n < p ∧ p ≤ 2*n :=
begin
    intros ,
    apply exists_of_not_forall ,
    by_contradiction ,
    simp at a_1 ,

    have h := factorize_choose_2n_n n a_1 a ,
    cases h , rename h_w r , cases h_h ,
    rename h_h_left r_divides ,
    rename h_h_right choose_2n_n_factorization ,

    have t := (
    calc 4^n ≤ 2*n * (choose (2*n) n) : four_n_bound n
    ... = 2*n * (((range_to 1 (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod : begin
                     rw <- choose_2n_n_factorization,
                end
    ... = 2*n *
            (((range_to 1 (nat.sqrt (2*n))).filter nat.prime).map
                (λ p , p ^ (r p))).prod *
            (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod :
            begin
                rw [@nat.mul_assoc (2*n) _ _] ,
                rw <- list.prod_append ,
                rw <- list.map_append ,
                rw <- list.filter_append ,
                rw <- range_to_append ,
                linarith ,
                apply sqrt_le_2_3 , assumption ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod :
            begin
                apply nat.mul_le_mul_right ,
                apply nat.mul_le_mul_left ,
                exact (prime_bounds_1 n r r_divides) ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            primorial (2*n / 3) :
            begin
                apply nat.mul_le_mul_left ,
                exact (prime_bounds_2 n r a r_divides) ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            4 ^ (2*n/3) :
            begin
                have q := (calc
                        (3:ℕ) ≤ 312 : (by linarith)
                        ... = 2 * 468 / 3 : by norm_num
                        ... ≤ 2 * n / 3 : begin
                            apply nat.div_le_div_right ,
                            apply nat.mul_le_mul_left ,
                            assumption ,
                        end
                ),
                have h := primorial_bound (2*n / 3) q,
                have h' := b_lt_c_of_ab_lt_c _ _ _ (by linarith) h,
                rw nat.pow_mul at h' ,
                have h'' : (2 ^ 2 = 4) := by refl ,
                rw h'' at h' ,
                apply nat.mul_le_mul_left ,
                apply le_of_lt , assumption ,
            end
    ... < 4^n : main_bound n a
    ),
    linarith ,

end

/- full bertrand's postulate -/

lemma prime_2 : nat.prime 2 := dec_trivial
lemma prime_3 : nat.prime 3 := dec_trivial
lemma prime_5 : nat.prime 5 := dec_trivial
lemma prime_7 : nat.prime 7 := dec_trivial
lemma prime_13 : nat.prime 13 := dec_trivial
lemma prime_23 : nat.prime 23 := dec_trivial
lemma prime_43 : nat.prime 43 := dec_trivial
lemma prime_83 : nat.prime 83 := dec_trivial
lemma prime_163 : nat.prime 163 := sorry
lemma prime_317 : nat.prime 317 := sorry
lemma prime_631 : nat.prime 631 := sorry

theorem bertrands_postulate : ∀ (n : ℕ) ,
    n ≥ 1 →
    ∃ p , nat.prime p ∧ n < p ∧ p ≤ 2*n :=
begin
    intros ,
    by_cases (n < 2) , existsi 2 ,
        exact (and.intro prime_2 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _2_le_n ,

    by_cases (n < 3) , existsi 3 ,
        exact (and.intro prime_3 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _3_le_n ,

    by_cases (n < 5) , existsi 5 ,
        exact (and.intro prime_5 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _5_le_n ,

    by_cases (n < 7) , existsi 7 ,
        exact (and.intro prime_7 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _7_le_n ,

    by_cases (n < 13) , existsi 13 ,
        exact (and.intro prime_13 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _13_le_n ,

    by_cases (n < 23) , existsi 23 ,
        exact (and.intro prime_23 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _23_le_n ,

    by_cases (n < 43) , existsi 43 ,
        have p_le_2n : 43 ≤ 2*n :=
            (calc 43 ≤ 2*23 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_43 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _43_le_n ,

    by_cases (n < 83) , existsi 83 ,
        have p_le_2n : 83 ≤ 2*n :=
            (calc 83 ≤ 2*43 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_83 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _83_le_n ,

    by_cases (n < 163) , existsi 163 ,
        have p_le_2n : 163 ≤ 2*n :=
            (calc 163 ≤ 2*83 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_163 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _163_le_n ,

    by_cases (n < 317) , existsi 317 ,
        have p_le_2n : 317 ≤ 2*n :=
            (calc 317 ≤ 2*163 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_317 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _317_le_n ,

    by_cases (n < 631) , existsi 631 ,
        have p_le_2n : 631 ≤ 2*n :=
            (calc 631 ≤ 2*317 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_631 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _631_le_n ,

    have t := (calc 468 ≤ 631 : by norm_num ... ≤ n : by assumption) ,
    exact (bertrands_postulate_main n t) ,
end


import data.nat.basic
import tactic
import data.int.basic
import tactic.nth_rewrite.default

def fib : ℕ → ℕ
| 0     := 0
| 1     := 1
| (n+2) := fib (n+1) + fib n

def luc: ℕ → ℕ
| 0 := 2
| 1 := 1
| (n+2) := luc(n+1) +luc n

@[simp] lemma fib_add ( n :  ℕ) : fib (n+2) = fib (n+1) + fib (n):= by refl
@[simp] lemma luc_add (n: ℕ ): luc (n+2) = luc (n+1) + luc n:= by refl
@[simp] lemma fib_zero : fib 0 =0:= by refl
@[simp] lemma fib_one : fib 1 = 1:= by refl
@[simp] lemma fib_two : fib 2= 1:= by refl
@[simp] lemma luc_zero : luc 0 =2:= by refl
@[simp] lemma luc_one : luc 1 = 1:= by refl

@[simp] lemma fib_luc_rec (n: ℕ ): luc(n+1) = fib(n+2) + fib (n):=
begin
    revert n,
    apply nat.two_step_induction,
    {simp},
    {simp},
    {intros, simp * at *, ring}
    end

lemma nat_case_bash (h n : ℕ ):  h ≤ n ∨ ∃ (k: ℕ ), h=k+n+1:=
begin
    have t1:= le_or_lt h n, cases t1 with t1 t2,
    {left, assumption},
    {right, use h-(n+1), omega }
end

lemma fib_luc1 (m n : ℕ ) : 2 * fib (m+n) = fib (m) * luc (n) + luc(m) * fib(n):=
begin
    revert n, apply nat.two_step_induction,
    {simp [mul_comm]},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw fib_add, rw mul_add, rw IH1, rw IH2, rw [show n+1+1 =n+2, from rfl], rw fib_add, rw luc_add, ring,}
end

lemma fib_luc2 (m n : ℕ ): 2* luc (m+n) = 5* fib (m) * fib (n) +luc(m) * luc(n) :=
begin
    revert n, apply nat.two_step_induction,
    {simp [mul_comm]},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw luc_add, rw mul_add, rw IH1, rw IH2, rw [show n+1+1 =n+2, from rfl], rw fib_add, rw luc_add, ring,}
end

lemma fib_luc_rec_2 (m n: ℕ): luc(m+n+1) = fib(m+1)*luc(n+1) + fib(m)*luc(n):=
begin
    revert n, apply nat.two_step_induction,
    {simp,ring},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw luc_add, rw IH1, rw IH2, ring,}
end

lemma fib_luc3_pre (d: ℕ ):  (↑(luc (d + 2)) * ↑(fib (d + 1)) + 2 * (-1) ^ (d + 1): ℤ) = (↑(luc (d + 1)) * ↑(fib (d + 2)): ℤ):=
begin
    revert d, apply nat.two_step_induction,
    {ring},
    {ring},
    {intros p q r, repeat {rw nat.succ_eq_add_one at *}, clear r,
    rw [luc_add, fib_add], push_cast, rw [add_mul, mul_add],
    have t1: (-1: ℤ )^(p+1+1+1) = (-1: ℤ )^ (p+1), rw pow_add (-1: ℤ ) (p+1) (1+1),rw [show (-1: ℤ )^(1+1)=1, from rfl], rw mul_one,
    rw t1, rw mul_add, rw [show p+1+1=p+2, from rfl], repeat{rw add_assoc},
    rw q,
    nth_rewrite_rhs 0 fib_add, push_cast, rw mul_add,
    nth_rewrite_rhs 0 fib_add, push_cast, rw mul_add,
    nth_rewrite_rhs 0 luc_add, push_cast, rw add_mul,
    ring}
end

lemma fib_luc3 (m: ℕ) : ((luc(2*m) + 2 * (-1: ℤ )^(m)) : ℤ )= ((luc(m))^2 : ℤ ):=
begin
    cases m with d hd,
    {simp, ring},
    {rw nat.succ_eq_add_one, rw [show 2*(d+1)= d+(d+1)+1, by ring], rw fib_luc_rec_2,
    rw fib_luc_rec d, rw pow_two,
    suffices goal:  (↑(luc (d + 2)) * ↑(fib (d + 1)) + 2 * (-1) ^ (d + 1): ℤ) = (↑(luc (d + 1)) * ↑(fib (d + 2)): ℤ),
        {push_cast,
        rw [add_right_comm, mul_comm], rw (show d+1+1=d+2, from rfl), rw goal,
        rw fib_luc_rec, push_cast, ring},
    {apply fib_luc3_pre}}
end

lemma fib_luc1_sub (m n: ℕ ) : (2 * fib (m) :ℤ ) =  (-1: ℤ )^(n)*fib (m+n)  *luc (n) + (-1: ℤ )^(n+1)*luc(m+n) * fib(n):=
begin
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring,
    revert m, apply nat.two_step_induction,
    {rw t1,simp, ring},
    {rw t1, cases nat_case_bash n 0,  interval_cases n,
        {cases h with k h,  rw [show k+0+1 = k+1, from rfl] at h, rw h,
        rw [show 1+(k+1) = k+2, by ring], rw [show ((-1) ^ (k + 1) * ↑(fib (k + 2)) * ↑(luc (k + 1)): ℤ )=  ↑(luc (k + 1))  * ↑(fib (k + 2))*(-1) ^ (k + 1) , by ring], rw ← fib_luc3_pre,
        rw add_mul, rw fib_one, nth_rewrite_rhs 0 mul_comm, rw add_right_comm, nth_rewrite_rhs 1 mul_assoc, ring, rw int.sub_self,
        rw mul_assoc, rw ← pow_add, rw[show k+1 +(k+1) = 2*(k+1), by ring], rw pow_mul, rw [show (-1 : ℤ )^2 =1, from rfl], simp},
        {swap, simp}},
    {intros k IH1 IH2, repeat {rw nat.succ_eq_add_one at *}, rw [show  (k+1+1) = k+2, by refl],
    rw fib_add, push_cast, rw mul_add, rw IH1, rw IH2,  rw [show k+2+n = k+n+2, by ring], rw fib_add, rw luc_add, push_cast, rw mul_add,  rw mul_add, rw add_mul, rw [show k+1+n =k+n+1, by ring], ring}
end

lemma fib_luc_4 (n : ℕ ) : (-1: ℤ )^(n+1)*5*(fib n)^2 + (-1: ℤ )^n * (luc n)^2 =4:=
begin
    suffices goal: (-1) ^ (n + 1) * 5 * ↑(fib n) ^ 2 + (-1) ^ n * ↑(luc n) ^ 2 + (-1:ℤ )^n * ↑(luc n)^2 = 4+ (-1:ℤ )^n * ↑(luc n)^2, simp * at *,
    repeat{nth_rewrite_lhs 0 ← fib_luc3}, rw add_assoc, rw ← mul_add ((-1: ℤ )^n) _ _,
    rw [show ↑(luc (2 * n)) + 2 * (-1) ^ n + (↑(luc (2 * n)) + 2 * (-1: ℤ ) ^ n) = ↑ (2*luc (2 * n)) + 4 * (-1) ^ n, by ring], rw [show 2*n = n+n, by ring], rw fib_luc2,
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring, rw t1,clear t1,
    rw mul_add,
    have t3: (-1: ℤ )^n *(4 *(-1)^n) = 4, rw ← mul_assoc,rw mul_comm, rw ← mul_assoc, rw ← pow_add, rw [show n+n=2*n, by ring], rw pow_mul, simp,
    rw add_comm,repeat{rw add_assoc},  rw t3, push_cast, rw mul_add, ring,
end

lemma fib_luc2_sub (m n : ℕ ) :  (2 * luc (m) :ℤ ) =  (-1: ℤ )^(n+1)*5*fib (m+n)  *fib (n) + (-1: ℤ )^(n)*luc(m+n) * luc(n):=
begin
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring, rw t1,
    have main: (2*(2 * ↑(luc m)):ℤ ) = 2*(-(-1) ^ n * 5 * ↑(fib (m + n)) * ↑(fib n) + (-1) ^ n * ↑(luc (m + n)) * ↑(luc n)),
    { rw mul_add, rw eq_comm,
    have t2: (2 * (-(-1) ^ n * 5 * ↑(fib (m + n)) * ↑(fib n)) :ℤ ) = ↑ (2 *(fib (m + n)))* (-(-1) ^ n * 5 *  ↑(fib n)), push_cast, ring,
    rw t2, rw fib_luc1,
    have t3: (2 * ((-1) ^ n * ↑(luc (m + n)) * ↑(luc n)) : ℤ ) = ↑(2  * (luc (m + n))) *(-1) ^ n* ↑(luc n), push_cast, ring,
    rw t3, rw fib_luc2,
    suffices goal: (-1: ℤ ) ^ n * ↑(luc m) * ↑(luc n) ^ 2 - 5 * (-1: ℤ ) ^ n * ↑(fib n) ^ 2 * ↑(luc m) = 4 * ↑(luc m),
    {push_cast, simp, ring, assumption},
    {have:= fib_luc_4 n, rw t1 at this, rw ← this, ring}
    },
    {rw ←  int.mul_div_cancel_left (2 * ↑(luc m)), rw main, rw int.mul_div_cancel_left, linarith, linarith}
end


import data.zmod.algebra
import tactic

class ec_field (F : Type*) extends field F :=
(deceqF : decidable_eq F)
(two_ne_zero : (2 : F) ≠ 0)

instance ec_field.decidable_eq (F : Type*) [h : ec_field F] : decidable_eq F :=
h.deceqF

def on_ec {F : Type*} [field F] (p : F × F) := p.2^2 = p.1^3 + 7

lemma eq_or_eq_neg_of_eq_of_on_ec {F : Type*} [field F] {x1 y1 x2 y2 : F}
    (hp : on_ec (x1, y1)) (hq : on_ec (x2, y2)) (heq : x1 = x2 ) :
  y1 = y2 ∨ y1 = -y2 :=
begin
  have : y1^2 = y2^2,
  { apply hp.trans, symmetry, apply hq.trans, dsimp, rw heq },
  apply eq_or_eq_neg_of_sq_eq_sq _ _ this
end

lemma eq_of_on_ec {F : Type*} [field F] {x1 y1 x2 y2 : F}
    (hp : on_ec (x1, y1)) (hq : on_ec (x2, y2)) (heq : x1 = x2 ) (hne : y1 ≠ -y2) :
  y1 = y2 :=
or.resolve_right (eq_or_eq_neg_of_eq_of_on_ec hp hq heq) hne

@[ext] structure AffineECPoint (F : Type*) [field F] :=
(x y : F) (h : on_ec (x, y))

inductive ECPoint  (F : Type*) [field F]
| ZeroPoint : ECPoint
| AffinePoint : AffineECPoint F → ECPoint

variables {F : Type*} [ec_field F]

def ec_double_slope (p : F × F) := 3 * p.1^2 / (2 * p.2)

def ec_double (p : F × F) :=
  let slope := ec_double_slope p,
      new_x := slope^2 - 2 * p.1 in
  (new_x, (p.1 - new_x) * slope - p.2)

theorem on_ec_ec_double {p : F × F} (h : on_ec p) (h' : p.2 ≠ 0) :
  on_ec (ec_double p) :=
begin
  cases p with x y,
  have pow_three : ∀ z : F, z^3 = z * z * z, { intro z, simp [pow_succ, mul_assoc] },
  simp [on_ec, ec_double, ec_double_slope] at *,
  apply eq_of_sub_eq_zero,
  field_simp [ec_field.two_ne_zero, h, h', pow_two, pow_three], ring_nf,
  simp [h], ring_nf,
end

def ec_add_slope (p1 p2 : F × F) := (p2.2 - p1.2) / (p2.1 - p1.1)

def ec_add (p1 p2 : F × F) : F × F :=
  let slope := ec_add_slope p1 p2,
      new_x := slope^2 - (p1.1 + p2.1) in
  (new_x, (p1.1 - new_x) * slope - p1.2)

theorem on_ec_ec_add {p1 p2 : F × F} (h1 : on_ec p1) (h2 : on_ec p2) (h' : p1.1 ≠ p2.1) :
  on_ec (ec_add p1 p2) :=
begin
  cases p1 with x1 y1,
  cases p2 with x2 y2,
  have pow_three : ∀ z : F, z^3 = z * z^2, { intro z, simp [pow_succ, mul_assoc], },
  have pow_four : ∀ z : F, z^4 = z^2 * z^2, { intro z, simp [pow_succ, mul_assoc], },
  simp [on_ec, ec_add, ec_add_slope] at *,
  have : x2 - x1 ≠ 0, { contrapose! h', symmetry, apply eq_of_sub_eq_zero h' },
  apply eq_of_sub_eq_zero,
  field_simp [ec_field.two_ne_zero, pow_two, pow_three, this], ring_nf,
  simp [h1, h2, pow_three, pow_four], ring_nf,
  simp [h1, h2], ring_nf
end

namespace ECPoint

protected def add : ECPoint F → ECPoint F → ECPoint F
| ZeroPoint       b               := b
| a               ZeroPoint       := a
| (AffinePoint a) (AffinePoint b) :=
    if axbx: a.x = b.x then
      if ayby: a.y = -b.y then
        -- a = -b
        ZeroPoint
      else
        have a.y = b.y, from eq_of_on_ec a.h b.h axbx ayby,
        have a.y ≠ 0, by { contrapose! ayby, rw [←this, ayby, neg_zero] },
        let p := ec_double (a.x, a.y) in
        AffinePoint ⟨p.1, p.2, on_ec_ec_double a.h this⟩
    else
      let p := ec_add (a.x, a.y) (b.x, b.y) in
      AffinePoint ⟨p.1, p.2, on_ec_ec_add a.h b.h axbx⟩

protected def neg : ECPoint F → ECPoint F
| ZeroPoint       := ZeroPoint
| (AffinePoint a) := AffinePoint ⟨a.x, -a.y, by { simp [on_ec], exact a.h} ⟩

protected theorem add_left_neg (a : ECPoint F) : a.neg.add a = ZeroPoint :=
by { cases a; simp [ECPoint.neg, ECPoint.add] }

protected theorem add_comm (a b: ECPoint F) : a.add b = b.add a :=
begin
  cases a; cases b; simp [ECPoint.add],
  by_cases h : a.x = b.x,
  { rw [dif_pos h, dif_pos h.symm], simp [h],
    by_cases h': a.y = -b.y,
    { rw [dif_pos h', dif_pos (eq_neg_of_eq_neg h')] },
    rw [dif_neg h', dif_neg (λ h'', h' (eq_neg_of_eq_neg h''))],
    simp [eq_of_on_ec a.h b.h h h'] },
  rw [dif_neg h, dif_neg (ne.symm h)],
  have : ec_add_slope (a.x, a.y) (b.x, b.y) = ec_add_slope (b.x, b.y) (a.x, a.y),
  { simp [ec_add_slope], rw [←neg_sub b.y a.y, ←neg_sub b.x a.x, neg_div_neg_eq] },
  congr' 1, ext; dsimp [ec_add]; simp [add_comm],
  { simp [this] },
  have h1 : a.x - b.x ≠ 0, { rwa [ne, sub_eq_zero] },
  have h2 : b.x - a.x ≠ 0, { rw [ne, sub_eq_zero], exact ne.symm h },
  field_simp [ec_add_slope, h1, h2, pow_two],
  ring_nf
end

end ECPoint

instance : add_comm_group (ECPoint F) :=
{ add          := ECPoint.add,
  neg          := ECPoint.neg,
  zero         := ECPoint.ZeroPoint,
  add_assoc    := sorry,
  zero_add     := by { intro a, cases a; simp [ECPoint.add] },
  add_zero     := by { intro a, cases a; simp [ECPoint.add] },
  add_left_neg := ECPoint.add_left_neg,
  add_comm     := ECPoint.add_comm }

theorem ECPoint.add_def (x y : ECPoint F) : x + y = ECPoint.add x y := rfl

theorem ECPoint.neg_def (x : ECPoint F) : - x = ECPoint.neg x := rfl

theorem ECPoint.zero_def : (0 : ECPoint F) = ECPoint.ZeroPoint := rfl


/-
Copyright (c) 2019 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou
-/
import measure_theory.function.lp_order

/-!
# Integrable functions and `L¹` space

In the first part of this file, the predicate `integrable` is defined and basic properties of
integrable functions are proved.

Such a predicate is already available under the name `mem_ℒp 1`. We give a direct definition which
is easier to use, and show that it is equivalent to `mem_ℒp 1`

In the second part, we establish an API between `integrable` and the space `L¹` of equivalence
classes of integrable functions, already defined as a special case of `L^p` spaces for `p = 1`.

## Notation

* `α →₁[μ] β` is the type of `L¹` space, where `α` is a `measure_space` and `β` is a `normed_group`
  with a `second_countable_topology`. `f : α →ₘ β` is a "function" in `L¹`. In comments, `[f]` is
  also used to denote an `L¹` function.

  `₁` can be typed as `\1`.

## Main definitions

* Let `f : α → β` be a function, where `α` is a `measure_space` and `β` a `normed_group`.
  Then `has_finite_integral f` means `(∫⁻ a, ∥f a∥₊) < ∞`.

* If `β` is moreover a `measurable_space` then `f` is called `integrable` if
  `f` is `measurable` and `has_finite_integral f` holds.

## Implementation notes

To prove something for an arbitrary integrable function, a useful theorem is
`integrable.induction` in the file `set_integral`.

## Tags

integrable, function space, l1

-/

noncomputable theory

open_locale classical topological_space big_operators ennreal measure_theory nnreal

open set filter topological_space ennreal emetric measure_theory

variables {α β γ δ : Type*} {m : measurable_space α} {μ ν : measure α} [measurable_space δ]
variables [normed_group β]
variables [normed_group γ]

namespace measure_theory

/-! ### Some results about the Lebesgue integral involving a normed group -/

lemma lintegral_nnnorm_eq_lintegral_edist (f : α → β) :
  ∫⁻ a, ∥f a∥₊ ∂μ = ∫⁻ a, edist (f a) 0 ∂μ :=
by simp only [edist_eq_coe_nnnorm]

lemma lintegral_norm_eq_lintegral_edist (f : α → β) :
  ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ = ∫⁻ a, edist (f a) 0 ∂μ :=
by simp only [of_real_norm_eq_coe_nnnorm, edist_eq_coe_nnnorm]

lemma lintegral_edist_triangle {f g h : α → β}
  (hf : ae_strongly_measurable f μ) (hh : ae_strongly_measurable h μ) :
  ∫⁻ a, edist (f a) (g a) ∂μ ≤ ∫⁻ a, edist (f a) (h a) ∂μ + ∫⁻ a, edist (g a) (h a) ∂μ :=
begin
  rw ← lintegral_add_left' (hf.edist hh),
  refine lintegral_mono (λ a, _),
  apply edist_triangle_right
end

lemma lintegral_nnnorm_zero : ∫⁻ a : α, ∥(0 : β)∥₊ ∂μ = 0 := by simp

lemma lintegral_nnnorm_add_left
  {f : α → β} (hf : ae_strongly_measurable f μ) (g : α → γ) :
  ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ + ∫⁻ a, ∥g a∥₊ ∂μ :=
lintegral_add_left' hf.ennnorm _

lemma lintegral_nnnorm_add_right
  (f : α → β) {g : α → γ} (hg : ae_strongly_measurable g μ) :
  ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ + ∫⁻ a, ∥g a∥₊ ∂μ :=
lintegral_add_right' _ hg.ennnorm

lemma lintegral_nnnorm_neg {f : α → β} :
  ∫⁻ a, ∥(-f) a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ :=
by simp only [pi.neg_apply, nnnorm_neg]

/-! ### The predicate `has_finite_integral` -/

/-- `has_finite_integral f μ` means that the integral `∫⁻ a, ∥f a∥ ∂μ` is finite.
  `has_finite_integral f` means `has_finite_integral f volume`. -/
def has_finite_integral {m : measurable_space α} (f : α → β) (μ : measure α . volume_tac) : Prop :=
∫⁻ a, ∥f a∥₊ ∂μ < ∞

lemma has_finite_integral_iff_norm (f : α → β) :
  has_finite_integral f μ ↔ ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ < ∞ :=
by simp only [has_finite_integral, of_real_norm_eq_coe_nnnorm]

lemma has_finite_integral_iff_edist (f : α → β) :
  has_finite_integral f μ ↔ ∫⁻ a, edist (f a) 0 ∂μ < ∞ :=
by simp only [has_finite_integral_iff_norm, edist_dist, dist_zero_right]

lemma has_finite_integral_iff_of_real {f : α → ℝ} (h : 0 ≤ᵐ[μ] f) :
  has_finite_integral f μ ↔ ∫⁻ a, ennreal.of_real (f a) ∂μ < ∞ :=
have lintegral_eq : ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ = ∫⁻ a, ennreal.of_real (f a) ∂μ :=
begin
  refine lintegral_congr_ae (h.mono $ λ a h, _),
  rwa [real.norm_eq_abs, abs_of_nonneg]
end,
by rw [has_finite_integral_iff_norm, lintegral_eq]

lemma has_finite_integral_iff_of_nnreal {f : α → ℝ≥0} :
  has_finite_integral (λ x, (f x : ℝ)) μ ↔ ∫⁻ a, f a ∂μ < ∞ :=
by simp [has_finite_integral_iff_norm]

lemma has_finite_integral.mono {f : α → β} {g : α → γ} (hg : has_finite_integral g μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ ∥g a∥) : has_finite_integral f μ :=
begin
  simp only [has_finite_integral_iff_norm] at *,
  calc ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ ≤ ∫⁻ (a : α), (ennreal.of_real ∥g a∥) ∂μ :
    lintegral_mono_ae (h.mono $ assume a h, of_real_le_of_real h)
    ... < ∞ : hg
end

lemma has_finite_integral.mono' {f : α → β} {g : α → ℝ} (hg : has_finite_integral g μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ g a) : has_finite_integral f μ :=
hg.mono $ h.mono $ λ x hx, le_trans hx (le_abs_self _)

lemma has_finite_integral.congr' {f : α → β} {g : α → γ} (hf : has_finite_integral f μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  has_finite_integral g μ :=
hf.mono $ eventually_eq.le $ eventually_eq.symm h

lemma has_finite_integral_congr' {f : α → β} {g : α → γ} (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  has_finite_integral f μ ↔ has_finite_integral g μ :=
⟨λ hf, hf.congr' h, λ hg, hg.congr' $ eventually_eq.symm h⟩

lemma has_finite_integral.congr {f g : α → β} (hf : has_finite_integral f μ) (h : f =ᵐ[μ] g) :
  has_finite_integral g μ :=
hf.congr' $ h.fun_comp norm

lemma has_finite_integral_congr {f g : α → β} (h : f =ᵐ[μ] g) :
  has_finite_integral f μ ↔ has_finite_integral g μ :=
has_finite_integral_congr' $ h.fun_comp norm

lemma has_finite_integral_const_iff {c : β} :
  has_finite_integral (λ x : α, c) μ ↔ c = 0 ∨ μ univ < ∞ :=
by simp [has_finite_integral, lintegral_const, lt_top_iff_ne_top, or_iff_not_imp_left]

lemma has_finite_integral_const [is_finite_measure μ] (c : β) :
  has_finite_integral (λ x : α, c) μ :=
has_finite_integral_const_iff.2 (or.inr $ measure_lt_top _ _)

lemma has_finite_integral_of_bounded [is_finite_measure μ] {f : α → β} {C : ℝ}
  (hC : ∀ᵐ a ∂μ, ∥f a∥ ≤ C) : has_finite_integral f μ :=
(has_finite_integral_const C).mono' hC

lemma has_finite_integral.mono_measure {f : α → β} (h : has_finite_integral f ν) (hμ : μ ≤ ν) :
  has_finite_integral f μ :=
lt_of_le_of_lt (lintegral_mono' hμ le_rfl) h

lemma has_finite_integral.add_measure {f : α → β} (hμ : has_finite_integral f μ)
  (hν : has_finite_integral f ν) : has_finite_integral f (μ + ν) :=
begin
  simp only [has_finite_integral, lintegral_add_measure] at *,
  exact add_lt_top.2 ⟨hμ, hν⟩
end

lemma has_finite_integral.left_of_add_measure {f : α → β} (h : has_finite_integral f (μ + ν)) :
  has_finite_integral f μ :=
h.mono_measure $ measure.le_add_right $ le_rfl

lemma has_finite_integral.right_of_add_measure {f : α → β} (h : has_finite_integral f (μ + ν)) :
  has_finite_integral f ν :=
h.mono_measure $ measure.le_add_left $ le_rfl

@[simp] lemma has_finite_integral_add_measure {f : α → β} :
  has_finite_integral f (μ + ν) ↔ has_finite_integral f μ ∧ has_finite_integral f ν :=
⟨λ h, ⟨h.left_of_add_measure, h.right_of_add_measure⟩, λ h, h.1.add_measure h.2⟩

lemma has_finite_integral.smul_measure {f : α → β} (h : has_finite_integral f μ) {c : ℝ≥0∞}
  (hc : c ≠ ∞) : has_finite_integral f (c • μ) :=
begin
  simp only [has_finite_integral, lintegral_smul_measure] at *,
  exact mul_lt_top hc h.ne
end

@[simp] lemma has_finite_integral_zero_measure {m : measurable_space α} (f : α → β) :
  has_finite_integral f (0 : measure α) :=
by simp only [has_finite_integral, lintegral_zero_measure, with_top.zero_lt_top]

variables (α β μ)
@[simp] lemma has_finite_integral_zero : has_finite_integral (λa:α, (0:β)) μ :=
by simp [has_finite_integral]
variables {α β μ}

lemma has_finite_integral.neg {f : α → β} (hfi : has_finite_integral f μ) :
  has_finite_integral (-f) μ :=
by simpa [has_finite_integral] using hfi

@[simp] lemma has_finite_integral_neg_iff {f : α → β} :
  has_finite_integral (-f) μ ↔ has_finite_integral f μ :=
⟨λ h, neg_neg f ▸ h.neg, has_finite_integral.neg⟩

lemma has_finite_integral.norm {f : α → β} (hfi : has_finite_integral f μ) :
  has_finite_integral (λa, ∥f a∥) μ :=
have eq : (λa, (nnnorm ∥f a∥ : ℝ≥0∞)) = λa, (∥f a∥₊ : ℝ≥0∞),
  by { funext, rw nnnorm_norm },
by { rwa [has_finite_integral, eq] }

lemma has_finite_integral_norm_iff (f : α → β) :
  has_finite_integral (λa, ∥f a∥) μ ↔ has_finite_integral f μ :=
has_finite_integral_congr' $ eventually_of_forall $ λ x, norm_norm (f x)

lemma has_finite_integral_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hf : ∫⁻ x, f x ∂μ ≠ ∞) :
  has_finite_integral (λ x, (f x).to_real) μ :=
begin
  have : ∀ x, (∥(f x).to_real∥₊ : ℝ≥0∞) =
    @coe ℝ≥0 ℝ≥0∞ _ (⟨(f x).to_real, ennreal.to_real_nonneg⟩ : ℝ≥0),
  { intro x, rw real.nnnorm_of_nonneg },
  simp_rw [has_finite_integral, this],
  refine lt_of_le_of_lt (lintegral_mono (λ x, _)) (lt_top_iff_ne_top.2 hf),
  by_cases hfx : f x = ∞,
  { simp [hfx] },
  { lift f x to ℝ≥0 using hfx with fx,
    simp [← h] }
end

lemma is_finite_measure_with_density_of_real {f : α → ℝ} (hfi : has_finite_integral f μ) :
  is_finite_measure (μ.with_density (λ x, ennreal.of_real $ f x)) :=
begin
  refine is_finite_measure_with_density ((lintegral_mono $ λ x, _).trans_lt hfi).ne,
  exact real.of_real_le_ennnorm (f x)
end

section dominated_convergence

variables {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}

lemma all_ae_of_real_F_le_bound (h : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a) :
  ∀ n, ∀ᵐ a ∂μ, ennreal.of_real ∥F n a∥ ≤ ennreal.of_real (bound a) :=
λn, (h n).mono $ λ a h, ennreal.of_real_le_of_real h

lemma all_ae_tendsto_of_real_norm (h : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top $ 𝓝 $ f a) :
  ∀ᵐ a ∂μ, tendsto (λn, ennreal.of_real ∥F n a∥) at_top $ 𝓝 $ ennreal.of_real ∥f a∥ :=
h.mono $
  λ a h, tendsto_of_real $ tendsto.comp (continuous.tendsto continuous_norm _) h

lemma all_ae_of_real_f_le_bound (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  ∀ᵐ a ∂μ, ennreal.of_real ∥f a∥ ≤ ennreal.of_real (bound a) :=
begin
  have F_le_bound := all_ae_of_real_F_le_bound h_bound,
  rw ← ae_all_iff at F_le_bound,
  apply F_le_bound.mp ((all_ae_tendsto_of_real_norm h_lim).mono _),
  assume a tendsto_norm F_le_bound,
  exact le_of_tendsto' tendsto_norm (F_le_bound)
end

lemma has_finite_integral_of_dominated_convergence {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}
  (bound_has_finite_integral : has_finite_integral bound μ)
  (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  has_finite_integral f μ :=
/- `∥F n a∥ ≤ bound a` and `∥F n a∥ --> ∥f a∥` implies `∥f a∥ ≤ bound a`,
  and so `∫ ∥f∥ ≤ ∫ bound < ∞` since `bound` is has_finite_integral -/
begin
  rw has_finite_integral_iff_norm,
  calc ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ ≤ ∫⁻ a, ennreal.of_real (bound a) ∂μ :
    lintegral_mono_ae $ all_ae_of_real_f_le_bound h_bound h_lim
    ... < ∞ :
    begin
      rw ← has_finite_integral_iff_of_real,
      { exact bound_has_finite_integral },
      exact (h_bound 0).mono (λ a h, le_trans (norm_nonneg _) h)
    end
end

lemma tendsto_lintegral_norm_of_dominated_convergence
  {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}
  (F_measurable : ∀ n, ae_strongly_measurable (F n) μ)
  (bound_has_finite_integral : has_finite_integral bound μ)
  (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  tendsto (λn, ∫⁻ a, (ennreal.of_real ∥F n a - f a∥) ∂μ) at_top (𝓝 0) :=
have f_measurable : ae_strongly_measurable f μ :=
  ae_strongly_measurable_of_tendsto_ae _ F_measurable h_lim,
let b := λ a, 2 * ennreal.of_real (bound a) in
/- `∥F n a∥ ≤ bound a` and `F n a --> f a` implies `∥f a∥ ≤ bound a`, and thus by the
  triangle inequality, have `∥F n a - f a∥ ≤ 2 * (bound a). -/
have hb : ∀ n, ∀ᵐ a ∂μ, ennreal.of_real ∥F n a - f a∥ ≤ b a,
begin
  assume n,
  filter_upwards [all_ae_of_real_F_le_bound h_bound n, all_ae_of_real_f_le_bound h_bound h_lim]
    with a h₁ h₂,
  calc ennreal.of_real ∥F n a - f a∥ ≤ (ennreal.of_real ∥F n a∥) + (ennreal.of_real ∥f a∥) :
  begin
    rw [← ennreal.of_real_add],
    apply of_real_le_of_real,
    { apply norm_sub_le }, { exact norm_nonneg _ }, { exact norm_nonneg _ }
  end
    ... ≤ (ennreal.of_real (bound a)) + (ennreal.of_real (bound a)) : add_le_add h₁ h₂
    ... = b a : by rw ← two_mul
end,
/- On the other hand, `F n a --> f a` implies that `∥F n a - f a∥ --> 0`  -/
have h : ∀ᵐ a ∂μ, tendsto (λ n, ennreal.of_real ∥F n a - f a∥) at_top (𝓝 0),
begin
  rw ← ennreal.of_real_zero,
  refine h_lim.mono (λ a h, (continuous_of_real.tendsto _).comp _),
  rwa ← tendsto_iff_norm_tendsto_zero
end,
/- Therefore, by the dominated convergence theorem for nonnegative integration, have
  ` ∫ ∥f a - F n a∥ --> 0 ` -/
begin
  suffices h : tendsto (λn, ∫⁻ a, (ennreal.of_real ∥F n a - f a∥) ∂μ) at_top (𝓝 (∫⁻ (a:α), 0 ∂μ)),
  { rwa lintegral_zero at h },
  -- Using the dominated convergence theorem.
  refine tendsto_lintegral_of_dominated_convergence' _ _ hb _ _,
  -- Show `λa, ∥f a - F n a∥` is almost everywhere measurable for all `n`
  { exact λ n,  measurable_of_real.comp_ae_measurable
      ((F_measurable n).sub f_measurable).norm.ae_measurable },
  -- Show `2 * bound` is has_finite_integral
  { rw has_finite_integral_iff_of_real at bound_has_finite_integral,
    { calc ∫⁻ a, b a ∂μ = 2 * ∫⁻ a, ennreal.of_real (bound a) ∂μ :
        by { rw lintegral_const_mul', exact coe_ne_top }
        ... ≠ ∞ : mul_ne_top coe_ne_top bound_has_finite_integral.ne },
    filter_upwards [h_bound 0] with _ h using le_trans (norm_nonneg _) h },
  -- Show `∥f a - F n a∥ --> 0`
  { exact h }
end

end dominated_convergence

section pos_part
/-! Lemmas used for defining the positive part of a `L¹` function -/

lemma has_finite_integral.max_zero {f : α → ℝ} (hf : has_finite_integral f μ) :
  has_finite_integral (λa, max (f a) 0) μ :=
hf.mono $ eventually_of_forall $ λ x, by simp [real.norm_eq_abs, abs_le, abs_nonneg, le_abs_self]

lemma has_finite_integral.min_zero {f : α → ℝ} (hf : has_finite_integral f μ) :
  has_finite_integral (λa, min (f a) 0) μ :=
hf.mono $ eventually_of_forall $ λ x,
  by simp [real.norm_eq_abs, abs_le, abs_nonneg, neg_le, neg_le_abs_self, abs_eq_max_neg, le_total]

end pos_part

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma has_finite_integral.smul (c : 𝕜) {f : α → β} : has_finite_integral f μ →
  has_finite_integral (c • f) μ :=
begin
  simp only [has_finite_integral], assume hfi,
  calc
    ∫⁻ (a : α), ∥c • f a∥₊ ∂μ = ∫⁻ (a : α), (∥c∥₊) * ∥f a∥₊ ∂μ :
      by simp only [nnnorm_smul, ennreal.coe_mul]
    ... < ∞ :
    begin
      rw lintegral_const_mul',
      exacts [mul_lt_top coe_ne_top hfi.ne, coe_ne_top]
    end
end

lemma has_finite_integral_smul_iff {c : 𝕜} (hc : c ≠ 0) (f : α → β) :
  has_finite_integral (c • f) μ ↔ has_finite_integral f μ :=
begin
  split,
  { assume h,
    simpa only [smul_smul, inv_mul_cancel hc, one_smul] using h.smul c⁻¹ },
  exact has_finite_integral.smul _
end

lemma has_finite_integral.const_mul {f : α → ℝ} (h : has_finite_integral f μ) (c : ℝ) :
  has_finite_integral (λ x, c * f x) μ :=
(has_finite_integral.smul c h : _)

lemma has_finite_integral.mul_const {f : α → ℝ} (h : has_finite_integral f μ) (c : ℝ) :
  has_finite_integral (λ x, f x * c) μ :=
by simp_rw [mul_comm, h.const_mul _]

end normed_space

/-! ### The predicate `integrable` -/

-- variables [measurable_space β] [measurable_space γ] [measurable_space δ]

/-- `integrable f μ` means that `f` is measurable and that the integral `∫⁻ a, ∥f a∥ ∂μ` is finite.
  `integrable f` means `integrable f volume`. -/
def integrable {α} {m : measurable_space α} (f : α → β) (μ : measure α . volume_tac) : Prop :=
ae_strongly_measurable f μ ∧ has_finite_integral f μ

lemma mem_ℒp_one_iff_integrable {f : α → β} : mem_ℒp f 1 μ ↔ integrable f μ :=
by simp_rw [integrable, has_finite_integral, mem_ℒp, snorm_one_eq_lintegral_nnnorm]

lemma integrable.ae_strongly_measurable {f : α → β} (hf : integrable f μ) :
  ae_strongly_measurable f μ :=
hf.1

lemma integrable.ae_measurable [measurable_space β] [borel_space β]
  {f : α → β} (hf : integrable f μ) :
  ae_measurable f μ :=
hf.ae_strongly_measurable.ae_measurable

lemma integrable.has_finite_integral {f : α → β} (hf : integrable f μ) : has_finite_integral f μ :=
hf.2

lemma integrable.mono {f : α → β} {g : α → γ}
  (hg : integrable g μ) (hf : ae_strongly_measurable f μ) (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ ∥g a∥) :
  integrable f μ :=
⟨hf, hg.has_finite_integral.mono h⟩

lemma integrable.mono' {f : α → β} {g : α → ℝ}
  (hg : integrable g μ) (hf : ae_strongly_measurable f μ) (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ g a) :
  integrable f μ :=
⟨hf, hg.has_finite_integral.mono' h⟩

lemma integrable.congr' {f : α → β} {g : α → γ}
  (hf : integrable f μ) (hg : ae_strongly_measurable g μ) (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  integrable g μ :=
⟨hg, hf.has_finite_integral.congr' h⟩

lemma integrable_congr' {f : α → β} {g : α → γ}
  (hf : ae_strongly_measurable f μ) (hg : ae_strongly_measurable g μ) (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  integrable f μ ↔ integrable g μ :=
⟨λ h2f, h2f.congr' hg h, λ h2g, h2g.congr' hf $ eventually_eq.symm h⟩

lemma integrable.congr {f g : α → β} (hf : integrable f μ) (h : f =ᵐ[μ] g) :
  integrable g μ :=
⟨hf.1.congr h, hf.2.congr h⟩

lemma integrable_congr {f g : α → β} (h : f =ᵐ[μ] g) :
  integrable f μ ↔ integrable g μ :=
⟨λ hf, hf.congr h, λ hg, hg.congr h.symm⟩

lemma integrable_const_iff {c : β} : integrable (λ x : α, c) μ ↔ c = 0 ∨ μ univ < ∞ :=
begin
  have : ae_strongly_measurable (λ (x : α), c) μ := ae_strongly_measurable_const,
  rw [integrable, and_iff_right this, has_finite_integral_const_iff]
end

lemma integrable_const [is_finite_measure μ] (c : β) : integrable (λ x : α, c) μ :=
integrable_const_iff.2 $ or.inr $ measure_lt_top _ _

lemma mem_ℒp.integrable_norm_rpow {f : α → β} {p : ℝ≥0∞}
  (hf : mem_ℒp f p μ) (hp_ne_zero : p ≠ 0) (hp_ne_top : p ≠ ∞) :
  integrable (λ (x : α), ∥f x∥ ^ p.to_real) μ :=
begin
  rw ← mem_ℒp_one_iff_integrable,
  exact hf.norm_rpow hp_ne_zero hp_ne_top,
end

lemma mem_ℒp.integrable_norm_rpow' [is_finite_measure μ] {f : α → β} {p : ℝ≥0∞}
  (hf : mem_ℒp f p μ) :
  integrable (λ (x : α), ∥f x∥ ^ p.to_real) μ :=
begin
  by_cases h_zero : p = 0,
  { simp [h_zero, integrable_const] },
  by_cases h_top : p = ∞,
  { simp [h_top, integrable_const] },
  exact hf.integrable_norm_rpow h_zero h_top
end

lemma integrable.mono_measure {f : α → β} (h : integrable f ν) (hμ : μ ≤ ν) : integrable f μ :=
⟨h.ae_strongly_measurable.mono_measure hμ, h.has_finite_integral.mono_measure hμ⟩

lemma integrable.of_measure_le_smul {μ' : measure α} (c : ℝ≥0∞) (hc : c ≠ ∞)
  (hμ'_le : μ' ≤ c • μ) {f : α → β} (hf : integrable f μ) :
  integrable f μ' :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.of_measure_le_smul c hc hμ'_le, }

lemma integrable.add_measure {f : α → β} (hμ : integrable f μ) (hν : integrable f ν) :
  integrable f (μ + ν) :=
begin
  simp_rw ← mem_ℒp_one_iff_integrable at hμ hν ⊢,
  refine ⟨hμ.ae_strongly_measurable.add_measure hν.ae_strongly_measurable, _⟩,
  rw [snorm_one_add_measure, ennreal.add_lt_top],
  exact ⟨hμ.snorm_lt_top, hν.snorm_lt_top⟩,
end

lemma integrable.left_of_add_measure {f : α → β} (h : integrable f (μ + ν)) : integrable f μ :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.left_of_add_measure, }

lemma integrable.right_of_add_measure {f : α → β} (h : integrable f (μ + ν)) : integrable f ν :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.right_of_add_measure, }

@[simp] lemma integrable_add_measure {f : α → β} :
  integrable f (μ + ν) ↔ integrable f μ ∧ integrable f ν :=
⟨λ h, ⟨h.left_of_add_measure, h.right_of_add_measure⟩, λ h, h.1.add_measure h.2⟩

@[simp] lemma integrable_zero_measure {m : measurable_space α} {f : α → β} :
  integrable f (0 : measure α) :=
⟨ae_measurable_zero_measure f, has_finite_integral_zero_measure f⟩

theorem integrable_finset_sum_measure {ι} {m : measurable_space α} {f : α → β}
  {μ : ι → measure α} {s : finset ι} :
  integrable f (∑ i in s, μ i) ↔ ∀ i ∈ s, integrable f (μ i) :=
by induction s using finset.induction_on; simp [*]

lemma integrable.smul_measure {f : α → β} (h : integrable f μ) {c : ℝ≥0∞} (hc : c ≠ ∞) :
  integrable f (c • μ) :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.smul_measure hc, }

lemma integrable_smul_measure {f : α → β} {c : ℝ≥0∞} (h₁ : c ≠ 0) (h₂ : c ≠ ∞) :
  integrable f (c • μ) ↔ integrable f μ :=
⟨λ h, by simpa only [smul_smul, ennreal.inv_mul_cancel h₁ h₂, one_smul]
  using h.smul_measure (ennreal.inv_ne_top.2 h₁), λ h, h.smul_measure h₂⟩

lemma integrable.to_average {f : α → β} (h : integrable f μ) :
  integrable f ((μ univ)⁻¹ • μ) :=
begin
  rcases eq_or_ne μ 0 with rfl|hne,
  { rwa smul_zero },
  { apply h.smul_measure, simpa }
end

lemma integrable_average [is_finite_measure μ] {f : α → β} :
  integrable f ((μ univ)⁻¹ • μ) ↔ integrable f μ :=
(eq_or_ne μ 0).by_cases (λ h, by simp [h]) $ λ h,
  integrable_smul_measure (ennreal.inv_ne_zero.2 $ measure_ne_top _ _)
    (ennreal.inv_ne_top.2 $ mt measure.measure_univ_eq_zero.1 h)

lemma integrable_map_measure {f : α → δ} {g : δ → β}
  (hg : ae_strongly_measurable g (measure.map f μ)) (hf : ae_measurable f μ) :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact mem_ℒp_map_measure_iff hg hf, }

lemma integrable.comp_ae_measurable {f : α → δ} {g : δ → β}
  (hg : integrable g (measure.map f μ)) (hf : ae_measurable f μ) : integrable (g ∘ f) μ :=
(integrable_map_measure hg.ae_strongly_measurable hf).mp hg

lemma integrable.comp_measurable {f : α → δ} {g : δ → β}
  (hg : integrable g (measure.map f μ)) (hf : measurable f) : integrable (g ∘ f) μ :=
hg.comp_ae_measurable hf.ae_measurable

lemma _root_.measurable_embedding.integrable_map_iff
  {f : α → δ} (hf : measurable_embedding f) {g : δ → β} :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact hf.mem_ℒp_map_measure_iff, }

lemma integrable_map_equiv (f : α ≃ᵐ δ) (g : δ → β) :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact f.mem_ℒp_map_measure_iff, }

lemma measure_preserving.integrable_comp {ν : measure δ} {g : δ → β}
  {f : α → δ} (hf : measure_preserving f μ ν) (hg : ae_strongly_measurable g ν) :
  integrable (g ∘ f) μ ↔ integrable g ν :=
by { rw ← hf.map_eq at hg ⊢, exact (integrable_map_measure hg hf.measurable.ae_measurable).symm }

lemma measure_preserving.integrable_comp_emb {f : α → δ} {ν} (h₁ : measure_preserving f μ ν)
  (h₂ : measurable_embedding f) {g : δ → β} :
  integrable (g ∘ f) μ ↔ integrable g ν :=
h₁.map_eq ▸ iff.symm h₂.integrable_map_iff

lemma lintegral_edist_lt_top {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  ∫⁻ a, edist (f a) (g a) ∂μ < ∞ :=
lt_of_le_of_lt
  (lintegral_edist_triangle hf.ae_strongly_measurable ae_strongly_measurable_zero)
  (ennreal.add_lt_top.2 $ by { simp_rw [pi.zero_apply, ← has_finite_integral_iff_edist],
                               exact ⟨hf.has_finite_integral, hg.has_finite_integral⟩ })

variables (α β μ)
@[simp] lemma integrable_zero : integrable (λ _, (0 : β)) μ :=
by simp [integrable, ae_strongly_measurable_const]
variables {α β μ}

lemma integrable.add' {f g : α → β} (hf : integrable f μ) (hg : integrable g μ) :
  has_finite_integral (f + g) μ :=
calc ∫⁻ a, ∥f a + g a∥₊ ∂μ ≤ ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ :
  lintegral_mono (λ a, by exact_mod_cast nnnorm_add_le _ _)
... = _ : lintegral_nnnorm_add_left hf.ae_strongly_measurable _
... < ∞ : add_lt_top.2 ⟨hf.has_finite_integral, hg.has_finite_integral⟩

lemma integrable.add
  {f g : α → β} (hf : integrable f μ) (hg : integrable g μ) : integrable (f + g) μ :=
⟨hf.ae_strongly_measurable.add hg.ae_strongly_measurable, hf.add' hg⟩

lemma integrable_finset_sum' {ι} (s : finset ι)
  {f : ι → α → β} (hf : ∀ i ∈ s, integrable (f i) μ) : integrable (∑ i in s, f i) μ :=
finset.sum_induction f (λ g, integrable g μ) (λ _ _, integrable.add)
  (integrable_zero _ _ _) hf

lemma integrable_finset_sum {ι} (s : finset ι)
  {f : ι → α → β} (hf : ∀ i ∈ s, integrable (f i) μ) : integrable (λ a, ∑ i in s, f i a) μ :=
by simpa only [← finset.sum_apply] using integrable_finset_sum' s hf

lemma integrable.neg {f : α → β} (hf : integrable f μ) : integrable (-f) μ :=
⟨hf.ae_strongly_measurable.neg, hf.has_finite_integral.neg⟩

@[simp] lemma integrable_neg_iff {f : α → β} :
  integrable (-f) μ ↔ integrable f μ :=
⟨λ h, neg_neg f ▸ h.neg, integrable.neg⟩

lemma integrable.sub {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) : integrable (f - g) μ :=
by simpa only [sub_eq_add_neg] using hf.add hg.neg

lemma integrable.norm {f : α → β} (hf : integrable f μ) :
  integrable (λ a, ∥f a∥) μ :=
⟨hf.ae_strongly_measurable.norm, hf.has_finite_integral.norm⟩

lemma integrable.inf {β} [normed_lattice_add_comm_group β] {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  integrable (f ⊓ g) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf hg ⊢, exact hf.inf hg, }

lemma integrable.sup {β} [normed_lattice_add_comm_group β] {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  integrable (f ⊔ g) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf hg ⊢, exact hf.sup hg, }

lemma integrable.abs {β} [normed_lattice_add_comm_group β] {f : α → β} (hf : integrable f μ) :
  integrable (λ a, |f a|) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.abs, }

lemma integrable.bdd_mul {F : Type*} [normed_division_ring F]
  {f g : α → F} (hint : integrable g μ) (hm : ae_strongly_measurable f μ)
  (hfbdd : ∃ C, ∀ x, ∥f x∥ ≤ C) :
  integrable (λ x, f x * g x) μ :=
begin
  casesI is_empty_or_nonempty α with hα hα,
  { rw μ.eq_zero_of_is_empty,
    exact integrable_zero_measure },
  { refine ⟨hm.mul hint.1, _⟩,
    obtain ⟨C, hC⟩ := hfbdd,
    have hCnonneg : 0 ≤ C := le_trans (norm_nonneg _) (hC hα.some),
    have : (λ x, ∥f x * g x∥₊) ≤ λ x, ⟨C, hCnonneg⟩ * ∥g x∥₊,
    { intro x,
      simp only [nnnorm_mul],
      exact mul_le_mul_of_nonneg_right (hC x) (zero_le _) },
    refine lt_of_le_of_lt (lintegral_mono_nnreal this) _,
    simp only [ennreal.coe_mul],
    rw lintegral_const_mul' _ _ ennreal.coe_ne_top,
    exact ennreal.mul_lt_top ennreal.coe_ne_top (ne_of_lt hint.2) },
end

lemma integrable_norm_iff {f : α → β} (hf : ae_strongly_measurable f μ) :
  integrable (λa, ∥f a∥) μ ↔ integrable f μ :=
by simp_rw [integrable, and_iff_right hf, and_iff_right hf.norm, has_finite_integral_norm_iff]

lemma integrable_of_norm_sub_le {f₀ f₁ : α → β} {g : α → ℝ}
  (hf₁_m : ae_strongly_measurable f₁ μ)
  (hf₀_i : integrable f₀ μ)
  (hg_i : integrable g μ)
  (h : ∀ᵐ a ∂μ, ∥f₀ a - f₁ a∥ ≤ g a) :
  integrable f₁ μ :=
begin
  have : ∀ᵐ a ∂μ, ∥f₁ a∥ ≤ ∥f₀ a∥ + g a,
  { apply h.mono,
    intros a ha,
    calc ∥f₁ a∥ ≤ ∥f₀ a∥ + ∥f₀ a - f₁ a∥ : norm_le_insert _ _
    ... ≤ ∥f₀ a∥ + g a : add_le_add_left ha _ },
  exact integrable.mono' (hf₀_i.norm.add hg_i) hf₁_m this
end

lemma integrable.prod_mk {f : α → β} {g : α → γ} (hf : integrable f μ) (hg : integrable g μ) :
  integrable (λ x, (f x, g x)) μ :=
⟨hf.ae_strongly_measurable.prod_mk hg.ae_strongly_measurable,
  (hf.norm.add' hg.norm).mono $ eventually_of_forall $ λ x,
  calc max ∥f x∥ ∥g x∥ ≤ ∥f x∥ + ∥g x∥   : max_le_add_of_nonneg (norm_nonneg _) (norm_nonneg _)
                 ... ≤ ∥(∥f x∥ + ∥g x∥)∥ : le_abs_self _⟩

lemma mem_ℒp.integrable {q : ℝ≥0∞} (hq1 : 1 ≤ q) {f : α → β} [is_finite_measure μ]
  (hfq : mem_ℒp f q μ) : integrable f μ :=
mem_ℒp_one_iff_integrable.mp (hfq.mem_ℒp_of_exponent_le hq1)

lemma lipschitz_with.integrable_comp_iff_of_antilipschitz {K K'} {f : α → β} {g : β → γ}
  (hg : lipschitz_with K g) (hg' : antilipschitz_with K' g) (g0 : g 0 = 0) :
  integrable (g ∘ f) μ ↔ integrable f μ :=
by simp [← mem_ℒp_one_iff_integrable, hg.mem_ℒp_comp_iff_of_antilipschitz hg' g0]

lemma integrable.real_to_nnreal {f : α → ℝ} (hf : integrable f μ) :
  integrable (λ x, ((f x).to_nnreal : ℝ)) μ :=
begin
  refine ⟨hf.ae_strongly_measurable.ae_measurable
    .real_to_nnreal.coe_nnreal_real.ae_strongly_measurable, _⟩,
  rw has_finite_integral_iff_norm,
  refine lt_of_le_of_lt _ ((has_finite_integral_iff_norm _).1 hf.has_finite_integral),
  apply lintegral_mono,
  assume x,
  simp [real.norm_eq_abs, ennreal.of_real_le_of_real, abs_le, abs_nonneg, le_abs_self],
end

lemma of_real_to_real_ae_eq {f : α → ℝ≥0∞} (hf : ∀ᵐ x ∂μ, f x < ∞) :
  (λ x, ennreal.of_real (f x).to_real) =ᵐ[μ] f :=
begin
  filter_upwards [hf],
  assume x hx,
  simp only [hx.ne, of_real_to_real, ne.def, not_false_iff],
end

lemma coe_to_nnreal_ae_eq {f : α → ℝ≥0∞} (hf : ∀ᵐ x ∂μ, f x < ∞) :
  (λ x, ((f x).to_nnreal : ℝ≥0∞)) =ᵐ[μ] f :=
begin
  filter_upwards [hf],
  assume x hx,
  simp only [hx.ne, ne.def, not_false_iff, coe_to_nnreal],
end

section
variables  {E : Type*} [normed_group E] [normed_space ℝ E]

lemma integrable_with_density_iff_integrable_coe_smul
  {f : α → ℝ≥0} (hf : measurable f) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, (f x : ℝ) • g x) μ :=
begin
  by_cases H : ae_strongly_measurable (λ (x : α), (f x : ℝ) • g x) μ,
  { simp only [integrable, ae_strongly_measurable_with_density_iff hf, has_finite_integral, H,
      true_and],
    rw lintegral_with_density_eq_lintegral_mul₀' hf.coe_nnreal_ennreal.ae_measurable,
    { congr',
      ext1 x,
      simp only [nnnorm_smul, nnreal.nnnorm_eq, coe_mul, pi.mul_apply] },
    { rw ae_measurable_with_density_ennreal_iff hf,
      convert H.ennnorm,
      ext1 x,
      simp only [nnnorm_smul, nnreal.nnnorm_eq, coe_mul] } },
  { simp only [integrable, ae_strongly_measurable_with_density_iff hf, H, false_and] }
end

lemma integrable_with_density_iff_integrable_smul {f : α → ℝ≥0} (hf : measurable f) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, f x • g x) μ :=
integrable_with_density_iff_integrable_coe_smul hf

lemma integrable_with_density_iff_integrable_smul'
  {f : α → ℝ≥0∞} (hf : measurable f) (hflt : ∀ᵐ x ∂μ, f x < ∞) {g : α → E} :
  integrable g (μ.with_density f) ↔ integrable (λ x, (f x).to_real • g x) μ :=
begin
  rw [← with_density_congr_ae (coe_to_nnreal_ae_eq hflt),
      integrable_with_density_iff_integrable_smul],
  { refl },
  { exact hf.ennreal_to_nnreal },
end

lemma integrable_with_density_iff_integrable_coe_smul₀
  {f : α → ℝ≥0} (hf : ae_measurable f μ) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, (f x : ℝ) • g x) μ :=
calc
integrable g (μ.with_density (λ x, f x))
    ↔ integrable g (μ.with_density (λ x, hf.mk f x)) :
begin
  suffices : (λ x, (f x : ℝ≥0∞)) =ᵐ[μ] (λ x, hf.mk f x), by rw with_density_congr_ae this,
  filter_upwards [hf.ae_eq_mk] with x hx,
  simp [hx],
end
... ↔ integrable (λ x, (hf.mk f x : ℝ) • g x) μ :
  integrable_with_density_iff_integrable_coe_smul hf.measurable_mk
... ↔ integrable (λ x, (f x : ℝ) • g x) μ :
begin
  apply integrable_congr,
  filter_upwards [hf.ae_eq_mk] with x hx,
  simp [hx],
end

lemma integrable_with_density_iff_integrable_smul₀
  {f : α → ℝ≥0} (hf : ae_measurable f μ) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, f x • g x) μ :=
integrable_with_density_iff_integrable_coe_smul₀ hf

end

lemma integrable_with_density_iff {f : α → ℝ≥0∞} (hf : measurable f)
  (hflt : ∀ᵐ x ∂μ, f x < ∞) {g : α → ℝ} :
  integrable g (μ.with_density f) ↔ integrable (λ x, g x * (f x).to_real) μ :=
begin
  have : (λ x, g x * (f x).to_real) = (λ x, (f x).to_real • g x), by simp [mul_comm],
  rw this,
  exact integrable_with_density_iff_integrable_smul' hf hflt,
end

section
variables {E : Type*} [normed_group E] [normed_space ℝ E]

lemma mem_ℒ1_smul_of_L1_with_density {f : α → ℝ≥0} (f_meas : measurable f)
  (u : Lp E 1 (μ.with_density (λ x, f x))) :
  mem_ℒp (λ x, f x • u x) 1 μ :=
mem_ℒp_one_iff_integrable.2 $ (integrable_with_density_iff_integrable_smul f_meas).1 $
mem_ℒp_one_iff_integrable.1 (Lp.mem_ℒp u)

variable (μ)
/-- The map `u ↦ f • u` is an isometry between the `L^1` spaces for `μ.with_density f` and `μ`. -/
noncomputable def with_density_smul_li {f : α → ℝ≥0} (f_meas : measurable f) :
  Lp E 1 (μ.with_density (λ x, f x)) →ₗᵢ[ℝ] Lp E 1 μ :=
{ to_fun := λ u, (mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _,
  map_add' :=
  begin
    assume u v,
    ext1,
    filter_upwards [(mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp,
      (mem_ℒ1_smul_of_L1_with_density f_meas v).coe_fn_to_Lp,
      (mem_ℒ1_smul_of_L1_with_density f_meas (u + v)).coe_fn_to_Lp,
      Lp.coe_fn_add ((mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _)
        ((mem_ℒ1_smul_of_L1_with_density f_meas v).to_Lp _),
      (ae_with_density_iff f_meas.coe_nnreal_ennreal).1 (Lp.coe_fn_add u v)],
    assume x hu hv huv h' h'',
    rw [huv, h', pi.add_apply, hu, hv],
    rcases eq_or_ne (f x) 0 with hx|hx,
    { simp only [hx, zero_smul, add_zero] },
    { rw [h'' _, pi.add_apply, smul_add],
      simpa only [ne.def, ennreal.coe_eq_zero] using hx }
  end,
  map_smul' :=
  begin
    assume r u,
    ext1,
    filter_upwards [(ae_with_density_iff f_meas.coe_nnreal_ennreal).1 (Lp.coe_fn_smul r u),
      (mem_ℒ1_smul_of_L1_with_density f_meas (r • u)).coe_fn_to_Lp,
      Lp.coe_fn_smul r ((mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _),
      (mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp],
    assume x h h' h'' h''',
    rw [ring_hom.id_apply, h', h'', pi.smul_apply, h'''],
    rcases eq_or_ne (f x) 0 with hx|hx,
    { simp only [hx, zero_smul, smul_zero] },
    { rw [h _, smul_comm, pi.smul_apply],
      simpa only [ne.def, ennreal.coe_eq_zero] using hx }
  end,
  norm_map' :=
  begin
    assume u,
    simp only [snorm, linear_map.coe_mk, Lp.norm_to_Lp, one_ne_zero, ennreal.one_ne_top,
      ennreal.one_to_real, if_false, snorm', ennreal.rpow_one, _root_.div_one, Lp.norm_def],
    rw lintegral_with_density_eq_lintegral_mul_non_measurable _ f_meas.coe_nnreal_ennreal
      (filter.eventually_of_forall (λ x, ennreal.coe_lt_top)),
    congr' 1,
    apply lintegral_congr_ae,
    filter_upwards [(mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp] with x hx,
    rw [hx, pi.mul_apply],
    change ↑∥(f x : ℝ) • u x∥₊ = ↑(f x) * ↑∥u x∥₊,
    simp only [nnnorm_smul, nnreal.nnnorm_eq, ennreal.coe_mul],
  end }

@[simp] lemma with_density_smul_li_apply {f : α → ℝ≥0} (f_meas : measurable f)
  (u : Lp E 1 (μ.with_density (λ x, f x))) :
  with_density_smul_li μ f_meas u =
    (mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp (λ x, f x • u x) :=
rfl

end

lemma mem_ℒ1_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hfm : ae_measurable f μ) (hfi : ∫⁻ x, f x ∂μ ≠ ∞) :
  mem_ℒp (λ x, (f x).to_real) 1 μ :=
begin
  rw [mem_ℒp, snorm_one_eq_lintegral_nnnorm],
  exact ⟨(ae_measurable.ennreal_to_real hfm).ae_strongly_measurable,
          has_finite_integral_to_real_of_lintegral_ne_top hfi⟩
end

lemma integrable_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hfm : ae_measurable f μ) (hfi : ∫⁻ x, f x ∂μ ≠ ∞) :
  integrable (λ x, (f x).to_real) μ :=
mem_ℒp_one_iff_integrable.1 $ mem_ℒ1_to_real_of_lintegral_ne_top hfm hfi

section pos_part
/-! ### Lemmas used for defining the positive part of a `L¹` function -/

lemma integrable.pos_part {f : α → ℝ} (hf : integrable f μ) : integrable (λ a, max (f a) 0) μ :=
⟨(hf.ae_strongly_measurable.ae_measurable.max ae_measurable_const).ae_strongly_measurable,
  hf.has_finite_integral.max_zero⟩

lemma integrable.neg_part {f : α → ℝ} (hf : integrable f μ) : integrable (λ a, max (-f a) 0) μ :=
hf.neg.pos_part

end pos_part

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma integrable.smul (c : 𝕜) {f : α → β}
  (hf : integrable f μ) : integrable (c • f) μ :=
⟨hf.ae_strongly_measurable.const_smul c, hf.has_finite_integral.smul c⟩

lemma integrable_smul_iff {c : 𝕜} (hc : c ≠ 0) (f : α → β) :
  integrable (c • f) μ ↔ integrable f μ :=
and_congr (ae_strongly_measurable_const_smul_iff₀ hc) (has_finite_integral_smul_iff hc f)

lemma integrable.const_mul {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, c * f x) μ :=
integrable.smul c h

lemma integrable.const_mul' {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable ((λ (x : α), c) * f) μ :=
integrable.smul c h

lemma integrable.mul_const {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, f x * c) μ :=
by simp_rw [mul_comm, h.const_mul _]

lemma integrable.mul_const' {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (f * (λ (x : α), c)) μ :=
integrable.mul_const h c

lemma integrable.div_const {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, f x / c) μ :=
by simp_rw [div_eq_mul_inv, h.mul_const]

end normed_space

section normed_space_over_complete_field
variables {𝕜 : Type*} [nondiscrete_normed_field 𝕜] [complete_space 𝕜]
variables {E : Type*} [normed_group E] [normed_space 𝕜 E]

lemma integrable_smul_const {f : α → 𝕜} {c : E} (hc : c ≠ 0) :
  integrable (λ x, f x • c) μ ↔ integrable f μ :=
begin
  simp_rw [integrable, ae_strongly_measurable_smul_const_iff hc, and.congr_right_iff,
    has_finite_integral, nnnorm_smul, ennreal.coe_mul],
  intro hf, rw [lintegral_mul_const' _ _ ennreal.coe_ne_top, ennreal.mul_lt_top_iff],
  have : ∀ x : ℝ≥0∞, x = 0 → x < ∞ := by simp,
  simp [hc, or_iff_left_of_imp (this _)]
end
end normed_space_over_complete_field

section is_R_or_C
variables {𝕜 : Type*} [is_R_or_C 𝕜] {f : α → 𝕜}

lemma integrable.re_im_iff :
  integrable (λ x, is_R_or_C.re (f x)) μ ∧ integrable (λ x, is_R_or_C.im (f x)) μ ↔
  integrable f μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact mem_ℒp_re_im_iff }

lemma integrable.re (hf : integrable f μ) : integrable (λ x, is_R_or_C.re (f x)) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.re, }

lemma integrable.im (hf : integrable f μ) : integrable (λ x, is_R_or_C.im (f x)) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.im, }

end is_R_or_C

section inner_product
variables {𝕜 E : Type*} [is_R_or_C 𝕜] [inner_product_space 𝕜 E] {f : α → E}

local notation `⟪`x`, `y`⟫` := @inner 𝕜 E _ x y

lemma integrable.const_inner (c : E) (hf : integrable f μ) : integrable (λ x, ⟪c, f x⟫) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.const_inner c, }

lemma integrable.inner_const (hf : integrable f μ) (c : E) : integrable (λ x, ⟪f x, c⟫) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.inner_const c, }

end inner_product

section trim

variables {H : Type*} [normed_group H] {m0 : measurable_space α} {μ' : measure α} {f : α → H}

lemma integrable.trim (hm : m ≤ m0) (hf_int : integrable f μ') (hf : strongly_measurable[m] f) :
  integrable f (μ'.trim hm) :=
begin
  refine ⟨hf.ae_strongly_measurable, _⟩,
  rw [has_finite_integral, lintegral_trim hm _],
  { exact hf_int.2, },
  { exact @strongly_measurable.ennnorm _ m _ _ f hf },
end

lemma integrable_of_integrable_trim (hm : m ≤ m0) (hf_int : integrable f (μ'.trim hm)) :
  integrable f μ' :=
begin
  obtain ⟨hf_meas_ae, hf⟩ := hf_int,
  refine ⟨ae_strongly_measurable_of_ae_strongly_measurable_trim hm hf_meas_ae, _⟩,
  rw has_finite_integral at hf ⊢,
  rwa lintegral_trim_ae hm _ at hf,
  exact ae_strongly_measurable.ennnorm hf_meas_ae
end

end trim

section sigma_finite

variables {E : Type*} {m0 : measurable_space α} [normed_group E]

lemma integrable_of_forall_fin_meas_le' {μ : measure α} (hm : m ≤ m0) [sigma_finite (μ.trim hm)]
  (C : ℝ≥0∞) (hC : C < ∞) {f : α → E} (hf_meas : ae_strongly_measurable f μ)
  (hf : ∀ s, measurable_set[m] s → μ s ≠ ∞ → ∫⁻ x in s, ∥f x∥₊ ∂μ ≤ C) :
  integrable f μ :=
⟨hf_meas, (lintegral_le_of_forall_fin_meas_le' hm C hf_meas.ennnorm hf).trans_lt hC⟩

lemma integrable_of_forall_fin_meas_le [sigma_finite μ]
  (C : ℝ≥0∞) (hC : C < ∞) {f : α → E} (hf_meas : ae_strongly_measurable f μ)
  (hf : ∀ s : set α, measurable_set s → μ s ≠ ∞ → ∫⁻ x in s, ∥f x∥₊ ∂μ ≤ C) :
  integrable f μ :=
@integrable_of_forall_fin_meas_le' _ _ _ _ _ _ _ (by rwa trim_eq_self) C hC _ hf_meas hf

end sigma_finite

/-! ### The predicate `integrable` on measurable functions modulo a.e.-equality -/

namespace ae_eq_fun

section

/-- A class of almost everywhere equal functions is `integrable` if its function representative
is integrable. -/
def integrable (f : α →ₘ[μ] β) : Prop := integrable f μ

lemma integrable_mk {f : α → β} (hf : ae_strongly_measurable f μ ) :
  (integrable (mk f hf : α →ₘ[μ] β)) ↔ measure_theory.integrable f μ :=
begin
  simp [integrable],
  apply integrable_congr,
  exact coe_fn_mk f hf
end

lemma integrable_coe_fn {f : α →ₘ[μ] β} : (measure_theory.integrable f μ) ↔ integrable f :=
by rw [← integrable_mk, mk_coe_fn]

lemma integrable_zero : integrable (0 : α →ₘ[μ] β) :=
(integrable_zero α β μ).congr (coe_fn_mk _ _).symm

end

section

lemma integrable.neg {f : α →ₘ[μ] β} : integrable f → integrable (-f) :=
induction_on f $ λ f hfm hfi, (integrable_mk _).2 ((integrable_mk hfm).1 hfi).neg

section

lemma integrable_iff_mem_L1 {f : α →ₘ[μ] β} : integrable f ↔ f ∈ (α →₁[μ] β) :=
by rw [← integrable_coe_fn, ← mem_ℒp_one_iff_integrable, Lp.mem_Lp_iff_mem_ℒp]

lemma integrable.add {f g : α →ₘ[μ] β} : integrable f → integrable g → integrable (f + g) :=
begin
  refine induction_on₂ f g (λ f hf g hg hfi hgi, _),
  simp only [integrable_mk, mk_add_mk] at hfi hgi ⊢,
  exact hfi.add hgi
end

lemma integrable.sub {f g : α →ₘ[μ] β} (hf : integrable f) (hg : integrable g) :
  integrable (f - g) :=
(sub_eq_add_neg f g).symm ▸ hf.add hg.neg

end

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma integrable.smul {c : 𝕜} {f : α →ₘ[μ] β} : integrable f → integrable (c • f) :=
induction_on f $ λ f hfm hfi, (integrable_mk _).2 $ ((integrable_mk hfm).1 hfi).smul _

end normed_space

end

end ae_eq_fun

namespace L1

lemma integrable_coe_fn (f : α →₁[μ] β) :
  integrable f μ :=
by { rw ← mem_ℒp_one_iff_integrable, exact Lp.mem_ℒp f }

lemma has_finite_integral_coe_fn (f : α →₁[μ] β) :
  has_finite_integral f μ :=
(integrable_coe_fn f).has_finite_integral

lemma strongly_measurable_coe_fn (f : α →₁[μ] β) : strongly_measurable f :=
Lp.strongly_measurable f

lemma measurable_coe_fn [measurable_space β] [borel_space β] (f : α →₁[μ] β) :
  measurable f :=
(Lp.strongly_measurable f).measurable

lemma ae_strongly_measurable_coe_fn (f : α →₁[μ] β) : ae_strongly_measurable f μ :=
Lp.ae_strongly_measurable f

lemma ae_measurable_coe_fn [measurable_space β] [borel_space β] (f : α →₁[μ] β) :
  ae_measurable f μ :=
(Lp.strongly_measurable f).measurable.ae_measurable

lemma edist_def (f g : α →₁[μ] β) :
  edist f g = ∫⁻ a, edist (f a) (g a) ∂μ :=
by { simp [Lp.edist_def, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

lemma dist_def (f g : α →₁[μ] β) :
  dist f g = (∫⁻ a, edist (f a) (g a) ∂μ).to_real :=
by { simp [Lp.dist_def, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

lemma norm_def (f : α →₁[μ] β) :
  ∥f∥ = (∫⁻ a, ∥f a∥₊ ∂μ).to_real :=
by { simp [Lp.norm_def, snorm, snorm'] }

/-- Computing the norm of a difference between two L¹-functions. Note that this is not a
  special case of `norm_def` since `(f - g) x` and `f x - g x` are not equal
  (but only a.e.-equal). -/
lemma norm_sub_eq_lintegral (f g : α →₁[μ] β) :
  ∥f - g∥ = (∫⁻ x, (∥f x - g x∥₊ : ℝ≥0∞) ∂μ).to_real :=
begin
  rw [norm_def],
  congr' 1,
  rw lintegral_congr_ae,
  filter_upwards [Lp.coe_fn_sub f g] with _ ha,
  simp only [ha, pi.sub_apply],
end

lemma of_real_norm_eq_lintegral (f : α →₁[μ] β) :
  ennreal.of_real ∥f∥ = ∫⁻ x, (∥f x∥₊ : ℝ≥0∞) ∂μ :=
by { rw [norm_def, ennreal.of_real_to_real], exact ne_of_lt (has_finite_integral_coe_fn f) }

/-- Computing the norm of a difference between two L¹-functions. Note that this is not a
  special case of `of_real_norm_eq_lintegral` since `(f - g) x` and `f x - g x` are not equal
  (but only a.e.-equal). -/
lemma of_real_norm_sub_eq_lintegral (f g : α →₁[μ] β) :
  ennreal.of_real ∥f - g∥ = ∫⁻ x, (∥f x - g x∥₊ : ℝ≥0∞) ∂μ :=
begin
  simp_rw [of_real_norm_eq_lintegral, ← edist_eq_coe_nnnorm],
  apply lintegral_congr_ae,
  filter_upwards [Lp.coe_fn_sub f g] with _ ha,
  simp only [ha, pi.sub_apply],
end

end L1

namespace integrable

/-- Construct the equivalence class `[f]` of an integrable function `f`, as a member of the
space `L1 β 1 μ`. -/
def to_L1 (f : α → β) (hf : integrable f μ) : α →₁[μ] β :=
(mem_ℒp_one_iff_integrable.2 hf).to_Lp f

@[simp] lemma to_L1_coe_fn (f : α →₁[μ] β) (hf : integrable f μ) : hf.to_L1 f = f :=
by simp [integrable.to_L1]

lemma coe_fn_to_L1 {f : α → β} (hf : integrable f μ) : hf.to_L1 f =ᵐ[μ] f :=
ae_eq_fun.coe_fn_mk _ _

@[simp] lemma to_L1_zero (h : integrable (0 : α → β) μ) : h.to_L1 0 = 0 := rfl

@[simp] lemma to_L1_eq_mk (f : α → β) (hf : integrable f μ) :
  (hf.to_L1 f : α →ₘ[μ] β) = ae_eq_fun.mk f hf.ae_strongly_measurable :=
rfl

@[simp] lemma to_L1_eq_to_L1_iff (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 f hf = to_L1 g hg ↔ f =ᵐ[μ] g :=
mem_ℒp.to_Lp_eq_to_Lp_iff _ _

lemma to_L1_add (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 (f + g) (hf.add hg) = to_L1 f hf + to_L1 g hg := rfl

lemma to_L1_neg (f : α → β) (hf : integrable f μ) :
  to_L1 (- f) (integrable.neg hf) = - to_L1 f hf := rfl

lemma to_L1_sub (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 (f - g) (hf.sub hg) = to_L1 f hf - to_L1 g hg := rfl

lemma norm_to_L1 (f : α → β) (hf : integrable f μ) :
  ∥hf.to_L1 f∥ = ennreal.to_real (∫⁻ a, edist (f a) 0 ∂μ) :=
by { simp [to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm] }

lemma norm_to_L1_eq_lintegral_norm (f : α → β) (hf : integrable f μ) :
  ∥hf.to_L1 f∥ = ennreal.to_real (∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ) :=
by { rw [norm_to_L1, lintegral_norm_eq_lintegral_edist] }

@[simp] lemma edist_to_L1_to_L1 (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  edist (hf.to_L1 f) (hg.to_L1 g) = ∫⁻ a, edist (f a) (g a) ∂μ :=
by { simp [integrable.to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

@[simp] lemma edist_to_L1_zero (f : α → β) (hf : integrable f μ) :
  edist (hf.to_L1 f) 0 = ∫⁻ a, edist (f a) 0 ∂μ :=
by { simp [integrable.to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm] }

variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma to_L1_smul (f : α → β) (hf : integrable f μ) (k : 𝕜) :
  to_L1 (λ a, k • f a) (hf.smul k) = k • to_L1 f hf := rfl

lemma to_L1_smul' (f : α → β) (hf : integrable f μ) (k : 𝕜) :
  to_L1 (k • f) (hf.smul k) = k • to_L1 f hf := rfl

end integrable

end measure_theory

open measure_theory

variables {E : Type*} [normed_group E]
          {𝕜 : Type*} [nondiscrete_normed_field 𝕜] [normed_space 𝕜 E]
          {H : Type*} [normed_group H] [normed_space 𝕜 H]

lemma measure_theory.integrable.apply_continuous_linear_map {φ : α → H →L[𝕜] E}
  (φ_int : integrable φ μ) (v : H) : integrable (λ a, φ a v) μ :=
(φ_int.norm.mul_const ∥v∥).mono' (φ_int.ae_strongly_measurable.apply_continuous_linear_map v)
  (eventually_of_forall $ λ a, (φ a).le_op_norm v)

lemma continuous_linear_map.integrable_comp {φ : α → H} (L : H →L[𝕜] E)
  (φ_int : integrable φ μ) : integrable (λ (a : α), L (φ a)) μ :=
((integrable.norm φ_int).const_mul ∥L∥).mono'
  (L.continuous.comp_ae_strongly_measurable φ_int.ae_strongly_measurable)
  (eventually_of_forall $ λ a, L.le_op_norm (φ a))


import tactic

open function

theorem challenge4 (X Y Z : Type) (f : X → Y) (g : Y → Z) : surjective (g ∘ f) → surjective g :=
begin
  intro h,
  intro z,
  cases h z with a ha,
  use f a,
  assumption,
end