{-# OPTIONS -WnoUnsupportedIndexedMatch #-}

module Cubical.Structures.Set.CMon.SList.Sort.Equiv 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
open import Cubical.Functions.Embedding
import Cubical.Data.List as L
open import Cubical.Functions.Logic as L hiding (¬_; )

open import Cubical.Structures.Prelude
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 import Cubical.Structures.Set.CMon.SList.Sort.Sort
open import Cubical.Structures.Set.CMon.SList.Sort.Order

open Iso

private
  variable
     : Level
    A : Type 

module MDef = F.Definition M.MonSig M.CMonEqSig M.CMonSEq
module LDef = F.Definition M.MonSig M.MonEqSig M.MonSEq

module Sort↔Order { : Level} {A : Type } (isSetA : isSet A) where
  open Sort isSetA
  open Sort.Section isSetA
  open Sort→Order isSetA
  open Order→Sort
  open IsToset
  open Toset

  IsHeadLeastSection : (SList A -> List A) -> Type _
  IsHeadLeastSection s = isSection s × isHeadLeast s

  IsSortSection : (SList A -> List A) -> Type _
  IsSortSection s = isSection s × isHeadLeast s × isTailSorted s

  HasHeadLeastSectionAndIsDiscrete : Type _
  HasHeadLeastSectionAndIsDiscrete = (Σ _ IsHeadLeastSection) × (Discrete A)

  HasSortSection : Type _
  HasSortSection = Σ (SList A -> List A) IsSortSection

  HasSortSectionAndIsDiscrete : Type _
  HasSortSectionAndIsDiscrete = HasSortSection × (Discrete A)

  IsSortSection→IsHeadLeastSection :  s -> IsSortSection s -> IsHeadLeastSection s
  IsSortSection→IsHeadLeastSection s (section , isHeadLeast , _) = section , isHeadLeast

  order→sort : HasDecOrder -> HasSortSectionAndIsDiscrete
  order→sort (_≤_ , isToset , isDec) =
    (sort _≤_ isToset isDec , subst  isSetA' -> Sort.isSortSection isSetA' (sort _≤_ isToset isDec)) (isPropIsSet _ _) (Order→Sort.sortIsSortSection _≤_ isToset isDec)) , isDiscreteA _≤_ isToset isDec

  order→isHeadLeast : HasDecOrder -> HasHeadLeastSectionAndIsDiscrete
  order→isHeadLeast p = let ((s , sIsSort) , r) = order→sort p in (s , IsSortSection→IsHeadLeastSection s sIsSort) , r

  isHeadLeast→order : HasHeadLeastSectionAndIsDiscrete -> HasDecOrder
  isHeadLeast→order ((s , sIsSection , sIsSort) , discA) =
    _≤_ s sIsSection , ≤IsToset s sIsSection sIsSort , dec≤ s sIsSection discA

  sort→order : HasSortSectionAndIsDiscrete -> HasDecOrder
  sort→order ((s , sIsSort) , r) = isHeadLeast→order ((s , IsSortSection→IsHeadLeastSection s sIsSort) , r)

  order→isHeadLeast→order :  x -> isHeadLeast→order (order→isHeadLeast x)  x
  order→isHeadLeast→order (_≤_ , isToset , isDec) =
    Σ≡Prop  _≤'_ -> isOfHLevelΣ 1 (isPropIsToset _)  p -> isPropΠ2 λ x y -> isPropDec (is-prop-valued p x y))) (sym ≤≡)
    where
    _≤*_ : A -> A -> Type _
    _≤*_ = isHeadLeast→order (order→isHeadLeast (_≤_ , isToset , isDec)) .fst

    ≤*isToset : IsToset _≤*_
    ≤*isToset = isHeadLeast→order (order→isHeadLeast (_≤_ , isToset , isDec)) .snd .fst

    isoTo :  x y -> x  y -> x ≤* y
    isoTo x y x≤y with isDec x y
    ... | yes p = refl
    ... | no ¬p = ⊥.rec (¬p x≤y)

    isoFrom :  x y -> x ≤* y -> x  y
    isoFrom x y x≤y with isDec x y
    ... | yes p = p
    ... | no ¬p = ⊥.rec (¬p (subst (_≤ y) (just-inj y x x≤y) (is-refl isToset y)))

    iso≤ :  x y -> Iso (x  y) (x ≤* y)
    iso≤ x y = iso (isoTo x y) (isoFrom x y)  p -> is-prop-valued ≤*isToset x y _ p)  p -> is-prop-valued isToset x y _ p)
    ≤≡ : _≤_  _≤*_
    ≤≡ = funExt λ x -> funExt λ y -> isoToPath (iso≤ x y)

  sort→order→sort :  x -> order→sort (sort→order x)  x
  sort→order→sort ((s , sIsSection , sIsSort) , discA) =
    Σ≡Prop  _ -> isPropDiscrete)
    $ Σ≡Prop isPropIsSortSection
    $ sym
    $ funExt λ xs -> uniqueSort' _≤*_ ≤*isToset ≤*dec s xs (sIsSection , ∣_∣₁  sIsSort')
    where
    s' : SList A -> List A
    s' = order→sort (sort→order ((s , sIsSection , sIsSort) , discA)) .fst .fst

    s'IsSortSection : isSortSection s'
    s'IsSortSection = order→sort (sort→order ((s , sIsSection , sIsSort) , discA)) .fst .snd

    _≤*_ : A -> A -> Type _
    _≤*_ = _≤_ s sIsSection

    ≤*isToset : IsToset _≤*_
    ≤*isToset = ≤IsToset s sIsSection (sIsSort .fst)

    ≤*dec :  x y -> Dec (x ≤* y)
    ≤*dec = dec≤ s sIsSection discA

    IsSorted* : List A -> Type _
    IsSorted* = IsSorted _≤*_ ≤*isToset ≤*dec

    sIsSort'' :  xs n -> n  L.length (s xs) -> IsSorted* (s xs)
    sIsSort'' xs n p with n | s xs | inspect s xs
    ... | zero  | []     | _ = sorted-nil
    ... | zero  | y  ys | _ = ⊥.rec (znots p)
    ... | suc _ | []     | _ = ⊥.rec (snotz p)
    ... | suc _ | y  [] | _ = sorted-one y
    ... | suc m | y  z  zs | [ q ]ᵢ = induction (sIsSort'' (list→slist (z  zs)) m (injSuc p  wit))
      where
      wit : suc (L.length zs)  L.length (s (list→slist (z  zs)))
      wit = sym $
        L.length (s (list→slist (z  zs))) ≡⟨ sortLength≡ s sIsSection (list→slist (z  zs)) 
        S.length (list→slist (z  zs)) ≡⟨ sym (sortLength≡α s sIsSection (z  zs)) 
        L.length (z  zs) 

      z∷zsSorted : s (list→slist (z  zs))  z  zs
      z∷zsSorted = sortUnique s sIsSection (z  zs) (sIsSort .snd y (z  zs)  _ , q ∣₁)

      induction : IsSorted* (s (list→slist (z  zs))) -> IsSorted* (y  z  zs)
      induction IH =
        sorted-cons y z zs
          (isSorted→≤ s sIsSection y z (sIsSort .fst y z _  _ , q ∣₁ (L.inr (L.inl refl))))
          (subst IsSorted* z∷zsSorted IH)

    sIsSort' :  xs -> IsSorted* (s xs)
    sIsSort' xs = sIsSort'' xs (L.length (s xs)) refl -- helps with termination checking

  -- without tail sort, we get an embedding
  order⊂isHeadLeast : isEmbedding order→isHeadLeast
  order⊂isHeadLeast = injEmbedding (isSet× (isSetΣ (isSetΠ  _ -> isOfHLevelList 0 isSetA)) λ q -> isProp→isSet (isProp× (isPropIsSection q) (isPropIsHeadLeast q))) (isProp→isSet isPropDiscrete))
     p -> sym (order→isHeadLeast→order _)  congS isHeadLeast→order p  order→isHeadLeast→order _)

  -- with tail sort, we can construct a full equivalence
  sort↔order : Iso HasDecOrder HasSortSectionAndIsDiscrete
  sort↔order = iso order→sort sort→order sort→order→sort order→isHeadLeast→order

  sort≃order : HasDecOrder  HasSortSectionAndIsDiscrete
  sort≃order = isoToEquiv sort↔order

  sort≃strict-order : HasDecStrictOrder  HasSortSectionAndIsDiscrete
  sort≃strict-order = compEquiv (invEquiv HasDecOrder≃HasDecStrictOrder) sort≃order

  sort≃lattice : Discrete A -> TotalMeetSemiLatticeStr A  HasSortSection
  sort≃lattice discA =
    TotalMeetSemiLatticeStr A ≃⟨ HasDecOrder≃DecTotalMeetSemiLattice discA 
    HasDecOrder ≃⟨ sort≃order 
    HasSortSectionAndIsDiscrete ≃⟨ Σ-contractSnd  _ -> isContrDiscreteA) 
    HasSortSection 
    where
    isContrDiscreteA : isContr (Discrete A)
    isContrDiscreteA = discA , λ z -> funExt λ x -> funExt λ y -> isPropDec (isSetA x y) (discA x y) (z x y)

