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

open import Prelude renaming (tt to ⟨⟩)
open import Signatures
open import FinitenessConditions
open import FreeMonad.PackagedTheory
open import Data.String.Properties renaming (discreteChar to _≟_)

module Effects.Parser.HElim
  (choiceString : SplitQuotientedChoiceω String)
  where

open import Effects.Parser choiceString
open import Effects.Parser.TreeParser choiceString

open import Hoare.Definition ℙ-theory
open import Hoare.Calculus ℙ-theory
open import Hoare.Lemmas ℙ-theory

open Signature (FullTheory.𝔽 ℙ-theory)

open import Truth

push-remaining : ∀ s → ⟅⟆ r ← (do r ← get; put (s ++ r); return r) ⟅ remaining (s ++ r) ⟆
push-remaining s =
  (λ r → put-remaining (s ++ r)) ⦂
    (∀ r → ⟅⟆ _ ← put (s ++ r) ⟅ remaining (s ++ r) ⟆ )
      ⇒⟨ seq ⟨return True ⟩ (const ⟨return True ⟩) (λ { (r , _) → ⟨remaining⟩ (s ++ r) }) (⟅→⟆True (return True) get) ⟩
    ⟅⟆ (r , _) ← (get ;, (λ r → put (s ++ r))) ⟅ remaining (s ++ r) ⟆
      ⇒⟨ focus fst ⟩
    ⟅⟆ r ← fst <$> (get ;, (λ r → put (s ++ r))) ⟅ remaining (s ++ r) ⟆
      ⇒⟨ subst-⟅⟆ (assoc get _ _) ⟩
    ⟅⟆ r ← (do r ← get; put (s ++ r); return r) ⟅ remaining (s ++ r) ⟆

remaining-get : ∀ r → ⟅ remaining r ⟆ r′ ← get ⟅ return (r |≡| r′) ⟆
remaining-get r .proof k =

  (do a ← remaining r
      r′ ← get
      k r′ (a |→| (r |≡| r′))) ≡⟨ assoc get _ _ ⟩

  (do r″ ← get
      r′ ← get
      k r′ ((r |≡| r″) |→| (r |≡| r′))) ≡⟨ get-get _ ⟩

  (do r′ ← get
      k r′ ((r |≡| r′) |→| (r |≡| r′))) ≡⟨ r′ ⇐ get ;/ cong (k r′) (|→|-id _) ⟩

  (do r′ ← get
      k r′ True) ≡˘⟨ get-nop (do r′ ← get; k r′ True) ⟩

  (do _ ← get
      r′ ← get
      k r′ True) ≡˘⟨ assoc get _ _ ⟩

  (do a ← remaining r
      r′ ← get
      k r′ True) ∎

parse-put-print : ∀ s (p : Term A) v
                → s ∈ p ↦ v
                → ⟅⟆ ((r , v′) , r′) ← (do r ← get; put (s ++ r); v′ ← p; r′ ← get; return ((r , v′) , r′)) ⟅ return ((r |≡| r′) |∧| (v |≡| v′)) ⟆
