import Mathlib.Algebra.MonoidAlgebra.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Algebra.Group.InjSurj
import LeanUnits.Framework.Quantities.Basic
import Mathlib.Tactic

/-!
Credit to @TerrenceTao for these lemmas about Fomalizing quantities into the AddMonoidAlgebra.
The original source code under Apache License is there :
https://github.com/teorth/analysis/blob/18d4fd7253ff17a05133d9b6b120b5f08f5ce6ad/analysis/Analysis/Misc/UnitsSystem.lean
Permission was given to use his lemmas and definitions as a starting point for the formal
side of the library.
-/

namespace Units.Quantity

set_option linter.unusedSectionVars false

-- for Formal we work over a Field like ℝ or ℚ or ℂ
variable {α : Type} [Field α]
-- we require δ to be an AddCommGroup with an equivalence relation
variable {δ : Type} [AddCommGroup δ] [Setoid δ]
-- here d, d₁, d₂, d₃ can be any dimensions or units in δ
variable {d d₁ d₂ d₃ d₄ : δ}

theorem dim_eq_dim {d : δ} [HasDimension δ] (q : Quantity d α) :
  𝒟 q = 𝒟 d := by rfl

theorem dim_add_eq_dim_left [HasDimension δ] (q₁ : Quantity d₁ α)
  (q₂ : Quantity d₂ α) (h : d₁ = d₂ := by module) :
  𝒟 (q₁ + q₂.cast (eq_imp_equiv h.symm)) = 𝒟 q₁ := by
  subst h
  rfl

theorem dim_add_eq_dim_left' [HasDimension δ] (q1 q2 : Quantity d α) : 𝒟 (q1 + q2) = 𝒟 q1 := by
  rfl

theorem dim_add_eq_dim_right {d₁ d₂ : δ} [HasDimension δ] (q₁ : Quantity d₁ α)
  (q₂ : Quantity d₂ α) (h : d₁ = d₂ := by module) :
  𝒟 (q₁.cast (eq_imp_equiv h) + q₂) = 𝒟 q₂ := by
  subst h
  rfl

theorem dim_add_eq_dim_right' [HasDimension δ] (q1 q2 : Quantity d α) : 𝒟 (q1 + q2) = 𝒟 q2 := by
  rfl

theorem eq_imp_equiv {μ} [Setoid μ] {u1 u2 : μ} (h : u1 = u2) : u1 ≈ u2 := by
  rw [h]

@[simp]
theorem val_inj (q₁ q₂ : Quantity d α) :
  q₁.val = q₂.val ↔ q₁ = q₂ := by
  constructor
  · intro h; cases q₁; cases q₂; congr
  · intro h; rw [h]

theorem cast_eq (q₁ : Quantity d₁ α) (q₂ : Quantity d₂ α) (h : d₁ = d₂ := by module)
  : q₁.val = q₂.val ↔ q₁ = q₂.cast (eq_imp_equiv h.symm) := by
  aesop


theorem cast_eq_symm (q₁ : Quantity d₁ α) (q₂ : Quantity d₂ α) (h : d₁ ≈ d₂ := by module)
  : q₁ = q₂.cast (Setoid.symm h) ↔ q₂ = q₁.cast h := by aesop

@[simp]
theorem cast_val (q : Quantity d₁ α) (h : d₁ = d₂ := by module)
  : (q.cast (eq_imp_equiv h)).val = q.val := by
  subst h
  rfl

@[simp]
theorem val_zero : (0:Quantity d α).val = 0 := rfl

@[simp]
theorem val_one : (1:Quantity d α).val = 1 := rfl

theorem neZero_iff (q : Quantity d α) : NeZero q ↔ q.val ≠ 0 := by
  simp only [_root_.neZero_iff, ne_eq, ← val_inj, val_zero]

@[simp]
theorem val_add (q₁ q₂ : Quantity d α) : (q₁ + q₂).val = q₁.val + q₂.val := rfl

@[simp]
theorem val_neg (q : Quantity d α) : (-q).val = -q.val := rfl

instance instNeZero_neg (q : Quantity d α) [h : NeZero q] : NeZero (-q) := by
  rw [neZero_iff] at h ⊢
  simp only [val_neg, ne_eq, neg_eq_zero, h, not_false_eq_true]

@[simp]
theorem val_sub (q₁ q₂ : Quantity d α) : (q₁ - q₂).val = q₁.val - q₂.val := rfl

@[simp]
theorem val_smul [SMul α α] (n : α) (q : Quantity d α) : (n • q).val = n • q.val := rfl

