\begin{code}
{-# OPTIONS --safe --experimental-lossy-unification #-}

open import Prelude renaming (tt to ⟨⟩)
open import Signatures
open import FinitenessConditions
open import FreeMonad.PackagedTheory

module Effects.State
  (S : Type)
  (finiteState : SplitQuotientedChoiceω S)
  (setState : isSet S)
  where

module StateFunctor where
  data Op : Type where
    `get  : Op
    `put  : S → Op

  Arity : Op → Type
  Arity `get      = S
  Arity (`put _)  = ⊤

open StateFunctor using (`get; `put) public

open StateFunctor

𝕊 : Signature
𝕊 = Op ◁ Arity

Sₘ : Type a → Type a
Sₘ A = S → A × S

open import FreeMonad.Theory hiding (Law; Theory)
import FreeMonad.Theory

Law : Signature → Type₁
Law 𝔽 = FreeMonad.Theory.Law 𝔽 0

Theory : Signature → Type₁
Theory 𝔽 = FreeMonad.Theory.Theory 𝔽 0

module Synt where
  open import FreeMonad.Syntax 𝕊 public hiding (Syntax)
  open import FreeMonad.Syntax public using (Syntax)
  open SyntaxBind public

  put : S → Syntax 𝕊 ⊤
  put s = Op⟦ `put s ⟧

  get : Syntax 𝕊 S
  get = Op⟦ `get ⟧

  toState : (S → A × S) → Syntax 𝕊 A
  toState f =
    do s ← get
       let y , s′ = f s
       put s′
       return y

  open FreeMonad.Theory.Law public
  open Equation public

  module PutPutLawDisplay where
\end{code}
%<*put-put-law-disp>
\begin{code}
    put-put-law : Law 𝕊
    put-put-law .Γ = S × S
    put-put-law .ν _ = ⊤
    put-put-law .eqn (s₁ , s₂) =
      (do put s₁ ; put s₂) ≐ (do put s₂)
\end{code}
%</put-put-law-disp>
\begin{code}
  data Laws : Type 
\end{code}
%<*laws>
\begin{code}
  data Laws where put-put put-get get-put : Laws
\end{code}
%</laws>
%<*eqns-sig>
\begin{code}
  Eqns : Laws → Law 𝕊
\end{code}
%</eqns-sig>
%<*put-get>
\begin{code}
  Eqns put-get .Γ  = S
  Eqns put-get .ν _ = S
\end{code}
%</put-get>
%<*put-put>
\begin{code}
  Eqns put-put .Γ = S × S
  Eqns put-put .ν _ = ⊤
\end{code}
%</put-put>
%<*get-put>
\begin{code}
  Eqns get-put .Γ  = ⊤
  Eqns get-put .ν _ = ⊤
\end{code}
%</get-put>
%<*eqns>
\begin{code}[number=state-laws]
  Eqns put-put  .eqn (s₁ , s₂)  = (do put s₁; put s₂)  ≐ (do put s₂)
  Eqns put-get  .eqn s          = (do put s; get)      ≐ (do put s; return s)
  Eqns get-put  .eqn _          = (do s ← get; put s)  ≐ (do return ⟨⟩)
\end{code}
%</eqns>
%<*state-alg>
\begin{code}[number=state-alg]
  state-alg : 𝕊 -Alg[ (S → A × S) ]
  state-alg (`get     , k) s₁ = k  s₁  s₁
  state-alg (`put s₂  , k) s₁ = k  ⟨⟩  s₂
\end{code}
%</state-alg>
\begin{code}
  runState : Syntax 𝕊 A → S → A × S
  runState = interp state-alg _,_

open Synt using (module PutPutLawDisplay; state-alg) public

StateTheory : Theory 𝕊
StateTheory = record { Synt }


module _ where
  open SplitQuotientedChoiceAt

  finiteOps : ∀ Oᵢ → SplitQuotientedChoiceω (Arity Oᵢ)
  finiteOps `get = finiteState
  finiteOps (`put x) = quotientedChoice⊤

  finiteVars : FiniteVars StateTheory
  finiteVars Synt.put-put _ = quotientedChoice⊤
  finiteVars Synt.get-put _ = quotientedChoice⊤
  finiteVars Synt.put-get _ = finiteState
  
𝕊-theory = full-theory 𝕊 StateTheory finiteOps finiteVars

open import FreeMonad.Quotiented 𝕊-theory renaming (Term to StateTerm) public

open Synt using (op; var)
Term = StateTerm

State : Type a → Type _
State = StateTerm

get : Term S
get = [ op (`get , var) ]

put : S → Term ⊤
put s = [ op (`put s , var) ]

put-put′ : ∀ s₁ s₂ → (do put s₁; put s₂) ≡ (do put s₂)
put-put′ s₁ s₂ = eq/ _ _ (lawₜ Synt.put-put (s₁ , s₂))

modify : (S → S) → Term ⊤
modify f = do s ← get; put (f s)

put-put : ∀ s₁ s₂ (k : Term A) → (do put s₁; put s₂; k) ≡ (do put s₂; k)
put-put s₁ s₂ k = cong (λ p → do p ; k) (put-put′ s₁ s₂)

\end{code}
%<*get-put>
\begin{code}
get-put :
  ∀ (k : State A)
  → (do s ← get
        put s
        k) ≡ k
\end{code}
%</get-put>
\begin{code}
get-put k = cong (_>> k) (eq/ _ _ (lawₜ Synt.get-put _))

put-get : ∀ s (k : S → State A) → (do put s; s′ ← get; k s′) ≡ (do put s; k s)
put-get s k = cong (λ p → do r ← p; k r) (eq/ _ _ (lawₜ Synt.put-get s))

