module DynamicallyChecked.Case (S V : Set) where

open import DynamicallyChecked.Partiality
open import DynamicallyChecked.Utilities
open import DynamicallyChecked.Lens

open import Agda.Primitive
open import Function
open import Data.Product
open import Data.Bool
open import Data.List
open import Relation.Binary.PropositionalEquality


data BranchType : Set₁ where
  normal   : (S  V)  (S  Bool)  BranchType
  adaptive : (S  V  S)  BranchType

elimBranchType : {l : Level} {A : Set l}  ((S  V)  (S  Bool)  A)  ((S  V  S)  A)  BranchType  A
elimBranchType g h (normal l q) = g l q
elimBranchType g h (adaptive f) = h f

isNormal : BranchType  Bool
isNormal = elimBranchType  _ _  true)  _  false)

Branch : Set₁
Branch = (S  V  Bool) × BranchType

check-diversion : List Branch  S  V  Par 
check-diversion []                      s v = return tt
check-diversion ((p , normal l q)  bs) s v = assert-not p s v then assert-not q s then check-diversion bs s v
check-diversion ((p , adaptive f)  bs) s v = assert-not p s v then check-diversion bs s v

put-with-adaptation : List Branch  List Branch  S  V  (S  Par S)  Par S
put-with-adaptation []             bs' s v cont = fail
put-with-adaptation ((p , b)  bs) bs' s v cont = 
  if p s v then elimBranchType  l q  Lens.put l s v >>= λ s' 
                                        assert p s' v then assert q s' then
                                        check-diversion bs' s' v >> return s')
                                f  cont (f s v)) b
           else put-with-adaptation bs ((p , b)  bs') s v cont

put : List Branch  S  V  Par S
put bs s v = put-with-adaptation bs [] s v  s'  put-with-adaptation bs [] s' v (const fail))

fail-continuation : (bs bs' : List Branch) {s' s : S} {v : V}  put-with-adaptation bs bs' s v (const fail)  s' 
                    {cont : S  Par S}  put-with-adaptation bs bs' s v cont  s'
fail-continuation []                      bs'             () 
fail-continuation ((p , b)  bs)          bs' {s = s} {v} c  with p s v
fail-continuation ((p , b)  bs)          bs'             c  | false = fail-continuation bs ((p , b)  bs') c
fail-continuation ((p , normal l q)  bs) bs'             c  | true  = c
fail-continuation ((p , adaptive f)  bs) bs'             () | true

get : List Branch  S  Par V
get []             s = fail
get ((p , normal l q)  bs) s = if q s then (Lens.get l s >>= λ v  assert p s v then return v)
                                       else (get bs s >>= λ v  assert-not p s v then return v)
get ((p , adaptive f)  bs) s = get bs s >>= λ v  assert-not p s v then return v