theorem smul_def [SMul α α] (n : α) (q : Quantity d α) : (n • q) = ⟨n • q.val⟩ := rfl

theorem val_smul_eq_mul (n : α) (q : Quantity d α) : n • q.val = n * q.val := by
  rw [_root_.smul_eq_mul]

instance instAddGroup : AddGroup (Quantity d α) where
  zero := Zero.zero
  add := Add.add
  neg := Neg.neg
  sub := Sub.sub
  nsmul := nsmul
  zsmul := zsmul
  zero_add := by
    intro q
    rw [← val_inj]
    simp only [val_add, val_zero, zero_add]
  add_zero := by
    intro q
    rw [← val_inj]
    simp only [val_add, val_zero, add_zero]
  add_assoc := by
    intro a b c
    rw [← val_inj]
    simp only [val_add, add_assoc]
  sub_eq_add_neg := by
    intro a b
    rw [← val_inj]
    simp only [val_sub, sub_eq_add_neg, val_add, val_neg]
  neg_add_cancel := by
    intro a
    rw [← val_inj]
    simp only [val_add, val_neg, neg_add_cancel, val_zero]
  nsmul_zero := by
    intro a
    rw [nsmul, ← val_inj]
    simp only [zero_nsmul, val_zero]
  nsmul_succ := by
    intro n a
    rw [nsmul, nsmul, ← val_inj]
    simp only [nsmul_eq_mul, Nat.cast_add, Nat.cast_one, add_mul, one_mul, val_add]
  zsmul_zero' := by
    intro a
    rw [zsmul, ← val_inj]
    simp only [zero_smul, val_zero]
  zsmul_succ' := by
    intro n a
    rw [zsmul, zsmul, ← val_inj]
    simp only [zsmul_eq_mul, Int.cast_natCast, val_add,Nat.cast_succ, add_mul,
    val_add,add_mul, zsmul_eq_mul, Int.cast_add, Int.cast_natCast, Nat.succ_eq_add_one]
    ring
  zsmul_neg' := by
    intro n a
    rw [zsmul, zsmul, ← val_inj]
    simp only [Int.negSucc_eq, zsmul_eq_mul, Int.cast_add, Int.cast_neg,
      Int.cast_natCast, Nat.succ_eq_add_one, Nat.cast_add, val_neg, neg_mul,Nat.cast_one]

instance : AddCommGroup (Quantity d α) where
  add_comm := by
    intro a b
    rw [← val_inj]
    simp only [val_add, add_comm]

@[simp]
theorem val_mul [Mul α] (q₁ : Quantity d₁ α) (q₂ : Quantity d₂ α) :
  (q₁ * q₂).val = q₁.val * q₂.val := rfl

@[simp]
theorem val_div [Div α] (q₁ : Quantity d₁ α) (q₂ : Quantity d₂ α) :
  (q₁ / q₂).val = q₁.val / q₂.val := rfl

@[simp]
theorem val_inv [Inv α] (q : Quantity d α) : (q⁻¹).val = Inv.inv q.val := rfl

@[simp]
theorem val_npow (q : Quantity d α) (n : ℕ) :
  (q.npow n).val = q.val ^ n := rfl

@[simp]
theorem val_zpow (q : Quantity d α) (n : ℤ) :
  (q.zpow n).val = q.val ^ n := rfl

@[simp]
theorem val_pow (q : Quantity d α) (n : ℕ) :
  (q ^ᵈ n).val = q.val ^ n := rfl

@[coe]
def ofField {α} [Field α] (a : α) : Quantity (0: δ) α := ⟨ a ⟩

instance instCoeField : Coe α (Quantity (0: δ) α) where
  coe := ofField

@[simp]
theorem coe_val (a : α) : (a:Quantity (0: δ) α).val = a := rfl

@[norm_cast, simp]
theorem coe_zero : ((0:α):Quantity (0: δ) α) = 0 := rfl

theorem neZero_coe_iff {a : α} : NeZero (a: Quantity (0: δ) α) ↔ a ≠ 0 := by
  simp only [neZero_iff, coe_val, ne_eq]

@[simp]
theorem coe_inj {a b : α} :
  (a:Quantity (0: δ) α) = (b:Quantity (0: δ) α) ↔ a = b := by
  rw [ofField, ofField, ←val_inj]

@[norm_cast, simp]
theorem coe_add (a b : α) :
  ((a+b:α):Quantity (0: δ) α) = (a:Quantity (0: δ) α) + (b:Quantity (0: δ) α) := rfl