module Triangle { : Level} {A : Type } (decOrder : Toset.HasDecOrder {A = A}) where
  _≤_ : A -> A -> Type _
  _≤_ = fst decOrder

  tosetA : IsToset _≤_
  tosetA = snd decOrder .fst

  decOrderA :  x y -> Dec (x  y)
  decOrderA = snd decOrder .snd

  open IsToset tosetA
  open Sort↔Order is-set
  open Order→Sort _≤_ tosetA decOrderA
  open Sort→Order is-set

  OList : Type 
  OList = Σ (List A) IsSorted

  quotient* : List A -> SList A
  quotient* = list→slist

  sort* : List A -> OList
  sort* xs = sort (quotient* xs) , sortIsSorted (quotient* xs)

  triangle : quotient*  fst  sort*  quotient*
  triangle = funExt λ xs -> sortIsPermute (quotient* xs)

module Univalence { : Level} {A : Type } (is-set : isSet A) where
  open import Cubical.Foundations.Transport 
  module FreeLMonDef = F.Definition M.MonSig M.MonEqSig M.MonSEq
  module FreeCMonDef = F.Definition M.MonSig M.CMonEqSig M.CMonSEq

  module _ (freeLMonDef :  {ℓ' ℓ''} -> FreeLMonDef.Free ℓ' ℓ'' 2)
           (freeCMonDef :  {ℓ' ℓ''} -> FreeCMonDef.Free ℓ' ℓ'' 2) where

    LA : Type 
    LA = FreeLMonDef.Free.F {ℓ' = } freeLMonDef A

    LA≡ListA : LA  List A
    LA≡ListA = FreeLMonDef.free≡ freeLMonDef listDef is-set

    MA : Type 
    MA = FreeCMonDef.Free.F {ℓ' = } freeCMonDef A

    MA≡SListA : MA  SList A
    MA≡SListA = FreeCMonDef.free≡ freeCMonDef slistDef is-set

    quotientHom* : _
    quotientHom* = FreeLMonDef.Free.ext freeLMonDef
      (FreeCMonDef.Free.trunc {ℓ' = } freeCMonDef is-set)
      (M.cmonSatMon (FreeCMonDef.Free.sat freeCMonDef))
      (FreeCMonDef.Free.η freeCMonDef)
    
    quotient* : LA -> MA
    quotient* = quotientHom* .fst