/-
Copyright (c) 2018 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel

! This file was ported from Lean 3 source module order.conditionally_complete_lattice.group
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Order.ConditionallyCompleteLattice.Basic
import Mathbin.Algebra.Order.Group.TypeTags

/-!
# Conditionally complete lattices and groups.

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

-/

section Group

variable {α : Type _} {ι : Sort _} {ι' : Sort _} [Nonempty ι] [Nonempty ι']
  [ConditionallyCompleteLattice α] [Group α]

/- warning: le_mul_cinfi -> le_mul_cinfᵢ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} [_inst_1 : Nonempty.{u2} ι] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4)))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : α} {h : ι -> α}, (forall (j : ι), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) g (h j))) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) g (infᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasInf.{u1} α _inst_3) ι h)))
but is expected to have type
  forall {α : Type.{u2}} {ι : Sort.{u1}} [_inst_1 : Nonempty.{u1} ι] [_inst_3 : ConditionallyCompleteLattice.{u2} α] [_inst_4 : Group.{u2} α] [_inst_5 : CovariantClass.{u2, u2} α α (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.50 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.52 : α) => HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.50 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.52) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.65 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.67 : α) => LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.65 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.67)] {a : α} {g : α} {h : ι -> α}, (forall (j : ι), LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) a (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) g (h j))) -> (LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) a (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) g (infᵢ.{u2, u1} α (ConditionallyCompleteLattice.toInfSet.{u2} α _inst_3) ι h)))
Case conversion may be inaccurate. Consider using '#align le_mul_cinfi le_mul_cinfᵢₓ'. -/
@[to_additive]
theorem le_mul_cinfᵢ [CovariantClass α α (· * ·) (· ≤ ·)] {a : α} {g : α} {h : ι → α}
    (H : ∀ j, a ≤ g * h j) : a ≤ g * infᵢ h :=
  inv_mul_le_iff_le_mul.mp <| le_cinfᵢ fun hi => inv_mul_le_iff_le_mul.mpr <| H _
#align le_mul_cinfi le_mul_cinfᵢ
#align le_add_cinfi le_add_cinfᵢ

/- warning: mul_csupr_le -> mul_csupᵢ_le is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} [_inst_1 : Nonempty.{u2} ι] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4)))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : α} {h : ι -> α}, (forall (j : ι), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) g (h j)) a) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) g (supᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasSup.{u1} α _inst_3) ι h)) a)
but is expected to have type
  forall {α : Type.{u2}} {ι : Sort.{u1}} [_inst_1 : Nonempty.{u1} ι] [_inst_3 : ConditionallyCompleteLattice.{u2} α] [_inst_4 : Group.{u2} α] [_inst_5 : CovariantClass.{u2, u2} α α (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.139 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.141 : α) => HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.139 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.141) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.154 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.156 : α) => LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.154 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.156)] {a : α} {g : α} {h : ι -> α}, (forall (j : ι), LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) g (h j)) a) -> (LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) g (supᵢ.{u2, u1} α (ConditionallyCompleteLattice.toSupSet.{u2} α _inst_3) ι h)) a)
Case conversion may be inaccurate. Consider using '#align mul_csupr_le mul_csupᵢ_leₓ'. -/
@[to_additive]
theorem mul_csupᵢ_le [CovariantClass α α (· * ·) (· ≤ ·)] {a : α} {g : α} {h : ι → α}
    (H : ∀ j, g * h j ≤ a) : g * supᵢ h ≤ a :=
  @le_mul_cinfᵢ αᵒᵈ _ _ _ _ _ _ _ _ H
#align mul_csupr_le mul_csupᵢ_le
#align add_csupr_le add_csupᵢ_le

/- warning: le_cinfi_mul -> le_cinfᵢ_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} [_inst_1 : Nonempty.{u2} ι] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (Function.swap.{succ u1, succ u1, succ u1} α α (fun (ᾰ : α) (ᾰ : α) => α) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : ι -> α} {h : α}, (forall (i : ι), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (g i) h)) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (infᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasInf.{u1} α _inst_3) ι g) h))
but is expected to have type
  forall {α : Type.{u2}} {ι : Sort.{u1}} [_inst_1 : Nonempty.{u1} ι] [_inst_3 : ConditionallyCompleteLattice.{u2} α] [_inst_4 : Group.{u2} α] [_inst_5 : CovariantClass.{u2, u2} α α (Function.swap.{succ u2, succ u2, succ u2} α α (fun (ᾰ : α) (ᾰ : α) => α) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.228 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.230 : α) => HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.228 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.230)) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.243 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.245 : α) => LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.243 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.245)] {a : α} {g : ι -> α} {h : α}, (forall (i : ι), LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) a (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) (g i) h)) -> (LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) a (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) (infᵢ.{u2, u1} α (ConditionallyCompleteLattice.toInfSet.{u2} α _inst_3) ι g) h))
Case conversion may be inaccurate. Consider using '#align le_cinfi_mul le_cinfᵢ_mulₓ'. -/
@[to_additive]
theorem le_cinfᵢ_mul [CovariantClass α α (Function.swap (· * ·)) (· ≤ ·)] {a : α} {g : ι → α}
    {h : α} (H : ∀ i, a ≤ g i * h) : a ≤ infᵢ g * h :=
  mul_inv_le_iff_le_mul.mp <| le_cinfᵢ fun gi => mul_inv_le_iff_le_mul.mpr <| H _
#align le_cinfi_mul le_cinfᵢ_mul
#align le_cinfi_add le_cinfᵢ_add

/- warning: csupr_mul_le -> csupᵢ_mul_le is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} [_inst_1 : Nonempty.{u2} ι] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (Function.swap.{succ u1, succ u1, succ u1} α α (fun (ᾰ : α) (ᾰ : α) => α) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : ι -> α} {h : α}, (forall (i : ι), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (g i) h) a) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (supᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasSup.{u1} α _inst_3) ι g) h) a)
but is expected to have type
  forall {α : Type.{u2}} {ι : Sort.{u1}} [_inst_1 : Nonempty.{u1} ι] [_inst_3 : ConditionallyCompleteLattice.{u2} α] [_inst_4 : Group.{u2} α] [_inst_5 : CovariantClass.{u2, u2} α α (Function.swap.{succ u2, succ u2, succ u2} α α (fun (ᾰ : α) (ᾰ : α) => α) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.320 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.322 : α) => HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.320 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.322)) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.335 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.337 : α) => LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.335 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.337)] {a : α} {g : ι -> α} {h : α}, (forall (i : ι), LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) (g i) h) a) -> (LE.le.{u2} α (Preorder.toLE.{u2} α (PartialOrder.toPreorder.{u2} α (SemilatticeInf.toPartialOrder.{u2} α (Lattice.toSemilatticeInf.{u2} α (ConditionallyCompleteLattice.toLattice.{u2} α _inst_3))))) (HMul.hMul.{u2, u2, u2} α α α (instHMul.{u2} α (MulOneClass.toMul.{u2} α (Monoid.toMulOneClass.{u2} α (DivInvMonoid.toMonoid.{u2} α (Group.toDivInvMonoid.{u2} α _inst_4))))) (supᵢ.{u2, u1} α (ConditionallyCompleteLattice.toSupSet.{u2} α _inst_3) ι g) h) a)
Case conversion may be inaccurate. Consider using '#align csupr_mul_le csupᵢ_mul_leₓ'. -/
@[to_additive]
theorem csupᵢ_mul_le [CovariantClass α α (Function.swap (· * ·)) (· ≤ ·)] {a : α} {g : ι → α}
    {h : α} (H : ∀ i, g i * h ≤ a) : supᵢ g * h ≤ a :=
  @le_cinfᵢ_mul αᵒᵈ _ _ _ _ _ _ _ _ H
#align csupr_mul_le csupᵢ_mul_le
#align csupr_add_le csupᵢ_add_le

