{-# OPTIONS --cubical #-}
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.HLevels
open import Cubical.Data.Sigma
open import Cubical.Data.FinData
open import Cubical.HITs.PropositionalTruncation hiding (map)
open import Cubical.HITs.PropositionalTruncation.Monad
open import Cubical.Categories.Limits.BinProduct
open import Realizability.CombinatoryAlgebra
open import Realizability.ApplicativeStructure

module Realizability.Assembly.BinProducts {} {A : Type } (ca : CombinatoryAlgebra A) where

open Realizability.CombinatoryAlgebra.Combinators ca renaming (i to Id; ia≡a to Ida≡a)
open import Realizability.Assembly.Base ca
open import Realizability.Assembly.Morphism ca
open CombinatoryAlgebra ca
open Assembly
open AssemblyMorphism

infixl 23 _⊗_
_⊗_ : {A B : Type }  Assembly A  Assembly B  Assembly (A × B)
(as  bs) .isSetX = isSetΣ (as .isSetX)  _  bs .isSetX)
(as  bs) ._⊩_ r (a , b) = (as ._⊩_ (pr₁  r) a) × (bs ._⊩_ (pr₂  r) b)
(as  bs) .⊩isPropValued r (a , b) = isPropΣ (as .⊩isPropValued (pr₁  r) a)
                                              _  bs .⊩isPropValued (pr₂  r) b)
(as  bs) .⊩surjective (a , b) = do
                                   (b~ , b~realizes)  bs .⊩surjective b
                                   (a~ , a~realizes)  as .⊩surjective a
                                   return
                                     ( pair  a~  b~
                                     , subst  x  as ._⊩_ x a) (sym (pr₁pxy≡x a~ b~)) a~realizes
                                     , subst  x  bs ._⊩_ x b) (sym (pr₂pxy≡y a~ b~)) b~realizes
                                     )

⟪_,_⟫ : {X Y Z W : Type }
        {xs : Assembly X}
        {ys : Assembly Y}
        {zs : Assembly Z}
        {ws : Assembly W}
        (f : AssemblyMorphism xs ys)
        (g : AssemblyMorphism zs ws)
         AssemblyMorphism (xs  zs) (ys  ws)
 f , g  .map (x , z) = f .map x , g .map z
⟪_,_⟫ {ys = ys} {ws = ws} f g .tracker =
  do
    (f~ , f~⊩isTrackedF)  f .tracker
    (g~ , g~⊩isTrackedG)  g .tracker
    let
      realizer : Term as 1
      realizer = ` pair ̇ (` f~ ̇ (` pr₁ ̇ # zero)) ̇ (` g~ ̇ (` pr₂ ̇ # zero))
    return
      (λ* realizer ,
       { (x , z) r (pr₁r⊩x , pr₂r⊩z) 
        subst  r'  r' ⊩[ ys ] (f .map x)) (sym (cong  x  pr₁  x) (λ*ComputationRule realizer r)  pr₁pxy≡x _ _)) (f~⊩isTrackedF x (pr₁  r) pr₁r⊩x) ,
        subst  r'  r' ⊩[ ws ] (g .map z)) (sym (cong  x  pr₂  x) (λ*ComputationRule realizer r)  pr₂pxy≡y _ _)) (g~⊩isTrackedG z (pr₂  r) pr₂r⊩z) }))
        
π₁ : {A B : Type } {as : Assembly A} {bs : Assembly B}  AssemblyMorphism (as  bs) as
π₁ .map (a , b) = a
π₁ .tracker =  pr₁ ,  (a , b) p (goal , _)  goal) ∣₁

π₂ : {A B : Type } {as : Assembly A} {bs : Assembly B}  AssemblyMorphism (as  bs) bs
π₂ .map (a , b) = b
π₂ .tracker =  pr₂ ,  (a , b) p (_ , goal)  goal) ∣₁

⟨_,_⟩ : {X Y Z : Type }
       {xs : Assembly X} {ys : Assembly Y} {zs : Assembly Z}
       AssemblyMorphism zs xs
       AssemblyMorphism zs ys
       AssemblyMorphism zs (xs  ys)
 f , g  .map z = f .map z , g .map z
⟨_,_⟩ {X} {Y} {Z} {xs} {ys} {zs} f g .tracker =
  do
    (f~ , f~⊩isTrackedF)  f .tracker
    (g~ , g~⊩isTrackedG)  g .tracker
    let
      realizer : Term as 1
      realizer = ` pair ̇ (` f~ ̇ # zero) ̇ (` g~ ̇ # zero)
    return
      (λ* realizer ,
       z r r⊩z 
        subst  r'  r' ⊩[ xs ] (f .map z)) (sym (cong  x  pr₁  x) (λ*ComputationRule realizer r)  pr₁pxy≡x _ _)) (f~⊩isTrackedF z r r⊩z) ,
        subst  r'  r' ⊩[ ys ] (g .map z)) (sym (cong  x  pr₂  x) (λ*ComputationRule realizer r)  pr₂pxy≡y _ _)) (g~⊩isTrackedG z r r⊩z)))
  
