-- Copyright (c) Microsoft Corporation. All rights reserved.
-- Licensed under the MIT license.

import ..smtexpr
import ..bitvector
import .spec
--import .lemmas
import .irstate
import .freevar
import .equiv
import smt2.syntax
import system.io
import init.meta.tactic
import init.meta.interactive

namespace spec

open irsem
open freevar

def closed_bv {sz:size} (bv:sbitvec sz) := ∀ (η:freevar.env), η⟦bv⟧ = bv
def closed_b (b:sbool) := ∀ (η:freevar.env), η⟦b⟧ = b
def closed_valty (v:valty irsem_smt) := ∀ (η:freevar.env), η⟦v⟧ = v
def closed_regfile (rf:regfile irsem_smt) :=
  ∀ (η:freevar.env), rf.apply_to_values irsem_smt (η.replace_valty) = rf
def closed_irstate (ss:irstate irsem_smt) := ∀ (η:freevar.env), η⟦ss⟧ = ss

lemma closed_bv_equiv: ∀ {sz:size} (η:freevar.env)
    (sb:sbitvec sz) (bv:bitvector sz)
    (HEQ:bv_equiv (η⟦sb⟧) bv)
    (HC:closed_bv sb),
  bv_equiv sb bv
:= begin
  intros, unfold closed_bv at HC, have HC := HC η, rw HC at HEQ, assumption
end

lemma closed_b_equiv: ∀ (η:freevar.env)
    (sb:sbool) (b:bool)
    (HEQ:b_equiv (η⟦sb⟧) b)
    (HC:closed_b sb),
  b_equiv sb b
:= begin
  intros, unfold closed_b at HC, have HC := HC η, rw HC at HEQ, assumption
end

lemma closed_irstate_equiv: ∀ (η:freevar.env)
    (ss:irstate irsem_smt) (se:irstate irsem_exec)
    (HEQ:irstate_equiv (η⟦ss⟧) se)
    (HC:closed_irstate ss),
  irstate_equiv ss se
:= begin
  intros, unfold closed_irstate at HC, have HC := HC η, rw HC at HEQ, assumption
end

lemma closed_b_never_var: ∀ s, ¬ closed_b (sbool.var s)
:= begin
  intros,
  intro H,
  unfold closed_b at H,
  have H' := H (freevar.env.empty.add_b s tt),
  unfold env.add_b at H',
  unfold freevar.env.replace_sb at H',
  simp at H',
  cases H'
end

lemma closed_bv_never_var: ∀ sz s, ¬ closed_bv (sbitvec.var sz s)
:= begin
  intros,
  intro H,
  unfold closed_bv at H,
  have H' := H (freevar.env.empty.add_bv s 1),
  unfold env.add_bv at H',
  unfold freevar.env.replace_sbv at H',
  simp at H',
  cases H'
end

lemma closed_irstate_closed_ub: ∀ {η:freevar.env} {ss:irstate irsem_smt}
    (HC:closed_irstate (η⟦ss⟧)),
  closed_b (η⟦irstate.getub irsem_smt ss⟧)
:= begin
  intros,
  cases ss,
  unfold closed_irstate at HC,
  unfold closed_b,
  intros η',
  have HC := HC η',
  unfold freevar.env.replace at HC,
  rw irstate.getub_apply_to_values at HC,
  unfold irstate.getub at *,
  unfold irstate.setub at HC,
  unfold irstate.apply_to_values at HC,
  injection HC
end

lemma closed_irstate_split: ∀ ub rf,
  closed_irstate (ub, rf) ↔ closed_b ub ∧ closed_regfile rf
:= begin
  intros,
  unfold closed_irstate,
  unfold freevar.env.replace,
  unfold closed_b,
  unfold closed_regfile,
  unfold irstate.getub,
  unfold irstate.setub,
  unfold irstate.apply_to_values,
  simp,
  split,
  {
    intros H,
    split,
    any_goals {
      intros, have H' := H η, injection H'
    }
  },
  {
    intros H,
    cases H,
    intros,
    rw [H_left, H_right]
  }
end

lemma closed_regfile_empty: closed_regfile (regfile.empty irsem_smt)
:= begin
  unfold regfile.empty,
  unfold closed_regfile,
  intros, refl
end

lemma closed_irstate_empty: closed_irstate (irstate.empty irsem_smt)
:= begin
  unfold irstate.empty,
  unfold closed_irstate,
  intros, refl
end

lemma closed_b_var_add: ∀ (η:freevar.env) (n:string) (v:bool) (s:string)
    (HC: closed_b (η⟦sbool.var s⟧)),
  closed_b ((env.add_b η n v)⟦sbool.var s⟧)
:= begin
  unfold freevar.env.replace_sb,
  unfold env.add_b,
  simp,
  intros,
  generalize Hb': (η.b s) = b',
  rw Hb' at *,
  have Heq: decidable (s = n), apply_instance,
  cases Heq,
  { rw if_neg, assumption, assumption },
  {
    rw if_pos,
    unfold env.replace_sb._match_1 at *,
    cases v; unfold closed_b; intros; refl,
    assumption
  }
end

lemma closed_b_var_add2: ∀ (η:freevar.env) (v:bool) (s:string),
  closed_b ((η.add_b s v)⟦sbool.var s⟧)
:= begin
  unfold env.add_b,
  unfold freevar.env.replace_sb,
  simp,
  intros,
  generalize Hb': (η.b s) = b',
  unfold closed_b,
  intros, cases v, refl, refl
end

lemma closed_bv_var_add: ∀ (η:freevar.env) sz (n:string) v (s:string)
    (HC: closed_bv (η⟦sbitvec.var sz s⟧)),
  closed_bv ((env.add_bv η n v)⟦sbitvec.var sz s⟧)
:= begin
  unfold freevar.env.replace_sbv,
  unfold env.add_bv,
  simp,
  intros,
  generalize Hb': (η.b s) = b',
  rw Hb' at *,
  have Heq: decidable (s = n), apply_instance,
  cases Heq,
  { rw if_neg, assumption, assumption },
  {
    rw if_pos,
    unfold env.replace_sbv._match_1 at *,
    unfold closed_bv,
    cases v,
    any_goals {
      unfold sbitvec.of_int; intros;
      unfold freevar.env.replace_sbv
    },
    assumption
  }
end

lemma closed_bv_var_add2: ∀ (η:freevar.env) sz v (s:string),
  closed_bv ((η.add_bv s v)⟦sbitvec.var sz s⟧)
:= begin
  unfold env.add_bv,
  intros,
  generalize Hb': (η.b s) = b',
  unfold closed_bv,
  intros, cases v;
  unfold freevar.env.replace_sbv; simp,
  { unfold sbitvec.of_int, unfold env.replace_sbv },
  { unfold sbitvec.of_int, unfold env.replace_sbv },
end

lemma ival_closed: ∀ sz vn pn (η:freevar.env) z b,
  closed_valty (((η.add_bv vn z).add_b pn b)
    ⟦irsem.valty.ival sz (sbitvec.var sz vn) (sbool.var pn)⟧)
:= begin
  intros,
  unfold closed_valty,
  intros,
  unfold env.add_b,
  unfold env.add_bv,
  simp,
  split,
  {
    cases z; unfold sbitvec.of_int;
    unfold freevar.env.replace_sbv
  },
  {
    rw env.replace_sb_of_bool
  }
end

lemma closed_ival_split: ∀ sz bv p,
  closed_valty (irsem.valty.ival sz bv p) ↔
  closed_b p ∧ closed_bv bv
:= begin
  intros,
  unfold closed_valty,
  split,
  {
    intros H,
    unfold freevar.env.replace_valty at H,
    split,
    {
      unfold closed_b,
      intros,
      have H' := H η,
      injection H'
    },
    {
      unfold closed_bv,
      intros,
      have H' := H η,
      injection H',
      apply eq_of_heq, assumption
    }
  },
  {
    intros H,
    cases H,
    intros,
    unfold freevar.env.replace_valty,
    rw H_left,
    rw H_right
  }
end

lemma closed_regfile_update_split: ∀ {rf:regfile irsem_smt} {n} {v},
  closed_regfile (regfile.update irsem_smt rf n v)
  ↔ closed_regfile rf ∧ closed_valty v
:= begin
  intros,
  unfold regfile.update,
  unfold closed_valty,
  unfold closed_regfile,
  split,
  {
    intros HC,
    unfold regfile.apply_to_values at *,
    simp at HC,
    split,
    { intros, have HC := HC η, injection HC },
    { intros, have HC := HC η, injection HC, injection h_1 }
  },
  {
    intros HC η,
    cases HC with HC1 HC2,
    have HC1 := HC1 η,
    have HC2 := HC2 η,
    unfold regfile.apply_to_values at *,
    simp at *,
    rw HC1, rw HC2
  }
end

-- TODO 1: Merge closed_b_add_b and closed_bv_add_b into a single
-- theorem (I tried merging them
-- but it raised excessive memory consumption error.)
-- TODO 2: closed_b_add_b, closed_b_add_bv, closed_bv_add_b,
-- closed_bv_add_bv are very similar. Is there any good way
-- to merge all of them into single theorem
lemma closed_b_add_b: ∀ {η s} n v
    (HC: closed_b (η⟦s⟧)),
  closed_b ((η.add_b n v)⟦s⟧)
