{-# OPTIONS --cubical --no-import-sorts --safe #-}
module Cubical.Data.List.Properties where

open import Agda.Builtin.List
open import Cubical.Core.Everything
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Prelude
open import Cubical.Data.Empty as 
open import Cubical.Data.Nat
open import Cubical.Data.Sigma
open import Cubical.Data.Unit
open import Cubical.Relation.Nullary

open import Cubical.Data.List.Base

module _ {} {A : Type } where

  ++-unit-r : (xs : List A)  xs ++ []  xs
  ++-unit-r [] = refl
  ++-unit-r (x  xs) = cong (_∷_ x) (++-unit-r xs)

  ++-assoc : (xs ys zs : List A)  (xs ++ ys) ++ zs  xs ++ ys ++ zs
  ++-assoc [] ys zs = refl
  ++-assoc (x  xs) ys zs = cong (_∷_ x) (++-assoc xs ys zs)

  rev-snoc : (xs : List A) (y : A)  rev (xs ++ [ y ])  y  rev xs
  rev-snoc [] y = refl
  rev-snoc (x  xs) y = cong (_++ [ x ]) (rev-snoc xs y)

  rev-++ : (xs ys : List A)  rev (xs ++ ys)  rev ys ++ rev xs
  rev-++ [] ys = sym (++-unit-r (rev ys))
  rev-++ (x  xs) ys =
    cong  zs  zs ++ [ x ]) (rev-++ xs ys)
     ++-assoc (rev ys) (rev xs) [ x ]

  rev-rev : (xs : List A)  rev (rev xs)  xs
  rev-rev [] = refl
  rev-rev (x  xs) = rev-snoc (rev xs) x  cong (_∷_ x) (rev-rev xs)

  rev-rev-snoc : (xs : List A) (y : A) 
    Square (rev-rev (xs ++ [ y ])) (cong (_++ [ y ]) (rev-rev xs)) (cong rev (rev-snoc xs y)) refl
  rev-rev-snoc [] y = sym (lUnit refl)
  rev-rev-snoc (x  xs) y i j =
    hcomp
       k  λ
        { (i = i1)  compPath-filler (rev-snoc (rev xs) x) (cong (x ∷_) (rev-rev xs)) k j ++ [ y ]
        ; (j = i0)  rev (rev-snoc xs y i ++ [ x ])
        ; (j = i1)  x  rev-rev-snoc xs y i k
        })
      (rev-snoc (rev-snoc xs y i) x j)

  data SnocView : List A  Type  where
    nil : SnocView []
    snoc : (x : A)  (xs : List A)  (sx : SnocView xs)  SnocView (xs ∷ʳ x)

  snocView : (xs : List A)  SnocView xs
  snocView xs = helper nil xs
    where
    helper : {l : List A} -> SnocView l -> (r : List A) -> SnocView (l ++ r)
    helper {l} sl [] = subst SnocView (sym (++-unit-r l)) sl
    helper {l} sl (x  r) = subst SnocView (++-assoc l (x  []) r) (helper (snoc x l sl) r)

-- Path space of list type
module ListPath {} {A : Type } where

  Cover : List A  List A  Type 
  Cover [] [] = Lift Unit
  Cover [] (_  _) = Lift 
  Cover (_  _) [] = Lift 
  Cover (x  xs) (y  ys) = (x  y) × Cover xs ys

  reflCode :  xs  Cover xs xs
  reflCode [] = lift tt
  reflCode (_  xs) = refl , reflCode xs

  encode :  xs ys  (p : xs  ys)  Cover xs ys
  encode xs _ = J  ys _  Cover xs ys) (reflCode xs)

  encodeRefl :  xs  encode xs xs refl  reflCode xs
  encodeRefl xs = JRefl  ys _  Cover xs ys) (reflCode xs)

  decode :  xs ys  Cover xs ys  xs  ys
  decode [] [] _ = refl
  decode [] (_  _) (lift ())
  decode (x  xs) [] (lift ())
  decode (x  xs) (y  ys) (p , c) = cong₂ _∷_ p (decode xs ys c)

  decodeRefl :  xs  decode xs xs (reflCode xs)  refl
  decodeRefl [] = refl
  decodeRefl (x  xs) = cong (cong₂ _∷_ refl) (decodeRefl xs)

  decodeEncode :  xs ys  (p : xs  ys)  decode xs ys (encode xs ys p)  p
  decodeEncode xs _ =
    J  ys p  decode xs ys (encode xs ys p)  p)
      (cong (decode xs xs) (encodeRefl xs)  decodeRefl xs)

  isOfHLevelCover : (n : HLevel) (p : isOfHLevel (suc (suc n)) A)
    (xs ys : List A)  isOfHLevel (suc n) (Cover xs ys)
  isOfHLevelCover n p [] [] =
    isOfHLevelLift (suc n) (isProp→isOfHLevelSuc n isPropUnit)
  isOfHLevelCover n p [] (y  ys) =
    isOfHLevelLift (suc n) (isProp→isOfHLevelSuc n isProp⊥)
  isOfHLevelCover n p (x  xs) [] =
    isOfHLevelLift (suc n) (isProp→isOfHLevelSuc n isProp⊥)
  isOfHLevelCover n p (x  xs) (y  ys) =
    isOfHLevelΣ (suc n) (p x y) (\ _  isOfHLevelCover n p xs ys)

