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

module Fragment.Extensions.Semigroup where

open import Fragment.Equational.Theory.Bundles

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

open import Fragment.Equational.FreeExtension Θ-semigroup
open import Fragment.Equational.Model Θ-semigroup

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

open import Level using (Level; _⊔_)

open import Data.Nat using ()
open import Data.Fin using (Fin; #_)
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  [])

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

    mutual

      data STree : Set a where
        leaf :  A   STree
        cons :  A   DTree  STree

      data DTree : Set a where
        leaf : Fin n  DTree
        cons : Fin n  Tree  DTree

      data Tree : Set a where
        sta : STree  Tree
        dyn : DTree  Tree

    mutual

      infix 6 _≋_ _≋⟨s⟩_ _≋⟨d⟩_

      data _≋⟨s⟩_ : STree  STree  Set (a  ) where
        leaf :  {x y}  x  y  leaf x ≋⟨s⟩ leaf y
        cons :  {x y xs ys}  x  y  xs ≋⟨d⟩ ys
                cons x xs ≋⟨s⟩ cons y ys

      data _≋⟨d⟩_ : DTree  DTree  Set (a  ) where
        leaf :  {x y}  x  y  leaf x ≋⟨d⟩ leaf y
        cons :  {x y xs ys}  x  y  xs  ys
                cons x xs ≋⟨d⟩ cons y ys

      data _≋_ : Tree  Tree  Set (a  ) where
        sta :  {x y}  x ≋⟨s⟩ y  sta x  sta y
        dyn :  {x y}  x ≋⟨d⟩ y  dyn x  dyn y

    mutual

      ≋⟨s⟩-refl :  {x}  x ≋⟨s⟩ x
      ≋⟨s⟩-refl {leaf x}    = leaf A.refl
      ≋⟨s⟩-refl {cons x xs} = cons A.refl ≋⟨d⟩-refl

      ≋⟨d⟩-refl :  {x}  x ≋⟨d⟩ x
      ≋⟨d⟩-refl {leaf x}    = leaf PE.refl
      ≋⟨d⟩-refl {cons x xs} = cons PE.refl ≋-refl

      ≋-refl :  {x}  x  x
      ≋-refl {sta x} = sta ≋⟨s⟩-refl
      ≋-refl {dyn x} = dyn ≋⟨d⟩-refl

    mutual

      ≋⟨s⟩-sym :  {x y}  x ≋⟨s⟩ y  y ≋⟨s⟩ x
      ≋⟨s⟩-sym (leaf p)    = leaf (A.sym p)
      ≋⟨s⟩-sym (cons p ps) = cons (A.sym p) (≋⟨d⟩-sym ps)

      ≋⟨d⟩-sym :  {x y}  x ≋⟨d⟩ y  y ≋⟨d⟩ x
      ≋⟨d⟩-sym (leaf p)    = leaf (PE.sym p)
      ≋⟨d⟩-sym (cons p ps) = cons (PE.sym p) (≋-sym ps)

      ≋-sym :  {x y}  x  y  y  x
      ≋-sym (sta p) = sta (≋⟨s⟩-sym p)
      ≋-sym (dyn p) = dyn (≋⟨d⟩-sym p)

    mutual

      ≋⟨s⟩-trans :  {x y z}  x ≋⟨s⟩ y  y ≋⟨s⟩ z  x ≋⟨s⟩ z
      ≋⟨s⟩-trans (leaf p)    (leaf q)    = leaf (A.trans p q)
      ≋⟨s⟩-trans (cons p ps) (cons q qs) = cons (A.trans p q) (≋⟨d⟩-trans ps qs)

      ≋⟨d⟩-trans :  {x y z}  x ≋⟨d⟩ y  y ≋⟨d⟩ z  x ≋⟨d⟩ z
      ≋⟨d⟩-trans (leaf p)    (leaf q)    = leaf (PE.trans p q)
      ≋⟨d⟩-trans (cons p ps) (cons q qs) = cons (PE.trans p q) (≋-trans ps qs)

      ≋-trans :  {x y z}  x  y  y  z  x  z
      ≋-trans (sta p) (sta q) = sta (≋⟨s⟩-trans p q)
      ≋-trans (dyn p) (dyn q) = dyn (≋⟨d⟩-trans p q)

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

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

  mutual

    _++⟨d⟩_ : DTree  Tree  DTree
    (leaf x)    ++⟨d⟩ y = cons x y
    (cons x xs) ++⟨d⟩ y = cons x (xs ++ y)

    _++_ : Tree  Tree  Tree
    sta (leaf x)    ++ sta (leaf y)    = sta (leaf (x · y))
    sta (leaf x)    ++ sta (cons y ys) = sta (cons (x · y) ys)
    sta (leaf x)    ++ dyn y           = sta (cons x y)
    sta (cons x xs) ++ y               = sta (cons x (xs ++⟨d⟩ y))
    dyn x           ++ y               = dyn (x ++⟨d⟩ y)

  mutual

    ++⟨d⟩-assoc :  x y z  (x ++⟨d⟩ y) ++⟨d⟩ z ≋⟨d⟩ x ++⟨d⟩ (y ++ z)
    ++⟨d⟩-assoc (leaf x)    y z = ≋⟨d⟩-refl
    ++⟨d⟩-assoc (cons x xs) y z = cons PE.refl (++-assoc xs y z)

    ++-assoc :  x y z  (x ++ y) ++ z  x ++ (y ++ z)
    ++-assoc (sta (leaf x))    (sta (leaf y))    (sta (leaf z))    = sta (leaf (·-assoc x y z))
    ++-assoc (sta (leaf x))    (sta (leaf y))    (sta (cons z zs)) = sta (cons (·-assoc x y z) ≋⟨d⟩-refl)
    ++-assoc (sta (leaf x))    (sta (leaf y))    (dyn z)           = ≋-refl
    ++-assoc (sta (leaf x))    (sta (cons y ys)) z                 = ≋-refl
    ++-assoc (sta (leaf x))    (dyn y)           z                 = ≋-refl
    ++-assoc (sta (cons x xs)) y                 z                 = sta (cons A.refl (++⟨d⟩-assoc xs y z))
    ++-assoc (dyn x)           y                 z                 = dyn (++⟨d⟩-assoc x y z)

  mutual

    ++⟨d⟩-cong :  {x y z w}  x ≋⟨d⟩ y  z  w  x ++⟨d⟩ z ≋⟨d⟩ y ++⟨d⟩ w
    ++⟨d⟩-cong (leaf p)    q = cons p q
    ++⟨d⟩-cong (cons p ps) q = cons p (++-cong ps q)

    ++-cong :  {x y z w}  x  y  z  w  x ++ z  y ++ w
    ++-cong (sta (leaf p))    (sta (leaf q))    = sta (leaf (·-cong p q))
    ++-cong (sta (leaf p))    (sta (cons q qs)) = sta (cons (·-cong p q) qs)
    ++-cong (sta (leaf p))    (dyn q)           = sta (cons p q)
    ++-cong (sta (cons p ps)) q                 = sta (cons p (++⟨d⟩-cong ps q))
    ++-cong (dyn p)           q                 = dyn (++⟨d⟩-cong p q)

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

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

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

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

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

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

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

  ∣inl∣ :  A    Frex 
  ∣inl∣ x = sta (leaf x)

  ∣inl∣-cong : Congruent _≈_ _≋_ ∣inl∣
  ∣inl∣-cong p = sta (leaf p)

  ∣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 = interp Frex  k  dyn (leaf k))

  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  [])

      ⊕-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 (leaf x))    =  f  x
      ∣resid∣ (sta (cons x xs)) =  f  x  ∣resid∣ (dyn xs)
      ∣resid∣ (dyn (leaf x))    =  g  (atom (dyn x))
      ∣resid∣ (dyn (cons x xs)) =  g  (atom (dyn x))  ∣resid∣ xs

      ∣resid∣-cong : Congruent _≋_ _~_ ∣resid∣
      ∣resid∣-cong (sta (leaf p))    =  f ∣-cong p
      ∣resid∣-cong (sta (cons p ps)) = ⊕-cong ( f ∣-cong p) (∣resid∣-cong (dyn ps))
      ∣resid∣-cong (dyn (leaf p))    =  g ∣-cong (inherit (atom (dyn p)))
      ∣resid∣-cong (dyn (cons p ps)) =
        ⊕-cong ( g ∣-cong (inherit (atom (dyn p)))) (∣resid∣-cong ps)

      open Reasoning  X ∥/≈

      ∣resid∣-hom : Homomorphic  Frex ∥ₐ  X ∥ₐ ∣resid∣
      ∣resid∣-hom  (sta (leaf x)  sta (leaf y)  [])    =  f ∣-hom  (x  y  [])
      ∣resid∣-hom  (sta (leaf x)  sta (cons y ys)  []) = begin
           f  x  ( f  y  ∣resid∣ (dyn ys))
        ≈⟨ X.sym (⊕-assoc ( f  x) ( f  y) _) 
          ( f  x   f  y)  ∣resid∣ (dyn ys)
        ≈⟨ ⊕-cong ( f ∣-hom  (x  y  [])) X.refl 
           f  (x · y)  ∣resid∣ (dyn ys)
        
      ∣resid∣-hom  (sta (leaf x)  dyn y  [])           = X.refl
      ∣resid∣-hom  (sta (cons x xs)  y  [])            = begin
          ( f  x  ∣resid∣ (dyn xs))  ∣resid∣ y
        ≈⟨ ⊕-assoc ( f  x) _ (∣resid∣ y) 
           f  x  (∣resid∣ (dyn xs)  ∣resid∣ y)
        ≈⟨ ⊕-cong X.refl (∣resid∣-hom  (dyn xs  y  [])) 
           f  x  ∣resid∣ (dyn xs ++ y)
        
      ∣resid∣-hom  (dyn (leaf x)  y  [])               = X.refl
      ∣resid∣-hom  (dyn (cons x xs)  y  [])            = begin
          ( g  (atom (dyn x))  ∣resid∣ xs)  ∣resid∣ y
        ≈⟨ ⊕-assoc _ (∣resid∣ xs) (∣resid∣ y) 
           g  (atom (dyn x))  (∣resid∣ xs  ∣resid∣ y)
        ≈⟨ ⊕-cong X.refl (∣resid∣-hom  (xs  y  [])) 
           g  (atom (dyn x))  ∣resid∣ (xs ++ 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  [])

      ⊕-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

      open Reasoning  X ∥/≈

      commute₂ : X [ f , g ]  inr  g
      commute₂ {atom (dyn k)} =
         X [ f , g ] ∣-cong (≋-refl {x = dyn (leaf k)})
      commute₂ {t@(term  (x  y  []))} = begin
           X [ f , g ]  ( inr  t)
        ≈⟨  X [ f , g ] ∣-cong ( inr ∣-hom  (x  y  [])) 
           X [ f , g ]  ( inr  x ++  inr  y)
        ≈⟨ X.sym ( X [ f , g ] ∣-hom  ( inr  x   inr  y  [])) 
           X [ f , g ]  ( inr  x)   X [ f , g ]  ( inr  y)
        ≈⟨ ⊕-cong commute₂ commute₂ 
           g  x   g  y
        ≈⟨  g ∣-hom  (x  y  []) 
           g  t
        

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

        universal : X [ f , g ]  h
        universal {sta (leaf x)}    = X.sym c₁
        universal {dyn (leaf x)}    = X.sym c₂
        universal {sta (cons x xs)} = begin
             f  x   X [ f , g ]  (dyn xs)
          ≈⟨ ⊕-cong (X.sym c₁) universal 
             h  (sta (leaf x))   h  (dyn xs)
          ≈⟨  h ∣-hom  (sta (leaf x)  dyn xs  []) 
             h  (sta (leaf x) ++ dyn xs)
          
        universal {dyn (cons x xs)} = begin
             g  (atom (dyn x))   X [ f , g ]  xs
          ≈⟨ ⊕-cong (X.sym c₂) universal 
             h  (dyn (leaf x))   h  xs
          ≈⟨  h ∣-hom  (dyn (leaf x)  xs  []) 
             h  (dyn (leaf x) ++ xs)
          

SemigroupFrex : FreeExtension
SemigroupFrex = 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}
                 }