module _ {X Y : Type } (xs : Assembly X) (ys : Assembly Y) where
    theπ₁ = π₁ {A = X} {B = Y} {as = xs} {bs = ys}
    theπ₂ = π₂ {A = X} {B = Y} {as = xs} {bs = ys}
    isBinProduct⊗ : ((Z , zs) : Σ[ Z  Type  ] Assembly Z)
                    (f : AssemblyMorphism zs xs)
                    (g : AssemblyMorphism zs ys)
                    ∃![ fg  AssemblyMorphism zs (xs  ys) ] (fg  theπ₁  f) × (fg  theπ₂  g)
    isBinProduct⊗ (Z , zs) f g =
                  uniqueExists
                    {B = λ fg  (fg  theπ₁  f) × (fg  theπ₂  g)}
                     f , g 
                    ( AssemblyMorphism≡ ( f , g   theπ₁) f (funExt  x  refl))
                    , AssemblyMorphism≡ ( f , g   theπ₂) g (funExt  x  refl)))
                     fg  isProp×
                            (isSetAssemblyMorphism zs xs (fg  theπ₁) f)
                            (isSetAssemblyMorphism zs ys (fg  theπ₂) g))
                    -- TODO : Come up with a prettier proof
                    λ fg (fgπ₁≡f , fgπ₂≡g)  sym ((lemma₂ fg fgπ₁≡f fgπ₂≡g)  (lemma₁ fg fgπ₁≡f fgπ₂≡g)) where
                      module _ (fg : AssemblyMorphism zs (xs  ys))
                               (fgπ₁≡f : fg  theπ₁  f)
                               (fgπ₂≡g : fg  theπ₂  g) where
                             lemma₁ :  fg  theπ₁ , fg  theπ₂    f , g 
                             lemma₁ = AssemblyMorphism≡
                                       fg  theπ₁ , fg  theπ₂ 
                                       f , g 
                                       i z  (fgπ₁≡f i .map z) , (fgπ₂≡g i .map z))

                             lemma₂ : fg   fg  theπ₁ , fg  theπ₂ 
                             lemma₂ = AssemblyMorphism≡
                                      fg
                                       fg  theπ₁ , fg  theπ₂ 
                                      (funExt λ x  ΣPathP (refl , refl))

module _ where
    open BinProduct
    ASMBinProducts : BinProducts ASM
    ASMBinProducts (X , xs) (Y , ys) .binProdOb = (X × Y) , (xs  ys)
    ASMBinProducts (X , xs) (Y , ys) .binProdPr₁ = π₁ {as = xs} {bs = ys}
    ASMBinProducts (X , xs) (Y , ys) .binProdPr₂ = π₂ {as = xs} {bs = ys}
    ASMBinProducts (X , xs) (Y , ys) .univProp {z} f g = isBinProduct⊗ xs ys z f g