\import Algebra.Monoid
\import Algebra.Pointed.PointedHom
\import Logic
\import Paths
\import Paths.Meta
\import Set.Fin

\record MonoidHom \extends PointedHom {
  \override Dom : Monoid
  \override Cod : Monoid
  | func-* {x y : Dom} : func (x * y) = func x * func y

  \lemma func-pow {x : Dom} {n : Nat} : func (Monoid.pow x n) = Monoid.pow (func x) n \elim n
    | 0 => func-ide
    | suc n => func-* *> pmap (`* _) func-pow

  \lemma func-BigProd {l : Array Dom} : func (Monoid.BigProd l) = Monoid.BigProd (\lam j => func (l j)) \elim l
    | nil => func-ide
    | a :: l => func-* *> pmap (_ *) func-BigProd

  \lemma func-LDiv {a b : Dom} (d : Monoid.LDiv a b) : Monoid.LDiv (func a) (func b) (func d.inv) \cowith
    | inv-right => inv func-* *> pmap func d.inv-right

  \lemma func-Inv {a : Dom} (d : Monoid.Inv a) : Monoid.Inv (func a) (func d.inv) \cowith
    | inv-left => inv func-* *> pmap func d.inv-left *> func-ide
    | inv-right => inv func-* *> pmap func d.inv-right *> func-ide
} \where {
  \func equals {M N : Monoid} {f g : MonoidHom M N} (p : \Pi (x : M) -> f x = g x) : f = g
    => exts p

  \func id {M : Monoid} : MonoidHom M M \cowith
    | func x => x
    | func-ide => idp
    | func-* => idp

  \open Monoid(Inv)

  \func presInv (h : MonoidHom) (e : Inv {h.Dom}) : Inv (h e) \cowith
    | inv => h e.inv
    | inv-left => inv h.func-* *> pmap h e.inv-left *> h.func-ide
    | inv-right => inv h.func-* *> pmap h e.inv-right *> h.func-ide

  \lemma presInvElem (h : MonoidHom) (e : Inv {h.Dom}) (e' : Inv (h e)) : h e.inv = e'.inv
    => pmap (\lam (j : Inv) => j.inv) (Inv.levelProp (\new Inv (h e) {
      | inv => h e.inv
      | inv-left => inv h.func-* *> pmap h e.inv-left *> h.func-ide
      | inv-right => inv h.func-* *> pmap h e.inv-right *> h.func-ide
    }) e')
}

\record AddMonoidHom \extends AddPointedHom {
  \override Dom : AddMonoid
  \override Cod : AddMonoid
  | func-+ {x y : Dom} : func (x + y) = func x + func y

  \lemma func-BigSum {l : Array Dom} : func (AddMonoid.BigSum l) = AddMonoid.BigSum (\lam j => func (l j)) \elim l
    | nil => func-zro
    | a :: l => func-+ *> pmap (_ +) func-BigSum

  \lemma func-*n {n : Nat} {x : Dom} : func (n AddMonoid.*n x) = n AddMonoid.*n func x \elim n
    | 0 => func-zro
    | suc n => func-+ *> pmap (`+ _) func-*n
} \where {
  \use \coerce toMonoidHom (f : AddMonoidHom) : MonoidHom \cowith
    | Dom => f.Dom
    | Cod => f.Cod
    | func => f
    | func-* => func-+
    | func-ide => func-zro

  \func id {M : AddMonoid} : AddMonoidHom M M \cowith
    | func x => x
    | func-zro => idp
    | func-+ => idp

  \lemma func-FinSum {A B : AbMonoid} (f : AddMonoidHom A B) {J : FinSet} {a : J -> A} : f (A.FinSum a) = B.FinSum (\lam j => f (a j))
    => \case A.FinSum_char a \with {
         | inP (e,q) => pmap f q *> f.func-BigSum *> inv (B.FinSum_char2 e)
       }
}