import Lean

universe u

structure Magma where
  α   : Type u
  mul : α → α → α

instance : CoeSort Magma (Type u) where
  coe s := s.α

abbrev mul {M : Magma} (a b : M) : M :=
  M.mul a b

set_option pp.all true

infixl:70 (priority := high) "*" => mul

structure Semigroup extends Magma where
  mul_assoc (a b c : α) : a * b * c = a * (b * c)

instance : CoeSort Semigroup (Type u) where
  coe s := s.α

structure CommSemigroup extends Semigroup where
  mul_comm (a b : α) : a * b = b * a

structure Monoid extends Semigroup where
  one : α
  one_mul (a : α) : one * a = a
  mul_one (a : α) : a * one = a

instance : CoeSort Monoid (Type u) where
  coe s := s.α

structure CommMonoid extends Monoid where
  mul_comm (a b : α) : a * b = b * a

instance : Coe CommMonoid CommSemigroup where
  coe s := {
      α   := s.α
      mul := s.mul
      mul_assoc := s.mul_assoc
      mul_comm  := s.mul_comm
    }

structure Group extends Monoid where
  inv : α → α
  mul_left_inv (a : α) : (inv a) * a = one

instance : CoeSort Group (Type u) where
  coe s := s.α

abbrev inv {G : Group} (a : G) : G :=
  G.inv a

postfix:max "⁻¹" => inv

instance (G : Group) : OfNat (CoeSort.coe G.toMagma) (nat_lit 1) where
  ofNat := G.one

instance (G : Group) : OfNat (G.toMagma.α) (nat_lit 1) where
  ofNat := G.one

structure CommGroup extends Group where
  mul_comm (a b : α) : a * b = b * a

instance : CoeSort CommGroup (Type u) where
  coe s := s.α

theorem inv_mul_cancel_left {G : Group} (a b : G) : a⁻¹ * (a * b) = b := by
  rw [← G.mul_assoc, G.mul_left_inv, G.one_mul]

theorem toMonoidOneEq {G : Group} : G.toMonoid.one = 1 :=
  rfl

theorem inv_eq_of_mul_eq_one {G : Group} {a b : G} (h : a * b = 1) : a⁻¹ = b := by
  rw [← G.mul_one a⁻¹, toMonoidOneEq, ←h, ← G.mul_assoc, G.mul_left_inv, G.one_mul]

theorem inv_inv {G : Group} (a : G) : (a⁻¹)⁻¹ = a :=
  inv_eq_of_mul_eq_one (G.mul_left_inv a)

theorem mul_right_inv {G : Group} (a : G) : a * a⁻¹ = 1 := by
  have : a⁻¹⁻¹ * a⁻¹ = 1 := by rw [G.mul_left_inv]; rfl
  rw [inv_inv] at this
  assumption

unif_hint (G : Group) where
  |- G.toMonoid.one =?= 1

theorem mul_inv_rev {G : Group} (a b : G) : (a * b)⁻¹ = b⁻¹ * a⁻¹ := by
  apply inv_eq_of_mul_eq_one
  rw [G.mul_assoc, ← G.mul_assoc b, mul_right_inv, G.one_mul, mul_right_inv]

theorem mul_inv {G : CommGroup} (a b : G) : (a * b)⁻¹ = a⁻¹ * b⁻¹ := by
  rw [mul_inv_rev, G.mul_comm]
