{-# OPTIONS --without-K --rewriting --overlapping-instances #-}

open import lib.Basics
open import lib.types.Coproduct
open import lib.types.Fin
open import lib.types.Pi
open import lib.types.Truncation

module lib.types.Choice where

unchoose :  {i j} {n : ℕ₋₂} {A : Type i} {B : A  Type j}
   Trunc n (Π A B)  Π A (Trunc n  B)
unchoose = Trunc-rec  f  [_]  f)

has-choice :  {i} (n : ℕ₋₂) (A : Type i) j  Type (lmax i (lsucc j))
has-choice {i} n A j = (B : A  Type j)  is-equiv (unchoose {n = n} {A} {B})

Empty-has-choice :  {n} {j}  has-choice n Empty j
Empty-has-choice {n} B = is-eq to from to-from from-to where
  to = unchoose {n = n} {Empty} {B}

  from : Π Empty (Trunc n  B)  Trunc n (Π Empty B)
  from _ = [ (λ{()}) ]

  abstract
    to-from :  f  to (from f) == f
    to-from _ = λ= λ{()}

    from-to :  f  from (to f) == f
    from-to = Trunc-elim  _  ap [_] (λ= λ{()}))

Unit-has-choice :  {n} {j}  has-choice n Unit j
Unit-has-choice {n} B = is-eq to from to-from from-to where
  to = unchoose {n = n} {Unit} {B}

  Unit-elim' : B unit  Π Unit B
  Unit-elim' u unit = u

  from : Π Unit (Trunc n  B)  Trunc n (Π Unit B)
  from f = Trunc-fmap Unit-elim' (f unit)

  abstract
    to-from :  f  to (from f) == f
    to-from f = λ= λ{unit 
      Trunc-elim
        {P = λ f-unit  to (Trunc-fmap Unit-elim' f-unit) unit == f-unit}
         _  idp)
        (f unit)}

    from-to :  f  from (to f) == f
    from-to = Trunc-elim  f  ap [_] (λ= λ{unit  idp}))

Coprod-has-choice :  {i j} {n} {A : Type i} {B : Type j} {k}
   has-choice n A k  has-choice n B k
   has-choice n (A  B) k
Coprod-has-choice {n = n} {A} {B} A-hc B-hc C = is-eq to from to-from from-to where
  A-unchoose = unchoose {n = n} {A} {C  inl}
  B-unchoose = unchoose {n = n} {B} {C  inr}
  module A-unchoose = is-equiv (A-hc (C  inl))
  module B-unchoose = is-equiv (B-hc (C  inr))

  to = unchoose {n = n} {A  B} {C}

  from : Π (A  B) (Trunc n  C)  Trunc n (Π (A  B) C)
  from f = Trunc-fmap2 Coprod-elim (A-unchoose.g (f  inl)) (B-unchoose.g (f  inr))

  abstract
    to-from-inl' :  f a  to (from f) (inl a) == A-unchoose (A-unchoose.g (f  inl)) a
    to-from-inl' f a = Trunc-elim
      {P = λ f-inl  to (Trunc-fmap2 Coprod-elim f-inl (B-unchoose.g (f  inr))) (inl a) == A-unchoose f-inl a}
       f-inl  Trunc-elim
        {P = λ f-inr  to (Trunc-fmap2 Coprod-elim [ f-inl ] f-inr) (inl a) == [ f-inl a ]}
         f-inr  idp)
        (B-unchoose.g (f  inr)))
      (A-unchoose.g (f  inl))

    to-from-inl :  f a  to (from f) (inl a) == f (inl a)
    to-from-inl f a = to-from-inl' f a  app= (A-unchoose.f-g (f  inl)) a

    to-from-inr' :  f b  to (from f) (inr b) == B-unchoose (B-unchoose.g (f  inr)) b
    to-from-inr' f b = Trunc-elim
      {P = λ f-inr  to (Trunc-fmap2 Coprod-elim (A-unchoose.g (f  inl)) f-inr) (inr b) == B-unchoose f-inr b}
       f-inr  Trunc-elim
        {P = λ f-inl  to (Trunc-fmap2 Coprod-elim f-inl [ f-inr ]) (inr b) == [ f-inr b ]}
         f-inl  idp)
        (A-unchoose.g (f  inl)))
      (B-unchoose.g (f  inr))

    to-from-inr :  f b  to (from f) (inr b) == f (inr b)
    to-from-inr f b = to-from-inr' f b  app= (B-unchoose.f-g (f  inr)) b

    to-from :  f  to (from f) == f
    to-from f = λ= λ{(inl a)  to-from-inl f a; (inr b)  to-from-inr f b}

    from-to :  f  from (to f) == f
    from-to = Trunc-elim
       f 
        Trunc-fmap2 Coprod-elim (A-unchoose.g  a  [ f (inl a)])) (B-unchoose.g  b  [ f (inr b)]))
          =⟨ ap2 (Trunc-fmap2 Coprod-elim) (A-unchoose.g-f [ f  inl ]) (B-unchoose.g-f [ f  inr ]) 
        [ Coprod-elim (f  inl) (f  inr) ]
          =⟨ ap [_] $ λ= (λ{(inl _)  idp; (inr _)  idp}) 
        [ f ]
          =∎)