get-put-get : (k : S → State A) → (do s ← get; put s; k s) ≡ (do s ← get; k s)
get-put-get k =   (do s ← get
                      put s
                      k s)

                                      ≡⟨⟩
                 
                  (do s ← get
                      put s
                      s′ ← return s
                      k s′)

                                      ≡˘⟨ s ⇐ get ;/ put-get s k ⟩
                 
                  (do s ← get
                      put s
                      s′ ← get
                      k s′)

                                      ≡⟨ get-put (get >>= k) ⟩
                 
                  (do s ← get
                      k s) ∎

get-get : ∀  (k : S → S → State B) →
             (do s₁ ← get
                 s₂ ← get
                 k s₁ s₂)  ≡ (do s ← get
                                 k s s)

get-get k =

  (do s₁ ← get
      s₂ ← get
      k s₁ s₂)          ≡˘⟨ get-put _ ⟩

  (do s ← get
      put s
      s₁ ← get
      s₂ ← get
      k s₁ s₂)          ≡⟨ s ⇐ get ;/ put-get s _ ⟩

  (do s₁ ← get
      put s₁
      s₂ ← get
      k s₁ s₂)          ≡⟨ s₁ ⇐ get ;/ put-get s₁ _ ⟩

  (do s ← get
      put s
      k s s)            ≡⟨ get-put-get _ ⟩

  (do s ← get
      k s s) ∎

get-nop : (k : State A) → (get >> k) ≡ k
get-nop k =

  (do get
      k)                  ≡˘⟨ get-put-get (λ _ → k) ⟩

  (do s ← get
      put s
      k)                  ≡⟨ get-put k ⟩

  k ∎

\end{code}
%<*toState>
\begin{code}
toState : (S → A × S) → Term A
toState k = do s₁ ← get
               let x , s₂ = k s₁
               put s₂
               return x
\end{code}
%</toState>
\begin{code}
open import Data.Vec

toState-synt : (f : S → A × S) → toState f ≡ [ Synt.toState f ]
toState-synt f =
  sym (syntactic-bind _ Synt.get ; cong (get >>=_) (funExt (λ s → syntactic-bind (λ _ → Synt.return (f s .fst)) (Synt.put (f s .snd)))))

module _ (truncCarrier : isSet A) where
  module _ where
    open Synt using (put-put; put-get; get-put)

    runState-resp : (state-alg {A = A}) Models StateTheory
    runState-resp put-put t k = refl
    runState-resp put-get t k = refl
    runState-resp get-put t k = refl
\end{code}
%<*runState>
\begin{code}
  runState : Term A → (S → A × S)
  runState = interpₜ  state-alg
                      (λ x s → x , s)
                      runState-resp
                      ⋯
\end{code}
%</runState>
\begin{code}
    where ⋯ = isSetΠ λ _ → isSet× truncCarrier setState

  rinv : ∀ p → runState (toState p) ≡ p
  linv : ∀ p → toState (runState p) ≡ p

  rinv f = cong runState (toState-synt f)

  linv =
    elimProp/
      (λ _ → squash/ _ _)
      (Synt.elim-s (λ xs → toState (Synt.runState xs) ≡ [ xs ]) alg)
    where
    open Synt renaming (var to var) using (DepAlg; op)

    alg : Ψ[ xs ⦂ Syntax A ] (toState (Synt.runState xs) ≡ [ xs ])

    alg (var x) = get-put (return x)

    alg (op (`get , xs) r) =

      (do s ← get
          let y , s′ = Synt.runState (op (`get , xs)) s
          put s′
          return y)                                         ≡⟨⟩

      (do s ← get
          let y , s′ = Synt.runState (xs s) s
          put s′
          return y)                                         ≡˘⟨ get-get _ ⟩

      (do s₁ ← get
          s₂ ← get
          let y , s₃ = Synt.runState (xs s₁) s₂
          put s₃
          return y)                                         ≡⟨ s ⇐ get ;/ r s ⟩

      (do s ← get
          [ xs s ])                                         ≡˘⟨ syntactic-bind xs Synt.get ⟩

      [ op (`get , xs) ] ∎

    alg (op (`put x , xs) r) =

      toState (Synt.runState (op (`put x , xs)))                      ≡⟨⟩

      (do s₁ ← get
          let y , s₂ = Synt.runState (op (`put x , xs)) s₁
          put s₂
          return y)                                                   ≡⟨⟩

      (do s₁ ← get
          let y , s₂ = Synt.runState (xs ⟨⟩) x
          put s₂
          return y)                                                   ≡⟨ get-nop _ ⟩

      (do let y , s₂ = Synt.runState (xs ⟨⟩) x
          put s₂
          return y)                                                   ≡˘⟨ put-put _ _ (return (fst (Synt.runState (xs ⟨⟩) x))) ⟩

      (do put x
          let y , s₂ = Synt.runState (xs ⟨⟩) x
          put s₂
          return y)                                                   ≡˘⟨ put-get _ _ ⟩

      (do put x
          s₁ ← get
          let y , s₂ = Synt.runState (xs ⟨⟩) s₁
          put s₂
          return y)                                                   ≡⟨ cong (put x >>_) (r ⟨⟩) ⟩

      (do put x
          [ xs ⟨⟩ ])                                                   ≡⟨⟩

      [ op (`put x , xs) ] ∎
      
  state-iso : State A ⇔ (S → A × S)
  state-iso .fun = runState
  state-iso .inv = toState
  state-iso .rightInv = rinv
  state-iso .leftInv = linv

open import Truth hiding (Ω)
open import Truth.MonoLevel 0
open import Truth.MonoLevel 1 using () renaming (Ω to Ω₁) 

\end{code}