{-# OPTIONS --safe #-}

open import Signatures
open import Prelude

module FreeMonad.Syntax.AsSignature (𝔽 : Signature) where

open import FreeMonad.Syntax 𝔽
open Signature 𝔽

Position : Syntax A → Type
Position (var _)        = ⊤
Position (op (Oᵢ , k))  = Σ[ i ⦂ Arity Oᵢ ] × Position (k i)

★ : Signature
★ = Syntax ⊤ ◁ Position

open SyntaxBind using (_<$>_; _>>=_)

shape : Syntax A → Syntax ⊤
shape t = const tt <$> t

Index : Syntax A → Type
Index = Position ∘ shape

lookup : (s : Syntax A) → Index s → A
lookup (var x)       _         = x
lookup (op (O , k))  (i , is)  = lookup (k i) is

fill : (s : Syntax A) → (Position s → B) → Syntax B
fill (var x)       i = var (i tt)
fill (op (O , k))  i = op (O , λ j → fill (k j) (i ∘ _,_ j))

⟦op⟧ : ⟦ 𝔽 ⟧ (⟦ ★ ⟧ A) → ⟦ ★ ⟧ A
⟦op⟧ (S , P) = op (S , fst ∘ P) , uncurry (snd ∘′ P)

Indexed : Syntax A ⇔ ⟦ ★ ⟧ A
Indexed .fun s = shape s , lookup s
Indexed .inv = uncurry fill
Indexed .leftInv =
  elim-s _ λ { (var x) → refl ; (op (O , k) P⟨xs⟩) → cong (op ∘ _,_ O) (funExt P⟨xs⟩) }
Indexed .rightInv = uncurry (elim-s _ λ { (var x) P → refl ; (op (O , k) P⟨xs⟩) P → cong (⟦op⟧ ∘ _,_ O) (funExt λ i → P⟨xs⟩ i _) })

<$>⇔cmap : (f : A → B) (s : Syntax A) → f <$> s ≡ Indexed .inv (cmap f (Indexed .fun s))
<$>⇔cmap f = elim-s _ λ { (var x) → refl ; (op (O , k) P⟨xs⟩) → cong (op ∘ _,_ O) (funExt P⟨xs⟩) }

<$>-com-inv : (f : A → B) (s : ⟦ ★ ⟧ A) → f <$> Indexed .inv s ≡ Indexed .inv (cmap f s)
<$>-com-inv f s = <$>⇔cmap f (Indexed .inv s) ; cong (Indexed .inv ∘ cmap f) (Indexed .rightInv s)

module _ (f : A → B) (s : Syntax A) where
  <$>-com-fun : cmap f (Indexed .fun s) ≡ Indexed .fun (f <$> s)
  <$>-com-fun = sym (cong (Indexed .fun) (<$>⇔cmap f s) ; Indexed .rightInv (cmap f (Indexed .fun s)))

  <$>-same-shape :  shape s ≡ shape (f <$> s)
  <$>-same-shape = PathPΣ <$>-com-fun .fst

  <$>-lookup : ∀ i → lookup (f <$> s) i ≡ f (lookup s (subst Position (sym <$>-same-shape) i))
  <$>-lookup i = sym (cong (_$ i) (fromPathP (PathPΣ <$>-com-fun .snd))) ; transportRefl _

open import Isomorphism.Properties

infixr 5 _∈_ _∉_
_∈_ : A → Syntax A → Type _
x ∈ xs = Fibre (lookup xs) x

_∉_ : A → Syntax A → Type _
x ∉ xs = ¬ (x ∈ xs)

open import Data.Sigma.Properties

module _ (disc : ∀ Oᵢ → Discrete (Signature.Arity 𝔽 Oᵢ)) where
  disc-index : (s : Syntax A) → Discrete (Index s)
  disc-index =
    elim-s _ λ { (var v) _ _ → yes refl
               ; (op (O , k) P⟨xs⟩) → discreteΣ (disc O) P⟨xs⟩ }

open SyntaxBind

>>=-ind : Syntax A → (A → Syntax B) → Type
>>=-ind xs k = Index (xs >>= k)

>>=∙-ind : Syntax A → (A → Syntax B) → Type
>>=∙-ind xs k = Σ[ i ⦂ Index xs ] × Index (k (lookup xs i))

lookup∙ : (xs : Syntax A) (k : A → Syntax B) → >>=∙-ind xs k → B
lookup∙ xs k = uncurry (lookup ∘′ k ∘′ lookup xs)

module _ (k : A → Syntax B) where
  pull-shape : (s : Syntax A) → >>=-ind s k → >>=∙-ind s k
  pull-shape = elim-s _ λ { (var x) i → tt , i ; (op (O , k) P⟨xs⟩) (i , is) → let j , js = P⟨xs⟩ i is in (i , j) , js }

  push-shape : (s : Syntax A) → >>=∙-ind s k → >>=-ind s k
  push-shape = elim-s _ λ { (var x) (i , is) → is ; (op (O , k) P⟨xs⟩) ((i , is) , js) → i , P⟨xs⟩ i (is , js) }

  push∘pull : (s : Syntax A) (i : >>=-ind s k) → push-shape s (pull-shape s i) ≡ i
  push∘pull = elim-s _ λ { (var x) _ → refl ; (op (O , k) P⟨xs⟩) (i , is) → cong (i ,_) (P⟨xs⟩ i is) }

  pull∘push : (s : Syntax A) (i : >>=∙-ind s k) → pull-shape s (push-shape s i) ≡ i
  pull∘push =
    elim-s _ λ { (var x) _ → refl
               ; (op (O , k) P⟨xs⟩) ((i , is) , js) p →  let (j′ , js′) = P⟨xs⟩ i (is , js) p in ((i , j′) , js′) }

  lookup-push : (s : Syntax A) (i : Index s) (is : Index (k (lookup s i))) → lookup (k (lookup s i)) is ≡ lookup (s >>= k) (push-shape s (i , is))
  lookup-push = elim-s _ λ { (var x) i is → refl
                           ; (op (O , k) P⟨xs⟩) (i , is′) is → P⟨xs⟩ i is′ is
                           }

  lookup-pull : (s : Syntax A) (i : Index (s >>= k)) → uncurry (lookup ∘′ k ∘′ lookup s) (pull-shape s i) ≡ lookup (s >>= k) i
  lookup-pull = elim-s _ λ { (var x) i → refl
                           ; (op (O , k) P⟨xs⟩) (i , is′) → P⟨xs⟩ i is′
                           }

>>=⟦⟧ :  (k : A → Syntax B) (f : ∀ x → Index (k x) → C) (xs : Syntax A)
      → Indexed .inv (shape (xs >>= k) , uncurry (f ∘′ lookup xs) ∘ pull-shape k xs) ≡ xs >>= fill _ ∘′ f
>>=⟦⟧ k f = elim-s _
  λ { (var x) → refl ; (op (O , _) P⟨xs⟩) → cong (op ∘ _,_ O) (funExt P⟨xs⟩) }