\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}