module Cubical.Structures.Set.Mon.List where

open import Cubical.Structures.Prelude

import Cubical.Data.Empty as 
open import Cubical.Data.List
open import Cubical.Data.Maybe as Maybe
open import Cubical.Data.Nat
open import Cubical.Data.Nat.Order
open import Cubical.Data.Sigma
open import Cubical.Data.Unit

open import Cubical.Functions.Logic as L

import Cubical.Structures.Set.Mon.Desc as M
import Cubical.Structures.Free as F
open import Cubical.Structures.Sig
open import Cubical.Structures.Str public
open import Cubical.Structures.Tree
open import Cubical.Structures.Eq
open import Cubical.Structures.Arity
open import Cubical.HITs.PropositionalTruncation as P
open import Cubical.Data.Sum as 

private
  variable
     : Level
    A B : Type 

listΑ : sig M.MonSig (List A) -> List A
listΑ (M.`e , i) = []
listΑ (M.`⊕ , i) = i fzero ++ i fone

𝔏 : {a : Level} {A : Type a} -> M.MonStruct
𝔏 {A = A} = < List A , listΑ >

module Free {x y : Level} {A : Type x} {𝔜 : struct y M.MonSig} (isSet𝔜 : isSet (𝔜 .car)) (𝔜-monoid : 𝔜  M.MonSEq) where
  module 𝔜 = M.MonSEq 𝔜 𝔜-monoid

  module _ (f : A -> 𝔜 .car) where
    _♯ : List A -> 𝔜 .car
    []  = 𝔜.e
    (x  xs)  = f x 𝔜.⊕ (xs )

    private
      ♯++ :  xs ys -> (xs ++ ys)   (xs ) 𝔜.⊕ (ys )
      ♯++ [] ys = sym (𝔜.unitl (ys ))
      ♯++ (x  xs) ys = cong (f x 𝔜.⊕_) (♯++ xs ys)  sym (𝔜.assocr (f x) (xs ) (ys ))

    ♯IsMonHom : structHom 𝔏 𝔜
    fst ♯IsMonHom = _♯
    snd ♯IsMonHom M.`e i = 𝔜.eEta
    snd ♯IsMonHom M.`⊕ i = 𝔜.⊕Eta i _♯  sym (♯++ (i fzero) (i fone))

  private
    listEquivLemma : (g : structHom 𝔏 𝔜) -> (x : List A) -> g .fst x  ((g .fst  [_]) ) x
    listEquivLemma (g , homMonWit) [] = sym (homMonWit M.`e (lookup []))  𝔜.eEta
    listEquivLemma (g , homMonWit) (x  xs) =
      g (x  xs) ≡⟨ sym (homMonWit M.`⊕ (lookup ([ x ]  xs  []))) 
      𝔜 .alg (M.`⊕ ,  w -> g (lookup ((x  [])  xs  []) w))) ≡⟨ 𝔜.⊕Eta (lookup ([ x ]  xs  [])) g 
      g [ x ] 𝔜.⊕ g xs ≡⟨ cong (g [ x ] 𝔜.⊕_) (listEquivLemma (g , homMonWit) xs) 
      _ 

    listEquivLemmaβ : (g : structHom 𝔏 𝔜) -> g  ♯IsMonHom (g .fst  [_])
    listEquivLemmaβ g = structHom≡ 𝔏 𝔜 g (♯IsMonHom (g .fst  [_])) isSet𝔜 (funExt (listEquivLemma g))

  listEquiv : structHom 𝔏 𝔜  (A -> 𝔜 .car)
  listEquiv =
    isoToEquiv (iso  g -> g .fst  [_]) ♯IsMonHom  g -> funExt (𝔜.unitr  g)) (sym  listEquivLemmaβ))

module Foldr {A : Type } {B : Type } {isSetB : isSet B} where

  EndoAlpha : M.MonStruct
  car EndoAlpha = B -> B
  alg EndoAlpha (M.`e , _) = idfun B
  alg EndoAlpha (M.`⊕ , ρ) = ρ fone  ρ fzero

  EndoSat : EndoAlpha  M.MonSEq
  EndoSat M.`unitl ρ = refl
  EndoSat M.`unitr ρ = refl
  EndoSat M.`assocr ρ = refl

  open Free {A = A} (isSet→ isSetB) EndoSat

  foldr' : (A -> B -> B) -> List A -> B -> B
  foldr' f = (f )

module ListDef = F.Definition M.MonSig M.MonEqSig M.MonSEq

listSat :  {n} {X : Type n} -> < List X , listΑ >  M.MonSEq
listSat M.`unitl ρ = refl
listSat M.`unitr ρ = ++-unit-r (ρ fzero)
listSat M.`assocr ρ = ++-assoc (ρ fzero) (ρ fone) (ρ ftwo)

listDef :  { ℓ'} -> ListDef.Free  ℓ' 2
F.Definition.Free.F listDef = List
F.Definition.Free.η listDef = [_]
F.Definition.Free.α listDef = listΑ
F.Definition.Free.sat listDef = listSat
F.Definition.Free.trunc listDef = isOfHLevelList zero
F.Definition.Free.isFree listDef isSet𝔜 satMon = (Free.listEquiv isSet𝔜 satMon) .snd

listBot : (List ⊥.⊥)  Unit
listBot = isoToEquiv (iso  _ -> tt)  _ -> [])  _ -> isPropUnit _ _) lemma)
  where
  lemma :  xs -> []  xs
  lemma [] = refl
  lemma (x  xs) = ⊥.rec x

module Membership {} {A : Type } (isSetA : isSet A) where
  open Free {A = A} isSetHProp (M.⊔-MonStr-MonSEq )

   : A -> A -> hProp 
   x = λ y -> (x  y) , isSetA x y

  ∈Prop : A -> List A -> hProp 
  ∈Prop x = ( x) 

  _∈_ : A -> List A -> Type 
  x  xs = ∈Prop x xs .fst

  isProp-∈ : (x : A) -> (xs : List A) -> isProp (x  xs)
  isProp-∈ x xs = (∈Prop x xs) .snd

  x∈xs :  x xs -> x  (x  xs)
  x∈xs x xs = L.inl refl

  x∈[x] :  x -> x  [ x ]
  x∈[x] x = x∈xs x []

  ∈-∷ :  x y xs -> x  xs -> x  (y  xs)
  ∈-∷ x y xs p = L.inr p

  ∈-++ :  x xs ys -> x  ys -> x  (xs ++ ys)
  ∈-++ x [] ys p = p
  ∈-++ x (a  as) ys p = ∈-∷ x a (as ++ ys) (∈-++ x as ys p)

  ¬∈[] :  x -> (x  []) -> ⊥.⊥
  ¬∈[] x = ⊥.rec*

  x∈[y]→x≡y :  x y -> x  [ y ] -> x  y
  x∈[y]→x≡y x y = P.rec (isSetA x y) $ ⊎.rec (idfun _) ⊥.rec*

module Head {} {A : Type } where

  _⊕_ : Maybe A -> Maybe A -> Maybe A
  nothing  b = b
  just a  b = just a

  ⊕-unitl :  x -> nothing  x  x
  ⊕-unitl x = refl

  ⊕-unitr :  x -> x  nothing  x
  ⊕-unitr nothing = refl
  ⊕-unitr (just x) = refl

  ⊕AssocR :  x y z -> (x  y)  z  x  (y  z)
  ⊕AssocR nothing y z = refl
  ⊕AssocR (just x) y z = refl

  MaybeMonStr : M.MonStruct
  car MaybeMonStr = Maybe A
  alg MaybeMonStr (M.`e , _) = nothing
  alg MaybeMonStr (M.`⊕ , i) = i fzero  i fone

  MaybeMonStrMonSEq : MaybeMonStr  M.MonSEq
  MaybeMonStrMonSEq M.`unitl ρ = ⊕-unitl (ρ fzero)
  MaybeMonStrMonSEq M.`unitr ρ = ⊕-unitr (ρ fzero)
  MaybeMonStrMonSEq M.`assocr ρ = ⊕AssocR (ρ fzero) (ρ fone) (ρ ftwo)

  module Set (isSetA : isSet A) where
    open Free {A = A} (isOfHLevelMaybe 0 isSetA) MaybeMonStrMonSEq

    head : List A -> Maybe A
    head = just 

    head-[] : head []  nothing
    head-[] = refl

    head-∷ :  x xs -> head (x  xs)  just x
    head-∷ _ _ = refl