module Cubical.Structures.Free where

open import Cubical.Structures.Prelude

open import Cubical.Data.List as L
open import Cubical.Data.Nat
open import Cubical.Data.Sigma

open import Cubical.Functions.Image
open import Cubical.Foundations.Univalence

open import Cubical.Categories.Monad.Base
open import Cubical.Categories.Functor renaming (𝟙⟨_⟩ to funcId)
open import Cubical.Categories.NaturalTransformation
open import Cubical.Categories.Instances.Sets

open import Cubical.HITs.PropositionalTruncation as P
open import Cubical.HITs.SetQuotients as Q

open import Cubical.Reflection.RecordEquiv

open import Cubical.Structures.Sig
open import Cubical.Structures.Str
open import Cubical.Structures.Tree
open import Cubical.Structures.Eq

-- defines a free structure on a signature and equations
module Definition {f a e n s : Level} (σ : Sig f a) (τ : EqSig e (ℓ-max n s)) (ε : sysEq {n = ℓ-max n s} σ τ) where
  ns : Level
  ns = ℓ-max n s

  record Free ( ℓ' : Level) (h : HLevel) : Type (ℓ-suc (ℓ-max ℓ' (ℓ-max  (ℓ-max f (ℓ-max a (ℓ-max e ns)))))) where
    field
      F : (X : Type ) -> Type (ℓ-max  ns)
      η : {X : Type } -> X -> F X
      α : {X : Type } -> sig σ (F X) -> F X
      sat : {X : Type } -> < F X , α >  ε
      trunc : {X : Type } -> isOfHLevel h X -> isOfHLevel h (F X)
      isFree : {X : Type }
        {𝔜 : struct (ℓ-max ℓ' ns) σ}
        (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε)
        -> isEquiv (\(f : structHom {x = ℓ-max  ns} < F X , α > 𝔜) -> f .fst  η)

    σStruct : Type  -> _
    σStruct X = < F X , α >

    ext : {X : Type } {𝔜 : struct (ℓ-max ℓ' ns) σ}
          (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε)
       -> (hom : X -> 𝔜 .car) -> structHom < F X , α > 𝔜
    ext H ϕ = invIsEq (isFree H ϕ)

    ext-β : {X : Type } {𝔜 : struct (ℓ-max ℓ' ns) σ}
            (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε) (Hom : structHom < F X , α > 𝔜)
         -> ext H ϕ (Hom .fst  η)  Hom
    ext-β H ϕ = retIsEq (isFree H ϕ)

    ext-η : {X : Type } {𝔜 : struct (ℓ-max ℓ' ns) σ}
            (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε) (h : X -> 𝔜 .car)
         -> (ext H ϕ h .fst)  η  h
    ext-η H ϕ = secIsEq (isFree H ϕ)

    hom≡ : {X : Type } {𝔜 : struct (ℓ-max ℓ' ns) σ}
        -> (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε)
        -> (H1 H2 : structHom < F X , α > 𝔜)
        -> H1 .fst  η  H2 .fst  η
        -> H1  H2
    hom≡ H ϕ H1 H2 α = sym (ext-β H ϕ H1)  cong (ext H ϕ) α  ext-β H ϕ H2

  open Free
  module _ {} {A : Type } (𝔛 : Free   2) (𝔜 : Free   2) (isSetA : isSet A) where
    private
      str𝔛 : struct (ℓ-max (ℓ-max n s) ) σ
      str𝔛 = < 𝔛 .F A , 𝔛 .α >

      str𝔜 : struct (ℓ-max (ℓ-max n s) ) σ
      str𝔜 = < 𝔜 .F A , 𝔜 .α >

      isSet𝔜 : isSet (𝔜 .F A)
      isSet𝔜 = 𝔜 .trunc isSetA

      isSet𝔛 : isSet (𝔛 .F A)
      isSet𝔛 = 𝔛 .trunc isSetA

      ϕ1 : structHom str𝔛 str𝔜
      ϕ1 = ext 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η)

      ϕ2 : structHom str𝔜 str𝔛
      ϕ2 = ext 𝔜 isSet𝔛 (𝔛 .sat) (𝔛 .η)

      ϕ1∘ϕ2 : structHom str𝔜 str𝔜
      ϕ1∘ϕ2 = structHom∘ str𝔜 str𝔛 str𝔜 ϕ1 ϕ2

      ϕ2∘ϕ1 : structHom str𝔛 str𝔛
      ϕ2∘ϕ1 = structHom∘ str𝔛 str𝔜 str𝔛 ϕ2 ϕ1

      ϕ1∘ϕ2≡ : ϕ1∘ϕ2 .fst  𝔜 .η  idHom str𝔜 .fst  𝔜 .η
      ϕ1∘ϕ2≡ =
          ϕ1 .fst  ((ext 𝔜 isSet𝔛 (𝔛 .sat) (𝔛 .η) .fst)  𝔜 .η)
        ≡⟨ congS (ϕ1 .fst ∘_) (ext-η 𝔜 isSet𝔛 (𝔛 .sat) (𝔛 .η)) 
          ext 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η) .fst  𝔛 .η
        ≡⟨ ext-η 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η) 
          𝔜 .η 

      ϕ2∘ϕ1≡ : ϕ2∘ϕ1 .fst  𝔛 .η  idHom str𝔛 .fst  𝔛 .η
      ϕ2∘ϕ1≡ =
          ϕ2 .fst  ((ext 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η) .fst)  𝔛 .η)
        ≡⟨ congS (ϕ2 .fst ∘_) (ext-η 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η)) 
          ext 𝔜 isSet𝔛 (𝔛 .sat) (𝔛 .η) .fst  𝔜 .η
        ≡⟨ ext-η 𝔜 isSet𝔛 (𝔛 .sat) (𝔛 .η) 
          𝔛 .η 

    freeIso : Iso (𝔛 .F A) (𝔜 .F A)
    freeIso = iso (ϕ1 .fst) (ϕ2 .fst)
       x -> congS  f -> f .fst x) (hom≡ 𝔜 isSet𝔜 (𝔜 .sat) ϕ1∘ϕ2 (idHom str𝔜) ϕ1∘ϕ2≡))
       x -> congS  f -> f .fst x) (hom≡ 𝔛 isSet𝔛 (𝔛 .sat) ϕ2∘ϕ1 (idHom str𝔛) ϕ2∘ϕ1≡))

    freeIsoFunHom : structIsHom str𝔛 str𝔜 (Iso.fun freeIso)
    freeIsoFunHom = ϕ1 .snd

    freeIsoInvHom : structIsHom str𝔜 str𝔛 (Iso.inv freeIso)
    freeIsoInvHom = ϕ2 .snd

    free≡ : 𝔛 .F A  𝔜 .F A
    free≡ = ua (isoToEquiv freeIso)

    η≡ :  x -> PathP  i -> free≡ i) (𝔛 .η x) (𝔜 .η x)
    η≡ x = toPathP $
      transport free≡ (𝔛 .η x) ≡⟨⟩
      transport  i -> 𝔜 .F A) (ϕ1 .fst (𝔛 .η x)) ≡⟨ sym (transport-filler refl (ϕ1 .fst (𝔛 .η x))) 
      ϕ1 .fst (𝔛 .η x) ≡⟨ cong  f -> f x) (ext-η 𝔛 isSet𝔜 (𝔜 .sat) (𝔜 .η)) 
      η 𝔜 x 

  -- Alternative definition where F is paramterized, used for transporting Free proofs
  record FreeAux ( ℓ' : Level) (h : HLevel) (F : (X : Type ) -> Type (ℓ-max  ns)) : Type (ℓ-suc (ℓ-max ℓ' (ℓ-max  (ℓ-max f (ℓ-max a (ℓ-max e ns)))))) where
    field
      η : {X : Type } -> X -> F X
      α : {X : Type } -> sig σ (F X) -> F X
      sat : {X : Type } -> < F X , α >  ε
      trunc : {X : Type } -> isOfHLevel h X -> isOfHLevel h (F X)
      isFree : {X : Type }
        {𝔜 : struct (ℓ-max ℓ' ns) σ}
        (H : isOfHLevel h (𝔜 .car)) (ϕ : 𝔜  ε)
        -> isEquiv (\(f : structHom {x = ℓ-max  ns} < F X , α > 𝔜) -> f .fst  η)

  isoAux : { ℓ' : Level} {h : HLevel} ->
           Iso (Σ[ F  ((X : Type ) -> Type (ℓ-max  ns)) ] FreeAux  ℓ' h F) (Free  ℓ' h)
  isoAux { = } {ℓ' = ℓ'} {h = h} = iso to from  _ -> refl)  _ -> refl)
    where
    to : Σ[ F  ((X : Type ) -> Type (ℓ-max  ns)) ] FreeAux  ℓ' h F -> Free  ℓ' h
    Free.F (to (F , aux)) = F
    Free.η (to (F , aux)) = FreeAux.η aux
    Free.α (to (F , aux)) = FreeAux.α aux
    Free.sat (to (F , aux)) = FreeAux.sat aux
    Free.isFree (to (F , aux)) = FreeAux.isFree aux
    Free.trunc (to (F , aux)) = FreeAux.trunc aux

    from : Free  ℓ' h -> Σ[ F  ((X : Type ) -> Type (ℓ-max  ns)) ] FreeAux  ℓ' h F
    fst (from free) = Free.F free
    FreeAux.η (snd (from free)) = Free.η free
    FreeAux.α (snd (from free)) = Free.α free
    FreeAux.sat (snd (from free)) = Free.sat free
    FreeAux.isFree (snd (from free)) = Free.isFree free
    FreeAux.trunc (snd (from free)) = Free.trunc free

module SameLevel {ℓ' f a e n s : Level} (σ : Sig f a) (τ : EqSig e (ℓ-max n s)) (ε : sysEq {n = ℓ-max n s} σ τ) where
  open Definition {n = n} {s = s} σ τ ε

   : Level
   = ℓ-max ℓ' ns

  module Equalities {h : HLevel} (freeDef : Free  ℓ' h) where
    open Free freeDef

    ext-η-id : {X : Type }
      -> (isHX : isOfHLevel h X)
      -> ext (trunc isHX) sat η  idHom (σStruct X)
    ext-η-id isHX = ext-β (trunc isHX) sat (idHom (σStruct _))

    ext-∘ :  {A B C : Type } (isHB : isOfHLevel h B) (isHC : isOfHLevel h C)
            (f : A -> F B) (g : B -> F C)
         -> ext (trunc isHC) sat (ext (trunc isHC) sat g .fst  f)  structHom∘ (σStruct A) (σStruct B) (σStruct C) (ext (trunc isHC) sat g) (ext (trunc isHB) sat f)
    ext-∘ isHB isHC f g = hom≡ (trunc isHC) sat _ _ $
        ext (trunc isHC) sat (ext (trunc isHC) sat g .fst  f) .fst  η
      ≡⟨ ext-η (trunc isHC) sat _ 
        ext (trunc isHC) sat g .fst  f
      ≡⟨ sym (congS (ext (trunc isHC) sat g .fst ∘_) (ext-η (trunc isHB) sat f)) 
        ext (trunc isHC) sat g .fst  ext (trunc isHB) sat f .fst  η 

  module Categories (freeDef : Free  ℓ' 2) where
    open Free freeDef
    open Equalities freeDef
    open Functor

    EndofunctorOnSet : Type _
    EndofunctorOnSet = Functor (SET ) (SET )

    module _ {F G : EndofunctorOnSet} where
      PreNatTransOnSet : Type _
      PreNatTransOnSet =  X -> F .F-ob X .fst -> G .F-ob X .fst

      NatTransSatSquare : PreNatTransOnSet -> Type _
      NatTransSatSquare η =  X Y f -> η Y  F .F-hom f  G .F-hom f  η X

      isPropNatTransSatSquare :  η -> isProp (NatTransSatSquare η)
      isPropNatTransSatSquare η = isPropΠ3 λ X Y f ->
        isSetΠ  _ -> G .F-ob Y .snd) (η Y  F .F-hom f) (G .F-hom f  η X)

      NatTransSetΣ : Type _
      NatTransSetΣ = Σ PreNatTransOnSet NatTransSatSquare

      setNaturalTrans :  η (square : NatTransSatSquare η) -> NatTrans F G
      setNaturalTrans η square .NatTrans.N-ob = η
      setNaturalTrans η square .NatTrans.N-hom {x = X} {y = Y} f = square X Y f

      NatTransSetIso : Iso NatTransSetΣ (NatTrans F G)
      NatTransSetIso = iso (uncurry setNaturalTrans)  nat -> NatTrans.N-ob nat , λ X Y -> NatTrans.N-hom nat)
         (natTrans N-ob N-hom) -> refl)
         (N-ob , N-hom) -> refl)

      NatTransSet≡ :  (η1 η2 : NatTrans F G) -> NatTrans.N-ob η1  NatTrans.N-ob η2 -> η1  η2
      NatTransSet≡ (natTrans N-ob1 N-hom1) (natTrans N-ob2 N-hom2) eq≡ =
        isoInvInjective NatTransSetIso (natTrans N-ob1 N-hom1) (natTrans N-ob2 N-hom2) (Σ≡Prop isPropNatTransSatSquare eq≡)

    algFunctor : EndofunctorOnSet
    algFunctor .Functor.F-ob (A , isSetA) =
      F A , trunc isSetA
    algFunctor .Functor.F-hom {y = Yset} f =
      ext (trunc (Yset .snd)) sat (η  f) .fst
    algFunctor .Functor.F-id {x = Xset} =
      congS fst (ext-β (trunc (Xset .snd)) sat (idHom (σStruct _)))
    algFunctor .Functor.F-seq {x = Xset} {y = Yset} {z = Zset} f g = congS fst $
      hom≡ (trunc (Zset .snd)) sat
        (ext (trunc (Zset .snd)) sat (η  g  f))
        (structHom∘ (σStruct (Xset .fst)) (σStruct (Yset .fst)) (σStruct (Zset .fst)) (ext (trunc (Zset .snd)) sat (η  g)) (ext (trunc (Yset .snd)) sat (η  f))) $ sym $
          ext (trunc (Zset .snd)) sat (η  g) .fst  ext (trunc (Yset .snd)) sat (η  f) .fst  η
        ≡⟨ congS (ext (trunc (Zset .snd)) sat (η  g) .fst ∘_) (ext-η (trunc (Yset .snd)) sat (η  f)) 
          ext (trunc (Zset .snd)) sat (η  g) .fst  η  f
        ≡⟨ congS (_∘ f) (ext-η (trunc (Zset .snd)) sat (η  g)) 
          η  g  f
        ≡⟨ sym (ext-η (trunc (Zset .snd)) sat (η  g  f)) 
          ext (trunc (Zset .snd)) sat (η  g  f) .fst  η 

    algIsMonad : IsMonad algFunctor
    algIsMonad .IsMonad.η =
      setNaturalTrans  X x -> η x)  X Y f -> sym (ext-η (trunc _) sat (η  f)))
    algIsMonad .IsMonad.μ =
      setNaturalTrans  X fx -> ext (trunc (X .snd)) sat (idfun _) .fst fx) λ X Y f -> congS fst $
        hom≡ (trunc (Y .snd)) sat
          (structHom∘ (σStruct (F _)) (σStruct (F (Y .fst))) (σStruct (Y .fst)) (ext (trunc (Y .snd)) sat (idfun _)) (ext (trunc (trunc (Y .snd))) sat (η  ext (trunc (Y .snd)) sat (η  f) .fst)))
          (structHom∘ (σStruct (F _)) (σStruct (X .fst)) (σStruct (Y .fst)) (ext (trunc (Y .snd)) sat (η  f)) (ext (trunc (X .snd)) sat (idfun _))) $
          ext (trunc _) sat (idfun _) .fst  (ext _ sat (η  ext (trunc _) sat (η  f) .fst) .fst)  η
        ≡⟨ congS (ext (trunc (Y .snd)) sat (idfun _) .fst ∘_) (ext-η (trunc (trunc (Y .snd))) sat (η  ext (trunc _) sat (η  f) .fst)) 
          ext (trunc (Y .snd)) sat (idfun _) .fst  η  ext (trunc (Y .snd)) sat (η  f) .fst
        ≡⟨ congS (_∘ ext (trunc (Y .snd)) sat (η  f) .fst) (ext-η (trunc (Y .snd)) sat (idfun _)) 
          ext (trunc (Y .snd)) sat (η  f) .fst
        ≡⟨ congS (ext (trunc (Y .snd)) sat (η  f) .fst ∘_) (sym (ext-η (trunc (X .snd)) sat (idfun _))) 
          ext (trunc (Y .snd)) sat (η  f) .fst  ext (trunc (X .snd)) sat (idfun _) .fst  η 
    algIsMonad .IsMonad.idl-μ = toPathP $ NatTransSet≡ _ _ $
        transport refl  X -> ext (trunc (X .snd)) sat (idfun _) .fst  η)
      ≡⟨ transportRefl _ 
         X -> ext (trunc (X .snd)) sat (idfun _) .fst  η)
      ≡⟨ funExt  X -> ext-η (trunc (X .snd)) sat (idfun _)) 
         X -> idfun _) 
    algIsMonad .IsMonad.idr-μ = toPathP $ NatTransSet≡ _ _ $
        transport refl  X -> ext (trunc (X .snd)) sat (idfun _) .fst  ext (trunc (trunc (X .snd))) sat (η  η) .fst)
      ≡⟨ transportRefl _ 
         X -> ext (trunc (X .snd)) sat (idfun _) .fst  ext (trunc (trunc (X .snd))) sat (η  η) .fst)
      ≡⟨ funExt  X -> sym (congS fst (ext-∘ (trunc (X .snd)) (X .snd) (η  η) (idfun _)))) 
         X -> ext (trunc (X .snd)) sat (ext (trunc (X .snd)) sat (idfun _) .fst  η  η) .fst)
      ≡⟨ funExt  X -> congS  f -> ext (trunc (X .snd)) sat (f  η) .fst) (ext-η (trunc (X .snd)) sat (idfun _))) 
         X -> ext (trunc (X .snd)) sat η .fst)
      ≡⟨ funExt  X -> congS fst (ext-η-id (X .snd))) 
         X -> idfun _) 
    algIsMonad .IsMonad.assoc-μ = toPathP $ NatTransSet≡ _ _ $
        transport refl  X -> ext (trunc (X .snd)) sat (idfun _) .fst  ext (trunc (trunc (X .snd))) sat (η  ext (trunc (X .snd)) sat (idfun _) .fst) .fst)
      ≡⟨ transportRefl _ 
         X -> ext (trunc (X .snd)) sat (idfun _) .fst  ext (trunc (trunc (X .snd))) sat (η  ext (trunc (X .snd)) sat (idfun _) .fst) .fst)
      ≡⟨ funExt  X -> sym (congS fst (ext-∘ (trunc (X .snd)) (X .snd) (η  ext (trunc (X .snd)) sat (idfun _) .fst) (idfun _)))) 
         X -> ext (trunc (X .snd)) sat (ext (trunc (X .snd)) sat (idfun _) .fst  η  (ext (trunc (X .snd)) sat (idfun (F (X .fst))) .fst)) .fst)
      ≡⟨ funExt  X -> congS  f -> ext (trunc (X .snd)) sat (f  (ext (trunc (X .snd)) sat (idfun (F (X .fst))) .fst)) .fst) (ext-η (trunc (X .snd)) sat (idfun _))) 
         X -> ext (trunc (X .snd)) sat (ext (trunc (X .snd)) sat (idfun (F (X .fst))) .fst) .fst)
      ≡⟨ funExt  X -> congS fst (ext-∘ (trunc (X .snd)) (X .snd) (idfun _) (idfun _))) 
         X -> ext (trunc (X .snd)) sat (idfun _) .fst  ext (trunc (trunc (X .snd))) sat (idfun _) .fst)