{-# OPTIONS --safe --postfix-projections #-}

module Cubical.Data.Fin.Recursive.Properties where

open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Transport
open import Cubical.Foundations.Univalence

open import Cubical.Functions.Embedding

import Cubical.Data.Empty as Empty
open Empty hiding (rec; elim)
open import Cubical.Data.Nat hiding (elim)
open import Cubical.Data.Nat.Order.Recursive
open import Cubical.Data.Sigma
import Cubical.Data.Sum as Sum
open Sum using (_⊎_; _⊎?_; inl; inr)

open import Cubical.Data.Fin.Recursive.Base

open import Cubical.Relation.Nullary

private
  variable
     : Level
    m n : 
    A : Type 
    x y : A

isPropFin0 : isProp (Fin 0)
isPropFin0 = isProp⊥

isContrFin1 : isContr (Fin 1)
isContrFin1 .fst = zero
isContrFin1 .snd zero = refl

Unit≡Fin1 : Unit  Fin 1
Unit≡Fin1 = ua (const zero , ctr)
  where
  fibr : fiber (const zero) zero
  fibr = tt , refl

  fibr! :  f  fibr  f
  fibr! (tt , p) i .fst = tt
  fibr! (tt , p) i .snd = J (λ{ zero q  refl  q }) refl p i

  ctr : isEquiv (const zero)
  ctr .equiv-proof zero .fst = fibr
  ctr .equiv-proof zero .snd = fibr!

module Cover where
  Cover : FinF A  FinF A  Type _
  Cover zero zero = Unit
  Cover (suc x) (suc y) = x  y
  Cover _ _ = 

  crefl : Cover x x
  crefl {x = zero} = _
  crefl {x = suc x} = refl

  cover : x  y  Cover x y
  cover p = transport  i  Cover (p i0) (p i)) crefl

  predp : Path (FinF A) (suc x) (suc y)  x  y
  predp {x = x} p = transport  i  Cover (suc x) (p i)) refl

  suc-predp-refl
    : Path (Path (FinF A) (suc x) (suc x))
         i  suc (predp  _  suc x) i)) refl
  suc-predp-refl {x = x} i j
    = suc (transportRefl (refl {x = x}) i j)

  suc-retract : (p : Path (FinF A) (suc x) (suc y))   i  suc (predp p i))  p
  suc-retract
    = J (λ{ (suc m) q   i  suc (predp q i))  q ; zero _  }) suc-predp-refl

  isEmbedding-suc : isEmbedding {B = FinF A} suc
  isEmbedding-suc w x = isoToIsEquiv theIso
    where
    open Iso
    theIso : Iso (w  x) (suc w  suc x)
    theIso .fun = cong suc
    theIso .inv p = predp p
    theIso .rightInv = suc-retract
    theIso .leftInv
      = J  _ q  transport  i  w  q i) refl  q) (transportRefl refl)

private
  zK : (p : Path (FinF A) zero zero)  p  refl
  zK = J (λ{ zero q  q  refl ; one _   }) refl

  isSetFinF : isSet A  isSet (FinF A)
  isSetFinF Aset zero zero p
    = J (λ{ zero q  p  q ; _ _   }) (zK p)
  isSetFinF Aset (suc x) (suc y)
    = isOfHLevelRetract 1 Cover.predp (cong suc) Cover.suc-retract (Aset x y)
  isSetFinF Aset zero (suc _) p = Empty.rec (Cover.cover p)
  isSetFinF Aset (suc _) zero p = Empty.rec (Cover.cover p)

isSetFin : isSet (Fin m)
isSetFin {zero} = isProp→isSet isPropFin0
isSetFin {suc m} = isSetFinF isSetFin

discreteFin : Discrete (Fin m)
discreteFin {suc m} zero zero = yes refl
discreteFin {suc m} (suc i) (suc j) with discreteFin i j
... | yes p = yes (cong suc p)
... | no ¬p = no (¬p  Cover.predp)
discreteFin {suc m} zero (suc _) = no Cover.cover
discreteFin {suc m} (suc _) zero = no Cover.cover

