module Uniform.Examples where


open import Prelude.Basic
open import Prelude.Equality
open import Prelude.Equivalences
open import Prelude.Fam

open import Data.Fin hiding (_+_; lift)

open import Uniform.IR
open import Uniform.Initial
open import Uniform.Coproduct
open import Uniform.Coproduct.Maps
open import Uniform.Composition using (_○_)

-- W-types

module _ (S : Set)(P : S -> Set) where

  cW₀ : {D : Set1} -> Uni D
  cW₀ = δ (σ ι  _  S))  { (_ , s)  P s })

  -- ordinary W-type: choose D = ⊤

  cW⊤ : UF {lsuc lzero} (Lift ) (Lift )
  cW⊤ = (cW₀ , λ _  _)

  W : Set
  W = U {c = cW⊤}

  sup : (s : S) -> (P s -> W) -> W
  sup s f = intro ((tt , s) , f)

  -- "upgraded" W-type where decoding component applies T everywhere

  cW : UF {lsuc lzero} Set Set
  cW = cW₀ ,  { ((_ , s) , Tf)  (x : P s) -> Tf x })

-- A universe closed under sigma- and pi-, and W-types

cUΣΠW : UF Set Set
cUΣΠW = (cUB UF-+ cUΣ) UF-+ (cUΠ UF-+ cUW)
  module UU where
   cUB = (ι , λ _  Bool)
   cUΣ = (δ (δ ι  _  ))  { (_ , A)  A _ }) ,
           { ((_ , A) , B)  Σ (A _) B }))
   cUΠ = (δ (δ ι  _  ))  { (_ , A)  A _ }) ,
           { ((_ , A) , B)  (x : A _) -> B x }))
   cUW = (δ (δ ι  _  ))  { (_ , A)  A _ }) ,
           { ((_ , A) , B)  W (A _) B }))

UΣΠW : Set
UΣΠW = U {c = cUΣΠW}

TΣΠW : UΣΠW -> Set
TΣΠW x = T x


-- one part of the ismorphism ⟦ c + d ⟧ ≃ ⟦ c ⟧ + ⟦ d ⟧
iso :   ( UU.cUB ⟧₀ (UΣΠW , TΣΠW)   UU.cUΣ ⟧₀ (UΣΠW , TΣΠW))
       ( UU.cUΠ ⟧₀ (UΣΠW , TΣΠW)   UU.cUW ⟧₀ (UΣΠW , TΣΠW))
                                     ->  cUΣΠW ⟧₀ (UΣΠW , TΣΠW)
iso =   right (UU.cUB UF-+ UU.cUΣ) (UU.cUΠ UF-+ UU.cUW)
       ⊎-map (right UU.cUB UU.cUΣ) (right UU.cUΠ UU.cUW)
  where right : (R Q : UF Set Set) ->
                 R ⟧₀ (UΣΠW , TΣΠW)   Q ⟧₀ (UΣΠW , TΣΠW) ->
                 R UF-+ Q ⟧₀ (UΣΠW , TΣΠW)
        right R Q = (_Fam⇒_.indmor (_≃_.right (coprodEq R Q {Z = (UΣΠW , TΣΠW)})))


bool : UΣΠW
bool = intro (iso (inj₁ (inj₁ tt)))

sig : (a : UΣΠW) -> (b : TΣΠW a -> UΣΠW) -> UΣΠW
sig a b = intro (iso (inj₁ (inj₂ ((tt ,  _  a)) , b))))

pi : (a : UΣΠW) -> (b : TΣΠW a -> UΣΠW) -> UΣΠW
pi a b = intro (iso (inj₂ (inj₁ ((tt ,  _  a)) , b))))

w : (a : UΣΠW) -> (b : TΣΠW a -> UΣΠW) -> UΣΠW
w a b = intro (iso (inj₂ (inj₂ ((tt ,  _  a)) , b))))

-- A language of sums and products

sumFin : (n : ) -> (Fin n -> ) -> 
sumFin zero f = 0
sumFin (suc n) f = f (fromℕ n) + sumFin n (f  inject₁)

