module Cubical.Structures.Set.CMon.SList.Sort.Sort 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 Sort→Order (isSetA : isSet A) (sort : SList A -> List A) (sort≡ :  xs -> list→slist (sort xs)  xs) where

  isSetMaybeA : isSet (Maybe A)
  isSetMaybeA = isOfHLevelMaybe 0 isSetA

  isSetListA : isSet (List A)
  isSetListA = isOfHLevelList 0 isSetA

  private
    module 𝔖 = M.CMonSEq < SList A , slistAlpha > slistSat

  open Membership isSetA
  open Membership* isSetA
  open Sort isSetA sort
  open Sort.Section isSetA sort sort≡
  open Head.Set isSetA renaming (head to headMaybe)

  least : SList A -> Maybe A
  least xs = headMaybe (sort xs)

  leastNothing :  xs -> least xs  nothing -> xs  []*
  leastNothing xs p with sort xs | inspect sort xs
  ... | []     | [ q ]ᵢ = sort[] xs q
  ... | y  ys | [ q ]ᵢ = ⊥.rec (¬just≡nothing p)

  leastΣ :  x xs -> least xs  just x -> Σ[ ys  List A ] (sort xs  x  ys)
  leastΣ x xs p with sort xs
  ... | []     = ⊥.rec (¬nothing≡just p)
  ... | y  ys = ys , congS (_∷ ys) (just-inj y x p)

  leastIn :  x xs -> least xs  just x -> x ∈* xs
  leastIn x xs p =
    let (ys , q) = leastΣ x xs p
        x∷ys≡xs = congS list→slist (sym q)  sort≡ xs
    in subst (x ∈*_) x∷ys≡xs (x∈*xs x (list→slist ys))

  leastChoice :  x y -> (least (x ∷* [ y ]*)  just x) ⊔′ (least (x ∷* [ y ]*)  just y)
  leastChoice x y = P.rec squash₁
    (⊎.rec
      (L.inl  congS headMaybe)
      (L.inr  congS headMaybe))
    (sortChoice x y)

  _≤_ : A -> A -> Type _
  x  y = least (x ∷* y ∷* []*)  just x

  ≤Prop :  x y -> hProp _
  ≤Prop x y = (x  y) , isSetMaybeA _ _

  refl≤ :  x -> x  x
  refl≤ x = P.rec (isSetMaybeA _ _) (⊎.rec (idfun _) (idfun _)) (leastChoice x x)

  antisym≤ :  x y -> x  y -> y  x -> x  y
  antisym≤ x y p q = P.rec (isSetA x y)
    (⊎.rec
       xy -> just-inj x y $
        just x ≡⟨ sym xy 
        least (x ∷* y ∷* []*) ≡⟨ congS least (swap x y []*) 
        least (y ∷* x ∷* []*) ≡⟨ q 
        just y
      )
       yx -> just-inj x y $
        just x ≡⟨ sym p 
        least (x ∷* [ y ]*) ≡⟨ yx 
        just y
      ))
     (leastChoice x y)

  total≤ :  x y -> (x  y) ⊔′ (y  x)
  total≤ x y = P.rec squash₁
    (⊎.rec
      L.inl
       p -> L.inr $
        least (y ∷* [ x ]*) ≡⟨ congS least (swap y x []*) 
        least (x ∷* [ y ]*) ≡⟨ p 
        just y
      ))
    (leastChoice x y)

  dec≤ : (discA : Discrete A) ->  x y -> Dec (x  y)
  dec≤ discA x y = discreteMaybe discA _ _

  isSorted→≤ :  x y -> isSorted (x  y  []) -> x  y
  isSorted→≤ x y = P.rec (isSetMaybeA _ _) λ (xs , p) ->
    congS headMaybe (congS sort (sym (sym (sort≡ xs)  congS list→slist p))  p)

  ≤→isSorted :  x y -> x  y -> isSorted (x  y  [])
  ≤→isSorted x y p =  x ∷* y ∷* []* , proof ∣₁
    where
      proof : sort (x ∷* [ y ]*)  x  y  []
      proof with leastΣ _ _ p
      ... | [] , r = ⊥.rec $ snotz $ injSuc $
        congS S.length (sym (sort≡ (x ∷* [ y ]*))   congS list→slist r)
      ... | a  b  c , r = ⊥.rec $ znots $ injSuc $ injSuc $
        congS S.length (sym (sort≡ (x ∷* [ y ]*))   congS list→slist r)
      ... | y'  [] , q = P.rec isPropΑ
        (⊎.rec
           r 
            sort (x ∷* [ y ]*) ≡⟨ congS  z  sort (x ∷* [ z ]*)) r 
            sort (x ∷* [ x ]*) ≡⟨ lemmaΑ 
            x  x  [] ≡⟨ congS  z  x  [ z ]) (sym r) 
            x  y  [] )
        (P.rec isPropΑ
          (⊎.rec
             r 
              sort (x ∷* [ y ]*) ≡⟨ q 
              x  y'  [] ≡⟨ congS  z  x  [ z ]) (sym r) 
              x  y  [] )
          ⊥.rec*)))
        y∈
        where
          isPropΑ : isProp (sort (x ∷* [ y ]*)  x  y  [])
          isPropΑ = isOfHLevelList 0 isSetA _ _
          y∈* : y ∈* (x ∷* [ y ]*)
          y∈* = L.inr (L.inl refl)
          y∈sort : y  sort (x ∷* [ y ]*)
          y∈sort = ∈*→∈ y (sort (x ∷* [ y ]*)) (subst (y ∈*_) (sym (sort≡ (x ∷* [ y ]*))) y∈*)
          y∈ : y  (x  y'  [])
          y∈ = subst (y ∈_) q y∈sort
          lemmaΑ : sort (x ∷* [ x ]*)  x  x  []
          lemmaΑ with sort (x ∷* [ x ]*) | inspect sort (x ∷* [ x ]*)
          ... | [] | [ r ]ᵢ = ⊥.rec $ snotz $
            congS S.length (sym (sort≡ (x ∷* [ x ]*))  congS list→slist r)
          ... | a  [] | [ r ]ᵢ = ⊥.rec $ snotz $ injSuc $
            congS S.length (sym (sort≡ (x ∷* [ x ]*))  congS list→slist r)
          ... | a  b  c  as | [ r ]ᵢ = ⊥.rec $ znots $ injSuc $ injSuc $
            congS S.length (sym (sort≡ (x ∷* [ x ]*))  congS list→slist r)
          ... | a  b  [] | [ r ]ᵢ = cong₂  m n  m  [ n ]) (x∈*[yy]→x≡y a x a∈*) (x∈*[yy]→x≡y b x b∈*)
            where
              xx≡ab : x ∷* [ x ]*  a ∷* [ b ]*
              xx≡ab = sym (sort≡ (x ∷* [ x ]*))  congS list→slist r
              a∈* : a ∈* (x ∷* [ x ]*)
              a∈* = subst (a ∈*_) (sym xx≡ab) (L.inl refl)
              b∈* : b ∈* (x ∷* [ x ]*)
              b∈* = subst (b ∈*_) (sym xx≡ab) (L.inr (L.inl refl))

  isSorted↔≤ :  x y -> isSorted (x  y  [])  (x  y)
  isSorted↔≤ x y = isoToEquiv (iso (isSorted→≤ x y) (≤→isSorted x y)
     p  isSetMaybeA _ _ _ p)
     p  squash₁ _ p))

  module _ (sortIsSort : isHeadLeast) where
    trans≤ :  x y z -> x  y -> y  z -> x  z
    trans≤ x y z x≤y y≤z with least (x ∷* y ∷* z ∷* []*) | inspect least (x ∷* y ∷* z ∷* []*)
    ... | nothing | [ p ]ᵢ = ⊥.rec (snotz (congS S.length (leastNothing _ p)))
    ... | just u | [ p ]ᵢ =
      P.rec (isSetMaybeA _ _)
        (⊎.rec case1
          (P.rec (isSetMaybeA _ _)
            (⊎.rec case2 (case3  x∈[y]→x≡y _ _))
          )
        )
        (leastIn u (x ∷* y ∷* z ∷* []*) p)
      where
      tail' : Σ[ xs  List A ] u  xs  sort (x ∷* y ∷* z ∷* []*)
      tail' with sort (x ∷* y ∷* z ∷* []*)
      ... | [] = ⊥.rec (¬nothing≡just p)
      ... | v  xs = xs , congS (_∷ xs) (just-inj _ _ (sym p))
      tail : List A
      tail = tail' .fst
      tailProof : u  tail  sort (x ∷* y ∷* z ∷* []*)
      tailProof = tail' .snd
      u∷tailIsSorted : isSorted (u  tail)
      u∷tailIsSorted =  ((x ∷* y ∷* z ∷* []*) , sym tailProof) ∣₁
      uIsSmallest :  v -> v ∈* (x ∷* y ∷* z ∷* []*) -> u  v
      uIsSmallest v q =
        isSorted→≤ u v (sortIsSort u v tail u∷tailIsSorted (subst (v ∈_) (sym tailProof) (sort∈ v _ q)))
      case1 : u  x -> x  z
      case1 u≡x = subst (_≤ z) u≡x (uIsSmallest z (L.inr (L.inr (L.inl refl))))
      case2 : u  y -> x  z
      case2 u≡y = subst (_≤ z) (antisym≤ y x y≤x x≤y) y≤z
        where
        y≤x : y  x
        y≤x = subst (_≤ x) u≡y (uIsSmallest x (L.inl refl))
      case3 : u  z -> x  z
      case3 u≡z = subst (x ≤_) (antisym≤ y z y≤z z≤y) x≤y
        where
        z≤y : z  y
        z≤y = subst (_≤ y) u≡z (uIsSmallest y (L.inr (L.inl refl)))

    ≤IsToset : IsToset _≤_
    IsToset.is-set ≤IsToset = isSetA
    IsToset.is-prop-valued ≤IsToset x y = isOfHLevelMaybe 0 isSetA _ _
    IsToset.is-refl ≤IsToset = refl≤
    IsToset.is-trans ≤IsToset = trans≤
    IsToset.is-antisym ≤IsToset = antisym≤
    IsToset.is-total ≤IsToset = total≤

