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

module Data.Bag where

open import Prelude
open import Algebra
open import Path.Reasoning

infixr 5 _∷_
data ⟅_⟆ (A : Type a) : Type a where
  []     : ⟅ A ⟆
  _∷_    : A → ⟅ A ⟆ → ⟅ A ⟆
  com    : ∀ x y xs → x ∷ y ∷ xs ≡ y ∷ x ∷ xs
  trunc  : isSet ⟅ A ⟆

record Elim {a ℓ}
             (A : Type a)
             (P : ⟅ A ⟆ → Type ℓ)
             : Type (a ℓ⊔ ℓ) where
  constructor elim
  field
    ⟅_⟆-set : ∀ {xs} → isSet (P xs)
    ⟅_⟆[] : P []
    ⟅_⟆_∷_⟨_⟩ : ∀ x xs → P xs → P (x ∷ xs)
  private z = ⟅_⟆[]; f = ⟅_⟆_∷_⟨_⟩
  field
    ⟅_⟆-com : (∀ x y xs pxs →
      PathP  (λ i → P (com x y xs i))
             (f x (y ∷ xs) (f y xs pxs))
             (f y (x ∷ xs) (f x xs pxs)))
  ⟅_⟆⇓ : (xs : ⟅ A ⟆) → P xs
  ⟅ [] ⟆⇓ = z
  ⟅ x ∷ xs ⟆⇓ = f x xs ⟅ xs ⟆⇓
  ⟅ com x y xs i ⟆⇓ = ⟅_⟆-com x y xs ⟅ xs ⟆⇓ i
  ⟅ trunc xs ys x y i j ⟆⇓ =
    isOfHLevel→isOfHLevelDep 2
      (λ xs → ⟅_⟆-set {xs})
      ⟅ xs ⟆⇓ ⟅ ys ⟆⇓
      (cong ⟅_⟆⇓ x) (cong ⟅_⟆⇓ y)
      (trunc xs ys x y)
      i j

open Elim


infixr 0 elim-syntax
elim-syntax  : ∀ {a ℓ}
             → (A : Type a)
             → (⟅ A ⟆ → Type ℓ)
             → Type (a ℓ⊔ ℓ)
elim-syntax = Elim

syntax elim-syntax A (λ xs → Pxs) = xs ⦂⟅ A ⟆→ Pxs

record ElimProp {a ℓ} (A : Type a) (P : ⟅ A ⟆ → Type ℓ) : Type (a ℓ⊔ ℓ) where
  constructor elim-prop
  field
    ⟦_⟧-prop : ∀ {xs} → isProp (P xs)
    ⟦_⟧[] : P []
    ⟦_⟧_∷_⟨_⟩ : ∀ x xs → P xs → P (x ∷ xs)
  private z = ⟦_⟧[]; f = ⟦_⟧_∷_⟨_⟩
  ⟦_⟧⇑ = elim
          (isProp→isSet ⟦_⟧-prop)
          z f
          (λ x y xs pxs → toPathP (⟦_⟧-prop (transp (λ i → P (com x y xs i)) i0
            (f x (y ∷ xs) (f y xs pxs))) (f y (x ∷ xs) (f x xs pxs))))
  ⟦_⟧⇓ = ⟅ ⟦_⟧⇑ ⟆⇓

open ElimProp

infixr 0 elim-prop-syntax
elim-prop-syntax : ∀ {a ℓ} → (A : Type a) → (⟅ A ⟆ → Type ℓ) → Type (a ℓ⊔ ℓ)
elim-prop-syntax = ElimProp

syntax elim-prop-syntax A (λ xs → Pxs) = xs ⦂⟅ A ⟆→∥ Pxs ∥

record [⟅_⟆→_] {a b} (A : Type a) (B : Type b) : Type (a ℓ⊔ b) where
  constructor rec
  field
    [_]-set  : isSet B
    [_]_∷_ : A → B → B
    [_][]    : B
  private f = [_]_∷_; z = [_][]
  field
    [_]-com : ∀ x y xs → f x (f y xs) ≡ f y (f x xs)
  [_]⇑ = elim [_]-set z (λ x _ → f x) (λ x y _ → [_]-com x y)
  [_]↓ = ⟅ [_]⇑ ⟆⇓
open [⟅_⟆→_]

infixr 5 _∪_
_∪_ : ⟅ A ⟆ → ⟅ A ⟆ → ⟅ A ⟆
_∪_ = λ xs ys → [ ys ∪′ ]↓ xs
  where
  _∪′ : ⟅ A ⟆ → [⟅ A ⟆→ ⟅ A ⟆ ]
  [ ys ∪′ ]-set = trunc
  [ ys ∪′ ] x ∷ xs = x ∷ xs
  [ ys ∪′ ][] = ys
  [ ys ∪′ ]-com = com

