abbrev Var := String

inductive Val where
  | int  (i : Int)
  | bool (b : Bool)
  deriving DecidableEq, Repr

instance : Coe Bool Val where
  coe b := .bool b

instance : Coe Int Val where
  coe i := .int i

instance : OfNat Val n where
  ofNat := .int n

#check (true : Val)

#check (0 : Val)

inductive BinOp where
  | eq | and | lt | add | sub
  deriving Repr

inductive UnaryOp where
  | not
  deriving Repr

inductive Expr where
  | val (v : Val)
  | var (x : Var)
  | bin (lhs : Expr) (op : BinOp) (rhs : Expr)
  | una (op : UnaryOp) (arg : Expr)
  deriving Repr

instance : Coe Val Expr where
  coe v := .val v

instance : Coe Var Expr where
  coe x := .var x

instance : OfNat Expr n where
  ofNat := .val n

@[simp] def BinOp.eval : BinOp → Val → Val → Option Val
  | .eq,  v₁,        v₂      => some (.bool (v₁ = v₂))
  | .and, .bool b₁, .bool b₂ => some (.bool (b₁ && b₂))
  | .lt,  .int i₁,  .int i₂  => some (.bool (i₁ < i₂))
  | .add, .int i₁,  .int i₂  => some (.int (i₁ + i₂))
  | .sub, .int i₁,  .int i₂  => some (.int (i₁ - i₂))
  | _,    _,        _        => none

@[simp] def UnaryOp.eval : UnaryOp → Val → Option Val
  | .not, .bool b => some (.bool !b)
  | _,    _       => none

inductive Stmt where
  | skip
  | assign (x : Var) (e : Expr)
  | seq    (s₁ s₂ : Stmt)
  | ite    (c : Expr) (e t : Stmt)
  | while  (c : Expr) (b : Stmt)
  deriving Repr

infix:150 " ::= " => Stmt.assign
infixr:130 ";; "   => Stmt.seq

syntax "`[Expr|" term "]" : term

