\import Data.Array
\import Data.Or
\import Function.Meta ($)
\import Logic
\import Logic.Meta
\import Meta
\import Order.Lattice
\import Order.PartialOrder
\import Order.StrictOrder
\import Paths
\import Paths.Meta
\import Set
\open JoinSemilattice
\open MeetSemilattice

\class TotalOrder \extends DistributiveLattice {
  | totality (x y : E) : x <= y || y <= x

  \default meet (x y : E) : E => (tmeet (totality x y)).1
  \default meet-left {x y : E} : meet x y <= x => (tmeet (totality x y)).2
  \default meet-right {x y : E} : meet x y <= y => (tmeet (totality x y)).3
  \default meet-univ {x y z : E} : z <= x -> z <= y -> z <= meet x y => (tmeet (totality x y)).4 z

  \default join (x y : E) : E => (tjoin (totality x y)).1
  \default join-left {x y : E} : x <= join x y => (tjoin (totality x y)).2
  \default join-right {x y : E} : y <= join x y => (tjoin (totality x y)).3
  \default join-univ {x y z : E} : x <= z -> y <= z -> join x y <= z => (tjoin (totality x y)).4 z

  | ldistr>= {x} {y} {z} =>
    \case totality y z \with {
      | byLeft y<=z => meet-monotone <=-refl (join-univ y<=z <=-refl) <=∘ join-right
      | byRight z<=y => meet-monotone <=-refl (join-univ <=-refl z<=y) <=∘ join-left
    }

  \lemma meet-isMin (x y : E) : (x  y = x) || (x  y = y)
    => \case totality x y \with {
      | byLeft x<=y => byLeft (<=-antisymmetric Lattice.meet-left (Lattice.meet-univ <=-refl x<=y))
      | byRight y<=x => byRight (<=-antisymmetric Lattice.meet-right (Lattice.meet-univ y<=x <=-refl))
    }

  \lemma meet-prop (P : E -> \Prop) {x y : E} (Px : P x) (Py : P y) : P (x  y)
    => \case meet-isMin x y \with {
      | byLeft p => transportInv P p Px
      | byRight p => transportInv P p Py
    }

  \lemma Big01_meet-isMin (l : Array E) (x : E) : (\Sigma (j : Fin l.len) (Big () x l = l j)) || (Big () x l = x) \elim l
    | nil => byRight idp
    | a :: l => \case meet-isMin a (Big () x l), Big01_meet-isMin l x \with {
      | byLeft r, _ => byLeft (0, r)
      | byRight r, byLeft q => byLeft (suc q.1, r *> q.2)
      | byRight r, byRight q => byRight (r *> q)
    }

  \lemma Big_meet-isMin {n : Nat} (l : Array E (suc n)) :  (j : Fin (suc n)) (Big_∧ l = l j) \elim l
    | a :: l => \case Big01_meet-isMin l a \with {
      | byLeft r => inP (suc r.1, r.2)
      | byRight r => inP (0, r)
    }

  \lemma join-isMax (x y : E) : (x  y = x) || (x  y = y)
    => \case totality x y \with {
      | byLeft x<=y => byRight (<=-antisymmetric (Lattice.join-univ x<=y <=-refl) Lattice.join-right)
      | byRight y<=x => byLeft (<=-antisymmetric (Lattice.join-univ <=-refl y<=x) Lattice.join-left)
    }

  \lemma join-prop (P : E -> \Prop) {x y : E} (Px : P x) (Py : P y) : P (x  y)
    => \case join-isMax x y \with {
      | byLeft p => transportInv P p Px
      | byRight p => transportInv P p Py
    }

  \lemma Big01_join-isMax (l : Array E) (x : E) : (\Sigma (j : Fin l.len) (Big () x l = l j)) || (Big () x l = x) \elim l
    | nil => byRight idp
    | a :: l => \case join-isMax a (Big () x l), Big01_join-isMax l x \with {
      | byLeft r, _ => byLeft (0, r)
      | byRight r, byLeft q => byLeft (suc q.1, r *> q.2)
      | byRight r, byRight q => byRight (r *> q)
    }

  \lemma Big_join-isMax {n : Nat} (l : Array E (suc n)) :  (j : Fin (suc n)) (Big_∨ l = l j) \elim l
    | a :: l => \case Big01_join-isMax l a \with {
      | byLeft r => inP (suc r.1, r.2)
      | byRight r => inP (0, r)
    }
} \where {
  \func tmeet {E : Poset} {x y : E} => ||.rec MeetSemilattice.Meets-isProp
                                              (\lam x<=y => (x, E.<=-refl, x<=y, \lam z z<=x _ => z<=x))
                                              (\lam y<=x => (y, y<=x, E.<=-refl, \lam z _ z<=y => z<=y))

  \func tjoin {E : Poset} {x y : E} => ||.rec JoinSemilattice.Joins-isProp
                                              (\lam x<=y => (y, x<=y, E.<=-refl, \lam z _ y<=z => y<=z))
                                              (\lam y<=x => (x, E.<=-refl, y<=x, \lam z z<=x _ => z<=x))
}

