{-# OPTIONS --safe #-}

module Finite where

open import Prelude
open import Function.Properties
open import Data.List.Membership

ℰ : Type a → Type a
ℰ A = ∃ n × ∥ Fin n ≃ A ∥


open import Data.Nat.Properties using (isSetNat)
open import Data.Fin.Injective
open import Isomorphism.Properties

isPropℰ : isProp (ℰ A)
isPropℰ (n , xe) (m , ye) = Σ≡Prop (λ _ → squash) (∥rec2∥ (isSetNat _ _) (λ lhs rhs → Fin-inj (ua (lhs ⟨ ≃-trans ⟩ ≃-sym rhs))) xe ye)

trav : ∀ {n} {P : Lift {ℓ₂ = b} (Fin n) → Type p} → (∀ x → ∥ P x ∥) → ∥ (∀ x → P x) ∥
trav {n = zero } f = ∣ (λ ()) ∣
trav {b = b} {n = suc n} f =
  let h = f (lift zero)
  in ∥liftA2∥ (λ { x xs (lift zero) → x ; x xs (lift (suc i)) → xs (lift i) }) h (trav {b = b} (f ∘′ lift ∘′ suc ∘′ lower))

travℰ : ℰ A → ((x : A) → ∥ P x ∥) → ∥ ((x : A) → P x) ∥
travℰ (n , p) k = ∥rec∥ squash (λ e → let h = ua (≃-trans (isoToEquiv ⇔-lift) e) in subst (λ t → ∀ {P : t → Type _} → ((x : t) → ∥ P x ∥) → ∥ (∀ x → P x) ∥ ) h trav k) p

module _ where
  open import Finite.OnLists

  ≃⇒ℰ! : ∃ n × (Fin n ≃ A) → ℰ! A
  ≃⇒ℰ! (n , p) .fst = tabulate (p .fst)
  ≃⇒ℰ! (n , p) .snd x =
    let e = p .snd .equiv-proof x
        h = tabulate-∈ (p .fst) (e .fst .fst)
    in subst (_∈ tabulate (p .fst)) (e .fst .snd) h

  module _ {A : Type a} where
    ℰΣ : ℰ A → ((x : A) → ℰ (P x)) → ℰ (Σ A P)
    ℰΣ (n , xs) k = ∥rec2∥ isPropℰ conv xs (travℰ (n , xs) (snd ∘′ k))
      where
      conv : Fin n ≃ A → ((x : A) → Fin (k x .fst) ≃ P x) → ℰ (Σ A P)
      conv e k′ =
        let h = ≃⇒ℰ! (_ , e) |Σ| λ x → ≃⇒ℰ! (_  , k′ x) 
        in map₂ ∣_∣ (ℬ→ℱ (ℰ!⇒ℬ h))

open import Data.Nat.Order

⇔→ℰ : A ⇔ B → ℰ A → ℰ B
⇔→ℰ i = map₂ (∥map∥ (flip ≃-trans (isoToEquiv i)))


Equiv⇒SplitSurj : (f : A → B) → isEquiv f → SplitSurjective f
Equiv⇒SplitSurj f x y = x .equiv-proof y .fst

≃⇒↠! : A ≃ B → A ↠! B
≃⇒↠! = map₂ (Equiv⇒SplitSurj _)

ℰ⇒Discrete : ℰ A → Discrete A
ℰ⇒Discrete = ∥rec∥ isPropDiscrete (flip Discrete-↠! discreteFin ∘ ≃⇒↠!) ∘′ snd


open import Data.Fin.Properties using (isPropFin1; Fin⊥)

ℰ⇒Dec : ℰ A → Dec ∥ A ∥
ℰ⇒Dec (zero  , xs) = no (∥rec2∥ (λ ()) (λ e x → Fin⊥ .fun (e .snd .equiv-proof x .fst .fst))  xs)
ℰ⇒Dec (suc n , xs) = yes (∥map∥ ((_$ zero) ∘ fst) xs)

open import Cubical.Foundations.Prelude using (isContr→isProp)

isContr⇒ℰ : isContr A → ℰ A
isContr⇒ℰ (x , p) .fst = 1
isContr⇒ℰ (x , p) .snd =
            ∣ (λ where .fst _ → x
                       .snd .equiv-proof y .fst .fst → 0
                       .snd .equiv-proof y .fst .snd → p y
                       .snd .equiv-proof y .snd (zero , q) i → zero , isProp→isSet (isContr→isProp (x , p)) x y (p y) q i) ∣

¬⇒ℰ : ¬ A → ℰ A
¬⇒ℰ ¬A .fst = 0
¬⇒ℰ ¬A .snd =
 ∣ (λ where .fst ()
            .snd .equiv-proof y → absurd (¬A y) ) ∣

ℰ× : ℰ A → ℰ B → ℰ (A × B)
ℰ× x y = ℰΣ x (const y)

ℰ⟨⊤⟩ : ℰ ⊤
ℰ⟨⊤⟩ = isContr⇒ℰ (tt , λ _ → refl)

open import Data.Nat.Properties

private variable n m : ℕ

ℰ⟨Fin⟩ : ℰ (Fin n)
ℰ⟨Fin⟩ {n = n} = n , ∣ isoToEquiv  id-⇔ ∣

ℰ⟨ℕ≤⟩ : ℰ (∃ m × m ≤ n)
ℰ⟨ℕ≤⟩ = suc _ , ∣ isoToEquiv lemma ∣
  where
  fin→≤ : Fin (suc n) → ∃ m × m ≤ n 
  fin→≤ zero    = zero , tt
  fin→≤ {n = suc n} (suc f) = let g , p = fin→≤ {n = n} f in suc g , p

  ≤→fin : ∃ m × m ≤ n → Fin (suc n)
  ≤→fin (zero , p) = zero
  ≤→fin {n = suc n} (suc m , p) = suc (≤→fin {n} (m , p))

  linv : (x : Fin (suc n)) → ≤→fin (fin→≤ x) ≡ x
  linv zero = refl
  linv {n = suc n} (suc x) = cong suc (linv {n} x)

  rinv : (x : ∃ m × m ≤ n) → fin→≤ (≤→fin x) ≡ x
  rinv {n} (zero , p) = refl
  rinv {suc n} (suc m , p) = cong (map-Σ suc id) (rinv {n} (m , p))

  lemma : Fin (suc n) ⇔ (∃ m × m ≤ n)
  lemma .fun = fin→≤
  lemma .inv = ≤→fin
  lemma .rightInv = rinv
  lemma .leftInv = linv

DecProp⇒ℰ : isProp A → Dec A → ℰ A
DecProp⇒ℰ isPropA (yes p) = isContr⇒ℰ (p , isPropA p)
DecProp⇒ℰ isPropA (no ¬p) = ¬⇒ℰ ¬p

Discrete⇒ℰ : Discrete A → {x y : A} → ℰ (x ≡ y)
Discrete⇒ℰ d = DecProp⇒ℰ (Discrete→isSet d _ _) (d _ _)

ℰ? : (∀ x → isProp (P x)) → ℰ A → ((x : A) → Dec (P x)) → ℰ (Σ A P)
ℰ? pIsProp ℰA ℰP? = ℰΣ ℰA λ x → DecProp⇒ℰ (pIsProp x) (ℰP? x)


module _ (ℰ⟨A⟩ : ℰ A) where
  ℰ⟨List·length⟩ : ∀ n → ℰ (Σ[ xs ⦂ List A ] × length xs ≡ n)
  ℰ⟨List·length⟩ zero    = ⇔→ℰ lemma ℰ⟨⊤⟩
    where
    lemma : ⊤ ⇔ (Σ[ xs ⦂ List A ] × length xs ≡ 0)
    lemma .fun _ = [] , refl
    lemma .inv _ = tt
    lemma .rightInv ([] , p) = Σ≡Prop (λ _ → isSetNat _ _) refl
    lemma .rightInv (x ∷ xs , p) = absurd (suc≢zero p)
    lemma .leftInv  _ = refl
  ℰ⟨List·length⟩ (suc n) = ⇔→ℰ lemma (ℰ× ℰ⟨A⟩ (ℰ⟨List·length⟩ n))
    where
    lemma : (A × Σ[ xs ⦂ List A ] × length xs ≡ n) ⇔ (Σ[ xs ⦂ List A ] × length xs ≡ suc n)
    lemma .fun (x , xs , p) = x ∷ xs , cong suc p
    lemma .inv ([] , p) = absurd (zero≢suc p)
    lemma .inv (x ∷ xs , p) = x , xs , suc-inj p
    lemma .rightInv ([] , p) = absurd (zero≢suc p)
    lemma .rightInv (x ∷ xs , p) = Σ≡Prop (λ _ → isSetNat _ _) refl
    lemma .leftInv (x , xs , p) = refl

  open import Isomorphism.Properties

  ℰ⟨List≤length⟩ : ∀ n → ℰ (Σ[ xs ⦂ List A ] × length xs ≤ n)
  ℰ⟨List≤length⟩ n = ⇔→ℰ lemma (ℰΣ (ℰ⟨ℕ≤⟩ {n}) (ℰ⟨List·length⟩ ∘′ fst))
    where
    lemma : (Σ[ p ⦂ (∃ m × m ≤ n) ] × Σ[ xs ⦂ List A ] × length xs ≡ fst p)
          ⇔ (Σ[ xs ⦂ List A ] × length xs ≤ n)
    lemma .fun ((m , p) , xs , q) = xs , subst (_≤ _) (sym q) p
    lemma .inv (xs , q) = (length xs , q) , xs , refl
    lemma .rightInv (xs , q) = Σ≡Prop (λ x → isProp≤ {length x}  ) refl
    lemma .leftInv  ((m , p) , xs , q) = ΣPathP (Σ≡Prop (λ x → isProp≤ {x}) q , toPathP (Σ≡Prop (λ x → isSetNat _ _) (transportRefl xs)))

  ℰ×NoDup⇒List≤ : (xs : List A) → NoDup xs → length xs ≤ ℰ⟨A⟩ .fst
  ℰ×NoDup⇒List≤ xs nd =
    let h = ((xs !!_) , NoDup⇒Inj xs nd) ⦂ (Fin (length xs) ↣ A)
    in ∥rec∥ (isProp≤ {length xs}) (λ p → ↣⇒≤ (↣-trans h (_ , ⇔-fun-inj (sym-⇔ (equivToIso p)) _ _))) (ℰ⟨A⟩ .snd)

  ℰ⟨List⟩  : ℰ (Σ[ xs ⦂ List A ] × NoDup xs)
  ℰ⟨List⟩ = ⇔→ℰ (Σ-assoc ⟨ trans-⇔ ⟩  cong-Σ lemma) (ℰ? (λ _ → isPropNoDup _) (ℰ⟨List≤length⟩ (ℰ⟨A⟩ .fst)) (NoDups? (ℰ⇒Discrete ℰ⟨A⟩) ∘′ fst))
    where
    lemma : (xs : List A) → ((length xs ≤ ℰ⟨A⟩ .fst) × NoDup xs) ⇔ NoDup xs
    lemma xs .fun = snd
    lemma xs .inv nd = ℰ×NoDup⇒List≤ xs nd , nd
    lemma xs .rightInv _ = refl
    lemma xs .leftInv x = ΣPathP (isProp≤ {length xs} _ _  , refl)

module _ {P : A → Type p} where
  ℰ⇒search : ℰ A → (P? : ∀ (x : A) → Dec (P x)) → Dec ∥ ∃ x × P x ∥
  ℰ⇒search ℰA P? = ∥rec∥ (isPropDec squash) (dec-trunc ∘ search) (ℰA .snd)
    where
    search′ : (f : Fin n → A) → Dec (∃ i × P (f i))
    search′ {n = zero}  f = no λ ()
    search′ {n = suc n} f with P? (f 0) | search′ (f ∘ suc)
    ... | yes p | _ = yes (0 , p)
    ... | no ¬p | yes (i , p) = yes (suc i , p)
    ... | no ¬p | no ¬ps = no λ { (zero , p) → ¬p p ; (suc i , p) → ¬ps (i , p) }

    open import Cubical.Foundations.Everything using (equivToIso)

    search : (Fin n ≃ A) → Dec (∃ x × P x)
    search f≃A = let f⇔A = equivToIso f≃A in
      iff-dec
        (map-Σ (f⇔A .fun) id ,
         map-Σ (f⇔A .inv) (subst P (sym (f⇔A .rightInv _)))) (search′ (f⇔A .fun))