\import Algebra.Group.Category
\import Algebra.Monoid
\import Data.Array
\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 (\new Equivalence (Array A) (\lam l l' => TruncP (EPerm l l')) {
        | ~-reflexive => inP EPerm.eperm-refl
        | ~-symmetric (inP e) => inP (EPerm.eperm-sym e)
        | ~-transitive (inP e1) (inP e2) => inP (eperm-trans e1 e2)
      }) (\lam e => inP e) (path (\lam i => p i))
  }

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

\instance PermSetAbMonoid (A : \Set) : AbMonoid (PermSet A)
  | zro => in~ nil
  | + (x y : PermSet A) : PermSet A \with {
    | in~ l, in~ l' => in~ (l ++ l')
    | in~ l, ~-equiv l1 l2 r => permSet-ext $ ~-pequiv (EPerm.eperm-++-right r)
    | ~-equiv l1 l2 r, in~ l => permSet-ext $ ~-pequiv (EPerm.eperm-++-left r)
  }
  | zro-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' => permSet-ext (~-pequiv 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 => permSet-ext $ ~-pequiv (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) : AddMonoidHom (PermSetAbMonoid A) (PermSetAbMonoid B) (permSet-map f) \cowith
  | func-zro => idp
  | func-+ {x} {y} => \case \elim x, \elim y \with {
    | in~ l, in~ l' => pmap PermSet.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 : AbMonoid} (x : PermSet A) : A \elim x
  | in~ l => A.BigSum l
  | ~-equiv x y r => A.BigSum_EPerm r

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

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

\func permSet-univ {A : \Set} {B : AbMonoid} (f : A -> B) : AddMonoidHom (PermSetAbMonoid A) B \cowith
  | func x => permSet-sum (permSet-map f x)
  | func-zro => idp
  | func-+ => pmap permSet-sum permSet-map_+ *> permSet-sum_+

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

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

\lemma permSet-split {A : \Set} {l : Array A} : AddMonoid.BigSum (map (\lam a => PermSet.inPS (a :: nil)) l) = PermSet.inPS l \elim l
  | nil => idp
  | a :: l => pmap (_ +) 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