{-# OPTIONS --cubical --safe #-}

open import Basis hiding (¬_)
open import Cubical.Data.Empty.Base using (; rec)
open import Cubical.Data.Empty.Properties using (isProp⊥)
open import Cubical.Relation.Nullary.DecidableEq using (Discrete→isSet)
open import Cubical.Relation.Nullary using (Discrete; yes; no; Dec; ¬_)

module SnocList (Z : Type ℓ-zero) (_≟_ : Discrete Z) where

data SnocList : Type ℓ-zero where
  []  : SnocList
  _⌢_ : SnocList  Z  SnocList

infixl 5 _⌢_

_elem_ : (z : Z)  SnocList  hProp ℓ-zero
z elem []        =  , isProp⊥
z elem (zs  z′) with z  z′
z elem (zs  z′) | yes  p = (Unit ℓ-zero) , Unit-prop
z elem (zs  z′) | no  ¬p = z elem zs

⌢-eq-left : {xs ys : SnocList} {x y : Z}  xs  x  ys  y  xs  ys
⌢-eq-left {ys = ys} p = subst  { (zs  _)  zs  ys ; []  Z }) (sym p) refl

⌢-eq-right : {xs ys : SnocList} {x y : Z}  xs  x  ys  y  x  y
⌢-eq-right {x = x} {y = y} p with x  y
⌢-eq-right {x = x} {y = y} p | yes q = q
⌢-eq-right {x = x} {y = y} p | no ¬q = sym (subst P p refl)
  where
    P : SnocList  Type ℓ-zero
    P []      = 
    P (_  z) = z  x

⌢≠[] : {xs : SnocList} {x : Z}  ¬ (xs  x  [])
⌢≠[] p = subst P p tt
  where
    P : SnocList  Type ℓ-zero
    P []      = 
    P (_  _) = Unit ℓ-zero


SnocList-discrete : Discrete SnocList
SnocList-discrete [] [] = yes refl
SnocList-discrete [] (ys  y) = no λ p  ⌢≠[] (sym p)
SnocList-discrete (xs  x) [] = no λ p  ⌢≠[] p
SnocList-discrete (xs  x) (ys  y)
  with x  y | SnocList-discrete xs ys
...  | _     | no ¬q = no λ q  ¬q (⌢-eq-left  q)
...  | no ¬p | _     = no λ p  ¬p (⌢-eq-right p)
...  | yes p | yes q = subst P (sym q) (subst Q (sym p) (yes refl))
                       where
                         P = λ xs′  Dec (xs′  x   ys  y)
                         Q = λ x′   Dec (ys   x′  ys  y)

SnocList-set : isSet SnocList
SnocList-set = Discrete→isSet SnocList-discrete

_++_ : SnocList  SnocList  SnocList
xs ++ []       = xs
xs ++ (ys  y) = (xs ++ ys)  y

elem-lemma : (xs ys : SnocList) (z : Z)  [ z elem xs ]  [ z elem (xs ++ ys) ]
elem-lemma xs []       z z∈xs = z∈xs
elem-lemma xs (ys  y) z z∈xs with z  y
elem-lemma xs (ys  y) z z∈xs | yes p = tt
elem-lemma xs (ys  y) z z∈xs | no ¬p = elem-lemma xs ys z z∈xs

[]≠⌢++ : (xs ys : SnocList) (x : Z)  ¬ ([]  ((xs  x) ++ ys))
[]≠⌢++ xs []        x p = ⌢≠[] (sym p)
[]≠⌢++ xs (ys  y)  x p = ⌢≠[] (sym p)

snoc-lemma : (xs ys : SnocList) (x : Z)  (xs  x) ++ ys  xs ++ (([]  x) ++ ys)
snoc-lemma xs [] x = refl
snoc-lemma xs (ys  y) x = cong  -  -  y) (snoc-lemma xs ys x)

++-left-id : (xs : SnocList)  [] ++ xs  xs
++-left-id [] = refl
++-left-id (xs  x) = cong  -  -  x) (++-left-id xs)

assoc : (xs ys zs : SnocList)  xs ++ (ys ++ zs)  (xs ++ ys) ++ zs
assoc xs ys []       = refl
assoc xs ys (zs  z) = cong  -  -  z) (assoc xs ys zs)

xs≠xs⌢y : {xs : SnocList} {y : Z}  ¬ (xs  xs  y)
xs≠xs⌢y {[]}     p = subst  { []  Unit ℓ-zero ; (_  _)   }) p tt
xs≠xs⌢y {xs  x} p = rec (xs≠xs⌢y (⌢-eq-left p))

xs≰xs⌢y : {xs ys : SnocList} {x : Z}  ¬ (xs  (xs  x) ++ ys)
xs≰xs⌢y {[]} {[]} {y} p = subst  { (_  _)   ; []  Unit ℓ-zero }) p tt
xs≰xs⌢y {[]} {ys  _} {y} p = subst  { (_  _)   ; []  Unit ℓ-zero }) p tt
xs≰xs⌢y {xs  x} {[]} {y} p = rec (xs≠xs⌢y (⌢-eq-left p))
xs≰xs⌢y {xs  x} {ys  y} {y′} p = rec (xs≰xs⌢y NTS)
  where
    NTS : xs  (xs  x) ++ (([]  y′) ++ ys)
    NTS = xs                            ≡⟨ ⌢-eq-left p                       
          (xs  x  y′) ++ ys           ≡⟨ snoc-lemma ((xs  x) ++ []) ys y′ 
          (xs  x) ++ (([]  y′) ++ ys) 

