{-# OPTIONS --without-K #-}
module Truncation_Level_Criteria where

open import lib.Basics hiding (_⊔_)
open import lib.NType2

open import lib.types.Nat hiding (_+_)
open import lib.types.Pi
open import lib.types.Sigma
open import lib.types.Unit
open import lib.types.Empty
open import lib.types.TLevel

open import Preliminaries
open import Pointed

-- Chapter 3: Truncation Level Criteria

-- Chapter 3.1: Hedberg's Theorem Revisited

-- Proposition 3.1.1 is below.

-- Definition 3.1.2
const : ∀{i j} {X : Type i} {Y : Type j} → (f : X → Y) → Type (i ⊔ j)
const {X = X} f = (x₁ x₂ : X) → (f x₁) == (f x₂)

coll : ∀{i} → Type i → Type i
coll X = Σ (X → X) const

pathColl : ∀ {i} → Type i → Type i
pathColl X = (x₁ x₂ : X) → coll (x₁ == x₂)

-- Lemma 3.1.3
discr→pathColl : ∀ {i} {X : Type i} → has-dec-eq X → pathColl X
discr→pathColl dec x₁ x₂ with (dec x₁ x₂)
discr→pathColl dec x₁ x₂ | inl p  = (λ _ → p) , (λ _ _ → idp)
discr→pathColl dec x₁ x₂ | inr np = idf _ , λ p → Empty-elim (np p)

-- Lemma 3.1.4
pathColl→isSet : ∀ {i} {X : Type i} → pathColl X → is-set X
pathColl→isSet {X = X} pc x₁ x₂ = all-paths-is-prop paths-equal where
  claim : (y₁ y₂ : X) → (p : y₁ == y₂) → p == ! (fst (pc _ _) idp) ∙ fst (pc _ _) p
  claim x₁ .x₁ idp = ! (!-inv-l (fst (pc x₁ x₁) idp))
  paths-equal : (p₁ p₂ : x₁ == x₂) → p₁ == p₂
  paths-equal p₁ p₂ =
    p₁                                      =⟨ claim _ _ p₁ ⟩
    ! (fst (pc _ _) idp) ∙ fst (pc _ _) p₁  =⟨ ap (λ q → (! (fst (pc x₁ x₁) idp)) ∙ q) (snd (pc _ _) p₁ p₂) ⟩ -- whiskering
    ! (fst (pc _ _) idp) ∙ fst (pc _ _) p₂  =⟨ ! (claim _ _ p₂) ⟩
    p₂                                      ∎

-- Proposition 3.1.1
hedberg : ∀ {i} {X : Type i} → has-dec-eq X → is-set X
hedberg = pathColl→isSet ∘ discr→pathColl

-- Definition 3.1.5
stable : ∀ {i} → Type i → Type i
stable X = ¬ (¬ X) → X

separated : ∀ {i} → Type i → Type i
separated X = (x₁ x₂ : X) → stable (x₁ == x₂)

-- Proposition 3.1.6
sep→set : ∀ {i} {X : Type i} → (separated X) → is-set X
sep→set {X = X} sep = pathColl→isSet isPc where
  isPc : pathColl X
  isPc x₁ x₂ = f , c where
    f : x₁ == x₂ → x₁ == x₂
    f = (sep x₁ x₂) ∘ (λ p np → np p)
    c : const f
    c p₁ p₂ =
      f p₁                        =⟨ idp ⟩
      (sep x₁ x₂) (λ np → np p₁)  =⟨ ap (sep x₁ x₂)
                                     (λ= λ np → Empty-elim {P = λ _ → np p₁ == np p₂} (np p₁)) ⟩
      (sep x₁ x₂) (λ np → np p₂)  =⟨ idp ⟩
      f p₂                        ∎

-- Lemma 3.1.7
-- first: definition of 'reflexive propositional relation that implies identity'
rel-ref-id : ∀ {i} → Type i → Type (lsucc i)
rel-ref-id {i} X = Σ (X → X → Type i)
                     λ R →
                           ((x : X) → R x x)
                         × ((x₁ x₂ : X) → (R x₁ x₂) → x₁ == x₂)
                         × ((x₁ x₂ : X) → is-prop (R x₁ x₂))

-- the actual Lemma 3.1.7
module lem317 {i : _} (X : Type i) (Rrip : rel-ref-id X) where

  R : X → X → Type i
  R = fst Rrip
  ref : (x : X) → R x x
  ref = fst (snd (Rrip)) -- fst (fst (snd Rrip))
  ii : (x₁ x₂ : X) → (R x₁ x₂) → x₁ == x₂
  ii = fst (snd (snd (Rrip))) -- snd (fst (snd Rrip))
  pr : (x₁ x₂ : X) → is-prop (R x₁ x₂)
  pr = snd (snd (snd Rrip))

  f : (x₁ x₂ : X) → x₁ == x₂ → R x₁ x₂
  f x₁ .x₁ idp = ref x₁

  id↔R : (x₁ x₂ : X) → (x₁ == x₂) ↔ R x₁ x₂
  id↔R x₁ x₂ = f _ _ , ii _ _

  pc : pathColl X
  pc x₁ x₂ = ii _ _ ∘ f _ _ , λ p₁ p₂ → ap (ii x₁ x₂) (prop-has-all-paths (pr x₁ x₂) _ _)

  isSet : is-set X
  isSet = pathColl→isSet pc

open wtrunc

-- Definition 3.1.9
splitSup : ∀ {i} → (X : Type i) → Type i
splitSup X = ∣∣ X ∣∣ → X

hSeparated : ∀ {i} → (X : Type i) → Type i
hSeparated X = (x₁ x₂ : X) → splitSup (x₁ == x₂)

-- Proposition 3.1.10
-- we show "2->1", "1->3", "3->2" as in the thesis.
-- for the last part, we show "1->4" and "4->3
set-characterisations : ∀ {i} → (X : Type i) →
      (pathColl X → is-set X)
    × (is-set X → rel-ref-id X)
    × (rel-ref-id X → pathColl X)
    × (is-set X → hSeparated X)
    × (hSeparated X → rel-ref-id X)
set-characterisations X =
  pathColl→isSet ,
  (λ ss → _==_ , (λ _ → idp) , (λ _ _ p → p) , ss) ,
  (lem317.pc X) ,
  (λ ss x₁ x₂ → tr-rec (ss x₁ x₂) (λ p → p)) ,
  (λ hsep → ((λ x₁ x₂ → ∣∣ x₁ == x₂ ∣∣) , (λ _ → ∣ idp ∣) , hsep , λ _ _ → tr-is-prop))

-- The rest of this section is only a replication of the arguments that we have given so far (for that reason, the proofs are not given in the article).
-- They do not directly follow from the propositions that we have proved before, but they directly imply them.
-- Of course, replication of arguments is not a good style for a formalization -
-- we chose this "disadvantageous" order purely as we believe it led to a better presentation in the article.

-- Lemma 3.1.11
pathColl→isSet-local : ∀ {i} {X : Type i} → {x₀ : X} → ((y : X) → coll (x₀ == y)) → (y : X) → is-prop (x₀ == y)
pathColl→isSet-local {X = X} {x₀} pc y = all-paths-is-prop paths-equal  where
  claim : (y : X) → (p : x₀ == y) → p == ! (fst (pc _) idp) ∙ fst (pc _) p
  claim .x₀ idp = ! (!-inv-l (fst (pc _) idp))
  paths-equal : (p₁ p₂ : x₀ == y) → p₁ == p₂
  paths-equal p₁ p₂ =
    p₁                                  =⟨ claim _ p₁ ⟩
    ! (fst (pc _) idp) ∙ fst (pc _) p₁  =⟨ ap (λ q → (! (fst (pc x₀) idp)) ∙ q) (snd (pc y) p₁ p₂) ⟩ -- whiskering
    ! (fst (pc _) idp) ∙ fst (pc _) p₂  =⟨ ! (claim _ p₂) ⟩
    p₂                                  ∎

-- Proposition 3.11.12
hedberg-local : ∀ {i} {X : Type i} → {x₀ : X} → ((y : X) → Coprod (x₀ == y) (¬(x₀ == y))) → (y : X) → is-prop (x₀ == y)
hedberg-local {X = X} {x₀ = x₀} dec = pathColl→isSet-local local-pathColl where
  local-pathColl : (y : X) → coll (x₀ == y)
  local-pathColl y with (dec y)
  local-pathColl y₁ | inl p  = (λ _ → p) , (λ _ _ → idp)
  local-pathColl y₁ | inr np = idf _ , (λ p → Empty-elim (np p))

-- Lemma 3.1.13. This needs function extensionality.
sep→set-local : ∀ {i} {X : Type i} {x₀ : X} → ((y : X) → stable (x₀ == y)) → (y : X) → is-prop (x₀ == y)
sep→set-local {X = X} {x₀ = x₀} sep = pathColl→isSet-local is-pathColl where
  is-pathColl : (y : X) → coll (x₀ == y)
  is-pathColl y = f , c where
    f : x₀ == y → x₀ == y
    f = (sep y) ∘ (λ p np → np p)
    c : const f
    c p₁ p₂ =
      f p₁                    =⟨ idp ⟩
      (sep _) (λ np → np p₁) =⟨ ap (sep y)
                                    (λ= (λ np → prop-has-all-paths Empty-is-prop _ _)) ⟩
      (sep _) (λ np → np p₂) =⟨ idp ⟩
      f p₂ ∎

-- Chapter 3.2: Generalisations to Higher Levels

-- first: some lemmata.

-- If A -> B -> A is the identity, then
-- a₁ == a₂ → s a₁ == s a₂ → r(s a₁) == r(s a₂)
-- is also the identity in an appropriate sense
retract-path-retract : ∀ {i j} {A : Type i} {B : Type j}
  → (s : A → B) → (r : B → A) → (s-r : (a : A) → r (s a) == a)
  → (a₁ a₂ : A)
  → (p : a₁ == a₂) → ! (s-r a₁) ∙ (ap r (ap s p)) ∙ (s-r a₂) == p
retract-path-retract s r s-r a₁ .a₁ idp = !-inv-l (s-r a₁)

-- retracts of n-types are n-truncated
retract-is-truncated : ∀ {n : ℕ₋₂} {i j} {A : Type i} {B : Type j}
  → (has-level n B) → (s : A → B) → (r : B → A) → ((a : A) → r (s a) == a) → has-level n A
retract-is-truncated {⟨-2⟩} h s r s-r = inhab-prop-is-contr (r (fst h)) (all-paths-is-prop (λ a₁ a₂ →
                       a₁       =⟨ ! (s-r a₁) ⟩
                       r(s(a₁)) =⟨ ap r (contr-has-all-paths h _ _) ⟩
                       r(s(a₂)) =⟨ s-r a₂ ⟩
                       a₂ ∎
                       ))
retract-is-truncated {S n} h s r s-r a₁ a₂ =
  retract-is-truncated {n} {A = a₁ == a₂} {B = s a₁ == s a₂}
                       (h (s a₁) (s a₂))
                       (ap s)
                       (λ p → ! (s-r a₁) ∙ (ap r p) ∙ (s-r a₂))
                       (retract-path-retract s r s-r a₁ a₂)

-- this is essentially one direction of a local version of the lemma which says that
-- a type is n-truncated iff its loop spaces are (n-1)-truncated
trunclevel-aux₁ : ∀ {i} {X : Type i} {x₀ : X} → (n : ℕ) → ((x : X) → has-level (n -2) (x₀ == x))
                   → is-contr (fst ((Ω ^ n) (X , x₀)))
trunclevel-aux₁ {x₀ = x₀} O h = x₀ , λ x → fst (h x)
trunclevel-aux₁ {X = X} {x₀ = x₀} (S n) h = trunclevel-aux₁ n (h x₀ idp)

-- the other direction...
trunclevel-aux₂ : ∀ {i} {X : Type i} {x₀ : X} → (n : ℕ) → is-contr (fst ((Ω ^ n) (X , x₀)))
                   → ((x : X) → has-level (n -2) (x₀ == x))
trunclevel-aux₂ {X = X} {x₀ = x₀} O h = λ x → inhab-prop-is-contr (contr-has-all-paths h x₀ x) (contr-is-prop (contr-is-prop h x₀ x))
trunclevel-aux₂ {X = X} {x₀ = x₀} (S n) h .x₀ idp = trunclevel-aux₂ n h

-- Theorem 3.2.1

-- in the thesis, we write "let n ≥ -1 be a number".
-- As we do not want to introduce a type of number that are at least -1,
-- we use ℕ.
-- To make up for this, we reduce n everywhere by 1.
module GLHA {i : ULevel} {X : Type i} {x₀ : X} {n : ℕ} where

  Ω-contr : Type i
--  loop-contr = is-contr (fst ((Ω ^ (n + 1)) (X , x₀)))
  Ω-contr = is-contr (fst ((Ω ^ n) (X , x₀)))

  locally-truncated : Type i
--  locally-truncated = (x : X) → has-level (n -1) (x₀ == x)
  locally-truncated = (x : X) → has-level (n -2) (x₀ == x)

  tr-rel-id : Type (lsucc i)
  tr-rel-id = Σ (X → Type i) λ Q →
--                ((x : X) → has-level (n -1) (Q x))
                ((x : X) → has-level (n -2) (Q x))
                × (Q x₀)
                × ((x : X) → Q x → x₀ == x)

  tr-rel-id→locally-truncated : tr-rel-id → locally-truncated
  tr-rel-id→locally-truncated (Q , h , q₀ , ii) = λ x → retract-is-truncated {n = n -2} {A = x₀ == x} {B = Q x} (h x) s r r-s where

    r : {x : X} → (Q x) → (x₀ == x)
    r {x} q = ! (ii x₀ q₀) ∙ (ii x q)

    s : {x : X} → (x₀ == x) → Q x
    s idp = q₀

    r-s : {x : X} → (p : x₀ == x) → r (s p) == p
    r-s idp = !-inv-l (ii x₀ q₀)

  locally-truncated→Ω-contr : locally-truncated → Ω-contr
  locally-truncated→Ω-contr loctr = trunclevel-aux₁ n loctr -- trunclevel-aux n loctr

  Ω-contr→tr-rel-id : Ω-contr → tr-rel-id
  Ω-contr→tr-rel-id h = (λ x → x₀ == x) , (trunclevel-aux₂ n h) , idp , (λ x p → p)

  module with-higher-truncs where
    -- for convenience, we import truncations from the library.
    open import lib.types.Truncation

    -- fourth item in Theorem 3.2.1
    id-stable : Type i
    id-stable = (x : X) → Trunc (n -2) (x₀ == x) → x₀ == x

    -- it is very easy to see that (4) and (2) are logically equivalent.
    -- 2 ⇒ 4
    tr-rel-id→id-stable : tr-rel-id → id-stable
    tr-rel-id→id-stable (Q , h , q₀ , ii) = λ x → (ii x) ∘ (Trunc-rec (h x) (λ p → transport _ p q₀))

    -- 4 ⇒ 2
    id-stable→tr-rel-id : id-stable → tr-rel-id
    id-stable→tr-rel-id idst = (λ x → Trunc (n -2) (x₀ == x)) , (λ _ → Trunc-level) , [ idp ] , idst


------------------------------------------------------------------------
-- List-related properties
------------------------------------------------------------------------

-- Note that the lemmas below could be generalised to work with other
-- equalities than _≡_.

module Data.List.Properties where

open import Data.List
open import Data.Nat
open import Data.Nat.Properties
open import Data.Bool
open import Data.Function
open import Data.Product as Prod hiding (map)
open import Data.Maybe
open import Relation.Binary.PropositionalEquality
import Relation.Binary.EqReasoning as Eq
open import Algebra

∷-injective : ∀ {A} {x y xs ys} →
              (List A ∶ x ∷ xs) ≡ (y ∷ ys) → (x ≡ y) × (xs ≡ ys)
∷-injective refl = (refl , refl)

right-identity-unique : ∀ {A} (xs : List A) {ys} →
                        xs ≡ xs ++ ys → ys ≡ []
right-identity-unique []       refl = refl
right-identity-unique (x ∷ xs) eq   =
  right-identity-unique xs (proj₂ (∷-injective eq))

-- Map, sum, and append.

map-++-commute : ∀ {a b} (f : a → b) xs ys →
                 map f (xs ++ ys) ≡ map f xs ++ map f ys
map-++-commute f []       ys = refl
map-++-commute f (x ∷ xs) ys =
  cong (_∷_ (f x)) (map-++-commute f xs ys)

sum-++-commute : ∀ xs ys → sum (xs ++ ys) ≡ sum xs + sum ys
sum-++-commute []       ys = refl
sum-++-commute (x ∷ xs) ys = begin
  x + sum (xs ++ ys)
                         ≡⟨ cong (_+_ x) (sum-++-commute xs ys) ⟩
  x + (sum xs + sum ys)
                         ≡⟨ sym $ +-assoc x _ _ ⟩
  (x + sum xs) + sum ys
                         ∎
  where
  open CommutativeSemiring commutativeSemiring hiding (_+_; sym)
  open ≡-Reasoning

-- Various properties about folds.

foldr-universal : ∀ {a b} (h : List a → b) f e →
                  (h [] ≡ e) →
                  (∀ x xs → h (x ∷ xs) ≡ f x (h xs)) →
                  h ≗ foldr f e
foldr-universal h f e base step []       = base
foldr-universal h f e base step (x ∷ xs) = begin
  h (x ∷ xs)
    ≡⟨ step x xs ⟩
  f x (h xs)
    ≡⟨ cong (f x) (foldr-universal h f e base step xs) ⟩
  f x (foldr f e xs)
    ∎
  where open ≡-Reasoning

foldr-fusion : ∀ {a b c} (h : b → c)
                 {f : a → b → b} {g : a → c → c} (e : b) →
               (∀ x y → h (f x y) ≡ g x (h y)) →
               h ∘ foldr f e ≗ foldr g (h e)
foldr-fusion h {f} {g} e fuse =
  foldr-universal (h ∘ foldr f e) g (h e) refl
                  (λ x xs → fuse x (foldr f e xs))

idIsFold : ∀ {a} → id {a = List a} ≗ foldr _∷_ []
idIsFold = foldr-universal id _∷_ [] refl (λ _ _ → refl)

++IsFold : ∀ {a} (xs ys : List a) →
           xs ++ ys ≡ foldr _∷_ ys xs
++IsFold xs ys =
  begin
    xs ++ ys
  ≡⟨ cong (λ xs → xs ++ ys) (idIsFold xs) ⟩
    foldr _∷_ [] xs ++ ys
  ≡⟨ foldr-fusion (λ xs → xs ++ ys) [] (λ _ _ → refl) xs ⟩
    foldr _∷_ ([] ++ ys) xs
  ≡⟨ refl ⟩
    foldr _∷_ ys xs
  ∎
  where open ≡-Reasoning

mapIsFold : ∀ {a b} {f : a → b} →
            map f ≗ foldr (λ x ys → f x ∷ ys) []
mapIsFold {f = f} =
  begin
    map f
  ≈⟨ cong (map f) ∘ idIsFold ⟩
    map f ∘ foldr _∷_ []
  ≈⟨ foldr-fusion (map f) [] (λ _ _ → refl) ⟩
    foldr (λ x ys → f x ∷ ys) []
  ∎
  where open Eq (_ →-setoid _)

concat-map : ∀ {a b} {f : a → b} →
             concat ∘ map (map f) ≗ map f ∘ concat
concat-map {f = f} =
  begin
    concat ∘ map (map f)
  ≈⟨ cong concat ∘ mapIsFold ⟩
    concat ∘ foldr (λ xs ys → map f xs ∷ ys) []
  ≈⟨ foldr-fusion concat [] (λ _ _ → refl) ⟩
    foldr (λ ys zs → map f ys ++ zs) []
  ≈⟨ sym ∘
     foldr-fusion (map f) [] (λ ys zs → map-++-commute f ys zs) ⟩
    map f ∘ concat
  ∎
  where open Eq (_ →-setoid _)

map-id : ∀ {A} → map id ≗ id {List A}
map-id = begin
  map id        ≈⟨ mapIsFold ⟩
  foldr _∷_ []  ≈⟨ sym ∘ idIsFold ⟩
  id            ∎
  where open Eq (_ →-setoid _)

map-compose : ∀ {a b c} {g : b → c} {f : a → b} →
              map (g ∘ f) ≗ map g ∘ map f
map-compose {g = g} {f} =
  begin
    map (g ∘ f)
  ≈⟨ cong (map (g ∘ f)) ∘ idIsFold ⟩
    map (g ∘ f) ∘ foldr _∷_ []
  ≈⟨ foldr-fusion (map (g ∘ f)) [] (λ _ _ → refl) ⟩
    foldr (λ a y → g (f a) ∷ y) []
  ≈⟨ sym ∘ foldr-fusion (map g) [] (λ _ _ → refl) ⟩
    map g ∘ foldr (λ a y → f a ∷ y) []
  ≈⟨ cong (map g) ∘ sym ∘ mapIsFold ⟩
    map g ∘ map f
  ∎
  where open Eq (_ →-setoid _)

foldr-cong : ∀ {a b} {f₁ f₂ : a → b → b} {e₁ e₂ : b} →
             (∀ x y → f₁ x y ≡ f₂ x y) → e₁ ≡ e₂ →
             foldr f₁ e₁ ≗ foldr f₂ e₂
foldr-cong {f₁ = f₁} {f₂} {e} f₁≗₂f₂ refl =
  begin
    foldr f₁ e
  ≈⟨ cong (foldr f₁ e) ∘ idIsFold ⟩
    foldr f₁ e ∘ foldr _∷_ []
  ≈⟨ foldr-fusion (foldr f₁ e) [] (λ x xs → f₁≗₂f₂ x (foldr f₁ e xs)) ⟩
    foldr f₂ e
  ∎
  where open Eq (_ →-setoid _)

map-cong : ∀ {a b} {f g : a → b} → f ≗ g → map f ≗ map g
map-cong {f = f} {g} f≗g =
  begin
    map f
  ≈⟨ mapIsFold ⟩
    foldr (λ x ys → f x ∷ ys) []
  ≈⟨ foldr-cong (λ x ys → cong₂ _∷_ (f≗g x) refl) refl ⟩
    foldr (λ x ys → g x ∷ ys) []
  ≈⟨ sym ∘ mapIsFold ⟩
    map g
  ∎
  where open Eq (_ →-setoid _)

-- Take, drop, and splitAt.

take++drop : ∀ {a} n (xs : List a) → take n xs ++ drop n xs ≡ xs
take++drop zero    xs       = refl
take++drop (suc n) []       = refl
take++drop (suc n) (x ∷ xs) =
  cong (λ xs → x ∷ xs) (take++drop n xs)

splitAt-defn : ∀ {a} n → splitAt {a} n ≗ < take n , drop n >
splitAt-defn zero    xs       = refl
splitAt-defn (suc n) []       = refl
splitAt-defn (suc n) (x ∷ xs) with splitAt n xs | splitAt-defn n xs
... | (ys , zs) | ih = cong (Prod.map (_∷_ x) id) ih

-- TakeWhile, dropWhile, and span.

takeWhile++dropWhile : ∀ {a} (p : a → Bool) (xs : List a) →
                       takeWhile p xs ++ dropWhile p xs ≡ xs
takeWhile++dropWhile p []       = refl
takeWhile++dropWhile p (x ∷ xs) with p x
... | true  = cong (_∷_ x) (takeWhile++dropWhile p xs)
... | false = refl

span-defn : ∀ {a} (p : a → Bool) →
            span p ≗ < takeWhile p , dropWhile p >
span-defn p []       = refl
span-defn p (x ∷ xs) with p x
... | true  = cong (Prod.map (_∷_ x) id) (span-defn p xs)
... | false = refl

-- Partition.

partition-defn : ∀ {a} (p : a → Bool) →
                 partition p ≗ < filter p , filter (not ∘ p) >
partition-defn p []       = refl
partition-defn p (x ∷ xs)
 with p x | partition p xs | partition-defn p xs
...  | true  | (ys , zs) | eq = cong (Prod.map (_∷_ x) id) eq
...  | false | (ys , zs) | eq = cong (Prod.map id (_∷_ x)) eq

-- Inits, tails, and scanr.

scanr-defn : ∀ {a b} (f : a → b → b) (e : b) →
             scanr f e ≗ map (foldr f e) ∘ tails
scanr-defn f e []             = refl
scanr-defn f e (x ∷ [])       = refl
scanr-defn f e (x₁ ∷ x₂ ∷ xs)
  with scanr f e (x₂ ∷ xs) | scanr-defn f e (x₂ ∷ xs)
...  | [] | ()
...  | y ∷ ys | eq with ∷-injective eq
...        | y≡fx₂⦇f⦈xs , _ = cong₂ (λ z zs → f x₁ z ∷ zs) y≡fx₂⦇f⦈xs eq

scanl-defn : ∀ {a b} (f : a → b → a) (e : a) →
             scanl f e ≗ map (foldl f e) ∘ inits
scanl-defn f e []       = refl
scanl-defn f e (x ∷ xs) = cong (_∷_ e) (begin
     scanl f (f e x) xs
  ≡⟨ scanl-defn f (f e x) xs ⟩
     map (foldl f (f e x)) (inits xs)
  ≡⟨ refl ⟩
     map (foldl f e ∘ (_∷_ x)) (inits xs)
  ≡⟨ map-compose (inits xs) ⟩
     map (foldl f e) (map (_∷_ x) (inits xs))
  ∎)
  where open ≡-Reasoning

-- Length.

length-map : ∀ {A B} (f : A → B) xs →
             length (map f xs) ≡ length xs
length-map f []       = refl
length-map f (x ∷ xs) = cong suc (length-map f xs)

length-gfilter : ∀ {A B} (p : A → Maybe B) xs →
                 length (gfilter p xs) ≤ length xs
length-gfilter p []       = z≤n
length-gfilter p (x ∷ xs) with p x
length-gfilter p (x ∷ xs) | just y  = s≤s (length-gfilter p xs)
length-gfilter p (x ∷ xs) | nothing = ≤-step (length-gfilter p xs)


{-# OPTIONS --without-K #-}
module PathStructure.Unit where

open import Equivalence
open import Types

split-path : {x y : ⊤} → x ≡ y → ⊤
split-path _ = _

merge-path : {x y : ⊤} → ⊤ → x ≡ y
merge-path _ = refl

split-merge-eq : {x y : ⊤} → (x ≡ y) ≃ ⊤
split-merge-eq
  = split-path
  , (merge-path , λ _ → refl)
  , (merge-path , J {A = ⊤}
      (λ _ _ p → refl ≡ p)
      (λ _ → refl) _ _)


{-# OPTIONS --without-K --safe #-}

module Categories.Category.Construction.Elements where

-- Category of Elements -- given a functor into Sets, construct the category of elements 'above' each object
-- see https://ncatlab.org/nlab/show/category+of+elements
open import Level
open import Data.Product using (Σ; _,_; Σ-syntax; proj₁; proj₂; map)
open import Relation.Binary.PropositionalEquality
  using (_≡_; refl; sym; trans; cong)
open import Function hiding (_∘_) renaming (id to idf)

open import Categories.Category using (Category)
open import Categories.Functor hiding (id)
open import Categories.Category.Instance.Sets
open import Categories.Category.Instance.Cats
open import Categories.Category.Construction.Functors
open import Categories.NaturalTransformation.NaturalIsomorphism hiding (refl; sym; trans)
open import Categories.NaturalTransformation hiding (id)
import Categories.Morphism.Reasoning as MR

private
  variable
    o ℓ e o′ : Level

-- the first, most explicit definition is taken as 'canonical'.
Elements : {C : Category o ℓ e} → Functor C (Sets o′) → Category (o ⊔ o′) (ℓ ⊔ o′) e
Elements {C = C} F = record
  { Obj       = Σ Obj F₀
  ; _⇒_       = λ { (c , x) (c′ , x′) → Σ (c ⇒ c′) (λ f → F₁ f x ≡ x′)  }
  ; _≈_       = λ p q → proj₁ p ≈ proj₁ q
  ; id        = id , identity
  ; _∘_       = λ { (f , Ff≡) (g , Fg≡) → f ∘ g ,  trans homomorphism (trans (cong (F₁ f) Fg≡) Ff≡)}
  ; assoc     = assoc
  ; sym-assoc = sym-assoc
  ; identityˡ = identityˡ
  ; identityʳ = identityʳ
  ; identity² = identity²
  ; equiv     = record { refl = Equiv.refl ; sym = Equiv.sym ; trans = Equiv.trans }
  ; ∘-resp-≈  = ∘-resp-≈
  }
  where
  open Category C
  open Functor F

-- This induces a functor. It is largely uninteresting as it is as close to 'strict'
-- as things get in this setting.
El : {C : Category o ℓ e} → Functor (Functors C (Sets o′)) (Cats (o ⊔ o′) (ℓ ⊔ o′) e)
El {C = C} = record
  { F₀ = Elements
  ; F₁ = λ A⇒B → record
    { F₀ = map idf (η A⇒B _)
    ; F₁ = map idf λ {f} eq → trans (sym $ commute A⇒B f) (cong (η A⇒B _) eq)
    ; identity = Equiv.refl
    ; homomorphism = Equiv.refl
    ; F-resp-≈ = idf
    }
  ; identity = λ {P} → record
    { F⇒G = record
      { η           = λ X → id , identity P
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; F⇐G = record
      { η           = λ X → id , identity P
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; iso = λ X → record { isoˡ = identityˡ ; isoʳ = identityʳ } }
  ; homomorphism = λ {X₁} {Y₁} {Z₁} → record
    { F⇒G = record
      { η           = λ X → id , identity Z₁
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; F⇐G = record
      { η           = λ X → id , identity Z₁
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; iso = λ X → record { isoˡ = identityˡ ; isoʳ = identityʳ }
    }
  ; F-resp-≈ = λ {_} {B₁} f≈g → record
    { F⇒G = record
      { η           = λ _ → id , trans (identity B₁) f≈g
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; F⇐G = record
      { η           = λ _ → id , trans (identity B₁) (sym f≈g)
      ; commute     = λ _ → MR.id-comm-sym C
      ; sym-commute = λ _ → MR.id-comm C
      }
    ; iso = λ X → record { isoˡ = identityˡ ; isoʳ = identityʳ } }
  }
  where
  open Category C
  open Functor
  open NaturalTransformation

-- But there are all sorts of interesting alternate definitions
-- note how this one is WAY less level polymorphic than the above!
-- it looks like it contains more information... but not really either.
{-  Unfinished because it is super tedious and not informative!
module Alternate-Pullback {C : Category (suc o) o o} (F : Functor C (Sets o)) where
  open import Categories.Category.Instance.PointedSets
  open import Categories.Category.Instance.Cats
  open import Categories.Diagram.Pullback (Cats (suc o) o o)
  open import Categories.Category.Product using (_※_; πˡ)
  open Category C
  open Functor F

  Pb : Pullback F Underlying
  Pb = record
    { P = Elements F
    ; p₁ = record
     { F₀ = proj₁
     ; F₁ = proj₁
     ; identity = Equiv.refl
     ; homomorphism = Equiv.refl
     ; F-resp-≈ = idf
     }
    ; p₂ = record
     { F₀ = map F₀ idf
     ; F₁ = map F₁ idf
     ; identity = λ x → identity {_} {x}
     ; homomorphism = λ _ → homomorphism
     ; F-resp-≈ = λ f≈g _ → F-resp-≈ f≈g
     }
    ; commute = record
      { F⇒G = record { η = λ _ → idf ; commute = λ _ → refl }
      ; F⇐G = record { η = λ _ → idf ; commute = λ _ → refl }
      ; iso = λ X → record { isoˡ = refl ; isoʳ = refl }
      }
    ; universal = λ {A} {h₁} {h₂} NI → record
      { F₀ = λ X → Functor.F₀ h₁ X , η (F⇐G NI) X (proj₂ $ Functor.F₀ h₂ X)
      ; F₁ = λ f → Functor.F₁ h₁ f ,
        trans (sym $ commute (F⇐G NI) f) (cong (η (F⇐G NI) _) (proj₂ $ Functor.F₁ h₂ f))
      ; identity = Functor.identity h₁
      ; homomorphism = Functor.homomorphism h₁
      ; F-resp-≈ = Functor.F-resp-≈ h₁
      }
    ; unique = λ πˡ∘i≃h₁ map∘i≃h₂ → record
      { F⇒G = record { η = λ X → {!!} ; commute = {!!} }
      ; F⇐G = record { η = {!!} ; commute = {!!} }
      ; iso = λ X → record { isoˡ = {!!} ; isoʳ = {!!} }
      }
    ; p₁∘universal≈h₁ = {!!}
    ; p₂∘universal≈h₂ = {!!}
    }
    where
    open NaturalTransformation
    open NaturalIsomorphism
-}


module Pi.Interp where
open import Data.Unit
open import Data.Product
open import Data.Sum
open import Pi.Syntax
open import Pi.Opsem

-- Big-step intepreter
interp : {A B : 𝕌} → (A ↔ B) → ⟦ A ⟧ → ⟦ B ⟧
interp unite₊l (inj₂ v)         = v
interp uniti₊l v                = inj₂ v
interp swap₊   (inj₁ v)         = inj₂ v
interp swap₊   (inj₂ v)         = inj₁ v
interp assocl₊ (inj₁ v)         = inj₁ (inj₁ v)
interp assocl₊ (inj₂ (inj₁ v))  = inj₁ (inj₂ v)
interp assocl₊ (inj₂ (inj₂ v))  = inj₂ v
interp assocr₊ (inj₁ (inj₁ v))  = inj₁ v
interp assocr₊ (inj₁ (inj₂ v))  = inj₂ (inj₁ v)
interp assocr₊ (inj₂ v)         = inj₂ (inj₂ v)
interp unite⋆l (tt , v)         = v
interp uniti⋆l v                = (tt , v)
interp swap⋆   (v₁ , v₂)        = (v₂ , v₁)
interp assocl⋆ (v₁ , (v₂ , v₃)) = ((v₁ , v₂) , v₃)
interp assocr⋆ ((v₁ , v₂) , v₃) = (v₁ , (v₂ , v₃))
interp dist    (inj₁ v₁ , v₃)   = inj₁ (v₁ , v₃)
interp dist    (inj₂ v₂ , v₃)   = inj₂ (v₂ , v₃)
interp factor  (inj₁ (v₁ , v₃)) = (inj₁ v₁ , v₃)
interp factor  (inj₂ (v₂ , v₃)) = (inj₂ v₂ , v₃)
interp id↔     v                = v
interp (c₁ ⨾ c₂) v = interp c₂ (interp c₁ v)
interp (c₁ ⊕ c₂) (inj₁ v) = inj₁ (interp c₁ v)
interp (c₁ ⊕ c₂) (inj₂ v) = inj₂ (interp c₂ v)
interp (c₁ ⊗ c₂) (v₁ , v₂) = (interp c₁ v₁ , interp c₂ v₂)


{-# OPTIONS --without-K --safe #-}
module Cats.Category.Sets.Facts.Product where

open import Data.Bool using (Bool ; true ; false)
open import Data.Product using (_×_ ; _,_ ; proj₁ ; proj₂)
open import Relation.Binary.PropositionalEquality using (_≡_ ; refl ; cong₂)

open import Cats.Category
open import Cats.Category.Sets using (Sets)

import Cats.Category.Sets.Facts.Terminal

instance
  hasBinaryProducts : ∀ {l} → HasBinaryProducts (Sets l)
  hasBinaryProducts .HasBinaryProducts._×′_ A B = record
    { prod = A × B
    ; proj = λ where
        true → proj₁
        false → proj₂
    ; isProduct = λ p → record
        { arr = λ x → p true x , p false x
        ; prop = λ where
            true → λ _ → refl
            false → λ _ → refl
        ; unique = λ eq x → cong₂ _,_ (eq true x) (eq false x)
        }
    }

  hasFiniteProducts :  ∀ {l} → HasFiniteProducts (Sets l)
  hasFiniteProducts = record {}