{-# OPTIONS --guardedness #-}
open import Action

open import Relation.Binary.Definitions using (Decidable ; DecidableEquality)
open import Relation.Nullary.Decidable
open import Relation.Unary using (Pred)
open import Data.List
open import Data.Maybe hiding (_>>=_)
open import Data.Product
open import Data.Sum
open import Data.Vec hiding (_++_)
open import Data.Nat
open import Data.Nat.Properties
open import Data.Fin using (toℕ ; fromℕ)
open import Function
open import Effect.Functor
open import Relation.Binary.PropositionalEquality renaming (subst to ≡-subst)

module Semantics {ℓ} (A : Set ℓ) {dec : DecidableEquality A} {Action : Act A dec} where
  open Act Action
  import Syntax
  open Syntax A {dec} {Action}
  open Action.Renaming A dec Action
  open import FreeAlgebra A {dec} {Action}

  record FST (L : Set ℓ) : Set ℓ where
    coinductive
    constructor node
    field
      children : List L

  open FST

  FSTF : RawFunctor FST
  (FSTF RawFunctor.<$> f) x .children = Data.List.map f (children x)

  Sub : ∀ (X Y : Set ℓ) → Set ℓ
  Sub X Y = List (Maybe X) → Y

  SubF : ∀ (X : Set ℓ) → RawFunctor (Sub X)
  (SubF X RawFunctor.<$> f) σ = f ∘ σ

  B : ∀ (X Y : Set ℓ) → Set ℓ
  B X Y = Sub X Y × FST (Aτ × Sub X Y)

  BF : ∀ (X : Set ℓ) → RawFunctor (B X)
  (BF X RawFunctor.<$> f) (σ , b) = (submap₂ f σ) , tmap (Data.Product.map₂ (submap₂ f)) b
    where open RawFunctor FSTF renaming (_<$>_ to tmap)
          open RawFunctor (SubF X) renaming (_<$>_ to submap₂)

  ϱ : ∀ {X Y : Set ℓ} → Sig (X × B X Y) → B X (Σ* (X ⊎ Y))
  ϱ dead = (λ _ → app dead)
                  , (node [])
  ϱ (name m) = (λ x → lookupM m x (app (name m)) λ P → app (fix (var (inj₁ P))))
                    , (node [])
    where
      lookupM : ∀ {A C : Set ℓ} → ℕ → List (Maybe A) → C → (A → C) → C
      lookupM zero [] c f = c
      lookupM zero (just x ∷ xs) c f = f x
      lookupM zero (nothing ∷ xs) c f = c
      lookupM (suc n) [] c f = c
      lookupM (suc n) (x ∷ xs) c f = lookupM n xs c f

  ϱ (prefix α (P , σ , _))
    = (app ∘ prefix α ∘ var ∘ inj₂ ∘ σ)
      , node ((α , var ∘ inj₂ ∘ σ) ∷ [])
  ϱ {X} {Y} (plus (P , σP , bP) (Q , σQ , bQ))
    = (λ x → app (plus (var (inj₂ (σP x))) (var (inj₂ (σQ x)))))
      , node (b (children bP) ++ b (children bQ))
    where
      b : List (Aτ × Sub X Y) → List (Aτ × Sub X (Σ* (X ⊎ Y)))
      b = Data.List.map (λ (α , σ) → α , var ∘ inj₂ ∘ σ)

  ϱ {X} {Y} (par (P , σP , bP) (Q , σQ , bQ))
    = (λ x → app (par (var (inj₂ (σP x))) (var (inj₂ (σQ x)))))
      , node (Data.List.map (λ (α , σ) → α , λ x → app (par (var (inj₂ (σ x))) (var (inj₂ (σQ x))))) (children bP)
              ++ Data.List.map (λ (α , σ) → α , λ x → app (par (var (inj₂ (σP x))) (var (inj₂ (σ x))))) (children bQ)
              ++ zipPWith ≈fst-dec (λ (_ , σ) (_ , σ') → τ , λ x → app (par (var (inj₂ (σ x))) (var (inj₂ (σ' x))))) (children bP) (children bQ))
    where
      _>>=_ : ∀ {A B : Set ℓ} → List A → (A → List B) → List B
      m >>= g = concatMap g m

      case : ∀ {A B C : Set ℓ} → {P : A → B → Set ℓ} → Decidable P → (f : A → B → C) → C → A → B → C
      case p f g x y with p x y
      ... | yes _ = f x y
      ... | no _ = g

      _≈fst_ : (Aτ × Sub X Y) → (Aτ × Sub X Y) → Set ℓ
      a ≈fst b = proj₁ a ≈ proj₁ b

      ≈fst-dec : Decidable _≈fst_
      ≈fst-dec (x , _) (y , _) = ≈-dec x y

      zipPWith : ∀ {A B C : Set ℓ} → {P : A → B → Set ℓ} → Decidable P → (f : A → B → C) → List A → List B → List C
      zipPWith p f xs ys = xs >>= λ x → ys >>= λ y → case p (λ a b → f a b ∷ []) [] x y

  ϱ (restr β (P , σP , bP))
    = (app ∘ restr β ∘ var ∘ inj₂ ∘ σP)
      , node (Data.List.map (λ (α , σ) → α , var ∘ inj₂ ∘ σ) (Data.List.filter (λ (α , _) → ≉-dec α (act β)) (children bP)))

  ϱ (ren φ (P , σP , bP))
    = (app ∘ ren φ ∘ var ∘ inj₂ ∘ σP)
      , node (Data.List.map (λ (α , σ) → (⟨ φ ⟩Aτ α) , λ x → var (inj₂ (σ x))) (children bP))
  ϱ (fix (P , σP , bP))
    = (λ x → app (fix (var (inj₂ (σP (nothing ∷ x))))))
      , node (Data.List.map (λ (α , σ) → α , λ x → var (inj₂ (σ (nothing ∷ x)))) (children bP))

  μΣ : Set ℓ
  μΣ = Σ ℕ λ n → Proc n

  ι : Sig μΣ → μΣ
  ι dead = 0 , ∅
  ι (name x) = suc x , # fromℕ x
  ι (prefix α (n , P)) = n , (α ∙ P)
  ι (plus (n , P) (m , Q)) = (n ⊔ m) , (max-cast n m P) + ≡-subst Proc (⊔-comm m n) (max-cast m n Q)
  ι (par (n , P) (m , Q) ) = n ⊔ m , (max-cast n m P) ∣ ≡-subst Proc (⊔-comm m n) (max-cast m n Q)
  ι (restr β (n , P)) = n , P ∖ β
  ι (ren φ (n , P)) = n , Syntax._[_] P φ
  ι (fix (n , P)) = n , fix (_↑ P)

  ini' : ∀ {X : Set ℓ} → ((Sig X) → X) → (n : ℕ) → Proc n → X
  ini' x n ∅ = x dead
  ini' x n (# y) = x (name n)
  ini' x n (α ∙ P) = x (prefix α (ini' x n P))
  ini' x n (P + Q) = x (plus (ini' x n P) (ini' x n Q))
  ini' x n (P ∣ Q) = x (par (ini' x n P) (ini' x n Q))
  ini' x n (P ∖ a) = x (restr a (ini' x n P))
  ini' x n (P [ φ ]) = x (ren φ (ini' x n P))
  ini' x n (fix P) = x (fix (ini' x (suc n) P))

  ini : ∀ {X : Set ℓ} → ((Sig X) → X) → μΣ → X
  ini x (n , P) = ini' x n P

  ιⁱ : μΣ → Sig μΣ
  ιⁱ (n , ∅) = dead
  ιⁱ (n , # x) = name (toℕ x)
  ιⁱ (n , α ∙ P) = prefix α (n , P)
  ιⁱ (n , P + Q) = plus (n , P) (n , Q)
  ιⁱ (n , P ∣ Q) = par (n , P) (n , Q)
  ιⁱ (n , (P ∖ a)) = restr a (n , P)
  ιⁱ (n , (P [ φ ])) = ren φ (n , P)
  ιⁱ (n , fix P) = fix (suc n , P)

  γ : μΣ → B μΣ μΣ
  γ = proj₂ ∘ ini {μΣ × B μΣ μΣ} (bar ∘ foo)
    where
      foo : Sig (μΣ × B μΣ μΣ) → μΣ × B μΣ (Σ* (μΣ ⊎ μΣ))
      foo = < ι ∘ (proj₁ <$>_) , ϱ {μΣ} {μΣ} >
        where open RawFunctor SigF

      bar : μΣ × B μΣ (Σ* (μΣ ⊎ μΣ)) → μΣ × B μΣ μΣ
      bar = Data.Product.map₂ (bmap₂ (lift ι id ∘ (∇ <$>_)))
        where open RawFunctor Σ*F
              open RawFunctor (BF μΣ) renaming (_<$>_ to bmap₂)
              ∇ : ∀ {X : Set ℓ} → X ⊎ X → X
              ∇ (inj₁ x) = x
              ∇ (inj₂ y) = y