macro_rules
  | `(`[Expr|true])      => `((true : Expr))
  | `(`[Expr|false])     => `((false : Expr))
  | `(`[Expr|$n:num])    => `(($n : Expr))
  | `(`[Expr|$x:ident])  => `(($(Lean.quote x.getId.toString) : Expr))
  | `(`[Expr|$x = $y])   => `(Expr.bin `[Expr|$x] BinOp.eq `[Expr|$y])
  | `(`[Expr|$x && $y])  => `(Expr.bin `[Expr|$x] BinOp.and `[Expr|$y])
  | `(`[Expr|$x < $y])   => `(Expr.bin `[Expr|$x] BinOp.lt `[Expr|$y])
  | `(`[Expr|$x + $y])   => `(Expr.bin `[Expr|$x] BinOp.add `[Expr|$y])
  | `(`[Expr|$x - $y])   => `(Expr.bin `[Expr|$x] BinOp.sub `[Expr|$y])
  | `(`[Expr|!$x])       => `(Expr.una UnaryOp.not `[Expr|$x])
  | `(`[Expr|($x)])      => `(`[Expr|$x])

declare_syntax_cat stmt

syntax ident " := " term ";": stmt
syntax "if " "(" term ")" " {\n" stmt* "\n}" (" else " "{\n" stmt* "\n}")? : stmt
syntax "while " "(" term ")" "{\n" stmt* "\n}" : stmt

syntax "`[Stmt|" stmt* "]" : term

macro_rules
  | `(`[Stmt| ]) => `(Stmt.skip)
  | `(`[Stmt| $x:ident := $e:term;]) => `(Stmt.assign $(Lean.quote x.getId.toString) `[Expr| $e:term])
  | `(`[Stmt| if ($c) { $ts* } else { $es* }]) => `(Stmt.ite `[Expr| $c] `[Stmt| $ts*] `[Stmt| $es*])
  | `(`[Stmt| if ($c) { $ts* }]) => `(Stmt.ite `[Expr| $c] `[Stmt| $ts*] Stmt.skip)
  | `(`[Stmt| while ($c) { $b* }]) => `(Stmt.while `[Expr|$c] `[Stmt|$b*])
  | `(`[Stmt| $s $ss*]) => `(Stmt.seq `[Stmt|$s] `[Stmt|$ss*])

def example1 := `[Stmt|
  x := 8;
  y := 10;
  if (x < y) {
    x := x + 1;
  } else {
    y := y + 3;
  }
]

#reduce example1

def example2 := `[Stmt|
  x := 8;
  if (x < y) {
    x := x + 1;
  } else {
    y := y + 3;
    x := 9;
  }
  y := x;]

#eval example2

abbrev State := List (Var × Val)

@[simp] def State.update (σ : State) (x : Var) (v : Val) : State :=
  match σ with
  | [] => [(x, v)]
  | (y, w)::σ => if x = y then (x, v)::σ else (y, w) :: update σ x v

@[simp] def State.find? (σ : State) (x : Var) : Option Val :=
  match σ with
  | [] => none
  | (y, v) :: σ => if x = y then some v else find? σ x

def State.get (σ : State) (x : Var) : Val :=
  σ.find? x |>.getD (.int 0)

@[simp] def State.erase (σ : State) (x : Var) : State :=
  match σ with
  | [] => []
  | (y, v) :: σ => if x = y then erase σ x else (y, v) :: erase σ x

@[simp] theorem State.find?_update_self (σ : State) (x : Var) (v : Val) : (σ.update x v).find? x = some v := by
  match σ with -- TODO: automate this proof
  | [] => simp
  | (y, w) :: s =>
    simp
    split <;> simp [*]
    apply find?_update_self

@[simp] theorem State.find?_update (σ : State) (v : Val) (h : x ≠ z) : (σ.update x v).find? z = σ.find? z := by
  match σ with -- TODO: automate this proof
  | [] => simp [h.symm]
  | (y, w) :: σ =>
    simp
    split <;> simp [*]
    next hc => split <;> simp_all
    next =>
      split
      next => rfl
      next => exact find?_update σ v h

-- TODO: remove after we add better automation
@[simp] theorem State.find?_update' (σ : State) (v : Val) (h : z ≠ x) : (σ.update x v).find? z = σ.find? z :=
  State.find?_update σ v h.symm

theorem State.get_of_find? {σ : State} (h : σ.find? x = some v) : σ.get x = v := by
  simp [State.get, h, Option.getD]

@[simp] theorem State.find?_erase_self (σ : State) (x : Var) : (σ.erase x).find? x = none := by
  match σ with
  | [] => simp
  | (y, w) :: σ =>
    simp
    split <;> simp [*]
    next => exact find?_erase_self σ y
    next => exact find?_erase_self σ x

@[simp] theorem State.find?_erase (σ : State) (h : x ≠ z) : (σ.erase x).find? z = σ.find? z := by
  match σ with
  | [] => simp
  | (y, w) :: σ =>
    simp
    split <;> simp [*]
    next hxy => rw [hxy] at h; simp [h.symm]; exact find?_erase σ h
    next =>
      split
      next => rfl
      next => exact find?_erase σ h

-- TODO: remove after we add better automation
@[simp] theorem State.find?_erase' (σ : State) (h : z ≠ x) : (σ.erase x).find? z = σ.find? z :=
  State.find?_erase σ h.symm

syntax ident " ↦ " term : term

macro_rules
  | `($id:ident ↦ $v:term) => `(($(Lean.quote id.getId.toString), $v:term))

example : State.get [x ↦ .int 10, y ↦ .int 20] "x" = .int 10 := rfl

example : State.get [x ↦ 10, y ↦ 20] "x" = 10     := rfl
example : State.get [x ↦ 10, y ↦ true] "y" = true := rfl

@[simp] def Expr.eval (σ : State) : Expr → Option Val
  | val v   => some v
  | var x   => σ.get x
  | bin lhs op rhs => match lhs.eval σ, rhs.eval σ with
    | some v₁, some v₂ => op.eval v₁ v₂  -- BinOp.eval : BinOp → Val → Val → Option Val
    | _,       _       => none
  | una op arg => match arg.eval σ with
    | some v => op.eval v
    | _      => none

@[simp] def evalTrue  (c : Expr) (σ : State) : Prop := c.eval σ = some (Val.bool true)
@[simp] def evalFalse (c : Expr) (σ : State) : Prop := c.eval σ = some (Val.bool false)

section
set_option hygiene false -- HACK: allow forward reference in notation
local notation:60 "(" σ ", " s ")"  " ⇓ " σ':60 => Bigstep σ s σ'

inductive Bigstep : State → Stmt → State → Prop where
  | skip       : (σ, .skip) ⇓ σ
  | assign     : e.eval σ = some v → (σ,  x ::= e) ⇓ σ.update x v
  | seq        : (σ₁, s₁) ⇓ σ₂ → (σ₂, s₂) ⇓ σ₃ → (σ₁, s₁ ;; s₂) ⇓ σ₃
  | ifTrue     : evalTrue c σ₁ → (σ₁, t) ⇓ σ₂ → (σ₁, .ite c t e) ⇓ σ₂
  | ifFalse    : evalFalse c σ₁ → (σ₁, e) ⇓ σ₂ → (σ₁, .ite c t e) ⇓ σ₂
  | whileTrue  : evalTrue c σ₁ → (σ₁, b) ⇓ σ₂ → (σ₂, .while c b) ⇓ σ₃ → (σ₁, .while c b) ⇓ σ₃
  | whileFalse : evalFalse c σ → (σ, .while c b) ⇓ σ

end

notation:60 "(" σ ", " s ")"  " ⇓ " σ':60 => Bigstep σ s σ'

/- This proof can be automated using forward reasoning. -/
theorem Bigstem.det (h₁ : (σ, s) ⇓ σ₁) (h₂ : (σ, s) ⇓ σ₂) : σ₁ = σ₂ := by
  induction h₁ generalizing σ₂ <;> cases h₂ <;> simp_all
  -- The rest of this proof should be automatic with congruence closure and a bit of forward reasoning
  case seq ih₁ ih₂ h₁ h₂ =>
    simp [ih₁ h₁] at ih₂
    simp [ih₂ h₂]
  case ifTrue ih h =>
    simp [ih h]
  case ifFalse ih h =>
    simp [ih h]
  case whileTrue ih₁ ih₂ h₁ h₂ =>
    simp [ih₁ h₁] at ih₂
    simp [ih₂ h₂]

abbrev EvalM := ExceptT String (StateM State)

def evalExpr (e : Expr) : EvalM Val := do
  match e.eval (← get) with
  | some v => return v
  | none => throw "failed to evaluate"

@[simp] def Stmt.eval (stmt : Stmt) (fuel : Nat := 100) : EvalM Unit := do
  match fuel with
  | 0 => throw "out of fuel"
  | fuel+1 =>
    match stmt with
    | skip        => return ()
    | assign x e  => let v ← evalExpr e; modify fun s => s.update x v
    | seq s₁ s₂   => s₁.eval fuel; s₂.eval fuel
    | ite c e t   =>
      match (← evalExpr c) with
      | .bool true  => e.eval fuel
      | .bool false => t.eval fuel
      | _ => throw "Boolean expected"
    | .while c b =>
      match (← evalExpr c) with
      | .bool true  => b.eval fuel; stmt.eval fuel
      | .bool false => return ()
      | _ => throw "Boolean expected"

#eval `[Stmt| x := 3; y := 5; x := x + y;].eval |>.run {}
#eval `[Stmt| x := 0; while (true) { x := x + 1; }].eval |>.run {}

instance : Repr State where
  reprPrec a n :=
    match a, n with
    | [], _ => "[]"
    | as, _ =>
      let fs := as.map fun
        | (x, .int v)  => f!"{x} ↦ {v}"
        | (x, .bool v) => f!"{x} ↦ {v}"
      Std.Format.bracket "[" (Std.Format.joinSep fs ("," ++ Std.Format.line)) "]"

#eval `[Stmt| x := 3; y := 5; x := x + y; ].eval |>.run {}

@[simp] def BinOp.simplify : BinOp → Expr → Expr → Expr
  | .eq,  .val v₁,  .val  v₂ => .val (.bool (v₁ = v₂))
  | .and, .val (.bool a), .val (.bool b) => .val (.bool (a && b))
  | .lt,  .val (.int a),  .val (.int b)  => .val (.bool (a < b))
  | .add, .val (.int a), .val (.int b) => .val (.int (a + b))
  | .sub, .val (.int a), .val (.int b) => .val (.int (a - b))
  | op, a, b => .bin a op b

@[simp] def UnaryOp.simplify : UnaryOp → Expr → Expr
  | .not, .val (.bool b) => .val (.bool !b)
  | op, a => .una op a

@[simp] def Expr.simplify : Expr → Expr
  | bin lhs op rhs => op.simplify lhs.simplify rhs.simplify
  | una op arg => op.simplify arg.simplify
  | e => e

@[simp] theorem Expr.eval_simplify (e : Expr) : e.simplify.eval σ = e.eval σ := by
  induction e with simp
  | bin lhs op rhs ih_lhs ih_rhs =>
    simp [← ih_lhs, ← ih_rhs]
    split <;> simp [*]
  | una op arg ih_arg =>
    simp [← ih_arg]
    split <;> simp [*]

@[simp] def Stmt.simplify : Stmt → Stmt
  | skip => skip
  | assign x e => assign x e.simplify
  | seq s₁ s₂ => seq s₁.simplify s₂.simplify
  | ite c e t =>
    match c.simplify with
    | .val (.bool true) => e.simplify
    | .val (.bool false) => t.simplify
    | c' => ite c' e.simplify t.simplify
  | .while c b =>
    match c.simplify with
    | .val (.bool false) => skip
    | c' => .while c' b.simplify

def example3 := `[Stmt|
  if (1 < 2 + 3) {
    x := 3 + 1;
    y := y + x;
  } else {
    y := y + 3;
  }
]

#eval example3.simplify

theorem Stmt.simplify_correct (h : (σ, s) ⇓ σ') : (σ, s.simplify) ⇓ σ' := by
  induction h with simp_all
  | skip => exact Bigstep.skip
  | seq h₁ h₂ ih₁ ih₂ => exact Bigstep.seq ih₁ ih₂
  | assign => apply Bigstep.assign; simp [*]
  | whileTrue heq h₁ h₂ ih₁ ih₂ =>
    rw [← Expr.eval_simplify] at heq
    split
    next h => rw [h] at heq; simp at heq
    next hnp => simp [hnp] at ih₂; apply Bigstep.whileTrue heq ih₁ ih₂
  | whileFalse heq =>
    split
    next => exact Bigstep.skip
    next => apply Bigstep.whileFalse; simp [heq]
  | ifFalse heq h ih =>
    rw [← Expr.eval_simplify] at heq
    split <;> simp_all
    rw [← Expr.eval_simplify] at heq
    apply Bigstep.ifFalse heq ih
  | ifTrue heq h ih =>
    rw [← Expr.eval_simplify] at heq
    split <;> simp_all
    rw [← Expr.eval_simplify] at heq
    apply Bigstep.ifTrue heq ih

@[simp] def Expr.constProp (e : Expr) (σ : State) : Expr :=
  match e with
  | val v => v
  | var x => match σ.find? x with
    | some v => val v
    | none   => var x
  | bin lhs op rhs => bin (lhs.constProp σ) op (rhs.constProp σ)
  | una op arg => una op (arg.constProp σ)

@[simp] theorem Expr.constProp_nil (e : Expr) : e.constProp [] = e := by
  induction e <;> simp [*]

def State.length_erase_le (σ : State) (x : Var) : (σ.erase x).length ≤ σ.length := by
  match σ with
  | [] => simp
  | (y, v) :: σ =>
    by_cases hxy : x = y <;> simp [hxy]
    next => exact Nat.le_trans (length_erase_le σ y) (by simp_arith)
    next => simp_arith [length_erase_le σ x]

def State.length_erase_lt (σ : State) (x : Var) : (σ.erase x).length < σ.length.succ :=
  Nat.lt_of_le_of_lt (length_erase_le ..) (by simp_arith)

@[simp] def State.join (σ₁ σ₂ : State) : State :=
  match σ₁ with
  | [] => []
  | (x, v) :: σ₁ =>
    let σ₁' := erase σ₁ x -- Must remove duplicates. Alternative design: carry invariant that input state at constProp has no duplicates
    have : (erase σ₁ x).length < σ₁.length.succ := length_erase_lt ..
    match σ₂.find? x with
    | some w => if v = w then (x, v) :: join σ₁' σ₂ else join σ₁' σ₂
    | none => join σ₁' σ₂
termination_by _ σ₁ _ => σ₁.length

local notation "⊥" => []

@[simp] def Stmt.constProp (s : Stmt) (σ : State) : Stmt × State :=
  match s with
  | skip => (skip, σ)
  | assign x e => match (e.constProp σ).simplify with
    | (.val v) => (assign x (.val v), σ.update x v)
    | e'       => (assign x e', σ.erase x)
  | seq s₁ s₂ => match s₁.constProp σ with
    | (s₁', σ₁) => match s₂.constProp σ₁ with
      | (s₂', σ₂) => (seq s₁' s₂', σ₂)
  | ite c s₁ s₂ =>
    match s₁.constProp σ, s₂.constProp σ with
    | (s₁', σ₁), (s₂', σ₂) => (ite (c.constProp σ) s₁' s₂', σ₁.join σ₂)
  | .while c b => (.while (c.constProp ⊥) (b.constProp ⊥).1, ⊥)

def State.le (σ₁ σ₂ : State) : Prop :=
  ∀ ⦃x : Var⦄ ⦃v : Val⦄, σ₁.find? x = some v → σ₂.find? x = some v

infix:50 " ≼ " => State.le

theorem State.le_refl (σ : State) : σ ≼ σ :=
  fun _ _ h => h

theorem State.le_trans : σ₁ ≼ σ₂ → σ₂ ≼ σ₃ → σ₁ ≼ σ₃ :=
  fun h₁ h₂ x v h => h₂ (h₁ h)

theorem State.bot_le (σ : State) : ⊥ ≼ σ :=
  fun _ _ h => by contradiction

theorem State.erase_le_cons (h : σ' ≼ σ) : σ'.erase x ≼ ((x, v) :: σ) := by
  intro y w hf'
  by_cases hyx : y = x <;> simp [*] at hf' |-
  exact h hf'

theorem State.cons_le_cons (h : σ' ≼ σ) : (x, v) :: σ' ≼ (x, v) :: σ := by
  intro y w hf'
  by_cases hyx : y = x <;> simp [*] at hf' |-
  next => assumption
  next => exact h hf'

theorem State.cons_le_of_eq (h₁ : σ' ≼ σ) (h₂ : σ.find? x = some v) : (x, v) :: σ' ≼ σ := by
  intro y w hf'
  by_cases hyx : y = x <;> simp [*] at hf' |-
  next => assumption
  next => exact h₁ hf'

theorem State.erase_le (σ : State) : σ.erase x ≼ σ := by
  match σ with
  | [] => simp; apply le_refl
  | (y, v) :: σ =>
    simp
    split <;> simp [*]
    next => apply erase_le_cons; apply le_refl
    next => apply cons_le_cons; apply erase_le

theorem State.join_le_left (σ₁ σ₂ : State) : σ₁.join σ₂ ≼ σ₁ := by
  match σ₁ with
  | [] => simp; apply le_refl
  | (x, v) :: σ₁ =>
    simp
    have : (erase σ₁ x).length < σ₁.length.succ := length_erase_lt ..
    have ih := join_le_left (State.erase σ₁ x) σ₂
    split
    next y w h =>
      split
      next => apply cons_le_cons; apply le_trans ih (erase_le _)
      next => apply le_trans ih (erase_le_cons (le_refl _))
    next h => apply le_trans ih (erase_le_cons (le_refl _))
termination_by _ σ₁ _ => σ₁.length

theorem State.join_le_left_of (h : σ₁ ≼ σ₂) (σ₃ : State) : σ₁.join σ₃ ≼ σ₂ :=
  le_trans (join_le_left σ₁ σ₃) h

theorem State.join_le_right (σ₁ σ₂ : State) : σ₁.join σ₂ ≼ σ₂ := by
  match σ₁ with
  | [] => simp; apply bot_le
  | (x, v) :: σ₁ =>
    simp
    have : (erase σ₁ x).length < σ₁.length.succ := length_erase_lt ..
    have ih := join_le_right (erase σ₁ x) σ₂
    split
    next y w h =>
      split <;> simp [*]
      next => apply cons_le_of_eq ih h
    next h => assumption
termination_by _ σ₁ _ => σ₁.length

theorem State.join_le_right_of (h : σ₁ ≼ σ₂) (σ₃ : State) : σ₃.join σ₁ ≼ σ₂ :=
  le_trans (join_le_right σ₃ σ₁) h

theorem State.eq_bot (h : σ ≼ ⊥) : σ = ⊥ := by
  match σ with
  | [] => simp
  | (y, v) :: σ =>
    have : State.find? ((y, v) :: σ) y = some v := by simp
    have := h this
    contradiction

theorem State.erase_le_of_le_cons (h : σ' ≼ (x, v) :: σ) : σ'.erase x ≼ σ := by
  intro y w hf'
  by_cases hxy : x = y <;> simp [*] at hf'
  have hf := h hf'
  simp [hxy, Ne.symm hxy] at hf
  assumption

theorem State.erase_le_update (h : σ' ≼ σ) : σ'.erase x ≼ σ.update x v := by
  intro y w hf'
  by_cases hxy : x = y <;> simp [*] at hf' |-
  exact h hf'

theorem State.update_le_update (h : σ' ≼ σ) : σ'.update x v ≼ σ.update x v := by
  intro y w hf
  induction σ generalizing σ' hf with
  | nil  => rw [eq_bot h] at hf; assumption
  | cons zw' σ ih =>
    have (z, w') := zw'; simp
    have : σ'.erase z ≼ σ := erase_le_of_le_cons h
    have ih := ih this
    revert ih hf
    split <;> simp [*] <;> by_cases hyz : y = z <;> simp (config := { contextual := true }) [*]
    next =>
      intro he'
      have he := h he'
      simp [*] at he
      assumption
    next =>
      by_cases hxy : x = y <;> simp [*]
      next => intros; assumption
      next =>
        intro he' ih
        exact ih he'

theorem Expr.eval_constProp_of_sub (e : Expr) (h : σ' ≼ σ) : (e.constProp σ').eval σ = e.eval σ := by
  induction e with simp [*]
  | var x =>
    split <;> simp
    next he => rw [State.get_of_find? (h he)]

theorem Expr.eval_constProp_of_eq_of_sub {e : Expr} (h₁ : e.eval σ = v) (h₂ : σ' ≼ σ) : (e.constProp σ').eval σ = v := by
  have := eval_constProp_of_sub e h₂
  simp [h₁] at this
  assumption

theorem Stmt.constProp_sub (h₁ : (σ₁, s) ⇓ σ₂) (h₂ : σ₁' ≼ σ₁) : (s.constProp σ₁').2 ≼ σ₂ := by
  induction h₁ generalizing σ₁' with simp
  | skip => assumption
  | assign heq =>
    split <;> simp
    next h =>
      have heq' := Expr.eval_constProp_of_eq_of_sub heq h₂
      rw [← Expr.eval_simplify, h] at heq'
      simp at heq'
      rw [heq']
      apply State.update_le_update h₂
    next h _ _ =>
      exact State.erase_le_update h₂
  | whileTrue heq h₃ h₄ ih₃ ih₄ =>
    have ih₃ := ih₃ h₂
    have ih₄ := ih₄ ih₃
    simp [heq] at ih₄
    exact ih₄
  | whileFalse heq => apply State.bot_le
  | ifTrue heq h ih =>
    have ih := ih h₂
    apply State.join_le_left_of ih
  | ifFalse heq h ih =>
    have ih := ih h₂
    apply State.join_le_right_of ih
  | seq h₃ h₄ ih₃ ih₄ => exact ih₄ (ih₃ h₂)

theorem Stmt.constProp_correct (h₁ : (σ₁, s) ⇓ σ₂) (h₂ : σ₁' ≼ σ₁) : (σ₁, (s.constProp σ₁').1) ⇓ σ₂ := by
  induction h₁ generalizing σ₁' with simp_all
  | skip => exact Bigstep.skip
  | assign heq =>
    split <;> simp
    next h =>
      have heq' := Expr.eval_constProp_of_eq_of_sub heq h₂
      rw [← Expr.eval_simplify, h] at heq'
      simp at heq'
      apply Bigstep.assign; simp [*]
    next =>
      have heq' := Expr.eval_constProp_of_eq_of_sub heq h₂
      rw [← Expr.eval_simplify] at heq'
      apply Bigstep.assign heq'
  | seq h₁ h₂ ih₁ ih₂ =>
    apply Bigstep.seq (ih₁ h₂) (ih₂ (constProp_sub h₁ h₂))
  | whileTrue heq h₁ h₂ ih₁ ih₂ =>
    have ih₁ := ih₁ (State.bot_le _)
    have ih₂ := ih₂ (State.bot_le _)
    exact Bigstep.whileTrue heq ih₁ ih₂
  | whileFalse heq =>
    exact Bigstep.whileFalse heq
  | ifTrue heq h ih =>
    exact Bigstep.ifTrue (Expr.eval_constProp_of_eq_of_sub heq h₂) (ih h₂)
  | ifFalse heq h ih =>
    exact Bigstep.ifFalse (Expr.eval_constProp_of_eq_of_sub heq h₂) (ih h₂)

def Stmt.constPropagation (s : Stmt) : Stmt :=
  (s.constProp ⊥).1

theorem Stmt.constPropagation_correct (h : (σ, s) ⇓ σ') : (σ, s.constPropagation) ⇓ σ' :=
  constProp_correct h (State.bot_le _)

def example4 := `[Stmt|
  x := 2;
  if (x < 3) {
    x := x + 1;
    y := y + x;
  } else {
    y := y + 3;
  }
]

#eval example4.constPropagation.simplify

#exit
-- TODO: add simp theorems for monadic code
theorem Stmt.eval_correct {s : Stmt} (h : (s.eval fuel).run σ = (.ok unit, σ')) : (σ, s) ⇓ σ' := by
  induction fuel generalizing s σ σ' with simp at h
  | zero => injection h; contradiction
  | succ fuel ih =>
    split at h
    next => injection h with _ h; rw [h]; exact Bigstep.skip
    next => done


import tactic
import tactic.induction

import .base .game .pw_1 .pw_2 .pw_ge

noncomputable theory
open_locale classical

def play {pw : ℕ} (a : A pw) (d : D) (n : ℕ) :=
simulate a d n

def A_wins {pw : ℕ} (a : A pw) (d : D) :=
(init_game a d state₀).A_wins

def D_wins {pw : ℕ} (a : A pw) (d : D) :=
(init_game a d state₀).D_wins

lemma A_pw_0_not_hws : ¬A_hws 0 :=
begin
  rintro ⟨a, h⟩, contrapose! h, clear h, use default, rw not_A_wins_at, use 1,
  rw [play_1, play_move_at_act], swap, { triv },
  rw [play_A_move_at, dif_neg], { exact not_false },
  push_neg, rintro h₁ ⟨ma, h₂, h₃, h₄⟩,
  rw [nat.le_zero_iff, dist_eq_zero_iff] at h₃, contradiction,
end

lemma A_pw_ge_hws {pw pw₁ : ℕ}
  (h₁ : pw ≤ pw₁) (h₂ : A_hws pw) : A_hws pw₁ :=
begin
  cases h₂ with a h₂, use mk_A_pw_ge a h₁,
  intro d, specialize h₂ d, apply mk_A_pw_ge_wins_at_of h₂,
end


/-
Copyright (c) 2020 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Patrick Massot, Eric Wieser

! This file was ported from Lean 3 source module data.pi.algebra
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.ToAdditive
import Mathbin.Algebra.Group.Defs
import Mathbin.Logic.Unique
import Mathbin.Tactic.Congr
import Mathbin.Tactic.Simpa
import Mathbin.Tactic.SplitIfs
import Mathbin.Data.Sum.Basic
import Mathbin.Data.Prod.Basic

/-!
# Instances and theorems on pi types

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

This file provides basic definitions and notation instances for Pi types.

Instances of more sophisticated classes are defined in `pi.lean` files elsewhere.
-/

open Function

universe u v₁ v₂ v₃

variable {I : Type u}

-- The indexing type
variable {α β γ : Type _}

-- The families of types already equipped with instances
variable {f : I → Type v₁} {g : I → Type v₂} {h : I → Type v₃}

variable (x y : ∀ i, f i) (i : I)

namespace Pi

/-! `1`, `0`, `+`, `*`, `+ᵥ`, `•`, `^`, `-`, `⁻¹`, and `/` are defined pointwise. -/

#print Pi.instOne /-
@[to_additive]
instance instOne [∀ i, One <| f i] : One (∀ i : I, f i) :=
  ⟨fun _ => 1⟩
#align pi.has_one Pi.instOne
#align pi.has_zero Pi.instZero
-/

#print Pi.one_apply /-
@[simp, to_additive]
theorem one_apply [∀ i, One <| f i] : (1 : ∀ i, f i) i = 1 :=
  rfl
#align pi.one_apply Pi.one_apply
#align pi.zero_apply Pi.zero_apply
-/

#print Pi.one_def /-
@[to_additive]
theorem one_def [∀ i, One <| f i] : (1 : ∀ i, f i) = fun i => 1 :=
  rfl
#align pi.one_def Pi.one_def
#align pi.zero_def Pi.zero_def
-/

#print Pi.const_one /-
@[simp, to_additive]
theorem const_one [One β] : const α (1 : β) = 1 :=
  rfl
#align pi.const_one Pi.const_one
#align pi.const_zero Pi.const_zero
-/

/- warning: pi.one_comp -> Pi.one_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : One.{u3} γ] (x : α -> β), Eq.{max (succ u1) (succ u3)} (α -> γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ (OfNat.ofNat.{max u2 u3} (β -> γ) 1 (OfNat.mk.{max u2 u3} (β -> γ) 1 (One.one.{max u2 u3} (β -> γ) (Pi.instOne.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))))) x) (OfNat.ofNat.{max u1 u3} (α -> γ) 1 (OfNat.mk.{max u1 u3} (α -> γ) 1 (One.one.{max u1 u3} (α -> γ) (Pi.instOne.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1)))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : One.{u3} γ] (x : α -> β), Eq.{max (succ u2) (succ u3)} (α -> γ) (Function.comp.{succ u2, succ u1, succ u3} α β γ (OfNat.ofNat.{max u1 u3} (β -> γ) 1 (One.toOfNat1.{max u1 u3} (β -> γ) (Pi.instOne.{u1, u3} β (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.301 : β) => γ) (fun (i : β) => _inst_1)))) x) (OfNat.ofNat.{max u2 u3} (α -> γ) 1 (One.toOfNat1.{max u2 u3} (α -> γ) (Pi.instOne.{u2, u3} α (fun (a._@.Init.Prelude._hyg.25 : α) => γ) (fun (i : α) => _inst_1))))
Case conversion may be inaccurate. Consider using '#align pi.one_comp Pi.one_compₓ'. -/
@[simp, to_additive]
theorem one_comp [One γ] (x : α → β) : (1 : β → γ) ∘ x = 1 :=
  rfl
#align pi.one_comp Pi.one_comp
#align pi.zero_comp Pi.zero_comp

/- warning: pi.comp_one -> Pi.comp_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : One.{u2} β] (x : β -> γ), Eq.{max (succ u1) (succ u3)} (α -> γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ x (OfNat.ofNat.{max u1 u2} (α -> β) 1 (OfNat.mk.{max u1 u2} (α -> β) 1 (One.one.{max u1 u2} (α -> β) (Pi.instOne.{u1, u2} α (fun (ᾰ : α) => β) (fun (i : α) => _inst_1)))))) (Function.const.{succ u3, succ u1} γ α (x (OfNat.ofNat.{u2} β 1 (OfNat.mk.{u2} β 1 (One.one.{u2} β _inst_1)))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u3}} {γ : Type.{u1}} [_inst_1 : One.{u3} β] (x : β -> γ), Eq.{max (succ u2) (succ u1)} (α -> γ) (Function.comp.{succ u2, succ u3, succ u1} α β γ x (OfNat.ofNat.{max u2 u3} (α -> β) 1 (One.toOfNat1.{max u2 u3} (α -> β) (Pi.instOne.{u2, u3} α (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.349 : α) => β) (fun (i : α) => _inst_1))))) (Function.const.{succ u1, succ u2} γ α (x (OfNat.ofNat.{u3} β 1 (One.toOfNat1.{u3} β _inst_1))))
Case conversion may be inaccurate. Consider using '#align pi.comp_one Pi.comp_oneₓ'. -/
@[simp, to_additive]
theorem comp_one [One β] (x : β → γ) : x ∘ 1 = const α (x 1) :=
  rfl
#align pi.comp_one Pi.comp_one
#align pi.comp_zero Pi.comp_zero

#print Pi.instMul /-
@[to_additive]
instance instMul [∀ i, Mul <| f i] : Mul (∀ i : I, f i) :=
  ⟨fun f g i => f i * g i⟩
#align pi.has_mul Pi.instMul
#align pi.has_add Pi.instAdd
-/

#print Pi.mul_apply /-
@[simp, to_additive]
theorem mul_apply [∀ i, Mul <| f i] : (x * y) i = x i * y i :=
  rfl
#align pi.mul_apply Pi.mul_apply
#align pi.add_apply Pi.add_apply
-/

#print Pi.mul_def /-
@[to_additive]
theorem mul_def [∀ i, Mul <| f i] : x * y = fun i => x i * y i :=
  rfl
#align pi.mul_def Pi.mul_def
#align pi.add_def Pi.add_def
-/

#print Pi.const_mul /-
@[simp, to_additive]
theorem const_mul [Mul β] (a b : β) : const α a * const α b = const α (a * b) :=
  rfl
#align pi.const_mul Pi.const_mul
#align pi.const_add Pi.const_add
-/

/- warning: pi.mul_comp -> Pi.mul_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Mul.{u3} γ] (x : β -> γ) (y : β -> γ) (z : α -> β), Eq.{max (succ u1) (succ u3)} (α -> γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ (HMul.hMul.{max u2 u3, max u2 u3, max u2 u3} (β -> γ) (β -> γ) (β -> γ) (instHMul.{max u2 u3} (β -> γ) (Pi.instMul.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x y) z) (HMul.hMul.{max u1 u3, max u1 u3, max u1 u3} (α -> γ) (α -> γ) (α -> γ) (instHMul.{max u1 u3} (α -> γ) (Pi.instMul.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) (Function.comp.{succ u1, succ u2, succ u3} α β γ x z) (Function.comp.{succ u1, succ u2, succ u3} α β γ y z))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : Mul.{u3} γ] (x : β -> γ) (y : β -> γ) (z : α -> β), Eq.{max (succ u2) (succ u3)} (α -> γ) (Function.comp.{succ u2, succ u1, succ u3} α β γ (HMul.hMul.{max u1 u3, max u1 u3, max u1 u3} (β -> γ) (β -> γ) (β -> γ) (instHMul.{max u1 u3} (β -> γ) (Pi.instMul.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x y) z) (HMul.hMul.{max u2 u3, max u2 u3, max u2 u3} (α -> γ) (α -> γ) (α -> γ) (instHMul.{max u2 u3} (α -> γ) (Pi.instMul.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) (Function.comp.{succ u2, succ u1, succ u3} α β γ x z) (Function.comp.{succ u2, succ u1, succ u3} α β γ y z))
Case conversion may be inaccurate. Consider using '#align pi.mul_comp Pi.mul_compₓ'. -/
@[to_additive]
theorem mul_comp [Mul γ] (x y : β → γ) (z : α → β) : (x * y) ∘ z = x ∘ z * y ∘ z :=
  rfl
#align pi.mul_comp Pi.mul_comp
#align pi.add_comp Pi.add_comp

#print Pi.instSMul /-
@[to_additive Pi.instVAdd]
instance instSMul [∀ i, SMul α <| f i] : SMul α (∀ i : I, f i) :=
  ⟨fun s x => fun i => s • x i⟩
#align pi.has_smul Pi.instSMul
#align pi.has_vadd Pi.instVAdd
-/

/- warning: pi.smul_apply -> Pi.smul_apply is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u3}} {f : I -> Type.{u2}} [_inst_1 : forall (i : I), SMul.{u3, u2} α (f i)] (s : α) (x : forall (i : I), f i) (i : I), Eq.{succ u2} (f i) (SMul.smul.{u3, max u1 u2} α (forall (i : I), f i) (Pi.instSMul.{u1, u2, u3} I α (fun (i : I) => f i) (fun (i : I) => _inst_1 i)) s x i) (SMul.smul.{u3, u2} α (f i) (_inst_1 i) s (x i))
but is expected to have type
  forall {I : Type.{u2}} {α : Type.{u1}} {f : I -> Type.{u3}} [_inst_1 : forall (i : I), SMul.{u1, u3} α (f i)] (s : α) (x : forall (i : I), f i) (i : I), Eq.{succ u3} (f i) (HSMul.hSMul.{u1, max u2 u3, max u2 u3} α (forall (i : I), f i) (forall (i : I), f i) (instHSMul.{u1, max u2 u3} α (forall (i : I), f i) (Pi.instSMul.{u2, u3, u1} I α (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) s x i) (HSMul.hSMul.{u1, u3, u3} α (f i) (f i) (instHSMul.{u1, u3} α (f i) (_inst_1 i)) s (x i))
Case conversion may be inaccurate. Consider using '#align pi.smul_apply Pi.smul_applyₓ'. -/
@[simp, to_additive]
theorem smul_apply [∀ i, SMul α <| f i] (s : α) (x : ∀ i, f i) (i : I) : (s • x) i = s • x i :=
  rfl
#align pi.smul_apply Pi.smul_apply
#align pi.vadd_apply Pi.vadd_apply

/- warning: pi.smul_def -> Pi.smul_def is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u3}} {f : I -> Type.{u2}} [_inst_1 : forall (i : I), SMul.{u3, u2} α (f i)] (s : α) (x : forall (i : I), f i), Eq.{succ (max u1 u2)} (forall (i : I), f i) (SMul.smul.{u3, max u1 u2} α (forall (i : I), f i) (Pi.instSMul.{u1, u2, u3} I α (fun (i : I) => f i) (fun (i : I) => _inst_1 i)) s x) (fun (i : I) => SMul.smul.{u3, u2} α (f i) (_inst_1 i) s (x i))
but is expected to have type
  forall {I : Type.{u2}} {α : Type.{u1}} {f : I -> Type.{u3}} [_inst_1 : forall (i : I), SMul.{u1, u3} α (f i)] (s : α) (x : forall (i : I), f i), Eq.{max (succ u2) (succ u3)} (forall (i : I), f i) (HSMul.hSMul.{u1, max u2 u3, max u2 u3} α (forall (i : I), f i) (forall (i : I), f i) (instHSMul.{u1, max u2 u3} α (forall (i : I), f i) (Pi.instSMul.{u2, u3, u1} I α (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) s x) (fun (i : I) => HSMul.hSMul.{u1, u3, u3} α (f i) (f i) (instHSMul.{u1, u3} α (f i) (_inst_1 i)) s (x i))
Case conversion may be inaccurate. Consider using '#align pi.smul_def Pi.smul_defₓ'. -/
@[to_additive]
theorem smul_def [∀ i, SMul α <| f i] (s : α) (x : ∀ i, f i) : s • x = fun i => s • x i :=
  rfl
#align pi.smul_def Pi.smul_def
#align pi.vadd_def Pi.vadd_def

/- warning: pi.smul_const -> Pi.smul_const is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : SMul.{u2, u3} α β] (a : α) (b : β), Eq.{succ (max u1 u3)} (I -> β) (SMul.smul.{u2, max u1 u3} α (I -> β) (Pi.instSMul.{u1, u3, u2} I α (fun (ᾰ : I) => β) (fun (i : I) => _inst_1)) a (Function.const.{succ u3, succ u1} β I b)) (Function.const.{succ u3, succ u1} β I (SMul.smul.{u2, u3} α β _inst_1 a b))
but is expected to have type
  forall {I : Type.{u3}} {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : SMul.{u1, u2} α β] (a : α) (b : β), Eq.{max (succ u3) (succ u2)} (I -> β) (HSMul.hSMul.{u1, max u3 u2, max u3 u2} α (I -> β) (I -> β) (instHSMul.{u1, max u3 u2} α (I -> β) (Pi.instSMul.{u3, u2, u1} I α (fun (a._@.Init.Prelude._hyg.54 : I) => β) (fun (i : I) => _inst_1))) a (Function.const.{succ u2, succ u3} β I b)) (Function.const.{succ u2, succ u3} β I (HSMul.hSMul.{u1, u2, u2} α β β (instHSMul.{u1, u2} α β _inst_1) a b))
Case conversion may be inaccurate. Consider using '#align pi.smul_const Pi.smul_constₓ'. -/
@[simp, to_additive]
theorem smul_const [SMul α β] (a : α) (b : β) : a • const I b = const I (a • b) :=
  rfl
#align pi.smul_const Pi.smul_const
#align pi.vadd_const Pi.vadd_const

/- warning: pi.smul_comp -> Pi.smul_comp is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} {γ : Type.{u4}} [_inst_1 : SMul.{u2, u4} α γ] (a : α) (x : β -> γ) (y : I -> β), Eq.{max (succ u1) (succ u4)} (I -> γ) (Function.comp.{succ u1, succ u3, succ u4} I β γ (SMul.smul.{u2, max u3 u4} α (β -> γ) (Pi.instSMul.{u3, u4, u2} β α (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)) a x) y) (SMul.smul.{u2, max u1 u4} α (I -> γ) (Pi.instSMul.{u1, u4, u2} I α (fun (ᾰ : I) => γ) (fun (i : I) => _inst_1)) a (Function.comp.{succ u1, succ u3, succ u4} I β γ x y))
but is expected to have type
  forall {I : Type.{u4}} {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : SMul.{u2, u3} α γ] (a : α) (x : β -> γ) (y : I -> β), Eq.{max (succ u4) (succ u3)} (I -> γ) (Function.comp.{succ u4, succ u1, succ u3} I β γ (HSMul.hSMul.{u2, max u1 u3, max u1 u3} α (β -> γ) (β -> γ) (instHSMul.{u2, max u1 u3} α (β -> γ) (Pi.instSMul.{u1, u3, u2} β α (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.1026 : β) => γ) (fun (i : β) => _inst_1))) a x) y) (HSMul.hSMul.{u2, max u4 u3, max u4 u3} α (I -> γ) (I -> γ) (instHSMul.{u2, max u4 u3} α (I -> γ) (Pi.instSMul.{u4, u3, u2} I α (fun (a._@.Init.Prelude._hyg.25 : I) => γ) (fun (i : I) => _inst_1))) a (Function.comp.{succ u4, succ u1, succ u3} I β γ x y))
Case conversion may be inaccurate. Consider using '#align pi.smul_comp Pi.smul_compₓ'. -/
@[to_additive]
theorem smul_comp [SMul α γ] (a : α) (x : β → γ) (y : I → β) : (a • x) ∘ y = a • x ∘ y :=
  rfl
#align pi.smul_comp Pi.smul_comp
#align pi.vadd_comp Pi.vadd_comp

@[to_additive Pi.instSMul]
instance hasPow [∀ i, Pow (f i) β] : Pow (∀ i, f i) β :=
  ⟨fun x b i => x i ^ b⟩
#align pi.has_pow Pi.hasPow
#align pi.has_smul Pi.instSMul

/- warning: pi.pow_apply -> Pi.pow_apply is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {β : Type.{u3}} {f : I -> Type.{u2}} [_inst_1 : forall (i : I), Pow.{u2, u3} (f i) β] (x : forall (i : I), f i) (b : β) (i : I), Eq.{succ u2} (f i) (HPow.hPow.{max u1 u2, u3, max u1 u2} (forall (i : I), f i) β (forall (i : I), f i) (instHPow.{max u1 u2, u3} (forall (i : I), f i) β (Pi.hasPow.{u1, u2, u3} I β (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) x b i) (HPow.hPow.{u2, u3, u2} (f i) β (f i) (instHPow.{u2, u3} (f i) β (_inst_1 i)) (x i) b)
but is expected to have type
  forall {I : Type.{u2}} {β : Type.{u1}} {f : I -> Type.{u3}} [_inst_1 : forall (i : I), Pow.{u3, u1} (f i) β] (x : forall (i : I), f i) (b : β) (i : I), Eq.{succ u3} (f i) (HPow.hPow.{max u2 u3, u1, max u2 u3} (forall (i : I), f i) β (forall (i : I), f i) (instHPow.{max u2 u3, u1} (forall (i : I), f i) β (Pi.instPow.{u2, u3, u1} I β (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) x b i) (HPow.hPow.{u3, u1, u3} (f i) β (f i) (instHPow.{u3, u1} (f i) β (_inst_1 i)) (x i) b)
Case conversion may be inaccurate. Consider using '#align pi.pow_apply Pi.pow_applyₓ'. -/
@[simp, to_additive Pi.smul_apply, to_additive_reorder 5]
theorem pow_apply [∀ i, Pow (f i) β] (x : ∀ i, f i) (b : β) (i : I) : (x ^ b) i = x i ^ b :=
  rfl
#align pi.pow_apply Pi.pow_apply
#align pi.smul_apply Pi.smul_apply

/- warning: pi.pow_def -> Pi.pow_def is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {β : Type.{u3}} {f : I -> Type.{u2}} [_inst_1 : forall (i : I), Pow.{u2, u3} (f i) β] (x : forall (i : I), f i) (b : β), Eq.{succ (max u1 u2)} (forall (i : I), f i) (HPow.hPow.{max u1 u2, u3, max u1 u2} (forall (i : I), f i) β (forall (i : I), f i) (instHPow.{max u1 u2, u3} (forall (i : I), f i) β (Pi.hasPow.{u1, u2, u3} I β (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) x b) (fun (i : I) => HPow.hPow.{u2, u3, u2} (f i) β (f i) (instHPow.{u2, u3} (f i) β (_inst_1 i)) (x i) b)
but is expected to have type
  forall {I : Type.{u2}} {β : Type.{u1}} {f : I -> Type.{u3}} [_inst_1 : forall (i : I), Pow.{u3, u1} (f i) β] (x : forall (i : I), f i) (b : β), Eq.{max (succ u2) (succ u3)} (forall (i : I), f i) (HPow.hPow.{max u2 u3, u1, max u2 u3} (forall (i : I), f i) β (forall (i : I), f i) (instHPow.{max u2 u3, u1} (forall (i : I), f i) β (Pi.instPow.{u2, u3, u1} I β (fun (i : I) => f i) (fun (i : I) => _inst_1 i))) x b) (fun (i : I) => HPow.hPow.{u3, u1, u3} (f i) β (f i) (instHPow.{u3, u1} (f i) β (_inst_1 i)) (x i) b)
Case conversion may be inaccurate. Consider using '#align pi.pow_def Pi.pow_defₓ'. -/
@[to_additive Pi.smul_def, to_additive_reorder 5]
theorem pow_def [∀ i, Pow (f i) β] (x : ∀ i, f i) (b : β) : x ^ b = fun i => x i ^ b :=
  rfl
#align pi.pow_def Pi.pow_def
#align pi.smul_def Pi.smul_def

/- warning: pi.const_pow -> Pi.const_pow is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} [_inst_1 : Pow.{u3, u2} β α] (b : β) (a : α), Eq.{succ (max u1 u3)} (I -> β) (HPow.hPow.{max u1 u3, u2, max u1 u3} (I -> β) α (I -> β) (instHPow.{max u1 u3, u2} (I -> β) α (Pi.hasPow.{u1, u3, u2} I α (fun (ᾰ : I) => β) (fun (i : I) => _inst_1))) (Function.const.{succ u3, succ u1} β I b) a) (Function.const.{succ u3, succ u1} β I (HPow.hPow.{u3, u2, u3} β α β (instHPow.{u3, u2} β α _inst_1) b a))
but is expected to have type
  forall {I : Type.{u3}} {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Pow.{u2, u1} α β] (b : α) (a : β), Eq.{max (succ u3) (succ u2)} (I -> α) (HPow.hPow.{max u3 u2, u1, max u3 u2} (I -> α) β (I -> α) (instHPow.{max u3 u2, u1} (I -> α) β (Pi.instPow.{u3, u2, u1} I β (fun (ᾰ : I) => α) (fun (i : I) => _inst_1))) (Function.const.{succ u2, succ u3} α I b) a) (Function.const.{succ u2, succ u3} α I (HPow.hPow.{u2, u1, u2} α β α (instHPow.{u2, u1} α β _inst_1) b a))
Case conversion may be inaccurate. Consider using '#align pi.const_pow Pi.const_powₓ'. -/
-- `to_additive` generates bad output if we take `has_pow α β`.
@[simp, to_additive smul_const, to_additive_reorder 5]
theorem const_pow [Pow β α] (b : β) (a : α) : const I b ^ a = const I (b ^ a) :=
  rfl
#align pi.const_pow Pi.const_pow
#align pi.smul_const Pi.smul_const

/- warning: pi.pow_comp -> Pi.pow_comp is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u2}} {β : Type.{u3}} {γ : Type.{u4}} [_inst_1 : Pow.{u4, u2} γ α] (x : β -> γ) (a : α) (y : I -> β), Eq.{max (succ u1) (succ u4)} (I -> γ) (Function.comp.{succ u1, succ u3, succ u4} I β γ (HPow.hPow.{max u3 u4, u2, max u3 u4} (β -> γ) α (β -> γ) (instHPow.{max u3 u4, u2} (β -> γ) α (Pi.hasPow.{u3, u4, u2} β α (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x a) y) (HPow.hPow.{max u1 u4, u2, max u1 u4} (I -> γ) α (I -> γ) (instHPow.{max u1 u4, u2} (I -> γ) α (Pi.hasPow.{u1, u4, u2} I α (fun (ᾰ : I) => γ) (fun (i : I) => _inst_1))) (Function.comp.{succ u1, succ u3, succ u4} I β γ x y) a)
but is expected to have type
  forall {I : Type.{u4}} {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : Pow.{u3, u2} γ α] (x : β -> γ) (a : α) (y : I -> β), Eq.{max (succ u4) (succ u3)} (I -> γ) (Function.comp.{succ u4, succ u1, succ u3} I β γ (HPow.hPow.{max u1 u3, u2, max u1 u3} (β -> γ) α (β -> γ) (instHPow.{max u1 u3, u2} (β -> γ) α (Pi.instPow.{u1, u3, u2} β α (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x a) y) (HPow.hPow.{max u4 u3, u2, max u4 u3} (I -> γ) α (I -> γ) (instHPow.{max u4 u3, u2} (I -> γ) α (Pi.instPow.{u4, u3, u2} I α (fun (ᾰ : I) => γ) (fun (i : I) => _inst_1))) (Function.comp.{succ u4, succ u1, succ u3} I β γ x y) a)
Case conversion may be inaccurate. Consider using '#align pi.pow_comp Pi.pow_compₓ'. -/
@[to_additive smul_comp, to_additive_reorder 6]
theorem pow_comp [Pow γ α] (x : β → γ) (a : α) (y : I → β) : (x ^ a) ∘ y = x ∘ y ^ a :=
  rfl
#align pi.pow_comp Pi.pow_comp
#align pi.smul_comp Pi.smul_comp

#print Pi.bit0_apply /-
@[simp]
theorem bit0_apply [∀ i, Add <| f i] : (bit0 x) i = bit0 (x i) :=
  rfl
#align pi.bit0_apply Pi.bit0_apply
-/

#print Pi.bit1_apply /-
@[simp]
theorem bit1_apply [∀ i, Add <| f i] [∀ i, One <| f i] : (bit1 x) i = bit1 (x i) :=
  rfl
#align pi.bit1_apply Pi.bit1_apply
-/

#print Pi.instInv /-
@[to_additive]
instance instInv [∀ i, Inv <| f i] : Inv (∀ i : I, f i) :=
  ⟨fun f i => (f i)⁻¹⟩
#align pi.has_inv Pi.instInv
#align pi.has_neg Pi.instNeg
-/

#print Pi.inv_apply /-
@[simp, to_additive]
theorem inv_apply [∀ i, Inv <| f i] : x⁻¹ i = (x i)⁻¹ :=
  rfl
#align pi.inv_apply Pi.inv_apply
#align pi.neg_apply Pi.neg_apply
-/

#print Pi.inv_def /-
@[to_additive]
theorem inv_def [∀ i, Inv <| f i] : x⁻¹ = fun i => (x i)⁻¹ :=
  rfl
#align pi.inv_def Pi.inv_def
#align pi.neg_def Pi.neg_def
-/

#print Pi.const_inv /-
@[to_additive]
theorem const_inv [Inv β] (a : β) : (const α a)⁻¹ = const α a⁻¹ :=
  rfl
#align pi.const_inv Pi.const_inv
#align pi.const_neg Pi.const_neg
-/

/- warning: pi.inv_comp -> Pi.inv_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Inv.{u3} γ] (x : β -> γ) (y : α -> β), Eq.{max (succ u1) (succ u3)} (α -> γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ (Inv.inv.{max u2 u3} (β -> γ) (Pi.instInv.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)) x) y) (Inv.inv.{max u1 u3} (α -> γ) (Pi.instInv.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1)) (Function.comp.{succ u1, succ u2, succ u3} α β γ x y))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : Inv.{u3} γ] (x : β -> γ) (y : α -> β), Eq.{max (succ u2) (succ u3)} (α -> γ) (Function.comp.{succ u2, succ u1, succ u3} α β γ (Inv.inv.{max u1 u3} (β -> γ) (Pi.instInv.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)) x) y) (Inv.inv.{max u2 u3} (α -> γ) (Pi.instInv.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1)) (Function.comp.{succ u2, succ u1, succ u3} α β γ x y))
Case conversion may be inaccurate. Consider using '#align pi.inv_comp Pi.inv_compₓ'. -/
@[to_additive]
theorem inv_comp [Inv γ] (x : β → γ) (y : α → β) : x⁻¹ ∘ y = (x ∘ y)⁻¹ :=
  rfl
#align pi.inv_comp Pi.inv_comp
#align pi.neg_comp Pi.neg_comp

#print Pi.instDiv /-
@[to_additive]
instance instDiv [∀ i, Div <| f i] : Div (∀ i : I, f i) :=
  ⟨fun f g i => f i / g i⟩
#align pi.has_div Pi.instDiv
#align pi.has_sub Pi.instSub
-/

#print Pi.div_apply /-
@[simp, to_additive]
theorem div_apply [∀ i, Div <| f i] : (x / y) i = x i / y i :=
  rfl
#align pi.div_apply Pi.div_apply
#align pi.sub_apply Pi.sub_apply
-/

#print Pi.div_def /-
@[to_additive]
theorem div_def [∀ i, Div <| f i] : x / y = fun i => x i / y i :=
  rfl
#align pi.div_def Pi.div_def
#align pi.sub_def Pi.sub_def
-/

/- warning: pi.div_comp -> Pi.div_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : Div.{u3} γ] (x : β -> γ) (y : β -> γ) (z : α -> β), Eq.{max (succ u1) (succ u3)} (α -> γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ (HDiv.hDiv.{max u2 u3, max u2 u3, max u2 u3} (β -> γ) (β -> γ) (β -> γ) (instHDiv.{max u2 u3} (β -> γ) (Pi.instDiv.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x y) z) (HDiv.hDiv.{max u1 u3, max u1 u3, max u1 u3} (α -> γ) (α -> γ) (α -> γ) (instHDiv.{max u1 u3} (α -> γ) (Pi.instDiv.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) (Function.comp.{succ u1, succ u2, succ u3} α β γ x z) (Function.comp.{succ u1, succ u2, succ u3} α β γ y z))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : Div.{u3} γ] (x : β -> γ) (y : β -> γ) (z : α -> β), Eq.{max (succ u2) (succ u3)} (α -> γ) (Function.comp.{succ u2, succ u1, succ u3} α β γ (HDiv.hDiv.{max u1 u3, max u1 u3, max u1 u3} (β -> γ) (β -> γ) (β -> γ) (instHDiv.{max u1 u3} (β -> γ) (Pi.instDiv.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) x y) z) (HDiv.hDiv.{max u2 u3, max u2 u3, max u2 u3} (α -> γ) (α -> γ) (α -> γ) (instHDiv.{max u2 u3} (α -> γ) (Pi.instDiv.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) (Function.comp.{succ u2, succ u1, succ u3} α β γ x z) (Function.comp.{succ u2, succ u1, succ u3} α β γ y z))
Case conversion may be inaccurate. Consider using '#align pi.div_comp Pi.div_compₓ'. -/
@[to_additive]
theorem div_comp [Div γ] (x y : β → γ) (z : α → β) : (x / y) ∘ z = x ∘ z / y ∘ z :=
  rfl
#align pi.div_comp Pi.div_comp
#align pi.sub_comp Pi.sub_comp

#print Pi.const_div /-
@[simp, to_additive]
theorem const_div [Div β] (a b : β) : const α a / const α b = const α (a / b) :=
  rfl
#align pi.const_div Pi.const_div
#align pi.const_sub Pi.const_sub
-/

section

variable [DecidableEq I]

variable [∀ i, One (f i)] [∀ i, One (g i)] [∀ i, One (h i)]

#print Pi.mulSingle /-
/-- The function supported at `i`, with value `x` there, and `1` elsewhere. -/
@[to_additive Pi.single "The function supported at `i`, with value `x` there, and `0` elsewhere."]
def mulSingle (i : I) (x : f i) : ∀ i, f i :=
  Function.update 1 i x
#align pi.mul_single Pi.mulSingle
#align pi.single Pi.single
-/

#print Pi.mulSingle_eq_same /-
@[simp, to_additive]
theorem mulSingle_eq_same (i : I) (x : f i) : mulSingle i x i = x :=
  Function.update_same i x _
#align pi.mul_single_eq_same Pi.mulSingle_eq_same
#align pi.single_eq_same Pi.single_eq_same
-/

#print Pi.mulSingle_eq_of_ne /-
@[simp, to_additive]
theorem mulSingle_eq_of_ne {i i' : I} (h : i' ≠ i) (x : f i) : mulSingle i x i' = 1 :=
  Function.update_noteq h x _
#align pi.mul_single_eq_of_ne Pi.mulSingle_eq_of_ne
#align pi.single_eq_of_ne Pi.single_eq_of_ne
-/

#print Pi.mulSingle_eq_of_ne' /-
/-- Abbreviation for `mul_single_eq_of_ne h.symm`, for ease of use by `simp`. -/
@[simp, to_additive "Abbreviation for `single_eq_of_ne h.symm`, for ease of\nuse by `simp`."]
theorem mulSingle_eq_of_ne' {i i' : I} (h : i ≠ i') (x : f i) : mulSingle i x i' = 1 :=
  mulSingle_eq_of_ne h.symm x
#align pi.mul_single_eq_of_ne' Pi.mulSingle_eq_of_ne'
#align pi.single_eq_of_ne' Pi.single_eq_of_ne'
-/

#print Pi.mulSingle_one /-
@[simp, to_additive]
theorem mulSingle_one (i : I) : mulSingle i (1 : f i) = 1 :=
  Function.update_eq_self _ _
#align pi.mul_single_one Pi.mulSingle_one
#align pi.single_zero Pi.single_zero
-/

/- warning: pi.mul_single_apply -> Pi.mulSingle_apply is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {β : Type.{u2}} [_inst_5 : One.{u2} β] (i : I) (x : β) (i' : I), Eq.{succ u2} β (Pi.mulSingle.{u1, u2} I (fun (i : I) => β) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5) i x i') (ite.{succ u2} β (Eq.{succ u1} I i' i) (_inst_1 i' i) x (OfNat.ofNat.{u2} β 1 (OfNat.mk.{u2} β 1 (One.one.{u2} β _inst_5))))
but is expected to have type
  forall {I : Type.{u2}} {_inst_1 : Type.{u1}} [β : DecidableEq.{succ u2} I] [_inst_5 : One.{u1} _inst_1] (i : I) (x : _inst_1) (i' : I), Eq.{succ u1} _inst_1 (Pi.mulSingle.{u2, u1} I (fun (i : I) => _inst_1) (fun (a : I) (b : I) => β a b) (fun (i : I) => _inst_5) i x i') (ite.{succ u1} _inst_1 (Eq.{succ u2} I i' i) (β i' i) x (OfNat.ofNat.{u1} _inst_1 1 (One.toOfNat1.{u1} _inst_1 _inst_5)))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_apply Pi.mulSingle_applyₓ'. -/
/-- On non-dependent functions, `pi.mul_single` can be expressed as an `ite` -/
@[to_additive "On non-dependent functions, `pi.single` can be expressed as an `ite`"]
theorem mulSingle_apply {β : Sort _} [One β] (i : I) (x : β) (i' : I) :
    mulSingle i x i' = if i' = i then x else 1 :=
  Function.update_apply 1 i x i'
#align pi.mul_single_apply Pi.mulSingle_apply
#align pi.single_apply Pi.single_apply

/- warning: pi.mul_single_comm -> Pi.mulSingle_comm is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} I] {β : Type.{u2}} [_inst_5 : One.{u2} β] (i : I) (x : β) (i' : I), Eq.{succ u2} β (Pi.mulSingle.{u1, u2} I (fun (i : I) => β) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5) i x i') (Pi.mulSingle.{u1, u2} I (fun (i' : I) => β) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5) i' x i)
but is expected to have type
  forall {I : Type.{u2}} {_inst_1 : Type.{u1}} [β : DecidableEq.{succ u2} I] [_inst_5 : One.{u1} _inst_1] (i : I) (x : _inst_1) (i' : I), Eq.{succ u1} _inst_1 (Pi.mulSingle.{u2, u1} I (fun (i : I) => _inst_1) (fun (a : I) (b : I) => β a b) (fun (i : I) => _inst_5) i x i') (Pi.mulSingle.{u2, u1} I (fun (i' : I) => _inst_1) (fun (a : I) (b : I) => β a b) (fun (i : I) => _inst_5) i' x i)
Case conversion may be inaccurate. Consider using '#align pi.mul_single_comm Pi.mulSingle_commₓ'. -/
/-- On non-dependent functions, `pi.mul_single` is symmetric in the two indices. -/
@[to_additive "On non-dependent functions, `pi.single` is symmetric in the two\nindices."]
theorem mulSingle_comm {β : Sort _} [One β] (i : I) (x : β) (i' : I) :
    mulSingle i x i' = mulSingle i' x i := by simp [mul_single_apply, eq_comm]
#align pi.mul_single_comm Pi.mulSingle_comm
#align pi.single_comm Pi.single_comm

#print Pi.apply_mulSingle /-
@[to_additive]
theorem apply_mulSingle (f' : ∀ i, f i → g i) (hf' : ∀ i, f' i 1 = 1) (i : I) (x : f i) (j : I) :
    f' j (mulSingle i x j) = mulSingle i (f' i x) j := by
  simpa only [Pi.one_apply, hf', mul_single] using Function.apply_update f' 1 i x j
#align pi.apply_mul_single Pi.apply_mulSingle
#align pi.apply_single Pi.apply_single
-/

#print Pi.apply_mulSingle₂ /-
@[to_additive apply_single₂]
theorem apply_mulSingle₂ (f' : ∀ i, f i → g i → h i) (hf' : ∀ i, f' i 1 1 = 1) (i : I) (x : f i)
    (y : g i) (j : I) : f' j (mulSingle i x j) (mulSingle i y j) = mulSingle i (f' i x y) j :=
  by
  by_cases h : j = i
  · subst h
    simp only [mul_single_eq_same]
  · simp only [mul_single_eq_of_ne h, hf']
#align pi.apply_mul_single₂ Pi.apply_mulSingle₂
#align pi.apply_single₂ Pi.apply_single₂
-/

/- warning: pi.mul_single_op -> Pi.mulSingle_op is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), One.{u2} (f i)] {g : I -> Type.{u3}} [_inst_5 : forall (i : I), One.{u3} (g i)] (op : forall (i : I), (f i) -> (g i)), (forall (i : I), Eq.{succ u3} (g i) (op i (OfNat.ofNat.{u2} (f i) 1 (OfNat.mk.{u2} (f i) 1 (One.one.{u2} (f i) (_inst_2 i))))) (OfNat.ofNat.{u3} (g i) 1 (OfNat.mk.{u3} (g i) 1 (One.one.{u3} (g i) (_inst_5 i))))) -> (forall (i : I) (x : f i), Eq.{max (succ u1) (succ u3)} (forall (i : I), g i) (Pi.mulSingle.{u1, u3} I (fun (i : I) => g i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5 i) i (op i x)) (fun (j : I) => op j (Pi.mulSingle.{u1, u2} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i x j)))
but is expected to have type
  forall {I : Type.{u2}} {f : I -> Type.{u3}} [_inst_1 : DecidableEq.{succ u2} I] [_inst_2 : forall (i : I), One.{u3} (f i)] {g : I -> Type.{u1}} [_inst_5 : forall (i : I), One.{u1} (g i)] (op : forall (i : I), (f i) -> (g i)), (forall (i : I), Eq.{succ u1} (g i) (op i (OfNat.ofNat.{u3} (f i) 1 (One.toOfNat1.{u3} (f i) (_inst_2 i)))) (OfNat.ofNat.{u1} (g i) 1 (One.toOfNat1.{u1} (g i) (_inst_5 i)))) -> (forall (i : I) (x : f i), Eq.{max (succ u2) (succ u1)} (forall (i : I), g i) (Pi.mulSingle.{u2, u1} I g (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5 i) i (op i x)) (fun (j : I) => op j (Pi.mulSingle.{u2, u3} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i x j)))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_op Pi.mulSingle_opₓ'. -/
@[to_additive]
theorem mulSingle_op {g : I → Type _} [∀ i, One (g i)] (op : ∀ i, f i → g i) (h : ∀ i, op i 1 = 1)
    (i : I) (x : f i) : mulSingle i (op i x) = fun j => op j (mulSingle i x j) :=
  Eq.symm <| funext <| apply_mulSingle op h i x
#align pi.mul_single_op Pi.mulSingle_op
#align pi.single_op Pi.single_op

/- warning: pi.mul_single_op₂ -> Pi.mulSingle_op₂ is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {f : I -> Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : forall (i : I), One.{u2} (f i)] {g₁ : I -> Type.{u3}} {g₂ : I -> Type.{u4}} [_inst_5 : forall (i : I), One.{u3} (g₁ i)] [_inst_6 : forall (i : I), One.{u4} (g₂ i)] (op : forall (i : I), (g₁ i) -> (g₂ i) -> (f i)), (forall (i : I), Eq.{succ u2} (f i) (op i (OfNat.ofNat.{u3} (g₁ i) 1 (OfNat.mk.{u3} (g₁ i) 1 (One.one.{u3} (g₁ i) (_inst_5 i)))) (OfNat.ofNat.{u4} (g₂ i) 1 (OfNat.mk.{u4} (g₂ i) 1 (One.one.{u4} (g₂ i) (_inst_6 i))))) (OfNat.ofNat.{u2} (f i) 1 (OfNat.mk.{u2} (f i) 1 (One.one.{u2} (f i) (_inst_2 i))))) -> (forall (i : I) (x₁ : g₁ i) (x₂ : g₂ i), Eq.{max (succ u1) (succ u2)} (forall (i : I), f i) (Pi.mulSingle.{u1, u2} I (fun (i : I) => f i) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i (op i x₁ x₂)) (fun (j : I) => op j (Pi.mulSingle.{u1, u3} I g₁ (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5 i) i x₁ j) (Pi.mulSingle.{u1, u4} I g₂ (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_6 i) i x₂ j)))
but is expected to have type
  forall {I : Type.{u3}} {f : I -> Type.{u4}} [_inst_1 : DecidableEq.{succ u3} I] [_inst_2 : forall (i : I), One.{u4} (f i)] {g₁ : I -> Type.{u2}} {g₂ : I -> Type.{u1}} [_inst_5 : forall (i : I), One.{u2} (g₁ i)] [_inst_6 : forall (i : I), One.{u1} (g₂ i)] (op : forall (i : I), (g₁ i) -> (g₂ i) -> (f i)), (forall (i : I), Eq.{succ u4} (f i) (op i (OfNat.ofNat.{u2} (g₁ i) 1 (One.toOfNat1.{u2} (g₁ i) (_inst_5 i))) (OfNat.ofNat.{u1} (g₂ i) 1 (One.toOfNat1.{u1} (g₂ i) (_inst_6 i)))) (OfNat.ofNat.{u4} (f i) 1 (One.toOfNat1.{u4} (f i) (_inst_2 i)))) -> (forall (i : I) (x₁ : g₁ i) (x₂ : g₂ i), Eq.{max (succ u3) (succ u4)} (forall (i : I), f i) (Pi.mulSingle.{u3, u4} I f (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_2 i) i (op i x₁ x₂)) (fun (j : I) => op j (Pi.mulSingle.{u3, u2} I g₁ (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_5 i) i x₁ j) (Pi.mulSingle.{u3, u1} I g₂ (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_6 i) i x₂ j)))
Case conversion may be inaccurate. Consider using '#align pi.mul_single_op₂ Pi.mulSingle_op₂ₓ'. -/
@[to_additive]
theorem mulSingle_op₂ {g₁ g₂ : I → Type _} [∀ i, One (g₁ i)] [∀ i, One (g₂ i)]
    (op : ∀ i, g₁ i → g₂ i → f i) (h : ∀ i, op i 1 1 = 1) (i : I) (x₁ : g₁ i) (x₂ : g₂ i) :
    mulSingle i (op i x₁ x₂) = fun j => op j (mulSingle i x₁ j) (mulSingle i x₂ j) :=
  Eq.symm <| funext <| apply_mulSingle₂ op h i x₁ x₂
#align pi.mul_single_op₂ Pi.mulSingle_op₂
#align pi.single_op₂ Pi.single_op₂

variable (f)

#print Pi.mulSingle_injective /-
@[to_additive]
theorem mulSingle_injective (i : I) : Function.Injective (mulSingle i : f i → ∀ i, f i) :=
  Function.update_injective _ i
#align pi.mul_single_injective Pi.mulSingle_injective
#align pi.single_injective Pi.single_injective
-/

#print Pi.mulSingle_inj /-
@[simp, to_additive]
theorem mulSingle_inj (i : I) {x y : f i} : mulSingle i x = mulSingle i y ↔ x = y :=
  (Pi.mulSingle_injective _ _).eq_iff
#align pi.mul_single_inj Pi.mulSingle_inj
#align pi.single_inj Pi.single_inj
-/

end

#print Pi.prod /-
/-- The mapping into a product type built from maps into each component. -/
@[simp]
protected def prod (f' : ∀ i, f i) (g' : ∀ i, g i) (i : I) : f i × g i :=
  (f' i, g' i)
#align pi.prod Pi.prod
-/

/- warning: pi.prod_fst_snd -> Pi.prod_fst_snd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ (max u1 u2)) (succ u1) (succ u2)} ((Prod.{u1, u2} α β) -> (Prod.{u1, u2} α β)) (Pi.prod.{max u1 u2, u1, u2} (Prod.{u1, u2} α β) (fun (self : Prod.{u1, u2} α β) => α) (fun (self : Prod.{u1, u2} α β) => β) (Prod.fst.{u1, u2} α β) (Prod.snd.{u1, u2} α β)) (id.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u2) (succ u1)} ((Prod.{u2, u1} α β) -> (Prod.{u2, u1} α β)) (Pi.prod.{max u2 u1, u2, u1} (Prod.{u2, u1} α β) (fun (self : Prod.{u2, u1} α β) => α) (fun (self : Prod.{u2, u1} α β) => β) (Prod.fst.{u2, u1} α β) (Prod.snd.{u2, u1} α β)) (id.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β))
Case conversion may be inaccurate. Consider using '#align pi.prod_fst_snd Pi.prod_fst_sndₓ'. -/
@[simp]
theorem prod_fst_snd : Pi.prod (Prod.fst : α × β → α) (Prod.snd : α × β → β) = id :=
  funext fun _ => Prod.mk.eta
#align pi.prod_fst_snd Pi.prod_fst_snd

/- warning: pi.prod_snd_fst -> Pi.prod_snd_fst is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ (max u1 u2)) (succ u2) (succ u1)} ((Prod.{u1, u2} α β) -> (Prod.{u2, u1} β α)) (Pi.prod.{max u1 u2, u2, u1} (Prod.{u1, u2} α β) (fun (self : Prod.{u1, u2} α β) => β) (fun (self : Prod.{u1, u2} α β) => α) (Prod.snd.{u1, u2} α β) (Prod.fst.{u1, u2} α β)) (Prod.swap.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u2) (succ u1)} ((Prod.{u2, u1} α β) -> (Prod.{u1, u2} β α)) (Pi.prod.{max u2 u1, u1, u2} (Prod.{u2, u1} α β) (fun (self : Prod.{u2, u1} α β) => β) (fun (self : Prod.{u2, u1} α β) => α) (Prod.snd.{u2, u1} α β) (Prod.fst.{u2, u1} α β)) (Prod.swap.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align pi.prod_snd_fst Pi.prod_snd_fstₓ'. -/
@[simp]
theorem prod_snd_fst : Pi.prod (Prod.snd : α × β → β) (Prod.fst : α × β → α) = Prod.swap :=
  rfl
#align pi.prod_snd_fst Pi.prod_snd_fst

end Pi

namespace Function

section Extend

#print Function.extend_one /-
@[to_additive]
theorem extend_one [One γ] (f : α → β) : Function.extend f (1 : α → γ) (1 : β → γ) = 1 :=
  funext fun _ => by apply if_t_t _ _
#align function.extend_one Function.extend_one
#align function.extend_zero Function.extend_zero
-/

#print Function.extend_mul /-
@[to_additive]
theorem extend_mul [Mul γ] (f : α → β) (g₁ g₂ : α → γ) (e₁ e₂ : β → γ) :
    Function.extend f (g₁ * g₂) (e₁ * e₂) = Function.extend f g₁ e₁ * Function.extend f g₂ e₂ :=
  funext fun _ => by convert(apply_dite₂ (· * ·) _ _ _ _ _).symm
#align function.extend_mul Function.extend_mul
#align function.extend_add Function.extend_add
-/

#print Function.extend_inv /-
@[to_additive]
theorem extend_inv [Inv γ] (f : α → β) (g : α → γ) (e : β → γ) :
    Function.extend f g⁻¹ e⁻¹ = (Function.extend f g e)⁻¹ :=
  funext fun _ => by convert(apply_dite Inv.inv _ _ _).symm
#align function.extend_inv Function.extend_inv
#align function.extend_neg Function.extend_neg
-/

#print Function.extend_div /-
@[to_additive]
theorem extend_div [Div γ] (f : α → β) (g₁ g₂ : α → γ) (e₁ e₂ : β → γ) :
    Function.extend f (g₁ / g₂) (e₁ / e₂) = Function.extend f g₁ e₁ / Function.extend f g₂ e₂ :=
  funext fun _ => by convert(apply_dite₂ (· / ·) _ _ _ _ _).symm
#align function.extend_div Function.extend_div
#align function.extend_sub Function.extend_sub
-/

end Extend

#print Function.surjective_pi_map /-
theorem surjective_pi_map {F : ∀ i, f i → g i} (hF : ∀ i, Surjective (F i)) :
    Surjective fun x : ∀ i, f i => fun i => F i (x i) := fun y =>
  ⟨fun i => (hF i (y i)).some, funext fun i => (hF i (y i)).choose_spec⟩
#align function.surjective_pi_map Function.surjective_pi_map
-/

#print Function.injective_pi_map /-
theorem injective_pi_map {F : ∀ i, f i → g i} (hF : ∀ i, Injective (F i)) :
    Injective fun x : ∀ i, f i => fun i => F i (x i) := fun x y h =>
  funext fun i => hF i <| (congr_fun h i : _)
#align function.injective_pi_map Function.injective_pi_map
-/

#print Function.bijective_pi_map /-
theorem bijective_pi_map {F : ∀ i, f i → g i} (hF : ∀ i, Bijective (F i)) :
    Bijective fun x : ∀ i, f i => fun i => F i (x i) :=
  ⟨injective_pi_map fun i => (hF i).Injective, surjective_pi_map fun i => (hF i).Surjective⟩
#align function.bijective_pi_map Function.bijective_pi_map
-/

end Function

#print uniqueOfSurjectiveOne /-
/-- If the one function is surjective, the codomain is trivial. -/
@[to_additive "If the zero function is surjective, the codomain is trivial."]
def uniqueOfSurjectiveOne (α : Type _) {β : Type _} [One β] (h : Function.Surjective (1 : α → β)) :
    Unique β :=
  h.uniqueOfSurjectiveConst α (1 : β)
#align unique_of_surjective_one uniqueOfSurjectiveOne
#align unique_of_surjective_zero uniqueOfSurjectiveZero
-/

/- warning: subsingleton.pi_mul_single_eq -> Subsingleton.pi_mulSingle_eq is a dubious translation:
lean 3 declaration is
  forall {I : Type.{u1}} {α : Type.{u2}} [_inst_1 : DecidableEq.{succ u1} I] [_inst_2 : Subsingleton.{succ u1} I] [_inst_3 : One.{u2} α] (i : I) (x : α), Eq.{max (succ u1) (succ u2)} (I -> α) (Pi.mulSingle.{u1, u2} I (fun (i : I) => α) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_3) i x) (fun (_x : I) => x)
but is expected to have type
  forall {I : Type.{u2}} {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u2} I] [_inst_2 : Subsingleton.{succ u2} I] [_inst_3 : One.{u1} α] (i : I) (x : α), Eq.{max (succ u2) (succ u1)} (I -> α) (Pi.mulSingle.{u2, u1} I (fun (i : I) => α) (fun (a : I) (b : I) => _inst_1 a b) (fun (i : I) => _inst_3) i x) (fun (_x : I) => x)
Case conversion may be inaccurate. Consider using '#align subsingleton.pi_mul_single_eq Subsingleton.pi_mulSingle_eqₓ'. -/
@[to_additive Subsingleton.pi_single_eq]
theorem Subsingleton.pi_mulSingle_eq {α : Type _} [DecidableEq I] [Subsingleton I] [One α] (i : I)
    (x : α) : Pi.mulSingle i x = fun _ => x :=
  funext fun j => by rw [Subsingleton.elim j i, Pi.mulSingle_eq_same]
#align subsingleton.pi_mul_single_eq Subsingleton.pi_mulSingle_eq
#align subsingleton.pi_single_eq Subsingleton.pi_single_eq

namespace Sum

variable (a a' : α → γ) (b b' : β → γ)

/- warning: sum.elim_one_one -> Sum.elim_one_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : One.{u3} γ], Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (OfNat.ofNat.{max u1 u3} (α -> γ) 1 (OfNat.mk.{max u1 u3} (α -> γ) 1 (One.one.{max u1 u3} (α -> γ) (Pi.instOne.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))))) (OfNat.ofNat.{max u2 u3} (β -> γ) 1 (OfNat.mk.{max u2 u3} (β -> γ) 1 (One.one.{max u2 u3} (β -> γ) (Pi.instOne.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)))))) (OfNat.ofNat.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) 1 (OfNat.mk.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) 1 (One.one.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) (Pi.instOne.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (i : Sum.{u1, u2} α β) => _inst_1)))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} [_inst_1 : One.{u3} γ], Eq.{max (max (succ u2) (succ u1)) (succ u3)} ((Sum.{u2, u1} α β) -> γ) (Sum.elim.{u2, u1, succ u3} α β γ (OfNat.ofNat.{max u2 u3} (α -> γ) 1 (One.toOfNat1.{max u2 u3} (α -> γ) (Pi.instOne.{u2, u3} α (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.4107 : α) => γ) (fun (i : α) => _inst_1)))) (OfNat.ofNat.{max u1 u3} (β -> γ) 1 (One.toOfNat1.{max u1 u3} (β -> γ) (Pi.instOne.{u1, u3} β (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.4113 : β) => γ) (fun (i : β) => _inst_1))))) (OfNat.ofNat.{max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) 1 (One.toOfNat1.{max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) (Pi.instOne.{max u2 u1, u3} (Sum.{u2, u1} α β) (fun (a._@.Mathlib.Data.Sum.Basic._hyg.1871 : Sum.{u2, u1} α β) => γ) (fun (i : Sum.{u2, u1} α β) => _inst_1))))
Case conversion may be inaccurate. Consider using '#align sum.elim_one_one Sum.elim_one_oneₓ'. -/
@[simp, to_additive]
theorem elim_one_one [One γ] : Sum.elim (1 : α → γ) (1 : β → γ) = 1 :=
  Sum.elim_const_const 1