@[norm_cast, simp]
theorem coe_neg (a : α) : ((-a:α):Quantity (0: δ) α) = -(a: Quantity (0:δ) α) := rfl

@[norm_cast, simp]
theorem coe_sub (a b : α) :
  ((a-b:α):Quantity (0:δ) α) = (a:Quantity (0:δ) α) - (b:Quantity (0:δ) α) := by
  simp only [ofField]
  rfl

def differentiable [NontriviallyNormedField α]
  (f : Quantity d₁ α → Quantity d₂ α) : Prop := Differentiable α (fun t : α => (f ⟨t⟩).val)

theorem differentiable_fun_id [NontriviallyNormedField α] :
  differentiable (fun t : Quantity d₁ α => t) := by
  rw [differentiable]
  exact _root_.differentiable_fun_id

theorem deriv_qconst [NontriviallyNormedField α] (x : Quantity d₁ α) (c : Quantity d₂ α) :
  deriv (fun _ => c ) x = 0 := by
  rw [← val_inj]
  simp only [deriv]
  exact _root_.deriv_const x.val c.val

theorem deriv_id [NontriviallyNormedField α] (x : Quantity d₁ α) :
  deriv (fun t => t) x = 1 := by
  rw [← val_inj]
  simp only [deriv]
  exact _root_.deriv_id x.val

-- to do generalize to α, but we get errors for now
theorem deriv_const_smul_real
  {f : Quantity d₁ ℝ → Quantity d₂ ℝ} {x : Quantity d₁ ℝ}
  (c : ℝ) (h_f_diff : differentiable f) :
  deriv (fun t => c • f t) x = c • deriv f x := by
  rw [← val_inj]
  simp only [deriv, val_smul]
  exact deriv_const_smul c h_f_diff.differentiableAt

theorem deriv_qconst_mul_real
  (c : Quantity d₃ ℝ) (f : Quantity d₁ ℝ → Quantity d₂ ℝ) (x : Quantity d₁ ℝ)
  (h_f_diff : differentiable f) :
  deriv (fun t => c * (f t) ) x = ↑(c * deriv f x) := by
  rw [← val_inj]
  simp only [deriv]
  exact deriv_const_mul c.val h_f_diff.differentiableAt

theorem deriv_add_real
  (f g : Quantity d₁ ℝ → Quantity d₂ ℝ) (x : Quantity d₁ ℝ)
  (h_f_diff : differentiable f) (h_g_diff : differentiable g) :
  deriv (f + g) x = deriv f x + deriv g x := by
  rw [← val_inj]
  simp only [deriv, val_add]
  exact deriv_add (h_f_diff.differentiableAt) (h_g_diff.differentiableAt)

theorem deriv_mul_real
  (f : Quantity d₁ ℝ → Quantity d₂ ℝ) (g : Quantity d₁ ℝ → Quantity d₃ ℝ) (x : Quantity d₁ ℝ)
  (h_f_diff : differentiable f) (h_g_diff : differentiable g) :
  deriv (fun t => (f t) * (g t)) x = ↑(deriv f x * g x + ↑(f x * deriv g x)) := by
  rw [← val_inj]
  simp only [deriv]
  exact deriv_mul (h_f_diff.differentiableAt) (h_g_diff.differentiableAt)

theorem deriv_div_real
  (f : Quantity d₁ ℝ → Quantity d₂ ℝ) (g : Quantity d₁ ℝ → Quantity d₃ ℝ) (x : Quantity d₁ ℝ)
  (h_f_diff : differentiable f) (h_g_diff : differentiable g) [h_gx : NeZero (g x)] :
  deriv (fun t => (f t) / (g t)) x =
    ↑((deriv f x * g x - ↑(f x * deriv g x)) / (g x)²) := by
  rw [← val_inj]
  simp [deriv, cast]
  rw [neZero_iff] at h_gx
  exact deriv_div (h_f_diff.differentiableAt) (h_g_diff.differentiableAt) h_gx

/--
A Quantity can be formalized into an AddMonoidAlgebra
`Formal δ α = AddMonoidAlgebra α δ`
where
- δ is the type of dimensions or units, which is an AddCommGroup with an equivalence relation
- α is the type of scalars, which is a Semiring (we use a Field here for convenience)

This allows to use the machinery of AddMonoidAlgebra to prove lemmas about quantities.
For example, the multiplication of quantities is associative up to equivalence
-/
abbrev Formal (δ α) [Field α] [AddCommGroup δ] [Setoid δ] := AddMonoidAlgebra α δ