equiv-preserves-choice :  {i j} {n} {A : Type i} {B : Type j} (e : A  B) {k}
   has-choice n A k  has-choice n B k
equiv-preserves-choice {n = n} {A} {B} (f , f-ise) A-hc C = is-eq to from to-from from-to where
  module f = is-equiv f-ise

  A-unchoose = unchoose {n = n} {A} {C  f}
  module A-unchoose = is-equiv (A-hc (C  f))

  to = unchoose {n = n} {B} {C}

  from' : Trunc n (Π A (C  f))  Trunc n (Π B C)
  from' = Trunc-fmap  g' b  transport C (f.f-g b) (g' (f.g b)))

  from : Π B (Trunc n  C)  Trunc n (Π B C)
  from g = from' (A-unchoose.g (g  f))

  abstract
    to-from''' :  (g-f' : Π A (C  f)) {a a'} (path : f.g (f a) == a')
       transport C (ap f path) (g-f' (f.g (f a))) == g-f' a'
    to-from''' g-f' idp = idp

    to-from'' :  (g-f' : Π A (C  f)) a
       transport C (f.f-g (f a)) (g-f' (f.g (f a))) == g-f' a
    to-from'' g-f' a =
      transport C (f.f-g (f a)) (g-f' (f.g (f a)))
        =⟨ ! $ ap  p  transport C p (g-f' (f.g (f a)))) (f.adj a) 
      transport C (ap f (f.g-f a)) (g-f' (f.g (f a)))
        =⟨ to-from''' g-f' (f.g-f a) 
      g-f' a
        =∎

    to-from' :  g a  to (from g) (f a) == A-unchoose (A-unchoose.g (g  f)) a
    to-from' g a =
      Trunc-elim
        {P = λ g-f  to (from' g-f) (f a) == A-unchoose g-f a}
         g-f'  ap [_] $ to-from'' g-f' a)
        (A-unchoose.g (g  f))

    to-from :  g  to (from g) == g
    to-from g = λ= λ b  transport
       b  to (from g) b == g b)
      (f.f-g b)
      ( to (from g) (f (f.g b))
          =⟨ to-from' g (f.g b) 
        A-unchoose (A-unchoose.g (g  f)) (f.g b)
          =⟨ app= (A-unchoose.f-g (g  f)) (f.g b) 
        g (f (f.g b))
          =∎)

    from-to' :  g {b b'} (path : f (f.g b) == b')
       transport C path (g (f (f.g b))) == g b'
    from-to' g idp = idp

    from-to :  g  from (to g) == g
    from-to = Trunc-elim
      {P = λ g  from (to g) == g}
       g 
        from' (A-unchoose.g  a  [ g (f a) ]))
          =⟨ ap from' (A-unchoose.g-f [ (g  f) ]) 
        from' [ (g  f) ]
          =⟨ ap [_] $ λ=  b  from-to' g (f.f-g b)) 
        [ g ]
          =∎)

Fin-has-choice :  (n : ℕ₋₂) {m} l  has-choice n (Fin m) l
Fin-has-choice _ {O} _ = equiv-preserves-choice
  (Fin-equiv-Empty ⁻¹) Empty-has-choice
Fin-has-choice n {S m} l = equiv-preserves-choice
  (Fin-equiv-Coprod ⁻¹) (Coprod-has-choice (Fin-has-choice n l) Unit-has-choice)