#align sum.elim_one_one Sum.elim_one_one
#align sum.elim_zero_zero Sum.elim_zero_zero

/- warning: sum.elim_mul_single_one -> Sum.elim_mulSingle_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : DecidableEq.{succ u1} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : One.{u3} γ] (i : α) (c : γ), Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (Pi.mulSingle.{u1, u3} α (fun (i : α) => γ) (fun (a : α) (b : α) => _inst_1 a b) (fun (i : α) => _inst_3) i c) (OfNat.ofNat.{max u2 u3} (β -> γ) 1 (OfNat.mk.{max u2 u3} (β -> γ) 1 (One.one.{max u2 u3} (β -> γ) (Pi.instOne.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_3)))))) (Pi.mulSingle.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (a : Sum.{u1, u2} α β) (b : Sum.{u1, u2} α β) => Sum.decidableEq.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) β (fun (a : β) (b : β) => _inst_2 a b) a b) (fun (i : Sum.{u1, u2} α β) => _inst_3) (Sum.inl.{u1, u2} α β i) c)
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : DecidableEq.{succ u3} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : One.{u1} γ] (i : α) (c : γ), Eq.{max (max (succ u3) (succ u2)) (succ u1)} ((Sum.{u3, u2} α β) -> γ) (Sum.elim.{u3, u2, succ u1} α β γ (Pi.mulSingle.{u3, u1} α (fun (i : α) => γ) (fun (a : α) (b : α) => _inst_1 a b) (fun (i : α) => _inst_3) i c) (OfNat.ofNat.{max u2 u1} (β -> γ) 1 (One.toOfNat1.{max u2 u1} (β -> γ) (Pi.instOne.{u2, u1} β (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.4181 : β) => γ) (fun (i : β) => _inst_3))))) (Pi.mulSingle.{max u2 u3, u1} (Sum.{u3, u2} α β) (fun (ᾰ : Sum.{u3, u2} α β) => γ) (fun (a : Sum.{u3, u2} α β) (b : Sum.{u3, u2} α β) => Sum.instDecidableEqSum.{u3, u2} α β (fun (a : α) (b : α) => _inst_1 a b) (fun (a : β) (b : β) => _inst_2 a b) a b) (fun (i : Sum.{u3, u2} α β) => _inst_3) (Sum.inl.{u3, u2} α β i) c)
Case conversion may be inaccurate. Consider using '#align sum.elim_mul_single_one Sum.elim_mulSingle_oneₓ'. -/
@[simp, to_additive]
theorem elim_mulSingle_one [DecidableEq α] [DecidableEq β] [One γ] (i : α) (c : γ) :
    Sum.elim (Pi.mulSingle i c) (1 : β → γ) = Pi.mulSingle (Sum.inl i) c := by
  simp only [Pi.mulSingle, Sum.elim_update_left, elim_one_one]