module Sort→Order-Counterexample where
  open import Cubical.Structures.Set.CMon.SList.Sort.Order 
  open import Cubical.HITs.FiniteMultiset.CountExtensionality
  open Order→Sort-Example renaming (sort to sortℕ)

  isSectionSortℕ :  xs -> list→slist (sortℕ xs)  xs
  isSectionSortℕ = sortIsPermute

  sort≡ℕ :  xs ys -> sortℕ xs  sortℕ ys -> xs  ys
  sort≡ℕ xs ys xs≡ys =
    xs ≡⟨ sym (sortIsPermute xs) 
    list→slist (sortℕ xs) ≡⟨ congS list→slist xs≡ys 
    list→slist (sortℕ ys) ≡⟨ sortIsPermute ys 
    ys 

  discreteMℕ : Discrete (SList )
  discreteMℕ xs ys with discreteList discreteℕ (sortℕ xs) (sortℕ ys)
  ... | yes p = yes (sort≡ℕ xs ys p)
  ... | no ¬p = no (¬p  congS sortℕ)

  counterexample : SList  -> List 
  counterexample xs with discreteMℕ xs (1 ∷* 3 ∷* []*)
  ... | yes p = 3  1  []
  ... | no ¬p = sortℕ xs

  isSectionCounterexample :  xs -> list→slist (counterexample xs)  xs
  isSectionCounterexample xs with discreteMℕ xs (1 ∷* 3 ∷* []*)
  ... | yes p =
    3 ∷* [ 1 ]* ≡⟨ swap 3 1 []* 
    1 ∷* [ 3 ]* ≡⟨ sym p 
    xs 
  ... | no ¬p = isSectionSortℕ xs

  open Sort→Order isSetℕ counterexample isSectionCounterexample

  notTrans : ¬ (∀ x y z -> x  y -> y  z -> x  z)
  notTrans trans = snotz (injSuc (just-inj _ _ (trans 1 2 3 refl refl)))