parse-put-print s p v s∈p↦v =
  s∈p↦v ⦂
    s ∈ p ↦ v
      ⇒⟨⟩
    (∀ r → ⟅ remaining (s ++ r) ⟆ v′ ← p ⟅ return (v |≡| v′) ⟨∧⟩ remaining r ⟆)
      ⇒⟨ seq ⟨return True ⟩
             (λ r → ⟨remaining⟩ (s ++ r))
             (λ { (r , v′) → return (v |≡| v′) ⟨∧⟩ remaining r , sef-<$> _ _ (sef-rem r) , det-<$> _ _ (det-rem r) } )
             {p = (do r ← get; put (s ++ r); return r)}
             {q = const p}
             (push-remaining s) ⟩
    ⟅⟆ (r , v′) ← ((do r ← get; put (s ++ r); return r) ⟪,⟫ p) ⟅ return (v |≡| v′) ⟨∧⟩ remaining r ⟆
      ⇒⟨ subst-⟅⟆ (assoc get _ _) ⟩
    ⟅⟆ (r , v′) ← (do r ← get; put (s ++ r); v′ ← p; return (r , v′)) ⟅ return (v |≡| v′) ⟨∧⟩ remaining r ⟆
      ⇒⟨ ⟅∙⟆≡ (λ { (r , v′) → assoc get _ _ ; (r′ ⇐ get ;/ cong return (∧-com (v |≡| v′) (r |≡| r′)) ) ; sym (assoc get _ _) }) ⟩
    ⟅⟆ (r , v′) ← (do r ← get; put (s ++ r); v′ ← p; return (r , v′)) ⟅ remaining r ⟨∧⟩ return (v |≡| v′) ⟆
      ⇒⟨ flip (seq ⟨return True ⟩ (λ { (r , v′) → remaining r ⟨∧⟩ return (v |≡| v′) ,  sef-<$> _ _ (sef-rem r) , det-<$> _ _ (det-rem r) }) λ { ((r , v′) , r′) → ⟨return ((r |≡| r′) |∧| (v |≡| v′)) ⟩ })
           ( λ { (r , v′) → ⟨&&&⟩ʳ (remaining r) (λ r′ → return (r |≡| r′)) (v |≡| v′) get (remaining-get r) }) ⟩
    ⟅⟆ ((r , v′) , r′) ← (do r ← get; put (s ++ r); v′ ← p; return (r , v′)) ⟪,⟫ get ⟅ return ((r |≡| r′) |∧| (v |≡| v′)) ⟆
      ⇒⟨ subst-⟅⟆ (assoc get _ _ ; r ⇐ get ;/ assoc (put (s ++ r)) (const (p >>= λ v′ → return (r , v′))) _ ;  _ ⇐ put (s ++ r) ;/ assoc p (λ v′ → return (r , v′)) _) ⟩
    ⟅⟆ ((r , v′) , r′) ← (do r ← get; put (s ++ r); v′ ← p; r′ ← get; return ((r , v′) , r′)) ⟅ return ((r |≡| r′) |∧| (v |≡| v′)) ⟆


open import FreeMonad.FindVars
open import Finite

open import Data.String.Properties

open import FreeMonad.Modalities ℙ-theory
open import FreeMonad.Syntax (FullTheory.𝔽 ℙ-theory)
open import Data.Tree

import Effects.NonDet as NDM
open NDM.NonDet using (`<|>; `fail)

import Effects.State as STM
open STM.StateFunctor using (`get; `put)

module _ {A : Type a} where
  Totalₛ : Syntax A → Type
  Totalₛ (op (inl `fail , _)) = ⊥
  Totalₛ _ = ⊤

  Total : Term A → Type a
  Total = □ₚ Totalₛ

  Total-nop : ∀ r t → Total t → (do t; put r) ≡ put r
  Total-nop r t (s , e , p) =
      cong (_>> put r) (sym e)
    ; elim-s (λ s → (p : □ₛₚ Totalₛ s) → [ s ] >> put r ≡ put r)
      (λ { (var x) _ → refl
         ; (op (inl `<|> , k) P⟨xs⟩) (p , ps) → cong₂ _<|>_ (P⟨xs⟩ false (ps false)) (P⟨xs⟩ true (ps true)) ; <|>-idem (put r)
         ; (op (inr (`put s) , k) P⟨xs⟩) (_ , ps) →
           [ op (inr (`put s) , k) ] >> put r ≡⟨ cong (_>> put r) (syntactic-bind k Op⟦ inr (`put s) ⟧) ⟩
           (do put s; [ k ⟨⟩ ]) >> put r ≡⟨ assoc (put s) (λ _ → [ k ⟨⟩  ]) (λ _ → put r) ⟩
           (do put s; [ k ⟨⟩ ]; put r) ≡⟨ _ ⇐ (put s) ;/ P⟨xs⟩ ⟨⟩ (ps ⟨⟩) ⟩
           (do put s; put r) ≡⟨ put-put s r (return ⟨⟩) ⟩
           put r ∎
           
         ; (op (inr `get , k) P⟨xs⟩) (_ , ps) → 
           [ op (inr `get , k) ] >> put r ≡⟨ cong (_>> put r) (syntactic-bind k Op⟦ inr `get ⟧) ⟩
           (do s ← get; [ k s ]) >> put r ≡⟨ assoc get _ _ ⟩
           (do s ← get; [ k s ]; put r) ≡⟨ s ⇐ get ;/ P⟨xs⟩ s (ps s) ⟩
           (do s ← get; put r) ≡⟨ get-nop (put r) ⟩
           put r ∎
         })
      s p 