#align sum.elim_mul_single_one Sum.elim_mulSingle_one
#align sum.elim_single_zero Sum.elim_single_zero

/- warning: sum.elim_one_mul_single -> Sum.elim_one_mulSingle is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} [_inst_1 : DecidableEq.{succ u1} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : One.{u3} γ] (i : β) (c : γ), Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (OfNat.ofNat.{max u1 u3} (α -> γ) 1 (OfNat.mk.{max u1 u3} (α -> γ) 1 (One.one.{max u1 u3} (α -> γ) (Pi.instOne.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_3))))) (Pi.mulSingle.{u2, u3} β (fun (ᾰ : β) => γ) (fun (a : β) (b : β) => _inst_2 a b) (fun (i : β) => _inst_3) i c)) (Pi.mulSingle.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (a : Sum.{u1, u2} α β) (b : Sum.{u1, u2} α β) => Sum.decidableEq.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) β (fun (a : β) (b : β) => _inst_2 a b) a b) (fun (i : Sum.{u1, u2} α β) => _inst_3) (Sum.inr.{u1, u2} α β i) c)
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} [_inst_1 : DecidableEq.{succ u3} α] [_inst_2 : DecidableEq.{succ u2} β] [_inst_3 : One.{u1} γ] (i : β) (c : γ), Eq.{max (max (succ u3) (succ u2)) (succ u1)} ((Sum.{u3, u2} α β) -> γ) (Sum.elim.{u3, u2, succ u1} α β γ (OfNat.ofNat.{max u3 u1} (α -> γ) 1 (One.toOfNat1.{max u3 u1} (α -> γ) (Pi.instOne.{u3, u1} α (fun (a._@.Mathlib.Data.Pi.Algebra._hyg.4249 : α) => γ) (fun (i : α) => _inst_3)))) (Pi.mulSingle.{u2, u1} β (fun (ᾰ : β) => γ) (fun (a : β) (b : β) => _inst_2 a b) (fun (i : β) => _inst_3) i c)) (Pi.mulSingle.{max u2 u3, u1} (Sum.{u3, u2} α β) (fun (ᾰ : Sum.{u3, u2} α β) => γ) (fun (a : Sum.{u3, u2} α β) (b : Sum.{u3, u2} α β) => Sum.instDecidableEqSum.{u3, u2} α β (fun (a : α) (b : α) => _inst_1 a b) (fun (a : β) (b : β) => _inst_2 a b) a b) (fun (i : Sum.{u3, u2} α β) => _inst_3) (Sum.inr.{u3, u2} α β i) c)
Case conversion may be inaccurate. Consider using '#align sum.elim_one_mul_single Sum.elim_one_mulSingleₓ'. -/
@[simp, to_additive]
theorem elim_one_mulSingle [DecidableEq α] [DecidableEq β] [One γ] (i : β) (c : γ) :
    Sum.elim (1 : α → γ) (Pi.mulSingle i c) = Pi.mulSingle (Sum.inr i) c := by
  simp only [Pi.mulSingle, Sum.elim_update_right, elim_one_one]