/- warning: le_cinfi_mul_cinfi -> le_cinfᵢ_mul_cinfᵢ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} {ι' : Sort.{u3}} [_inst_1 : Nonempty.{u2} ι] [_inst_2 : Nonempty.{u3} ι'] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4)))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] [_inst_6 : CovariantClass.{u1, u1} α α (Function.swap.{succ u1, succ u1, succ u1} α α (fun (ᾰ : α) (ᾰ : α) => α) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : ι -> α} {h : ι' -> α}, (forall (i : ι) (j : ι'), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (g i) (h j))) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (infᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasInf.{u1} α _inst_3) ι g) (infᵢ.{u1, u3} α (ConditionallyCompleteLattice.toHasInf.{u1} α _inst_3) ι' h)))
but is expected to have type
  forall {α : Type.{u3}} {ι : Sort.{u2}} {ι' : Sort.{u1}} [_inst_1 : Nonempty.{u2} ι] [_inst_2 : Nonempty.{u1} ι'] [_inst_3 : ConditionallyCompleteLattice.{u3} α] [_inst_4 : Group.{u3} α] [_inst_5 : CovariantClass.{u3, u3} α α (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.406 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.408 : α) => HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.406 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.408) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.421 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.423 : α) => LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.421 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.423)] [_inst_6 : CovariantClass.{u3, u3} α α (Function.swap.{succ u3, succ u3, succ u3} α α (fun (ᾰ : α) (ᾰ : α) => α) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.443 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.445 : α) => HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.443 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.445)) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.458 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.460 : α) => LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.458 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.460)] {a : α} {g : ι -> α} {h : ι' -> α}, (forall (i : ι) (j : ι'), LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) a (HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) (g i) (h j))) -> (LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) a (HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) (infᵢ.{u3, u2} α (ConditionallyCompleteLattice.toInfSet.{u3} α _inst_3) ι g) (infᵢ.{u3, u1} α (ConditionallyCompleteLattice.toInfSet.{u3} α _inst_3) ι' h)))
Case conversion may be inaccurate. Consider using '#align le_cinfi_mul_cinfi le_cinfᵢ_mul_cinfᵢₓ'. -/
@[to_additive]
theorem le_cinfᵢ_mul_cinfᵢ [CovariantClass α α (· * ·) (· ≤ ·)]
    [CovariantClass α α (Function.swap (· * ·)) (· ≤ ·)] {a : α} {g : ι → α} {h : ι' → α}
    (H : ∀ i j, a ≤ g i * h j) : a ≤ infᵢ g * infᵢ h :=
  le_cinfᵢ_mul fun i => le_mul_cinfᵢ <| H _
#align le_cinfi_mul_cinfi le_cinfᵢ_mul_cinfᵢ
#align le_cinfi_add_cinfi le_cinfᵢ_add_cinfᵢ

/- warning: csupr_mul_csupr_le -> csupᵢ_mul_csupᵢ_le is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {ι : Sort.{u2}} {ι' : Sort.{u3}} [_inst_1 : Nonempty.{u2} ι] [_inst_2 : Nonempty.{u3} ι'] [_inst_3 : ConditionallyCompleteLattice.{u1} α] [_inst_4 : Group.{u1} α] [_inst_5 : CovariantClass.{u1, u1} α α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4)))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] [_inst_6 : CovariantClass.{u1, u1} α α (Function.swap.{succ u1, succ u1, succ u1} α α (fun (ᾰ : α) (ᾰ : α) => α) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))))) (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))))] {a : α} {g : ι -> α} {h : ι' -> α}, (forall (i : ι) (j : ι'), LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (g i) (h j)) a) -> (LE.le.{u1} α (Preorder.toLE.{u1} α (PartialOrder.toPreorder.{u1} α (SemilatticeInf.toPartialOrder.{u1} α (Lattice.toSemilatticeInf.{u1} α (ConditionallyCompleteLattice.toLattice.{u1} α _inst_3))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_4))))) (supᵢ.{u1, u2} α (ConditionallyCompleteLattice.toHasSup.{u1} α _inst_3) ι g) (supᵢ.{u1, u3} α (ConditionallyCompleteLattice.toHasSup.{u1} α _inst_3) ι' h)) a)
but is expected to have type
  forall {α : Type.{u3}} {ι : Sort.{u2}} {ι' : Sort.{u1}} [_inst_1 : Nonempty.{u2} ι] [_inst_2 : Nonempty.{u1} ι'] [_inst_3 : ConditionallyCompleteLattice.{u3} α] [_inst_4 : Group.{u3} α] [_inst_5 : CovariantClass.{u3, u3} α α (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.534 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.536 : α) => HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.534 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.536) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.549 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.551 : α) => LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.549 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.551)] [_inst_6 : CovariantClass.{u3, u3} α α (Function.swap.{succ u3, succ u3, succ u3} α α (fun (ᾰ : α) (ᾰ : α) => α) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.571 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.573 : α) => HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.571 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.573)) (fun (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.586 : α) (x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.588 : α) => LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.586 x._@.Mathlib.Order.ConditionallyCompleteLattice.Group._hyg.588)] {a : α} {g : ι -> α} {h : ι' -> α}, (forall (i : ι) (j : ι'), LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) (HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) (g i) (h j)) a) -> (LE.le.{u3} α (Preorder.toLE.{u3} α (PartialOrder.toPreorder.{u3} α (SemilatticeInf.toPartialOrder.{u3} α (Lattice.toSemilatticeInf.{u3} α (ConditionallyCompleteLattice.toLattice.{u3} α _inst_3))))) (HMul.hMul.{u3, u3, u3} α α α (instHMul.{u3} α (MulOneClass.toMul.{u3} α (Monoid.toMulOneClass.{u3} α (DivInvMonoid.toMonoid.{u3} α (Group.toDivInvMonoid.{u3} α _inst_4))))) (supᵢ.{u3, u2} α (ConditionallyCompleteLattice.toSupSet.{u3} α _inst_3) ι g) (supᵢ.{u3, u1} α (ConditionallyCompleteLattice.toSupSet.{u3} α _inst_3) ι' h)) a)
Case conversion may be inaccurate. Consider using '#align csupr_mul_csupr_le csupᵢ_mul_csupᵢ_leₓ'. -/
@[to_additive]
theorem csupᵢ_mul_csupᵢ_le [CovariantClass α α (· * ·) (· ≤ ·)]
    [CovariantClass α α (Function.swap (· * ·)) (· ≤ ·)] {a : α} {g : ι → α} {h : ι' → α}
    (H : ∀ i j, g i * h j ≤ a) : supᵢ g * supᵢ h ≤ a :=
  csupᵢ_mul_le fun i => mul_csupᵢ_le <| H _
#align csupr_mul_csupr_le csupᵢ_mul_csupᵢ_le
#align csupr_add_csupr_le csupᵢ_add_csupᵢ_le

end Group


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

! This file was ported from Lean 3 source module algebra.big_operators.finsupp
! leanprover-community/mathlib commit 842328d9df7e96fd90fc424e115679c15fb23a71
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Finsupp.Indicator
import Mathlib.Algebra.BigOperators.Pi
import Mathlib.Algebra.BigOperators.Ring
import Mathlib.Algebra.BigOperators.Order
import Mathlib.GroupTheory.Submonoid.Membership

/-!
# Big operators for finsupps

This file contains theorems relevant to big operators in finitely supported functions.
-/

noncomputable section

open Finset Function

open BigOperators

variable {α ι γ A B C : Type _} [AddCommMonoid A] [AddCommMonoid B] [AddCommMonoid C]

variable {t : ι → A → C} (h0 : ∀ i, t i 0 = 0) (h1 : ∀ i x y, t i (x + y) = t i x + t i y)

variable {s : Finset α} {f : α → ι →₀ A} (i : ι)

variable (g : ι →₀ A) (k : ι → A → γ → B) (x : γ)

variable {β M M' N P G H R S : Type _}

namespace Finsupp

/-!
### Declarations about `sum` and `prod`

In most of this section, the domain `β` is assumed to be an `AddMonoid`.
-/

section SumProd

/-- `prod f g` is the product of `g a (f a)` over the support of `f`. -/
@[to_additive "`sum f g` is the sum of `g a (f a)` over the support of `f`. "]
def prod [Zero M] [CommMonoid N] (f : α →₀ M) (g : α → M → N) : N :=
  ∏ a in f.support, g a (f a)
#align finsupp.prod Finsupp.prod
#align finsupp.sum Finsupp.sum

variable [Zero M] [Zero M'] [CommMonoid N]

@[to_additive]
theorem prod_of_support_subset (f : α →₀ M) {s : Finset α} (hs : f.support ⊆ s) (g : α → M → N)
    (h : ∀ i ∈ s, g i 0 = 1) : f.prod g = ∏ x in s, g x (f x) := by
  refine Finset.prod_subset hs fun x hxs hx => h x hxs ▸ (congr_arg (g x) ?_)
  exact not_mem_support_iff.1 hx
#align finsupp.prod_of_support_subset Finsupp.prod_of_support_subset
#align finsupp.sum_of_support_subset Finsupp.sum_of_support_subset

@[to_additive]
theorem prod_fintype [Fintype α] (f : α →₀ M) (g : α → M → N) (h : ∀ i, g i 0 = 1) :
    f.prod g = ∏ i, g i (f i) :=
  f.prod_of_support_subset (subset_univ _) g fun x _ => h x
#align finsupp.prod_fintype Finsupp.prod_fintype
#align finsupp.sum_fintype Finsupp.sum_fintype

@[to_additive (attr := simp)]
theorem prod_single_index {a : α} {b : M} {h : α → M → N} (h_zero : h a 0 = 1) :
    (single a b).prod h = h a b :=
  calc
    (single a b).prod h = ∏ x in {a}, h x (single a b x) :=
      prod_of_support_subset _ support_single_subset h fun x hx =>
        (mem_singleton.1 hx).symm ▸ h_zero
    _ = h a b := by simp

#align finsupp.prod_single_index Finsupp.prod_single_index
#align finsupp.sum_single_index Finsupp.sum_single_index

@[to_additive]
theorem prod_mapRange_index {f : M → M'} {hf : f 0 = 0} {g : α →₀ M} {h : α → M' → N}
    (h0 : ∀ a, h a 0 = 1) : (mapRange f hf g).prod h = g.prod fun a b => h a (f b) :=
  Finset.prod_subset support_mapRange fun _ _ H => by rw [not_mem_support_iff.1 H, h0]
#align finsupp.prod_map_range_index Finsupp.prod_mapRange_index
#align finsupp.sum_map_range_index Finsupp.sum_mapRange_index

@[to_additive (attr := simp)]
theorem prod_zero_index {h : α → M → N} : (0 : α →₀ M).prod h = 1 :=
  rfl
#align finsupp.prod_zero_index Finsupp.prod_zero_index
#align finsupp.sum_zero_index Finsupp.sum_zero_index

@[to_additive]
theorem prod_comm (f : α →₀ M) (g : β →₀ M') (h : α → M → β → M' → N) :
    (f.prod fun x v => g.prod fun x' v' => h x v x' v') =
      g.prod fun x' v' => f.prod fun x v => h x v x' v' :=
  Finset.prod_comm
#align finsupp.prod_comm Finsupp.prod_comm
#align finsupp.sum_comm Finsupp.sum_comm

@[to_additive (attr := simp)]
theorem prod_ite_eq [DecidableEq α] (f : α →₀ M) (a : α) (b : α → M → N) :
    (f.prod fun x v => ite (a = x) (b x v) 1) = ite (a ∈ f.support) (b a (f a)) 1 := by
  dsimp [Finsupp.prod]
  rw [f.support.prod_ite_eq]
#align finsupp.prod_ite_eq Finsupp.prod_ite_eq
#align finsupp.sum_ite_eq Finsupp.sum_ite_eq

/- Porting note: simpnf linter, added aux lemma below
Left-hand side simplifies from
  Finsupp.sum f fun x v => if a = x then v else 0
to
  if ↑f a = 0 then 0 else ↑f a
-/
-- @[simp]
theorem sum_ite_self_eq [DecidableEq α] {N : Type _} [AddCommMonoid N] (f : α →₀ N) (a : α) :
    (f.sum fun x v => ite (a = x) v 0) = f a := by
  classical
    convert f.sum_ite_eq a fun _ => id
    simp [ite_eq_right_iff.2 Eq.symm]
#align finsupp.sum_ite_self_eq Finsupp.sum_ite_self_eq

-- Porting note: Added this thm to replace the simp in the previous one. Need to add [DecidableEq N]
@[simp]
theorem sum_ite_self_eq_aux [DecidableEq α] {N : Type _} [AddCommMonoid N] (f : α →₀ N) (a : α) :
    (if a ∈ f.support then f a else 0) = f a := by
  simp only [mem_support_iff, ne_eq, ite_eq_left_iff, not_not]
  exact fun h ↦ h.symm

/-- A restatement of `prod_ite_eq` with the equality test reversed. -/
@[to_additive (attr := simp) "A restatement of `sum_ite_eq` with the equality test reversed."]
theorem prod_ite_eq' [DecidableEq α] (f : α →₀ M) (a : α) (b : α → M → N) :
    (f.prod fun x v => ite (x = a) (b x v) 1) = ite (a ∈ f.support) (b a (f a)) 1 := by
  dsimp [Finsupp.prod]
  rw [f.support.prod_ite_eq']
#align finsupp.prod_ite_eq' Finsupp.prod_ite_eq'
#align finsupp.sum_ite_eq' Finsupp.sum_ite_eq'

-- Porting note: simp can prove this
-- @[simp]
theorem sum_ite_self_eq' [DecidableEq α] {N : Type _} [AddCommMonoid N] (f : α →₀ N) (a : α) :
    (f.sum fun x v => ite (x = a) v 0) = f a := by
  classical
    convert f.sum_ite_eq' a fun _ => id
    simp [ite_eq_right_iff.2 Eq.symm]
#align finsupp.sum_ite_self_eq' Finsupp.sum_ite_self_eq'

@[simp]
theorem prod_pow [Fintype α] (f : α →₀ ℕ) (g : α → N) :
    (f.prod fun a b => g a ^ b) = ∏ a, g a ^ f a :=
  f.prod_fintype _ fun _ ↦ pow_zero _
#align finsupp.prod_pow Finsupp.prod_pow

/-- If `g` maps a second argument of 0 to 1, then multiplying it over the
result of `onFinset` is the same as multiplying it over the original `Finset`. -/
@[to_additive
      "If `g` maps a second argument of 0 to 0, summing it over the
      result of `onFinset` is the same as summing it over the original `Finset`."]
theorem onFinset_prod {s : Finset α} {f : α → M} {g : α → M → N} (hf : ∀ a, f a ≠ 0 → a ∈ s)
    (hg : ∀ a, g a 0 = 1) : (onFinset s f hf).prod g = ∏ a in s, g a (f a) :=
  Finset.prod_subset support_onFinset_subset <| by simp (config := { contextual := true }) [*]
#align finsupp.on_finset_prod Finsupp.onFinset_prod
#align finsupp.on_finset_sum Finsupp.onFinset_sum

/-- Taking a product over `f : α →₀ M` is the same as multiplying the value on a single element
`y ∈ f.support` by the product over `erase y f`. -/
@[to_additive
      " Taking a sum over over `f : α →₀ M` is the same as adding the value on a
      single element `y ∈ f.support` to the sum over `erase y f`. "]
theorem mul_prod_erase (f : α →₀ M) (y : α) (g : α → M → N) (hyf : y ∈ f.support) :
    g y (f y) * (erase y f).prod g = f.prod g := by
  classical
    rw [Finsupp.prod, Finsupp.prod, ← Finset.mul_prod_erase _ _ hyf, Finsupp.support_erase,
      Finset.prod_congr rfl]
    intro h hx
    rw [Finsupp.erase_ne (ne_of_mem_erase hx)]
#align finsupp.mul_prod_erase Finsupp.mul_prod_erase
#align finsupp.add_sum_erase Finsupp.add_sum_erase

/-- Generalization of `Finsupp.mul_prod_erase`: if `g` maps a second argument of 0 to 1,
then its product over `f : α →₀ M` is the same as multiplying the value on any element
`y : α` by the product over `erase y f`. -/
@[to_additive
      " Generalization of `Finsupp.add_sum_erase`: if `g` maps a second argument of 0
      to 0, then its sum over `f : α →₀ M` is the same as adding the value on any element
      `y : α` to the sum over `erase y f`. "]
theorem mul_prod_erase' (f : α →₀ M) (y : α) (g : α → M → N) (hg : ∀ i : α, g i 0 = 1) :
    g y (f y) * (erase y f).prod g = f.prod g := by
  classical
    by_cases hyf : y ∈ f.support
    · exact Finsupp.mul_prod_erase f y g hyf
    · rw [not_mem_support_iff.mp hyf, hg y, erase_of_not_mem_support hyf, one_mul]
#align finsupp.mul_prod_erase' Finsupp.mul_prod_erase'
#align finsupp.add_sum_erase' Finsupp.add_sum_erase'

@[to_additive]
theorem _root_.SubmonoidClass.finsupp_prod_mem {S : Type _} [SetLike S N] [SubmonoidClass S N]
    (s : S) (f : α →₀ M) (g : α → M → N) (h : ∀ c, f c ≠ 0 → g c (f c) ∈ s) : f.prod g ∈ s :=
  prod_mem fun _i hi => h _ (Finsupp.mem_support_iff.mp hi)
#align submonoid_class.finsupp_prod_mem SubmonoidClass.finsupp_prod_mem
#align add_submonoid_class.finsupp_sum_mem AddSubmonoidClass.finsupp_sum_mem

@[to_additive]
theorem prod_congr {f : α →₀ M} {g1 g2 : α → M → N} (h : ∀ x ∈ f.support, g1 x (f x) = g2 x (f x)) :
    f.prod g1 = f.prod g2 :=
  Finset.prod_congr rfl h
#align finsupp.prod_congr Finsupp.prod_congr
#align finsupp.sum_congr Finsupp.sum_congr

end SumProd

end Finsupp

@[to_additive]
theorem map_finsupp_prod [Zero M] [CommMonoid N] [CommMonoid P] {H : Type _} [MonoidHomClass H N P]
    (h : H) (f : α →₀ M) (g : α → M → N) : h (f.prod g) = f.prod fun a b => h (g a b) :=
  map_prod h _ _
#align map_finsupp_prod map_finsupp_prod
#align map_finsupp_sum map_finsupp_sum

/-- Deprecated, use `_root_.map_finsupp_prod` instead. -/
@[to_additive (attr := deprecated)
  "Deprecated, use `_root_.map_finsupp_sum` instead."]
protected theorem MulEquiv.map_finsupp_prod [Zero M] [CommMonoid N] [CommMonoid P] (h : N ≃* P)
    (f : α →₀ M) (g : α → M → N) : h (f.prod g) = f.prod fun a b => h (g a b) :=
  map_finsupp_prod h f g
#align mul_equiv.map_finsupp_prod MulEquiv.map_finsupp_prod
#align add_equiv.map_finsupp_sum AddEquiv.map_finsupp_sum

/-- Deprecated, use `_root_.map_finsupp_prod` instead. -/
@[to_additive (attr := deprecated)
  "Deprecated, use `_root_.map_finsupp_sum` instead."]
protected theorem MonoidHom.map_finsupp_prod [Zero M] [CommMonoid N] [CommMonoid P] (h : N →* P)
    (f : α →₀ M) (g : α → M → N) : h (f.prod g) = f.prod fun a b => h (g a b) :=
  map_finsupp_prod h f g
#align monoid_hom.map_finsupp_prod MonoidHom.map_finsupp_prod
#align add_monoid_hom.map_finsupp_sum AddMonoidHom.map_finsupp_sum

/-- Deprecated, use `_root_.map_finsupp_sum` instead. -/
@[deprecated map_finsupp_sum]
protected theorem RingHom.map_finsupp_sum [Zero M] [Semiring R] [Semiring S] (h : R →+* S)
    (f : α →₀ M) (g : α → M → R) : h (f.sum g) = f.sum fun a b => h (g a b) :=
  map_finsupp_sum h f g
#align ring_hom.map_finsupp_sum RingHom.map_finsupp_sum

/-- Deprecated, use `_root_.map_finsupp_prod` instead. -/
@[deprecated map_finsupp_prod]
protected theorem RingHom.map_finsupp_prod [Zero M] [CommSemiring R] [CommSemiring S] (h : R →+* S)
    (f : α →₀ M) (g : α → M → R) : h (f.prod g) = f.prod fun a b => h (g a b) :=
  map_finsupp_prod h f g
#align ring_hom.map_finsupp_prod RingHom.map_finsupp_prod

-- Porting note: inserted ⇑ on the rhs
@[to_additive]
theorem MonoidHom.coe_finsupp_prod [Zero β] [Monoid N] [CommMonoid P] (f : α →₀ β)
    (g : α → β → N →* P) : ⇑(f.prod g) = f.prod fun i fi => ⇑(g i fi) :=
  MonoidHom.coe_finset_prod _ _
#align monoid_hom.coe_finsupp_prod MonoidHom.coe_finsupp_prod
#align add_monoid_hom.coe_finsupp_sum AddMonoidHom.coe_finsupp_sum

@[to_additive (attr := simp)]
theorem MonoidHom.finsupp_prod_apply [Zero β] [Monoid N] [CommMonoid P] (f : α →₀ β)
    (g : α → β → N →* P) (x : N) : f.prod g x = f.prod fun i fi => g i fi x :=
  MonoidHom.finset_prod_apply _ _ _
#align monoid_hom.finsupp_prod_apply MonoidHom.finsupp_prod_apply
#align add_monoid_hom.finsupp_sum_apply AddMonoidHom.finsupp_sum_apply

namespace Finsupp

theorem single_multiset_sum [AddCommMonoid M] (s : Multiset M) (a : α) :
    single a s.sum = (s.map (single a)).sum :=
  Multiset.induction_on s (single_zero _) fun a s ih => by
    rw [Multiset.sum_cons, single_add, ih, Multiset.map_cons, Multiset.sum_cons]
#align finsupp.single_multiset_sum Finsupp.single_multiset_sum

theorem single_finset_sum [AddCommMonoid M] (s : Finset ι) (f : ι → M) (a : α) :
    single a (∑ b in s, f b) = ∑ b in s, single a (f b) := by
  trans
  · apply single_multiset_sum
  · rw [Multiset.map_map]
    rfl
#align finsupp.single_finset_sum Finsupp.single_finset_sum

theorem single_sum [Zero M] [AddCommMonoid N] (s : ι →₀ M) (f : ι → M → N) (a : α) :
    single a (s.sum f) = s.sum fun d c => single a (f d c) :=
  single_finset_sum _ _ _
#align finsupp.single_sum Finsupp.single_sum

@[to_additive]
theorem prod_neg_index [AddGroup G] [CommMonoid M] {g : α →₀ G} {h : α → G → M}
    (h0 : ∀ a, h a 0 = 1) : (-g).prod h = g.prod fun a b => h a (-b) :=
  prod_mapRange_index h0
#align finsupp.prod_neg_index Finsupp.prod_neg_index
#align finsupp.sum_neg_index Finsupp.sum_neg_index

end Finsupp

namespace Finsupp

theorem finset_sum_apply [AddCommMonoid N] (S : Finset ι) (f : ι → α →₀ N) (a : α) :
    (∑ i in S, f i) a = ∑ i in S, f i a :=
  (applyAddHom a : (α →₀ N) →+ _).map_sum _ _
#align finsupp.finset_sum_apply Finsupp.finset_sum_apply

@[simp]
theorem sum_apply [Zero M] [AddCommMonoid N] {f : α →₀ M} {g : α → M → β →₀ N} {a₂ : β} :
    (f.sum g) a₂ = f.sum fun a₁ b => g a₁ b a₂ :=
  finset_sum_apply _ _ _
#align finsupp.sum_apply Finsupp.sum_apply

-- Porting note: inserted ⇑ on the rhs
theorem coe_finset_sum [AddCommMonoid N] (S : Finset ι) (f : ι → α →₀ N) :
    ⇑(∑ i in S, f i) = ∑ i in S, ⇑(f i) :=
  (coeFnAddHom : (α →₀ N) →+ _).map_sum _ _
#align finsupp.coe_finset_sum Finsupp.coe_finset_sum

-- Porting note: inserted ⇑ on the rhs
theorem coe_sum [Zero M] [AddCommMonoid N] (f : α →₀ M) (g : α → M → β →₀ N) :
    ⇑(f.sum g) = f.sum fun a₁ b => ⇑(g a₁ b) :=
  coe_finset_sum _ _
#align finsupp.coe_sum Finsupp.coe_sum

theorem support_sum [DecidableEq β] [Zero M] [AddCommMonoid N] {f : α →₀ M} {g : α → M → β →₀ N} :
    (f.sum g).support ⊆ f.support.bunionᵢ fun a => (g a (f a)).support := by
  have : ∀ c, (f.sum fun a b => g a b c) ≠ 0 → ∃ a, f a ≠ 0 ∧ ¬(g a (f a)) c = 0 := fun a₁ h =>
    let ⟨a, ha, ne⟩ := Finset.exists_ne_zero_of_sum_ne_zero h
    ⟨a, mem_support_iff.mp ha, ne⟩
  simpa only [Finset.subset_iff, mem_support_iff, Finset.mem_bunionᵢ, sum_apply, exists_prop]
#align finsupp.support_sum Finsupp.support_sum

theorem support_finset_sum [DecidableEq β] [AddCommMonoid M] {s : Finset α} {f : α → β →₀ M} :
    (Finset.sum s f).support ⊆ s.bunionᵢ fun x => (f x).support := by
  rw [← Finset.sup_eq_bunionᵢ]
  induction' s using Finset.cons_induction_on with a s ha ih
  · rfl
  · rw [Finset.sum_cons, Finset.sup_cons]
    exact support_add.trans (Finset.union_subset_union (Finset.Subset.refl _) ih)
#align finsupp.support_finset_sum Finsupp.support_finset_sum

@[simp]
theorem sum_zero [Zero M] [AddCommMonoid N] {f : α →₀ M} : (f.sum fun _ _ => (0 : N)) = 0 :=
  Finset.sum_const_zero
#align finsupp.sum_zero Finsupp.sum_zero

@[to_additive (attr := simp)]
theorem prod_mul [Zero M] [CommMonoid N] {f : α →₀ M} {h₁ h₂ : α → M → N} :
    (f.prod fun a b => h₁ a b * h₂ a b) = f.prod h₁ * f.prod h₂ :=
  Finset.prod_mul_distrib
#align finsupp.prod_mul Finsupp.prod_mul
#align finsupp.sum_add Finsupp.sum_add

@[to_additive (attr := simp)]
theorem prod_inv [Zero M] [CommGroup G] {f : α →₀ M} {h : α → M → G} :
    (f.prod fun a b => (h a b)⁻¹) = (f.prod h)⁻¹ :=
  (map_prod (MonoidHom.id G)⁻¹ _ _).symm
#align finsupp.prod_inv Finsupp.prod_inv
#align finsupp.sum_neg Finsupp.sum_neg

@[simp]

/-- Taking the product under `h` is an additive-to-multiplicative homomorphism of finsupps,
if `h` is an additive-to-multiplicative homomorphism on the support.
This is a more general version of `Finsupp.prod_add_index'`; the latter has simpler hypotheses. -/
@[to_additive
      "Taking the product under `h` is an additive homomorphism of finsupps,  if `h` is an
      additive homomorphism on the support. This is a more general version of
      `Finsupp.sum_add_index'`; the latter has simpler hypotheses."]
theorem prod_add_index [DecidableEq α] [AddZeroClass M] [CommMonoid N] {f g : α →₀ M}
    {h : α → M → N} (h_zero : ∀ a ∈ f.support ∪ g.support, h a 0 = 1)
    (h_add : ∀ a ∈ f.support ∪ g.support, ∀ (b₁ b₂), h a (b₁ + b₂) = h a b₁ * h a b₂) :
    (f + g).prod h = f.prod h * g.prod h := by
  rw [Finsupp.prod_of_support_subset f (subset_union_left _ g.support) h h_zero,
    Finsupp.prod_of_support_subset g (subset_union_right f.support _) h h_zero, ←
    Finset.prod_mul_distrib, Finsupp.prod_of_support_subset (f + g) Finsupp.support_add h h_zero]
  exact Finset.prod_congr rfl fun x hx => by apply h_add x hx
#align finsupp.prod_add_index Finsupp.prod_add_index
#align finsupp.sum_add_index Finsupp.sum_add_index

/-- Taking the product under `h` is an additive-to-multiplicative homomorphism of finsupps,
if `h` is an additive-to-multiplicative homomorphism.
This is a more specialized version of `Finsupp.prod_add_index` with simpler hypotheses. -/
@[to_additive
      "Taking the sum under `h` is an additive homomorphism of finsupps,if `h` is an additive
      homomorphism. This is a more specific version of `finsupp.sum_add_index` with simpler
      hypotheses."]
theorem prod_add_index' [AddZeroClass M] [CommMonoid N] {f g : α →₀ M} {h : α → M → N}
    (h_zero : ∀ a, h a 0 = 1) (h_add : ∀ a b₁ b₂, h a (b₁ + b₂) = h a b₁ * h a b₂) :
    (f + g).prod h = f.prod h * g.prod h := by
  classical exact prod_add_index (fun a _ => h_zero a) fun a _ => h_add a
#align finsupp.prod_add_index' Finsupp.prod_add_index'
#align finsupp.sum_add_index' Finsupp.sum_add_index'

@[simp]
theorem sum_hom_add_index [AddZeroClass M] [AddCommMonoid N] {f g : α →₀ M} (h : α → M →+ N) :
    ((f + g).sum fun x => h x) = (f.sum fun x => h x) + g.sum fun x => h x :=
  sum_add_index' (fun a => (h a).map_zero) fun a => (h a).map_add
#align finsupp.sum_hom_add_index Finsupp.sum_hom_add_index

@[simp]
theorem prod_hom_add_index [AddZeroClass M] [CommMonoid N] {f g : α →₀ M}
    (h : α → Multiplicative M →* N) :
    ((f + g).prod fun a b => h a (Multiplicative.ofAdd b)) =
      (f.prod fun a b => h a (Multiplicative.ofAdd b)) *
        g.prod fun a b => h a (Multiplicative.ofAdd b) :=
  prod_add_index' (fun a => (h a).map_one) fun a => (h a).map_mul
#align finsupp.prod_hom_add_index Finsupp.prod_hom_add_index

/-- The canonical isomorphism between families of additive monoid homomorphisms `α → (M →+ N)`
and monoid homomorphisms `(α →₀ M) →+ N`. -/
def liftAddHom [AddZeroClass M] [AddCommMonoid N] : (α → M →+ N) ≃+ ((α →₀ M) →+ N)
    where
  toFun F :=
    { toFun := fun f ↦ f.sum fun x ↦ F x
      map_zero' := Finset.sum_empty
      map_add' := fun _ _ => sum_add_index' (fun x => (F x).map_zero) fun x => (F x).map_add }
  invFun F x := F.comp (singleAddHom x)
  left_inv F := by
    ext
    simp [singleAddHom]
  right_inv F := by
  -- Porting note: This was `ext` and used the wrong lemma
    apply Finsupp.addHom_ext'
    simp [singleAddHom, AddMonoidHom.comp, Function.comp]
  map_add' F G := by
    ext x
    exact sum_add
#align finsupp.lift_add_hom Finsupp.liftAddHom

@[simp]
theorem liftAddHom_apply [AddCommMonoid M] [AddCommMonoid N] (F : α → M →+ N) (f : α →₀ M) :
    (liftAddHom (α := α) (M := M) (N := N)) F f = f.sum fun x => F x :=
  rfl
#align finsupp.lift_add_hom_apply Finsupp.liftAddHom_apply

@[simp]
theorem liftAddHom_symm_apply [AddCommMonoid M] [AddCommMonoid N] (F : (α →₀ M) →+ N) (x : α) :
    (liftAddHom (α := α) (M := M) (N := N)).symm F x = F.comp (singleAddHom x) :=
  rfl
#align finsupp.lift_add_hom_symm_apply Finsupp.liftAddHom_symm_apply

theorem liftAddHom_symm_apply_apply [AddCommMonoid M] [AddCommMonoid N] (F : (α →₀ M) →+ N) (x : α)
    (y : M) : (liftAddHom (α := α) (M := M) (N := N)).symm F x y = F (single x y) :=
  rfl
#align finsupp.lift_add_hom_symm_apply_apply Finsupp.liftAddHom_symm_apply_apply

@[simp]
theorem liftAddHom_singleAddHom [AddCommMonoid M] :
    (liftAddHom (α := α) (M := M) (N := α →₀ M)) (singleAddHom : α → M →+ α →₀ M) =
      AddMonoidHom.id _ :=
  liftAddHom.toEquiv.apply_eq_iff_eq_symm_apply.2 rfl
#align finsupp.lift_add_hom_single_add_hom Finsupp.liftAddHom_singleAddHom

@[simp]
theorem sum_single [AddCommMonoid M] (f : α →₀ M) : f.sum single = f :=
  FunLike.congr_fun liftAddHom_singleAddHom f
#align finsupp.sum_single Finsupp.sum_single

@[simp]
theorem sum_univ_single [AddCommMonoid M] [Fintype α] (i : α) (m : M) :
    (∑ j : α, (single i m) j) = m := by
-- Porting note: rewrite due to leaky classical in lean3
  classical rw [single, coe_mk, Finset.sum_pi_single']
  simp
#align finsupp.sum_univ_single Finsupp.sum_univ_single

@[simp]
theorem sum_univ_single' [AddCommMonoid M] [Fintype α] (i : α) (m : M) :
    (∑ j : α, (single j m) i) = m := by
-- Porting note: rewrite due to leaky classical in lean3
  simp_rw [single, coe_mk, Finset.sum_pi_single]
  classical rw [Finset.sum_pi_single]
  simp
#align finsupp.sum_univ_single' Finsupp.sum_univ_single'

-- Porting note: simp can prove this
-- @[simp]
theorem liftAddHom_apply_single [AddCommMonoid M] [AddCommMonoid N] (f : α → M →+ N) (a : α)
    (b : M) : (liftAddHom (α := α) (M := M) (N := N)) f (single a b) = f a b :=
  sum_single_index (f a).map_zero
#align finsupp.lift_add_hom_apply_single Finsupp.liftAddHom_apply_single

@[simp]
theorem liftAddHom_comp_single [AddCommMonoid M] [AddCommMonoid N] (f : α → M →+ N) (a : α) :
    ((liftAddHom (α := α) (M := M) (N := N)) f).comp (singleAddHom a) = f a :=
  AddMonoidHom.ext fun b => liftAddHom_apply_single f a b
#align finsupp.lift_add_hom_comp_single Finsupp.liftAddHom_comp_single

theorem comp_liftAddHom [AddCommMonoid M] [AddCommMonoid N] [AddCommMonoid P] (g : N →+ P)
    (f : α → M →+ N) :
    g.comp ((liftAddHom (α := α) (M := M) (N := N)) f) =
      (liftAddHom (α := α) (M := M) (N := P)) fun a => g.comp (f a) :=
  liftAddHom.symm_apply_eq.1 <|
    funext fun a => by
      rw [liftAddHom_symm_apply, AddMonoidHom.comp_assoc, liftAddHom_comp_single]
#align finsupp.comp_lift_add_hom Finsupp.comp_liftAddHom

theorem sum_sub_index [AddCommGroup β] [AddCommGroup γ] {f g : α →₀ β} {h : α → β → γ}
    (h_sub : ∀ a b₁ b₂, h a (b₁ - b₂) = h a b₁ - h a b₂) : (f - g).sum h = f.sum h - g.sum h :=
  ((liftAddHom (α := α) (M := β) (N := γ)) fun a =>
    AddMonoidHom.ofMapSub (h a) (h_sub a)).map_sub f g
#align finsupp.sum_sub_index Finsupp.sum_sub_index

@[to_additive]
theorem prod_embDomain [Zero M] [CommMonoid N] {v : α →₀ M} {f : α ↪ β} {g : β → M → N} :
    (v.embDomain f).prod g = v.prod fun a b => g (f a) b := by
  rw [prod, prod, support_embDomain, Finset.prod_map]
  simp_rw [embDomain_apply]
#align finsupp.prod_emb_domain Finsupp.prod_embDomain
#align finsupp.sum_emb_domain Finsupp.sum_embDomain

@[to_additive]
theorem prod_finset_sum_index [AddCommMonoid M] [CommMonoid N] {s : Finset ι} {g : ι → α →₀ M}
    {h : α → M → N} (h_zero : ∀ a, h a 0 = 1) (h_add : ∀ a b₁ b₂, h a (b₁ + b₂) = h a b₁ * h a b₂) :
    (∏ i in s, (g i).prod h) = (∑ i in s, g i).prod h :=
  Finset.cons_induction_on s rfl fun a s has ih => by
    rw [prod_cons, ih, sum_cons, prod_add_index' h_zero h_add]
#align finsupp.prod_finset_sum_index Finsupp.prod_finset_sum_index
#align finsupp.sum_finset_sum_index Finsupp.sum_finset_sum_index

@[to_additive]
theorem prod_sum_index [AddCommMonoid M] [AddCommMonoid N] [CommMonoid P] {f : α →₀ M}
    {g : α → M → β →₀ N} {h : β → N → P} (h_zero : ∀ a, h a 0 = 1)
    (h_add : ∀ a b₁ b₂, h a (b₁ + b₂) = h a b₁ * h a b₂) :
    (f.sum g).prod h = f.prod fun a b => (g a b).prod h :=
  (prod_finset_sum_index h_zero h_add).symm
#align finsupp.prod_sum_index Finsupp.prod_sum_index
#align finsupp.sum_sum_index Finsupp.sum_sum_index

theorem multiset_sum_sum_index [AddCommMonoid M] [AddCommMonoid N] (f : Multiset (α →₀ M))
    (h : α → M → N) (h₀ : ∀ a, h a 0 = 0)
    (h₁ : ∀ (a : α) (b₁ b₂ : M), h a (b₁ + b₂) = h a b₁ + h a b₂) :
    f.sum.sum h = (f.map fun g : α →₀ M => g.sum h).sum :=
  Multiset.induction_on f rfl fun a s ih => by
    rw [Multiset.sum_cons, Multiset.map_cons, Multiset.sum_cons, sum_add_index' h₀ h₁, ih]
#align finsupp.multiset_sum_sum_index Finsupp.multiset_sum_sum_index

theorem support_sum_eq_bunionᵢ {α : Type _} {ι : Type _} {M : Type _} [DecidableEq α]
    [AddCommMonoid M] {g : ι → α →₀ M} (s : Finset ι)
    (h : ∀ i₁ i₂, i₁ ≠ i₂ → Disjoint (g i₁).support (g i₂).support) :
    (∑ i in s, g i).support = s.bunionᵢ fun i => (g i).support := by
  classical
  -- Porting note: apply Finset.induction_on s was not working; refine does.
  refine Finset.induction_on s ?_ ?_
  · simp
  · intro i s hi
    simp only [hi, sum_insert, not_false_iff, bunionᵢ_insert]
    intro hs
    rw [Finsupp.support_add_eq, hs]
    rw [hs, Finset.disjoint_bunionᵢ_right]
    intro j hj
    refine' h _ _ (ne_of_mem_of_not_mem hj hi).symm
#align finsupp.support_sum_eq_bUnion Finsupp.support_sum_eq_bunionᵢ

theorem multiset_map_sum [Zero M] {f : α →₀ M} {m : β → γ} {h : α → M → Multiset β} :
    Multiset.map m (f.sum h) = f.sum fun a b => (h a b).map m :=
  (Multiset.mapAddMonoidHom m).map_sum _ f.support
#align finsupp.multiset_map_sum Finsupp.multiset_map_sum

theorem multiset_sum_sum [Zero M] [AddCommMonoid N] {f : α →₀ M} {h : α → M → Multiset N} :
    Multiset.sum (f.sum h) = f.sum fun a b => Multiset.sum (h a b) :=
  (Multiset.sumAddMonoidHom : Multiset N →+ N).map_sum _ f.support
#align finsupp.multiset_sum_sum Finsupp.multiset_sum_sum

/-- For disjoint `f1` and `f2`, and function `g`, the product of the products of `g`
over `f1` and `f2` equals the product of `g` over `f1 + f2` -/
@[to_additive
      "For disjoint `f1` and `f2`, and function `g`, the sum of the sums of `g`
      over `f1` and `f2` equals the sum of `g` over `f1 + f2`"]
theorem prod_add_index_of_disjoint [AddCommMonoid M] {f1 f2 : α →₀ M}
    (hd : Disjoint f1.support f2.support) {β : Type _} [CommMonoid β] (g : α → M → β) :
    (f1 + f2).prod g = f1.prod g * f2.prod g := by
  have :
    ∀ {f1 f2 : α →₀ M},
      Disjoint f1.support f2.support → (∏ x in f1.support, g x (f1 x + f2 x)) = f1.prod g :=
    fun hd =>
    Finset.prod_congr rfl fun x hx => by
      simp only [not_mem_support_iff.mp (disjoint_left.mp hd hx), add_zero]
  classical simp_rw [← this hd, ← this hd.symm, add_comm (f2 _), Finsupp.prod, support_add_eq hd,
      prod_union hd, add_apply]
#align finsupp.prod_add_index_of_disjoint Finsupp.prod_add_index_of_disjoint
#align finsupp.sum_add_index_of_disjoint Finsupp.sum_add_index_of_disjoint

theorem prod_dvd_prod_of_subset_of_dvd [AddCommMonoid M] [CommMonoid N] {f1 f2 : α →₀ M}
    {g1 g2 : α → M → N} (h1 : f1.support ⊆ f2.support)
    (h2 : ∀ a : α, a ∈ f1.support → g1 a (f1 a) ∣ g2 a (f2 a)) : f1.prod g1 ∣ f2.prod g2 := by
  classical
    simp only [Finsupp.prod, Finsupp.prod_mul]
    rw [← sdiff_union_of_subset h1, prod_union sdiff_disjoint]
    apply dvd_mul_of_dvd_right
    apply prod_dvd_prod_of_dvd
    exact h2
#align finsupp.prod_dvd_prod_of_subset_of_dvd Finsupp.prod_dvd_prod_of_subset_of_dvd

lemma indicator_eq_sum_single [AddCommMonoid M] (s : Finset α) (f : ∀ a ∈ s, M) :
    indicator s f = ∑ x in s.attach, single ↑x (f x x.2) := by
  rw [← sum_single (indicator s f), sum, sum_subset (support_indicator_subset _ _), ← sum_attach]
  · refine' Finset.sum_congr rfl (fun _ _ => _)
    rw [indicator_of_mem]
  · intro i _ hi
    rw [not_mem_support_iff.mp hi, single_zero]
#align finsupp.indicator_eq_sum_single Finsupp.indicator_eq_sum_single

@[to_additive (attr := simp)]
lemma prod_indicator_index [Zero M] [CommMonoid N]
    {s : Finset α} (f : ∀ a ∈ s, M) {h : α → M → N} (h_zero : ∀ a ∈ s, h a 0 = 1) :
    (indicator s f).prod h = ∏ x in s.attach, h ↑x (f x x.2) := by
  rw [prod_of_support_subset _ (support_indicator_subset _ _) h h_zero, ← prod_attach]
  refine' Finset.prod_congr rfl (fun _ _ => _)
  rw [indicator_of_mem]
#align finsupp.prod_indicator_index Finsupp.prod_indicator_index
#align finsupp.sum_indicator_index Finsupp.sum_indicator_index

end Finsupp

theorem Finset.sum_apply' : (∑ k in s, f k) i = ∑ k in s, f k i :=
  (Finsupp.applyAddHom i : (ι →₀ A) →+ A).map_sum f s
#align finset.sum_apply' Finset.sum_apply'

theorem Finsupp.sum_apply' : g.sum k x = g.sum fun i b => k i b x :=
  Finset.sum_apply _ _ _
#align finsupp.sum_apply' Finsupp.sum_apply'

section

open Classical

theorem Finsupp.sum_sum_index' : (∑ x in s, f x).sum t = ∑ x in s, (f x).sum t :=
  Finset.induction_on s rfl fun a s has ih => by
    simp_rw [Finset.sum_insert has, Finsupp.sum_add_index' h0 h1, ih]
#align finsupp.sum_sum_index' Finsupp.sum_sum_index'

end

section

variable [NonUnitalNonAssocSemiring R] [NonUnitalNonAssocSemiring S]

theorem Finsupp.sum_mul (b : S) (s : α →₀ R) {f : α → R → S} :
    s.sum f * b = s.sum fun a c => f a c * b := by simp only [Finsupp.sum, Finset.sum_mul]
#align finsupp.sum_mul Finsupp.sum_mul

theorem Finsupp.mul_sum (b : S) (s : α →₀ R) {f : α → R → S} :
    b * s.sum f = s.sum fun a c => b * f a c := by simp only [Finsupp.sum, Finset.mul_sum]
#align finsupp.mul_sum Finsupp.mul_sum

end

namespace Nat

-- Porting note: Needed to replace pow with (· ^ ·)
/-- If `0 : ℕ` is not in the support of `f : ℕ →₀ ℕ` then `0 < ∏ x in f.support, x ^ (f x)`. -/
theorem prod_pow_pos_of_zero_not_mem_support {f : ℕ →₀ ℕ} (hf : 0 ∉ f.support) :
    0 < f.prod (· ^ ·) :=
 Finset.prod_pos fun a ha => pos_iff_ne_zero.mpr (pow_ne_zero _ fun H => by subst H; exact hf ha)

#align nat.prod_pow_pos_of_zero_not_mem_support Nat.prod_pow_pos_of_zero_not_mem_support

end Nat


import set_theory.cardinal
import Rings.ToMathlib.fol
import data.W.cardinal

universes u v

namespace fol

variables {L : Language.{u}}

open_locale cardinal

open fol.Language

def bounded_term.rec2_aux {n} {C : bounded_term L n → Sort v}
  (hvar : ∀(k : fin n), C &k)
  (hfunc : Π {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
    (ih_ts : ∀t, ts.pmem t → C t), C (bd_apps (bd_func f) ts)) :
  Π {l} (t : bounded_preterm L n l) (ts : dvector (bounded_term L n) l)
  (ih_ts : ∀s, ts.pmem s → C s), C (bd_apps t ts)
| l (bd_var k) dvector.nil := λ _, hvar k
| l (bd_func f)  ts := λ hs, hfunc f ts hs
| l (bd_app t s) ts := λ hs, bounded_term.rec2_aux t (dvector.cons s ts) $
  λ r hr, psum.cases_on hr
    (λ hrs, eq.rec_on hrs.symm (bounded_term.rec2_aux s dvector.nil $
      λ s₀ hs₀, false.elim $ by {cases hs₀}))
    (hs _)

def bounded_term.rec2 {n} {C : bounded_term L n → Sort v}
  (hvar : ∀(k : fin n), C &k)
  (hfunc : Π {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
    (ih_ts : ∀t, ts.pmem t → C t), C (bd_apps (bd_func f) ts)) :
  ∀(t : bounded_term L n), C t :=
λt, bounded_term.rec2_aux hvar (λ _, hfunc) t dvector.nil (λ s hs, false.elim $ by {cases hs})

-- have h : ∀{n l} (f : bounded_preformula L n l) (ts : dvector (bounded_term L n) l),
--   C n (bd_apps_rel f ts),
-- begin
--   intros, induction f; try {rw ts.zero_eq},
--   apply hfalsum, apply hequal, apply hrel, apply f_ih (f_t::ts),
--   exact himp (f_ih_f₁ ([])) (f_ih_f₂ ([])), exact hall (f_ih ([]))
-- end,
-- λn f, h f ([])

@[simp] def bounded_formula.rec2_aux {C : Πn, bounded_formula L n → Sort v}
  (hfalsum : Π {n}, C n ⊥)
  (hequal : Π {n} (t₁ t₂ : bounded_term L n), C n (t₁ ≃ t₂))
  (hrel : Π {n l : ℕ} (R : L.relations l) (ts : dvector (bounded_term L n) l),
    C n (bd_apps_rel (bd_rel R) ts))
  (himp : Π {n} {f₁ f₂ : bounded_formula L n} (ih₁ : C n f₁) (ih₂ : C n f₂), C n (f₁ ⟹ f₂))
  (hall : Π {n} {f : bounded_formula L (n+1)} (ih : C (n+1) f), C n (∀' f)) :
  ∀{n l} (f : bounded_preformula L n l) (ts : dvector (bounded_term L n) l),
  C n (bd_apps_rel f ts)
| _ _ bd_falsum dvector.nil := hfalsum
| _ _ (t₁ ≃ t₂) dvector.nil := hequal _ _
| _ _ (bd_rel R)         ts := hrel _ _
| _ _ (bd_apprel f t)    ts := by {let x := bounded_formula.rec2_aux f (dvector.cons t ts),
  dsimp [bd_apps_rel] at x, exact x }
| _ _ (f₁ ⟹ f₂) dvector.nil := himp (bounded_formula.rec2_aux f₁ dvector.nil)
  (bounded_formula.rec2_aux f₂ dvector.nil)
| _ _ (∀' f)    dvector.nil := hall (bounded_formula.rec2_aux f dvector.nil)

@[simp] def bounded_formula.rec2 {C : Πn, bounded_formula L n → Sort v}
  (hfalsum : Π {n}, C n ⊥)
  (hequal : Π {n} (t₁ t₂ : bounded_term L n), C n (t₁ ≃ t₂))
  (hrel : Π {n l : ℕ} (R : L.relations l) (ts : dvector (bounded_term L n) l),
    C n (bd_apps_rel (bd_rel R) ts))
  (himp : Π {n} {f₁ f₂ : bounded_formula L n} (ih₁ : C n f₁) (ih₂ : C n f₂), C n (f₁ ⟹ f₂))
  (hall : Π {n} {f : bounded_formula L (n+1)} (ih : C (n+1) f), C n (∀' f)) :
  ∀{n : ℕ} (f : bounded_formula L n), C n f :=
λ n f, bounded_formula.rec2_aux (λ _, hfalsum) (λ _, hequal) (λ _ _, hrel) (λ _ _ _, himp)
  (λ _ _, hall) f dvector.nil

-- lemma bounded_term.rec2_aux_bd_apps {n} {C : bounded_term L n → Sort v}
--   (hvar : ∀(k : fin n), C &k)
--   (hfunc : Π {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
--     (ih_ts : ∀t, ts.pmem t → C t), C (bd_apps (bd_func f) ts)) :
--   ∀ {l} (t : bounded_preterm L n l) (ts : dvector (bounded_term L n) l)
--     (ih_ts : ∀t, ts.pmem t → C t),
--   bounded_term.rec2_aux hvar (λ _, hfunc) (bd_apps t ts)
--     = sorry :=
-- begin
--   sorry
--   -- intros l t,
--   -- induction t,
--   -- {
--   --   intro ts,
--   --   -- induction ts,

--   -- },
--   -- {sorry},
-- end

lemma bounded_term.rec2_bd_var {n} {C : bounded_term L n → Sort v}
  (hvar : ∀(k : fin n), C &k)
  (hfunc : Π {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
    (ih_ts : ∀t, ts.pmem t → C t), C (bd_apps (bd_func f) ts)) :
  ∀ (k : fin n),
  bounded_term.rec2 hvar (λ _, hfunc) &k = hvar k := λ k, rfl

lemma bounded_term.rec2_bd_apps {n} {C : bounded_term L n → Sort v}
  (hvar : ∀(k : fin n), C &k)
  (hfunc : Π {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
    (ih_ts : ∀t, ts.pmem t → C t), C (bd_apps (bd_func f) ts)) :
  ∀ {l} (f : L.functions l) (ts : dvector (bounded_term L n) l)
    (ih_ts : ∀t, ts.pmem t → C t),
  bounded_term.rec2 hvar (λ _, hfunc) (bd_apps (bd_func f) ts)
    = hfunc f ts ih_ts :=
begin
  intros l f ts,
  induction ts with a b c hind e f g,
  { intro ih_ts,
    dsimp [bounded_term.rec2, bounded_term.rec2_aux],
    apply congr_arg,
    ext _ a,
    cases a },
  {
    intro ih_ts,
    dsimp [bd_apps, bounded_term.rec2, bounded_term.rec2_aux],
    sorry,

  },
end

namespace cardinal

variables (L) (n : ℕ)

/-- We make `bounded_term L n` as a `W_type`, viewing the `W_type` as an inductive type
  the constructors would be indexed by the following definition.
  For each `k < n` we have a variable `xₙ` (with arity zero given by `pempty`)
  For each `⟨ n , f ⟩ : Σ n : ℕ, L.functions n` we have a function application (with arity `n`) -/
@[reducible] def term_α := ulift.{u} (fin n) ⊕ Σ m : ulift.{u} ℕ, L.functions m.down

/-- To define the arities in the `W_type` for `closed_term`.
  For each `n : ℕ` we have a variable `xₙ` (with arity zero given by `pempty`)
  For each `⟨ n , f ⟩ : Σ n : ℕ, L.functions n` we have a function application (with arity `n`) -/
@[reducible] def term_β : Π (c : term_α.{u} L n), Type u
| (sum.inl m) := pempty.{u+1}
| (sum.inr ⟨ m , f ⟩) := ulift.{u} (fin m.down)

variable {L}

/-- The forward map of the equivalence `W_type_term_β_equiv_closed_term` -/
@[reducible] def bounded_term_of_W_type_term_β : W_type (term_β L n) → bounded_term L n
| ⟨ sum.inl m , b ⟩ := x_ m.down
| ⟨ sum.inr (⟨ n , f ⟩) , b ⟩ := bd_apps (bd_func f)
  (dvector.of_fn (λ k, bounded_term_of_W_type_term_β $ b (ulift.up k)))

/-- The forward map of the equivalence `W_type_term_β_equiv_closed_term` -/
@[reducible] def W_type_term_β_of_bounded_term : bounded_term L n → W_type (term_β L n) :=
  bounded_term.rec2
    (λ m, ⟨ sum.inl ⟨m⟩ , pempty.elim ⟩) $
    λ l f ts rec,
    ⟨ sum.inr ⟨ ulift.up l , f ⟩, λ k : ulift (fin l), rec (dvector.nth' ts $ k.down) dvector.pmem_nth' ⟩

lemma bounded_term_of_W_type_term_β_right_inv : ∀ t : bounded_term L n,
  bounded_term_of_W_type_term_β n (W_type_term_β_of_bounded_term n t) = t :=
begin
  apply bounded_term.rec2,
  { intro k, refl },
  { intros l f ts hind,
    dsimp only [W_type_term_β_of_bounded_term],
    rw bounded_term.rec2_bd_apps _ _ _ _ (λ t _, W_type_term_β_of_bounded_term n t),
    dsimp [bounded_term_of_W_type_term_β],
    congr,
    rw dvector.ext,
    intro i,
    simp [dvector.nth'_of_fn],
    apply hind,
    exact dvector.pmem_nth' },
end

/- This is really an equivalence, but we only need surjectivity -/
lemma bounded_term_of_W_type_term_β_surjective :
  function.surjective (@bounded_term_of_W_type_term_β L n) :=
begin
  intros t,
  use W_type_term_β_of_bounded_term n t,
  exact bounded_term_of_W_type_term_β_right_inv _ _,
end

lemma fintype_term_β : Π (a : term_α L n), fintype (term_β L n a)
| (sum.inl ⟨ m ⟩) := by apply_instance
| (sum.inr ⟨ m , f ⟩) := fintype.of_equiv (fin m.down) equiv.ulift.symm

local attribute [instance] fintype_term_β

lemma bounded_term_le_functions : #(bounded_term L n) ≤
  max (cardinal.sum (λ n : ulift.{u} (ℕ), #(L.functions n.down))) ω :=
calc #(bounded_term L n)
      ≤ #(W_type (term_β.{u} L n)) :
    cardinal.mk_le_of_surjective (bounded_term_of_W_type_term_β_surjective n)
  ... ≤ max (# (ulift.{u} (fin n) ⊕ Σ (m : ulift.{u} ℕ), L.functions m.down)) ω :
    W_type.cardinal_mk_le_max_omega_of_fintype
  ... ≤ max (#(Σ n : ulift.{u} ℕ, L.functions n.down)) ω :
  begin
    apply max_le _ (le_max_right _ _),
    simp only [cardinal.mk_sum],
    apply le_trans (cardinal.add_le_max _ _),
    apply max_le _ (le_max_right _ _),
    apply max_le (le_max_of_le_right _) (le_max_of_le_left _),
    { apply le_of_lt, simp [cardinal.lt_omega] },
    { simp },
  end
  ... = max (cardinal.sum (λ n : ulift.{u} ℕ, #(L.functions n.down))) ω :
    by {rw cardinal.mk_sigma _}

/- We show that the formulas are bounded above by the terms.
  We first construct a `W_type` for each `bounded_formula L n` as follows
  ```
  | constructor | multiplicity         | arity |
  |-------------+----------------------+-------|
  | ⊥           | unit                 | empty |
  | t₁ ≃ t₂     | (bounded_term L n)²  | empty |
  | ⟹           | unit                 | bool  |
  ```
  This gives us a way of injecting `bounded_formula L n`
  into `W_type β n ⊕ bounded_formula L (n+1)` (to account for `∀`) (there are no relations).
  This ultimately gives us
  `bounded_formula L 0 ↪ Σ n : ℕ, W_type β n ` where instead of
  mapping into `bounded_formula L (n+1)` we map into the next `W_type β (n+1)`.
  We have bounds on each `W_type β n`, namely by `unit`, `(bounded_term L n)²` and `ω`.
  Finally we can remove `unit` and the squaring.

  The difference with the case of terms is `bd_all`
  and the induction is captured in the following example
  ```
  inductive box : ℕ → Type u
  | base {n} : box n
  | drop {n} (f : box (n+1)) : box n
  ```
-/

variable (L)

def atomic_formula_α : Type u := punit.{u+1} ⊕ ((bounded_term L n) × (bounded_term L n)) ⊕ punit.{u+1}

@[reducible] def atomic_formula_β : atomic_formula_α L n → Type u
| (sum.inl x)           := pempty.{u+1}
| (sum.inr (sum.inl x)) := pempty.{u+1}
| (sum.inr (sum.inr x)) := ulift.{u} bool

/-- We inject `bounded_formula L n` into the `W_type` built from this `⊕` `bounded_formula L (n+1)`.
  This could be generalized to when `L` has relation symbols,
  in which case more constructors should be added.
  -/
@[reducible] def formula_α : Type u := atomic_formula_α L n ⊕ (bounded_formula L (n+1))

/-- The arities of the constructors `formula_α` for the `W_type` -/
@[reducible] def formula_β : formula_α L n → Type u
| (sum.inl x) := atomic_formula_β L n x
| (sum.inr x) := pempty.{u+1}

lemma fintype_formula_β : Π (a : formula_α L n), fintype (formula_β L n a)
| (sum.inl (sum.inl x)) := by apply_instance
| (sum.inl (sum.inr (sum.inl x))) := by apply_instance
| (sum.inl (sum.inr (sum.inr x))) := by apply_instance
| (sum.inr x) := by apply_instance

local attribute [instance] fintype_formula_β

variable {L}

/-- For all `n` we make a characterizing type for `bounded_formula L n`,
  in the sense that this should be an equivalence.
  For the recursion to work in the `∀` case,
  it is important to define it for all `n` at once.
  This is part of an equivalence -/
@[simp, reducible] def W_type_of_bounded_formula [is_algebraic L] {n} :
  bounded_formula L n → W_type (formula_β L n) :=
bounded_formula.rec2
  (λ _, ⟨ sum.inl $ sum.inl punit.star , pempty.elim ⟩) -- bd_falsum
  (λ _ t s, ⟨ sum.inl $ sum.inr $ sum.inl ⟨ t , s ⟩ , pempty.elim ⟩) -- t ≃ s
  (λ _ l r, false.elim $ Language.is_algebraic.empty_relations l r) -- there are no relation symbols
  (λ _ f₁ f₂ recf₁ recf₂, ⟨ sum.inl $ sum.inr $ sum.inr punit.star ,
    λ ⟨b⟩, bool.rec_on b recf₁ recf₂ ⟩ ) -- bd_imp
  (λ _ f _, ⟨ sum.inr f , pempty.elim ⟩) -- bd_all degenerate case

/-- Mapping `W_type_to_bounded_formula` (part of an equivalence) -/
@[simp, reducible] def bounded_formula_of_W_type {n} :
  W_type (formula_β L n) → bounded_formula L n
| ⟨ (sum.inl (sum.inl x)) , y ⟩ := ⊥
| ⟨ sum.inl (sum.inr (sum.inl ⟨ t , s ⟩)) , y ⟩ := t ≃ s
| ⟨ sum.inl (sum.inr (sum.inr x)) , y ⟩ :=
  bounded_formula_of_W_type (y $ ⟨ ff ⟩) ⟹
  bounded_formula_of_W_type (y $ ⟨ tt ⟩)
| ⟨ sum.inr f , y ⟩ := ∀' f

lemma bounded_formula_of_W_type_left_inv
  [is_algebraic L] {n} : ∀ f : bounded_formula L n,
  (bounded_formula_of_W_type (W_type_of_bounded_formula f) = f) :=
begin
  apply bounded_formula.rec2,
  { intros,
    simp [bounded_formula.rec2, bounded_formula.rec2_aux] },
  { intros,
    simp [bounded_formula.rec2, bounded_formula.rec2_aux] },
  { intros n l r,
    exfalso,
    exact Language.is_algebraic.empty_relations l r },
  { intros _ _ _ h1 h2,
    simp only [bounded_formula_of_W_type, W_type_of_bounded_formula,
      bounded_formula.rec2, bounded_formula.rec2_aux ],
    exact ⟨ h1 , h2 ⟩ },
  { intros,
    simp [bounded_formula.rec2, bounded_formula.rec2_aux] },
end

lemma bounded_formula_of_W_type_right_inv [is_algebraic L] {n} (f : W_type (formula_β L n)) :
  (W_type_of_bounded_formula (bounded_formula_of_W_type f) = f) :=
begin
  induction f with a b hind,
  cases a,
  cases a,
  { tidy },
  cases a,
  { tidy },
  {
    cases a,
    have hff := hind ⟨ ff ⟩,
    have htt := hind ⟨ tt ⟩,
    simp only [W_type_of_bounded_formula, bounded_formula.rec2_aux, bounded_formula.rec2,
      eq_self_iff_true, heq_iff_eq, true_and] at ⊢ hff htt,
    rw [hff, htt],
    ext bl, cases bl, cases bl,
    {refl}, {refl}, },
  { tidy },
end

/-- The types `bounded_formula L n` and `W_type (formula_β L n)` are equivalent -/
def bounded_formula_equiv_W_type [is_algebraic L] (n : ℕ) :
  _root_.equiv (bounded_formula L n) (W_type (formula_β L n)) :=
{ to_fun := W_type_of_bounded_formula,
  inv_fun := bounded_formula_of_W_type,
  left_inv := bounded_formula_of_W_type_left_inv,
  right_inv := bounded_formula_of_W_type_right_inv }

inductive box : ℕ → Type u
| base {n} : box n
| succ {n} : box n → box n
| drop {n} (x : box (n+1)) : box n

def nat_of_box : ∀ n, box n → ℕ × ℕ × ℕ
| n (box.base) := ⟨ n , 0 , 0 ⟩
| n (box.succ x) := ⟨ (nat_of_box n x).1, (nat_of_box n x).2.1.succ , (nat_of_box n x).2.2 ⟩
| n (box.drop x) := ⟨(nat_of_box (n+1) x).1 , (nat_of_box (n+1) x).2.1 ,
  (nat_of_box (n+1) x).2.2.succ ⟩

/-- Write formulas as lists of the following symbols -/
inductive logic_symbol (L : Language.{u}) : Type u
| bot : logic_symbol
| eq : logic_symbol
| imp : logic_symbol
| all : logic_symbol
| term : Π (l : ℕ), bounded_term L l → logic_symbol
| nat : ℕ → logic_symbol

/-- We inject `bounded_formula L n` into lists of symbols, keeping track
  of how the formula is build.
  We always include the number of variables of the formula at the beginning
  by adding `logic_symbo.nat l`,
  we then note the symbol for the constructor,
  and then we keep the data of the any inductively attained list. -/
@[simp] def logic_symbol_of_formula [is_algebraic L] {n} :
  bounded_formula L n → list (logic_symbol L) :=
bounded_formula.rec2
  (λ l, [logic_symbol.nat l, logic_symbol.bot]) -- ⊥
  (λ l t s, [ logic_symbol.nat l, logic_symbol.eq ,
    logic_symbol.term l t , logic_symbol.term l s ]) -- t ≃ s
  (λ _ _ r, false.elim $ Language.is_algebraic.empty_relations _ r) -- bd_rel
  (λ l ϕ ψ lϕ lψ, (logic_symbol.nat l) :: (logic_symbol.nat (list.length lϕ))
    :: (logic_symbol.nat (list.length lψ)) :: logic_symbol.imp :: lϕ.append lψ ) -- ϕ ⟹ ψ
  (λ l ϕ lϕ, (logic_symbol.nat l) :: logic_symbol.all :: lϕ) -- ∀ₗ ϕ

lemma logic_symbol_of_preformula_injective [is_algebraic L] {n} : ∀ (x : bounded_formula L n)
  {m} (y : bounded_formula L m),
  logic_symbol_of_formula x = logic_symbol_of_formula y → x == y :=
begin
  -- apply bounded_formula.rec2,
  have hrel : ∀ {l} {p : Prop} (r : L.relations l), p,
  { intros _ _ r,
    exact false.elim (Language.is_algebraic.empty_relations _ r) },
  apply @bounded_formula.rec2 _ (λ _ x, ∀ {m} (y : bounded_formula L m),
    logic_symbol_of_formula x = logic_symbol_of_formula y → x == y),
  { intro l,
    apply @bounded_formula.rec2 _ (λ n y,
      logic_symbol_of_formula _ = logic_symbol_of_formula y → _ == y),
    { intros k h, simp only [logic_symbol_of_formula, bounded_formula.rec2_aux,
        bounded_formula.rec2, eq_self_iff_true, and_true] at h, subst h },
    { intros _ _ _ h, simp only [logic_symbol_of_formula, bounded_formula.rec2_aux,
        bounded_formula.rec2, and_false] at h, simpa [h] },
    { intros _ _ r, apply hrel r },
    { intros _ _ _ _ _ h, simp only [logic_symbol_of_formula, bounded_formula.rec2_aux,
        bounded_formula.rec2, and_false] at h, simpa [h] },
    { intros _ _ _ h, simp only [logic_symbol_of_formula, bounded_formula.rec2_aux,
        bounded_formula.rec2, false_and, and_false] at h, simpa [h] } },
  { intros l t s,
    apply @bounded_formula.rec2 _ (λ n y,
      logic_symbol_of_formula _ = logic_symbol_of_formula y → _ == y),
    { intros k h, simp at h, simpa [h], },
    { intros _ _ _ h, simp at h, cases h with h h', subst h, cases h' with h h',
      cases h with h h1, subst h1, cases h' with h' h'1, subst h'1 },
    { intros _ _ r, apply hrel r },
    { intros _ _ _ _ _ h, simp at h, simpa [h] },
    { intros _ _ _ h, simp at h, simpa [h] } },
  { intros _ _ r, apply hrel r },
  { intros l f₁ f₂ hf₁ hf₂,
    apply @bounded_formula.rec2 _ (λ n y,
      logic_symbol_of_formula _ = logic_symbol_of_formula y → _ == y),
    { intros k h, simp at h, simpa [h], },
    { intros _ _ _ h, simp at h, simpa [h] },
    { intros _ _ r, apply hrel r },
    { intros l' f₁' f₂' hf₁' hf₂' h, simp at h, obtain ⟨ hll' , hlenϕ , hlenψ , h ⟩ := h,
      subst hll', obtain ⟨ hf₁f₁' , hf₂f₂'⟩ := list.append_inj h hlenϕ, congr,
      { simp at hf₁, specialize hf₁ f₁' hf₁f₁', subst hf₁ },
      { simp at hf₂, specialize hf₂ f₂' hf₂f₂', subst hf₂ } },
    { intros _ _ _ h, simp at h, simpa [h] } },
  { intros l f₁ hf₁ m, apply @bounded_formula.rec2 _
      (λ n y, logic_symbol_of_formula _ = logic_symbol_of_formula y → _ == y),
    { intros k h, simp at h, simpa [h], },
    { intros _ _ _ h, simp at h, simpa [h] },
    { intros _ _ r, apply hrel r },
    { intros _ _ _ _ _ h, simp at h, simpa [h] },
    { intros k f₂ hf₂ h, simp only [logic_symbol_of_formula, bounded_formula.rec2_aux,
        bounded_formula.rec2, eq_self_iff_true, true_and] at h hf₁,
      cases h with hlk h, subst hlk, congr1, apply eq_of_heq, apply hf₁, exact h } },
end

lemma card_le_max [is_algebraic L] {n} (hn : #(bounded_formula L (n + 1))
    ≤ max (cardinal.sum (λ (m : ulift.{u} ℕ), #(bounded_term L m.down))) ω) :
  #(bounded_formula L n)
    ≤ max (cardinal.sum (λ m : ulift.{u} ℕ, #(bounded_term L m.down))) ω :=
calc #(bounded_formula L n)
        = #(W_type (formula_β L n)) :
  cardinal.mk_congr (bounded_formula_equiv_W_type _)
    ... ≤ _ : W_type.cardinal_mk_le_max_omega_of_fintype
    ... ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(bounded_term L n.down))) ω :
begin
  dsimp only [formula_α, atomic_formula_α],
  apply max_le _ (le_max_right _ _),
  simp only [cardinal.mk_sum, cardinal.mk_punit, cardinal.mk_prod, cardinal.lift_id],
  apply le_trans (cardinal.add_le_max _ _) (max_le (max_le _ _) (le_max_right _ _)),
  { apply le_trans (cardinal.add_le_max _ _) (max_le (max_le _ _) (le_max_right _ _)),
    { apply le_of_lt, simp },
    { apply le_trans (cardinal.add_le_max _ _) (max_le (max_le _ _) (le_max_right _ _)),
      { apply le_trans (cardinal.mul_le_max _ _) (max_le (max_le _ _) (le_max_right _ _)),
        repeat { apply le_max_of_le_left,
          apply cardinal.le_sum.{u u} (λ (n : ulift.{u} ℕ), #(bounded_term.{u} L n.down)) ⟨ n ⟩ }},
      { apply le_max_of_le_right (le_of_lt _),
        simp } } },
  { exact hn },
end

lemma bounded_formula_le_bounded_term [is_algebraic L] :
  #(bounded_formula L n) ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(bounded_term L n.down))) ω :=
calc #(bounded_formula L n)
        = #(W_type (formula_β L n)) :
  cardinal.mk_congr (bounded_formula_equiv_W_type _)
    ... ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(bounded_term L n.down))) ω : sorry

lemma sentence_le_bounded_term :
  #(bounded_formula L 0) ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(bounded_term L n.down))) ω :=
calc #(bounded_formula L 0) ≤ #(W_type (formula_β L 0)) : sorry
     ... ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(bounded_term L n.down))) ω : sorry

lemma sentence_le_functions :
  #(bounded_formula L 0) ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(L.functions n.down))) ω :=
begin
  apply le_trans (sentence_le_bounded_term),
  apply max_le _ (le_max_right _ _),
  apply le_trans (cardinal.sum_le_sup _),
  simp only [cardinal.mk_denumerable],
  apply le_trans (cardinal.mul_le_max _ _),
  apply max_le _ (le_max_right _ _),
  apply max_le (le_max_right _ _),
  rw cardinal.sup_le,
  intro i,
  apply bounded_term_le_functions,
end

variable (L)

/-- Applying `∀` is an injection downwards. -/
def bounded_formula_bd_all : bounded_formula L (n+1) → (bounded_formula L n) :=
λ ϕ, ∀' ϕ

/-- Applying `∀` n times is an injection. "Dropbox" -/
def bounded_formula_bd_alls : Π n, bounded_formula L n → (bounded_formula L 0)
| 0 := id
| (n+1) := (bounded_formula_bd_alls n) ∘ bounded_formula_bd_all L n

variable {L}

lemma bounded_formula_bd_all_injective : function.injective (bounded_formula_bd_all L n) :=
λ ϕ ψ, bounded_preformula.bd_all.inj

lemma bounded_formula_bd_alls_injective : Π n, function.injective (bounded_formula_bd_alls L n)
| 0 := function.injective_id
| (n+1) := function.injective.comp (bounded_formula_bd_alls_injective n) (bounded_formula_bd_all_injective n)

/- Using ∀ we can embed `bounded_formula L (n+1)` into `bounded_formula L n`,
  hence showing they are all bounded by the function symbols
 -/
lemma bounded_formula_le_functions (n : ℕ) :
  #(bounded_formula L n) ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(L.functions n.down))) ω :=
calc #(bounded_formula L n) ≤ #(bounded_formula L 0) : cardinal.mk_le_of_injective (bounded_formula_bd_alls_injective _)
                        ... ≤ max (cardinal.sum (λ n : ulift.{u} ℕ, #(L.functions n.down))) ω :
                        sentence_le_functions

end cardinal

end fol