namespace Formal


@[coe]
noncomputable def toFormal (q : Quantity d α) : Formal δ α :=
  AddMonoidAlgebra.single d q.val

noncomputable instance instCoeFormal : CoeOut (Quantity d α) (Formal δ α) where
  coe := toFormal

@[simp]
theorem toFormal_inj (q₁ q₂: Quantity d α) :
  (q₁ : Formal δ α) = (q₂ : Formal δ α) ↔ q₁ = q₂ := by
  constructor
  · simp only [toFormal, ← val_inj]
    intro h
    replace h := congr($h d)
    repeat rw [Finsupp.single_eq_same] at h
    exact h
  · intro h
    rw [h]

@[simp]
theorem toFormal_cast (q : Quantity d₁ α) (h : d₁ = d₂ := by module) :
  ((q.cast (eq_imp_equiv h)):Formal δ α) = (q:Formal δ α) := by
  subst h
  rw [cast]

@[simp, norm_cast]
theorem toFormal_zero : ((0:Quantity d α): Formal δ α) = 0 := by
  simp only [toFormal, val_zero, Finsupp.single_zero]

@[simp, norm_cast]
theorem toFormal_one : ((1:Quantity (0:δ) α): Formal δ α) = 1 := by
  simp only [toFormal, val_one]
  rfl

@[simp, norm_cast]
theorem toFormal_add (q₁ q₂ : Quantity d α) :
  ((q₁ + q₂:Quantity d α):Formal δ α) = (q₁:Formal δ α) + (q₂:Formal δ α) := by
  simp only [toFormal, val_add, Finsupp.single_add]

@[simp, norm_cast]
theorem toFormal_neg (q : Quantity d α) :
  ((-q:Quantity d α):Formal δ α) = -(q:Formal δ α) := by
  simp only [toFormal, val_neg, Finsupp.single_neg]

@[simp, norm_cast]
theorem toFormal_sub (q₁ q₂ : Quantity d α) :
  ((q₁ - q₂ :Quantity d α):Formal δ α) = (q₁:Formal δ α) - (q₂:Formal δ α) := by
  simp only [toFormal, val_sub, Finsupp.single_sub]

@[norm_cast, simp]
theorem toFormal_pow (q : Quantity d α) (n : ℕ) :
  ((q ^ᵈ n):Formal δ α) = (q:Formal δ α) ^ n := by
  simp [toFormal, AddMonoidAlgebra.single_pow]

@[norm_cast, simp]
theorem toFormal_sq (q : Quantity d α) :
  ((q²):Formal δ α) = (q:Formal δ α) ^ 2 := by
  simp [toFormal, AddMonoidAlgebra.single_pow]

@[norm_cast, simp]
theorem toFormal_cube (q : Quantity d α) :
  ((q³):Formal δ α) = (q:Formal δ α) ^ 3 := by
  simp [toFormal, AddMonoidAlgebra.single_pow]


@[simp]
theorem pow_inv (q : Quantity d α) (n : ℕ) : (q ^ᵈ n)⁻¹ = ↑(q⁻¹ ^ᵈ n) := by
  simp [←toFormal_inj, toFormal]

noncomputable instance instCoeReal : Coe α (Formal δ α) where
  coe a := ((a:Quantity (0:δ) α):Formal δ α)

@[norm_cast, simp]
theorem coe_zero : ((0:α): Formal δ α) = 0 := by
  simp only [Quantity.coe_zero, toFormal_zero]

@[norm_cast, simp]
theorem coe_one : ((1:α):Formal δ α) = 1 := by
  rfl

@[norm_cast, simp]
theorem coe_nat (n : ℕ) : ((n:α):Formal δ α) = (n:Formal δ α) := by
  rfl

@[norm_cast, simp]
theorem coe_int (n : ℤ) : ((n:α):Formal δ α) = (n:Formal δ α) := by
  rfl

@[norm_cast, simp]
theorem toFormal_smul (c : α) (q : Quantity d α)
  : ((c • q:Quantity d α):Formal δ α) = (c:Formal δ α) * (q:Formal δ α) := by
  simp only [toFormal, val_smul, _root_.smul_eq_mul, coe_val, AddMonoidAlgebra.single_mul_single,
    zero_add]

@[simp]
theorem smul_eq_mul (c : α) (x : Formal δ α) : c • x = (c:Formal δ α) * x := by
  ext n
  rw [toFormal, coe_val]
  rw [Finsupp.smul_apply, AddMonoidAlgebra.single_zero_mul_apply, _root_.smul_eq_mul]

