\import Function
\import Logic
\import Logic.Classical
\import Logic.Meta
\import Meta
\import Paths
\import Set

\class Transitive (A : \Set)
  | \infix 4 ~ : A -> A -> \Prop
  | ~-transitive {x y z : A} : x ~ y -> y ~ z -> x ~ z
  \where {
    \truncated \data Closure {A : \Type} (R : A -> A -> \Type) (x y : A) : \Prop
      | cin (R x y)
      | ctrans {z : A} (Closure R x z) (Closure R z y)
      \where {
        \func isTransitive {A : \Set} (R : A -> A -> \Type) : Transitive A \cowith
          | ~ x y => Closure R x y
          | ~-transitive => ctrans

        \lemma isSymmetric {A : \Set} {R : A -> A -> \Type} (sym : \Pi {a a' : A} -> R a a' -> R a' a) {a a' : A} (c : Closure R a a') : Closure R a' a \elim c
          | cin r => cin (sym r)
          | ctrans c1 c2 => ctrans (isSymmetric sym c2) (isSymmetric sym c1)

        \lemma univ {A : \Set} {R : A -> A -> \Type} (E : Transitive A) (f : \Pi {x y : A} -> R x y -> x ~ y) {x y : A} (c : Closure R x y) : x ~ y \elim c
          | cin r => f r
          | ctrans p q => ~-transitive (univ E f p) (univ E f q)

        \lemma ofTransitive (E : Transitive) {x y : E} (c : Closure (~) x y) : x ~ y => univ E (\lam p => p) c

        \lemma toEquality {A B : \Set} (f : A -> B) {R : A -> A -> \Type} (p : \Pi {a a' : A} -> R a a' -> f a = f a') {a a' : A} (c : Closure R a a') : f a = f a'
          => univ (\new Transitive A (f __ = f __) (*>)) p c
      }
  }

\class Equivalence \extends Transitive
  | ~-reflexive {x : A} : x ~ x
  | ~-symmetric {x y : A} : x ~ y -> y ~ x
  \where {
    \func map {A B : \Set} (f : A -> B) (E : Equivalence B) : Equivalence A \cowith
      | ~ a a' => f a ~ f a'
      | ~-transitive => ~-transitive
      | ~-reflexive => ~-reflexive
      | ~-symmetric => ~-symmetric

    \truncated \data Closure {A : \Type} (R : A -> A -> \Type) (x y : A) : \Prop
      | cin (R x y)
      | crefl (x = y)
      | csym (Closure R y x)
      | ctrans {z : A} (Closure R x z) (Closure R z y)
      \where {
        \func isEquivalence {A : \Set} (R : A -> A -> \Type) : Equivalence A \cowith
          | ~ x y => Closure R x y
          | ~-reflexive {x} => crefl idp
          | ~-symmetric {x} {y} x~y => csym x~y
          | ~-transitive {x} {y} {z} x~y y~z => ctrans x~y y~z

        \lemma univ {A : \Set} {R : A -> A -> \Type} (E : Equivalence A) (f : \Pi {x y : A} -> R x y -> x ~ y) {x y : A} (c : Closure R x y) : x ~ y \elim c
          | cin r => f r
          | crefl x=y => transport (x ~) x=y ~-reflexive
          | csym p => ~-symmetric (univ E f p)
          | ctrans {z} p q => ~-transitive (univ E f p) (univ E f q)

        \lemma ofEquivalence (E : Equivalence) {x y : E} (c : Closure (~) x y) : x ~ y => univ E (\lam p => p) c
      }
  }

\truncated \data Quotient {A : \Type} (R : A -> A -> \Type) : \Set
  | in~ A
  | ~-equiv (x y : A) (R x y) : in~ x = in~ y
  \where {
    \open Equivalence

    \lemma in-surj {A : \Type} {R : A -> A -> \Type} : isSurj (in~ {A} {R})
      => \lam (in~ a) => inP (a,idp)

    \lemma equality {A : \Type} {R : A -> A -> \Type} {x y : A} (p : in~ x = {Quotient R} in~ y) : Closure R x y =>
      \let | R' ([x] [y] : Quotient R) : \Prop =>
              \case [x], [y] \with {
                | in~ x, in~ y => Closure R x y
                | in~ x, ~-equiv y y' y~y' => propExt (ctrans __ (cin y~y')) (ctrans __ (csym (cin y~y')))
                | ~-equiv x x' x~x', in~ y => propExt (ctrans (csym (cin x~x')) __) (ctrans (cin x~x') __)
              }
           | R'-refl ([x] : Quotient R) =>
               \case \elim [x] \return R' [x] [x] \with {
                 | in~ x => crefl idp
               }
           | =-implies-R' {[x] [y] : Quotient R} (p : [x] = [y]) => transport (R' [x]) p (R'-refl [x])
      \in =-implies-R' p

    \lemma equalityEquiv (E : Equivalence) {x y : E} (p : in~ x = {Quotient (~)} in~ y) : x ~ y
      => Closure.ofEquivalence E (equality p)

    \lemma equalityClosure (E : Equivalence) {R : E -> E -> \Type} (s : \Pi {x y : E} -> R x y -> x ~ y) {x y : E} (p : in~ x = {Quotient R} in~ y) : x ~ y
      => Closure.univ E s (equality p)

    \lemma fromEquality {A : \Type} {R : A -> A -> \Type} {x y : A} (p : Closure R x y) : in~ x = {Quotient R} in~ y \elim p
      | cin r => path (~-equiv x y r)
      | crefl p => pmap in~ p
      | csym p => inv (fromEquality p)
      | ctrans p1 p2 => fromEquality p1 *> fromEquality p2

    \func map {A B : \Type} (f : A -> B) {R : A -> A -> \Type} {Q : B -> B -> \Type} (p : \Pi {a a' : A} -> R a a' -> Q (f a) (f a')) (x : Quotient R) : Quotient Q \elim x
      | in~ a => in~ (f a)
      | ~-equiv x y r => ~-pequiv (p r)

    \func liftArray {k : Nat} {A : Fin k -> \Type} (R : \Pi {j : Fin k} -> A j -> A j -> \Type) (refl : \Pi {j : Fin k} (x : A j) -> R x x) (ts : DArray (\lam j => Quotient (R {j})))
      : Quotient {DArray A} (\lam f g => \Pi (j : Fin ts.len) -> R (f j) (g j)) \elim k, ts
      | 0, nil => in~ nil
      | suc k, :: a l => \case \elim a, liftArray (\lam {j} => R {suc j}) (\lam {j} => refl {suc j}) l \with {
        | in~ a, in~ a1 => in~ (a :: a1)
        | in~ a, ~-equiv x y r i => ~-equiv _ _ (later \case \elim __ \with {
          | 0 => refl _
          | suc j => r j
        }) i
        | ~-equiv x y r i, in~ a => ~-equiv _ _ (later \case \elim __ \with {
          | 0 => r
          | suc j => refl _
        }) i
      }

    \func liftArrayFun {k : Nat} {A : Fin k -> \Type} (R : \Pi {j : Fin k} -> A j -> A j -> \Type) (refl : \Pi {j : Fin k} (x : A j) -> R x x) {B : \Type} (S : B -> B -> \Type) (h : DArray A -> B)
                       (p : \Pi (l l' : DArray A) -> (\Pi (j : Fin k) -> R (l j) (l' j)) -> S (h l) (h l'))
                       (ts : DArray (\lam j => Quotient (R {j}))) : Quotient S
      => \case liftArray R refl ts \with {
        | in~ l => in~ (h l)
        | ~-equiv x y r i => ~-equiv (h x) (h y) (p x y r) i
      }

    \lemma liftChoice {A : Choice} (B : A -> \Set) (R : \Pi {a : A} -> B a -> B a -> \Type) (f : \Pi (a : A) -> Quotient (\lam b b' => R {a} b b'))
      :  (g : \Pi (a : A) -> B a)  a (in~ (g a) = f a)
      => TruncP.map (choice {_} {\lam a => \Sigma (b : B a) (in~ b = f a)} (\lam a => cases (f a) \with { | in~ b => inP (b,idp) }))
          (\lam g => (\lam a => (g a).1, \lam a => (g a).2))
  }

\func ~-pequiv {A : \Type} {R : A -> A -> \Type} {x y : A} (r : R x y) : in~ x = in~ y => path (~-equiv x y r)

\type SubQuotient {A : \Type} (R : A -> A -> \Type)
  => Quotient {\Sigma (a : A) (R a a)} (\lam p q => R p.1 q.1)

\func QuotientDec {R : Equivalence} (D : \Pi (a a' : R) -> Dec (a ~ a')) : DecSet (Quotient (~)) \cowith
  | decideEq (in~ a) (in~ a') => \case D a a' \with {
    | yes e => yes (~-pequiv e)
    | no q => no \lam r => q (Quotient.equalityEquiv R r)
  }