∪-assoc : (xs ys zs : ⟅ A ⟆) →
  (xs ∪ ys) ∪ zs ≡ xs ∪ (ys ∪ zs)
∪-assoc = λ xs ys zs → ⟦ ∪-assoc′ ys zs ⟧⇓ xs
  where
  ∪-assoc′ : ∀ ys zs →
    xs ⦂⟅ A ⟆→∥ (xs ∪ ys) ∪ zs ≡ xs ∪ (ys ∪ zs) ∥
  ⟦ ∪-assoc′ ys zs ⟧-prop = trunc _ _
  ⟦ ∪-assoc′ ys zs ⟧[] = refl
  ⟦ ∪-assoc′ ys zs ⟧ x ∷ xs ⟨ P ⟩ = cong (x ∷_) P

∪-cons  : ∀ (x : A) xs ys
        → (x ∷ xs) ∪ ys ≡ xs ∪ (x ∷ ys)
∪-cons = λ x xs ys → ⟦ ∪-cons′ x ys ⟧⇓ xs
  where
  ∪-cons′ : ∀ x ys →
    xs ⦂⟅ A ⟆→∥ (x ∷ xs) ∪ ys ≡ xs ∪ (x ∷ ys) ∥
  ⟦ ∪-cons′ x ys ⟧-prop = trunc _ _
  ⟦ ∪-cons′ x ys ⟧[] = refl
  ⟦ ∪-cons′ x ys ⟧ y ∷ xs ⟨ P ⟩ = cong (_∪ ys) (com x y xs) ; cong (y ∷_) P

∪-idʳ : (xs : ⟅ A ⟆) → xs ∪ [] ≡ xs
∪-idʳ = ⟦ ∪-idʳ′ ⟧⇓
  where
  ∪-idʳ′ : xs ⦂⟅ A ⟆→∥ xs ∪ [] ≡ xs ∥
  ⟦ ∪-idʳ′ ⟧-prop = trunc _ _
  ⟦ ∪-idʳ′ ⟧[] = refl
  ⟦ ∪-idʳ′ ⟧ x ∷ xs ⟨ P ⟩ = cong (x ∷_) P

∪-comm : (xs ys : ⟅ A ⟆) → xs ∪ ys ≡ ys ∪ xs
∪-comm {A = A} = λ xs ys → ⟦ ∪-comm′ ys ⟧⇓ xs
  where
  ∪-comm′ : (ys : ⟅ A ⟆) →
    xs ⦂⟅ A ⟆→∥ xs ∪ ys ≡ ys ∪ xs ∥
  ⟦ ∪-comm′ ys ⟧-prop = trunc _ _
  ⟦ ∪-comm′ ys ⟧[] = sym (∪-idʳ ys)
  ⟦ ∪-comm′ ys ⟧ x ∷ xs ⟨ P ⟩ =
    (x ∷ xs) ∪ ys ≡⟨ cong (x ∷_) P ⟩
    (x ∷ ys) ∪ xs ≡⟨ ∪-cons x ys xs ⟩
    ys ∪ x ∷ xs ∎

⟅⟆-commutative-monoid : ∀ {a} (A : Type a) → CommutativeMonoid ⟅ A ⟆
Monoid._∙_ (CommutativeMonoid.monoid (⟅⟆-commutative-monoid A)) = _∪_
Monoid.ε (CommutativeMonoid.monoid (⟅⟆-commutative-monoid A)) = []
Monoid.assoc (CommutativeMonoid.monoid (⟅⟆-commutative-monoid A)) = ∪-assoc
Monoid.ε∙ (CommutativeMonoid.monoid (⟅⟆-commutative-monoid A)) _ = refl
Monoid.∙ε (CommutativeMonoid.monoid (⟅⟆-commutative-monoid A)) = ∪-idʳ
CommutativeMonoid.comm (⟅⟆-commutative-monoid A) = ∪-comm

module _ {ℓ} {𝑆 : Type ℓ} (mon : CommutativeMonoid 𝑆) (sIsSet : isSet 𝑆) where
  open CommutativeMonoid mon
  ⟦_⟧ : (A → 𝑆) → ⟅ A ⟆ → 𝑆
  ⟦_⟧ = λ h → [ ⟦ h ⟧′ ]↓
    where
    ⟦_⟧′ : (A → 𝑆) → [⟅ A ⟆→ 𝑆 ]
    [ ⟦ h ⟧′ ] x ∷ xs = h x ∙ xs
    [ ⟦ h ⟧′ ][] = ε
    [ ⟦ h ⟧′ ]-com x y xs =
      h x ∙ (h y ∙ xs)
        ≡˘⟨ assoc _ _ _ ⟩
      (h x ∙ h y) ∙ xs
        ≡⟨ cong (_∙ xs) (comm _ _) ⟩
      (h y ∙ h x) ∙ xs
        ≡⟨ assoc _ _ _ ⟩
      h y ∙ (h x ∙ xs) ∎
    [ ⟦ h ⟧′ ]-set = sIsSet

