\import Data.Array
\import Data.Bool
\import Function.Meta
\import Logic
\import Meta
\import Paths
\import Paths.Meta
\import Set.Fin(FinSet,DecFin,SigmaFin)

\data Dec (E : \Prop)
  | yes E
  | no (Not E)
  \where {
    \use \level levelProp : \Prop

    \func rec {R : \Type} {E : \Prop} (y : E -> R) (n : Not E -> R) (d : Dec E) : R \elim d
      | yes e => y e
      | no q => n q
  }

\func decToBool {E : \Prop} (d : Dec E) : Bool
  | yes _ => true
  | no _ => false

\class Decide (E : \Prop)
  | decide : Dec E

\lemma dec_decide {E : \Prop} (d : Dec E) : Decide E \cowith
  | decide => d

\lemma dec_yes_reduce {E : \Prop} {d : Dec E} (e : E) : d = yes e
  => prop-pi

\lemma dec_no_reduce {E : \Prop} {d : Dec E} (q : Not E) : d = no q
  => prop-pi

\lemma Dec_|| {A B : \Prop} (d : Dec A) (e : Dec B) : Dec (A || B) \elim d, e
  | yes e, _ => yes (byLeft e)
  | _, yes e => yes (byRight e)
  | no q, no q' => no \case __ \with {
    | byLeft a => q a
    | byRight b => q' b
  }

\func SigmaDecide {A : Decide} (B : A -> Decide) : Decide (\Sigma (a : A) (B a)) \cowith
  | decide => \case A.decide \with {
    | yes a => \case decide {B a} \with {
      | yes b => yes (a,b)
      | no q => no \lam p => q $ transport (B __) prop-pi p.2
    }
    | no q => no \lam p => q p.1
  }

\instance ProductDecide (A B : Decide) => SigmaDecide {A} (\lam _ => B)

\lemma NotDec {P : \Prop} (d : Dec P) : Dec (Not P) \elim d
  | yes a => no \lam f => f a
  | no q => yes q

\instance NotDecide (A : Decide) : Decide (Not A)
  | decide => NotDec A.decide

\class BaseSet (E : \Set)

\class SubSet (S : BaseSet) (\classifying contains : S -> \Prop) {
  \func struct : BaseSet \cowith
    | E => \Sigma (x : S) (contains x)
}

\class DecSubSet \extends SubSet
  | isDec (x : S) : Dec (contains x)
  \where {
    \func max {S : BaseSet} : DecSubSet S \cowith
      | contains _ => \Sigma
      | isDec _ => yes ()

    \lemma isFin (S : FinSet) (D : DecSubSet S) : FinSet D.struct
      => SigmaFin S (\lam x => DecFin (D.isDec x))
  }

\class SeparatedSet \extends BaseSet
  | separatedEq {x y : E} : Not (Not (x = y)) -> x = y

-- | A set with a tight apartness relation.
\class Set# \extends SeparatedSet {
  | \infix 4 # : E -> E -> \Prop
  | #-irreflexive {x : E} : Not (x # x)
  | #-symmetric {x y : E} : x # y -> y # x
  | #-comparison (x y z : E) : x # z -> x # y || y # z
  | tightness {x y : E} : Not (x # y) -> x = y
  | separatedEq {x} ~~x=y => tightness (\lam x#y => ~~x=y (\lam x=y => #-irreflexive (transport (x #) (inv x=y) x#y)))

  \lemma apartNotEqual {x y : E} (x#y : x # y) : x /= y =>
    \lam x=y => #-irreflexive (transport (x #) (inv x=y) x#y)
}

\class DecSet \extends BaseSet, Set# {
  \field decideEq (x y : E) : Dec (x = y)
  | nonEqualApart {x y : E} : x /= y -> x # y
  | \infix 4 == : E -> E -> Bool
  | == x y => decToBool (decideEq x y)

  \default # x y : \Prop => x /= y
  \default nonEqualApart \as nonEqualApartImpl {x} {y} (p : x /= y) : x # y => p
  \default #-irreflexive {x} (x/=x : x # x) : Empty => x/=x idp
  \default #-symmetric {x} {y} (x/=y : x # y) : y # x => /=-sym x/=y
  \default #-comparison x y z (x/=z : x # z) : x # y || y # z => \case decideEq x y \with {
    | yes x=y => byRight (\lam y=z => x/=z (x=y *> y=z))
    | no x/=y => byLeft x/=y
  }
  \default tightness {x} {y} (x//=y : Not (x # y)) : x = y => \case decideEq x y \with {
    | yes x=y => x=y
    | no x/=y => absurd (x//=y x/=y)
  }
}

\func SigmaDecSet {A : DecSet} (B : A -> DecSet) : DecSet (\Sigma (a : A) (B a)) \cowith
  | decideEq p q => \case decideEq p.1 q.1 \with {
    | yes e => \case decideEq (transport (\lam a => B a) e p.2) q.2 \with {
      | yes e' => yes (ext (e,e'))
      | no n => no (\lam e' => n (rewrite (prop-isProp e (pmap __.1 e')) (pmapd __.2 e')))
    }
    | no n => no (\lam e => n (pmap __.1 e))
  }

\func SubDecSet {A : DecSet} {B : A -> \Prop} : DecSet (\Sigma (a : A) (B a))
  => SigmaDecSet \lam a => \new DecSet (B a) {
    | decideEq b b' => yes prop-pi
  }

\instance ProductDecSet (A B : DecSet) : DecSet (\Sigma A B)
  => SigmaDecSet (\lam _ => B)

\instance EqualityDecide (A : DecSet) (a a' : A) : Decide (a = a')
  | decide => decideEq a a'

\instance ArrayDec (A : DecSet) (n : Nat) : DecSet (Array A n)
  | decideEq => aux
  \where {
    \private \lemma aux {n : Nat} (l l' : Array A n) : Dec (l = l') \elim n, l, l'
      | 0, nil, nil => yes idp
      | suc n, a :: l, a' :: l' => \case decideEq a a', aux l l' \with {
        | yes e, yes e' => yes $ pmap2 (::) e e'
        | _, no q => no \lam p => q (pmap taild p)
        | no q, _ => no \lam p => q (unhead p)
      }
  }

\lemma decideEq=_reduce {A : DecSet} {x y : A} (p : x = y) : decideEq x y = yes p
  => prop-pi

\lemma decideEq/=_reduce {A : DecSet} {x y : A} (p : x /= y) : decideEq x y = no p
  => prop-pi

\lemma ==_= {A : DecSet} {a a' : A} (so : So (a == a')) : a = a'
  => unfold (==) at so $ mcases {decToBool} so _ \with {
    | yes p, _ => p
    | no n, ()
  }

\truncated \data Trunc0 (A : \Type) : \Set
  | in0 A