@[simp]
theorem nsmul_eq_mul (c : ℕ) (x : Formal δ α) : c • x = (c:Formal δ α) * x := by
  rw [_root_.nsmul_eq_mul]

@[simp]
theorem zsmul_eq_mul (c : ℤ) (x : Formal δ α) : c • x = (c : Formal δ α) * x := by
  rw [_root_.zsmul_eq_mul]

end Formal

@[norm_cast, simp]
theorem coe_mul (a b : α) : ((a*b:α):Quantity (0: δ) α) = a • (b:Quantity (0:δ) α) := by
  simp only [ofField]
  rfl

instance instModule : Module α (Quantity d α) where
  smul_add c q₁ q₂ := by simp [←Formal.toFormal_inj]; ring
  add_smul c1 c2 q := by simp [←Formal.toFormal_inj]; ring
  one_smul q := by simp [←Formal.toFormal_inj]
  zero_smul q := by simp [←Formal.toFormal_inj]
  mul_smul c1 c2 q := by simp [←Formal.toFormal_inj]; ring
  smul_zero c := by simp [←Formal.toFormal_inj]

instance instMulAction : MulAction α (Quantity d α) where
  smul := (· • ·)
  one_smul := by simp only [one_smul, implies_true]
  mul_smul := by simp only [mul_smul, implies_true]

@[simp]
theorem val_nsmul (c : ℕ) (q : Quantity d α) : (c • q).val = c * q.val := by
  simp [←Nat.cast_smul_eq_nsmul α]

@[simp]
theorem val_zsmul (c : ℤ) (q : Quantity d α) : (c • q).val = c * q.val := by
  simp [←Int.cast_smul_eq_zsmul α]

@[norm_cast, simp]
theorem toFormal_nsmul (c : ℕ) (q : Quantity d α)
  : ((c • q:Quantity d α):Formal δ α) = (c:Formal δ α) * (q:Formal δ α) := by
  simp [←Nat.cast_smul_eq_nsmul α]

@[norm_cast, simp]
theorem toFormal_zsmul (c : ℤ) (q : Quantity d α)
  : ((c • q:Quantity d α):Formal δ α) = (c:Formal δ α) * (q:Formal δ α) := by
  simp [←Int.cast_smul_eq_zsmul α]

@[norm_cast, simp]
theorem toFormal_mul (q₁ : Quantity d₁ α) (q₂ : Quantity d₂ α) :
  ((q₁ * q₂:Quantity (d₁+d₂) α):Formal δ α) = (q₁:Formal δ α) * (q₂:Formal δ α) := by
  simp [Formal.toFormal, AddMonoidAlgebra.single_mul_single]

protected theorem mul_one (a : Quantity d₁ α) (h : d₁ = d₁ + d₂ := by module) :
  a * (1: Quantity (d₂:δ) α) = a.cast (eq_imp_equiv h) := by
  rw [←val_inj,val_mul, val_one,_root_.mul_one]
  rfl

protected theorem mul_one' (a : Quantity d α) :
  a * (1: Quantity (0:δ) α) = ↑a := by
  rw [Quantity.mul_one]

protected theorem one_mul (a : Quantity d₂ α) (h : d₂ = d₁ + d₂ := by module) :
  (1: Quantity (d₁:δ) α) * a = a.cast (eq_imp_equiv h) := by
  rw [← val_inj, val_mul, val_one, _root_.one_mul]
  rfl

protected theorem one_mul' (a : Quantity d α) :
  (1: Quantity (0:δ) α) * a = ↑a := by
  rw [Quantity.one_mul]

protected theorem mul_zero (a : Quantity d₁ α) (h : d₁ = d₁ + d₂ := by module) :
  a * (0: Quantity (d₂:δ) α) = (0: Quantity d₁ α).cast (eq_imp_equiv h) := by
  rw [← val_inj, val_mul, val_zero, MulZeroClass.mul_zero]
  rfl

protected theorem mul_zero' (a : Quantity d α) :
  a * (0: Quantity (0:δ) α) = ↑(0: Quantity d α) := by
  rw [Quantity.mul_zero]

protected theorem zero_mul (a : Quantity d₂ α) (h : d₂ = d₁ + d₂ := by module) :
  (0: Quantity (d₁:δ) α) * a = (0: Quantity d₂ α).cast (eq_imp_equiv h) := by
  rw [← val_inj, val_mul, val_zero, MulZeroClass.zero_mul]
  rfl