module _ (fv : FindVars (FullTheory.𝒯 ℙ-theory)) (fa : ∀ O → ℰ (Arity O)) where
  open import Hoare.HElim ℙ-theory fv fa 

  parse-equality : ∀ t n →
    (do s ← get; put (print t ++ s); t′ ← parse-tree n; s′ ← get; put s′; return t′)
      ≡
    (do s ← get; put (print t ++ s); _  ← parse-tree n; s′ ← get; put s ; return t)
  parse-equality t n =
    let h = H-elim (λ { ((r , v′) , r′) → (r |≡| r′) |∧| (t |≡| v′) })
                   (λ { ((r , v′ ) , r′) → put r′ >> return v′ })
                   (λ { ((r , v′) , r′) → put r >> return t })
                   (λ { ((r , v′) , r′) (r≡r′ , t≡v′) → cong₂ (λ r v → put r >> return v) (∥rec∥ (isOfHLevelList 0 isSetChar _ _) sym r≡r′) (∥rec∥ (Discrete→isSet tree-eq _ _) sym t≡v′) })
                   _
                   (parse-put-print (print t) (parse-tree n) t (round-trip n t))
    in sym ( assoc get _ _
           ; r ⇐ get ;/ assoc (put (print t ++ r)) _ _
           ; _ ⇐ put (print t ++ r) ;/ assoc (parse-tree n) _ _
           ; v′ ⇐ parse-tree n ;/ assoc get _ _)
     ; h
     ; assoc get _ _
     ; r ⇐ get ;/ assoc (put (print t ++ r)) _ _
     ; _ ⇐ put (print t ++ r) ;/ assoc (parse-tree n) _ _
     ; v′ ⇐ parse-tree n ;/ assoc get _ _

  push-print-lhs : ∀ t n →
    (do s ← get; put (print t ++ s); t′ ← parse-tree n; s′ ← get; put s′; return t′)
      ≡
    (do s ← get; put (print t ++ s); parse-tree n)
  push-print-lhs t n = s ⇐ get ;/ _ ⇐ put (print t ++ s) ;/  ((t′ ⇐ parse-tree n ;/ get-put (return t′))) ; >>=-idʳ (parse-tree n)

  push-print-rhs : ∀ t n → Total (do push (print t); parse-tree n) →
    (do s ← get; put (print t ++ s); _  ← parse-tree n; s′ ← get; put s ; return t)
      ≡
    return t
  push-print-rhs t n tot =

    (do s ← get
        put (print t ++ s)
        _ ← parse-tree n
        s′ ← get
        put s
        return t) ≡⟨ s ⇐ get ;/ _ ⇐ put (print t ++ s) ;/ _ ⇐ parse-tree n ;/ get-nop (put s >> return t) ⟩

    (do s ← get
        put (print t ++ s)
        _ ← parse-tree n
        put s
        return t) ≡˘⟨ get-get _ ⟩

    (do s ← get
        s′ ← get
        put (print t ++ s′)
        _ ← parse-tree n
        put s
        return t) ≡˘⟨ s ⇐ get ;/ assoc (push (print t) >> parse-tree n) _ _ ; assoc (push (print t)) (const (parse-tree n)) (const (put s >> return t)) ; assoc get _ _ ⟩

    (do s ← get
        ((push (print t) >> parse-tree n) >> put s)
        return t) ≡⟨ s ⇐ get ;/  cong (_>> return t) (Total-nop s (push (print t) >> parse-tree n) tot )  ⟩

    (do s ← get
        put s
        return t) ≡⟨ get-put (return t) ⟩

    return t ∎
    
  push-print-parse :
    ∀ t n
    → Total (do push (print t); parse-tree n)
    → (do push (print t); parse-tree n) ≡ return t
  push-print-parse t n tot = assoc get _ _
                           ; sym (push-print-lhs t n)
                           ; parse-equality t n
                           ; push-print-rhs t n tot
\end{code}