inject< : m < n  Fin m  Fin n
inject< {suc m} {suc n} _ zero = zero
inject< {suc m} {suc n} m<n (suc i) = suc (inject< m<n i)

inject≤ : m  n  Fin m  Fin n
inject≤ {suc m} {suc n} _ zero = zero
inject≤ {suc m} {suc n} m≤n (suc i) = suc (inject≤ m≤n i)

any? : {P : Fin m  Type }  (∀ i  Dec (P i))  Dec (Σ _ P)
any? {zero} P? = no fst
any? {suc m} P? with P? zero ⊎? any? (P?  suc)
... | yes (inl p) = yes (zero , p)
... | yes (inr (i , p)) = yes (suc i , p)
... | no k = no λ where
  (zero , p)  k (inl p)
  (suc x , p)  k (inr (x , p))

_#_ : Fin m  Fin m  Type₀
_#_ {m = suc m} zero zero = 
_#_ {m = suc m} (suc i) zero = Unit
_#_ {m = suc m} zero (suc j) = Unit
_#_ {m = suc m} (suc i) (suc j) = i # j

#→≢ : ∀{i j : Fin m}  i # j  ¬ i  j
#→≢ {suc _} {zero} {suc j} _ = Cover.cover
#→≢ {suc _} {suc i} {zero} _ = Cover.cover
#→≢ {suc m} {suc i} {suc j} ap p = #→≢ ap (Cover.predp p)

≢→# : ∀{i j : Fin m}  ¬ i  j  i # j
≢→# {suc m} {zero}  {zero}  ¬p = ¬p refl
≢→# {suc m} {zero}  {suc j}  _ = _
≢→# {suc m} {suc i} {zero}   _ = _
≢→# {suc m} {suc i} {suc j} ¬p = ≢→# {m} {i} {j} (¬p  cong suc)

#-inject< : ∀{l : m < n} (i j : Fin m)  i # j  inject< {m} {n} l i # inject< l j
#-inject< {suc m} {suc n} zero    (suc _) _ = _
#-inject< {suc m} {suc n} (suc _) zero    _ = _
#-inject< {suc m} {suc n} (suc i) (suc j) a = #-inject< {m} {n} i j a

punchOut : (i j : Fin (suc m))  i # j  Fin m
punchOut {suc m} zero (suc j) _ = j
punchOut {suc m} (suc i) zero _ = zero
punchOut {suc m} (suc i) (suc j) ap = suc (punchOut i j ap)