isOfHLevelList :  {} (n : HLevel) {A : Type }
   isOfHLevel (suc (suc n)) A  isOfHLevel (suc (suc n)) (List A)
isOfHLevelList n ofLevel xs ys =
  isOfHLevelRetract (suc n)
    (ListPath.encode xs ys)
    (ListPath.decode xs ys)
    (ListPath.decodeEncode xs ys)
    (ListPath.isOfHLevelCover n ofLevel xs ys)

private
  variable
     : Level
    A : Type 

  caseList :  { ℓ'} {A : Type } {B : Type ℓ'}  (n c : B)  List A  B
  caseList n _ []      = n
  caseList _ c (_  _) = c

  safe-head : A  List A  A
  safe-head x []      = x
  safe-head _ (x  _) = x

  safe-tail : List A  List A
  safe-tail []       = []
  safe-tail (_  xs) = xs

cons-inj₁ :  {x y : A} {xs ys}  x  xs  y  ys  x  y
cons-inj₁ {x = x} p = cong (safe-head x) p

cons-inj₂ :  {x y : A} {xs ys}  x  xs  y  ys  xs  ys
cons-inj₂ = cong safe-tail

¬cons≡nil :  {x : A} {xs}  ¬ (x  xs  [])
¬cons≡nil {A = A} p = lower (subst (caseList (Lift ) (List A)) p [])

¬nil≡cons :  {x : A} {xs}  ¬ ([]  x  xs)
¬nil≡cons {A = A} p = lower (subst (caseList (List A) (Lift )) p [])

¬snoc≡nil :  {x : A} {xs}  ¬ (xs ∷ʳ x  [])
¬snoc≡nil {xs = []} contra = ¬cons≡nil contra
¬snoc≡nil {xs = x  xs} contra = ¬cons≡nil contra

¬nil≡snoc :  {x : A} {xs}  ¬ ([]  xs ∷ʳ x)
¬nil≡snoc contra = ¬snoc≡nil (sym contra)

cons≡rev-snoc : (x : A)  (xs : List A)  x  rev xs  rev (xs ∷ʳ x)
cons≡rev-snoc _ [] = refl
cons≡rev-snoc x (y  ys) = λ i  cons≡rev-snoc x ys i ++ y  []

isContr[]≡[] : isContr (Path (List A) [] [])
isContr[]≡[] = refl , ListPath.decodeEncode [] []

isPropXs≡[] : {xs : List A}  isProp (xs  [])
isPropXs≡[] {xs = []} = isOfHLevelSuc 0 isContr[]≡[]
isPropXs≡[] {xs = x  xs} = λ p _  ⊥.rec (¬cons≡nil p)

discreteList : Discrete A  Discrete (List A)
discreteList eqA []       []       = yes refl
discreteList eqA []       (y  ys) = no ¬nil≡cons
discreteList eqA (x  xs) []       = no ¬cons≡nil
discreteList eqA (x  xs) (y  ys) with eqA x y | discreteList eqA xs ys
... | yes p | yes q = yes  i  p i  q i)
... | yes _ | no ¬q = no  p  ¬q (cons-inj₂ p))
... | no ¬p | _     = no  q  ¬p (cons-inj₁ q))

foldrCons : (xs : List A)  foldr _∷_ [] xs  xs
foldrCons [] = refl
foldrCons (x  xs) = cong (x ∷_) (foldrCons xs)