#align sum.elim_one_mul_single Sum.elim_one_mulSingle
#align sum.elim_zero_single Sum.elim_zero_single

/- warning: sum.elim_inv_inv -> Sum.elim_inv_inv is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} (a : α -> γ) (b : β -> γ) [_inst_1 : Inv.{u3} γ], Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (Inv.inv.{max u1 u3} (α -> γ) (Pi.instInv.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1)) a) (Inv.inv.{max u2 u3} (β -> γ) (Pi.instInv.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)) b)) (Inv.inv.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) (Pi.instInv.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (i : Sum.{u1, u2} α β) => _inst_1)) (Sum.elim.{u1, u2, succ u3} α β γ a b))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} (a : α -> γ) (b : β -> γ) [_inst_1 : Inv.{u3} γ], Eq.{max (max (succ u2) (succ u1)) (succ u3)} ((Sum.{u2, u1} α β) -> γ) (Sum.elim.{u2, u1, succ u3} α β γ (Inv.inv.{max u2 u3} (α -> γ) (Pi.instInv.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1)) a) (Inv.inv.{max u3 u1} (β -> γ) (Pi.instInv.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1)) b)) (Inv.inv.{max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) (Pi.instInv.{max u2 u1, u3} (Sum.{u2, u1} α β) (fun (ᾰ : Sum.{u2, u1} α β) => γ) (fun (i : Sum.{u2, u1} α β) => _inst_1)) (Sum.elim.{u2, u1, succ u3} α β γ a b))
Case conversion may be inaccurate. Consider using '#align sum.elim_inv_inv Sum.elim_inv_invₓ'. -/
@[to_additive]
theorem elim_inv_inv [Inv γ] : Sum.elim a⁻¹ b⁻¹ = (Sum.elim a b)⁻¹ :=
  (Sum.comp_elim Inv.inv a b).symm
