{-# OPTIONS -WnoUnsupportedIndexedMatch #-}

module Cubical.Structures.Set.CMon.SList.Sort.Order where

open import Cubical.Structures.Prelude

open import Cubical.Data.Empty as 
open import Cubical.Data.Maybe as Maybe
open import Cubical.Data.Nat
open import Cubical.Data.Nat.Order renaming (_≤_ to _≤ℕ_; _<_ to _<ℕ_)
open import Cubical.Data.Sigma
open import Cubical.Data.Sum as 
open import Cubical.Induction.WellFounded
open import Cubical.Relation.Binary
open import Cubical.Relation.Binary.Order
open import Cubical.Relation.Nullary
open import Cubical.Relation.Nullary.HLevels
open import Cubical.Data.List hiding (tail)
open import Cubical.Data.List.Properties hiding (tail)
open import Cubical.HITs.PropositionalTruncation as P
import Cubical.Data.List as L
open import Cubical.Functions.Logic as L hiding (¬_; )

import Cubical.Structures.Set.Mon.Desc as M
import Cubical.Structures.Set.CMon.Desc as M
import Cubical.Structures.Free as F
open import Cubical.Structures.Sig
open import Cubical.Structures.Str public
open import Cubical.Structures.Tree
open import Cubical.Structures.Eq
open import Cubical.Structures.Arity
open import Cubical.Structures.Set.Mon.List
open import Cubical.Structures.Set.CMon.SList.Base renaming (_∷_ to _∷*_; [] to []*; [_] to [_]*; _++_ to _++*_)
import Cubical.Structures.Set.CMon.SList.Base as S
open import Cubical.Structures.Set.CMon.SList.Length as S
open import Cubical.Structures.Set.CMon.SList.Membership as S
open import Cubical.Structures.Set.CMon.SList.Sort.Base

open Iso

private
  variable
     : Level
    A : Type 

module Order→Sort {A : Type } (_≤_ : A -> A -> Type ) (≤-isToset : IsToset _≤_) (_≤?_ :  x y -> Dec (x  y)) where
  open IsToset ≤-isToset
  open Membership is-set
  open Membership* is-set

  isDiscreteA : Discrete A
  isDiscreteA x y with x ≤? y | y ≤? x
  ... | yes p | yes q = yes (is-antisym x y p q)
  ... | yes p | no ¬q = no λ r -> ¬q (subst (_≤ x) r (is-refl x))
  ... | no ¬p | yes q = no λ r -> ¬p (subst (x ≤_) r (is-refl x))
  ... | no ¬p | no ¬q = ⊥.rec $ P.rec isProp⊥ (⊎.rec ¬p ¬q) (is-total x y)

  A-is-loset : Loset _ _
  A-is-loset = Toset→Loset (A , tosetstr _≤_ ≤-isToset) _≤?_

  _<_ : A -> A -> Type 
  _<_ = LosetStr._<_ (A-is-loset .snd)

  <-isLoset : IsLoset _<_
  <-isLoset = LosetStr.isLoset (A-is-loset .snd)

  insert : A -> List A -> List A
  insert x [] = [ x ]
  insert x (y  ys) with x ≤? y
  ... | yes p = x  y  ys
  ... | no ¬p = y  insert x ys

  insertβ1 :  x y ys -> x  y -> insert x (y  ys)  x  y  ys
  insertβ1 x y ys p with x ≤? y
  ... | yes _ = refl
  ... | no ¬p = ⊥.rec (¬p p)

  insertβ2 :  x y ys -> ¬ (x  y) -> insert x (y  ys)  y  insert x ys
  insertβ2 x y ys ¬p with x ≤? y
  ... | yes p = ⊥.rec (¬p p)
  ... | no ¬_ = refl

  insert≤ :  x y xs ys -> insert x (insert y xs)  x  y  ys -> x  y
  insert≤ x y [] ys p with x ≤? y
  ... | yes x≤y = x≤y
  ... | no ¬x≤y = subst (_≤ y) (cons-inj₁ p) (is-refl y)
  insert≤ x y (z  zs) ys p with y ≤? z
  ... | yes y≤z = lemma
    where
    lemma : x  y
    lemma with x ≤? y
    ... | yes x≤y = x≤y
    ... | no ¬x≤y = subst (_≤ y) (cons-inj₁ p) (is-refl y)
  ... | no ¬y≤z = lemma
    where
    lemma : x  y
    lemma with x ≤? z
    ... | yes x≤z = subst (x ≤_) (cons-inj₁ (cons-inj₂ p)) x≤z
    ... | no ¬x≤z = ⊥.rec (¬x≤z (subst (_≤ z) (cons-inj₁ p) (is-refl z)))

  insertCons :  x xs ys -> x  xs  insert x ys -> xs  ys
  insertCons x xs [] p = cons-inj₂ p
  insertCons x xs (y  ys) p with x ≤? y
  ... | yes x≤y = cons-inj₂ p
  ... | no ¬x≤y = ⊥.rec (¬x≤y (subst (x ≤_) (cons-inj₁ p) (is-refl x)))

  private
    not-total-impossible :  {x y} -> ¬(x  y) -> ¬(y  x) -> 
    not-total-impossible {x} {y} p q =
      P.rec isProp⊥ (⊎.rec p q) (is-total x y)

  insertInsert :  x y xs -> insert x (insert y xs)  insert y (insert x xs)
  insertInsert x y [] with x ≤? y | y ≤? x
  ... | yes x≤y | no ¬y≤x = refl
  ... | no ¬x≤y | yes y≤x = refl
  ... | no ¬x≤y | no ¬y≤x = ⊥.rec (not-total-impossible ¬x≤y ¬y≤x)
  ... | yes x≤y | yes y≤x = let x≡y = is-antisym x y x≤y y≤x in cong₂  u v -> u  v  []) x≡y (sym x≡y)
  insertInsert x y (z  zs) with y ≤? z | x ≤? z
  ... | yes y≤z | yes x≤z = case1 y≤z x≤z
    where
    case1 : y  z -> x  z -> insert x (y  z  zs)  insert y (x  z  zs)
    case1 y≤z x≤z with x ≤? y | y ≤? x
    ... | yes x≤y | yes y≤x = let x≡y = is-antisym x y x≤y y≤x in cong₂  u v -> (u  v  z  zs)) x≡y (sym x≡y)
    ... | yes x≤y | no ¬y≤x = sym (congS (x ∷_) (insertβ1 y z zs y≤z))
    ... | no ¬x≤y | yes y≤x = congS (y ∷_) (insertβ1 x z zs x≤z)
    ... | no ¬x≤y | no ¬y≤x = ⊥.rec (not-total-impossible ¬x≤y ¬y≤x)
  ... | yes y≤z | no ¬x≤z = case2 y≤z ¬x≤z
    where
    case2 : y  z -> ¬(x  z) -> insert x (y  z  zs)  insert y (z  insert x zs)
    case2 y≤z ¬x≤z with x ≤? y
    ... | yes x≤y = ⊥.rec (¬x≤z (is-trans x y z x≤y y≤z))
    ... | no ¬x≤y = congS (y ∷_) (insertβ2 x z zs ¬x≤z)  sym (insertβ1 y z _ y≤z)
  ... | no ¬y≤z | yes x≤z = case3 ¬y≤z x≤z
    where
    case3 : ¬(y  z) -> x  z -> insert x (z  insert y zs)  insert y (x  z  zs)
    case3 ¬y≤z x≤z with y ≤? x
    ... | yes y≤x = ⊥.rec (¬y≤z (is-trans y x z y≤x x≤z))
    ... | no ¬y≤x = insertβ1 x z _ x≤z  congS (x ∷_) (sym (insertβ2 y z zs ¬y≤z))
  ... | no ¬y≤z | no ¬x≤z =
    insert x (z  insert y zs) ≡⟨ insertβ2 x z _ ¬x≤z 
    z  insert x (insert y zs) ≡⟨ congS (z ∷_) (insertInsert x y zs) 
    z  insert y (insert x zs) ≡⟨ sym (insertβ2 y z _ ¬y≤z) 
    insert y (z  insert x zs) 

  sort : SList A -> List A
  sort = Elim.f []
     x xs -> insert x xs)
     x y xs -> insertInsert x y xs)
     _ -> isOfHLevelList 0 is-set)

  insertIsPermute :  x xs -> list→slist (x  xs)  list→slist (insert x xs)
  insertIsPermute x [] = refl
  insertIsPermute x (y  ys) with x ≤? y
  ... | yes p = refl
  ... | no ¬p =
    x ∷* y ∷* list→slist ys ≡⟨ swap x y _ 
    y ∷* x ∷* list→slist ys ≡⟨ congS (y ∷*_) (insertIsPermute x ys) 
    y ∷* list→slist (insert x ys) 

  open Sort is-set

  sortIsPermute : isSection sort
  sortIsPermute = ElimProp.f (trunc _ _) refl lemma
    where
    lemma :  x {xs} p -> list→slist (sort (x ∷* xs))  x ∷* xs
    lemma x {xs} p = sym $
      x ∷* xs ≡⟨ congS (x ∷*_) (sym p) 
      list→slist (x  sort xs) ≡⟨ insertIsPermute x (sort xs) 
      list→slist (insert x (sort xs)) ≡⟨⟩
      list→slist (sort (x ∷* xs)) 

  tailIsSorted :  x xs -> isSorted sort (x  xs) -> isSorted sort xs
  tailIsSorted x xs = P.rec squash₁ (uncurry lemma)
    where
    lemma :  ys p -> isSorted sort xs
    lemma ys p =  (list→slist xs) , sym (insertCons x _ _ sortProof) ∣₁
      where
      ysProof : ys  x ∷* list→slist xs
      ysProof = sym (sortIsPermute ys)  (congS list→slist p)
      sortProof : x  xs  insert x (sort (list→slist xs))
      sortProof = sym p  congS sort ysProof

  -- Inductive definition of IsSorted
  data IsSorted : List A -> Type  where
    sorted-nil : IsSorted []
    sorted-one :  x -> IsSorted [ x ]
    sorted-cons :  x y zs -> x  y -> IsSorted (y  zs) -> IsSorted (x  y  zs)

  isPropSorted :  xs -> isProp (IsSorted xs)
  isPropSorted [] = isContr→isProp (sorted-nil , λ { sorted-nil  refl })
  isPropSorted (x  []) = isContr→isProp (sorted-one x , λ { (sorted-one .x)  refl })
  isPropSorted (x  y  xs) (sorted-cons .x .y .xs ϕ p) (sorted-cons .x .y .xs ψ q) =
    cong₂ (sorted-cons x y xs) (is-prop-valued x y ϕ ψ) (isPropSorted (y  xs) p q)

  open Sort.Section is-set

  isSort' : (SList A -> List A) -> Type _
  isSort' f = (∀ xs -> list→slist (f xs)  xs) × (∀ xs ->  IsSorted (f xs) ∥₁)

  tailSorted' :  {x xs} -> IsSorted (x  xs) -> IsSorted xs
  tailSorted' (sorted-one ._) = sorted-nil
  tailSorted' (sorted-cons ._ _ _ _ p) = p

  ≤tail :  {x y ys} -> y  (x  ys) -> IsSorted (x  ys) -> x  y
  ≤tail {y = y} p (sorted-one x) = subst (_≤ y) (x∈[y]→x≡y y x p) (is-refl y)
  ≤tail {x} {y = z} p (sorted-cons x y zs q r) =
    P.rec (is-prop-valued x z)
      (⊎.rec
         z≡x -> subst (x ≤_) (sym z≡x) (is-refl x))
         z∈ys -> is-trans x y z q (≤tail z∈ys r))
      ) p

  smallestSorted :  x xs -> (∀ y -> y  xs -> x  y) -> IsSorted xs -> IsSorted (x  xs)
  smallestSorted x .[] p sorted-nil =
    sorted-one x
  smallestSorted x .([ y ]) p (sorted-one y) =
    sorted-cons x y [] (p y (x∈[x] y)) (sorted-one y)
  smallestSorted x .(_  _  _) p (sorted-cons y z zs y≤z r) =
    sorted-cons x y (z  zs) (p y (x∈xs y (z  zs))) (smallestSorted y (z  zs) lemma r)
    where
    lemma :  a -> a  (z  zs) -> y  a
    lemma a = P.rec (is-prop-valued y a) $ ⊎.rec
       a≡z -> subst (y ≤_) (sym a≡z) y≤z)
       a∈zs -> is-trans y z a y≤z (≤tail (L.inr a∈zs) r))

  insert∈ :  {x} {y} ys -> x  insert y ys -> x  (y  ys)
  insert∈ {x} {y} ys p = ∈*→∈ x (y  ys)
    (subst (x ∈*_) (sym (insertIsPermute y ys)) (∈→∈* x (insert y ys) p))

  insertIsSorted :  x xs -> IsSorted xs -> IsSorted (insert x xs)
  insertIsSorted x [] p = sorted-one x
  insertIsSorted x (y  ys) p with x ≤? y
  ... | yes q = sorted-cons x y ys q p
  ... | no ¬q = smallestSorted y (insert x ys) (lemma ys p) IH
    where
    IH : IsSorted (insert x ys)
    IH = insertIsSorted x ys (tailSorted' p)
    y≤x : y  x
    y≤x = P.rec (is-prop-valued y x) (⊎.rec (idfun _) (⊥.rec  ¬q)) (is-total y x)
    lemma :  zs -> IsSorted (y  zs) ->  z -> z  insert x zs  y  z
    lemma zs p z r = P.rec (is-prop-valued y z)
      (⊎.rec  z≡x -> subst (y ≤_) (sym z≡x) y≤x)  z∈zs -> ≤tail (L.inr z∈zs) p)) (insert∈ zs r)

  -- TODO: refactor this since IsSorted is a prop
  sortIsSorted' :  xs ->  IsSorted (sort xs) ∥₁
  sortIsSorted' = ElimProp.f squash₁  sorted-nil ∣₁
    λ x -> P.rec squash₁ λ p ->  (insertIsSorted x _ p) ∣₁

  sortIsSorted :  xs -> IsSorted (sort xs)
  sortIsSorted xs = P.rec (isPropSorted (sort xs)) (idfun _) (sortIsSorted' xs)

  sortIsSorted'' :  xs -> isSorted sort xs ->  IsSorted xs ∥₁
  sortIsSorted'' xs = P.rec squash₁ λ (ys , p) ->
    P.map (subst IsSorted p) (sortIsSorted' ys)

  -- Step 1. show both sort xs and sort->order->sort xs give sorted list
  -- Step 2. apply this lemma
  -- Step 3. get sort->order->sort = sort
  uniqueSorted :  xs ys -> list→slist xs  list→slist ys -> IsSorted xs -> IsSorted ys -> xs  ys
  uniqueSorted [] [] p xsSorted ysSorted = refl
  uniqueSorted [] (y  ys) p xsSorted ysSorted = ⊥.rec (znots (congS S.length p))
  uniqueSorted (x  xs) [] p xsSorted ysSorted = ⊥.rec (snotz (congS S.length p))
  uniqueSorted (x  xs) (y  ys) p xsSorted ysSorted =
    cong₂ _∷_ x≡y (uniqueSorted xs ys xs≡ys (tailSorted' xsSorted) (tailSorted' ysSorted))
    where
    x≤y : x  y
    x≤y = ≤tail (∈*→∈ y (x  xs) (subst (y ∈*_) (sym p) (L.inl refl))) xsSorted
    y≤x : y  x
    y≤x = ≤tail (∈*→∈ x (y  ys) (subst (x ∈*_) p (L.inl refl))) ysSorted
    x≡y : x  y
    x≡y = is-antisym x y x≤y y≤x
    xs≡ys : list→slist xs  list→slist ys
    xs≡ys =
      list→slist xs ≡⟨ remove1-≡-lemma isDiscreteA (list→slist xs) refl 
      remove1 isDiscreteA x (list→slist (x  xs)) ≡⟨ congS (remove1 isDiscreteA x) p 
      remove1 isDiscreteA x (list→slist (y  ys)) ≡⟨ sym (remove1-≡-lemma isDiscreteA (list→slist ys) x≡y) 
      list→slist ys 

  uniqueSort :  f -> isSort' f -> f  sort
  uniqueSort f (fIsPermute , fIsSorted) = funExt λ xs ->
    P.rec2 (isOfHLevelList 0 is-set _ _)
      (uniqueSorted (f xs) (sort xs) (fIsPermute xs  sym (sortIsPermute xs)))
      (fIsSorted xs)
      (sortIsSorted' xs)

  uniqueSort' :  f xs -> isSort' f -> f xs  sort xs
  uniqueSort' f xs p = congS  g -> g xs) (uniqueSort f p)

  private
    sort→orderLemma :  x y xs -> isSorted sort (x  y  xs) -> x  y
    sort→orderLemma x y xs = P.rec (is-prop-valued x y) (uncurry lemma)
      where
      lemma :  ys p -> x  y
      lemma ys p = insert≤ x y (sort tail) xs (congS sort tailProof  p)
        where
        tail : SList A
        tail = list→slist xs
        tailProof : x ∷* y ∷* tail  ys
        tailProof = sym (congS list→slist p)  sortIsPermute ys

    sort→order :  x y xs -> isSorted sort (x  xs) -> y  (x  xs) -> x  y
    sort→order x y [] p y∈xs = subst (_≤ y) (x∈[y]→x≡y y x y∈xs) (is-refl y)
    sort→order x y (z  zs) p y∈x∷z∷zs with isDiscreteA x y
    ... | yes x≡y = subst (x ≤_) x≡y (is-refl x)
    ... | no ¬x≡y =
      P.rec (is-prop-valued x y) (⊎.rec (⊥.rec  ¬x≡y  sym) lemma) y∈x∷z∷zs
      where
      lemma : y  (z  zs) -> x  y
      lemma y∈z∷zs = is-trans x z y
        (sort→orderLemma x z zs p)
        (sort→order z y zs (tailIsSorted x (z  zs) p) y∈z∷zs)

  sortIsHeadLeast : isHeadLeast sort
  sortIsHeadLeast x y xs p y∈xs =  (x ∷* y ∷* []* , insertβ1 x y [] (sort→order x y xs p y∈xs)) ∣₁

  sortIsSortSection : isSortSection sort
  sortIsSortSection = sortIsPermute , sortIsHeadLeast , tailIsSorted

  -- counter example that obeys isHeadLeast and not tail-sort

  swap12 : List A -> List A
  swap12 [] = []
  swap12 (x  []) = x  []
  swap12 (x  y  xs) = y  x  xs

  swap12Id :  xs -> swap12 (swap12 xs)  xs
  swap12Id [] = refl
  swap12Id (x  []) = refl
  swap12Id (x  y  xs) = refl

  swap23 : List A -> List A
  swap23 [] = []
  swap23 (x  xs) = x  swap12 xs

  swap23Id :  xs -> swap23 (swap23 xs)  xs
  swap23Id [] = refl
  swap23Id (x  xs) = congS (x ∷_) (swap12Id xs)

  swap23IsPermute :  f -> isSection f -> isSection (swap23  f)
  swap23IsPermute f fIsPermute xs with f xs | inspect f xs
  ... | [] | [ p ]ᵢ = sym (congS list→slist p)  fIsPermute xs
  ... | x  [] | [ p ]ᵢ = sym (congS list→slist p)  fIsPermute xs
  ... | x  y  [] | [ p ]ᵢ = sym (congS list→slist p)  fIsPermute xs
  ... | x  y  z  ys | [ p ]ᵢ =
    x ∷* z ∷* y ∷* list→slist ys ≡⟨ congS (x ∷*_) (swap z y (list→slist ys)) 
    x ∷* y ∷* z ∷* list→slist ys ≡⟨ sym (congS list→slist p) 
    list→slist (f xs) ≡⟨ fIsPermute xs 
    xs 

  isHeadLeastOnly : SList A -> List A
  isHeadLeastOnly = swap23  sort

  isHeadLeastOnlyIsPermute : isSection isHeadLeastOnly
  isHeadLeastOnlyIsPermute = swap23IsPermute sort sortIsPermute

  private
    isHeadLeastIsSameFor2 :  u v -> isSorted sort (u  v  []) -> isSorted isHeadLeastOnly (u  v  [])
    isHeadLeastIsSameFor2 u v = P.map λ (ys , q) -> ys , congS swap23 q

    isHeadLeast→sorted :  x xs -> isSorted isHeadLeastOnly (x  xs) -> isSorted sort (x  swap12 xs)
    isHeadLeast→sorted x [] =
      P.map λ (ys , q) -> ys , sym (swap23Id (sort ys))  congS swap23 q
    isHeadLeast→sorted x (y  []) =
      P.map λ (ys , q) -> ys , sym (swap23Id (sort ys))  congS swap23 q
    isHeadLeast→sorted x (y  z  xs) =
      P.map λ (ys , q) -> ys , sym (swap23Id (sort ys))  congS swap23 q

    swap12∈ :  x xs -> x  xs -> x  swap12 xs
    swap12∈ x [] x∈ = x∈
    swap12∈ x (y  []) x∈ = x∈
    swap12∈ x (y  z  xs) = P.rec squash₁
      (⊎.rec (L.inr  L.inl) (P.rec squash₁ (⊎.rec L.inl (L.inr  L.inr))))

    swap23∈ :  x xs -> x  xs -> x  swap23 xs
    swap23∈ x (y  xs) = P.rec squash₁ (⊎.rec L.inl (L.inr  swap12∈ x xs))

    isSorted→≤ :  {x y} -> isSorted sort (x  y  []) -> x  y
    isSorted→≤ {x} {y} p = P.rec (is-prop-valued x y)
      (≤tail {ys = y  []} (L.inr (L.inl refl)))
      (sortIsSorted'' (x  y  []) p)

    isHeadLeastTailSort→x≡y :  x y -> isTailSorted isHeadLeastOnly -> x  y -> x  y
    isHeadLeastTailSort→x≡y x y hTailSort x≤y =
      is-antisym x y x≤y (isSorted→≤ (lemma1  x ∷* x ∷* y ∷* []* , lemma2 ∣₁))
      where
      -- [1, 2, 3] sorted by sort -> [1, 3, 2] sorted by isHeadLeast -> [3, 2] sorted by isHeadLeast -> [3, 2] sorted by sort
      lemma1 : isSorted sort (x  x  y  []) -> isSorted sort (y  x  [])
      lemma1 = P.rec squash₁ λ (ys , q) -> isHeadLeast→sorted y [ x ] (hTailSort x _  ys , congS swap23 q ∣₁)
      lemma2 : sort (x ∷* x ∷* [ y ]*)  x  x  y  []
      lemma2 =
        insert x (insert x [ y ]) ≡⟨ congS (insert x) (insertβ1 x y [] x≤y) 
        insert x (x  y  []) ≡⟨ insertβ1 x x [ y ] (is-refl x) 
        x  x  [ y ] 

  isHeadLeastOnlyIsHeadLeast : isHeadLeast isHeadLeastOnly
  isHeadLeastOnlyIsHeadLeast x y xs p y∈xs = isHeadLeastIsSameFor2 x y
    (sortIsHeadLeast x y (swap12 xs) (isHeadLeast→sorted x xs p) (swap23∈ y (x  xs) y∈xs))

  isHeadLeastTailSort→isPropA : isTailSorted isHeadLeastOnly -> isProp A
  isHeadLeastTailSort→isPropA hTailSort x y = P.rec (is-set _ _)
    (⊎.rec (isHeadLeastTailSort→x≡y x y hTailSort) (sym  (isHeadLeastTailSort→x≡y y x hTailSort)))
    (is-total x y)

module Order→Sort-Example where

  ≤ℕ-isToset : IsToset _≤ℕ_
  ≤ℕ-isToset = istoset isSetℕ
     _ _ -> isProp≤)
     _ -> ≤-refl)
     _ _ _ -> ≤-trans)
     _ _ -> ≤-antisym)
    lemma
    where
    <→≤ :  {n m} -> n <ℕ m -> n ≤ℕ m
    <→≤ (k , p) = suc k , sym (+-suc k _)  p
    lemma : BinaryRelation.isTotal _≤ℕ_
    lemma x y =  ⊎.rec ⊎.inl (_⊎_.inr  <→≤) (splitℕ-≤ x y) ∣₁

  open Order→Sort _≤ℕ_ ≤ℕ-isToset ≤Dec public

  _ : sort (4 ∷* 6 ∷* 1 ∷* 2 ∷* []*)  (1  2  4  6  [])
  _ = refl