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

open import Fragment.Algebra.Signature

module Fragment.Algebra.Free.Monad (Σ : Signature) where

open import Fragment.Algebra.Free.Base Σ
open import Fragment.Algebra.Algebra Σ
open import Fragment.Algebra.Homomorphism Σ using (_⟿_; Congruent; Homomorphic)
open import Fragment.Setoid.Morphism

open import Level using (Level)

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

open import Relation.Binary using (Setoid)

private
  variable
    a b ℓ₁ ℓ₂ : Level

unit :  {S : Setoid a ℓ₁}  S  Herbrand S
unit = record { ∣_∣      = atom
              ; ∣_∣-cong = atom
              }

module _
  {A : Setoid a ℓ₁}
  {B : Setoid b ℓ₂}
  (f : A  Herbrand B)
  where

  private

    open Setoid  Free B ∥/≈

    module _ {A : Set a} {B : Set b} where

      mutual
        map-∣bind∣ :  {n}  (A  Term B)
                  Vec (Term A) n  Vec (Term B) n
        map-∣bind∣ f []       = []
        map-∣bind∣ f (x  xs) = ∣bind∣ f x  map-∣bind∣ f xs

        ∣bind∣ : (A  Term B)  Term A  Term B
        ∣bind∣ f (atom x)     = f x
        ∣bind∣ f (term op xs) = term op (map-∣bind∣ f xs)

    mutual
      map-∣bind∣-cong :  {n} {xs ys : Vec  Free A  n}
                         Pointwise (_~_ A) xs ys
                         Pointwise (_~_ B) (map-∣bind∣  f  xs)
                                            (map-∣bind∣  f  ys)
      map-∣bind∣-cong []       = []
      map-∣bind∣-cong (p  ps) = ∣bind∣-cong p  map-∣bind∣-cong ps

      ∣bind∣-cong : Congruent (_~_ A) (_~_ B) (∣bind∣  f )
      ∣bind∣-cong (atom p)                 =  f ∣-cong p
      ∣bind∣-cong {x = term op _} (term p) = term (map-∣bind∣-cong p)

    map-∣bind∣≡map :  {n} {xs : Vec  Free A  n}
                      Pointwise (_~_ B) (map-∣bind∣  f  xs)
                                         (map (∣bind∣  f ) xs)
    map-∣bind∣≡map {xs = []}     = []
    map-∣bind∣≡map {xs = x  xs} = refl  map-∣bind∣≡map

    ∣bind∣-hom : Homomorphic (Free A) (Free B) (∣bind∣  f )
    ∣bind∣-hom op []       = refl
    ∣bind∣-hom op (x  xs) = sym (term (map-∣bind∣≡map {xs = x  xs}))

    ∣bind∣⃗ : Herbrand A  Herbrand B
    ∣bind∣⃗ = record { ∣_∣      = ∣bind∣  f 
                     ; ∣_∣-cong = ∣bind∣-cong
                     }

  bind : Free A  Free B
  bind = record { ∣_∣⃗    = ∣bind∣⃗
                ; ∣_∣-hom = ∣bind∣-hom
                }

fmap :  {A : Setoid a ℓ₁} {B : Setoid b ℓ₂}
        A  B
        Free A  Free B
fmap f = bind (unit · f)

join :  {A : Setoid a ℓ₁}  Free (Herbrand A)  Free A
join = bind id