-- Free functor between categories generated from two graphs and a
-- function on nodes between them
{-# OPTIONS --safe --lossy-unification #-}
module Cubical.Categories.Constructions.Free.Functor where

open import Cubical.Foundations.Prelude hiding (J)
open import Cubical.Foundations.Function renaming (_∘_ to _∘f_)
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Path
open import Cubical.Foundations.Transport

open import Cubical.Data.Empty
open import Cubical.Data.Equality.Conversion
open import Cubical.Data.Equality hiding (id; sym)
  renaming (_≡_ to Eq; refl to reflEq; _∙_ to _∙Eq_; transport to transportEq)
open import Cubical.Data.Graph.Base
open import Cubical.Data.Sigma

open import Cubical.Categories.Category.Base
open import Cubical.Categories.Constructions.Free.Category
open import Cubical.Categories.Functor.Base
open import Cubical.Categories.NaturalTransformation.Base
open import Cubical.Categories.UnderlyingGraph

private
  variable
     ℓ' ℓc ℓc' ℓd ℓd' ℓg ℓg' ℓh ℓh' : Level

open Category
open Functor
open NatTrans
open NatIso
open isIso

module FreeFunctor (G : Graph ℓg ℓg')
                   (H : Graph ℓh ℓh')
                   (ϕ : G .Node  H .Node) where
  module FreeCatG = FreeCategory G
  open FreeCatG.Exp
  FG = FreeCatG.FreeCat
  Exp = FreeCatG.Exp
  data FExp : H .Node  H .Node
             Type (((ℓ-max ℓg (ℓ-max ℓg' (ℓ-max ℓh ℓh'))))) where
    -- free category on H with a functor from G to H freely added
    ↑_ :  {A B}  H .Edge A B  FExp A B
    idₑ :  {A}  FExp A A
    _⋆ₑ_ :  {A B C}  FExp A B  FExp B C  FExp A C
    F⟪_⟫ :  {A B}  Exp A B  FExp (ϕ A) (ϕ B)

    ⋆ₑIdL :  {A B} (e : FExp A B)  idₑ ⋆ₑ e  e
    ⋆ₑIdR :  {A B} (e : FExp A B)  e ⋆ₑ idₑ  e
    ⋆ₑAssoc :  {A B C D} (e : FExp A B)(f : FExp B C)(g : FExp C D)
             (e ⋆ₑ f) ⋆ₑ g  e ⋆ₑ (f ⋆ₑ g)
    F-idₑ :  {A}  F⟪ idₑ {A = A}   idₑ
    F-seqₑ :  {A B C} (f : Exp A B)(g : Exp B C)
            F⟪ f ⋆ₑ g   (F⟪ f  ⋆ₑ F⟪ g )

    isSetFExp :  {A B}  isSet (FExp A B)

  FH : Category _ _
  FH .ob = H .Node
  FH .Hom[_,_] = FExp
  FH .id = idₑ
  FH ._⋆_ = _⋆ₑ_
  FH .⋆IdL = ⋆ₑIdL
  FH .⋆IdR = ⋆ₑIdR
  FH .⋆Assoc = ⋆ₑAssoc
  FH .isSetHom = isSetFExp

   : Functor FG FH
   .F-ob = ϕ
   .F-hom = F⟪_⟫
   .F-id = F-idₑ
   .F-seq = F-seqₑ

  -- The universal interpretation
  ηG = FreeCatG.η

  ηH : Interp H FH
  ηH $g x = x
  ηH <$g> x =  x

  Fϕ-homo : GraphHom G (Cat→Graph FH)
  Fϕ-homo $g x = ϕ x
  Fϕ-homo <$g> x = F⟪  x 

  ηϕ : Eq ( .F-ob ∘f ηG ._$g_) (ηH ._$g_ ∘f ϕ)
  ηϕ = reflEq

  module _ {𝓒 : Category ℓc ℓc'}{𝓓 : Category ℓd ℓd'} {𝓕 : Functor 𝓒 𝓓} where
    {-

       It is very important for the implementation of the functor
       solver that ıϕ uses Data.Equality.Eq rather than Path. The
       reason is that the case semH-hom (F⟪_⟫ {A}{B} x) inherently
       involves a transport when expressed at this level of
       generality, and transport of a refl is the identity function
       for Eq but not for Path.

    -}
    module Semantics (ıG : Interp G 𝓒) (ıH : Interp H 𝓓)
                     (ıϕ : Eq (𝓕 .F-ob ∘f ıG ._$g_) (ıH ._$g_ ∘f ϕ))
           where
      semG = FreeCatG.Semantics.sem 𝓒 ıG

      semH-hom :  {A B}  FExp A B  𝓓 [ ıH $g A , ıH $g B ]
      semH-hom ( x) = ıH <$g> x
      semH-hom idₑ = 𝓓 .id
      semH-hom (e ⋆ₑ e₁) = semH-hom e ⋆⟨ 𝓓  semH-hom e₁
      semH-hom (F⟪_⟫ {A}{B} x) =
        transportEq  (f : G .Node  𝓓 .ob)  𝓓 [ f A , f B ])
                    ıϕ
                    (𝓕  semG  x  )
      semH-hom (⋆ₑIdL f i) = 𝓓 .⋆IdL (semH-hom f) i
      semH-hom (⋆ₑIdR f i) = 𝓓 .⋆IdR (semH-hom f) i
      semH-hom (⋆ₑAssoc f f' f'' i) =
        𝓓 .⋆Assoc (semH-hom f) (semH-hom f') (semH-hom f'') i
      semH-hom (F-idₑ {A} i) = unbound i
        where
          unbound : transportEq  f  𝓓 [ f A , f A ]) ıϕ (𝓕  semG  idₑ  )
                     𝓓 .id
          unbound =
            J  g p  transportEq  f  𝓓 [ f A , f A ]) p
                                   (𝓕  semG  idₑ  )
                        𝓓 .id)
              ((𝓕 ∘F semG) .F-id) ıϕ
      semH-hom (F-seqₑ {A}{B}{C} e e' i) = unbound i
        where
          unbound :
            transportEq  f  𝓓 [ f A , f C ]) ıϕ (𝓕  semG  e ⋆ₑ e'  )
             (transportEq  f  𝓓 [ f A , f B ]) ıϕ (𝓕  semG  e  ))
              ⋆⟨ 𝓓  (transportEq  f  𝓓 [ f B , f C ]) ıϕ
                                  (𝓕  semG  e'  ))
          unbound =
            J  g p 
                transportEq  f  𝓓 [ f A , f C ]) p (𝓕  semG  e ⋆ₑ e'  )
                 (transportEq  f  𝓓 [ f A , f B ]) p (𝓕  semG  e  ))
                  ⋆⟨ 𝓓  (transportEq  f  𝓓 [ f B , f C ]) p
                                      (𝓕  semG  e'  )))
              ((𝓕 ∘F semG) .F-seq e e')
              ıϕ
      semH-hom (isSetFExp f g p q i j) =
        𝓓 .isSetHom (semH-hom f)
                    (semH-hom g)
                    (cong semH-hom p)
                    (cong semH-hom q)
                    i
                    j

      semH : Functor FH 𝓓
      semH .F-ob = ıH ._$g_
      semH .F-hom = semH-hom
      semH .F-id = refl
      semH .F-seq f g = refl

      semϕ : Eq (𝓕 ∘F semG) (semH ∘F )
      semϕ = pathToEq (FreeCatG.induction (funcComp 𝓕 semG)
                                          (funcComp semH )
                                          (GrHom≡ aoo aoe))
        where
        𝓕G = (𝓕 .F-ob ∘f ıG ._$g_)
         = (ıH ._$g_ ∘f ϕ)

        aoo-gen :  (v : Node G) f g
                 Eq {A = G .Node  𝓓 .ob} f g
                 Path _ (f v) (g v)
        aoo-gen v f g = J ((λ f' _  Path _ (f v) (f' v))) refl
        aoo : (v : Node G)
             Path _ (((𝓕 ∘F semG) ∘Interp ηG) $g v)
                     (((semH ∘F ) ∘Interp ηG) $g v)
        aoo v = aoo-gen v 𝓕G  ıϕ

        aoe : {v w : Node G} (e : G .Edge v w) 
              PathP  i  𝓓 .Hom[_,_] (aoo v i) (aoo w i))
                    (𝓕  semG   e  )
                    (semH     e  )
        aoe {v}{w} e = toPathP lem where
          lem : Path _
                (transport  i  𝓓 [ aoo-gen v 𝓕G  ıϕ i
                                        , aoo-gen w 𝓕G  ıϕ i ])
                               (𝓕  ıG <$g> e ))
                (transportEq    f  𝓓 [ f v , f w ]) ıϕ (𝓕  ıG <$g> e ))
          lem =
            J  f p 
                Path _
                     ((transport  i  𝓓 [ aoo-gen v 𝓕G f p i
                                              , aoo-gen w 𝓕G f p i ])
                                     (𝓕  ıG <$g> e )))
                     ((transportEq  f  𝓓 [ f v , f w ]) p
                                   (𝓕  ıG <$g> e ))))
              (transportRefl (𝓕  ıG <$g> e ))
              ıϕ

      module Uniqueness (arb𝓒 : Functor FG 𝓒)
                        (arb𝓓 : Functor FH 𝓓)
                        (arb𝓕 : Path (Functor FG 𝓓) (𝓕 ∘F arb𝓒) (arb𝓓 ∘F ))
                        (arb𝓒-agree : arb𝓒 ∘Interp ηG  ıG)
                        (arb𝓓-agree : arb𝓓 ∘Interp ηH  ıH)
                        (arb𝓕-agree : Square {A = G .Node  𝓓 .ob}
                                         i x  arb𝓕 i  x )
                                        (eqToPath ıϕ)
                                         i x  𝓕  arb𝓒-agree i $g x )
                                         i x  arb𝓓-agree i $g (ϕ x)))
             where
        sem-uniq-G : arb𝓒  semG
        sem-uniq-G = FreeCatG.Semantics.sem-uniq _ _ arb𝓒-agree

        sem-uniq-H : arb𝓓  semH
        sem-uniq-H = Functor≡ aoo aom where
          aoo : (v : H .Node)  arb𝓓  v   ıH $g v
          aoo =  v i  arb𝓓-agree i $g v)

          aom-type :  {v w}  (f : FH [ v , w ])  Type _
          aom-type {v}{w} f = PathP  i  𝓓 [ aoo v i , aoo w i ])
                                    (arb𝓓  f )
                                    (semH  f )

          aom-id :  {v}  aom-type {v} idₑ
          aom-id = arb𝓓 .F-id  λ i  𝓓 .id

          aom-seq :  {v w x}  {f : FH [ v , w ]} {g : FH [ w , x ]}
                   aom-type f
                   aom-type g
                   aom-type (f ⋆ₑ g)
          aom-seq hypf hypg = arb𝓓 .F-seq _ _  λ i  hypf i ⋆⟨ 𝓓  hypg i
          ıϕp = eqToPath ıϕ

          aom-F :  {v w}
                 (e : FG [ v , w ])
                 PathP  i  𝓓 [ (arb𝓓-agree i $g (ϕ v))
                                 , (arb𝓓-agree i $g (ϕ w)) ])
                        (arb𝓓    e  )
                        (transportEq  (f : G .Node  𝓓 .ob)  𝓓 [ f v , f w ])
                                     ıϕ
                                     (𝓕  semG  e  ))
          aom-F {v}{w} e =
            pathified  eqToPath (
              substPath≡transport'
                 (f : G .Node  𝓓 .ob)  𝓓 [ f v , f w ])
                (𝓕  semG  e  )
                ıϕ)
            where
              pathified :
                PathP  i  𝓓 [ arb𝓓-agree i $g ϕ v , arb𝓓-agree i $g ϕ w ])
                      (arb𝓓    e  )
                      (transport  i  𝓓 [ ıϕp i v , ıϕp i w ])
                                     (𝓕  semG  e  ))
              pathified = toPathP⁻ ((
                fromPathP⁻ lem'
                 cong (transport⁻  i  𝓓 [ arb𝓕 (~ i)  v 
                                            , arb𝓕 (~ i)  w  ]))
                       (fromPathP⁻ lem𝓒)
                 sym (transportComposite
                        ((λ i  𝓓 [ 𝓕  arb𝓒-agree (~ i) $g v 
                                  , 𝓕  arb𝓒-agree (~ i) $g w   ]))
                         i  𝓓 [ arb𝓕 i  v  , arb𝓕 i  w   ])
                        ((𝓕  semG  e  )))
                 ((λ i  transport (substOf-sems-agreeϕ i) (𝓕  semG  e  )))
                 transportComposite
                     i  𝓓 [ ıϕp i v , ıϕp i w ])
                     i  𝓓 [ arb𝓓-agree (~ i) $g ϕ v
                             , arb𝓓-agree (~ i) $g ϕ w ])
                    (𝓕  semG  e  )
                ))
                where
                  lem' : PathP  i  𝓓 [ arb𝓕 (~ i)  v 
                                        , arb𝓕 (~ i)  w   ])
                           (arb𝓓    e  )
                           (𝓕  arb𝓒  e  )
                  lem' i = arb𝓕 (~ i)  e 

                  lem𝓒 : PathP  i  𝓓 [ 𝓕  arb𝓒-agree i $g v 
                                        , 𝓕  arb𝓒-agree i $g w  ])
                           (𝓕  arb𝓒  e  )
                           (𝓕  semG  e  )
                  lem𝓒 i = 𝓕  sem-uniq-G i  e  

                  substOf-sems-agreeϕ :
                    ((λ i  𝓓 [ 𝓕  arb𝓒-agree (~ i) $g v 
                               , 𝓕  arb𝓒-agree (~ i) $g w  ])
                      i  𝓓 [ arb𝓕 i  v  , arb𝓕 i  w  ]))
                     ((λ i  𝓓 [ ıϕp i v , ıϕp i w ])
                        i  𝓓 [ arb𝓓-agree (~ i) $g ϕ v
                                 , arb𝓓-agree (~ i) $g ϕ w ]))
                  substOf-sems-agreeϕ =
                    sym (cong-∙ A  i x  𝓕  arb𝓒-agree (~ i) $g x )
                                λ i x  arb𝓕 i  x )
                     cong (cong A)
                           (Square→compPath λ i j x  arb𝓕-agree (~ i) j x)
                     cong-∙ A  i x  ıϕp i x)
                              i x  arb𝓓-agree (~ i) $g ϕ x) where
                      the-type = (G .Node  𝓓 .ob)
                      A =  (f : the-type)  𝓓 [ f v , f w ])
          aom :  {v w : H .Node} (f : FH [ v , w ])  aom-type f
          aom ( x) = λ i  arb𝓓-agree i <$g> x
          aom idₑ = aom-id
          aom (f ⋆ₑ g) = aom-seq (aom f) (aom g)
          aom F⟪ x  = aom-F x
          aom (⋆ₑIdL f i) =
            isSet→SquareP
               i j  𝓓 .isSetHom)
              (aom-seq aom-id (aom f))
              (aom f)
               i  arb𝓓  ⋆ₑIdL f i )
               i  (semH  ⋆ₑIdL f i ))
              i
          aom (⋆ₑIdR f i) =
            isSet→SquareP  i j  𝓓 .isSetHom)
              (aom-seq (aom f) aom-id)
              (aom f )
               i  arb𝓓  ⋆ₑIdR f i )
               i  semH  ⋆ₑIdR f i )
              i
          aom (⋆ₑAssoc f f₁ f₂ i) =
              isSet→SquareP
                 i j  𝓓 .isSetHom)
                (aom-seq (aom-seq (aom f) (aom f₁)) (aom f₂))
                (aom-seq (aom f) (aom-seq (aom f₁) (aom f₂)))
                 i  arb𝓓  ⋆ₑAssoc f f₁ f₂ i )
                 i  semH  ⋆ₑAssoc f f₁ f₂ i )
                i
          aom (F-idₑ i) =
            isSet→SquareP
               i j  𝓓 .isSetHom)
              (aom-F idₑ)
              aom-id
               i  arb𝓓  F-idₑ i )
               i  semH  F-idₑ i )
              i
          aom (F-seqₑ f g i) =
            isSet→SquareP
               i j  𝓓 .isSetHom)
              (aom-F (f ⋆ₑ g))
              (aom-seq (aom-F f) (aom-F g))
               i  arb𝓓  F-seqₑ f g i )
               i  semH  F-seqₑ f g i )
              i
          aom (isSetFExp f f₁ x y i j) k =
            isSet→SquareP
               i j  (isOfHLevelPathP {A = λ k  𝓓 [ aoo _ k , aoo _ k ]} 2
                                        (𝓓 .isSetHom)
                                        (arb𝓓  isSetFExp f f₁ x y i j )
                                        ((semH  isSetFExp f f₁ x y i j ))))
               j k  aom (x j) k)
               j k  aom (y j) k)
               i k  aom f k)
               i k  aom f₁ k)
              i j k