{-# OPTIONS --without-K --exact-split --safe #-}

module Fragment.Extensions.CSemigroup.Base where

open import Fragment.Equational.Theory.Bundles

open import Fragment.Algebra.Signature
open import Fragment.Algebra.Free Σ-magma hiding (_~_)
open import Fragment.Algebra.Homomorphism Σ-magma
open import Fragment.Algebra.Algebra Σ-magma
  using (Algebra; IsAlgebra; Interpretation; Congruence)

open import Fragment.Equational.FreeExtension Θ-csemigroup
  using (FreeExtension; IsFreeExtension)
open import Fragment.Equational.Model Θ-csemigroup
open import Fragment.Extensions.CSemigroup.Monomial

open import Fragment.Setoid.Morphism using (_↝_)

open import Level using (Level; _⊔_)

open import Data.Nat using ()
open import Data.Fin using (#_)

open import Data.Vec using (Vec; []; _∷_; map)
open import Data.Vec.Relation.Binary.Pointwise.Inductive using ([]; _∷_)

import Relation.Binary.Reasoning.Setoid as Reasoning
open import Relation.Binary using (Setoid; IsEquivalence)
open import Relation.Binary.PropositionalEquality as PE using (_≡_)

private
  variable
    a  : Level

module _ (A : Model {a} {}) (n : ) where

  private

    open module A = Setoid  A ∥/≈

    _·_ :  A    A    A 
    x · y = A    (x  y  [])

    ·-cong :  {x y z w}  x  y  z  w  x · z  y · w
    ·-cong x≈y z≈w = (A   ⟧-cong) (x≈y  z≈w  [])

    ·-comm :  (x y :  A )  x · y  y · x
    ·-comm x y =  A ∥ₐ-models comm (env {A =  A ∥ₐ} (x  y  []))

    ·-assoc :  (x y z :  A )  (x · y) · z  x · (y · z)
    ·-assoc x y z =  A ∥ₐ-models assoc (env {A =  A ∥ₐ} (x  y  z  []))

    data Blob : Set a where
      sta  :  A   Blob
      dyn  :  J' n   Blob
      blob :  J' n    A   Blob

    infix 6 _≋_

    data _≋_ : Blob  Blob  Set (a  ) where
      sta  :  {x y}  x  y  sta x  sta y
      dyn  :  {xs ys}  xs  ys  dyn xs  dyn ys
      blob :  {x y xs ys}  xs  ys  x  y
              blob xs x  blob ys y

    ≋-refl :  {x}  x  x
    ≋-refl {x = sta x}     = sta A.refl
    ≋-refl {x = dyn xs}    = dyn PE.refl
    ≋-refl {x = blob xs x} = blob PE.refl A.refl

    ≋-sym :  {x y}  x  y  y  x
    ≋-sym (sta p)     = sta (A.sym p)
    ≋-sym (dyn ps)    = dyn (PE.sym ps)
    ≋-sym (blob ps p) = blob (PE.sym ps) (A.sym p)

    ≋-trans :  {x y z}  x  y  y  z  x  z
    ≋-trans (sta p)     (sta q)     = sta (A.trans p q)
    ≋-trans (dyn ps)    (dyn qs)    = dyn (PE.trans ps qs)
    ≋-trans (blob ps p) (blob qs q) = blob (PE.trans ps qs) (A.trans p q)

  ≋-isEquivalence : IsEquivalence _≋_
  ≋-isEquivalence = record { refl  = ≋-refl
                           ; sym   = ≋-sym
                           ; trans = ≋-trans
                           }

  Blob/≋ : Setoid _ _
  Blob/≋ = record { Carrier       = Blob
                  ; _≈_           = _≋_
                  ; isEquivalence = ≋-isEquivalence
                  }

  _++_ : Blob  Blob  Blob
  sta x     ++ sta y     = sta (x · y)
  dyn xs    ++ dyn ys    = dyn (xs  ys)
  sta x     ++ dyn ys    = blob ys        x
  sta x     ++ blob ys y = blob ys        (x · y)
  dyn xs    ++ sta y     = blob xs        y
  dyn xs    ++ blob ys y = blob (xs  ys) y
  blob xs x ++ sta y     = blob xs        (x · y)
  blob xs x ++ dyn ys    = blob (xs  ys) x
  blob xs x ++ blob ys y = blob (xs  ys) (x · y)

  ++-cong :  {x y z w}  x  y  z  w  x ++ z  y ++ w
  ++-cong (sta p)     (sta q)     = sta (·-cong p q)
  ++-cong (dyn ps)    (dyn qs)    = dyn (⊗-cong ps qs)
  ++-cong (sta p)     (dyn qs)    = blob qs p
  ++-cong (sta p)     (blob qs q) = blob qs (·-cong p q)
  ++-cong (dyn ps)    (sta q)     = blob ps q
  ++-cong (dyn ps)    (blob qs q) = blob (⊗-cong ps qs) q
  ++-cong (blob ps p) (sta q)     = blob ps (·-cong p q)
  ++-cong (blob ps p) (dyn qs)    = blob (⊗-cong ps qs) p
  ++-cong (blob ps p) (blob qs q) = blob (⊗-cong ps qs) (·-cong p q)

  ++-comm :  x y  x ++ y  y ++ x
  ++-comm (sta x)     (sta y)     = sta (·-comm x y)
  ++-comm (dyn xs)    (dyn ys)    = dyn (⊗-comm xs ys)
  ++-comm (sta x)     (dyn ys)    = ≋-refl
  ++-comm (sta x)     (blob ys y) = blob PE.refl (·-comm x y)
  ++-comm (dyn xs)    (sta y)     = ≋-refl
  ++-comm (dyn xs)    (blob ys y) = blob (⊗-comm xs ys) A.refl
  ++-comm (blob xs x) (sta y)     = blob PE.refl (·-comm x y)
  ++-comm (blob xs x) (dyn ys)    = blob (⊗-comm xs ys) A.refl
  ++-comm (blob xs x) (blob ys y) = blob (⊗-comm xs ys) (·-comm x y)

  ++-assoc :  x y z  (x ++ y) ++ z  x ++ (y ++ z)
  ++-assoc (sta x)     (sta y)     (sta z)     = sta (·-assoc x y z)
  ++-assoc (dyn xs)    (dyn ys)    (dyn zs)    = dyn (⊗-assoc xs ys zs)
  ++-assoc (sta x)     (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (sta y)     (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (sta x)     (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (sta x)     (dyn ys)    (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (dyn ys)    (blob zs z) = ≋-refl
  ++-assoc (sta x)     (blob ys y) (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (sta x)     (blob ys y) (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (blob ys y) (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (dyn xs)    (sta y)     (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (dyn xs)    (sta y)     (blob zs z) = ≋-refl
  ++-assoc (dyn xs)    (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (dyn ys)    (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (dyn xs)    (blob ys y) (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (blob ys y) (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (dyn xs)    (blob ys y) (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (sta y)     (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (blob xs x) (sta y)     (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (blob xs x) (dyn ys)    (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (dyn ys)    (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (blob ys y) (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (blob ys y) (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (blob ys y) (blob zs z) = blob (⊗-assoc xs ys zs) (·-assoc x y z)

  Blob⟦_⟧ : Interpretation Blob/≋
  Blob⟦   (x  y  []) = x ++ y

  Blob⟦_⟧-cong : Congruence Blob/≋ Blob⟦_⟧
  Blob⟦  ⟧-cong (p  q  []) = ++-cong p q

  Blob/≋-isAlgebra : IsAlgebra Blob/≋
  Blob/≋-isAlgebra = record { ⟦_⟧     = Blob⟦_⟧
                            ; ⟦⟧-cong = Blob⟦_⟧-cong
                            }

  Blob/≋-algebra : Algebra
  Blob/≋-algebra =
    record { ∥_∥/≈           = Blob/≋
           ; ∥_∥/≈-isAlgebra = Blob/≋-isAlgebra
           }

  Blob/≋-models : Models Blob/≋-algebra
  Blob/≋-models comm θ  = ++-comm (θ (# 0)) (θ (# 1))
  Blob/≋-models assoc θ = ++-assoc (θ (# 0)) (θ (# 1)) (θ (# 2))

  Blob/≋-isModel : IsModel Blob/≋
  Blob/≋-isModel =
    record { isAlgebra = Blob/≋-isAlgebra
           ; models    = Blob/≋-models
           }

  Frex : Model
  Frex = record { ∥_∥/≈   = Blob/≋
                ; isModel = Blob/≋-isModel
                }

  ∣inl∣ :  A    Frex 
  ∣inl∣ = sta

  ∣inl∣-cong : Congruent _≈_ _≋_ ∣inl∣
  ∣inl∣-cong = sta

  ∣inl∣⃗ :  A ∥/≈   Frex ∥/≈
  ∣inl∣⃗ = record { ∣_∣      = ∣inl∣
                  ; ∣_∣-cong = ∣inl∣-cong
                  }

  ∣inl∣-hom : Homomorphic  A ∥ₐ  Frex ∥ₐ ∣inl∣
  ∣inl∣-hom  (x  y  []) = ≋-refl

  inl :  A ∥ₐ   Frex ∥ₐ
  inl = record { ∣_∣⃗    = ∣inl∣⃗
               ; ∣_∣-hom = ∣inl∣-hom
               }

  ∣inr∣ :  J n    Frex 
  ∣inr∣ x = dyn ( norm  x)

  ∣inr∣-cong : Congruent ≈[ J n ] _≋_ ∣inr∣
  ∣inr∣-cong p = dyn ( norm ∣-cong p)

  ∣inr∣⃗ :  J n ∥/≈   Frex ∥/≈
  ∣inr∣⃗ = record { ∣_∣      = ∣inr∣
                  ; ∣_∣-cong = ∣inr∣-cong
                  }

  ∣inr∣-hom : Homomorphic  J n ∥ₐ  Frex ∥ₐ ∣inr∣
  ∣inr∣-hom  (x  y  []) = dyn ( norm ∣-hom  (x  y  []))


  inr :  J n ∥ₐ   Frex ∥ₐ
  inr = record { ∣_∣⃗    = ∣inr∣⃗
               ; ∣_∣-hom = ∣inr∣-hom
               }

  module _ {b } (X : Model {b} {}) where

    private

      open module X = Setoid  X ∥/≈ renaming (_≈_ to _~_)

      _⊕_ :  X    X    X 
      x  y = X    (x  y  [])

      ⊕-cong :  {x y z w}  x ~ y  z ~ w  x  z ~ y  w
      ⊕-cong p q = (X   ⟧-cong) (p  q  [])

      ⊕-comm :  (x y :  X )  x  y ~ y  x
      ⊕-comm x y =  X ∥ₐ-models comm (env {A =  X ∥ₐ} (x  y  []))

      ⊕-assoc :  (x y z :  X )  (x  y)  z ~ x  (y  z)
      ⊕-assoc x y z =  X ∥ₐ-models assoc (env {A =  X ∥ₐ} (x  y  z  []))

    module _
      (f :  A ∥ₐ   X ∥ₐ)
      (g :  J n ∥ₐ   X ∥ₐ)
      where

      ∣resid∣ :  Frex    X 
      ∣resid∣ (sta x)     =  f  x
      ∣resid∣ (dyn xs)    =  g  ( syn  xs)
      ∣resid∣ (blob xs x) =  g  ( syn  xs)   f  x

      ∣resid∣-cong : Congruent _≋_ _~_ ∣resid∣
      ∣resid∣-cong (sta p)     =  f ∣-cong p
      ∣resid∣-cong (dyn ps)    =  g ∣-cong ( syn ∣-cong ps)
      ∣resid∣-cong (blob ps p) =
        ⊕-cong ( g ∣-cong ( syn ∣-cong ps)) ( f ∣-cong p)

      open Reasoning  X ∥/≈

      ∣resid∣-hom : Homomorphic  Frex ∥ₐ  X ∥ₐ ∣resid∣
      ∣resid∣-hom  (sta x      sta y      []) =  f ∣-hom  (x  y  [])
      ∣resid∣-hom  (sta x      dyn ys     []) = ⊕-comm ( f  x) _
      ∣resid∣-hom  (sta x      blob ys y  []) = begin
           f  x  ( g  ( syn  ys)   f  y)
        ≈⟨ ⊕-cong X.refl (⊕-comm _ ( f  y)) 
           f  x  ( f  y   g  ( syn  ys))
        ≈⟨ X.sym (⊕-assoc ( f  x) ( f  y) _) 
          ( f  x   f  y)   g  ( syn  ys)
        ≈⟨ ⊕-cong ( f ∣-hom  (x  y  [])) X.refl 
           f  (x · y)   g  ( syn  ys)
        ≈⟨ ⊕-comm _ _ 
           g  ( syn  ys)   f  (x · y)
        
      ∣resid∣-hom  (dyn xs     sta y      []) = X.refl
      ∣resid∣-hom  (dyn xs     dyn ys     []) =  g  syn ∣-hom  (xs  ys  [])
      ∣resid∣-hom  (dyn xs     blob ys y  []) = begin
           g  ( syn  xs)  ( g  ( syn  ys)   f  y)
        ≈⟨ X.sym (⊕-assoc _ _ ( f  y)) 
          ( g  ( syn  xs)   g  ( syn  ys))   f  y
        ≈⟨ ⊕-cong ( g  syn ∣-hom  (xs  ys  [])) X.refl 
           g  ( syn  (xs  ys))   f  y
        
      ∣resid∣-hom  (blob xs x  sta y      []) = begin
          ( g  ( syn  xs)   f  x)   f  y
        ≈⟨ ⊕-assoc _ ( f  x) ( f  y) 
           g  ( syn  xs)  ( f  x   f  y)
        ≈⟨ ⊕-cong X.refl ( f ∣-hom  (x  y  [])) 
           g  ( syn  xs)   f  (x · y)
        
      ∣resid∣-hom  (blob xs x  dyn ys     []) = begin
          ( g  ( syn  xs)   f  x)   g  ( syn  ys)
        ≈⟨ ⊕-assoc _ ( f  x) _ 
           g  ( syn  xs)  ( f  x   g  ( syn  ys))
        ≈⟨ ⊕-cong X.refl (⊕-comm ( f  x) _) 
           g  ( syn  xs)  ( g  ( syn  ys)   f  x)
        ≈⟨ X.sym (⊕-assoc _ _ ( f  x)) 
          ( g  ( syn  xs)   g  ( syn  ys))   f  x
        ≈⟨ ⊕-cong ( g  syn ∣-hom  (xs  ys  [])) X.refl 
           g  ( syn  (xs  ys))   f  x
        
      ∣resid∣-hom  (blob xs x  blob ys y  []) = begin
          ( g  ( syn  xs)   f  x)  ( g  ( syn  ys)   f  y)
        ≈⟨ ⊕-assoc _ ( f  x) _ 
           g  ( syn  xs)  ( f  x  ( g  ( syn  ys)   f  y))
        ≈⟨ ⊕-cong X.refl (X.sym (⊕-assoc ( f  x) _ _)) 
           g  ( syn  xs)  (( f  x   g  ( syn  ys))   f  y)
        ≈⟨ ⊕-cong X.refl (⊕-cong (⊕-comm ( f  x) _) X.refl) 
           g  ( syn  xs)  (( g  ( syn  ys)   f  x)   f  y)
        ≈⟨ ⊕-cong X.refl (⊕-assoc _ ( f  x) _) 
           g  ( syn  xs)  ( g  ( syn  ys)  ( f  x   f  y))
        ≈⟨ X.sym (⊕-assoc _ _ _) 
          ( g  ( syn  xs)   g  ( syn  ys))  ( f  x   f  y)
        ≈⟨ ⊕-cong ( g  syn ∣-hom  (xs  ys  [])) ( f ∣-hom  (x  y  [])) 
           g  ( syn  (xs  ys))   f  (x · y)
        

      ∣resid∣⃗ :  Frex ∥/≈   X ∥/≈
      ∣resid∣⃗ = record { ∣_∣      = ∣resid∣
                        ; ∣_∣-cong = ∣resid∣-cong
                        }

      _[_,_] :  Frex ∥ₐ   X ∥ₐ
      _[_,_] = record { ∣_∣⃗    = ∣resid∣⃗
                      ; ∣_∣-hom = ∣resid∣-hom
                      }

  module _ {b } {X : Model {b} {}} where

    private

      open module X = Setoid  X ∥/≈ renaming (_≈_ to _~_)

      _⊕_ :  X    X    X 
      x  y = X    (x  y  [])

      ⊕-cong :  {x y z w}  x ~ y  z ~ w  x  z ~ y  w
      ⊕-cong p q = (X   ⟧-cong) (p  q  [])

      ⊕-comm :  (x y :  X )  x  y ~ y  x
      ⊕-comm x y =  X ∥ₐ-models comm (env {A =  X ∥ₐ} (x  y  []))

      ⊕-assoc :  (x y z :  X )  (x  y)  z ~ x  (y  z)
      ⊕-assoc x y z =  X ∥ₐ-models assoc (env {A =  X ∥ₐ} (x  y  z  []))

    module _
      {f :  A ∥ₐ   X ∥ₐ}
      {g :  J n ∥ₐ   X ∥ₐ}
      where

      commute₁ : X [ f , g ]  inl  f
      commute₁ = X.refl

      commute₂ : X [ f , g ]  inr  g
      commute₂ {x} =  g ∣-cong (syn⊙norm≗id {x = x})

      module _ {h :  Frex ∥ₐ   X ∥ₐ}
        (c₁ : h  inl  f)
        (c₂ : h  inr  g)
        where

        open Reasoning  X ∥/≈

        universal : X [ f , g ]  h
        universal {sta x}     = X.sym c₁
        universal {dyn xs}    = begin
             g  ( syn  xs)
          ≈⟨ X.sym c₂ 
             h  (dyn ( norm  ( syn  xs)))
          ≈⟨  h ∣-cong (dyn norm⊙syn≗id) 
             h  (dyn xs)
          
        universal {blob xs x} = begin
             g  ( syn  xs)   f  x
          ≈⟨ ⊕-cong universal universal 
             h  (dyn xs)   h  (sta x)
          ≈⟨  h ∣-hom  (dyn xs  sta x  []) 
             h  (blob xs x)
          

CSemigroupFrex : FreeExtension
CSemigroupFrex = record { _[_]        = Frex
                        ; _[_]-isFrex = isFrex
                        }
  where isFrex : IsFreeExtension Frex
        isFrex A n =
          record { inl       = inl A n
                 ; inr       = inr A n
                 ; _[_,_]    = _[_,_] A n
                 ; commute₁  = λ {_ _ X f g}  commute₁ A n {X = X} {f} {g}
                 ; commute₂  = λ {_ _ X f g}  commute₂ A n {X = X} {f} {g}
                 ; universal = λ {_ _ X f g h}  universal A n {X = X} {f} {g} {h}
                 }