\class BiorderedSet \extends StrictPoset, Poset {
  | <-transitive-right \alias \infixr 9 <∘r {a1 a2 a3 : E} : a1 <= a2 -> a2 < a3 -> a1 < a3
  | <-transitive-left \alias \infixl 8 <∘l {a1 a2 a3 : E} : a1 < a2 -> a2 <= a3 -> a1 < a3
  | <=-less {a1 a2 : E} : a1 < a2 -> a1 <= a2

  \func op : BiorderedSet \cowith
    | StrictPoset => StrictPoset.op
    | Poset => Poset.op
    | <-transitive-right p q => q <∘l p
    | <-transitive-left p q => q <∘r p
    | <=-less => <=-less
}

-- | A linearly ordered set.
\class LinearOrder \extends BiorderedSet {
  | <-comparison (y : E) {x z : E} : x < z -> x < y || y < z
  | <-connectedness {x y : E} : Not (x < y) -> Not (y < x) -> x = y

  | <= => <=
  | <=-refl => <-irreflexive
  | <=-transitive {x} {y} {z} x<=y y<=z => \case <-comparison y __ \with {
    | byLeft z<y => y<=z z<y
    | byRight y<x => x<=y y<x
  }
  | <=-antisymmetric x<=y y<=x => <-connectedness y<=x x<=y

  | <-transitive-right {a1} {a2} {a3} a2/<a1 a2<a3 => \case <-comparison a1 a2<a3 \with {
    | byLeft a2<a1 => absurd (a2/<a1 a2<a1)
    | byRight a1<a3 => a1<a3
  }
  | <-transitive-left {a1} {a2} {a3} a1<a2 a3/<a2 => \case <-comparison a3 a1<a2 \with {
    | byLeft a1<a3 => a1<a3
    | byRight a3<a2 => absurd (a3/<a2 a3<a2)
  }
  | <=-less p => \lam q => <-irreflexive (p <∘ q)

  \func op : LinearOrder \cowith
    | StrictPoset => StrictPoset.op
    | <-comparison y x<z => \case <-comparison y x<z \with {
      | byLeft z<y => byRight z<y
      | byRight y<x => byLeft y<x
    }
    | <-connectedness p q => <-connectedness q p
} \where {
  \class With# \extends LinearOrder, Set#
    | # x y => x < y || y < x
    | #-irreflexive x#x => \case x#x \with {
      | byLeft x<x => <-irreflexive x<x
      | byRight x<x => <-irreflexive x<x
    }
    | #-symmetric x#y => \case x#y \with {
      | byLeft x<y => byRight x<y
      | byRight y<x => byLeft y<x
    }
    | #-comparison x y z x#z => \case x#z \with {
      | byLeft x<z => \case <-comparison y x<z \with {
        | byLeft x<y => byLeft (byLeft x<y)
        | byRight y<z => byRight (byLeft y<z)
      }
      | byRight z<x => \case <-comparison y z<x \with {
        | byLeft z<y => byRight (byRight z<y)
        | byRight y<x => byLeft (byRight y<x)
      }
    }
    | tightness x/#y => <-connectedness (\lam x<y => x/#y (byLeft x<y)) (\lam y<x => x/#y (byRight y<x))

  \type \infix 4 <= {A : StrictPoset} (a a' : A) => Not (a' < a)

  \lemma <_<= {A : StrictPoset} {a a' : A} (p : a < a') : a <= a'
    => \lam q => <-irreflexive (p <∘ q)

  \lemma notLess {A : StrictPoset} {a a' : A} (p : a <= a') (q : a' < a) : Empty
    => p q

  -- | A linearly ordered set with decidable inequality.
  \class Dec \extends LinearOrder, DecSet, TotalOrder {
    \field trichotomy (x y : E) : Tri x y

    | <-comparison y {x} {z} x<z => \case trichotomy x y \with {
      | less x<y => byLeft x<y
      | equals x=y => byRight (transport (`< z) x=y x<z)
      | greater y<x => byRight (y<x <∘ x<z)
    }
    | <-connectedness {x} {y} x/<y y/<x => \case trichotomy x y \with {
      | less x<y => absurd (x/<y x<y)
      | equals x=y => x=y
      | greater y<x => absurd (y/<x y<x)
    }
    | totality x y => \case trichotomy x y \with {
      | less x<y => byLeft (<=-less x<y)
      | equals x=y => byLeft (rewrite x=y <-irreflexive)
      | greater y<x => byRight (<=-less y<x)
    }

    \default meet (x y : E) : E
      => \case trichotomy x y \with {
        | less _ => x
        | equals _ => x
        | greater _ => y
      }
    \default meet-left {x y : E} : meet x y <= x
      => unfold meet (mcases <=-refl \with {
        | greater y<x => <=-less y<x
      })
    \default meet-right {x y : E} : meet x y <= y
      => unfold meet (mcases \with {
        | less x<y => <=-less x<y
        | equals x=y => rewrite x=y <=-refl
        | greater _ => <=-refl
      })
    \default meet-univ {x y z : E} : z <= x -> z <= y -> z <= meet x y
      => \lam z<=x z<=y => unfold meet (mcases z<=x \with {
        | greater _ => z<=y
      })

    \default join (x y : E) : E
      => \case trichotomy x y \with {
        | less _ => y
        | equals _ => x
        | greater _ => x
      }
    \default join-left {x y : E} : x <= join x y
      => unfold join (mcases <=-refl \with {
        | less x<y => <=-less x<y
      })
    \default join-right {x y : E} : y <= join x y
      => unfold join (mcases \with {
        | less _x => <=-refl
        | equals x=y => rewrite x=y <=-refl
        | greater y<x => <=-less y<x
      })
    \default join-univ {x y z : E} : x <= z -> y <= z -> join x y <= z
      => \lam x<=z y<=z => unfold join (mcases x<=z \with {
        | less _ => y<=z
      })

    \default decideEq x y => \case trichotomy x y \with {
      | less x<y => no (\lam x=y => <-irreflexive (transport (`< y) x=y x<y))
      | equals x=y => yes x=y
      | greater y<x => no (\lam x=y => <-irreflexive (transport (y <) x=y y<x))
    }

    \func op : Dec \cowith
      | LinearOrder => LinearOrder.op
      | decideEq => decideEq
      | trichotomy x y => \case trichotomy x y \with {
        | less y<x => greater y<x
        | equals x=y => equals x=y
        | greater x<y => less x<y
      }
  }

  \func <-dec {A : Dec} (a a' : A) : Decide (a < a') \cowith
    | decide => \case dec<_<= a a' \with {
      | inl a<a' => yes a<a'
      | inr a'<=a => no a'<=a
    }

  \lemma <=_/= {A : Dec} {a a' : A} (p : a <= a') (q : a /= a') : a < a'
    => \case trichotomy a a' \with {
         | less r => r
         | equals r => absurd (q r)
         | greater r => absurd (p r)
       }

  \func <=-dec {A : Dec} {a a' : A} (p : a <= a') : (a < a') `Or` (a = a') => \case trichotomy a a' \with {
    | less a<a' => inl a<a'
    | equals a=a' => inr a=a'
    | greater a'<a => absurd (p a'<a)
  }

  \func dec<_<= {A : Dec} (a a' : A) : (a < a') `Or` (a' <= a) => \case trichotomy a a' \with {
    | less a<a' => inl a<a'
    | equals a=a' => inr (rewrite a=a' <-irreflexive)
    | greater a'<a => inr (<=-less a'<a)
  }

  \lemma dec<_reduce {A : Dec} {a a' : A} (p : a < a') : dec<_<= a a' = inl p
    => unfold dec<_<= (mcases contradiction \with {
      | less a<a' => pmap inl prop-pi
    })

  \lemma dec<=_reduce {A : Dec} {a a' : A} (p : a' <= a) : dec<_<= a a' = inr p
    => unfold dec<_<= (mcases (pmap inr prop-pi) \with {
      | less a<a' => contradiction
    })

  \lemma dec<= {A : Dec} (a a' : A) : Set.Dec (a <= a')
    => \case dec<_<= a' a \with {
         | inl q => no \lam p => <-irreflexive (q <∘l p)
         | inr q => yes q
       }

  \lemma trichotomy<_reduce {A : Dec} {a a' : A} (p : a < a') : trichotomy a a' = less p
    => cases (trichotomy a a') contradiction \with {
      | less q => pmap less prop-pi
    }

  \lemma trichotomy=_reduce {A : Dec} {a a' : A} (p : a = a') : trichotomy a a' = equals p
    => cases (trichotomy a a') contradiction \with {
      | equals q => pmap equals prop-pi
    }

  \lemma trichotomy>_reduce {A : Dec} {a a' : A} (p : a' < a) : trichotomy a a' = greater p
    => cases (trichotomy a a') contradiction \with {
      | greater q => pmap greater prop-pi
    }

  \lemma meet/=left {A : LinearOrder} {_ : MeetSemilattice A (<=)} {a b : A} (p : a  b /= a) : a  b = b
    => <=-antisymmetric meet-right $ meet-univ (\lam a<b => p $ <=-antisymmetric meet-left $ meet-univ <=-refl $ <_<= a<b) <=-refl

  \lemma meet/=right {A : LinearOrder} {_ : MeetSemilattice A (<=)} {a b : A} (p : a  b /= b) : a  b = a
    => rewrite meet-comm $ meet/=left $ later $ rewrite meet-comm p

  \lemma <_meet-univ {A : LinearOrder} {_ : MeetSemilattice A (<=)} {x y z : A} (x<y : x < y) (x<z : x < z) : x < y  z
    => \case <-comparison (y  z) x<y \with {
      | byLeft q => q
      | byRight q => rewrite (meet/=left $ <_/= q) x<z
    }

  \lemma <_meet-monotone {A : Dec} {x x' y y' : A} (p : x < y) (q : x' < y') : x  x' < y  y'
    => <_meet-univ (meet-left <∘r p) (meet-right <∘r q)

  \lemma Big_<_meet-univ {A : Dec} {l : Array A} {x y : A} (p : y < x) (f : \Pi (i : Fin l.len) -> y < l i) : y < Big () x l \elim l
    | nil => p
    | :: a l => <_meet-univ (f 0) (Big_<_meet-univ p (\lam i => f (suc i)))

  \lemma join/=left {A : LinearOrder} {_ : JoinSemilattice A (<=)} {a b : A} (p : a /= a  b) : a  b = b
    => <=-antisymmetric (join-univ (\lam b<a => p $ <=-antisymmetric join-left (join-univ <=-refl $ <_<= b<a)) <=-refl) join-right

  \lemma join/=right {A : LinearOrder} {_ : JoinSemilattice A (<=)} {a b : A} (p : b /= a  b) : a  b = a
    => rewrite join-comm $ join/=left $ later $ rewrite join-comm p

  \lemma <_join-univ {A : LinearOrder} {_ : JoinSemilattice A (<=)} {x y z : A} (x<z : x < z) (y<z : y < z) : x  y < z
    => \case <-comparison (x  y) x<z \with {
      | byLeft q => rewrite (join/=left $ <_/= q) y<z
      | byRight q => q
    }

  \lemma <_join-monotone {A : Dec} {x x' y y' : A} (p : x < y) (q : x' < y') : x  x' < y  y'
    => <_join-univ (p <∘l join-left) (q <∘l join-right)

  \lemma Big_<_join-univ {A : Dec} {l : Array A} {x y : A} (p : x < y) (f : \Pi (i : Fin l.len) -> l i < y) : Big () x l < y \elim l
    | nil => p
    | :: a l => <_join-univ (f 0) (Big_<_join-univ p (\lam i => f (suc i)))

  \func findMin {A : Dec} {n : Nat} (l : Array A (suc n)) : \Sigma (j : Fin (suc n)) (Big_∧ l = l j) \elim n, l
    | 0, a :: l => (0, idp)
    | suc n, a :: a' :: l => \case dec<_<= a' (Big () a l) \with {
      | inl r => (1, <=-antisymmetric meet-left $ meet-univ <=-refl $ <_<= r)
      | inr r => \case findMin (a :: l) \with {
        | (0, p) => (0, rewrite p $ <=-antisymmetric meet-right $ meet-univ (rewrite p in r) <=-refl)
        | (suc j, p) => (suc (suc j), rewrite p $ <=-antisymmetric meet-right $ meet-univ (rewrite p in r) <=-refl)
      }
    }

  \func findMax {A : Dec} {n : Nat} (l : Array A (suc n)) : \Sigma (j : Fin (suc n)) (Big_∨ l = l j) \elim n, l
    | 0, a :: l => (0, idp)
    | suc n, a :: a' :: l => \case dec<_<= a' (Big () a l) \with {
      | inl r => \case findMax (a :: l) \with {
        | (0, p) => (0, rewrite p $ <=-antisymmetric (join-univ (<_<= $ rewrite p in r) <=-refl) join-right)
        | (suc j, p) => (suc (suc j), rewrite p $ <=-antisymmetric (join-univ (<_<= $ rewrite p in r) <=-refl) join-right)
      }
      | inr r => (1, <=-antisymmetric (join-univ <=-refl r) join-left)
    }
}

\class DenseLinearOrder \extends LinearOrder
  | isDense {x z : E} : x < z ->  (y : E) (x < y) (y < z)
  \where {
    \class Dec \extends DenseLinearOrder, LinearOrder.Dec
  }

\class UnboundedDenseLinearOrder \extends DenseLinearOrder
  | withoutUpperBound (x : E) :  (y : E) (x < y)
  | withoutLowerBound (x : E) :  (y : E) (y < x)
  \where {
    \class Dec \extends UnboundedDenseLinearOrder, DenseLinearOrder.Dec
  }

\data Tri {A : StrictPoset} (a a' : A)
  | less (a < a')
  | equals (a = a')
  | greater (a > a')
  \where
    \use \level levelProp : \Prop