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)))