prodFin : (n : ) -> (Fin n -> ) -> 
prodFin zero f = 1
prodFin (suc n) f = f (fromℕ n) * prodFin n (f  inject₁)

cArith : UF {lsuc lzero} (Lift ) (Lift )
cArith = cFin UF-+ cSum UF-+ cProd
  module Ar where
   cFin = (σ ι  _  )) ,  { (_ , n)  lift n })
   cSum = δ (δ ι  _  ))  { (_ , n)  Fin (lower (n _)) }) ,
           { ((_ , n) , f)  lift (sumFin (lower (n _)) (lower  f)) })
   cProd = δ (δ ι  _  ))  { (_ , n)  Fin (lower (n _)) }) ,
           { ((_ , n) , f)  lift (prodFin (lower (n _)) (lower  f)) })

Arith : Set
Arith = U {c = cArith}

eval : Arith -> 
eval = lower  T

isoA :    ( Ar.cFin ⟧₀ (Arith , T)
          Ar.cSum ⟧₀ (Arith , T))
          Ar.cProd ⟧₀ (Arith , T) ->  cArith ⟧₀ (Arith , T)
isoA = right (Ar.cFin UF-+ Ar.cSum) Ar.cProd  ⊎-map (right Ar.cFin Ar.cSum) id
  where right : (R Q : UF (Lift ) (Lift )) ->
                 R ⟧₀ (Arith , T)   Q ⟧₀ (Arith , T) ->
                 R UF-+ Q ⟧₀ (Arith , T)
        right R Q = (_Fam⇒_.indmor (_≃_.right (coprodEq R Q {Z = (Arith , T)})))


fin :  -> Arith
fin n = intro (isoA (inj₁ (inj₁ (tt , n))))

sum : (n : Arith) -> (Fin (eval n) -> Arith) -> Arith
sum n f = intro (isoA (inj₁ (inj₂ ((tt ,  _  n)) , f) )))

prod : (n : Arith) -> (Fin (eval n) -> Arith) -> Arith
prod n f = intro (isoA (inj₂ ((tt ,  _  n)) , f) ))

fac :  -> Arith
fac n = prod (fin n)  x  fin (suc (toℕ x)))

_ : eval (fac 5)  120
_ = refl

-- List of arguments for free: composing with W ℕ Fin

cUList : UF Set Set
cUList = (UU.cUB UF-+ UU.cUΣ)  (cW  Fin)

-- the isomorphism (a + b) ∘ c ≃ (a ∘ c) + (b ∘ c) makes it a bit
-- awkward to find the exact code cUList reduces to. Instead, we find
-- the code it morally reduces to using this isomorphism, presented as
-- a function of decodings: note that this is clearly invertible as it
-- is just shuffling trivial `lift tt`s and `⊥-elim`s around.

compCode : let
       cUΣ-composed
         = (δ (σ (cW₀  Fin)
                          { ((_ , s) , Tf)  ((x : Fin s)  Tf x)  }))
                  ((λ { (((_ , s) , Tf) , e) 
                                 Σ[ y  ((x : Fin s)  Tf x) ] (Fin (e y))}))) ,
            { ((((_ , s) , Tf) , e) , B) 
                Σ[ y  ((x : Fin s) -> Tf x) ] ((w : Fin (e y)) -> B (y , w)) })
    in {Z : Fam Set} ->
        cUList ⟧₀ Z ->
        UU.cUB UF-+ cUΣ-composed ⟧₀ Z
compCode (((((((((_ , true) , _) , _) , _) , _) , _) , _) , _) , _)
   = ((((((((((tt , true) , ⊥-elim) , lift tt) , ⊥-elim) , lift tt) , ⊥-elim) , lift tt) , ⊥-elim) , lift tt) , ⊥-elim)
compCode (((((((((_ , false) , _) , _) , _) , n) , f) , _) , e) , y)
   = ((((((((((tt , false) , ⊥-elim) , n _) , ⊥-elim) , tt) ,  x  f (_ , x))) , e) , ⊥-elim) , tt) , y)