protected theorem zero_mul' (a : Quantity d α) :
  (0: Quantity (0:δ) α) * a = ↑(0: Quantity d α) := by
  rw [Quantity.zero_mul]

protected theorem mul_eq_zero {a : Quantity d₁ α} {b : Quantity d₂ α} :
  a * b = 0 ↔ a = 0 ∨ b = 0 := by
  simp only [← val_inj, val_mul, val_zero, _root_.mul_eq_zero]

@[simp]
protected theorem mul_inv_cancel (a : Quantity d α) [h : NeZero a] :
  a * a⁻¹ = ↑(1: Quantity (0:δ) α) := by
  rw [neZero_iff] at h
  rw [← val_inj, val_mul, val_inv, cast_val, val_one]
  exact mul_inv_cancel₀ h

@[simp]
protected theorem inv_mul_cancel (a : Quantity d α) [h : NeZero a] :
  a⁻¹ * a = ↑(1: Quantity (0:δ) α) := by
  rw [neZero_iff] at h
  rw [← val_inj, val_mul, val_inv, cast_val, val_one]
  exact inv_mul_cancel₀ h

protected theorem mul_comm (a : Quantity d₁ α) (b : Quantity d₂ α) :
  a * b = ↑(b * a) := by
  rw [←Formal.toFormal_inj, Formal.toFormal_cast]
  repeat rw [toFormal_mul]
  ring

protected theorem mul_assoc (a : Quantity d₁ α) (b : Quantity d₂ α) (c : Quantity d₃ α) :
  a * (b * c) = ↑((a * b) * c) := by
  rw [←Formal.toFormal_inj, Formal.toFormal_cast]
  repeat rw [toFormal_mul]
  ring

theorem conj_eq_self (a : Quantity d₁ α) (b : Quantity d₂ α) [h : NeZero a] :
  a⁻¹ * b * a = ↑b := by
  rw [Quantity.mul_comm, Quantity.mul_assoc, Quantity.mul_inv_cancel, ← Formal.toFormal_inj]
  simp

theorem conj_eq_self' (a : Quantity d₁ α) (b : Quantity d₂ α) [h : NeZero a] :
  a * b * a⁻¹ = ↑b := by
  rw [Quantity.mul_comm, Quantity.mul_assoc, Quantity.inv_mul_cancel, ← Formal.toFormal_inj]
  simp

theorem left_distrib (a : Quantity d₁ α) (b c : Quantity d₂ α) :
  a * (b + c) = ↑(a * b + a * c) := by
  simp [←Formal.toFormal_inj]
  ring

theorem right_distrib (a b : Quantity d₁ α) (c : Quantity d₂ α) :
  (a + b) * c = ↑(a * c + b * c) := by
  simp [←Formal.toFormal_inj]
  ring

theorem square_add (a b : Quantity d α) : (a + b) ^ᵈ (2:ℕ) = a ^ᵈ 2 + ↑(2 • a * b) + b ^ᵈ 2 := by
  simp only [← Formal.toFormal_inj, Formal.toFormal_pow, Formal.toFormal_add]
  rw [Formal.toFormal_cast, toFormal_mul, toFormal_nsmul, Nat.cast_ofNat]
  ring

theorem sq_add (a b : Quantity d α) : (a + b)² = a² + ↑(2 • a * b) + b² := by
  simp [← Formal.toFormal_inj]
  rw [Formal.toFormal_cast, toFormal_mul, toFormal_nsmul, Nat.cast_ofNat]
  ring

protected theorem smul_mul_smul (c1 c2 : α) (q1 : Quantity d₁ α) (q2 : Quantity d₂ α) :
  (c1 • q1) * (c2 • q2) = (c1 * c2) • (q1 * q2) := by
  simp [← Formal.toFormal_inj]
  ring

theorem smul_inj {c : α} {q1 q2 : Quantity d α} (h_ne_zero : c ≠ 0) :
  (c • q1) = (c • q2) ↔ q1 = q2 := by
  constructor
  · intro h
    apply congrArg ((1/c) • ·) at h
    repeat rw [smul_smul] at h
    rw [mul_comm,mul_one_div,div_self h_ne_zero,one_smul,one_smul] at h
    exact h
  · intro h
    rw [h]

@[simp]
theorem dim_def [HasDimension δ] (q : Quantity d α) : q.dimension = HasDimension.dimension d := rfl

end Units.Quantity
