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

module Data.List.Properties where

open import Data.List
open import Prelude
open import Data.Fin
open import Strict.Properties

reverse : List A  List A
reverse = foldl (flip _∷_) []

foldl-by-r : (B  A  B)  B  List A  B
foldl-by-r f b xs = foldr  x k xs  k (f xs x)) id xs b

map-length : (f : A  B) (xs : List A)
            length xs  length (map f xs)
map-length f [] _ = zero
map-length f (x  xs) i = suc (map-length f xs i)

map-ind : (f : A  B) (xs : List A)
         PathP  i  Fin (map-length f xs i)  B) (f  (xs !_)) (map f xs !_)
map-ind f [] i ()
map-ind f (x  xs) i f0 = f x
map-ind f (x  xs) i (fs n) = map-ind f xs i n

tab-length :  n (f : Fin n  A)  length (tabulate n f)  n
tab-length zero f _ = zero
tab-length (suc n) f i = suc (tab-length n (f  fs) i)

tab-distrib :  n (f : Fin n  A) m   i × (f i  tabulate n f ! m)
tab-distrib (suc n) f f0 = f0 , refl
tab-distrib (suc n) f (fs m) = let i , p = tab-distrib n (f  fs) m in fs i , p

tab-id :  n (f : Fin n  A)  PathP  i  Fin (tab-length n f i)  A) (_!_ (tabulate n f)) f
tab-id zero f _ ()
tab-id (suc n) f i f0 = f f0
tab-id (suc n) f i (fs m) = tab-id n (f  fs) i m

list-elim :  {p} (P : List A  Type p) 
                  (∀ x xs  P xs  P (x  xs)) 
                  (P []) 
                   xs  P xs
list-elim P f b [] = b
list-elim P f b (x  xs) = f x xs (list-elim P f b xs)

foldr-universal :  (h : List B  A) f e
                 (h []  e)
                 (∀ x xs  h (x  xs)  f x (h xs))
                  xs  h xs  foldr f e xs
foldr-universal h f e base step [] = base
foldr-universal h f e base step (x  xs) =
  step x xs ; cong (f x) (foldr-universal h f e base step xs)

foldr-id : (xs : List A)  xs  foldr _∷_ [] xs
foldr-id = foldr-universal id _∷_ [] refl  _ _  refl)

foldr-fusion :  (f : C  A) {_⊕_ : B  C  C} {_⊗_ : B  A  A} e
               (∀ x y  f (x  y)  x  f y)
                xs  f (foldr _⊕_ e xs)  foldr _⊗_ (f e) xs
foldr-fusion h {f} {g} e fuse =
  foldr-universal (h  foldr f e) g (h e) refl  x xs  fuse x (foldr f e xs))

foldr-++ : (f : A  B  B) (b : B) (xs ys : List A)
          foldr f b (xs ++ ys)  foldr f (foldr f b ys) xs
foldr-++ f b xs ys = foldr-fusion (foldr f b) ys  _ _  refl) xs

foldl-is-foldr : (f : B  A  B) (z : B) (xs : List A) 
                 foldl f z xs  foldl-by-r f z xs
foldl-is-foldr f z xs =
  cong (_$ z) (foldr-universal (flip (foldl f))  x k xs  k (f xs x)) id refl  x xs  refl) xs) 

foldl-fusion :  (f : C  A) {_⊕_ : C  B  C}  {_⊗_ : A  B  A} e 
                 (∀ x y  f (x  y)  f x  y) 
                  xs  f (foldl _⊕_ e xs)  foldl _⊗_ (f e) xs
foldl-fusion h {f} {g} e fuse [] = refl
foldl-fusion h {f} {g} e fuse (x  xs) =
  foldl-fusion h (f e x) fuse xs ; cong (flip (foldl g) xs) (fuse e x)

open import Path.Reasoning

module _ {A : Type a} {B : Type b} where
  foldl-++ : (f : B  A  B) (b : B) (xs ys : List A)
           foldl f b (xs ++ ys)  foldl f (foldl f b xs) ys
  foldl-++ f b xs ys =
    foldl f b (xs ++ ys) ≡⟨ foldl-is-foldr f b (xs ++ ys) 
    foldl-by-r f b (xs ++ ys) ≡⟨ cong (_$ b) (foldr-++  x k b  k (f b x)) id xs ys) 
    foldr  x k b  k (f b x)) (foldr  x k b  k (f b x)) id ys) xs b ≡˘⟨ cong  e  foldr  x k b  k (f b x)) e xs b) (funExt λ b  foldl-is-foldr f b ys) 
    foldr  x k b  k (f b x)) (flip (foldl f) ys) xs b ≡˘⟨ cong′ {A = B  B} (_$ b) (foldr-universal  xs b  foldl f (foldl f b xs) ys)  x k b  k (f b x)) (flip (foldl f) ys) refl  _ _  refl) xs ) 
    foldl f (foldl f b xs) ys 