punchOut-inj
  : ∀{i j k : Fin (suc m)}
   (i#j : i # j) (i#k : i # k)
   punchOut i j i#j  punchOut i k i#k
   j  k
punchOut-inj {suc m} {suc i} {zero} {zero} i#j i#k p = refl
punchOut-inj {suc m} {zero} {suc j} {suc k} i#j i#k p = cong suc p
punchOut-inj {suc m} {suc i} {suc j} {suc k} i#j i#k p
  = cong suc (punchOut-inj {m} {i} {j} {k} i#j i#k (Cover.predp p))
punchOut-inj {suc m} {suc i} {zero} {suc x} i#j i#k p
  = Empty.rec (Cover.cover p)
punchOut-inj {suc m} {suc i} {suc j} {zero} i#j i#k p
  = Empty.rec (Cover.cover p)

_⊕_ : (m : )  Fin n  Fin (m + n)
zero   i = i
suc m  i = suc (m  i)

toFin : (n : )  Fin (suc n)
toFin zero = zero
toFin (suc n) = suc (toFin n)

inject<#toFin : ∀(i : Fin n)  inject< (≤-refl (suc n)) i # toFin n
inject<#toFin {suc n} zero = _
inject<#toFin {suc n} (suc i) = inject<#toFin {n} i

inject≤#⊕ : ∀(i : Fin m) (j : Fin n)  inject≤ (k≤k+n m) i # (m  j)
inject≤#⊕ {suc m} {suc n} zero    j = _
inject≤#⊕ {suc m} {suc n} (suc i) j = inject≤#⊕ i j

split : (m : )  Fin (m + n)  Fin m  Fin n
split zero    j = inr j
split (suc m) zero = inl zero
split (suc m) (suc i) with split m i
... | inl k = inl (suc k)
... | inr j = inr j

pigeonhole
  : m < n
   (f : Fin n  Fin m)
   Σ[ i  Fin n ] Σ[ j  Fin n ] (i # j) × (f i  f j)
pigeonhole {zero} {suc n} m<n f = Empty.rec (f zero)
pigeonhole {suc m} {suc n} m<n f with any?  i  discreteFin (f zero) (f (suc i)))
... | yes (j , p) = zero , suc j , _ , p
... | no ¬p = let i , j , ap , p = pigeonhole {m} {n} m<n g
               in suc i , suc j , ap
                , punchOut-inj {i = f zero} (apart i) (apart j) p
  where
  apart : (i : Fin n)  f zero # f (suc i)
  apart i = ≢→# {suc m} {f zero} (¬p  _,_ i)

  g : Fin n  Fin m
  g i = punchOut (f zero) (f (suc i)) (apart i)

Fin-inj₀ : m < n  ¬ Fin n  Fin m
Fin-inj₀ m<n P with pigeonhole m<n (transport P)
... | i , j , i#j , p = #→≢ i#j i≡j
  where
  i≡j : i  j
  i≡j = transport  k  transport⁻Transport P i k  transport⁻Transport P j k)
          (cong (transport⁻ P) p)

Fin-inj : (m n : )  Fin m  Fin n  m  n
Fin-inj m n P with m  n
... | eq p   = p
... | lt m<n = Empty.rec (Fin-inj₀ m<n (sym P))
... | gt n<m = Empty.rec (Fin-inj₀ n<m P)

module Isos where
  open Iso

  up : Fin m  Fin (m + n)
  up {m} = inject≤ (k≤k+n m)

  resplit-identᵣ₀ :  m (i : Fin n)  Sum.⊎Path.Cover (split m (m  i)) (inr i)
  resplit-identᵣ₀ zero    i = lift refl
  resplit-identᵣ₀ (suc m) i with split m (m  i) | resplit-identᵣ₀ m i
  ... | inr j | p = p

  resplit-identᵣ :  m (i : Fin n)  split m (m  i)  inr i
  resplit-identᵣ m i = Sum.⊎Path.decode _ _ (resplit-identᵣ₀ m i)

  resplit-identₗ₀ :  m (i : Fin m)  Sum.⊎Path.Cover (split {n} m (up i)) (inl i)
  resplit-identₗ₀ (suc m) zero = lift refl
  resplit-identₗ₀ {n} (suc m) (suc i)
    with split {n} m (up i) | resplit-identₗ₀ {n} m i
  ... | inl j | lift p = lift (cong suc p)

  resplit-identₗ :  m (i : Fin m)  split {n} m (up i)  inl i
  resplit-identₗ m i = Sum.⊎Path.decode _ _ (resplit-identₗ₀ m i)

  desplit-ident :  m  (i : Fin (m + n))  Sum.rec up (m ⊕_) (split m i)  i
  desplit-ident zero i = refl
  desplit-ident (suc m) zero = refl
  desplit-ident (suc m) (suc i) with split m i | desplit-ident m i
  ... | inl j | p = cong suc p
  ... | inr j | p = cong suc p

  sumIso : Iso (Fin m  Fin n) (Fin (m + n))
  sumIso {m} .fun = Sum.rec up (m ⊕_)
  sumIso {m} .inv i = split m i
  sumIso {m} .rightInv i = desplit-ident m i
  sumIso {m} .leftInv (inr j) = resplit-identᵣ m j
  sumIso {m} .leftInv (inl i) = resplit-identₗ m i

sum≡ : Fin m  Fin n  Fin (m + n)
sum≡ = isoToPath Isos.sumIso