lemma3 : {xs : SnocList} {ys : SnocList} {y : Z}  ¬ (xs  xs ++ (ys  y))
lemma3 {[]}     {ys}     {y}  p = rec (⌢≠[] NTS)
  where
    NTS : ys  y  []
    NTS = (ys  y)       ≡⟨ sym (cong  -  -  y) (++-left-id ys)) 
          ([] ++ ys)  y ≡⟨ sym p 
          []             
lemma3 {xs  x} {[]}     {y}  p = rec (lemma3 (⌢-eq-left p))
lemma3 {xs  x} {ys  y} {y′} p = rec (lemma3 NTS)
  where
    NTS : xs  (xs ++ (([]  x) ++ ys))  y
    NTS = xs                         ≡⟨ ⌢-eq-left p              
          ((xs  x) ++ ys)  y       ≡⟨ snoc-lemma xs (ys  y) x 
          xs ++ (([]  x) ++ ys)  y 


++-lemma : {xs ys zs₀ zs₁ : SnocList}  xs  ys ++ zs₀  xs  ys ++ zs₁  zs₀  zs₁
++-lemma {[]} {[]} {zs₀} {zs₁} p q = zs₀       ≡⟨ sym (++-left-id zs₀) 
                                     [] ++ zs₀ ≡⟨ sym p 
                                     []        ≡⟨ q 
                                     [] ++ zs₁ ≡⟨ ++-left-id zs₁ 
                                     zs₁       
++-lemma {[]} {ys  y} {[]} {[]} p q = refl
++-lemma {[]} {ys  y} {[]} {zs₁  x} p q = rec (⌢≠[] (sym p))
++-lemma {[]} {ys  y} {zs₀  x} {[]} p q = rec (⌢≠[] (sym q))
++-lemma {[]} {ys  y} {zs₀  x} {zs₁  x₁} p q = rec (⌢≠[] (sym p))
++-lemma {xs  x} {[]} {[]} {zs₁} p q = rec (⌢≠[] p)
++-lemma {xs  x} {[]} {zs₀  z₀} {[]} p q = rec (⌢≠[] q)
++-lemma {xs  x} {[]} {zs₀  z₀} {zs₁  z₁} p q =
  zs₀  z₀         ≡⟨ cong  -  -  z₀) (sym (++-left-id zs₀)) 
  ([] ++ zs₀)  z₀ ≡⟨ sym p 
  xs  x           ≡⟨ q 
  ([] ++ zs₁)  z₁ ≡⟨ cong  -  -  z₁) (++-left-id zs₁) 
  zs₁  z₁         
++-lemma {xs  x} {ys  y} {[]} {[]} p q = refl
++-lemma {xs  x} {ys  y} {[]} {zs₁  z₁} p q = rec (xs≰xs⌢y (⌢-eq-left NTS))
  where
    NTS : ys  y  ((ys  y) ++ zs₁)  z₁
    NTS = ys  y ≡⟨ sym p  xs  x ≡⟨ q  ((ys  y) ++ zs₁)  z₁ 
++-lemma {xs  x} {ys  y} {zs₀  z₀} {[]} p q = rec (xs≰xs⌢y (⌢-eq-left NTS))
  where
    NTS : ys  y  ((ys  y) ++ zs₀)  z₀
    NTS = ys  y ≡⟨ sym q  xs  x ≡⟨ p  ((ys  y) ++ (zs₀  z₀)) 
++-lemma {xs  x} {ys  y} {zs₀  z₀} {zs₁  z₁} p q with z₀  z₁
... | yes z₀=z₁ = zs₀  z₀ ≡⟨ cong  -  -  z₀) IH 
                  zs₁  z₀ ≡⟨ cong (_⌢_ zs₁) z₀=z₁   
                  zs₁  z₁ 
                  where
                    IH : zs₀  zs₁
                    IH = ++-lemma (⌢-eq-left p) (⌢-eq-left q)
... | no ¬p = rec (¬p (⌢-eq-right NTS))
              where
                NTS : ((ys  y) ++ zs₀)  z₀  ((ys  y) ++ zs₁)  z₁
                NTS = (ys  y) ++ zs₀  z₀ ≡⟨ sym p  xs  x ≡⟨ q  (ys  y) ++ zs₁  z₁ 

nonempty : SnocList  Type ℓ-zero
nonempty []       = 
nonempty (xs  x) = Unit ℓ-zero

head : (xs : SnocList)  nonempty xs  Z
head ([]  x) p = x
head ((xs  x′)  x) p = head (xs  x′) tt

tail : (xs : SnocList)  nonempty xs  SnocList
tail ([]  x) tt = []
tail (xs  x₀  x₁) p = (tail (xs  x₀) tt)  x₁

hd-tl-lemma : (xs : SnocList) (ne : nonempty xs)  ([]  head xs ne) ++ tail xs ne  xs
hd-tl-lemma ([]  x) tt = refl
hd-tl-lemma (xs  x₀  x₁) tt = cong  -  -  x₁) (hd-tl-lemma (xs  x₀) tt)