get-revcat : (bs : List Branch) {s : S} {v : V} (bs' : List Branch)  check-diversion bs' s v  tt 
             get bs s  v  get (revcat bs' bs) s  v
get-revcat bs []              _                                         get↦ = get↦
get-revcat bs ((p , normal l q)  bs') (assert-not p-s-v≡false then assert-not q-s≡false then check-diversion↦) get↦
  with get-revcat ((p , normal l q)  bs) bs' check-diversion↦
get-revcat bs ((p , normal l q)  bs') (assert-not p-s-v≡false then assert-not q-s≡false then check-diversion↦) get↦
  | k rewrite q-s≡false = k (get↦ >>= assert-not p-s-v≡false then return refl)
get-revcat bs ((p , adaptive f)  bs') (assert-not p-s-v≡false then check-diversion↦) get↦ =
  get-revcat ((p , adaptive f)  bs) bs' check-diversion↦ (get↦ >>= assert-not p-s-v≡false then return refl)

PutGet-with-adaptation :
  (bs : List Branch) {s : S} {v : V} {s' : S} (bs' : List Branch) 
  {cont : S  Par S}  ({s : S}  cont s  s'  get (revcat bs' bs) s'  v) 
  put-with-adaptation bs bs' s v cont  s'  get (revcat bs' bs) s'  v
PutGet-with-adaptation []                              bs' PutGet-cont ()
PutGet-with-adaptation ((p , b)  bs) {s} {v} bs' PutGet-cont put↦ with p s v
PutGet-with-adaptation ((p , b)  bs) bs' PutGet-cont put↦ | false =
  PutGet-with-adaptation bs ((p , b)  bs') PutGet-cont put↦
PutGet-with-adaptation ((p , normal l q)  bs) bs' PutGet-cont
  (put↦ >>= assert p-s'-v≡true then assert q-s'≡true then check-diversion↦ >>= return refl) | true
  with get-revcat ((p , normal l q)  bs) bs' check-diversion↦
PutGet-with-adaptation ((p , normal l q)  bs) {s' = s'} bs' PutGet-cont
  (put↦ >>= assert p-s'-v≡true then assert q-s'≡true then check-diversion↦ >>= return refl) | true | k rewrite q-s'≡true =
  k (Lens.PutGet l put↦ >>= assert p-s'-v≡true then return refl)
PutGet-with-adaptation ((p , adaptive f)  bs) bs' PutGet-cont put↦ | true  = PutGet-cont put↦

PutGet : (bs : List Branch) {s s' : S} {v : V}  put bs s v  s'  get bs s'  v
PutGet bs put↦ = PutGet-with-adaptation bs [] (PutGet-with-adaptation bs []  ())) put↦

GetPut-with-adaptation :
  (bs : List Branch) {s : S} {v : V} {f : S  Par S}
  (bs' : List Branch)  check-diversion bs' s v  tt 
  get bs s  v  put-with-adaptation bs bs' s v f  s
GetPut-with-adaptation []                     bs' check-diversion↦ ()
GetPut-with-adaptation ((p , b)  bs) {s} {v} bs' check-diversion↦ get↦ with p s v | inspect (p s) v
GetPut-with-adaptation ((p , normal l q)  bs) {s} bs' check-diversion↦ get↦
  | false | [ p-s-v≡false ] with q s | inspect q s
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (get↦ >>= assert-not _ then return refl)
  | false | [ p-s-v≡false ] | false | [ q-s≡false ] =
  GetPut-with-adaptation bs ((p , normal l q)  bs')
    (assert-not p-s-v≡false then assert-not q-s≡false then check-diversion↦) get↦
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (_ >>= assert p-s-v≡true then return refl)
  | false | [ p-s-v≡false ] | true | _ with trans (sym p-s-v≡true) p-s-v≡false
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (_ >>= assert p-s-v≡true then return refl)
  | false | [ p-s-v≡false ] | true | _ | ()
GetPut-with-adaptation ((p , adaptive f)  bs) bs' check-diversion↦ (get↦ >>= assert-not _ then return refl)
  | false | [ p-s-v≡false ] =
  GetPut-with-adaptation bs ((p , adaptive f)  bs') (assert-not p-s-v≡false then check-diversion↦) get↦
GetPut-with-adaptation ((p , normal l q)  bs) {s} bs' check-diversion↦ get↦
  | true | [ p-s-v≡true ] with q s | inspect q s
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (get↦ >>= assert-not p-s-v≡false then return refl)
  | true | [ p-s-v≡true ] | false | [ q-s≡false ] with trans (sym p-s-v≡false) p-s-v≡true
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (get↦ >>= assert-not p-s-v≡false then return refl)
  | true | [ p-s-v≡true ] | false | [ q-s≡false ] | ()
GetPut-with-adaptation ((p , normal l q)  bs) bs' check-diversion↦ (get↦ >>= assert _ then return refl)
  | true | [ p-s-v≡true ] | true | [ q-s≡true ] =
  Lens.GetPut l get↦ >>= assert p-s-v≡true then assert q-s≡true then check-diversion↦ >>= return refl
GetPut-with-adaptation ((p , adaptive f)  bs) bs' check-diversion↦ (_ >>= assert-not p-s-v≡false then return refl)
  | true | [ p-s-v≡true ] with trans (sym p-s-v≡false) p-s-v≡true
GetPut-with-adaptation ((p , adaptive f)  bs) bs' check-diversion↦ (_ >>= assert-not p-s-v≡false then return refl)
  | true | [ p-s-v≡true ] | ()

GetPut : (bs : List Branch) {s : S} {v : V} {f : S  Par S}  get bs s  v  put bs s v  s
GetPut bs = GetPut-with-adaptation bs [] (return refl)

case-lens : List Branch  S  V
case-lens bs = record { put = put bs; get = get bs; PutGet = PutGet bs; GetPut = GetPut bs {f = const fail} }