\import Algebra.Monoid
\import Algebra.Monoid.Category
\import Data.Array
\import Data.Or
\import Function.Meta
\import Logic
\import Paths
\import Relation.Equivalence
\import Set

\type PermSet (A : \Set) => Quotient {Array A} EPerm
  \where {
    \func inPS~ {A : \Set} (l : Array A) : Quotient EPerm => in~ l

    \func inPS {A : \Set} (l : Array A) : PermSet A => in~ l

    \lemma unext {A : \Set} {l l' : Array A} (p : inPS~ l = {PermSet A} inPS~ l') : TruncP (EPerm l l')
      => Quotient.equalityClosure EPerm.EPerm-equivalence (\lam e => inP e) (path (\lam i => p i))

    \lemma ~-psequiv {A : \Set} {l l' : Array A} (e : EPerm l l') : inPS l = inPS l'
      => permSet-ext (~-pequiv e)
  }

\open PermSet

\lemma permSet-ext {A : \Set} {x y : PermSet A} (p : x = {Quotient EPerm} y) : x = y
  => path (\lam i => p i)

\instance PermSetDec (A : DecSet) : DecSet (PermSet A)
  | decideEq => \case \elim __, \elim __ \with {
    | in~ l, in~ l' => \case EPerm.EPermDec {A} {l} {l'} \with {
      | inl e => yes (~-psequiv e)
      | inr q => no \lam p => \case unext p \with {
        | inP p' => q p'
      }
    }
  }

\instance PermSetMonoid (A : \Set) : CMonoid (PermSet A)
  | ide => in~ nil
  | * (x y : PermSet A) : PermSet A \with {
    | in~ l, in~ l' => in~ (l ++ l')
    | in~ l, ~-equiv l1 l2 r => ~-psequiv (EPerm.eperm-++-right r)
    | ~-equiv l1 l2 r, in~ l => ~-psequiv (EPerm.eperm-++-left r)
  }
  | ide-left {x} => \case \elim x \with {
    | in~ l => idp
  }
  | *-assoc {x} {y} {z} => \case \elim x, \elim y, \elim z \with {
    | in~ l1, in~ l2, in~ l3 => permSet-ext (pmap in~ ++-assoc)
  }
  | *-comm {x} {y} => \case \elim x, \elim y \with {
    | in~ l, in~ l' => ~-psequiv EPerm.eperm-++-comm
  }

\func permSet-map {A B : \Set} (f : A -> B) (s : PermSet A) : PermSet B \elim s
  | in~ l => in~ (map f l)
  | ~-equiv x y r => ~-psequiv (EPerm.EPerm_map f r)

\lemma permSet-map-comp {A B C : \Set} {f : A -> B} {g : B -> C} {s : PermSet A}
  : permSet-map g (permSet-map f s) = permSet-map (\lam a => g (f a)) s \elim s
  | in~ l => idp

\func permSet-hom {A B : \Set} (f : A -> B) : MonoidHom (PermSetMonoid A) (PermSetMonoid B) (permSet-map f) \cowith
  | func-ide => idp
  | func-* {x} {y} => \case \elim x, \elim y \with {
    | in~ l, in~ l' => pmap inPS (map_++ f)
  }

\lemma permSet-map_+ {A B : \Set} {f : A -> B} {x y : PermSet A} : permSet-map f (x * y) = permSet-map f x * permSet-map f y \elim x, y
  | in~ l, in~ l' => permSet-ext $ pmap in~ (map_++ f)

\func permSet-sum {A : CMonoid} (x : PermSet A) : A \elim x
  | in~ l => A.BigProd l
  | ~-equiv x y r => A.BigProd_EPerm r

\lemma permSet-sum-natural {A B : CMonoid} (f : MonoidHom A B) {x : PermSet A} : f (permSet-sum x) = permSet-sum (permSet-map f x) \elim x
  | in~ l => f.func-BigProd

\lemma permSet-sum_+ {A : CMonoid} {x y : PermSet A} : permSet-sum (x * y) = permSet-sum x * permSet-sum y \elim x, y
  | in~ l, in~ l' => A.BigProd_++

\func permSet-univ {A : \Set} {B : CMonoid} (f : A -> B) : MonoidHom (PermSetMonoid A) B \cowith
  | func x => permSet-sum (permSet-map f x)
  | func-ide => idp
  | func-* => pmap permSet-sum permSet-map_+ *> permSet-sum_+

\lemma permSet-univ-natural {A B : CMonoid} (f : MonoidHom A B) {x : PermSet A} : f (permSet-sum x) = permSet-sum (permSet-map f x) \elim x
  | in~ l => f.func-BigProd

\func permSet-zro-dec {A : \Set} (x : PermSet A) : Dec (x = 1) \elim x
  | in~ nil => yes idp
  | in~ (_ :: _) => no \lam p => \case unext p \with {
    | inP e => \case EPerm.EPerm_len e
  }

\lemma permSet-split {A : \Set} {l : Array A} : Monoid.BigProd (map (\lam a => inPS (a :: nil)) l) = inPS l \elim l
  | nil => idp
  | a :: l => pmap (_ *) permSet-split

\lemma permSet-pow {A : \Set} {a : A} {n : Nat} : Monoid.pow (inPS (a :: nil)) n = inPS (replicate n a)
  => inv Monoid.BigProd_replicate *> permSet-split

\func permSet-length {A : \Set} (p : PermSet A) : Nat \elim p
  | in~ (l : Array) => l.len
  | ~-equiv l l' e => EPerm.EPerm_len e