foldl-by-r-cons : (A  B  B)  A  (B  B)  B  B
foldl-by-r-cons f x k xs = k (f x xs)

foldr-reverse : (f : A  B  B) (b : B) (xs : List A)
               foldr f b (reverse xs)  foldl (flip f) b xs
foldr-reverse f b = foldl-fusion (foldr f b) []  _ _  refl)

module _ {A : Type a} where
  reverse-reverse : (xs : List A)  reverse (reverse xs)  xs
  reverse-reverse xs = foldl-is-foldr (flip _∷_) [] (reverse xs)
                     ; cong (_$ [])
                       ( foldr-reverse (foldl-by-r-cons _∷_) id xs
                       ; foldl-is-foldr (flip (foldl-by-r-cons _∷_)) id xs
                       ; cong′ (_$ id) (sym (foldr-universal  xs k a  k (foldr _∷_ a xs)) (foldl-by-r-cons (foldl-by-r-cons _∷_)) id refl  x xs  refl) xs))
                       )
                     ; sym (foldr-id xs)

++-assoc : (xs ys zs : List A)  (xs ++ ys) ++ zs  xs ++ (ys ++ zs)
++-assoc xs ys zs = foldr-fusion (_++ zs) ys  _ _  refl) xs

map-fusion :  f (b : C) (g : A  B) xs  foldr f b (map g xs)  foldr (f  g) b xs
map-fusion f b g  = foldr-fusion (foldr f b) [] λ _ _  refl

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

open import Function.Injective

∷-inj : (x : A)  Injective (x ∷_)
∷-inj x xs ys = cong λ where []  []
                             (_  zs)  zs

++-inj : (xs : List A)  Injective (xs ++_)
++-inj []       ys zs ys≡zs = ys≡zs
++-inj (x  xs) ys zs ys≡zs = ++-inj xs ys zs (∷-inj x (xs ++ ys) (xs ++ zs) ys≡zs)

open import Algebra

module _ {𝑆 : Type b} (mon : Monoid 𝑆) where
  open Monoid mon

  module _ (f : A  𝑆) where
    monStepL : 𝑆  A  𝑆
    monStepL xs x = xs  f x
    {-# INLINE monStepL #-}

    foldMapL : List A  𝑆
    foldMapL = foldl monStepL ε

    foldMapLStep :  x xs  f x  foldMapL xs  foldMapL (x  xs)
    foldMapLStep x xs = foldl-fusion (f x ∙_) ε  y z  sym (assoc (f x) y (f z))) xs ; cong (flip (foldl monStepL) xs) (∙ε (f x) ; sym (ε∙ (f x)))

    foldl-foldr-monoid : (xs : List A)  foldMapL xs  foldr (_∙_  f) ε xs
    foldl-foldr-monoid = foldr-universal _ (_∙_  f) ε refl λ x xs  sym (foldMapLStep x xs)

module _ (A : Type a) where
  listMonoid : Monoid (List A) 
  listMonoid .Monoid._∙_ = _++_
  listMonoid .Monoid.ε = []
  listMonoid .Monoid.assoc = ++-assoc
  listMonoid .Monoid.ε∙ _ = refl
  listMonoid .Monoid.∙ε = ++-idʳ

listFunctor : Functor {ℓ₁ = a} List
listFunctor .Functor.map = map
listFunctor .Functor.map-id = funExt (sym  foldr-id)
listFunctor .Functor.map-comp f g = funExt λ xs  sym (map-fusion _ _ _ xs)

foldl′-foldl : (f : B  A  B) (z : B) (xs : List A)  foldl′ f z xs  foldl f z xs
foldl′-foldl f z [] = refl
foldl′-foldl f z (x  xs) = $!-≡  y  foldl′ f y xs) (f z x) ; foldl′-foldl f (f z x) xs 

foldr′-foldr : (f : A  B  B) (z : B) (xs : List A)  foldr′ f z xs  foldr f z xs
foldr′-foldr f z [] = refl
foldr′-foldr f z (x  xs) = $!-≡ (f x) (foldr′ f z xs) ; cong (f x) (foldr′-foldr f z xs)

is-empty : List A  Bool
is-empty [] = true
is-empty (_  _) = false

NonEmpty : List A  Type
NonEmpty = T  not  is-empty

open import Data.Maybe.Properties

foldrMay-nonEmpty : (f : A  A  A) (xs : List A)  NonEmpty xs  IsJust (foldrMay f xs)
foldrMay-nonEmpty f (x  xs) _ = tt

foldr1 : (A  A  A)  (xs : List A)   NonEmpty xs   A
foldr1 f xs  xsne  = fromJust (foldrMay f xs)
 where instance _ = foldrMay-nonEmpty f xs xsne