record ⟦_≡_⟧ {a b} {A : Type a} {B : Type b}
            (h : ⟅ A ⟆ → B)
            (xf : [⟅ A ⟆→ B ])
            : Type (a ℓ⊔ b) where
  constructor elim-univ
  field
    ⟦_≡⟧_∷_ : ∀ x xs → h (x ∷ xs) ≡ [ xf ] x ∷ h xs
    ⟦_≡⟧[] : h [] ≡ [ xf ][]
  ⟦_≡⟧⇓ : ∀ xs → h xs ≡ [ xf ]↓ xs
  ⟦_≡⟧⇓ = ⟦ ≡⇓′ ⟧⇓
    where
    ≡⇓′ : xs ⦂⟅ A ⟆→∥ h xs ≡ [ xf ]↓ xs ∥
    ⟦ ≡⇓′ ⟧-prop = [ xf ]-set _ _
    ⟦ ≡⇓′ ⟧[] = ⟦_≡⟧[]
    ⟦ ≡⇓′ ⟧ x ∷ xs ⟨ P ⟩ = ⟦_≡⟧_∷_ x _ ; cong ([ xf ] x ∷_) P
open ⟦_≡_⟧

record ⟦_⊚_≡_⟧ {a b c} {A : Type a} {B : Type b} {C : Type c}
               (h : B → C)
               (xf : [⟅ A ⟆→ B ])
               (yf : [⟅ A ⟆→ C ])
               : Type (a ℓ⊔ b ℓ⊔ c)
    where
  constructor elim-fuse
  field
    ⟦_∘≡⟧_∷_ : ∀ x xs → h ([ xf ] x ∷ xs) ≡ [ yf ] x ∷ h xs
    ⟦_∘≡⟧[] : h [ xf ][] ≡ [ yf ][]
  ⟦_∘≡⟧⇓ : ∀ xs → h ([ xf ]↓ xs) ≡ [ yf ]↓ xs
  ⟦_∘≡⟧⇓ = ⟦ ≡⇓′ ⟧⇓
    where
    ≡⇓′ : xs ⦂⟅ A ⟆→∥ h ([ xf ]↓ xs) ≡ [ yf ]↓ xs ∥
    ⟦ ≡⇓′ ⟧-prop = [ yf ]-set _ _
    ⟦ ≡⇓′ ⟧[] = ⟦_∘≡⟧[]
    ⟦ ≡⇓′ ⟧ x ∷ xs ⟨ P ⟩ = ⟦_∘≡⟧_∷_ x _ ; cong ([ yf ] x ∷_) P
open ⟦_⊚_≡_⟧

map-alg : (A → B) → [⟅ A ⟆→ ⟅ B ⟆ ]
[ map-alg f ]-set = trunc
[ map-alg f ][] = []
[ map-alg f ] x ∷ xs = f x ∷ xs
[ map-alg f ]-com x y = com (f x) (f y)

map : (A → B) → ⟅ A ⟆ → ⟅ B ⟆
map f = [ map-alg f ]↓

[_]∘_ : [⟅ B ⟆→ C ] → (A → B) → [⟅ A ⟆→ C ]
[ [ g ]∘ f ]-set      = [ g ]-set
[ [ g ]∘ f ][]        = [ g ][]
[ [ g ]∘ f ] x ∷ xs   = [ g ] f x ∷ xs
[ [ g ]∘ f ]-com x y  = [ g ]-com (f x) (f y)

map-fuse  : ∀ (g : [⟅ B ⟆→ C ]) (f : A → B)
          → [ g ]↓ ∘ map f ≡ [ [ g ]∘ f ]↓
map-fuse g f = funExt ⟦ map-fuse′ g f ∘≡⟧⇓
  where
  map-fuse′  : (g : [⟅ B ⟆→ C ]) (f : A → B)
             → ⟦ [ g ]↓ ⊚ map-alg f ≡ [ g ]∘ f ⟧
  ⟦ map-fuse′ g f ∘≡⟧ x ∷ xs = refl
  ⟦ map-fuse′ g f ∘≡⟧[] = refl

bind : ⟅ A ⟆ → (A → ⟅ B ⟆) → ⟅ B ⟆
bind xs f = ⟦ ⟅⟆-commutative-monoid _ ⟧ trunc f xs