open import Cubical.Foundations.Prelude hiding (empty)
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Univalence hiding (Glue; glue)

open import Cubical.Data.List
open import Cubical.Data.Sigma hiding (empty)

module Queue.Quotient
  (ABS : Type) (ABS-isProp : isProp ABS)
  (E : Type) (e₀ : E) (ESet : isSet E) where

open import Modality.Abstract ABS ABS-isProp
open import Queue.Base ABS ABS-isProp E e₀ ESet

-- Ideas adapted from "Internalizing Representation Independence with Univalence" to a phased setting.

private
  revAppend : List E × List E  List E
  revAppend (l₁ , l₂) = l₂ ++ rev l₁

data Batch (E : Type) : Type where
  inj : List E × List E  Batch E
  tilt :  (∀ e l₁ l₂  inj (l₁ ++ [ e ] , l₂)  inj (l₁ , l₂ ++ [ e ]))
  trunc : (b b' : Batch E) (α β : b  b')  α  β

multitilt : (l₁ l₂ l₃ : List E)   (inj (l₁ ++ rev l₃ , l₂)  inj (l₁ , l₂ ++ l₃))
multitilt l₁ l₂ [] abs = cong inj (cong₂ _,_ (++-unit-r l₁) (sym (++-unit-r l₂)))
multitilt l₁ l₂ (e  l₃) abs =
  cong  ws  inj (ws , l₂)) (sym (++-assoc l₁ (rev l₃) (e  [])))
   tilt abs e (l₁ ++ rev l₃) l₂
   multitilt l₁ (l₂ ++ [ e ]) l₃ abs
   cong  l  inj (l₁ , l)) (++-assoc l₂ [ e ] l₃)

batchedPreQueue : PreQueue
batchedPreQueue .X = Batch E
batchedPreQueue .empty = inj ([] , [])
batchedPreQueue .enqueue = b-enqueue
  where
    b-enqueue : E  Batch E  Batch E
    b-enqueue e (inj (l₁ , l₂)) = inj (e  l₁ , l₂)
    b-enqueue e (tilt abs e' l₁ l₂ i) = tilt abs e' (e  l₁) l₂ i
    b-enqueue e (trunc b b' α β i j) = trunc _ _ (cong (b-enqueue e) α) (cong (b-enqueue e) β) i j

batchedPreQueue .dequeue = b-dequeue
  where
    dequeueFlush : List E  E × Batch E
    dequeueFlush [] = e₀ , inj ([] , [])
    dequeueFlush (e  es) = e , inj ([] , es)

    b-dequeue : Batch E  E × Batch E
    b-dequeue (inj (l₁ , [])) = dequeueFlush (rev l₁)
    b-dequeue (inj (l₁ , e  l₂)) = e , inj (l₁ , l₂)
    b-dequeue (tilt abs e l₁ [] i) = dequeueP i
      where
        dequeueP : dequeueFlush (rev (l₁ ++ [ e ]))  (e , inj (l₁ , []))
        dequeueP =
          cong dequeueFlush (rev-++ l₁ [ e ])
           cong₂ {B = λ _  Batch E} {x = e} _,_ refl (sym (multitilt [] [] (rev l₁) abs))
           cong  l  (e , inj (l , []))) (rev-rev l₁)
    b-dequeue (tilt abs e' l₁ (e  l₂) i) = e , tilt abs e' l₁ l₂ i
    b-dequeue (trunc b b' α β i j) =
      isSetΣ ESet  _  trunc)
      (b-dequeue b) (b-dequeue b') (cong b-dequeue α) (cong b-dequeue β)
      i j

quot : List E  Batch E
quot l = inj ([] , l)

eval : Batch E  List E
eval (inj (l₁ , l₂)) = revAppend (l₁ , l₂)
eval (tilt abs e l₁ l₂ i) =
  (cong₂ _++_ refl (rev-++ l₁ [ e ])
   sym (++-assoc l₂ [ e ] (rev l₁)))
  i
eval (trunc b b' α β i j) = isOfHLevelList 0 ESet (eval b) (eval b') (cong eval α) (cong eval β) i j

eval-quot : (l : List E)   (eval (quot l)  l)
eval-quot l _ = ++-unit-r l

quot-eval : (b : Batch E)   (quot (eval b)  b)
quot-eval (inj (l₁ , l₂)) abs =
  sym (multitilt [] l₂ (rev l₁) abs)
   cong inj (cong₂ {B = λ _  List E} _,_ (rev-rev l₁) {u = l₂} refl)
quot-eval (tilt abs e l₁ l₂ i) abs' =
  isOfHLevelPathP'
  {A = λ i  quot (eval (tilt abs e l₁ l₂ i))  tilt abs e l₁ l₂ i}
  0
  (trunc _ _)
      (sym (multitilt [] l₂ (rev (l₁ ++ [ e ])) abs')
       cong inj (cong₂ {B = λ _  List E} _,_ (rev-rev (l₁ ++ [ e ])) {u = l₂} refl))
      (sym (multitilt [] (l₂ ++ [ e ]) (rev l₁) abs')
       cong inj (cong₂ {B = λ _  List E} _,_ (rev-rev l₁) {u = l₂ ++ [ e ]} refl))
    .fst i
quot-eval (trunc b b' α β i j) abs =
  isOfHLevelPathP'
  {A = λ i 
    PathP  j  quot (eval (trunc b b' α β i j))  trunc b b' α β i j)
      (quot-eval b abs) (quot-eval b' abs)}
  0
  (isOfHLevelPathP' 1 (isOfHLevelSuc 2 trunc _ _) _ _)
  (cong  b  quot-eval b abs) α) (cong  b  quot-eval b abs) β)
  .fst i j

quot-empty :  (quot (listPreQueue .empty)  batchedPreQueue .empty)
quot-empty abs = refl

quot-enqueue :  ((e : E) (l : List E)  quot (listPreQueue .enqueue e l)  batchedPreQueue .enqueue e (quot l))
quot-enqueue abs e l = sym (multitilt [] l [ e ] abs)

quot-dequeue :  ((l : List E) 
    (listPreQueue .dequeue l .fst  batchedPreQueue .dequeue (quot l) .fst)
  × (quot (listPreQueue .dequeue l .snd)  batchedPreQueue .dequeue (quot l) .snd))
quot-dequeue abs [] = refl , refl
quot-dequeue abs (x  l) = refl , refl

batchedEquiv :  (Batch E  List E)
batchedEquiv abs = isoToEquiv (iso eval quot  l  eval-quot l abs)  b  quot-eval b abs))

batchedQueue : Queue
batchedQueue .fst = batchedPreQueue
batchedQueue .snd abs = sym $
  prequeue-path
    (invEquiv (batchedEquiv abs))
    (quot-empty abs)
    (quot-enqueue abs)
    (fst  quot-dequeue abs)
    (snd  quot-dequeue abs)