:= begin
  intros,
  revert s,
  apply sbool.induction
      (λ s, closed_b (η⟦s⟧) → closed_b ((η.add_b n v)⟦s⟧))
      (λ {sz} sb, closed_bv (η⟦sb⟧) → closed_bv ((η.add_b n v)⟦sb⟧)),
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  {
    intros,
    apply closed_b_var_add; assumption
  },
  -- I didn't use any_goals because it raises excessive
  -- memory consumptions.
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b1 b2 b3 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b IH H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_b, unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros b IH H0 η',
    unfold freevar.env.replace_sbv,
    done
  },
  {
    intros,
    generalize Hb': (η.bv n_1) = b',
    unfold env.add_b,
    unfold freevar.env.replace_sbv at *,
    rw Hb' at *,
    cases b'; unfold env.replace_sbv._match_1 at *; assumption
  },
  any_goals {
    unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_bv,
    intros sz v sz' IH H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH,
    { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    intros sz sz' v h l IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH2,
    unfold closed_bv at *,
    { intros η'', have H0' := H0 η'', unfold freevar.env.replace_sbv at H0',
      injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros sz b v1 v2 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  }
end

lemma closed_b_add_bv: ∀ {η s} n v
    (HC: closed_b (η⟦s⟧)),
  closed_b ((η.add_bv n v)⟦s⟧)
:= begin
  intros,
  revert s,
  apply sbool.induction
      (λ s, closed_b (η⟦s⟧) → closed_b ((η.add_bv n v)⟦s⟧))
      (λ {sz} sb, closed_bv (η⟦sb⟧) → closed_bv ((η.add_bv n v)⟦sb⟧)),
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  {
    intros b1 b2 IH1 IH2 H η,
    unfold env.add_bv at *,
    unfold closed_b at *,
    unfold freevar.env.replace_sb at *,
    simp at *,
    rw IH1, rw IH2,
    any_goals { split; refl },
    all_goals { intros η', have H' := H η', cases H'; assumption }
  },
  any_goals {
    unfold closed_b,
    intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b1 b2 b3 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b IH H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_b, unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros b IH H0 η',
    unfold freevar.env.replace_sbv,
    done
  },
  {
    intros,
    apply closed_bv_var_add; assumption
  },
  any_goals {
    unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_bv,
    intros sz v sz' IH H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH,
    { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    intros sz sz' v h l IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH2,
    unfold closed_bv at *,
    { intros η'', have H0' := H0 η'', unfold freevar.env.replace_sbv at H0',
      injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros sz b v1 v2 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  }
end

lemma closed_bv_add_b: ∀ {sz} {η} {s:sbitvec sz} n v
    (HC: closed_bv (η⟦s⟧)),
  closed_bv ((η.add_b n v)⟦s⟧)
:= begin
  intros,
  revert s,
  apply sbitvec.induction
      (λ s, closed_b (η⟦s⟧) → closed_b ((η.add_b n v)⟦s⟧))
      (λ {sz} sb, closed_bv (η⟦sb⟧) → closed_bv ((η.add_b n v)⟦sb⟧)),
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  {
    intros,
    apply closed_b_var_add; assumption
  },
  -- I didn't use any_goals because it raises excessive
  -- memory consumptions.
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  { unfold closed_b, intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0, rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b1 b2 b3 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b IH H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_b, unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros b IH H0 η',
    unfold freevar.env.replace_sbv,
    done
  },
  {
    intros,
    generalize Hb': (η.bv n_1) = b',
    unfold env.add_b,
    unfold freevar.env.replace_sbv at *,
    rw Hb' at *,
    cases b'; unfold env.replace_sbv._match_1 at *; assumption
  },
  any_goals {
    unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_bv,
    intros sz v sz' IH H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH,
    { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    intros sz sz' v h l IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH2,
    unfold closed_bv at *,
    { intros η'', have H0' := H0 η'', unfold freevar.env.replace_sbv at H0',
      injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros sz b v1 v2 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  }
end

lemma closed_bv_add_bv: ∀ {sz} {η} {s:sbitvec sz} n v
    (HC: closed_bv (η⟦s⟧)),
  closed_bv ((η.add_bv n v)⟦s⟧)
:= begin
  intros,
  revert s,
  apply sbitvec.induction
      (λ s, closed_b (η⟦s⟧) → closed_b ((η.add_bv n v)⟦s⟧))
      (λ {sz} sb, closed_bv (η⟦sb⟧) → closed_bv ((η.add_bv n v)⟦sb⟧)),
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  { unfold freevar.env.replace_sb,
    intros, assumption },
  {
    intros b1 b2 IH1 IH2 H η,
    unfold env.add_bv at *,
    unfold closed_b at *,
    unfold freevar.env.replace_sb at *,
    simp at *,
    rw IH1, rw IH2,
    any_goals { split; refl },
    all_goals { intros η', have H' := H η', cases H'; assumption }
  },
  any_goals {
    unfold closed_b,
    intros b1 b2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b1 b2 b3 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  {
    unfold closed_b,
    intros b IH H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_b, unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sb,
    unfold freevar.env.replace_sb at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros b IH H0 η',
    unfold freevar.env.replace_sbv,
    done
  },
  {
    intros,
    apply closed_bv_var_add; assumption
  },
  any_goals {
    unfold closed_bv,
    intros sz v1 v2 IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH1, rw IH2,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  },
  any_goals {
    unfold closed_bv,
    intros sz v sz' IH H0 η',
    unfold freevar.env.replace_sbv,
    unfold freevar.env.replace_sbv at H0,
    rw IH,
    { intros η'', have H0' := H0 η'', injection H0', apply eq_of_heq, assumption }
  },
  {
    intros sz sz' v h l IH1 IH2 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH2,
    unfold closed_bv at *,
    { intros η'', have H0' := H0 η'', unfold freevar.env.replace_sbv at H0',
      injection H0', apply eq_of_heq, assumption }
  },
  {
    unfold closed_bv,
    intros sz b v1 v2 IH1 IH2 IH3 H0 η',
    unfold freevar.env.replace_sbv at *,
    rw IH1, rw IH2, rw IH3,
    all_goals { intros η'', have H0' := H0 η'', injection H0', done }
  }
end

end spec


lemma le_refl (x : mynat) : x ≤ x :=
begin
use 0,
refl,
end


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import data.list.chain
import category_theory.punit
import category_theory.groupoid

/-!
# Connected category

Define a connected category as a _nonempty_ category for which every functor
to a discrete category is isomorphic to the constant functor.

NB. Some authors include the empty category as connected, we do not.
We instead are interested in categories with exactly one 'connected
component'.

We give some equivalent definitions:
- A nonempty category for which every functor to a discrete category is
  constant on objects.
  See `any_functor_const_on_obj` and `connected.of_any_functor_const_on_obj`.
- A nonempty category for which every function `F` for which the presence of a
  morphism `f : j₁ ⟶ j₂` implies `F j₁ = F j₂` must be constant everywhere.
  See `constant_of_preserves_morphisms` and `connected.of_constant_of_preserves_morphisms`.
- A nonempty category for which any subset of its elements containing the
  default and closed under morphisms is everything.
  See `induct_on_objects` and `connected.of_induct`.
- A nonempty category for which every object is related under the reflexive
  transitive closure of the relation "there is a morphism in some direction
  from `j₁` to `j₂`".
  See `connected_zigzag` and `zigzag_connected`.
- A nonempty category for which for any two objects there is a sequence of
  morphisms (some reversed) from one to the other.
  See `exists_zigzag'` and `connected_of_zigzag`.

We also prove the result that the functor given by `(X × -)` preserves any
connected limit. That is, any limit of shape `J` where `J` is a connected
category is preserved by the functor `(X × -)`. This appears in `category_theory.limits.connected`.
-/

universes v₁ v₂ u₁ u₂

noncomputable theory

open category_theory.category
open opposite

namespace category_theory

/--
A possibly empty category for which every functor to a discrete category is constant.
-/
class is_preconnected (J : Type u₁) [category.{v₁} J] : Prop :=
(iso_constant : Π {α : Type u₁} (F : J ⥤ discrete α) (j : J),
  nonempty (F ≅ (functor.const J).obj (F.obj j)))

/--
We define a connected category as a _nonempty_ category for which every
functor to a discrete category is constant.

NB. Some authors include the empty category as connected, we do not.
We instead are interested in categories with exactly one 'connected
component'.

This allows us to show that the functor X ⨯ - preserves connected limits.

See https://stacks.math.columbia.edu/tag/002S
-/
class is_connected (J : Type u₁) [category.{v₁} J] extends is_preconnected J : Prop :=
[is_nonempty : nonempty J]

attribute [instance, priority 100] is_connected.is_nonempty

variables {J : Type u₁} [category.{v₁} J]
variables {K : Type u₂} [category.{v₂} K]

/--
If `J` is connected, any functor `F : J ⥤ discrete α` is isomorphic to
the constant functor with value `F.obj j` (for any choice of `j`).
-/
def iso_constant [is_preconnected J] {α : Type u₁} (F : J ⥤ discrete α) (j : J) :
  F ≅ (functor.const J).obj (F.obj j) :=
  (is_preconnected.iso_constant F j).some

/--
If J is connected, any functor to a discrete category is constant on objects.
The converse is given in `is_connected.of_any_functor_const_on_obj`.
-/
lemma any_functor_const_on_obj [is_preconnected J]
  {α : Type u₁} (F : J ⥤ discrete α) (j j' : J) :
  F.obj j = F.obj j' :=
((iso_constant F j').hom.app j).down.1

/--
If any functor to a discrete category is constant on objects, J is connected.
The converse of `any_functor_const_on_obj`.
-/
lemma is_connected.of_any_functor_const_on_obj [nonempty J]
  (h : ∀ {α : Type u₁} (F : J ⥤ discrete α), ∀ (j j' : J), F.obj j = F.obj j') :
  is_connected J :=
{ iso_constant := λ α F j',
  ⟨nat_iso.of_components (λ j, eq_to_iso (h F j j')) (λ _ _ _, subsingleton.elim _ _)⟩ }

/--
If `J` is connected, then given any function `F` such that the presence of a
morphism `j₁ ⟶ j₂` implies `F j₁ = F j₂`, we have that `F` is constant.
This can be thought of as a local-to-global property.

The converse is shown in `is_connected.of_constant_of_preserves_morphisms`
-/
lemma constant_of_preserves_morphisms [is_preconnected J] {α : Type u₁} (F : J → α)
  (h : ∀ (j₁ j₂ : J) (f : j₁ ⟶ j₂), F j₁ = F j₂) (j j' : J) :
  F j = F j' :=
any_functor_const_on_obj { obj := F, map := λ _ _ f, eq_to_hom (h _ _ f) } j j'

/--
`J` is connected if: given any function `F : J → α` which is constant for any
`j₁, j₂` for which there is a morphism `j₁ ⟶ j₂`, then `F` is constant.
This can be thought of as a local-to-global property.

The converse of `constant_of_preserves_morphisms`.
-/
lemma is_connected.of_constant_of_preserves_morphisms [nonempty J]
  (h : ∀ {α : Type u₁} (F : J → α), (∀ {j₁ j₂ : J} (f : j₁ ⟶ j₂), F j₁ = F j₂) →
    (∀ j j' : J, F j = F j')) :
  is_connected J :=
is_connected.of_any_functor_const_on_obj (λ _ F, h F.obj (λ _ _ f, (F.map f).down.1))

/--
An inductive-like property for the objects of a connected category.
If the set `p` is nonempty, and `p` is closed under morphisms of `J`,
then `p` contains all of `J`.

The converse is given in `is_connected.of_induct`.
-/
lemma induct_on_objects [is_preconnected J] (p : set J) {j₀ : J} (h0 : j₀ ∈ p)
  (h1 : ∀ {j₁ j₂ : J} (f : j₁ ⟶ j₂), j₁ ∈ p ↔ j₂ ∈ p) (j : J) :
  j ∈ p :=
begin
  injection (constant_of_preserves_morphisms (λ k, ulift.up (k ∈ p)) (λ j₁ j₂ f, _) j j₀) with i,
  rwa i,
  dsimp,
  exact congr_arg ulift.up (propext (h1 f)),
end

/--
If any maximal connected component containing some element j₀ of J is all of J, then J is connected.

The converse of `induct_on_objects`.
-/
lemma is_connected.of_induct [nonempty J] {j₀ : J}
  (h : ∀ (p : set J), j₀ ∈ p → (∀ {j₁ j₂ : J} (f : j₁ ⟶ j₂), j₁ ∈ p ↔ j₂ ∈ p) → ∀ (j : J), j ∈ p) :
  is_connected J :=
is_connected.of_constant_of_preserves_morphisms (λ α F a,
begin
  have w := h {j | F j = F j₀} rfl (λ _ _ f, by simp [a f]),
  dsimp at w,
  intros j j',
  rw [w j, w j'],
end)

/--
Another induction principle for `is_preconnected J`:
given a type family `Z : J → Sort*` and
a rule for transporting in *both* directions along a morphism in `J`,
we can transport an `x : Z j₀` to a point in `Z j` for any `j`.
-/
lemma is_preconnected_induction [is_preconnected J] (Z : J → Sort*)
  (h₁ : Π {j₁ j₂ : J} (f : j₁ ⟶ j₂), Z j₁ → Z j₂)
  (h₂ : Π {j₁ j₂ : J} (f : j₁ ⟶ j₂), Z j₂ → Z j₁)
  {j₀ : J} (x : Z j₀) (j : J) : nonempty (Z j) :=
(induct_on_objects {j | nonempty (Z j)} ⟨x⟩
  (λ j₁ j₂ f, ⟨by { rintro ⟨y⟩, exact ⟨h₁ f y⟩, }, by { rintro ⟨y⟩, exact ⟨h₂ f y⟩, }⟩) j : _)

/-- If `J` and `K` are equivalent, then if `J` is preconnected then `K` is as well. -/
lemma is_preconnected_of_equivalent {K : Type u₁} [category.{v₂} K] [is_preconnected J]
  (e : J ≌ K) :
  is_preconnected K :=
{ iso_constant := λ α F k, ⟨
  calc F ≅ e.inverse ⋙ e.functor ⋙ F : (e.inv_fun_id_assoc F).symm
     ... ≅ e.inverse ⋙ (functor.const J).obj ((e.functor ⋙ F).obj (e.inverse.obj k)) :
                       iso_whisker_left e.inverse (iso_constant (e.functor ⋙ F) (e.inverse.obj k))

     ... ≅ e.inverse ⋙ (functor.const J).obj (F.obj k) :
          iso_whisker_left _ ((F ⋙ functor.const J).map_iso (e.counit_iso.app k))
     ... ≅ (functor.const K).obj (F.obj k) : nat_iso.of_components (λ X, iso.refl _) (by simp),
  ⟩ }

/-- If `J` and `K` are equivalent, then if `J` is connected then `K` is as well. -/
lemma is_connected_of_equivalent {K : Type u₁} [category.{v₂} K]
  (e : J ≌ K) [is_connected J] :
  is_connected K :=
{ is_nonempty := nonempty.map e.functor.obj (by apply_instance),
  to_is_preconnected := is_preconnected_of_equivalent e }

/-- If `J` is preconnected, then `Jᵒᵖ` is preconnected as well. -/
instance is_preconnected_op [is_preconnected J] : is_preconnected Jᵒᵖ :=
{ iso_constant := λ α F X, ⟨
    nat_iso.of_components
      (λ Y, (nonempty.some $ is_preconnected.iso_constant
        (F.right_op ⋙ (discrete.opposite α).functor) (unop X)).app (unop Y))
      (λ Y Z f, subsingleton.elim _ _)
  ⟩ }

/-- If `J` is connected, then `Jᵒᵖ` is connected as well. -/
instance is_connected_op [is_connected J] : is_connected Jᵒᵖ :=
{ is_nonempty := nonempty.intro (op (classical.arbitrary J)) }

lemma is_preconnected_of_is_preconnected_op [is_preconnected Jᵒᵖ] : is_preconnected J :=
is_preconnected_of_equivalent (op_op_equivalence J)

lemma is_connected_of_is_connected_op [is_connected Jᵒᵖ] : is_connected J :=
is_connected_of_equivalent (op_op_equivalence J)

/-- j₁ and j₂ are related by `zag` if there is a morphism between them. -/
@[reducible]
def zag (j₁ j₂ : J) : Prop := nonempty (j₁ ⟶ j₂) ∨ nonempty (j₂ ⟶ j₁)

lemma zag_symmetric : symmetric (@zag J _) :=
λ j₂ j₁ h, h.swap

/--
`j₁` and `j₂` are related by `zigzag` if there is a chain of
morphisms from `j₁` to `j₂`, with backward morphisms allowed.
-/
@[reducible]
def zigzag : J → J → Prop := relation.refl_trans_gen zag

lemma zigzag_symmetric : symmetric (@zigzag J _) :=
relation.refl_trans_gen.symmetric zag_symmetric

lemma zigzag_equivalence : _root_.equivalence (@zigzag J _) :=
mk_equivalence _
    relation.reflexive_refl_trans_gen
    zigzag_symmetric
    relation.transitive_refl_trans_gen

/--
The setoid given by the equivalence relation `zigzag`. A quotient for this
setoid is a connected component of the category.
-/
def zigzag.setoid (J : Type u₂) [category.{v₁} J] : setoid J :=
{ r := zigzag,
  iseqv := zigzag_equivalence }

/--
If there is a zigzag from `j₁` to `j₂`, then there is a zigzag from `F j₁` to
`F j₂` as long as `F` is a functor.
-/
lemma zigzag_obj_of_zigzag (F : J ⥤ K) {j₁ j₂ : J} (h : zigzag j₁ j₂) :
  zigzag (F.obj j₁) (F.obj j₂) :=
h.lift _ $ λ j k, or.imp (nonempty.map (λ f, F.map f)) (nonempty.map (λ f, F.map f))

-- TODO: figure out the right way to generalise this to `zigzag`.
lemma zag_of_zag_obj (F : J ⥤ K) [full F] {j₁ j₂ : J} (h : zag (F.obj j₁) (F.obj j₂)) :
  zag j₁ j₂ :=
or.imp (nonempty.map F.preimage) (nonempty.map F.preimage) h

/-- Any equivalence relation containing (⟶) holds for all pairs of a connected category. -/
lemma equiv_relation [is_connected J] (r : J → J → Prop) (hr : _root_.equivalence r)
  (h : ∀ {j₁ j₂ : J} (f : j₁ ⟶ j₂), r j₁ j₂) :
  ∀ (j₁ j₂ : J), r j₁ j₂ :=
begin
  have z : ∀ (j : J), r (classical.arbitrary J) j :=
    induct_on_objects (λ k, r (classical.arbitrary J) k)
      (hr.1 (classical.arbitrary J)) (λ _ _ f, ⟨λ t, hr.2.2 t (h f), λ t, hr.2.2 t (hr.2.1 (h f))⟩),
  intros, apply hr.2.2 (hr.2.1 (z _)) (z _)
end

/-- In a connected category, any two objects are related by `zigzag`. -/
lemma is_connected_zigzag [is_connected J] (j₁ j₂ : J) : zigzag j₁ j₂ :=
equiv_relation _ zigzag_equivalence
  (λ _ _ f, relation.refl_trans_gen.single (or.inl (nonempty.intro f))) _ _

/--
If any two objects in an nonempty category are related by `zigzag`, the category is connected.
-/
lemma zigzag_is_connected [nonempty J] (h : ∀ (j₁ j₂ : J), zigzag j₁ j₂) : is_connected J :=
begin
  apply is_connected.of_induct,
  intros p hp hjp j,
  have: ∀ (j₁ j₂ : J), zigzag j₁ j₂ → (j₁ ∈ p ↔ j₂ ∈ p),
  { introv k,
    induction k with _ _ rt_zag zag,
    { refl },
    { rw k_ih,
      rcases zag with ⟨⟨_⟩⟩ | ⟨⟨_⟩⟩,
      apply hjp zag,
      apply (hjp zag).symm } },
  rwa this j (classical.arbitrary J) (h _ _)
end

lemma exists_zigzag' [is_connected J] (j₁ j₂ : J) :
  ∃ l, list.chain zag j₁ l ∧ list.last (j₁ :: l) (list.cons_ne_nil _ _) = j₂ :=
list.exists_chain_of_relation_refl_trans_gen (is_connected_zigzag _ _)

/--
If any two objects in an nonempty category are linked by a sequence of (potentially reversed)
morphisms, then J is connected.

The converse of `exists_zigzag'`.
-/
lemma is_connected_of_zigzag [nonempty J]
  (h : ∀ (j₁ j₂ : J), ∃ l, list.chain zag j₁ l ∧ list.last (j₁ :: l) (list.cons_ne_nil _ _) = j₂) :
  is_connected J :=
begin
  apply zigzag_is_connected,
  intros j₁ j₂,
  rcases h j₁ j₂ with ⟨l, hl₁, hl₂⟩,
  apply list.relation_refl_trans_gen_of_exists_chain l hl₁ hl₂,
end

/-- If `discrete α` is connected, then `α` is (type-)equivalent to `punit`. -/
def discrete_is_connected_equiv_punit {α : Type u₁} [is_connected (discrete α)] : α ≃ punit :=
discrete.equiv_of_equivalence.{u₁ u₁}
  { functor := functor.star α,
    inverse := discrete.functor (λ _, classical.arbitrary _),
    unit_iso := by { exact (iso_constant _ (classical.arbitrary _)), },
    counit_iso := functor.punit_ext _ _ }

variables {C : Type u₂} [category.{u₁} C]

/--
For objects `X Y : C`, any natural transformation `α : const X ⟶ const Y` from a connected
category must be constant.
This is the key property of connected categories which we use to establish properties about limits.
-/

instance [is_connected J] : full (functor.const J : C ⥤ J ⥤ C) :=
{ preimage := λ X Y f, f.app (classical.arbitrary J),
  witness' := λ X Y f,
  begin
    ext j,
    apply nat_trans_from_is_connected f (classical.arbitrary J) j,
  end }

instance nonempty_hom_of_connected_groupoid {G} [groupoid G] [is_connected G] :
  ∀ (x y : G), nonempty (x ⟶ y) :=
begin
  refine equiv_relation _ _ (λ j₁ j₂, nonempty.intro),
  exact ⟨λ j, ⟨𝟙 _⟩, λ j₁ j₂, nonempty.map (λ f, inv f), λ _ _ _, nonempty.map2 (≫)⟩,
end

end category_theory


/-
Instances of `Universe` that correspond to basic Lean types and universes, with structure such
as functors, products, ...

The actual universes are already defined in `Universes.lean` because they are occasionally
referenced without importing this file. They are:
* `sort.{u}  := ⟨Sort u⟩`
* `prop      := sort.{0}`
* `type.{u}  := sort.{u + 1}`
* `tsort.{u} := sort.{max 1 u}`

The structure on all of these universes is "trivial" to varying degrees, compared to what is
allowed in principle. Therefore, in this file there is often an instance of a class that is
defined in `Utils/Trivial.lean`, and which indirectly generates instances of classes in
`Axioms/Universe`.
-/

import UniverseAbstractions.Axioms.Universes
import UniverseAbstractions.Axioms.Universe.Identity
import UniverseAbstractions.Axioms.Universe.Functors
import UniverseAbstractions.Axioms.Universe.FunctorExtensionality
import UniverseAbstractions.Axioms.Universe.Singletons
import UniverseAbstractions.Axioms.Universe.Products
import UniverseAbstractions.Axioms.Universe.Equivalences
import UniverseAbstractions.Axioms.Universe.DependentTypes.Properties
import UniverseAbstractions.Axioms.Universe.DependentTypes.DependentFunctors
import UniverseAbstractions.Axioms.Universe.DependentTypes.DependentProducts
import UniverseAbstractions.Instances.Utils.Trivial

import UniverseAbstractions.MathlibFragments.Init.CoreExt
import UniverseAbstractions.MathlibFragments.Data.Equiv.Basic

set_option autoBoundImplicitLocal false
-- TODO: It looks like there are bad instances.
set_option synthInstance.maxHeartbeats 100000
--set_option pp.universes true

universe u v w w' upv

-- Each Lean universe is also a universe according to our definition. Some definitions work
-- generically for `sort.{u}`, others need to be split between `prop` and `type.{u}`.

namespace sort

  open MetaRelation HasFunctors HasInternalEquivalences HasDependentFunctors

  -- Instance equivalences of all `sort.{u}` are given by equality.
  -- For `prop`, we could define instance equivalences to be in `unit` instead of relying on proof
  -- irrelevance, but it's easier to generalize over `prop` and `type` if we have a single
  -- definition.

  instance hasEquivalenceRelation (α : sort.{u}) : HasEquivalenceRelation α prop :=
  ⟨nativeRelation (@Eq α)⟩

  instance hasInstanceEquivalences : HasInstanceEquivalences sort.{u} prop :=
  ⟨hasEquivalenceRelation⟩

  -- Functors from `sort` to any universe are just functions: Instance equivalence in `sort` is
  -- given by equality, so functors do not need to respect anything else besides equality.

  instance hasOutFunctors (V : Universe.{v}) : HasFunctors sort.{u} V sort.{imax u v} :=
  { Fun   := λ α B => α → B,
    apply := id }

  def defOutFun {α : sort.{u}} {V : Universe.{v}} [HasIdentity V] {B : V} (f : ⌈α ⟶ B⌉) :
    α ⟶{f} B :=
  toDefFun f

  instance hasTrivialOutFunctoriality (V : Universe.{v}) [HasIdentity V] :
    HasTrivialFunctoriality sort.{u} V :=
  ⟨defOutFun⟩

  instance hasCongrArg : HasCongrArg sort.{u} sort.{v} := ⟨λ f => congrArg f⟩

  instance (priority := low) hasOutCongrArg (V : Universe.{v}) [HasIdentity V] :
    HasCongrArg sort.{u} V :=
  ⟨λ {_ _} f {a₁ a₂} h => h ▸ HasInstanceEquivalences.refl (f a₁)⟩

  theorem hasOutCongrArg.reflEq {α : sort.{u}} {V : Universe.{v}} [HasIdentity V] {B : V}
                                (f : ⌈α ⟶ B⌉) (a : α) :
    (hasOutCongrArg V).congrArg f (Eq.refl a) = HasInstanceEquivalences.refl (f a) :=
  rfl

  theorem hasOutCongrArg.symmEq {α : sort.{u}} {V : Universe.{v}} [HasIdentity V] {B : V}
                                (f : ⌈α ⟶ B⌉) {a₁ a₂ : α} (h : a₁ = a₂)
                                (hRefl : ∀ b : B, (HasInstanceEquivalences.refl b)⁻¹ = HasInstanceEquivalences.refl b) :
    (hasOutCongrArg V).congrArg f (Eq.symm h) = ((hasOutCongrArg V).congrArg f h)⁻¹ :=
  by subst h; exact Eq.symm (hRefl (f a₁))

  theorem hasOutCongrArg.transEq {α : sort.{u}} {V : Universe.{v}} [HasIdentity V] {B : V}
                                 (f : ⌈α ⟶ B⌉) {a₁ a₂ a₃ : α} (h : a₁ = a₂) (i : a₂ = a₃)
                                 (hRefl : ∀ b : B, HasInstanceEquivalences.refl b • HasInstanceEquivalences.refl b = HasInstanceEquivalences.refl b) :
    (hasOutCongrArg V).congrArg f (Eq.trans h i) = (hasOutCongrArg V).congrArg f i • (hasOutCongrArg V).congrArg f h :=
  by subst h; subst i; exact Eq.symm (hRefl (f a₁))

  instance hasCongrFun : HasCongrFun sort.{u} sort.{v} := ⟨congrFun⟩

  instance (priority := low) hasOutCongrFun (V : Universe.{v}) [HasIdentity V] :
    HasCongrFun sort.{u} V :=
  ⟨λ h _ => h ▸ HasInstanceEquivalences.refl _⟩

  instance hasInternalFunctors : HasInternalFunctors sort.{u} := ⟨⟩

  instance hasTrivialExtensionality : HasTrivialExtensionality sort.{u} sort.{v} := ⟨funext⟩

  -- Functors into `sort` need to be well-defined.

  structure InFunctor {U : Universe.{u}} [HasIdentity U] (A : U) (B : sort.{v}) :
    Sort (max 1 u v) where
  (f                    : A → B)
  (congrArg {a₁ a₂ : A} : a₁ ≃ a₂ → f a₁ = f a₂)

  instance (priority := low) hasInFunctors (U : Universe.{u}) [HasIdentity U] :
    HasFunctors U sort.{v} sort.{max 1 u v} :=
  { Fun   := InFunctor,
    apply := InFunctor.f }

  instance (priority := low) hasInCongrArg (U : Universe.{u}) [HasIdentity U] :
    HasCongrArg U sort.{v} :=
  ⟨InFunctor.congrArg⟩

  def defInFun {U : Universe.{u}} [HasIdentity U] {A : U} {B : sort.{v}} (F : InFunctor A B) :
    A ⟶{F.f} B :=
  toDefFun' F (λ _ => by rfl)

  instance hasInCompFun (U : Universe.{u}) (V : Universe.{v}) {W : Universe.{w'}} [HasIdentity U]
                        [HasIdentity V] [HasFunctors U V W] [HasCongrArg U V] :
    HasCompFun U V sort.{w} :=
  ⟨λ F G => defInFun ⟨λ a => G.f (F a), λ e => G.congrArg (HasCongrArg.congrArg F e)⟩⟩

  -- There are top and bottom types that work generically for `sort`.

  instance (priority := low) hasTop : HasTop sort.{u} :=
  { T := PUnit,
    t := PUnit.unit }

  instance (priority := low) hasTopEq : HasTop.HasTopEq sort.{u} :=
  ⟨λ _ => rfl⟩

  instance (priority := low) hasBot : HasBot sort.{u} :=
  { B    := PEmpty,
    elim := PEmpty.elim }

  noncomputable def byContradiction (α : sort.{u}) (f : HasInternalBot.Not (HasInternalBot.Not α)) : α :=
  Classical.choice (Classical.byContradiction (λ h => PEmpty.elim (f (λ a => False.elim (h ⟨a⟩)))))

  noncomputable instance (priority := low) hasClassicalLogic : HasClassicalLogic sort.{u} :=
  { byContradictionFun := byContradiction }

  -- Same for products, but usually the specialized versions for `prop` and `type` should be used.

  instance (priority := low) hasProducts : HasProducts sort.{u} sort.{v} tsort.{max u v} :=
  { Prod  := PProd,
    intro := PProd.mk,
    fst   := PProd.fst,
    snd   := PProd.snd }

  instance (priority := low) hasProductEq :
    HasProducts.HasProductEq sort.{u} sort.{v} (UxV := tsort.{max u v}) :=
  { introEq := λ _   => rfl,
    fstEq   := λ _ _ => rfl,
    sndEq   := λ _ _ => rfl }

  -- `Equiv` also works for general `sort`, but is overridden for `prop`.

  def equivDesc {α : sort.{u}} {β : sort.{v}} (e : Equiv α β) : α ⮂ β :=
  { toFun  := e.toFun,
    invFun := e.invFun,
    left   := ⟨e.leftInv⟩,
    right  := ⟨e.rightInv⟩ }

  instance (priority := low) hasEquivalences : HasEquivalences sort.{u} sort.{v} sort.{max 1 u v} :=
  { Equiv := Equiv,
    desc  := equivDesc }

  -- Properties out of `sort` are functorial.

  -- TODO: Replace `subst` tactic with explicit recursor invocation.
  instance (priority := low) hasOutPropCongrArg (V : Universe.{v}) [HasTypeIdentity V] :
    HasPropCongrArg sort.{u} V :=
  { congrArgReflEq  := λ     φ            a   => HasInstanceEquivalences.refl (HasEquivOp.refl (φ a)),
    congrArgSymmEq  := λ {_} φ {a₁ a₂}    h   => by subst h; exact (HasEquivOp.symmRefl (φ a₁))⁻¹,
    congrArgTransEq := λ {_} φ {a₁ a₂ a₃} h i => by subst h; subst i; exact (HasEquivOp.transReflRefl (φ a₁))⁻¹ }

  -- Dependent functors are analogous to independent functors.

  instance hasDependentOutFunctors (V : Universe.{v}) :
    HasDependentFunctors sort.{u} V sort.{imax u v} :=
  { Pi    := HasFunctors.Pi,
    apply := id }

  def defPi {α : sort.{u}} {V : Universe.{v}} [HasTypeIdentity V] {φ : ⌈α ⟶ ⌊V⌋⌉}
            (f : HasFunctors.Pi φ) :
    Π{f} (HasFunctors.toDefFun φ) :=
  toDefPi' f (λ _ => HasInstanceEquivalences.refl _) (λ _ => DependentEquivalence.refl _)

  instance hasTrivialDependentOutFunctoriality (V : Universe.{v}) [HasTypeIdentity V] :
    HasTrivialDependentFunctoriality sort.{u} V :=
  ⟨defPi⟩

  instance hasDependentCongrFun : HasDependentCongrFun sort.{u} sort.{v} := ⟨congrFun⟩

  instance (priority := low) hasDependentOutCongrFun (V : Universe.{v}) [HasIdentity V] :
    HasDependentCongrFun sort.{u} V :=
  ⟨λ e _ => e ▸ HasInstanceEquivalences.refl _⟩

end sort

namespace prop

  open MetaRelation HasFunctors HasEquivOp HasEquivOpFun HasDependentFunctors

  instance hasTrivialIdentity : HasTrivialIdentity prop := ⟨proofIrrel⟩

  -- Mapping into `prop` is expecially simple.

  instance hasInFunctors (U : Universe.{u}) : HasFunctors U prop prop :=
  { Fun   := λ A q => A → q,
    apply := id }

  def defInFun {U : Universe.{u}} {A : U} {q : prop} (f : ⌈A ⟶ q⌉) : A ⟶{f} q :=
  toDefFun f

  instance hasTrivialInFunctoriality (U : Universe.{u}) : HasTrivialFunctoriality U prop :=
  ⟨defInFun⟩

  -- Propositional trunction is functorial.

  def Truncated {U : Universe.{u}} (A : U) : prop := Nonempty A

  theorem trunc {U : Universe.{u}} {A : U} (a : A) : Truncated A := ⟨a⟩
  theorem truncFun {U : Universe.{u}} (A : U) : A ⟶ Truncated A := trunc

  instance trunc.isFunApp {U : Universe.{u}} {A : U} (a : A) : IsFunApp (V := prop) A (trunc a) :=
  { F := truncFun A,
    a := a,
    e := proofIrrel _ _ }

  theorem truncProj {U : Universe.{u}} [HasFunctors U U U] {A B : U} (F : A ⟶ B) :
    Truncated A ⟶ Truncated B :=
  λ ⟨a⟩ => ⟨F a⟩

  theorem truncProjFun {U : Universe.{u}} [HasFunctors U U U] (A B : U) :
    (A ⟶ B) ⟶ (Truncated A ⟶ Truncated B) :=
  truncProj

  instance truncProj.isFunApp {U : Universe.{u}} [HasFunctors U U U] {A B : U} (F : A ⟶ B) :
    IsFunApp (V := prop) (A ⟶ B) (truncProj F) :=
  { F := truncProjFun A B,
    a := F,
    e := proofIrrel _ _ }

  theorem truncProjFun' {U : Universe.{u}} [HasFunctors U U U] (A B : U) :
    Truncated (A ⟶ B) ⟶ (Truncated A ⟶ Truncated B) :=
  λ ⟨F⟩ => truncProj F

  -- In `prop`, `Top` is `True` and `Bot` is `False`.

  instance hasTop : HasTop prop :=
  { T := True,
    t := trivial }

  instance hasBot : HasBot prop :=
  { B    := False,
    elim := False.elim }

  -- `prop` has classical logic if we want.

  instance hasClassicalLogic : HasClassicalLogic prop :=
  { byContradictionFun := @Classical.byContradiction }

  -- Products are given by `And`.

  instance hasProducts : HasProducts prop prop prop :=
  { Prod  := And,
    intro := And.intro,
    fst   := And.left,
    snd   := And.right }

  -- Equivalences are given by `Iff`.

  instance hasEquivalences : HasEquivalences prop prop prop :=
  { Equiv := Iff,
    desc  := λ h => HasTrivialIdentity.equivDesc h.mp h.mpr }

  instance hasTrivialEquivalenceCondition : HasTrivialEquivalenceCondition prop :=
  ⟨λ e => HasTrivialIdentity.defEquiv (Iff.intro e.toFun e.invFun)⟩

  -- Dependent incoming functors are analogous to independent incoming functors.

  instance hasDependentInFunctors (U : Universe.{u}) {UpV : Universe.{upv}} [HasIdentity U]
                                  [HasFunctors U {prop} UpV] :
    HasDependentFunctors U prop prop :=
  { Pi    := λ φ => ∀ a, φ a,
    apply := id }

  def defInPi {U : Universe.{u}} {UpV : Universe.{upv}} [HasIdentity U]
              [HasFunctors U {prop} UpV] {A : U} {φ : A ⟶ ⌊prop⌋} (f : Π φ) :
    Π{f} (HasFunctors.toDefFun φ) :=
  -- `toDefPi` results in Lean bug.
  toDefPi' f (λ a => HasInstanceEquivalences.refl (φ a)) (λ _ => proofIrrel _ _)

  instance hasTrivialDependentInFunctoriality (U : Universe.{u}) {UpV : Universe.{upv}}
                                              [HasIdentity U] [HasFunctors U {prop} UpV] :
    HasTrivialDependentFunctoriality U prop :=
  ⟨defInPi⟩

  -- Dependent products are given by `∃`, requiring choice to obtain a witness unless the witness
  -- is in `prop`.

  instance hasDependentProducts : HasDependentProducts prop prop prop :=
  { Sigma := λ φ => ∃ h₁, φ h₁,
    intro := λ h₁ h₂ => ⟨h₁, h₂⟩,
    fst   := λ ⟨h₁, _⟩ => h₁,
    snd   := λ ⟨_, h₂⟩ => h₂ }

  noncomputable instance (priority := low) hasClassicalDependentProducts :
    HasDependentProducts sort.{u} prop prop :=
  { Sigma := λ φ => ∃ a, φ a,
    intro := λ a h => ⟨a, h⟩,
    fst   := Classical.choose,
    snd   := Classical.choose_spec }

end prop

namespace tsort

  open HasPropCongrArg HasDependentFunctors

  -- `tsort` has internal equivalences given by `Equiv`. An `Equiv` essentially matches our
  -- `EquivDesc`, so we can directly use the equivalence proofs from generic code.

  instance (priority := low) hasInternalEquivalences : HasInternalEquivalences tsort.{u} :=
  HasTrivialExtensionality.hasInternalEquivalences tsort (λ h => Equiv.inj h)

  --instance (priority := low) hasInternalEquivalences : HasInternalEquivalences tsort.{u} :=
  --{ defToFunFun := λ _ _ => HasTrivialFunctoriality.defFun,
  --  isExt       := λ E => HasTrivialExtensionality.equivDescExt tsort.{u} (HasEquivalences.desc E) }

  instance hasTrivialEquivalenceCondition : HasTrivialEquivalenceCondition tsort.{u} :=
  ⟨λ e => ⟨⟨e.toFun, e.invFun, e.left.inv, e.right.inv⟩, rfl, rfl⟩⟩

  -- Dependent incoming functors are analogous to independent incoming functors.

  structure DependentInFunctor {U : Universe.{u}} {UpV : Universe.{upv}} [HasIdentity U]
                               [HasFunctors U {tsort.{v}} UpV] [HasPropCongrArg U tsort.{v}]
                               {A : U} (φ : A ⟶ ⌊tsort.{v}⌋) :
    Sort (max 1 u v) where
  (f                                  : HasFunctors.Pi φ)
  (congrArg {a₁ a₂ : A} (e : a₁ ≃ a₂) : f a₁ ≃[propCongrArg φ e] f a₂)

  instance (priority := low) hasDependentInFunctors (U : Universe.{u}) {UpV : Universe.{upv}}
                                                    [HasIdentity U] [HasFunctors U {tsort.{v}} UpV]
                                                    [HasPropCongrArg U tsort.{v}] :
    HasDependentFunctors U tsort.{v} tsort.{max u v} :=
  { Pi    := DependentInFunctor,
    apply := DependentInFunctor.f }

  instance (priority := low) hasDependentInCongrArg (U : Universe.{u}) {UpV : Universe.{upv}}
                                                    [HasIdentity U] [HasFunctors U {tsort.{v}} UpV]
                                                    [HasPropCongrArg U tsort.{v}] :
    HasDependentCongrArg U tsort.{v} :=
  ⟨DependentInFunctor.congrArg⟩

  def defInPi {U : Universe.{u}} {UpV : Universe.{upv}} [HasIdentity U]
              [HasFunctors U {tsort.{v}} UpV] [HasPropCongrArg U tsort.{v}]
              {A : U} {φ : A ⟶ ⌊tsort.{v}⌋} (F : Π φ) :
    Π{F.f} (HasFunctors.toDefFun φ) :=
  toDefPi' F (λ a => HasInstanceEquivalences.refl (φ a)) (λ _ => by rfl)

  -- Dependent products are given by either `PSigma` or `Subtype`, depending on the
  -- universe levels.

  instance (priority := low) hasDependentProducts :
    HasDependentProducts sort.{u} tsort.{v} tsort.{max u v} :=
  { Sigma := PSigma,
    intro := PSigma.mk,
    fst   := PSigma.fst,
    snd   := PSigma.snd }

  instance (priority := low) hasDependentProductEq :
    HasDependentProducts.HasDependentProductEq sort.{u} tsort.{v} (UxV := tsort.{max u v}) :=
  { introEq := λ _   => rfl,
    fstEq   := λ _ _ => rfl,
    sndEq   := λ _ _ => rfl }

  instance (priority := low) hasSubtypes :
    HasDependentProducts sort.{u} prop tsort.{u} :=
  { Sigma := Subtype,
    intro := Subtype.mk,
    fst   := Subtype.val,
    snd   := Subtype.property }

  instance (priority := low) hasSubtypeEq :
    HasDependentProducts.HasDependentProductEq sort.{u} prop (UxV := tsort.{u}) :=
  { introEq := λ _   => rfl,
    fstEq   := λ _ _ => rfl,
    sndEq   := λ _ _ => HasTrivialIdentity.eq }

end tsort

namespace type

  open MetaRelation

  -- Use specialized types for `type.{0}`.

  instance hasTop : HasTop type.{0} :=
  { T := Unit,
    t := Unit.unit }

  instance hasTopEq : HasTop.HasTopEq type.{0} :=
  ⟨λ _ => rfl⟩

  instance hasBot : HasBot type.{0} :=
  { B    := Empty,
    elim := Empty.elim }

  noncomputable def byContradiction (α : type.{0}) (f : HasInternalBot.Not (HasInternalBot.Not α)) : α :=
  Classical.choice (Classical.byContradiction (λ h => Empty.elim (f (λ a => False.elim (h ⟨a⟩)))))

  noncomputable instance hasClassicalLogic : HasClassicalLogic type.{0} :=
  { byContradictionFun := byContradiction }

  -- Use `Prod` instead of `PProd` where possible.

  instance hasProducts : HasProducts type.{u} type.{v} type.{max u v} :=
  { Prod  := Prod,
    intro := Prod.mk,
    fst   := Prod.fst,
    snd   := Prod.snd }

  instance hasProductEq : HasProducts.HasProductEq type.{u} type.{v} :=
  { introEq := λ _   => rfl,
    fstEq   := λ _ _ => rfl,
    sndEq   := λ _ _ => rfl }

  -- Internal equivalences of `type` are a special case of `tsort`.

  instance hasInternalEquivalences : HasInternalEquivalences type.{u} :=
  tsort.hasInternalEquivalences.{u + 1}

  instance hasTrivialEquivalenceCondition : HasTrivialEquivalenceCondition type.{u} :=
  tsort.hasTrivialEquivalenceCondition.{u + 1}

  -- The target equality of dependent functors contains a cast (from `sort.hasOutCongrArg`),
  -- but we can eliminate it easily.

  instance hasDependentCongrArg : HasDependentCongrArg sort.{u} type.{v} :=
  ⟨λ {_ _} _ {_ _} e => by subst e; rfl⟩

  -- Use `Sigma` instead of `PSigma` where possible.

  instance hasDependentProducts : HasDependentProducts type.{u} type.{v} type.{max u v} :=
  { Sigma := Sigma,
    intro := Sigma.mk,
    fst   := Sigma.fst,
    snd   := Sigma.snd }

  instance hasDependentProductEq :
    HasDependentProducts.HasDependentProductEq type.{u} type.{v} (UxV := type.{max u v}) :=
  { introEq := λ _   => rfl,
    fstEq   := λ _ _ => rfl,
    sndEq   := λ _ _ => rfl }

end type


-- Copyright (c) 2017 Scott Morrison. All rights reserved.
-- Released under Apache 2.0 license as described in the file LICENSE.
-- Authors: Stephen Morgan, Scott Morrison
import .monoids

open categories
open categories.functor
open categories.monoidal_category

namespace categories.internal_objects

universes u v

variables {C : Type u} [𝒞 : monoidal_category.{u v} C]
include 𝒞

def fmod (A : C) [MonoidObject A] := C

open SemigroupObject
open MonoidObject

definition CategoryOfFreeModules (A : C) [MonoidObject A] : category (fmod A) :=
{ Hom := λ X Y : C, X ⟶ (A ⊗ Y),
  identity := λ X : C, (inverse_left_unitor X) ≫ ((ι A) ⊗ (𝟙 X)),
  compose := λ _ _ Z f g, f ≫ ((𝟙 A) ⊗ g) ≫ (inverse_associator A A Z) ≫ ((μ A) ⊗ (𝟙 Z)),
  left_identity := begin
                    -- PROJECT dealing with associativity here is quite tedious.
                    -- PROJECT this is a great example problem for clever automation.
                    -- A human quickly sees that we need to combine A.unit and A.multiplication to make them cancel,
                    -- and then performs the necessary rewrites to get there.
                    intros,
                    conv {
                      to_lhs,
                      rewrite category.associativity,
                      congr, skip,
                      rewrite ← category.associativity,
                      rewrite ← interchange_identities,
                      rewrite category.associativity,
                      congr, skip,
                      rewrite ← category.associativity,
                      rewrite ← tensor_identities,
                      rewrite inverse_associator_naturality_0,
                      rewrite category.associativity,
                      congr, skip,
                      rewrite interchange_left_identity,
                      congr,
                      rewrite [MonoidObject.left_identity] {tactic.rewrite_cfg . md := semireducible},
                    },
                    simp,
                    conv {
                      to_lhs,
                      rewrite ← category.associativity,
                      congr,
                      rewrite [← 𝒞.left_unitor_transformation.inverse.naturality] {tactic.rewrite_cfg . md := semireducible},
                    },
                    simp,
                    dunfold IdentityFunctor, dsimp,
                    -- PROJECT this needs Proposition 2.2.4 of Etingof's "Tensor Categories" to finish; and that seems awkward to prove in our setup!
                    exact sorry
                   end,
  right_identity := sorry,
  associativity := sorry
}

-- PROJECT show that after idempotent completing the category of free modules we get the category of modules??
-- PROJECT bimodules
-- PROJECT commutative algebras; modules give bimodules

end categories.internal_objects


import logic.equiv.basic

lemma equiv.apply {α β : Type*} (f : α ≃ β) (a : α) :
  f a = f.to_fun a := rfl


lemma maze (P Q R S T U: Prop)
(p : P)
(h : P → Q)
(i : Q → R)
(j : Q → T)
(k : S → T)
(l : T → U)
: U :=
begin
have q := h(p),
have t := j(q),
have u := l(t),
exact u,
end


/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.list.infix
! leanprover-community/mathlib commit 00f4ab49e7d5139216e0b3daad15fffa504897ab
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.List.Basic

/-!
# Prefixes, subfixes, infixes

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file proves properties about
* `list.prefix`: `l₁` is a prefix of `l₂` if `l₂` starts with `l₁`.
* `list.subfix`: `l₁` is a subfix of `l₂` if `l₂` ends with `l₁`.
* `list.infix`: `l₁` is an infix of `l₂` if `l₁` is a prefix of some subfix of `l₂`.
* `list.inits`: The list of prefixes of a list.
* `list.tails`: The list of prefixes of a list.
* `insert` on lists

All those (except `insert`) are defined in `data.list.defs`.

## Notation

`l₁ <+: l₂`: `l₁` is a prefix of `l₂`.
`l₁ <:+ l₂`: `l₁` is a subfix of `l₂`.
`l₁ <:+: l₂`: `l₁` is an infix of `l₂`.
-/

open Nat

variable {α β : Type _}

namespace List

variable {l l₁ l₂ l₃ : List α} {a b : α} {m n : ℕ}

/-! ### prefix, suffix, infix -/

section Fix

#print List.prefix_append /-
@[simp]
theorem prefix_append (l₁ l₂ : List α) : l₁ <+: l₁ ++ l₂ :=
  ⟨l₂, rfl⟩
#align list.prefix_append List.prefix_append
-/

#print List.suffix_append /-
@[simp]
theorem suffix_append (l₁ l₂ : List α) : l₂ <:+ l₁ ++ l₂ :=
  ⟨l₁, rfl⟩
#align list.suffix_append List.suffix_append
-/

#print List.infix_append /-
theorem infix_append (l₁ l₂ l₃ : List α) : l₂ <:+: l₁ ++ l₂ ++ l₃ :=
  ⟨l₁, l₃, rfl⟩
#align list.infix_append List.infix_append
-/

#print List.infix_append' /-
@[simp]
theorem infix_append' (l₁ l₂ l₃ : List α) : l₂ <:+: l₁ ++ (l₂ ++ l₃) := by
  rw [← List.append_assoc] <;> apply infix_append
#align list.infix_append' List.infix_append'
-/

#print List.isPrefix.isInfix /-
theorem isPrefix.isInfix : l₁ <+: l₂ → l₁ <:+: l₂ := fun ⟨t, h⟩ => ⟨[], t, h⟩
#align list.is_prefix.is_infix List.isPrefix.isInfix
-/

#print List.isSuffix.isInfix /-
theorem isSuffix.isInfix : l₁ <:+ l₂ → l₁ <:+: l₂ := fun ⟨t, h⟩ => ⟨t, [], by rw [h, append_nil]⟩
#align list.is_suffix.is_infix List.isSuffix.isInfix
-/

#print List.nil_prefix /-
theorem nil_prefix (l : List α) : [] <+: l :=
  ⟨l, rfl⟩
#align list.nil_prefix List.nil_prefix
-/

#print List.nil_suffix /-
theorem nil_suffix (l : List α) : [] <:+ l :=
  ⟨l, append_nil _⟩
#align list.nil_suffix List.nil_suffix
-/

#print List.nil_infix /-
theorem nil_infix (l : List α) : [] <:+: l :=
  (nil_prefix _).isInfix
#align list.nil_infix List.nil_infix
-/

#print List.prefix_refl /-
@[refl]
theorem prefix_refl (l : List α) : l <+: l :=
  ⟨[], append_nil _⟩
#align list.prefix_refl List.prefix_refl
-/

#print List.suffix_refl /-
@[refl]
theorem suffix_refl (l : List α) : l <:+ l :=
  ⟨[], rfl⟩
#align list.suffix_refl List.suffix_refl
-/

#print List.infix_refl /-
@[refl]
theorem infix_refl (l : List α) : l <:+: l :=
  (prefix_refl l).isInfix
#align list.infix_refl List.infix_refl
-/

#print List.prefix_rfl /-
theorem prefix_rfl : l <+: l :=
  prefix_refl _
#align list.prefix_rfl List.prefix_rfl
-/

#print List.suffix_rfl /-
theorem suffix_rfl : l <:+ l :=
  suffix_refl _
#align list.suffix_rfl List.suffix_rfl
-/

#print List.infix_rfl /-
theorem infix_rfl : l <:+: l :=
  infix_refl _
#align list.infix_rfl List.infix_rfl
-/

#print List.suffix_cons /-
@[simp]
theorem suffix_cons (a : α) : ∀ l, l <:+ a :: l :=
  suffix_append [a]
#align list.suffix_cons List.suffix_cons
-/

#print List.prefix_concat /-
theorem prefix_concat (a : α) (l) : l <+: concat l a := by simp
#align list.prefix_concat List.prefix_concat
-/

#print List.infix_cons /-
theorem infix_cons : l₁ <:+: l₂ → l₁ <:+: a :: l₂ := fun ⟨L₁, L₂, h⟩ => ⟨a :: L₁, L₂, h ▸ rfl⟩
#align list.infix_cons List.infix_cons
-/

#print List.infix_concat /-
theorem infix_concat : l₁ <:+: l₂ → l₁ <:+: concat l₂ a := fun ⟨L₁, L₂, h⟩ =>
  ⟨L₁, concat L₂ a, by simp_rw [← h, concat_eq_append, append_assoc]⟩
#align list.infix_concat List.infix_concat
-/

#print List.isPrefix.trans /-
@[trans]
theorem isPrefix.trans : ∀ {l₁ l₂ l₃ : List α}, l₁ <+: l₂ → l₂ <+: l₃ → l₁ <+: l₃
  | l, _, _, ⟨r₁, rfl⟩, ⟨r₂, rfl⟩ => ⟨r₁ ++ r₂, (append_assoc _ _ _).symm⟩
#align list.is_prefix.trans List.isPrefix.trans
-/

#print List.isSuffix.trans /-
@[trans]
theorem isSuffix.trans : ∀ {l₁ l₂ l₃ : List α}, l₁ <:+ l₂ → l₂ <:+ l₃ → l₁ <:+ l₃
  | l, _, _, ⟨l₁, rfl⟩, ⟨l₂, rfl⟩ => ⟨l₂ ++ l₁, append_assoc _ _ _⟩
#align list.is_suffix.trans List.isSuffix.trans
-/

#print List.isInfix.trans /-
@[trans]
theorem isInfix.trans : ∀ {l₁ l₂ l₃ : List α}, l₁ <:+: l₂ → l₂ <:+: l₃ → l₁ <:+: l₃
  | l, _, _, ⟨l₁, r₁, rfl⟩, ⟨l₂, r₂, rfl⟩ => ⟨l₂ ++ l₁, r₁ ++ r₂, by simp only [append_assoc]⟩
#align list.is_infix.trans List.isInfix.trans
-/

#print List.isInfix.sublist /-
protected theorem isInfix.sublist : l₁ <:+: l₂ → l₁ <+ l₂ := fun ⟨s, t, h⟩ =>
  by
  rw [← h]
  exact (sublist_append_right _ _).trans (sublist_append_left _ _)
#align list.is_infix.sublist List.isInfix.sublist
-/

#print List.isInfix.subset /-
protected theorem isInfix.subset (hl : l₁ <:+: l₂) : l₁ ⊆ l₂ :=
  hl.Sublist.Subset
#align list.is_infix.subset List.isInfix.subset
-/

#print List.isPrefix.sublist /-
protected theorem isPrefix.sublist (h : l₁ <+: l₂) : l₁ <+ l₂ :=
  h.isInfix.Sublist
#align list.is_prefix.sublist List.isPrefix.sublist
-/

#print List.isPrefix.subset /-
protected theorem isPrefix.subset (hl : l₁ <+: l₂) : l₁ ⊆ l₂ :=
  hl.Sublist.Subset
#align list.is_prefix.subset List.isPrefix.subset
-/

#print List.isSuffix.sublist /-
protected theorem isSuffix.sublist (h : l₁ <:+ l₂) : l₁ <+ l₂ :=
  h.isInfix.Sublist
#align list.is_suffix.sublist List.isSuffix.sublist
-/

#print List.isSuffix.subset /-
protected theorem isSuffix.subset (hl : l₁ <:+ l₂) : l₁ ⊆ l₂ :=
  hl.Sublist.Subset
#align list.is_suffix.subset List.isSuffix.subset
-/

#print List.reverse_suffix /-
@[simp]
theorem reverse_suffix : reverse l₁ <:+ reverse l₂ ↔ l₁ <+: l₂ :=
  ⟨fun ⟨r, e⟩ => ⟨reverse r, by rw [← reverse_reverse l₁, ← reverse_append, e, reverse_reverse]⟩,
    fun ⟨r, e⟩ => ⟨reverse r, by rw [← reverse_append, e]⟩⟩
#align list.reverse_suffix List.reverse_suffix
-/

#print List.reverse_prefix /-
@[simp]
theorem reverse_prefix : reverse l₁ <+: reverse l₂ ↔ l₁ <:+ l₂ := by
  rw [← reverse_suffix] <;> simp only [reverse_reverse]
#align list.reverse_prefix List.reverse_prefix
-/

#print List.reverse_infix /-
@[simp]
theorem reverse_infix : reverse l₁ <:+: reverse l₂ ↔ l₁ <:+: l₂ :=
  ⟨fun ⟨s, t, e⟩ =>
    ⟨reverse t, reverse s, by
      rw [← reverse_reverse l₁, append_assoc, ← reverse_append, ← reverse_append, e,
        reverse_reverse]⟩,
    fun ⟨s, t, e⟩ =>
    ⟨reverse t, reverse s, by rw [append_assoc, ← reverse_append, ← reverse_append, e]⟩⟩
#align list.reverse_infix List.reverse_infix
-/

alias reverse_prefix ↔ _ is_suffix.reverse
#align list.is_suffix.reverse List.isSuffix.reverse

alias reverse_suffix ↔ _ is_prefix.reverse
#align list.is_prefix.reverse List.isPrefix.reverse

alias reverse_infix ↔ _ is_infix.reverse
#align list.is_infix.reverse List.isInfix.reverse

#print List.isInfix.length_le /-
theorem isInfix.length_le (h : l₁ <:+: l₂) : l₁.length ≤ l₂.length :=
  h.Sublist.length_le
#align list.is_infix.length_le List.isInfix.length_le
-/

#print List.isPrefix.length_le /-
theorem isPrefix.length_le (h : l₁ <+: l₂) : l₁.length ≤ l₂.length :=
  h.Sublist.length_le
#align list.is_prefix.length_le List.isPrefix.length_le
-/

#print List.isSuffix.length_le /-
theorem isSuffix.length_le (h : l₁ <:+ l₂) : l₁.length ≤ l₂.length :=
  h.Sublist.length_le
#align list.is_suffix.length_le List.isSuffix.length_le
-/

#print List.eq_nil_of_infix_nil /-
theorem eq_nil_of_infix_nil (h : l <:+: []) : l = [] :=
  eq_nil_of_sublist_nil h.Sublist
#align list.eq_nil_of_infix_nil List.eq_nil_of_infix_nil
-/

#print List.infix_nil_iff /-
@[simp]
theorem infix_nil_iff : l <:+: [] ↔ l = [] :=
  ⟨fun h => eq_nil_of_sublist_nil h.Sublist, fun h => h ▸ infix_rfl⟩
#align list.infix_nil_iff List.infix_nil_iff
-/

alias infix_nil_iff ↔ eq_nil_of_infix_nil _
#align list.eq_nil_of_infix_nil List.eq_nil_of_infix_nil

#print List.prefix_nil_iff /-
@[simp]
theorem prefix_nil_iff : l <+: [] ↔ l = [] :=
  ⟨fun h => eq_nil_of_infix_nil h.isInfix, fun h => h ▸ prefix_rfl⟩
#align list.prefix_nil_iff List.prefix_nil_iff
-/

#print List.suffix_nil_iff /-
@[simp]
theorem suffix_nil_iff : l <:+ [] ↔ l = [] :=
  ⟨fun h => eq_nil_of_infix_nil h.isInfix, fun h => h ▸ suffix_rfl⟩
#align list.suffix_nil_iff List.suffix_nil_iff
-/

alias prefix_nil_iff ↔ eq_nil_of_prefix_nil _
#align list.eq_nil_of_prefix_nil List.eq_nil_of_prefix_nil

alias suffix_nil_iff ↔ eq_nil_of_suffix_nil _
#align list.eq_nil_of_suffix_nil List.eq_nil_of_suffix_nil

#print List.infix_iff_prefix_suffix /-
theorem infix_iff_prefix_suffix (l₁ l₂ : List α) : l₁ <:+: l₂ ↔ ∃ t, l₁ <+: t ∧ t <:+ l₂ :=
  ⟨fun ⟨s, t, e⟩ => ⟨l₁ ++ t, ⟨_, rfl⟩, by rw [← e, append_assoc] <;> exact ⟨_, rfl⟩⟩,
    fun ⟨_, ⟨t, rfl⟩, s, e⟩ => ⟨s, t, by rw [append_assoc] <;> exact e⟩⟩
#align list.infix_iff_prefix_suffix List.infix_iff_prefix_suffix
-/

#print List.eq_of_infix_of_length_eq /-
theorem eq_of_infix_of_length_eq (h : l₁ <:+: l₂) : l₁.length = l₂.length → l₁ = l₂ :=
  h.Sublist.eq_of_length
#align list.eq_of_infix_of_length_eq List.eq_of_infix_of_length_eq
-/

#print List.eq_of_prefix_of_length_eq /-
theorem eq_of_prefix_of_length_eq (h : l₁ <+: l₂) : l₁.length = l₂.length → l₁ = l₂ :=
  h.Sublist.eq_of_length
#align list.eq_of_prefix_of_length_eq List.eq_of_prefix_of_length_eq
-/

#print List.eq_of_suffix_of_length_eq /-
theorem eq_of_suffix_of_length_eq (h : l₁ <:+ l₂) : l₁.length = l₂.length → l₁ = l₂ :=
  h.Sublist.eq_of_length
#align list.eq_of_suffix_of_length_eq List.eq_of_suffix_of_length_eq
-/

#print List.prefix_of_prefix_length_le /-
theorem prefix_of_prefix_length_le :
    ∀ {l₁ l₂ l₃ : List α}, l₁ <+: l₃ → l₂ <+: l₃ → length l₁ ≤ length l₂ → l₁ <+: l₂
  | [], l₂, l₃, h₁, h₂, _ => nil_prefix _
  | a :: l₁, b :: l₂, _, ⟨r₁, rfl⟩, ⟨r₂, e⟩, ll =>
    by
    injection e with _ e'; subst b
    rcases prefix_of_prefix_length_le ⟨_, rfl⟩ ⟨_, e'⟩ (le_of_succ_le_succ ll) with ⟨r₃, rfl⟩
    exact ⟨r₃, rfl⟩
#align list.prefix_of_prefix_length_le List.prefix_of_prefix_length_le
-/

#print List.prefix_or_prefix_of_prefix /-
theorem prefix_or_prefix_of_prefix (h₁ : l₁ <+: l₃) (h₂ : l₂ <+: l₃) : l₁ <+: l₂ ∨ l₂ <+: l₁ :=
  (le_total (length l₁) (length l₂)).imp (prefix_of_prefix_length_le h₁ h₂)
    (prefix_of_prefix_length_le h₂ h₁)
#align list.prefix_or_prefix_of_prefix List.prefix_or_prefix_of_prefix
-/

#print List.suffix_of_suffix_length_le /-
theorem suffix_of_suffix_length_le (h₁ : l₁ <:+ l₃) (h₂ : l₂ <:+ l₃) (ll : length l₁ ≤ length l₂) :
    l₁ <:+ l₂ :=
  reverse_prefix.1 <|
    prefix_of_prefix_length_le (reverse_prefix.2 h₁) (reverse_prefix.2 h₂) (by simp [ll])
#align list.suffix_of_suffix_length_le List.suffix_of_suffix_length_le
-/

#print List.suffix_or_suffix_of_suffix /-
theorem suffix_or_suffix_of_suffix (h₁ : l₁ <:+ l₃) (h₂ : l₂ <:+ l₃) : l₁ <:+ l₂ ∨ l₂ <:+ l₁ :=
  (prefix_or_prefix_of_prefix (reverse_prefix.2 h₁) (reverse_prefix.2 h₂)).imp reverse_prefix.1
    reverse_prefix.1
#align list.suffix_or_suffix_of_suffix List.suffix_or_suffix_of_suffix
-/

#print List.suffix_cons_iff /-
theorem suffix_cons_iff : l₁ <:+ a :: l₂ ↔ l₁ = a :: l₂ ∨ l₁ <:+ l₂ :=
  by
  constructor
  · rintro ⟨⟨hd, tl⟩, hl₃⟩
    · exact Or.inl hl₃
    · simp only [cons_append] at hl₃
      exact Or.inr ⟨_, hl₃.2⟩
  · rintro (rfl | hl₁)
    · exact (a :: l₂).suffix_refl
    · exact hl₁.trans (l₂.suffix_cons _)
#align list.suffix_cons_iff List.suffix_cons_iff
-/

#print List.infix_cons_iff /-
theorem infix_cons_iff : l₁ <:+: a :: l₂ ↔ l₁ <+: a :: l₂ ∨ l₁ <:+: l₂ :=
  by
  constructor
  · rintro ⟨⟨hd, tl⟩, t, hl₃⟩
    · exact Or.inl ⟨t, hl₃⟩
    · simp only [cons_append] at hl₃
      exact Or.inr ⟨_, t, hl₃.2⟩
  · rintro (h | hl₁)
    · exact h.is_infix
    · exact infix_cons hl₁
#align list.infix_cons_iff List.infix_cons_iff
-/

#print List.infix_of_mem_join /-
theorem infix_of_mem_join : ∀ {L : List (List α)}, l ∈ L → l <:+: join L
  | _ :: L, Or.inl rfl => infix_append [] _ _
  | l' :: L, Or.inr h => isInfix.trans (infix_of_mem_join h) <| (suffix_append _ _).isInfix
#align list.infix_of_mem_join List.infix_of_mem_join
-/

#print List.prefix_append_right_inj /-
theorem prefix_append_right_inj (l) : l ++ l₁ <+: l ++ l₂ ↔ l₁ <+: l₂ :=
  exists_congr fun r => by rw [append_assoc, append_right_inj]
#align list.prefix_append_right_inj List.prefix_append_right_inj
-/

#print List.prefix_cons_inj /-
theorem prefix_cons_inj (a) : a :: l₁ <+: a :: l₂ ↔ l₁ <+: l₂ :=
  prefix_append_right_inj [a]
#align list.prefix_cons_inj List.prefix_cons_inj
-/

#print List.take_prefix /-
theorem take_prefix (n) (l : List α) : take n l <+: l :=
  ⟨_, take_append_drop _ _⟩
#align list.take_prefix List.take_prefix
-/

#print List.drop_suffix /-
theorem drop_suffix (n) (l : List α) : drop n l <:+ l :=
  ⟨_, take_append_drop _ _⟩
#align list.drop_suffix List.drop_suffix
-/

#print List.take_sublist /-
theorem take_sublist (n) (l : List α) : take n l <+ l :=
  (take_prefix n l).Sublist
#align list.take_sublist List.take_sublist
-/

#print List.drop_sublist /-
theorem drop_sublist (n) (l : List α) : drop n l <+ l :=
  (drop_suffix n l).Sublist
#align list.drop_sublist List.drop_sublist
-/

#print List.take_subset /-
theorem take_subset (n) (l : List α) : take n l ⊆ l :=
  (take_sublist n l).Subset
#align list.take_subset List.take_subset
-/

#print List.drop_subset /-
theorem drop_subset (n) (l : List α) : drop n l ⊆ l :=
  (drop_sublist n l).Subset
#align list.drop_subset List.drop_subset
-/

#print List.mem_of_mem_take /-
theorem mem_of_mem_take (h : a ∈ l.take n) : a ∈ l :=
  take_subset n l h
#align list.mem_of_mem_take List.mem_of_mem_take
-/

/- warning: list.mem_of_mem_drop -> List.mem_of_mem_drop is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} {n : Nat}, (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a (List.drop.{u1} α n l)) -> (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l)
but is expected to have type
  forall {α : Type.{u1}} {l : α} {a : Nat} {n : List.{u1} α}, (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) l (List.drop.{u1} α a n)) -> (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) l n)
Case conversion may be inaccurate. Consider using '#align list.mem_of_mem_drop List.mem_of_mem_dropₓ'. -/
theorem mem_of_mem_drop (h : a ∈ l.drop n) : a ∈ l :=
  drop_subset n l h
#align list.mem_of_mem_drop List.mem_of_mem_drop

#print List.dropSlice_sublist /-
theorem dropSlice_sublist (n m : ℕ) (l : List α) : l.slice n m <+ l :=
  by
  rw [List.dropSlice_eq]
  conv_rhs => rw [← List.take_append_drop n l]
  rw [List.append_sublist_append_left, add_comm, List.drop_add]
  exact List.drop_sublist _ _
#align list.slice_sublist List.dropSlice_sublist
-/

#print List.dropSlice_subset /-
theorem dropSlice_subset (n m : ℕ) (l : List α) : l.slice n m ⊆ l :=
  (dropSlice_sublist n m l).Subset
#align list.slice_subset List.dropSlice_subset
-/

#print List.mem_of_mem_dropSlice /-
theorem mem_of_mem_dropSlice {n m : ℕ} {l : List α} {a : α} (h : a ∈ l.slice n m) : a ∈ l :=
  dropSlice_subset n m l h
#align list.mem_of_mem_slice List.mem_of_mem_dropSlice
-/

/- warning: list.take_while_prefix -> List.takeWhile_prefix is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], List.isPrefix.{u1} α (List.takeWhile.{u1} α p (fun (a : α) => _inst_1 a) l) l
but is expected to have type
  forall {α : Type.{u1}} {l : List.{u1} α} (p : α -> Bool), List.isPrefix.{u1} α (List.takeWhile.{u1} α p l) l
Case conversion may be inaccurate. Consider using '#align list.take_while_prefix List.takeWhile_prefixₓ'. -/
theorem takeWhile_prefix (p : α → Prop) [DecidablePred p] : l.takeWhile p <+: l :=
  ⟨l.dropWhileₓ p, takeWhile_append_drop p l⟩
#align list.take_while_prefix List.takeWhile_prefix

/- warning: list.drop_while_suffix -> List.dropWhile_suffix is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p], List.isSuffix.{u1} α (List.dropWhileₓ.{u1} α p (fun (a : α) => _inst_1 a) l) l
but is expected to have type
  forall {α : Type.{u1}} {l : List.{u1} α} (p : α -> Bool), List.isSuffix.{u1} α (List.dropWhile.{u1} α p l) l
Case conversion may be inaccurate. Consider using '#align list.drop_while_suffix List.dropWhile_suffixₓ'. -/
theorem dropWhile_suffix (p : α → Prop) [DecidablePred p] : l.dropWhileₓ p <:+ l :=
  ⟨l.takeWhile p, takeWhile_append_drop p l⟩
#align list.drop_while_suffix List.dropWhile_suffix

#print List.dropLast_prefix /-
theorem dropLast_prefix : ∀ l : List α, l.dropLast <+: l
  | [] => ⟨nil, by rw [init, List.append_nil]⟩
  | a :: l => ⟨_, dropLast_append_getLast (cons_ne_nil a l)⟩
#align list.init_prefix List.dropLast_prefix
-/

#print List.tail_suffix /-
theorem tail_suffix (l : List α) : tail l <:+ l := by rw [← drop_one] <;> apply drop_suffix
#align list.tail_suffix List.tail_suffix
-/

#print List.dropLast_sublist /-
theorem dropLast_sublist (l : List α) : l.dropLast <+ l :=
  (dropLast_prefix l).Sublist
#align list.init_sublist List.dropLast_sublist
-/

#print List.tail_sublist /-
theorem tail_sublist (l : List α) : l.tail <+ l :=
  (tail_suffix l).Sublist
#align list.tail_sublist List.tail_sublist
-/

#print List.dropLast_subset /-
theorem dropLast_subset (l : List α) : l.dropLast ⊆ l :=
  (dropLast_sublist l).Subset
#align list.init_subset List.dropLast_subset
-/

#print List.tail_subset /-
theorem tail_subset (l : List α) : tail l ⊆ l :=
  (tail_sublist l).Subset
#align list.tail_subset List.tail_subset
-/

#print List.mem_of_mem_dropLast /-
theorem mem_of_mem_dropLast (h : a ∈ l.dropLast) : a ∈ l :=
  dropLast_subset l h
#align list.mem_of_mem_init List.mem_of_mem_dropLast
-/

#print List.mem_of_mem_tail /-
theorem mem_of_mem_tail (h : a ∈ l.tail) : a ∈ l :=
  tail_subset l h
#align list.mem_of_mem_tail List.mem_of_mem_tail
-/

#print List.prefix_iff_eq_append /-
theorem prefix_iff_eq_append : l₁ <+: l₂ ↔ l₁ ++ drop (length l₁) l₂ = l₂ :=
  ⟨by rintro ⟨r, rfl⟩ <;> rw [drop_left], fun e => ⟨_, e⟩⟩
#align list.prefix_iff_eq_append List.prefix_iff_eq_append
-/

#print List.suffix_iff_eq_append /-
theorem suffix_iff_eq_append : l₁ <:+ l₂ ↔ take (length l₂ - length l₁) l₂ ++ l₁ = l₂ :=
  ⟨by rintro ⟨r, rfl⟩ <;> simp only [length_append, add_tsub_cancel_right, take_left], fun e =>
    ⟨_, e⟩⟩
#align list.suffix_iff_eq_append List.suffix_iff_eq_append
-/

#print List.prefix_iff_eq_take /-
theorem prefix_iff_eq_take : l₁ <+: l₂ ↔ l₁ = take (length l₁) l₂ :=
  ⟨fun h => append_right_cancel <| (prefix_iff_eq_append.1 h).trans (take_append_drop _ _).symm,
    fun e => e.symm ▸ take_prefix _ _⟩
#align list.prefix_iff_eq_take List.prefix_iff_eq_take
-/

#print List.suffix_iff_eq_drop /-
theorem suffix_iff_eq_drop : l₁ <:+ l₂ ↔ l₁ = drop (length l₂ - length l₁) l₂ :=
  ⟨fun h => append_left_cancel <| (suffix_iff_eq_append.1 h).trans (take_append_drop _ _).symm,
    fun e => e.symm ▸ drop_suffix _ _⟩
#align list.suffix_iff_eq_drop List.suffix_iff_eq_drop
-/

#print List.decidablePrefix /-
instance decidablePrefix [DecidableEq α] : ∀ l₁ l₂ : List α, Decidable (l₁ <+: l₂)
  | [], l₂ => isTrue ⟨l₂, rfl⟩
  | a :: l₁, [] => isFalse fun ⟨t, te⟩ => List.noConfusion te
  | a :: l₁, b :: l₂ =>
    if h : a = b then
      decidable_of_decidable_of_iff (decidable_prefix l₁ l₂) (by rw [← h, prefix_cons_inj])
    else isFalse fun ⟨t, te⟩ => h <| by injection te
#align list.decidable_prefix List.decidablePrefix
-/

#print List.decidableSuffix /-
-- Alternatively, use mem_tails
instance decidableSuffix [DecidableEq α] : ∀ l₁ l₂ : List α, Decidable (l₁ <:+ l₂)
  | [], l₂ => isTrue ⟨l₂, append_nil _⟩
  | a :: l₁, [] => isFalse <| mt (Sublist.length_le ∘ isSuffix.sublist) (by decide)
  | l₁, b :: l₂ =>
    decidable_of_decidable_of_iff (@Or.decidable _ _ _ (l₁.decidableSuffix l₂)) suffix_cons_iff.symm
#align list.decidable_suffix List.decidableSuffix
-/

#print List.decidableInfix /-
instance decidableInfix [DecidableEq α] : ∀ l₁ l₂ : List α, Decidable (l₁ <:+: l₂)
  | [], l₂ => isTrue ⟨[], l₂, rfl⟩
  | a :: l₁, [] => isFalse fun ⟨s, t, te⟩ => by simp at te <;> exact te
  | l₁, b :: l₂ =>
    decidable_of_decidable_of_iff
      (@Or.decidable _ _ (l₁.decidablePrefix (b :: l₂)) (l₁.decidableInfix l₂)) infix_cons_iff.symm
#align list.decidable_infix List.decidableInfix
-/

/- ./././Mathport/Syntax/Translate/Tactic/Lean3.lean:564:6: unsupported: specialize @hyp -/
#print List.prefix_take_le_iff /-
theorem prefix_take_le_iff {L : List (List (Option α))} (hm : m < L.length) :
    L.take m <+: L.take n ↔ m ≤ n :=
  by
  simp only [prefix_iff_eq_take, length_take]
  induction' m with m IH generalizing L n
  · simp only [min_eq_left, eq_self_iff_true, Nat.zero_le, take]
  cases' L with l ls
  · exact (not_lt_bot hm).elim
  cases n
  · refine' iff_of_false _ (zero_lt_succ _).not_le
    rw [take_zero, take_nil]
    simp only [take]
    exact not_false
  · simp only [length] at hm
    specialize IH ls n (Nat.lt_of_succ_lt_succ hm)
    simp only [le_of_lt (Nat.lt_of_succ_lt_succ hm), min_eq_left] at IH
    simp only [le_of_lt hm, IH, true_and_iff, min_eq_left, eq_self_iff_true, length, take]
    exact ⟨Nat.succ_le_succ, Nat.le_of_succ_le_succ⟩
#align list.prefix_take_le_iff List.prefix_take_le_iff
-/

#print List.cons_prefix_iff /-
theorem cons_prefix_iff : a :: l₁ <+: b :: l₂ ↔ a = b ∧ l₁ <+: l₂ :=
  by
  constructor
  · rintro ⟨L, hL⟩
    simp only [cons_append] at hL
    exact ⟨hL.left, ⟨L, hL.right⟩⟩
  · rintro ⟨rfl, h⟩
    rwa [prefix_cons_inj]
#align list.cons_prefix_iff List.cons_prefix_iff
-/

/- warning: list.is_prefix.map -> List.isPrefix.map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {l₁ : List.{u1} α} {l₂ : List.{u1} α}, (List.isPrefix.{u1} α l₁ l₂) -> (forall (f : α -> β), List.isPrefix.{u2} β (List.map.{u1, u2} α β f l₁) (List.map.{u1, u2} α β f l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {l₁ : List.{u2} α} {l₂ : List.{u2} α}, (List.isPrefix.{u2} α l₁ l₂) -> (forall (f : α -> β), List.isPrefix.{u1} β (List.map.{u2, u1} α β f l₁) (List.map.{u2, u1} α β f l₂))
Case conversion may be inaccurate. Consider using '#align list.is_prefix.map List.isPrefix.mapₓ'. -/
theorem isPrefix.map (h : l₁ <+: l₂) (f : α → β) : l₁.map f <+: l₂.map f :=
  by
  induction' l₁ with hd tl hl generalizing l₂
  · simp only [nil_prefix, map_nil]
  · cases' l₂ with hd₂ tl₂
    · simpa only using eq_nil_of_prefix_nil h
    · rw [cons_prefix_iff] at h
      simp only [h, prefix_cons_inj, hl, map]
#align list.is_prefix.map List.isPrefix.map

/- warning: list.is_prefix.filter_map -> List.isPrefix.filter_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {l₁ : List.{u1} α} {l₂ : List.{u1} α}, (List.isPrefix.{u1} α l₁ l₂) -> (forall (f : α -> (Option.{u2} β)), List.isPrefix.{u2} β (List.filterMap.{u1, u2} α β f l₁) (List.filterMap.{u1, u2} α β f l₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {l₁ : List.{u2} α} {l₂ : List.{u2} α}, (List.isPrefix.{u2} α l₁ l₂) -> (forall (f : α -> (Option.{u1} β)), List.isPrefix.{u1} β (List.filterMap.{u2, u1} α β f l₁) (List.filterMap.{u2, u1} α β f l₂))
Case conversion may be inaccurate. Consider using '#align list.is_prefix.filter_map List.isPrefix.filter_mapₓ'. -/
theorem isPrefix.filter_map (h : l₁ <+: l₂) (f : α → Option β) :
    l₁.filterMap f <+: l₂.filterMap f :=
  by
  induction' l₁ with hd₁ tl₁ hl generalizing l₂
  · simp only [nil_prefix, filter_map_nil]
  · cases' l₂ with hd₂ tl₂
    · simpa only using eq_nil_of_prefix_nil h
    · rw [cons_prefix_iff] at h
      rw [← @singleton_append _ hd₁ _, ← @singleton_append _ hd₂ _, filter_map_append,
        filter_map_append, h.left, prefix_append_right_inj]
      exact hl h.right
#align list.is_prefix.filter_map List.isPrefix.filter_map

#print List.isPrefix.reduceOption /-
theorem isPrefix.reduceOption {l₁ l₂ : List (Option α)} (h : l₁ <+: l₂) :
    l₁.reduceOption <+: l₂.reduceOption :=
  h.filterMap id
#align list.is_prefix.reduce_option List.isPrefix.reduceOption
-/

/- warning: list.is_prefix.filter -> List.isPrefix.filter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p] {{l₁ : List.{u1} α}} {{l₂ : List.{u1} α}}, (List.isPrefix.{u1} α l₁ l₂) -> (List.isPrefix.{u1} α (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₁) (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₂))
but is expected to have type
  forall {α : Type.{u1}} (p : α -> Bool) {{_inst_1 : List.{u1} α}} {{l₁ : List.{u1} α}}, (List.isPrefix.{u1} α _inst_1 l₁) -> (List.isPrefix.{u1} α (List.filter.{u1} α p _inst_1) (List.filter.{u1} α p l₁))
Case conversion may be inaccurate. Consider using '#align list.is_prefix.filter List.isPrefix.filterₓ'. -/
theorem isPrefix.filter (p : α → Prop) [DecidablePred p] ⦃l₁ l₂ : List α⦄ (h : l₁ <+: l₂) :
    l₁.filterₓ p <+: l₂.filterₓ p := by
  obtain ⟨xs, rfl⟩ := h
  rw [filter_append]
  exact prefix_append _ _
#align list.is_prefix.filter List.isPrefix.filter

/- warning: list.is_suffix.filter -> List.isSuffix.filter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p] {{l₁ : List.{u1} α}} {{l₂ : List.{u1} α}}, (List.isSuffix.{u1} α l₁ l₂) -> (List.isSuffix.{u1} α (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₁) (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₂))
but is expected to have type
  forall {α : Type.{u1}} (p : α -> Bool) {{_inst_1 : List.{u1} α}} {{l₁ : List.{u1} α}}, (List.isSuffix.{u1} α _inst_1 l₁) -> (List.isSuffix.{u1} α (List.filter.{u1} α p _inst_1) (List.filter.{u1} α p l₁))
Case conversion may be inaccurate. Consider using '#align list.is_suffix.filter List.isSuffix.filterₓ'. -/
theorem isSuffix.filter (p : α → Prop) [DecidablePred p] ⦃l₁ l₂ : List α⦄ (h : l₁ <:+ l₂) :
    l₁.filterₓ p <:+ l₂.filterₓ p := by
  obtain ⟨xs, rfl⟩ := h
  rw [filter_append]
  exact suffix_append _ _
#align list.is_suffix.filter List.isSuffix.filter

/- warning: list.is_infix.filter -> List.isInfix.filter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} (p : α -> Prop) [_inst_1 : DecidablePred.{succ u1} α p] {{l₁ : List.{u1} α}} {{l₂ : List.{u1} α}}, (List.isInfix.{u1} α l₁ l₂) -> (List.isInfix.{u1} α (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₁) (List.filterₓ.{u1} α p (fun (a : α) => _inst_1 a) l₂))
but is expected to have type
  forall {α : Type.{u1}} (p : α -> Bool) {{_inst_1 : List.{u1} α}} {{l₁ : List.{u1} α}}, (List.isInfix.{u1} α _inst_1 l₁) -> (List.isInfix.{u1} α (List.filter.{u1} α p _inst_1) (List.filter.{u1} α p l₁))
Case conversion may be inaccurate. Consider using '#align list.is_infix.filter List.isInfix.filterₓ'. -/
theorem isInfix.filter (p : α → Prop) [DecidablePred p] ⦃l₁ l₂ : List α⦄ (h : l₁ <:+: l₂) :
    l₁.filterₓ p <:+: l₂.filterₓ p :=
  by
  obtain ⟨xs, ys, rfl⟩ := h
  rw [filter_append, filter_append]
  exact infix_append _ _ _
#align list.is_infix.filter List.isInfix.filter

instance : IsPartialOrder (List α) (· <+: ·)
    where
  refl := prefix_refl
  trans _ _ _ := isPrefix.trans
  antisymm _ _ h₁ h₂ := eq_of_prefix_of_length_eq h₁ <| h₁.length_le.antisymm h₂.length_le

instance : IsPartialOrder (List α) (· <:+ ·)
    where
  refl := suffix_refl
  trans _ _ _ := isSuffix.trans
  antisymm _ _ h₁ h₂ := eq_of_suffix_of_length_eq h₁ <| h₁.length_le.antisymm h₂.length_le

instance : IsPartialOrder (List α) (· <:+: ·)
    where
  refl := infix_refl
  trans _ _ _ := isInfix.trans
  antisymm _ _ h₁ h₂ := eq_of_infix_of_length_eq h₁ <| h₁.length_le.antisymm h₂.length_le

end Fix

section InitsTails

#print List.mem_inits /-
@[simp]
theorem mem_inits : ∀ s t : List α, s ∈ inits t ↔ s <+: t
  | s, [] =>
    suffices s = nil ↔ s <+: nil by simpa only [inits, mem_singleton]
    ⟨fun h => h.symm ▸ prefix_refl [], eq_nil_of_prefix_nil⟩
  | s, a :: t =>
    suffices (s = nil ∨ ∃ l ∈ inits t, a :: l = s) ↔ s <+: a :: t by simpa
    ⟨fun o =>
      match s, o with
      | _, Or.inl rfl => ⟨_, rfl⟩
      | s, Or.inr ⟨r, hr, hs⟩ => by
        let ⟨s, ht⟩ := (mem_inits _ _).1 hr
        rw [← hs, ← ht] <;> exact ⟨s, rfl⟩,
      fun mi =>
      match s, mi with
      | [], ⟨_, rfl⟩ => Or.inl rfl
      | b :: s, ⟨r, hr⟩ =>
        List.noConfusion hr fun ba (st : s ++ r = t) =>
          Or.inr <| by rw [ba] <;> exact ⟨_, (mem_inits _ _).2 ⟨_, st⟩, rfl⟩⟩
#align list.mem_inits List.mem_inits
-/

#print List.mem_tails /-
@[simp]
theorem mem_tails : ∀ s t : List α, s ∈ tails t ↔ s <:+ t
  | s, [] => by
    simp only [tails, mem_singleton] <;>
      exact ⟨fun h => by rw [h] <;> exact suffix_refl [], eq_nil_of_suffix_nil⟩
  | s, a :: t => by
    simp only [tails, mem_cons_iff, mem_tails s t] <;>
      exact
        show s = a :: t ∨ s <:+ t ↔ s <:+ a :: t from
          ⟨fun o =>
            match s, t, o with
            | _, t, Or.inl rfl => suffix_rfl
            | s, _, Or.inr ⟨l, rfl⟩ => ⟨a :: l, rfl⟩,
            fun e =>
            match s, t, e with
            | _, t, ⟨[], rfl⟩ => Or.inl rfl
            | s, t, ⟨b :: l, he⟩ => List.noConfusion he fun ab lt => Or.inr ⟨l, lt⟩⟩
#align list.mem_tails List.mem_tails
-/

#print List.inits_cons /-
theorem inits_cons (a : α) (l : List α) : inits (a :: l) = [] :: l.inits.map fun t => a :: t := by
  simp
#align list.inits_cons List.inits_cons
-/

#print List.tails_cons /-
theorem tails_cons (a : α) (l : List α) : tails (a :: l) = (a :: l) :: l.tails := by simp
#align list.tails_cons List.tails_cons
-/

#print List.inits_append /-
@[simp]
theorem inits_append : ∀ s t : List α, inits (s ++ t) = s.inits ++ t.inits.tail.map fun l => s ++ l
  | [], [] => by simp
  | [], a :: t => by simp
  | a :: s, t => by simp [inits_append s t]
#align list.inits_append List.inits_append
-/

#print List.tails_append /-
@[simp]
theorem tails_append :
    ∀ s t : List α, tails (s ++ t) = (s.tails.map fun l => l ++ t) ++ t.tails.tail
  | [], [] => by simp
  | [], a :: t => by simp
  | a :: s, t => by simp [tails_append s t]
#align list.tails_append List.tails_append
-/

#print List.inits_eq_tails /-
-- the lemma names `inits_eq_tails` and `tails_eq_inits` are like `sublists_eq_sublists'`
theorem inits_eq_tails : ∀ l : List α, l.inits = (reverse <| map reverse <| tails <| reverse l)
  | [] => by simp
  | a :: l => by simp [inits_eq_tails l, map_eq_map_iff]
#align list.inits_eq_tails List.inits_eq_tails
-/

#print List.tails_eq_inits /-
theorem tails_eq_inits : ∀ l : List α, l.tails = (reverse <| map reverse <| inits <| reverse l)
  | [] => by simp
  | a :: l => by simp [tails_eq_inits l, append_left_inj]
#align list.tails_eq_inits List.tails_eq_inits
-/

#print List.inits_reverse /-
theorem inits_reverse (l : List α) : inits (reverse l) = reverse (map reverse l.tails) :=
  by
  rw [tails_eq_inits l]
  simp [reverse_involutive.comp_self]
#align list.inits_reverse List.inits_reverse
-/

#print List.tails_reverse /-
theorem tails_reverse (l : List α) : tails (reverse l) = reverse (map reverse l.inits) :=
  by
  rw [inits_eq_tails l]
  simp [reverse_involutive.comp_self]
#align list.tails_reverse List.tails_reverse
-/

#print List.map_reverse_inits /-
theorem map_reverse_inits (l : List α) : map reverse l.inits = (reverse <| tails <| reverse l) :=
  by
  rw [inits_eq_tails l]
  simp [reverse_involutive.comp_self]
#align list.map_reverse_inits List.map_reverse_inits
-/

#print List.map_reverse_tails /-
theorem map_reverse_tails (l : List α) : map reverse l.tails = (reverse <| inits <| reverse l) :=
  by
  rw [tails_eq_inits l]
  simp [reverse_involutive.comp_self]
#align list.map_reverse_tails List.map_reverse_tails
-/

#print List.length_tails /-
@[simp]
theorem length_tails (l : List α) : length (tails l) = length l + 1 :=
  by
  induction' l with x l IH
  · simp
  · simpa using IH
#align list.length_tails List.length_tails
-/

#print List.length_inits /-
@[simp]
theorem length_inits (l : List α) : length (inits l) = length l + 1 := by simp [inits_eq_tails]
#align list.length_inits List.length_inits
-/

#print List.nth_le_tails /-
@[simp]
theorem nth_le_tails (l : List α) (n : ℕ) (hn : n < length (tails l)) :
    nthLe (tails l) n hn = l.drop n :=
  by
  induction' l with x l IH generalizing n
  · simp
  · cases n
    · simp
    · simpa using IH n _
#align list.nth_le_tails List.nth_le_tails
-/

#print List.nth_le_inits /-
@[simp]
theorem nth_le_inits (l : List α) (n : ℕ) (hn : n < length (inits l)) :
    nthLe (inits l) n hn = l.take n :=
  by
  induction' l with x l IH generalizing n
  · simp
  · cases n
    · simp
    · simpa using IH n _
#align list.nth_le_inits List.nth_le_inits
-/

end InitsTails

/-! ### insert -/

section Insert

variable [DecidableEq α]

#print List.insert_nil /-
@[simp]
theorem insert_nil (a : α) : insert a nil = [a] :=
  rfl
#align list.insert_nil List.insert_nil
-/

/- warning: list.insert.def -> List.insert.def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] (a : α) (l : List.{u1} α), Eq.{succ u1} (List.{u1} α) (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l) (ite.{succ u1} (List.{u1} α) (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l) (List.decidableMem.{u1} α (fun (a : α) (b : α) => _inst_1 a b) a l) l (List.cons.{u1} α a l))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] (a : α) (l : List.{u1} α), Eq.{succ u1} (List.{u1} α) (Insert.insert.{u1, u1} α (List.{u1} α) (List.instInsertList.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l) (ite.{succ u1} (List.{u1} α) (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a l) (List.instDecidableMemListInstMembershipList.{u1} α (fun (a : α) (b : α) => _inst_1 a b) a l) l (List.cons.{u1} α a l))
Case conversion may be inaccurate. Consider using '#align list.insert.def List.insert.defₓ'. -/
theorem insert.def (a : α) (l : List α) : insert a l = if a ∈ l then l else a :: l :=
  rfl
#align list.insert.def List.insert.def

/- warning: list.insert_of_mem -> List.insert_of_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α], (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l) -> (Eq.{succ u1} (List.{u1} α) (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l) l)
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {_inst_1 : List.{u1} α}, (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1) -> (Eq.{succ u1} (List.{u1} α) (List.insert.{u1} α (fun (a : α) (b : α) => l a b) a _inst_1) _inst_1)
Case conversion may be inaccurate. Consider using '#align list.insert_of_mem List.insert_of_memₓ'. -/
@[simp]
theorem insert_of_mem (h : a ∈ l) : insert a l = l := by simp only [insert.def, if_pos h]
#align list.insert_of_mem List.insert_of_mem

/- warning: list.insert_of_not_mem -> List.insert_of_not_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α], (Not (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l)) -> (Eq.{succ u1} (List.{u1} α) (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l) (List.cons.{u1} α a l))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {_inst_1 : List.{u1} α}, (Not (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1)) -> (Eq.{succ u1} (List.{u1} α) (List.insert.{u1} α (fun (a : α) (b : α) => l a b) a _inst_1) (List.cons.{u1} α a _inst_1))
Case conversion may be inaccurate. Consider using '#align list.insert_of_not_mem List.insert_of_not_memₓ'. -/
@[simp]
theorem insert_of_not_mem (h : a ∉ l) : insert a l = a :: l := by
  simp only [insert.def, if_neg h] <;> constructor <;> rfl
#align list.insert_of_not_mem List.insert_of_not_mem

/- warning: list.mem_insert_iff -> List.mem_insert_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} {b : α} [_inst_1 : DecidableEq.{succ u1} α], Iff (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) b l)) (Or (Eq.{succ u1} α a b) (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {b : α} {_inst_1 : List.{u1} α}, Iff (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a (List.insert.{u1} α (fun (a : α) (b : α) => l a b) b _inst_1)) (Or (Eq.{succ u1} α a b) (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1))
Case conversion may be inaccurate. Consider using '#align list.mem_insert_iff List.mem_insert_iffₓ'. -/
@[simp]
theorem mem_insert_iff : a ∈ insert b l ↔ a = b ∨ a ∈ l :=
  by
  by_cases h' : b ∈ l
  · simp only [insert_of_mem h']
    apply (or_iff_right_of_imp _).symm
    exact fun e => e.symm ▸ h'
  · simp only [insert_of_not_mem h', mem_cons_iff]
#align list.mem_insert_iff List.mem_insert_iff

#print List.suffix_insert /-
@[simp]
theorem suffix_insert (a : α) (l : List α) : l <:+ insert a l := by
  by_cases a ∈ l <;> [simp only [insert_of_mem h], simp only [insert_of_not_mem h, suffix_cons]]
#align list.suffix_insert List.suffix_insert
-/

#print List.infix_insert /-
theorem infix_insert (a : α) (l : List α) : l <:+: insert a l :=
  (suffix_insert a l).isInfix
#align list.infix_insert List.infix_insert
-/

#print List.sublist_insert /-
theorem sublist_insert (a : α) (l : List α) : l <+ l.insert a :=
  (suffix_insert a l).Sublist
#align list.sublist_insert List.sublist_insert
-/

#print List.subset_insert /-
theorem subset_insert (a : α) (l : List α) : l ⊆ l.insert a :=
  (sublist_insert a l).Subset
#align list.subset_insert List.subset_insert
-/

#print List.mem_insert_self /-
@[simp]
theorem mem_insert_self (a : α) (l : List α) : a ∈ l.insert a :=
  mem_insert_iff.2 <| Or.inl rfl
#align list.mem_insert_self List.mem_insert_self
-/

/- warning: list.mem_insert_of_mem -> List.mem_insert_of_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} {b : α} [_inst_1 : DecidableEq.{succ u1} α], (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l) -> (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) b l))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {b : α} {_inst_1 : List.{u1} α}, (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1) -> (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a (List.insert.{u1} α (fun (a : α) (b : α) => l a b) b _inst_1))
Case conversion may be inaccurate. Consider using '#align list.mem_insert_of_mem List.mem_insert_of_memₓ'. -/
theorem mem_insert_of_mem (h : a ∈ l) : a ∈ insert b l :=
  mem_insert_iff.2 (Or.inr h)
#align list.mem_insert_of_mem List.mem_insert_of_mem

/- warning: list.eq_or_mem_of_mem_insert -> List.eq_or_mem_of_mem_insert is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} {b : α} [_inst_1 : DecidableEq.{succ u1} α], (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) b l)) -> (Or (Eq.{succ u1} α a b) (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {b : α} {_inst_1 : List.{u1} α}, (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a (List.insert.{u1} α (fun (a : α) (b : α) => l a b) b _inst_1)) -> (Or (Eq.{succ u1} α a b) (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1))
Case conversion may be inaccurate. Consider using '#align list.eq_or_mem_of_mem_insert List.eq_or_mem_of_mem_insertₓ'. -/
theorem eq_or_mem_of_mem_insert (h : a ∈ insert b l) : a = b ∨ a ∈ l :=
  mem_insert_iff.1 h
#align list.eq_or_mem_of_mem_insert List.eq_or_mem_of_mem_insert

/- warning: list.length_insert_of_mem -> List.length_insert_of_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α], (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l) -> (Eq.{1} Nat (List.length.{u1} α (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l)) (List.length.{u1} α l))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {_inst_1 : List.{u1} α}, (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1) -> (Eq.{1} Nat (List.length.{u1} α (List.insert.{u1} α (fun (a : α) (b : α) => l a b) a _inst_1)) (List.length.{u1} α _inst_1))
Case conversion may be inaccurate. Consider using '#align list.length_insert_of_mem List.length_insert_of_memₓ'. -/
@[simp]
theorem length_insert_of_mem (h : a ∈ l) : (insert a l).length = l.length :=
  congr_arg _ <| insert_of_mem h
#align list.length_insert_of_mem List.length_insert_of_mem

/- warning: list.length_insert_of_not_mem -> List.length_insert_of_not_mem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {l : List.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α], (Not (Membership.Mem.{u1, u1} α (List.{u1} α) (List.hasMem.{u1} α) a l)) -> (Eq.{1} Nat (List.length.{u1} α (Insert.insert.{u1, u1} α (List.{u1} α) (List.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a l)) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (List.length.{u1} α l) (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))
but is expected to have type
  forall {α : Type.{u1}} [l : DecidableEq.{succ u1} α] {a : α} {_inst_1 : List.{u1} α}, (Not (Membership.mem.{u1, u1} α (List.{u1} α) (List.instMembershipList.{u1} α) a _inst_1)) -> (Eq.{1} Nat (List.length.{u1} α (List.insert.{u1} α (fun (a : α) (b : α) => l a b) a _inst_1)) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (List.length.{u1} α _inst_1) (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))
Case conversion may be inaccurate. Consider using '#align list.length_insert_of_not_mem List.length_insert_of_not_memₓ'. -/
@[simp]
theorem length_insert_of_not_mem (h : a ∉ l) : (insert a l).length = l.length + 1 :=
  congr_arg _ <| insert_of_not_mem h
#align list.length_insert_of_not_mem List.length_insert_of_not_mem

end Insert

#print List.mem_of_mem_suffix /-
theorem mem_of_mem_suffix (hx : a ∈ l₁) (hl : l₁ <:+ l₂) : a ∈ l₂ :=
  hl.Subset hx
#align list.mem_of_mem_suffix List.mem_of_mem_suffix
-/

end List