#align sum.elim_inv_inv Sum.elim_inv_inv
#align sum.elim_neg_neg Sum.elim_neg_neg

/- warning: sum.elim_mul_mul -> Sum.elim_mul_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} (a : α -> γ) (a' : α -> γ) (b : β -> γ) (b' : β -> γ) [_inst_1 : Mul.{u3} γ], Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (HMul.hMul.{max u1 u3, max u1 u3, max u1 u3} (α -> γ) (α -> γ) (α -> γ) (instHMul.{max u1 u3} (α -> γ) (Pi.instMul.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) a a') (HMul.hMul.{max u2 u3, max u2 u3, max u2 u3} (β -> γ) (β -> γ) (β -> γ) (instHMul.{max u2 u3} (β -> γ) (Pi.instMul.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) b b')) (HMul.hMul.{max (max u1 u2) u3, max (max u1 u2) u3, max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) ((Sum.{u1, u2} α β) -> γ) ((Sum.{u1, u2} α β) -> γ) (instHMul.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) (Pi.instMul.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (i : Sum.{u1, u2} α β) => _inst_1))) (Sum.elim.{u1, u2, succ u3} α β γ a b) (Sum.elim.{u1, u2, succ u3} α β γ a' b'))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} (a : α -> γ) (a' : α -> γ) (b : β -> γ) (b' : β -> γ) [_inst_1 : Mul.{u3} γ], Eq.{max (max (succ u2) (succ u1)) (succ u3)} ((Sum.{u2, u1} α β) -> γ) (Sum.elim.{u2, u1, succ u3} α β γ (HMul.hMul.{max u2 u3, max u2 u3, max u2 u3} (α -> γ) (α -> γ) (α -> γ) (instHMul.{max u2 u3} (α -> γ) (Pi.instMul.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) a a') (HMul.hMul.{max u1 u3, max u1 u3, max u1 u3} (β -> γ) (β -> γ) (β -> γ) (instHMul.{max u1 u3} (β -> γ) (Pi.instMul.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) b b')) (HMul.hMul.{max (max u2 u1) u3, max (max u2 u1) u3, max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) ((Sum.{u2, u1} α β) -> γ) ((Sum.{u2, u1} α β) -> γ) (instHMul.{max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) (Pi.instMul.{max u2 u1, u3} (Sum.{u2, u1} α β) (fun (ᾰ : Sum.{u2, u1} α β) => γ) (fun (i : Sum.{u2, u1} α β) => _inst_1))) (Sum.elim.{u2, u1, succ u3} α β γ a b) (Sum.elim.{u2, u1, succ u3} α β γ a' b'))
Case conversion may be inaccurate. Consider using '#align sum.elim_mul_mul Sum.elim_mul_mulₓ'. -/
@[to_additive]
theorem elim_mul_mul [Mul γ] : Sum.elim (a * a') (b * b') = Sum.elim a b * Sum.elim a' b' :=
  by
  ext x
  cases x <;> rfl
#align sum.elim_mul_mul Sum.elim_mul_mul
#align sum.elim_add_add Sum.elim_add_add

/- warning: sum.elim_div_div -> Sum.elim_div_div is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} (a : α -> γ) (a' : α -> γ) (b : β -> γ) (b' : β -> γ) [_inst_1 : Div.{u3} γ], Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Sum.{u1, u2} α β) -> γ) (Sum.elim.{u1, u2, succ u3} α β γ (HDiv.hDiv.{max u1 u3, max u1 u3, max u1 u3} (α -> γ) (α -> γ) (α -> γ) (instHDiv.{max u1 u3} (α -> γ) (Pi.instDiv.{u1, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) a a') (HDiv.hDiv.{max u2 u3, max u2 u3, max u2 u3} (β -> γ) (β -> γ) (β -> γ) (instHDiv.{max u2 u3} (β -> γ) (Pi.instDiv.{u2, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) b b')) (HDiv.hDiv.{max (max u1 u2) u3, max (max u1 u2) u3, max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) ((Sum.{u1, u2} α β) -> γ) ((Sum.{u1, u2} α β) -> γ) (instHDiv.{max (max u1 u2) u3} ((Sum.{u1, u2} α β) -> γ) (Pi.instDiv.{max u1 u2, u3} (Sum.{u1, u2} α β) (fun (ᾰ : Sum.{u1, u2} α β) => γ) (fun (i : Sum.{u1, u2} α β) => _inst_1))) (Sum.elim.{u1, u2, succ u3} α β γ a b) (Sum.elim.{u1, u2, succ u3} α β γ a' b'))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} (a : α -> γ) (a' : α -> γ) (b : β -> γ) (b' : β -> γ) [_inst_1 : Div.{u3} γ], Eq.{max (max (succ u2) (succ u1)) (succ u3)} ((Sum.{u2, u1} α β) -> γ) (Sum.elim.{u2, u1, succ u3} α β γ (HDiv.hDiv.{max u2 u3, max u2 u3, max u2 u3} (α -> γ) (α -> γ) (α -> γ) (instHDiv.{max u2 u3} (α -> γ) (Pi.instDiv.{u2, u3} α (fun (ᾰ : α) => γ) (fun (i : α) => _inst_1))) a a') (HDiv.hDiv.{max u1 u3, max u1 u3, max u1 u3} (β -> γ) (β -> γ) (β -> γ) (instHDiv.{max u1 u3} (β -> γ) (Pi.instDiv.{u1, u3} β (fun (ᾰ : β) => γ) (fun (i : β) => _inst_1))) b b')) (HDiv.hDiv.{max (max u2 u1) u3, max (max u2 u1) u3, max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) ((Sum.{u2, u1} α β) -> γ) ((Sum.{u2, u1} α β) -> γ) (instHDiv.{max (max u2 u1) u3} ((Sum.{u2, u1} α β) -> γ) (Pi.instDiv.{max u2 u1, u3} (Sum.{u2, u1} α β) (fun (ᾰ : Sum.{u2, u1} α β) => γ) (fun (i : Sum.{u2, u1} α β) => _inst_1))) (Sum.elim.{u2, u1, succ u3} α β γ a b) (Sum.elim.{u2, u1, succ u3} α β γ a' b'))
Case conversion may be inaccurate. Consider using '#align sum.elim_div_div Sum.elim_div_divₓ'. -/
@[to_additive]
theorem elim_div_div [Div γ] : Sum.elim (a / a') (b / b') = Sum.elim a b / Sum.elim a' b' :=
  by
  ext x
  cases x <;> rfl
#align sum.elim_div_div Sum.elim_div_div
#align sum.elim_sub_sub Sum.elim_sub_sub

end Sum


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

! This file was ported from Lean 3 source module algebraic_topology.dold_kan.n_reflects_iso
! leanprover-community/mathlib commit 88bca0ce5d22ebfd9e73e682e51d60ea13b48347
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.AlgebraicTopology.DoldKan.FunctorN
import Mathbin.AlgebraicTopology.DoldKan.Decomposition
import Mathbin.CategoryTheory.Idempotents.HomologicalComplex
import Mathbin.CategoryTheory.Idempotents.KaroubiKaroubi

/-!

# N₁ and N₂ reflects isomorphisms

In this file, it is shown that the functors
`N₁ : simplicial_object C ⥤ karoubi (chain_complex C ℕ)` and
`N₂ : karoubi (simplicial_object C) ⥤ karoubi (chain_complex C ℕ))`
reflect isomorphisms for any preadditive category `C`.

-/

open CategoryTheory

open CategoryTheory.Category

open CategoryTheory.Idempotents

open Opposite

open Simplicial

namespace AlgebraicTopology

namespace DoldKan

variable {C : Type _} [Category C] [Preadditive C]

open MorphComponents

instance : ReflectsIsomorphisms (n₁ : SimplicialObject C ⥤ Karoubi (ChainComplex C ℕ)) :=
  ⟨fun X Y f => by
    intro
    -- restating the result in a way that allows induction on the degree n
    suffices ∀ n : ℕ, is_iso (f.app (op [n]))
      by
      haveI : ∀ Δ : SimplexCategoryᵒᵖ, is_iso (f.app Δ) := fun Δ => this Δ.unop.len
      apply nat_iso.is_iso_of_is_iso_app
    -- restating the assumption in a more practical form
    have h₁ := HomologicalComplex.congr_hom (karoubi.hom_ext.mp (is_iso.hom_inv_id (N₁.map f)))
    have h₂ := HomologicalComplex.congr_hom (karoubi.hom_ext.mp (is_iso.inv_hom_id (N₁.map f)))
    have h₃ := fun n =>
      karoubi.homological_complex.p_comm_f_assoc (inv (N₁.map f)) n (f.app (op [n]))
    simp only [N₁_map_f, karoubi.comp_f, HomologicalComplex.comp_f,
      alternating_face_map_complex.map_f, N₁_obj_p, karoubi.id_eq, assoc] at h₁ h₂ h₃
    -- we have to construct an inverse to f in degree n, by induction on n
    intro n
    induction' n with n hn
    -- degree 0
    · use (inv (N₁.map f)).f.f 0
      have h₁₀ := h₁ 0
      have h₂₀ := h₂ 0
      dsimp at h₁₀ h₂₀
      simp only [id_comp, comp_id] at h₁₀ h₂₀
      tauto
    -- induction step
    · haveI := hn
      use φ {
            a := P_infty.f (n + 1) ≫ (inv (N₁.map f)).f.f (n + 1)
            b := fun i => inv (f.app (op [n])) ≫ X.σ i }
      simp only [morph_components.id, ← id_φ, ← pre_comp_φ, pre_comp, ← post_comp_φ, post_comp,
        P_infty_f_naturality_assoc, is_iso.hom_inv_id_assoc, assoc, is_iso.inv_hom_id_assoc,
        simplicial_object.σ_naturality, h₁, h₂, h₃]
      tauto⟩

theorem compatibility_n₂_n₁_karoubi :
    n₂ ⋙ (karoubiChainComplexEquivalence C ℕ).Functor =
      karoubiFunctorCategoryEmbedding SimplexCategoryᵒᵖ C ⋙
        n₁ ⋙
          (karoubiChainComplexEquivalence (Karoubi C) ℕ).Functor ⋙
            Functor.mapHomologicalComplex (KaroubiKaroubi.equivalence C).inverse _ :=
  by
  refine' CategoryTheory.Functor.ext (fun P => _) fun P Q f => _
  · refine' HomologicalComplex.ext _ _
    · ext n
      · dsimp
        simp only [karoubi_P_infty_f, comp_id, P_infty_f_naturality, id_comp]
      · rfl
    · rintro _ n (rfl : n + 1 = _)
      ext
      have h := (alternating_face_map_complex.map P.p).comm (n + 1) n
      dsimp [N₂, karoubi_chain_complex_equivalence, karoubi_karoubi.inverse,
        karoubi_homological_complex_equivalence.functor.obj] at h⊢
      simp only [karoubi.comp_f, assoc, karoubi.eq_to_hom_f, eq_to_hom_refl, id_comp, comp_id,
        karoubi_alternating_face_map_complex_d, karoubi_P_infty_f, ←
        HomologicalComplex.Hom.comm_assoc, ← h, app_idem_assoc]
  · ext n
    dsimp [karoubi_karoubi.inverse, karoubi_functor_category_embedding,
      karoubi_functor_category_embedding.map]
    simp only [karoubi.comp_f, karoubi_P_infty_f, HomologicalComplex.eqToHom_f, karoubi.eq_to_hom_f,
      assoc, comp_id, P_infty_f_naturality, app_p_comp,
      karoubi_chain_complex_equivalence_functor_obj_X_p, N₂_obj_p_f, eq_to_hom_refl,
      P_infty_f_naturality_assoc, app_comp_p, P_infty_f_idem_assoc]
#align algebraic_topology.dold_kan.compatibility_N₂_N₁_karoubi AlgebraicTopology.DoldKan.compatibility_n₂_n₁_karoubi

/-- We deduce that `N₂ : karoubi (simplicial_object C) ⥤ karoubi (chain_complex C ℕ))`
reflects isomorphisms from the fact that
`N₁ : simplicial_object (karoubi C) ⥤ karoubi (chain_complex (karoubi C) ℕ)` does. -/
instance : ReflectsIsomorphisms (n₂ : Karoubi (SimplicialObject C) ⥤ Karoubi (ChainComplex C ℕ)) :=
  ⟨fun X Y f => by
    intro
    -- The following functor `F` reflects isomorphism because it is
    -- a composition of four functors which reflects isomorphisms.
    -- Then, it suffices to show that `F.map f` is an isomorphism.
    let F :=
      karoubi_functor_category_embedding SimplexCategoryᵒᵖ C ⋙
        N₁ ⋙
          (karoubi_chain_complex_equivalence (karoubi C) ℕ).Functor ⋙
            functor.map_homological_complex (karoubi_karoubi.equivalence C).inverse
              (ComplexShape.down ℕ)
    have : is_iso (F.map f) := by
      dsimp only [F]
      rw [← compatibility_N₂_N₁_karoubi, functor.comp_map]
      apply functor.map_is_iso
    exact is_iso_of_reflects_iso f F⟩

end DoldKan

end AlgebraicTopology


import params
import quarterround
import utils

import category_theory.category.basic
import category_theory.core

open params
open operations
open quarterround
open utils

open category_theory

namespace rowround

variables [category (bitvec word_len)]

/-!
  # Rowround

  The `rowround` function and the relation with its inverse.
-/

/-!
  ## Definitions and lemmas
-/

/-- The row round of a single row. Complete `rowround` function will use 4 of this. -/
@[simp] def rowround_single (R : vecType): vecType :=
  (
    (quarterround R).fst, (quarterround R).snd.fst,
    (quarterround R).snd.snd.fst, (quarterround R).snd.snd.snd
  )

/-- The inverse of a single row round. -/
@[simp] def rowround_single_inv (R : vecType) : vecType :=
  (
    (quarterround_inv R).fst, (quarterround_inv R).snd.fst,
    (quarterround_inv R).snd.snd.fst, (quarterround_inv R).snd.snd.snd
  )

/- Just some notation for inverses. -/
local notation `rowround_single⁻¹` := rowround_single_inv

/-- Each row is invertible. -/
lemma rowround_single_is_inv (I : rowround_single ≅ rowround_single⁻¹) : I.hom ≫ I.inv = 𝟙 rowround_single :=
  by rw [iso.hom_inv_id]

/-- Apply `rowround_single` to get a row round matrix output -/
@[simp] def rowround (M : matrixType) : matrixType :=
  (
    rowround_single M.fst,
    rowround_single M.snd.fst,
    rowround_single M.snd.snd.fst,
    rowround_single M.snd.snd.snd
  )

/-- Reverses `rowround` by doing `rowround_single_inv` to get the original matrix output -/
@[simp] def rowround_inv (M : matrixType) : matrixType :=
  (
    rowround_single_inv M.fst,
    rowround_single_inv M.snd.fst,
    rowround_single_inv M.snd.snd.fst,
    rowround_single_inv M.snd.snd.snd
  )

/- Just some notation for inverses. -/
local notation `rowround⁻¹` := rowround_inv

/-- The full `rowround` is invertible. -/
lemma rowround_is_inv (I : rowround ≅ rowround⁻¹) : I.hom ≫ I.inv = 𝟙 rowround := by rw [iso.hom_inv_id]

/-- This rowround call will sort all the elements of the input and the output to match salsa20 spec.
It should be used in `doubleround`. -/
@[simp] def rowround_salsa20 (M : matrixType) := rowround_output (rowround (rowround_input M))

/-- This rowround inverse call will sort all the elements of the input and the output to match salsa20.
It should be used in `doubleround`. -/
@[simp] def rowround_salsa20_inv (M : matrixType) := rowround_output (rowround⁻¹ (rowround_input M))

/- Just some notation for inverses. -/
local notation `rowround_salsa20⁻¹` := rowround_salsa20_inv

/-- For any `rowround` output, we can get back to original values using the defined inverse. -/
@[simp] lemma rowround_salsa20_is_inv (I : rowround_salsa20 ≅ rowround_salsa20⁻¹) :
  I.hom ≫ I.inv = 𝟙 rowround_salsa20 := by rw [iso.hom_inv_id]

end rowround