-- Axiomatic embedding of guarded recursion in Agda
module guarded-recursion.embedding where

open import guarded-recursion.prelude
  renaming (O to zero; S to suc)
open Coe

module M
    (▹_ : ∀ {a} → Type_ a → Type_ a)

    (▸ : ∀ {a} → ▹ (Type_ a) → Type_ a)

    (next : ∀ {a} {A : Type_ a} → A → ▹ A)

    (▸-rule : ∀ {a} {A : Type_ a} → ▸ (next A) ≡ ▹ A)

    (fix      : ∀ {a} {A : Type_ a} → (▹ A → A) → A)
    (fix-rule : ∀ {a} {A : Type_ a} {f : ▹ A → A} → fix f ≡ f (next (fix f)))

    (_⊛′_ : ∀ {a b} {A : Type_ a} {B : Type_ b} → ▹ (A → B) → ▹ A → ▹ B)
    (_⊛_ : ∀ {a b} {A : Type_ a} {B : A → Type_ b}
           → ▹ ((x : A) → B x) → (x : ▹ A) → ▸ (next B ⊛′ x))

    (fix-uniq : ∀ {a} {A : Type_ a} (u : A) f → u ≡ f (next u) → u ≡ fix f)

    (next⊛next : ∀ {a b} {A : Type_ a} {B : Type_ b} (f : A → B) (x : A)
                 → next f ⊛′ next x ≡ next (f x))

    where

    roll▸ : ∀ {a} {A : Type_ a} → ▹ A → ▸ (next A)
    roll▸ = coe! ▸-rule

    un▸ : ∀ {a} {A : Type_ a} → ▸ (next A) → ▹ A
    un▸ = coe ▸-rule

    ▹Fix : ∀ {a} → Type_ a → Type_ a
    ▹Fix X = (▹ X → X) → X

    ▹Endo : ∀ {a} → Type_ a → Type_ a
    ▹Endo X = ▹ X → X

    μ : ∀ {a} → Fix (Type_ a)
    μ F = fix (F ∘ ▸)

    un : ∀ {a f} → fix {A = Type_ a} f → f (next (fix f))
    un = coe fix-rule

    unμ : ∀ {a} f → μ {a} f → f (▹ μ f)
    unμ {a} f x rewrite ! (▸-rule {A = μ f}) = un x

    roll : ∀ {a f} → f (next (fix f)) → fix {A = Type_ a} f
    roll = coe! fix-rule

    μ-rule : ∀ {a} f → μ {a} f ≡ f (▹ μ f)
    μ-rule f = fix-rule ∙ ap f (▸-rule {A = μ f})

    rollμ : ∀ {a} f → f (▹ μ f) → μ {a} f
    rollμ f = coe! (μ-rule f)

    un₁ : ∀ {a b} {A : Type_ a} {f x} → fix {A = A → Type_ b} f x → f (next (fix f)) x
    un₁ = coe₁ fix-rule

    roll₁ : ∀ {a b} {A : Type_ a} {f x} → f (next (fix f)) x → fix {A = A → Type_ b} f x
    roll₁ = coe₁! fix-rule

    un₂ : ∀ {a b} {A : Type_ a} {B : Type_ b} {c f x y} → fix {A = A → B → Type_ c} f x y → f (next (fix f)) x y
    un₂ = coe₂ fix-rule

    roll₂ : ∀ {a b} {A : Type_ a} {B : Type_ b} {c f x y} → f (next (fix f)) x y → fix {A = A → B → Type_ c} f x y
    roll₂ = coe₂! fix-rule

    map▹ : ∀ {a b} {A : Type_ a} {B : Type_ b} → (A → B) → ▹ A → ▹ B
    map▹ f ▹x = next f ⊛′ ▹x

    {-
    alternatively
    _⊛′′_ : ∀ {a b} {A : Type_ a} {B : A → Type_ b} → ▹ ((x : A) → B x) → (x : A) → ▹ (B x)
    ▹f ⊛′′ x = map▹ (λ f → f x) ▹f
    -}

    {-
    alternatively
    _$_ : ∀ {a b} {A : Type_ a} (B : A → Type_ b) → ▹ A → ▹ (Type_ b)
    f $ ▹x = map▹ f ▹x
    -}

    ▹^ : ∀ {a} → ℕ → Type_ a → Type_ a
    ▹^ zero    A = A
    ▹^ (suc n) A = ▹ ▹^ n A

    next^ : ∀ {a} {A : Type_ a} n → A → ▹^ n A
    next^ zero    x = x
    next^ (suc n) x = next (next^ n x)

    map▹^ : ∀ {a b} {A : Type_ a} {B : Type_ b} n → (A → B) → ▹^ n A → ▹^ n B
    map▹^ zero    f = f
    map▹^ (suc n) f = map▹ (map▹^ n f)

    module SimpleStream where
      F : Type → Type → Type
      F A X = A × X

      S : Type → Type
      S A = μ (F A)

    μ₁F' : ∀ {a} {A : Type_ a} → ((A → ▹ Type) → A → Type) → (▹(A → Type) → A → Type)
    μ₁F' F self = F (λ x → (self ⊛′ next x))

    μ₁F : ∀ {a} {A : Type_ a} → ((A → Type) → A → Type) → (▹(A → Type) → A → Type)
    μ₁F F self = F (λ x → ▸ (self ⊛′ next x))

    μ₁ : ∀ {a} {A : Type_ a} → ((A → Type) → A → Type) → A → Type
    μ₁ F = fix (μ₁F F)

    module μId where
        μid : Type
        μid = μ id

        μid-rule : μid ≡ ▹ μid
        μid-rule = fix-rule ∙ ▸-rule {A = μ id}

        ω : μid
        ω = fix (rollμ id)

    module CoNat where
        Coℕ : Type
        Coℕ = μ Maybe

        rollNat : Maybe (▹ Coℕ) → Coℕ
        rollNat = rollμ Maybe

        ze : Coℕ
        ze = rollNat nothing

        su : ▹ Coℕ → Coℕ
        su x = rollNat (just x)

        su′ : Coℕ → Coℕ
        su′ = su ∘ next

        ω : Coℕ
        ω = fix su

    module Neg where
        {- data X : Type where
             rollX : Fix X
                   : (X → X) → X
           -}
        X : Type
        X = μ Endo

        rollX : Endo (▹ X) → X
           -- : (▹ X → ▹ X) → X
        rollX = rollμ Endo

        rollX′ : ▹(Endo X) → X
            -- : ▹(X → X) → X
        rollX′ = rollX ∘ _⊛′_

        unX : X → Endo (▹ X)
        unX = unμ Endo

        -- δ = λ x → x x
        δ : X → ▹ X
        δ = λ x → (unX x) (next x)

    module Neg' where
        {- data X : Type where
             c : Fix X
               : ((X → X) → X) → X
           -}
        X : Type
        X = μ Fix

        rollX : Fix (▹ X) → X
        rollX = rollμ Fix

        unX : X → Fix (▹ X)
        unX = unμ Fix

    module μ₁Id where
        -- μ₁id = ▹∘▹∘…∘▹
        -- μ₁id A = ▹ (▹ … (▹ A))
        μ₁id : Type → Type
        μ₁id = μ₁ id

        betterfix₁ : ∀ {a} {A : Type_ a} {x : A} (F : Endo (A → Type)) → (▹ μ₁ F x → μ₁F F (next (μ₁ F)) x) → μ₁ F x
        betterfix₁ {a} {A} {x} F f = fix helper
          where helper : _ → _
                helper self = roll₁ (f self)

        ▹ω-inh' : ∀ {A : Type} {x : A} (F : Endo (A → Type)) → (▸ (next (μ₁ F) ⊛′ next x) → μ₁F F (next (μ₁ F)) x) → μ₁ F x
        ▹ω-inh' {A} {x} F f = fix helper
          where helper : _ → _
                helper self = roll₁ (f (coe! (ap ▸ (next⊛next (μ₁ F) x)) (roll▸ self)))

        ▹ω-inh : ∀ {A} → μ₁id A
        -- ▹ω-inh {A} = fix λ self → roll₁ (coe! (ap ▸ (next⊛next μ₁id A)) (roll▸ self))
        ▹ω-inh {A} = betterfix₁ id (λ self → coe! (ap ▸ (next⊛next μ₁id A)) (roll▸ self))

        -- ▹ω-inh {A} = fix λ self → {!!} -- (coe! (ap ▸ (next⊛next μ₁idω A)) (roll▸ self))

    fix2 : ∀ {a} {A : Type_ a} → (▹ A → A) → A
    fix2 f = fix (f ∘ next ∘ f)

    fix≡fix2 : ∀ {a} {A : Type_ a} (f : ▹ A → A) → fix f ≡ fix2 f
    fix≡fix2 f = fix-uniq (fix f) (f ∘ next ∘ f) (fix-rule ∙ ap (f ∘ next) fix-rule)

    module Streams where
        F : Type → Type → Type
        F A X = A × X

        -- S : Type → Type
        -- S A = μ (F A)

        F^ : ℕ → Type → Type → Type
        F^ n A X = A × ▹^ n X

        S^ : ℕ → Type → Type
        S^ n A = μ (F^ n A)

        S : Type → Type
        S = S^ 0

        S₂ = S^ 1

        unS : ∀ {A} → S A → F A (▹ S A)
        unS = unμ (F _)

        rollS : ∀ {A} → F A (▹ S A) → S A
        rollS = rollμ (F _)

        unS^ : ∀ {A} n → S^ n A → F^ n A (▹ S^ n A)
        unS^ n = unμ (F^ n _)

        rollS^ : ∀ {A} n → F^ n A (▹ S^ n A) → S^ n A
        rollS^ n = rollμ (F^ n _)

        hd : ∀ {A} → S A → A
        hd = fst ∘ unS

        tl : ∀ {A} → S A → ▹ S A
        tl = snd ∘ unS

        cons : ∀ {A} n → A → ▹^ n (▹ (S^ n A)) → S^ n A
        cons n x xs = rollS^ n (x , xs)

        infixr 4 _∷_
        _∷_ : ∀ {A} → A → ▹ (S A) → S A
        _∷_ = cons 0

        infixr 4 _∷₂_
        _∷₂_ : ∀ {A} → A → ▹^ 2 (S₂ A) → S₂ A
        x ∷₂ xs = roll (x , map▹ roll▸ xs)

        repeatS : ∀ {A} → A → S A
        repeatS x = fix λ x… → x ∷ x…

        module MapS {A B : Type} (f : A → B) where
            mapSf : ▹(S A → S B) → S A → S B
            mapSf self s = f (hd s) ∷ self ⊛′ tl s

            mapS : S A → S B
            mapS = fix mapSf

            mapS2f : ▹(S A → S B) → S A → S B
            mapS2f self s = f (hd s) ∷ map▹ (λ s' → f (hd s') ∷ self ⊛′ tl s') (tl s)

            mapS2f' : ▹(S A → S B) → S A → S B
            mapS2f' self = mapSf (next (mapSf self))

            mapS2f≡mapS2f' : mapS2f ≡ mapS2f'
            mapS2f≡mapS2f' = idp

            mapS2 : S A → S B
            mapS2 = fix mapS2f

            mapS2' : S A → S B
            mapS2' = fix mapS2f'

            mapS2≡mapS2' : mapS2 ≡ mapS2'
            mapS2≡mapS2' = idp

            mapS2'' : S A → S B
            mapS2'' = fix2 mapSf

            mapS2≡mapS2'' : mapS2 ≡ mapS2''
            mapS2≡mapS2'' = idp

            mapS≡mapS2 : mapS ≡ mapS2
            mapS≡mapS2 = fix≡fix2 mapSf

        open MapS

        group2 : S ℕ → ▹ S₂ ℕ²
        group2 = fix λ self s → map▹ (λ tls → (hd s , hd tls) ∷₂ self ⊛′ tl tls) (tl s)

        ‼ : ∀ {A} → (n : ℕ) → S A → ▹^ n A
        ‼ zero    = hd
        ‼ (suc n) = map▹ (‼ n) ∘ tl

        toFun : ∀ {A} → S A → (n : ℕ) → ▹^ n A
        toFun s n = ‼ n s

        fromFun : ∀ {A} → (ℕ → A) → S A
        fromFun {A} = fix λ self (f : ℕ → A) → f 0 ∷ self ⊛′ next (f ∘ suc)

        nats : S ℕ
        nats = fix λ self → 0 ∷ map▹ (mapS suc) self

        nats2 : S ℕ
        nats2 = fix λ self → 0 ∷ map▹ (mapS2 suc) self

        nats≡nats2 : nats ≡ nats2
        nats≡nats2 rewrite mapS≡mapS2 suc = idp

        arrow : ▹ ℕ
        arrow = ‼ 1 nats

        module Sim
            {A : Type}
            (ℛ : A → A → Type)
            (ℛ-refl : Reflexive ℛ)
          where
            ≈F : ▹(S A × S A → Type) → S A × S A → Type
            ≈F X (xs , ys) = ℛ (hd xs) (hd ys) × ▸ ((map▹ curry X ⊛′ (tl xs)) ⊛′ tl ys)

            _≈_ : S A × S A → Type
            _≈_ = fix ≈F

            ≈-tail : ∀ {xs ys : S A} → _≈_ (xs , ys) → ▸ ((map▹ curry (next _≈_) ⊛′ tl xs) ⊛′ tl ys)
            ≈-tail pf = snd (un₁ pf)

            {- Does not work yet
            ≈-refl : Reflexive (curry _≈_)
            ≈-refl {x} = (fix λ pf x → roll₁ {f = ≈F} (ℛ-refl , helper pf x)) x
              where helper' : _ → _ → _
                    helper' pf x = map▹ (λ f → f x) pf
                    helper : _ → _ → _
                    helper pf x = let r = helper' pf x in {!roll▸ r!}
            -}

    module DelayedStreams where
        data F (A : Type) (X : Type) : Type where
          done  : F A X
          skip  : X → F A X
          yield : A → X → F A X

        mapF : ∀ {A B X Y} → (A → B) → (X → Y) → F A X → F B Y
        mapF f g done = done
        mapF f g (skip x) = skip (g x)
        mapF f g (yield a x) = yield (f a) (g x)

        S : Type → Type
        S A = μ (F A)

        unS : ∀ {A} → S A → F A (▹ S A)
        unS = mapF id un▸ ∘ un

        rollS : ∀ {A} → F A (▹ S A) → S A
        rollS = roll ∘ mapF id roll▸

        unfoldS : ∀ {A X} → (X → F A (▹ X)) → X → S A
        unfoldS coalg = fix λ self x → rollS (mapF id (λ x′ → self ⊛′ x′) (coalg x))

        repeatS : ∀ {A} → A → S A
        repeatS x = fix λ self → rollS (yield x self)

        neverS : ∀ {A} → S A
        neverS = fix λ self → rollS (skip self)

        -- Co-algebra style...
        mapS : ∀ {A B} → (A → B) → S A → S B
        mapS {A} {B} f = unfoldS (mapF f id ∘ unS)

        filterF : ∀ {A X} → (A → 𝟚) → F A X → F A X
        filterF f done         = done
        filterF f (skip xs)    = skip xs
        filterF f (yield x xs) = if f x then yield x xs
                                        else skip xs

        filterS : ∀ {A} → (A → 𝟚) → S A → S A
        filterS f = unfoldS (filterF f ∘ unS)

module FuelBased where
    fix : ∀ {a} {A : Type_ a} → ℕ → (A → A) → A
    fix zero    f = STUCK where postulate STUCK : _
    fix (suc n) f = f (fix n f)

    fix-rule : ∀ {a} {A : Type_ a} (n : ℕ) {f : A → A} → fix n f ≡ f (fix n f)
    fix-rule zero        = ThisIsUnsafeButPlease.trustMe
    fix-rule (suc n) {f} = ap f (fix-rule n)

    fix-uniq : ∀ {a} {A : Type_ a} (n : ℕ) (u : A) f → u ≡ f u → u ≡ fix n f
    fix-uniq zero    u f pf = ThisIsUnsafeButPlease.trustMe
    fix-uniq (suc n) u f pf = pf ∙ ap f (fix-uniq n u f pf)

    module I (n : ℕ) = M id id id idp (fix n) (fix-rule n) id id
                         (fix-uniq n) (λ _ _ → idp)

module HiddenFix {a} {A : Type_ a} (f : A → A) where
    -- This definition is not intended to termination-check.
    -- Use with care it's really easy to make the type-checker loop.
    {-# TERMINATING #-}
    fix : Hidden A
    fix = hide f (reveal fix)

    fix-rule : reveal fix ≡ f (reveal fix)
    fix-rule = idp {a} {A} {reveal fix}

    -- This definition is not intended to termination-check.
    -- Use with care it's really easy to make the type-checker loop.
    {-# TERMINATING #-}
    fix-uniq : (u : A) → u ≡ f u → u ≡ reveal fix
    fix-uniq u pf = pf ∙ ap f (fix-uniq u pf) ∙ ! fix-rule

module Test where
  open HiddenFix
  open M id id id idp (reveal ∘ fix) (λ {_} {_} {f} → fix-rule f) id id
                 (λ {_} {_} u f → fix-uniq f u) (λ _ _ → idp) public
  open Streams
  two : map▹ hd (tl nats) ≡ 1
  two = idp

-- -}
-- -}
-- -}


open import OutsideIn.Prelude
open import OutsideIn.X
module OutsideIn.Inference.ConstraintGen(x : X) where
  import OutsideIn.Constraints as C
  import OutsideIn.TypeSchema as TS
  import OutsideIn.Expressions as E
  import OutsideIn.Environments as V
  open X(x) renaming (funType to _⟶_; appType to _··_)
  open C(x)
  open TS(x)
  open E(x)
  open V(x)
  open import Data.Vec

  private module PlusN-m n        = Monad (PlusN-is-monad {n})
          module PlusN-f n        = Functor (Monad.is-functor (PlusN-is-monad {n}))
          module TypeSchema-f {n} = Functor (type-schema-is-functor {n})
          module Type-f           = Functor (type-is-functor)
          module QC-f             = Functor (qconstraint-is-functor)
          module Exp-f₁ {tv} {r}  = Functor (expression-is-functor₁ {tv} {r})
          module Exp-f₂ {ev} {r}  = Functor (expression-is-functor₂ {ev} {r})
          module Constraint-f {s} = Functor (constraint-is-functor {s})
          module Vec-f {n}        = Functor (vec-is-functor {n})
          open Monad (type-is-monad) using () renaming (unit to TVar)

  private

    upindex : {X : Set → Set}{tv : Set} → ⦃ is-functor : Functor X ⦄ → X tv → X (Ⓢ tv)
    upindex ⦃ is-functor ⦄ e = suc <$> e
      where open Functor (is-functor)

    _↑c : {tv : Set}{s : Strata} → (Constraint tv s) → (Constraint (Ⓢ tv) s)
    _↑c {s = s} = upindex ⦃ constraint-is-functor {s} ⦄

    _↑e : {ev tv : Set}{r : Shape} → (Expression ev tv r) → (Expression ev (Ⓢ tv) r)
    _↑e = upindex ⦃ expression-is-functor₂ ⦄

    _↑a : {ev tv : Set}{r : Shape} → (Alternatives ev tv r) → (Alternatives ev (Ⓢ tv) r)
    _↑a = upindex ⦃ alternatives-is-functor₂ ⦄

    _↑t : {tv : Set} → (Type tv) → (Type (Ⓢ tv))
    _↑t = upindex ⦃ type-is-functor ⦄

    _↑q : {tv : Set} → (QConstraint tv) → (QConstraint (Ⓢ tv))
    _↑q = upindex ⦃ qconstraint-is-functor ⦄

    infixr 7 _↑e
    infixr 7 _↑c
    infixr 7 _↑t
    infixr 7 _↑a

    applyAll : ∀{tv}(n : ℕ) → Type tv → Type (tv ⨁ n)
    applyAll zero x = x
    applyAll (suc n) x = applyAll n ((x ↑t) ·· (TVar zero))

    funType : ∀{tv}{n} → Vec (Type tv) n → Type tv → Type tv
    funType [] t = t
    funType (x ∷ xs) t = x ⟶ (funType xs t)

    upType : ∀ {n}{tv} → Type tv → Type (tv ⨁ n)
    upType {n} t = Type-f.map (Monad.unit (PlusN-is-monad {n})) t
    upGamma : ∀ {n}{ev}{tv} → Environment ev tv → Environment ev (tv ⨁ n)
    upGamma {n} Γ = TypeSchema-f.map (Monad.unit (PlusN-is-monad {n})) ∘ Γ
    upExp : ∀ {n}{ev}{tv}{r} → Expression ev tv r → Expression ev (tv ⨁ n) r
    upExp {n} e = Exp-f₂.map (Monad.unit (PlusN-is-monad {n})) e
    upAlts : ∀ {n}{ev}{tv}{r} → Alternatives ev tv r → Alternatives ev (tv ⨁ n) r
    upAlts {n} e = Functor.map alternatives-is-functor₂ (Monad.unit (PlusN-is-monad {n})) e

  mutual

    syntax alternativeConstraintGen Γ α₀ α₁ alt C = Γ ►′ alt ∶ α₀ ⟶ α₁ ↝ C
    data alternativeConstraintGen {ev : Set}{tv : Set}(Γ : Environment ev tv)(α₀ α₁ : Type tv) :
                                   {r : Shape} → Alternative ev tv r → Constraint tv Extended → Set where
      Simple : ∀ {r}{n}{v : Name ev (Datacon n)}{e : Expression _ _ r}{a}{τs}{T}{C}
             → let δ = TVar zero
            in Γ v ≡ DC∀ a · τs ⟶ T
             → addAll (Vec-f.map (_↑t) τs) (upGamma {a} Γ ↑Γ) ► (upExp {a} e ↑e) ∶ δ ↝ C
             → Γ ►′ v →′ e ∶ α₀ ⟶ α₁ ↝ Ⅎ′ a · (Ⅎ δ ∼′ (upType {a} α₁ ↑t) ∧′ C) ∧′ applyAll a (TVar T) ∼′ upType {a} α₀
      GADT : ∀ {r}{n}{v : Name ev (Datacon n)}{e : Expression _ _ r}{a}{b}{Q}{τs}{T}{C}
           → let δ = TVar zero
          in Γ v ≡ DC∀′ a , b · Q ⇒ τs ⟶ T
           →   addAll (Vec-f.map (_↑t) τs) (upGamma {b} (upGamma {a} Γ) ↑Γ)
             ► (upExp {b} (upExp {a} e) ↑e) ∶ δ ↝ C
           → Γ ►′ v →′ e ∶ α₀ ⟶ α₁ ↝ Ⅎ′ a · Ⅎ′ b · (Imp′ Q (Ⅎ (C ∧′ δ ∼′ (upType {b} (upType {a} α₁) ↑t))))
                                                  ∧′ upType {b} (upType {a} α₀) ∼′ Type-f.map (PlusN-m.unit b) (applyAll a (TVar T))

    syntax alternativesConstraintGen Γ α₀ α₁ alts C = Γ ►► alts ∶ α₀ ⟶ α₁ ↝ C
    data alternativesConstraintGen {ev : Set}{tv : Set}(Γ : Environment ev tv)(α₀ α₁ : Type tv) :
                                   {r : Shape} → Alternatives ev tv r → Constraint tv Extended → Set where
      NoAlternative : Γ ►► esac ∶ α₀ ⟶ α₁ ↝ ε′
      AnAlternative : ∀ {r₁ r₂}{a : Alternative _ _ r₁}{as : Alternatives _ _ r₂}{C₁}{C₂}
                    → Γ ►′ a      ∶ α₀ ⟶ α₁ ↝ C₁
                    → Γ ►► as     ∶ α₀ ⟶ α₁ ↝ C₂
                    → Γ ►► a ∣ as ∶ α₀ ⟶ α₁ ↝ C₂

    syntax constraintGen a c b d = a ► b ∶ c ↝ d
    data constraintGen {ev : Set}{tv : Set}
                       (Γ : Environment ev tv)(τ : Type tv) : {r : Shape} →
                       Expression ev tv r → Constraint tv Extended → Set where
      VarCon₁ : ∀ {v}{n}{q}{t}
              → Γ (N v) ≡ ∀′ n · q ⇒ t
              → Γ ► Var (N v) ∶ τ ↝ Ⅎ′ n · QC q ∧′ upType {n} τ ∼′ t
      VarCon₂ : ∀ {n}{d}{a}{τs : Vec _ n}{k}
              → Γ (DC d) ≡ DC∀ a · τs ⟶ k
              → Γ ► Var (DC d) ∶ τ ↝ Ⅎ′ a · upType {a} τ ∼′ funType τs (applyAll a (TVar k))
      VarCon₃ : ∀ {n}{d}{a}{b}{Q}{τs : Vec _ n}{k}
              → Γ (DC d) ≡ DC∀′ a , b · Q ⇒ τs ⟶ k
              → Γ ► Var (DC d) ∶ τ ↝ Ⅎ′ a · Ⅎ′ b ·
                                       QC Q ∧′ upType {b} (upType {a} τ) ∼′ funType τs (upType {b} (applyAll a (TVar k)))
      App     : ∀ {r₁}{r₂}{e₁ : Expression _ _ r₁}{e₂ : Expression _ _ r₂}{C₁}{C₂}
              → let α₀ = TVar zero
                    α₁ = TVar (suc zero)
                    α₂ = TVar (suc (suc zero))
                 in upGamma {3} Γ ► upExp {3} e₁ ∶ α₀ ↝ C₁
                  → upGamma {3} Γ ► upExp {3} e₂ ∶ α₁ ↝ C₂
                  → Γ ► e₁ · e₂ ∶ τ ↝ Ⅎ Ⅎ Ⅎ C₁ ∧′ C₂ ∧′ α₀ ∼′ (α₁ ⟶ α₂) ∧′ upType {3} τ ∼′ α₂
      Abs :  ∀ {r}{e : Expression _ _ r}{C}
              → let α₀ = TVar zero
                    α₁ = TVar (suc zero)
                 in ⟨ ∀′ 0 · ε ⇒ α₀ ⟩, upGamma {2} Γ ► upExp {2} e ∶ α₁ ↝ C
                  → Γ ► λ′ e ∶ τ ↝ Ⅎ Ⅎ C ∧′ upType {2} τ ∼′ (α₀ ⟶ α₁)
      Let : ∀{r₁}{r₂}{x : Expression _ _ r₁}{y : Expression _ _ r₂}{C₁}{C₂}
          → let α₀ = TVar zero
                α₁ = TVar (suc zero)
             in                    upGamma {2} Γ ► upExp {2} x ∶ α₀ ↝ C₁
              → ⟨ ∀′ 0 · ε ⇒ α₀ ⟩, upGamma {2} Γ ► upExp {2} y ∶ α₁ ↝ C₂
              → Γ ► let₁ x in′ y ∶ τ ↝ Ⅎ Ⅎ C₁ ∧′ C₂ ∧′ upType {2} τ ∼′ α₁
      LetA : ∀{r₁}{r₂}{x : Expression _ _ r₁}{y : Expression _ _ r₂}{t}{C₁}{C₂}
           → let α₀ = TVar zero
                 α₁ = TVar (suc zero)
              in                    upGamma {2} Γ ► upExp {2} x ∶ α₀ ↝ C₁
               → ⟨ ∀′ 0 · ε ⇒ α₀ ⟩, upGamma {2} Γ ► upExp {2} y ∶ α₁ ↝ C₂
               → Γ ► let₂ x ∷ t in′ y ∶ τ ↝ Ⅎ Ⅎ C₁ ∧′ C₂ ∧′ upType {2} τ ∼′ α₁ ∧′ upType {2} t ∼′ α₀
      GLetA : ∀{n}{r₁}{r₂}{x : Expression _ _ r₁}{y : Expression _ _ r₂}{Q}{t}{C}{C₂}
           → let α₀ = upType {n} (TVar zero)
                 α₁ = upType {n} (TVar (suc zero))
                 up2 = PlusN-f.map n (PlusN-m.unit 2)
              in                     upGamma {n} (upGamma {2} Γ) ► Exp-f₂.map up2 x ∶ α₀ ↝ C
               → upGamma {n} (upGamma {2} (⟨ ∀′ n · Q ⇒ t ⟩, Γ)) ► upExp {n} (upExp {2} y) ∶ α₁ ↝ C₂
               → Γ ► let₃ n · x ∷ Q ⇒ t in′ y ∶ τ ↝ Ⅎ Ⅎ Ⅎ′ n · Imp′ (QC-f.map up2 Q)  (C ∧′ α₀ ∼′ Type-f.map up2 t)
                                                            ∧′ C₂ ∧′ upType {n} (upType {2} τ) ∼′ α₁
      Case : ∀{r₁}{r₂}{x : Expression _ _ r₁}{alts : Alternatives _ _ r₂}{C₁}{C₂}
           → let α₀ = TVar zero
                 α₁ = TVar (suc zero)
              in upGamma {2} Γ ► upExp {2} x ∶ α₀ ↝ C₁
               → upGamma {2} Γ ►► upAlts {2} alts ∶ α₀ ⟶ α₁ ↝ C₂
               → Γ ► case x of alts ∶ τ ↝ Ⅎ Ⅎ C₁ ∧′ C₂

  genConstraint : {ev : Set}{tv : Set}{r : Shape}
                  (Γ : Environment ev tv)(e : Expression ev tv r)(τ : Type tv) → ∃ (λ C → Γ ► e ∶ τ ↝ C)
  genConstraintAlternative : {ev : Set}{tv : Set}{r : Shape} (Γ : Environment ev tv)(a : Alternative ev tv r)(α₀ α₁ : Type tv)
                           → ∃ (λ C → Γ ►′ a ∶ α₀ ⟶ α₁ ↝ C)
  genConstraintAlternative Γ (n →′ e) α₀ α₁ with Γ n | inspect Γ n
  ... | DC∀ a · τs ⟶ k | iC p with genConstraint (addAll (Vec-f.map _↑t τs) (upGamma {a} Γ ↑Γ)) (upExp {a} e ↑e) (TVar zero)
  ...                          | C , p₂ = _ , Simple p p₂
  genConstraintAlternative Γ (n →′ e) α₀ α₁
      | DC∀′ a , b · Q ⇒ τs ⟶ k | iC p with genConstraint (addAll (Vec-f.map _↑t τs) (upGamma {b} (upGamma {a} Γ) ↑Γ))
                                                           (upExp {b} (upExp {a} e) ↑e)
                                                           (TVar zero)
  ...                                   | C , p₂ = _ , GADT p p₂
  genConstraintAlternatives : {ev : Set}{tv : Set}{r : Shape} (Γ : Environment ev tv)(a : Alternatives ev tv r)(α₀ α₁ : Type tv)
                            → ∃ (λ C → Γ ►► a ∶ α₀ ⟶ α₁ ↝ C)
  genConstraintAlternatives Γ esac α₀ α₁ = _ , NoAlternative
  genConstraintAlternatives Γ (a ∣ as) α₀ α₁ with genConstraintAlternative Γ a α₀ α₁ | genConstraintAlternatives Γ as α₀ α₁
  ... | C₁ , p₁ | C₂ , p₂ = _ , AnAlternative p₁ p₂
  genConstraint  Γ (Var (N v)) τ with Γ (N v) | inspect Γ (N v)
  ... | ∀′ n · q ⇒ t | iC prf = _ , VarCon₁ prf
  genConstraint  Γ (Var (DC d)) τ with Γ (DC d) | inspect Γ (DC d)
  ... | DC∀  a · τs ⟶ k         | iC prf = _ , VarCon₂ prf
  ... | DC∀′ a , b · q ⇒ τs ⟶ k | iC prf = _ , VarCon₃ prf
  genConstraint Γ (e₁ · e₂) τ with genConstraint (upGamma {3} Γ) (upExp {3} e₁) (TVar zero)
                                 | genConstraint (upGamma {3} Γ) (upExp {3} e₂) (TVar (suc zero))
  ... | C₁ , p₁ | C₂ , p₂ = _ , App p₁ p₂
  genConstraint Γ (λ′ e′) τ with genConstraint (⟨ ∀′ 0 · ε ⇒ TVar zero ⟩, upGamma {2} Γ) (upExp {2} e′) (TVar (suc zero))
  ... | C , p = _ , Abs p
  genConstraint  Γ (let₁ x in′ y) τ with genConstraint (upGamma {2} Γ) (upExp {2} x) (TVar zero)
                                       | genConstraint (⟨ ∀′ 0 · ε ⇒ TVar zero ⟩, upGamma {2} Γ) (upExp {2} y) (TVar (suc zero))
  ... | C₁ , p₁ | C₂ , p₂ = _ , Let p₁ p₂
  genConstraint Γ (let₂ x ∷ t in′ y) τ with genConstraint (upGamma {2} Γ) (upExp {2} x) (TVar zero)
                                       | genConstraint (⟨ ∀′ 0 · ε ⇒ TVar zero ⟩, upGamma {2} Γ) (upExp {2} y) (TVar (suc zero))
  ... | C₁ , p₁ | C₂ , p₂ = _ , LetA p₁ p₂
  genConstraint Γ (let₃ n · x ∷ Q ⇒ t in′ y) τ with genConstraint (upGamma {n} (upGamma {2} Γ))
                                                                  (Exp-f₂.map (PlusN-f.map n (PlusN-m.unit 2)) x)
                                                                  (upType {n} (TVar zero))
                                                  | genConstraint (upGamma {n} (upGamma {2} (⟨ ∀′ n · Q ⇒ t ⟩, Γ)))
                                                                  (upExp {n} (upExp {2} y))
                                                                  (upType {n} (TVar (suc zero)))
  ... | C₁ , p₁ | C₂ , p₂ = _ , GLetA p₁ p₂
  genConstraint Γ (case x of alts) τ with genConstraint (upGamma {2} Γ) (upExp {2} x) (TVar zero)
                                        | genConstraintAlternatives (upGamma {2} Γ) (upAlts {2} alts) (TVar zero) (TVar (suc zero))
  ... | C₁ , p₁ | C₂ , p₂ = _ , Case p₁ p₂


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

open import lib.Basics
open import lib.types.Empty
open import lib.types.Sigma
open import lib.types.Paths

module lib.types.Pi where

instance
  Π-level : ∀ {i j} {A : Type i} {B : A → Type j} {n : ℕ₋₂}
    → ((x : A) → has-level n (B x)) → has-level n (Π A B)
  Π-level {n = ⟨-2⟩} p = has-level-in ((λ x → contr-center (p x)) , lemma)
    where abstract lemma = λ f → λ= (λ x → contr-path (p x) (f x))
  Π-level {n = S n} p = has-level-in lemma where
    abstract
      lemma = λ f g →
        equiv-preserves-level λ=-equiv {{Π-level (λ x → has-level-apply (p x) (f x) (g x))}}

  Πi-level : ∀ {i j} {A : Type i} {B : A → Type j} {n : ℕ₋₂}
    → ((x : A) → has-level n (B x)) → has-level n ({x : A} → B x)
  Πi-level {A = A} {B} p = equiv-preserves-level e {{Π-level p}}  where

    e : Π A B ≃ ({x : A} → B x)
    fst e f {x} = f x
    is-equiv.g (snd e) f x = f
    is-equiv.f-g (snd e) _ = idp
    is-equiv.g-f (snd e) _ = idp
    is-equiv.adj (snd e) _ = idp

{- Equivalences in a Π-type -}
Π-emap-l : ∀ {i j k} {A : Type i} {B : Type j} (P : B → Type k)
            → (e : A ≃ B) → Π A (P ∘ –> e) ≃ Π B P
Π-emap-l {A = A} {B = B} P e = equiv f g f-g g-f where
  f : Π A (P ∘ –> e) → Π B P
  f u b = transport P (<–-inv-r e b) (u (<– e b))

  g : Π B P → Π A (P ∘ –> e)
  g v a = v (–> e a)

  abstract
    f-g : ∀ v → f (g v) == v
    f-g v = λ= λ b → to-transp (apd v (<–-inv-r e b))

    g-f : ∀ u → g (f u) == u
    g-f u = λ= λ a → to-transp $ transport (λ p → u _ == _ [ P ↓ p ])
                                           (<–-inv-adj e a)
                                           (↓-ap-in P (–> e)
                                                      (apd u $ <–-inv-l e a))

Π-emap-r : ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k}
  → (∀ x → B x ≃ C x) → Π A B ≃ Π A C
Π-emap-r {A = A} {B = B} {C = C} k = equiv f g f-g g-f
  where f : Π A B → Π A C
        f c x = –> (k x) (c x)

        g : Π A C → Π A B
        g d x = <– (k x) (d x)

        abstract
          f-g : ∀ d → f (g d) == d
          f-g d = λ= (λ x →  <–-inv-r (k x) (d x))

          g-f : ∀ c → g (f c) == c
          g-f c = λ= (λ x → <–-inv-l (k x) (c x))

{-
favonia: This part is not used.

module _ {i₀ i₁ j₀ j₁} {A₀ : Type i₀} {A₁ : Type i₁}
         {B₀ : A₀ → Type j₀} {B₁ : A₁ → Type j₁} where
  Π-emap : (u : A₀ ≃ A₁) (v : ∀ a → B₀ (<– u a) ≃ B₁ a) → Π A₀ B₀ ≃ Π A₁ B₁
  Π-emap u v = Π A₀ B₀           ≃⟨ Π-emap-l _ (u ⁻¹) ⁻¹ ⟩
               Π A₁ (B₀ ∘ <– u)  ≃⟨ Π-emap-r v ⟩
               Π A₁ B₁           ≃∎

  Π-emap' : (u : A₀ ≃ A₁) (v : ∀ a → B₀ a ≃ B₁ (–> u a)) → Π A₀ B₀ ≃ Π A₁ B₁
  Π-emap' u v = Π A₀ B₀           ≃⟨ Π-emap-r v ⟩
                Π A₀ (B₁ ∘ –> u)  ≃⟨ Π-emap-l _ u ⟩
                Π A₁ B₁           ≃∎
-}

{- Coversions between functions with implicit and explicit arguments -}

expose-equiv : ∀ {i j} {A : Type i} {B : A → Type j}
  → ({x : A} → B x) ≃ ((x : A) → B x)
expose-equiv = (λ f a → f {a}) , is-eq
  _
  (λ f {a} → f a)
  (λ _ → idp)
  (λ _ → idp)

{- Dependent paths in a Π-type -}
module _ {i j k} {A : Type i} {B : A → Type j} {C : (a : A) → B a → Type k}
  where

  ↓-Π-in : {x x' : A} {p : x == x'} {u : Π (B x) (C x)} {u' : Π (B x') (C x')}
    → ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
        → u t == u' t' [ uncurry C ↓ pair= p q ])
    → (u == u' [ (λ x → Π (B x) (C x)) ↓ p ])
  ↓-Π-in {p = idp} f = λ= (λ x → f (idp {a = x}))

  ↓-Π-out : {x x' : A} {p : x == x'} {u : Π (B x) (C x)} {u' : Π (B x') (C x')}
    → (u == u' [ (λ x → Π (B x) (C x)) ↓ p ])
    → ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
        → u t == u' t' [ uncurry C ↓ pair= p q ])
  ↓-Π-out {p = idp} q idp = app= q _

  ↓-Π-β : {x x' : A} {p : x == x'} {u : Π (B x) (C x)} {u' : Π (B x') (C x')}
    → (f : {t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
            → u t == u' t' [ uncurry C ↓ pair= p q ])
    → {t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
    → ↓-Π-out (↓-Π-in f) q == f q
  ↓-Π-β {p = idp} f idp = app=-β (λ x → f (idp {a = x})) _

  ↓-Π-η : {x x' : A} {p : x == x'} {u : Π (B x) (C x)} {u' : Π (B x') (C x')}
    → (q : (u == u' [ (λ x → Π (B x) (C x)) ↓ p ]))
    → ↓-Π-in (↓-Π-out q) == q
  ↓-Π-η {p = idp} q = ! (λ=-η q)

  ↓-Π-equiv : {x x' : A} {p : x == x'} {u : Π (B x) (C x)} {u' : Π (B x') (C x')}
    → ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
        → u t == u' t' [ uncurry C ↓ pair= p q ])
    ≃ (u == u' [ (λ x → Π (B x) (C x)) ↓ p ])
  ↓-Π-equiv {p = idp} = equiv ↓-Π-in ↓-Π-out ↓-Π-η
    (λ u → <– (ap-equiv expose-equiv _ _)
      (λ= (λ t → <– (ap-equiv expose-equiv _ _)
        (λ= (λ t' → λ= (↓-Π-β u))))))

{- Dependent paths in a Π-type where the codomain is not dependent on anything

Right now, this is defined in terms of the previous one. Maybe it’s a good idea,
maybe not.
-}
module _ {i j k} {A : Type i} {B : A → Type j} {C : Type k} {x x' : A}
  {p : x == x'} {u : B x → C} {u' : B x' → C} where

  ↓-app→cst-in :
    ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
      → u t == u' t')
    → (u == u' [ (λ x → B x → C) ↓ p ])
  ↓-app→cst-in f = ↓-Π-in (λ q → ↓-cst-in (f q))

  ↓-app→cst-out :
    (u == u' [ (λ x → B x → C) ↓ p ])
    → ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
        → u t == u' t')
  ↓-app→cst-out r q = ↓-cst-out (↓-Π-out r q)

  ↓-app→cst-β :
    (f : ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
           → u t == u' t'))
    → {t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
    → ↓-app→cst-out (↓-app→cst-in f) q == f q
  ↓-app→cst-β f q =
    ↓-app→cst-out (↓-app→cst-in f) q
             =⟨ idp ⟩
    ↓-cst-out (↓-Π-out (↓-Π-in (λ qq → ↓-cst-in (f qq))) q)
             =⟨ ↓-Π-β (λ qq → ↓-cst-in (f qq)) q |in-ctx
                      ↓-cst-out ⟩
    ↓-cst-out (↓-cst-in {p = pair= p q} (f q))
             =⟨ ↓-cst-β (pair= p q) (f q) ⟩
    f q =∎

{- favonia: these lemmas are not used anywhere

{- Similar to above, with domain being the identity function. -}
{- These lemmas were in homotopy.FunctionOver and in different conventions. -}

module _ {i j} {A B : Type i} {C : Type j}
  {u : A → C} {v : B → C} where

  ↓-idf→cst-in : ∀ (p : A == B)
    → u == v ∘ coe p
    → u == v [ (λ x → x → C) ↓ p ]
  ↓-idf→cst-in idp q = q

  ↓-idf→cst-ua-in : ∀ (e : A ≃ B)
    → u == v ∘ –> e
    → u == v [ (λ x → x → C) ↓ ua e ]
  ↓-idf→cst-ua-in e q = ↓-idf→cst-in (ua e) (q ∙ ap (v ∘_) (λ= λ a → ! (coe-β e a)))

  ↓-idf→cst-in' : ∀ (p : A == B)
    → u ∘ coe! p == v
    → u == v [ (λ x → x → C) ↓ p ]
  ↓-idf→cst-in' idp q = q

  ↓-idf→cst-ua-in' : ∀ (e : A ≃ B)
    → u ∘ <– e == v
    → u == v [ (λ x → x → C) ↓ ua e ]
  ↓-idf→cst-ua-in' e q = ↓-idf→cst-in' (ua e) (ap (u ∘_) (λ= λ a → coe!-β e a) ∙ q)

module _ {i j} {A B : Type i} {C : Type j}
  {u : C → A} {v : C → B} where

  ↓-cst→idf-in : ∀ (p : A == B)
    → coe p ∘ u == v
    → u == v [ (λ x → C → x) ↓ p ]
  ↓-cst→idf-in idp q = q

  ↓-cst→idf-ua-in : ∀ (e : A ≃ B)
    → –> e ∘ u == v
    → u == v [ (λ x → C → x) ↓ ua e ]
  ↓-cst→idf-ua-in e q = ↓-cst→idf-in (ua e) (ap (_∘ u) (λ= λ a → coe-β e a) ∙ q)

  ↓-cst→idf-in' : ∀ (p : A == B)
    → u == coe! p ∘ v
    → u == v [ (λ x → C → x) ↓ p ]
  ↓-cst→idf-in' idp q = q

  ↓-cst→idf-ua-in' : ∀ (e : A ≃ B)
    → u == <– e ∘ v
    → u == v [ (λ x → C → x) ↓ ua e ]
  ↓-cst→idf-ua-in' e q = ↓-cst→idf-in' (ua e) (q ∙ ap (_∘ v) (λ= λ a → ! (coe!-β e a)))
-}

{- Dependent paths in an arrow type -}
module _ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k}
  {x x' : A} {p : x == x'} {u : B x → C x} {u' : B x' → C x'} where

  ↓-→-in :
    ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
      → u t == u' t' [ C ↓ p ])
    → (u == u' [ (λ x → B x → C x) ↓ p ])
  ↓-→-in f = ↓-Π-in (λ q → ↓-cst2-in p q (f q))

  ↓-→-out :
    (u == u' [ (λ x → B x → C x) ↓ p ])
    → ({t : B x} {t' : B x'} (q : t == t' [ B ↓ p ])
      → u t == u' t' [ C ↓ p ])
  ↓-→-out r q = ↓-cst2-out p q (↓-Π-out r q)

{- Transport form of dependent path in an arrow type -}
module _ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k} where

  ↓-→-from-transp : {x x' : A} {p : x == x'}
    {u : B x → C x} {u' : B x' → C x'}
    → transport C p ∘ u == u' ∘ transport B p
    → u == u' [ (λ x → B x → C x) ↓ p ]
  ↓-→-from-transp {p = idp} q = q

  ↓-→-to-transp : {x x' : A} {p : x == x'}
    {u : B x → C x} {u' : B x' → C x'}
    → u == u' [ (λ x → B x → C x) ↓ p ]
    → transport C p ∘ u == u' ∘ transport B p
  ↓-→-to-transp {p = idp} q = q

-- Dependent paths in a Π-type where the domain is constant
module _ {i j k} {A : Type i} {B : Type j} {C : A → B → Type k} where

  ↓-Π-cst-app-in : {x x' : A} {p : x == x'}
    {u : (b : B) → C x b} {u' : (b : B) → C x' b}
    → ((b : B) → u b == u' b [ (λ x → C x b) ↓ p ])
    → (u == u' [ (λ x → (b : B) → C x b) ↓ p ])
  ↓-Π-cst-app-in {p = idp} f = λ= f

  ↓-Π-cst-app-out : {x x' : A} {p : x == x'}
    {u : (b : B) → C x b} {u' : (b : B) → C x' b}
    → (u == u' [ (λ x → (b : B) → C x b) ↓ p ])
    → ((b : B) → u b == u' b [ (λ x → C x b) ↓ p ])
  ↓-Π-cst-app-out {p = idp} q = app= q

split-ap2 : ∀ {i j k} {A : Type i} {B : A → Type j} {C : Type k} (f : Σ A B → C)
  {x y : A} (p : x == y)
  {u : B x} {v : B y} (q : u == v [ B ↓ p ])
  → ap f (pair= p q) == ↓-app→cst-out (apd (curry f) p) q
split-ap2 f idp idp = idp

{-
Interaction of [apd] with function composition.
The basic idea is that [apd (g ∘ f) p == apd g (apd f p)] but the version here
is well-typed. Note that we assume a propositional equality [r] between
[apd f p] and [q].
-}
apd-∘ : ∀ {i j k} {A : Type i} {B : A → Type j} {C : (a : A) → B a → Type k}
  (g : {a : A} → Π (B a) (C a)) (f : Π A B) {x y : A} (p : x == y)
  {q : f x == f y [ B ↓ p ]} (r : apd f p == q)
  → apd (g ∘ f) p == ↓-apd-out C r (apd↓ g q)
apd-∘ g f idp idp = idp

{- When [g] is nondependent, it’s much simpler -}
apd-∘' : ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k}
  (g : {a : A} → B a → C a) (f : Π A B) {x y : A} (p : x == y)
  → apd (g ∘ f) p == ap↓ g (apd f p)
apd-∘' g f idp = idp

∘'-apd : ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k}
  (g : {a : A} → B a → C a) (f : Π A B) {x y : A} (p : x == y)
  → ap↓ g (apd f p) == apd (g ∘ f) p
∘'-apd g f idp = idp

{- And when [f] is nondependent, it’s also a bit simpler -}
apd-∘'' : ∀ {i j k} {A : Type i} {B : Type j} {C : (b : B) → Type k}
  (g : Π B C) (f : A → B) {x y : A} (p : x == y)
  {q : f x == f y} (r : ap f p == q)
  → apd (g ∘ f) p == ↓-ap-out= C f p r (apd g q) --(apd↓ g q)
apd-∘'' g f idp idp = idp

{- 2-dimensional coherence conditions -}

-- lhs :
--   ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k} {f g : Π A B}
--   {x y : A} {p : x == y} {u : f x == g x} {v : f y == g y}
--   (k : (u ◃ apd g p) == (apd f p ▹ v))
--   (h : {a : A} → B a → C a)
--   → ap h u ◃ apd (h ∘ g) p == ap↓ h (u ◃ apd g p)

-- rhs :
--   ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k} {f g : Π A B}
--   {x y : A} {p : x == y} {u : f x == g x} {v : f y == g y}
--   (k : (u ◃ apd g p) == (apd f p ▹ v))
--   (h : {a : A} → B a → C a)
--   → ap↓ h (apd f p ▹ v) == apd (h ∘ f) p ▹ ap h v

-- ap↓-↓-=-in :
--   ∀ {i j k} {A : Type i} {B : A → Type j} {C : A → Type k} {f g : Π A B}
--   {x y : A} {p : x == y} {u : f x == g x} {v : f y == g y}
--   (k : (u ◃ apd g p) == (apd f p ▹ v))
--   (h : {a : A} → B a → C a)
--   → ap↓ (λ {a} → ap (h {a = a})) (↓-=-in {p = p} {u = u} {v = v} k)
--   == ↓-=-in (lhs {f = f} {g = g} k h ∙ ap (ap↓ (λ {a} → h {a = a})) k
--                                      ∙ rhs {f = f} {g = g} k h)

{-
Commutation of [ap↓ (ap h)] and [↓-swap!]. This is "just" J, but it’s not as
easy as it seems.
-}

-- module Ap↓-swap! {i j k ℓ} {A : Type i} {B : Type j} {C : Type k}
--   {D : Type ℓ} (h : C → D) (f : A → C) (g : B → C)
--   {a a' : A} {p : a == a'} {b b' : B} {q : b == b'}
--   (r : f a == g b') (s : f a' == g b)
--   (t : r == s ∙ ap g q  [ (λ x → f x == g b') ↓ p ])
--   where

--   lhs : ap h (ap f p ∙' s) == ap (h ∘ f) p ∙' ap h s
--   lhs = ap-∙' h (ap f p) s ∙ (ap (λ u → u ∙' ap h s) (∘-ap h f p))

--   rhs : ap h (s ∙ ap g q) == ap h s ∙ ap (h ∘ g) q
--   rhs = ap-∙ h s (ap g q) ∙ (ap (λ u → ap h s ∙ u) (∘-ap h g q))

--   β : ap↓ (ap h) (↓-swap! f g r s t) ==
--               lhs ◃ ↓-swap! (h ∘ f) (h ∘ g) (ap h r) (ap h s) (ap↓ (ap h) t ▹ rhs)
--   β with a | a' | p | b | b' | q | r | s | t
--   β | a | .a | idp | b | .b | idp | r | s | t = coh r s t  where

--     T : {x x' : C} (r s : x == x') (t : r == s ∙ idp) → Type _
--     T r s t =
--       ap (ap h) (∙'-unit-l s ∙ ! (∙-unit-r s) ∙ ! t) ==
--       (ap-∙' h idp s ∙ idp)
--       ∙
--       (∙'-unit-l (ap h s) ∙
--       ! (∙-unit-r (ap h s)) ∙
--       !
--       (ap (ap h) t ∙'
--         (ap-∙ h s idp ∙ idp)))

--     coh' : {x x' : C} {r s : x == x'} (t : r == s) → T r s (t ∙ ! (∙-unit-r s))
--     coh' {r = idp} {s = .idp} idp = idp

--     coh : {x x' : C} (r s : x == x') (t : r == s ∙ idp) → T r s t
--     coh r s t = transport (λ t → T r s t) (coh2 t (∙-unit-r s)) (coh' (t ∙ ∙-unit-r s))  where

--       coh2 : ∀ {i} {A : Type i} {x y z : A} (p : x == y) (q : y == z) → (p ∙ q) ∙ ! q == p
--       coh2 idp idp = idp

-- module _ {i j k} {A : Type i} {B B' : Type j} {C : Type k} (f : A → C) (g' : B' → B) (g : B → C) where

--   abc : {a a' : A} {p : a == a'} {c c' : B'} {q' : c == c'} {q : g' c == g' c'}
--     (r : f a == g (g' c')) (s : f a' == g (g' c))
--     (t : q == ap g' q')
--     (α : r == s ∙ ap g q [ (λ x → f x == g (g' c')) ↓ p ])
--     → {!(↓-swap! f g r s α ▹ ?) ∙'2ᵈ ?!} == ↓-swap! f (g ∘ g') {p = p} {q = q'} r s (α ▹ ap (λ u → s ∙ u) (ap (ap g) t ∙ ∘-ap g g' q'))
--   abc = {!!}

{- Functoriality of application and function extensionality -}

∙-app= : ∀ {i j} {A : Type i} {B : A → Type j} {f g h : Π A B}
  (α : f == g) (β : g == h)
  → α ∙ β == λ= (λ x → app= α x ∙ app= β x)
∙-app= idp β = λ=-η β

∙-λ= : ∀ {i j} {A : Type i} {B : A → Type j} {f g h : Π A B}
  (α : f ∼ g) (β : g ∼ h)
  → λ= α ∙ λ= β == λ= (λ x → α x ∙ β x)
∙-λ= α β = ∙-app= (λ= α) (λ= β)
  ∙ ap λ= (λ= (λ x → ap (λ w → w ∙ app= (λ= β) x) (app=-β α x)
                   ∙ ap (λ w → α x ∙ w) (app=-β β x)))


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

open import Base
open import Homotopy.PushoutDef
open import Homotopy.VanKampen.Guide

module Homotopy.VanKampen.SplitCode {i} (d : pushout-diag i)
  (l : legend i (pushout-diag.C d)) (a₁ : pushout-diag.A d) where

  open pushout-diag d
  open legend l

  open import Homotopy.Truncation
  open import Homotopy.PathTruncation

  -- Definition.
  module _ where
    private
      data #code-a (a₂ : A) : Set i
      data #code-b (b₂ : B) : Set i

      data #code-a a₂ where
        #a : a₁ ≡₀ a₂ → #code-a a₂
        #ba : ∀ n → #code-b (g $ loc n) → f (loc n) ≡₀ a₂ → #code-a a₂
      data #code-b b₂ where
        #ab : ∀ n → #code-a (f $ loc n) → g (loc n) ≡₀ b₂ → #code-b b₂

    code-b : B → Set i
    code-b = #code-b

    code-a : A → Set i
    code-a = #code-a

    postulate -- HIT
      code-a-is-set : ∀ a → is-set (code-a a)
      code-b-is-set : ∀ b → is-set (code-b b)

    a : ∀ {a₂} → a₁ ≡₀ a₂ → code-a a₂
    a = #a

    infixl 6 a
    syntax a co = ⟧a co

    ba : ∀ {a₂} n → code-b (g $ loc n) → f (loc n) ≡₀ a₂ → code-a a₂
    ba = #ba

    infixl 6 ba
    syntax ba n co p = co b⟦ n ⟧a p

    ab : ∀ {b₂} n → code-a (f $ loc n) → g (loc n) ≡₀ b₂ → code-b b₂
    ab = #ab

    infixl 6 ab
    syntax ab n co p = co a⟦ n ⟧b p

    postulate -- HIT
      code-a-refl-refl : ∀ n (co : code-a (f $ loc n))
        → co a⟦ n ⟧b refl₀ b⟦ n ⟧a refl₀ ≡ co
      code-b-refl-refl : ∀ n (co : code-b (g $ loc n))
        → co b⟦ n ⟧a refl₀ a⟦ n ⟧b refl₀ ≡ co
      -- The other diriction is fine.
      -- There's no canonical choice between two, I think.
      code-ab-swap : ∀ n₁ co n₂ (r : loc n₁ ≡₀ loc n₂)
        → co a⟦ n₁ ⟧b ap₀ g r b⟦ n₂ ⟧a refl₀
        ≡ co a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a ap₀ f r

    -- Dependent recursor.
    code-rec : ∀ {j}
      (P-a : ∀ {a₂} → code-a a₂ → Set j) ⦃ _ : ∀ {a₂} co → is-set $ P-a {a₂} co ⦄
      (P-b : ∀ {b₂} → code-b b₂ → Set j) ⦃ _ : ∀ {b₂} co → is-set $ P-b {b₂} co ⦄
      (h₀-a : ∀ {a₂} (p : _ ≡₀ a₂) → P-a (a p))
      (h₀-ba : ∀ {a₂} n {co} (_ : P-b co) (p : _ ≡₀ a₂)
        → P-a (co b⟦ n ⟧a p))
      (h₀-ab : ∀ {b₂} n {co} (_ : P-a co) (p : _ ≡₀ b₂)
        → P-b (co a⟦ n ⟧b p))
      (h₁-a-rr : ∀ n {co} (pco : P-a co) →
        transport (P-a {f $ loc n}) (code-a-refl-refl n co)
          (h₀-ba n (h₀-ab n pco $ refl₀) $ refl₀)
        ≡ pco)
      (h₁-b-rr : ∀ n {co} (pco : P-b co) →
        transport (P-b {g $ loc n}) (code-b-refl-refl n co)
          (h₀-ab n (h₀-ba n pco $ refl₀) $ refl₀)
        ≡ pco)
      (h₁-ab-s : ∀ n₁ {co} (pco : P-a co) n₂ r →
        transport (P-a {f $ loc n₂}) (code-ab-swap n₁ co n₂ r)
          (h₀-ba n₂ (h₀-ab n₁ pco $ ap₀ g r) $ refl₀)
        ≡ (h₀-ba n₁ (h₀-ab n₁ pco $ refl₀) $ ap₀ f r))
      → (∀ {a₂} (co : code-a a₂) → P-a co)
      × (∀ {b₂} (co : code-b b₂) → P-b co)
    code-rec P-a P-b h₀-a h₀-ba h₀-ab _ _ _ = elim-a , elim-b
      where
        elim-a : ∀ {a₂} (co : code-a a₂) → P-a co
        elim-b : ∀ {b₂} (co : code-b b₂) → P-b co
        elim-a (#a       p) = h₀-a                p
        elim-a (#ba n co p) = h₀-ba n (elim-b co) p
        elim-b (#ab n co p) = h₀-ab n (elim-a co) p

    -- This is actually "partially dependent".
    -- P-a and P-b are indexed by the end points.
    -- This is fine because they are fixed in
    -- all transports in 1-cells.
    code-rec-nondep : ∀ {j}
      (P-a : A → Set j) ⦃ _ : ∀ a → is-set $ P-a a ⦄
      (P-b : B → Set j) ⦃ _ : ∀ b → is-set $ P-b b ⦄
      (h₀-a : ∀ {a₂} (p : a₁ ≡₀ a₂) → P-a a₂)
      (h₀-ba : ∀ {a₂} n {co : code-b (g $ loc n)} → P-b (g $ loc n)
        → f (loc n) ≡₀ a₂ → P-a a₂)
      (h₀-ab : ∀ {b₂} n {co : code-a (f $ loc n)} → P-a (f $ loc n)
        → g (loc n) ≡₀ b₂ → P-b b₂)
      (h₁-a-rr : ∀ n {co} (pco : P-a (f $ loc n))
        → h₀-ba n {co a⟦ n ⟧b refl₀} (h₀-ab n {co} pco $ refl₀) refl₀ ≡ pco)
      (h₁-b-rr : ∀ n {co} (pco : P-b (g $ loc n))
        → h₀-ab n {co b⟦ n ⟧a refl₀} (h₀-ba n {co} pco $ refl₀) refl₀ ≡ pco)
      (h₁-ab-s : ∀ n₁ {co : code-a $ f $ loc n₁} (pco : P-a $ f $ loc n₁) n₂ (r : loc n₁ ≡₀ loc n₂) →
          h₀-ba n₂ {co a⟦ n₁ ⟧b ap₀ g r} (h₀-ab n₁ {co} pco $ ap₀ g r) refl₀
        ≡ h₀-ba n₁ {co a⟦ n₁ ⟧b refl₀} (h₀-ab n₁ {co} pco $ refl₀) (ap₀ f r))
      → (∀ {a₂} (co : code-a a₂) → P-a a₂)
      × (∀ {b₂} (co : code-b b₂) → P-b b₂)
    code-rec-nondep P-a P-b h₀-a h₀-ba h₀-ab _ _ _ = elim-a , elim-b
      where
        elim-a : ∀ {a₂} (co : code-a a₂) → P-a a₂
        elim-b : ∀ {b₂} (co : code-b b₂) → P-b b₂
        elim-a (#a       p) = h₀-a                     p
        elim-a (#ba c co p) = h₀-ba c {co} (elim-b co) p
        elim-b (#ab c co p) = h₀-ab c {co} (elim-a co) p

  module _ where
    trans-a : ∀ {y z} (q : y ≡ z) (p : a₁ ≡₀ y)
      → transport code-a q (⟧a p) ≡ ⟧a (p ∘₀ proj q)
    trans-a refl p = ap a (! (refl₀-right-unit p))

    trans-ba : ∀ {y z} (q : y ≡ z) n co (p : f (loc n) ≡₀ y)
      → transport code-a q (co b⟦ n ⟧a p) ≡ co b⟦ n ⟧a p ∘₀ proj q
    trans-ba refl n co p = ap (ba n co) (! (refl₀-right-unit p))

    trans-ab : ∀ {y b} (q : y ≡ b) n co (p : g (loc n) ≡₀ y)
      → transport code-b q (co a⟦ n ⟧b p) ≡ co a⟦ n ⟧b p ∘₀ proj q
    trans-ab refl n co p = ap (ab n co) (! (refl₀-right-unit p))

  module _ where
    -- all-paths
    abstract
      code-has-all-cells₂-a : ∀ {a} {x y : code-a a} (p q : x ≡ y) → p ≡ q
      code-has-all-cells₂-a = prop-has-all-paths $ code-a-is-set _ _ _

      code-has-all-cells₂-b : ∀ {b} {x y : code-b b} (p q : x ≡ y) → p ≡ q
      code-has-all-cells₂-b = prop-has-all-paths $ code-b-is-set _ _ _

    code-a-rec : ∀ {j}
      (P-a : ∀ {a₂} → code-a a₂ → Set j) ⦃ _ : ∀ {a₂} co → is-set $ P-a {a₂} co ⦄
      (h₀-a : ∀ {a₂} (p : _ ≡₀ a₂) → P-a (⟧a p))
      (h₀-ba : ∀ {a₂} n co (p : _ ≡₀ a₂)
        → P-a (co b⟦ n ⟧a p))
      (h₁-a-rr : ∀ n {co} (pco : P-a co) →
        transport (P-a {f $ loc n}) (code-a-refl-refl n co)
          (h₀-ba n (co a⟦ n ⟧b refl₀) $ refl₀)
        ≡ pco)
      → (∀ {a₂} (co : code-a a₂) → P-a co)
    code-a-rec {j} P-a ⦃ P-a-is-set ⦄ h₀-a h₀-ba h₁-a-rr = π₁ $ code-rec
      P-a ⦃ P-a-is-set ⦄
      (λ _ → unit) ⦃ λ _ → unit-is-set ⦄
      h₀-a
      (λ n {co} _ p → h₀-ba n co p)
      (λ _ _ _ → tt)
      h₁-a-rr
      (λ _ _ → prop-has-all-paths unit-is-prop _ _)
      -- FIXME This proof is too ugly.
      (λ n₁ {co} (pco : P-a co) n₂ r →
        transport (P-a {f $ loc n₂}) (code-ab-swap n₁ co n₂ r)
          (h₀-ba n₂ (co a⟦ n₁ ⟧b ap₀ g r) $ refl₀)
            ≡⟨ ! $ h₁-a-rr n₂ _ ⟩
        transport (P-a {f $ loc n₂}) (code-a-refl-refl n₂ _)
          (h₀-ba n₂ (_ a⟦ n₂ ⟧b refl₀) $ refl₀)
            ≡⟨ ap (λ x → transport (P-a {f $ loc n₂}) (code-a-refl-refl n₂ _)
                $ h₀-ba n₂ (_ a⟦ n₂ ⟧b refl₀) $ refl₀) $ code-ab-swap n₁ co n₂ r ⟩
        transport (P-a {f $ loc n₂}) (code-a-refl-refl n₂ _)
          (h₀-ba n₂ (_ a⟦ n₂ ⟧b refl₀) $ refl₀)
            ≡⟨ h₁-a-rr n₂ _ ⟩∎
        h₀-ba n₁ (co a⟦ n₁ ⟧b refl₀) (ap₀ f r)
            ∎)

    code-b-rec : ∀ {j}
      (P-b : ∀ {b₂} → code-b b₂ → Set j) ⦃ _ : ∀ {b₂} co → is-set $ P-b {b₂} co ⦄
      (h₀-ab : ∀ {b₂} n co (p : _ ≡₀ b₂)
        → P-b (co a⟦ n ⟧b p))
      (h₁-b-rr : ∀ n {co} (pco : P-b co) →
        transport (P-b {g $ loc n}) (code-b-refl-refl n co)
          (h₀-ab n (co b⟦ n ⟧a refl₀) $ refl₀)
        ≡ pco)
      → (∀ {b₂} (co : code-b b₂) → P-b co)
    code-b-rec {j} P-b ⦃ P-b-is-set ⦄ h₀-ab h₁-b-rr = π₂ $ code-rec
      (λ _ → unit) ⦃ λ _ → unit-is-set ⦄
      P-b ⦃ P-b-is-set ⦄
      (λ _ → tt)
      (λ _ _ _ → tt)
      (λ n {co} _ p → h₀-ab n co p)
      (λ _ _ → prop-has-all-paths unit-is-prop _ _)
      h₁-b-rr
      (λ _ _ _ _ → prop-has-all-paths unit-is-prop _ _)

  -- Derived 1-cells.
  module _ where
    abstract
      code-ba-swap : ∀ n₁ co n₂ (r : loc n₁ ≡₀ loc n₂)
        → co b⟦ n₁ ⟧a ap₀ f r a⟦ n₂ ⟧b refl₀
        ≡ co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b ap₀ g r
      code-ba-swap n₁ co n₂ r =
        co                                   b⟦ n₁ ⟧a ap₀ f r a⟦ n₂ ⟧b refl₀
          ≡⟨ ap (λ x → x b⟦ n₁ ⟧a ap₀ f r a⟦ n₂ ⟧b refl₀) $ ! $ code-b-refl-refl n₁ co ⟩
        co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a ap₀ f r a⟦ n₂ ⟧b refl₀
          ≡⟨ ap (λ x → x a⟦ n₂ ⟧b refl₀) $ ! $ code-ab-swap n₁ (co b⟦ n₁ ⟧a refl₀) n₂ r ⟩
        co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b ap₀ g r b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b refl₀
          ≡⟨ code-b-refl-refl n₂ $ co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b ap₀ g r ⟩∎
        co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b ap₀ g r
          ∎

    abstract
      -- Old (provable) rules.
      code-a-merge : ∀ {a₂} n p₁ (p₂ : _ ≡₀ a₂)
        → ⟧a p₁ a⟦ n ⟧b refl₀ b⟦ n ⟧a p₂
        ≡ ⟧a p₁            ∘₀           p₂
      code-a-merge {a₂} n p₁ = π₀-extend
        ⦃ λ _ → ≡-is-set $ code-a-is-set a₂ ⦄
        ( λ p₂ →
            ⟧a p₁ a⟦ n ⟧b refl₀ b⟦ n ⟧a proj p₂
              ≡⟨ ! $ trans-ba p₂ n (⟧a p₁ a⟦ n ⟧b refl₀) refl₀ ⟩
            transport code-a p₂ (⟧a p₁ a⟦ n ⟧b refl₀ b⟦ n ⟧a refl₀)
              ≡⟨ ap (transport code-a p₂) $ code-a-refl-refl n (⟧a p₁) ⟩
            transport code-a p₂ (⟧a p₁)
              ≡⟨ trans-a p₂ p₁ ⟩∎
            ⟧a p₁ ∘₀ proj p₂
              ∎)

    abstract
      code-ab-merge : ∀ {b₂} n₁ co n₂ p₁ (p₂ : _ ≡₀ b₂)
        → co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b p₂
        ≡ co a⟦ n₁ ⟧b p₁             ∘₀            p₂
      code-ab-merge {b₂} n₁ co n₂ p₁ = π₀-extend
        ⦃ λ _ → ≡-is-set $ code-b-is-set b₂ ⦄
        ( λ p₂ →
            co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b proj p₂
              ≡⟨ ! $ trans-ab p₂ n₂ (co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a refl₀) refl₀ ⟩
            transport code-b p₂ (co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b refl₀)
              ≡⟨ ap (transport code-b p₂) $ code-b-refl-refl n₂ (co a⟦ n₁ ⟧b p₁) ⟩
            transport code-b p₂ (co a⟦ n₁ ⟧b p₁)
              ≡⟨ trans-ab p₂ n₁ co p₁ ⟩∎
            co a⟦ n₁ ⟧b p₁ ∘₀ proj p₂
              ∎)

    abstract
      code-ba-merge : ∀ {a₂} n₁ co n₂ p₁ (p₂ : _ ≡₀ a₂)
        → co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b refl₀ b⟦ n₂ ⟧a p₂
        ≡ co b⟦ n₁ ⟧a p₁             ∘₀            p₂
      code-ba-merge {a₂} n₁ co n₂ p₁ = π₀-extend
        ⦃ λ _ → ≡-is-set $ code-a-is-set a₂ ⦄
        ( λ p₂ →
            co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b refl₀ b⟦ n₂ ⟧a proj p₂
              ≡⟨ ! $ trans-ba p₂ n₂ (co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b refl₀) refl₀ ⟩
            transport code-a p₂ (co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b refl₀ b⟦ n₂ ⟧a refl₀)
              ≡⟨ ap (transport code-a p₂) $ code-a-refl-refl n₂ (co b⟦ n₁ ⟧a p₁) ⟩
            transport code-a p₂ (co b⟦ n₁ ⟧a p₁)
              ≡⟨ trans-ba p₂ n₁ co p₁ ⟩∎
            co b⟦ n₁ ⟧a p₁ ∘₀ proj p₂
              ∎)

    abstract
      code-a-shift : ∀ {b₂} n₁ p₁ n₂ (r : loc n₁ ≡₀ loc n₂) (p₂ : _ ≡₀ b₂)
        → ⟧a p₁ ∘₀ ap₀ f r a⟦ n₂ ⟧b            p₂
        ≡ ⟧a p₁            a⟦ n₁ ⟧b ap₀ g r ∘₀ p₂
      code-a-shift n₁ p₁ n₂ r p₂ =
        ⟧a p₁             ∘₀            ap₀ f r a⟦ n₂ ⟧b p₂
          ≡⟨ ap (λ x → x a⟦ n₂ ⟧b p₂) $ ! $ code-a-merge n₁ p₁ (ap₀ f r) ⟩
        ⟧a p₁ a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a ap₀ f r a⟦ n₂ ⟧b p₂
          ≡⟨ ap (λ x → x a⟦ n₂ ⟧b p₂) $ ! $ code-ab-swap n₁ (⟧a p₁) n₂ r ⟩
        ⟧a p₁ a⟦ n₁ ⟧b ap₀ g r b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b p₂
          ≡⟨ code-ab-merge n₁ (⟧a p₁) n₂ (ap₀ g r) p₂ ⟩∎
        ⟧a p₁ a⟦ n₁ ⟧b ap₀ g r             ∘₀            p₂
          ∎

    abstract
      code-ba-shift : ∀ {b₂} n₁ co n₂ p₁ n₃ (r : loc n₂ ≡₀ loc n₃) (p₂ : _ ≡₀ b₂)
        → co b⟦ n₁ ⟧a p₁ ∘₀ ap₀ f r a⟦ n₃ ⟧b            p₂
        ≡ co b⟦ n₁ ⟧a p₁            a⟦ n₂ ⟧b ap₀ g r ∘₀ p₂
      code-ba-shift n₁ co n₂ p₁ n₃ r p₂ =
        co b⟦ n₁ ⟧a p₁             ∘₀            ap₀ f r a⟦ n₃ ⟧b p₂
          ≡⟨ ap (λ x → x a⟦ n₃ ⟧b p₂) $ ! $ code-ba-merge n₁ co n₂ p₁ (ap₀ f r) ⟩
        co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b refl₀ b⟦ n₂ ⟧a ap₀ f r a⟦ n₃ ⟧b p₂
          ≡⟨ ap (λ x → x a⟦ n₃ ⟧b p₂) $ ! $ code-ab-swap n₂ (co b⟦ n₁ ⟧a p₁) n₃ r ⟩
        co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b ap₀ g r b⟦ n₃ ⟧a refl₀ a⟦ n₃ ⟧b p₂
          ≡⟨ code-ab-merge n₂ (co b⟦ n₁ ⟧a p₁) n₃ (ap₀ g r) p₂ ⟩∎
        co b⟦ n₁ ⟧a p₁ a⟦ n₂ ⟧b ap₀ g r             ∘₀            p₂
          ∎

    abstract
      code-ab-shift : ∀ {a₂} n₁ co n₂ p₁ n₃ (r : loc n₂ ≡₀ loc n₃) (p₂ : _ ≡₀ a₂)
        → co a⟦ n₁ ⟧b p₁ ∘₀ ap₀ g r b⟦ n₃ ⟧a            p₂
        ≡ co a⟦ n₁ ⟧b p₁            b⟦ n₂ ⟧a ap₀ f r ∘₀ p₂
      code-ab-shift n₁ co n₂ p₁ n₃ r p₂ =
        co a⟦ n₁ ⟧b p₁             ∘₀            ap₀ g r b⟦ n₃ ⟧a p₂
          ≡⟨ ap (λ x → x b⟦ n₃ ⟧a p₂) $ ! $ code-ab-merge n₁ co n₂ p₁ (ap₀ g r) ⟩
        co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a refl₀ a⟦ n₂ ⟧b ap₀ g r b⟦ n₃ ⟧a p₂
          ≡⟨ ap (λ x → x b⟦ n₃ ⟧a p₂) $ ! $ code-ba-swap n₂ (co a⟦ n₁ ⟧b p₁) n₃ r ⟩
        co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a ap₀ f r a⟦ n₃ ⟧b refl₀ b⟦ n₃ ⟧a p₂
          ≡⟨ code-ba-merge n₂ (co a⟦ n₁ ⟧b p₁) n₃ (ap₀ f r) p₂ ⟩∎
        co a⟦ n₁ ⟧b p₁ b⟦ n₂ ⟧a ap₀ f r             ∘₀            p₂
          ∎

  -- Definition of code
  module _ where
    P : Set i
    P = pushout d

    -- Basic conversion
    a⇒b : ∀ n → code-a (f $ loc n) → code-b (g $ loc n)
    a⇒b n co = co a⟦ n ⟧b refl₀

    b⇒a : ∀ n → code-b (g $ loc n) → code-a (f $ loc n)
    b⇒a n co = co b⟦ n ⟧a refl₀

    private
      -- The head of the code
      code-glue-eq-loc : ∀ n → code-a (f $ loc n) ≃ code-b (g $ loc n)
      code-glue-eq-loc n = a⇒b n ,
        iso-is-eq (a⇒b n) (b⇒a n) (code-b-refl-refl n) (code-a-refl-refl n)

    abstract
      trans-code-a-r : ∀ n₁ n₂ (r : loc n₁ ≡ loc n₂) (co : code-a $ f $ loc n₁)
        → transport (code-a ◯ f) r co ≡ co a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a proj (ap f r)
      trans-code-a-r n₁ n₂ r co =
        transport (code-a ◯ f) r co
          ≡⟨ ! $ trans-ap code-a f r co ⟩
        transport code-a (ap f r) co
          ≡⟨ ap (transport code-a $ ap f r) $ ! $ code-a-refl-refl n₁ co ⟩
        transport code-a (ap f r) (co a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a refl₀)
          ≡⟨ trans-ba (ap f r) n₁ _ refl₀ ⟩∎
        co a⟦ n₁ ⟧b refl₀ b⟦ n₁ ⟧a proj (ap f r)
          ∎

    abstract
      trans-code-b-r : ∀ n₁ n₂ (r : loc n₁ ≡ loc n₂) (co : code-b $ g $ loc n₁)
        → transport (code-b ◯ g) r co ≡ co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b proj (ap g r)
      trans-code-b-r n₁ n₂ r co =
        transport (code-b ◯ g) r co
          ≡⟨ ! $ trans-ap code-b g r co ⟩
        transport code-b (ap g r) co
          ≡⟨ ap (transport code-b $ ap g r) $ ! $ code-b-refl-refl n₁ co ⟩
        transport code-b (ap g r) (co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b refl₀)
          ≡⟨ trans-ab (ap g r) n₁ _ refl₀ ⟩∎
        co b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b proj (ap g r)
          ∎

    private
      abstract
        code-glue-eq-route : ∀ n₁ n₂ r
          → transport (λ c → code-a (f c) ≃ code-b (g c)) r (code-glue-eq-loc n₁)
          ≡ code-glue-eq-loc n₂
        code-glue-eq-route n₁ n₂ r = equiv-eq $ funext λ co →
          π₁ (transport (λ c → code-a (f c) ≃ code-b (g c)) r (code-glue-eq-loc n₁)) co
              ≡⟨ ap (λ x → x co) $ app-trans
                (λ c →  code-a (f c) ≃ code-b (g c))
                (λ c → code-a (f c) → code-b (g c))
                (λ _ → π₁) r (code-glue-eq-loc n₁) ⟩
          transport (λ c → code-a (f c) → code-b (g c)) r (a⇒b n₁) co
              ≡⟨ trans-→ (code-a ◯ f) (code-b ◯ g) r (a⇒b n₁) co ⟩
          transport (code-b ◯ g) r (a⇒b n₁ $ transport (code-a ◯ f) (! r) co)
              ≡⟨ ap (transport (code-b ◯ g) r ◯ a⇒b n₁) $ trans-code-a-r _ _ (! r) co ⟩
          transport (code-b ◯ g) r (co a⟦ n₂ ⟧b refl₀ b⟦ n₂ ⟧a proj (ap f $ ! r) a⟦ n₁ ⟧b refl₀)
              ≡⟨ ap (λ x → transport (code-b ◯ g) r $ x a⟦ n₁ ⟧b refl₀) $ ! $ code-ab-swap _ co _ (proj $ ! r) ⟩
          transport (code-b ◯ g) r (co a⟦ n₂ ⟧b proj (ap g $ ! r) b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b refl₀)
              ≡⟨ ap (transport (code-b ◯ g) r) $ code-b-refl-refl n₁ _ ⟩
          transport (code-b ◯ g) r (co a⟦ n₂ ⟧b proj (ap g $ ! r))
              ≡⟨ trans-code-b-r _ _ r _ ⟩
          co a⟦ n₂ ⟧b proj (ap g $ ! r) b⟦ n₁ ⟧a refl₀ a⟦ n₁ ⟧b proj (ap g r)
              ≡⟨ code-ab-merge n₂ co n₁ _ _ ⟩
          co a⟦ n₂ ⟧b proj (ap g (! r) ∘ ap g r)
              ≡⟨ ap (λ x → co a⟦ n₂ ⟧b proj x) $ concat-ap g (! r) r ⟩
          co a⟦ n₂ ⟧b proj (ap g $ ! r ∘ r)
              ≡⟨ ap (λ x → co a⟦ n₂ ⟧b proj (ap g x)) $ opposite-left-inverse r ⟩∎
          a⇒b n₂ co
              ∎

    private
      code-glue-eq : ∀ c → code-a (f c) ≃ code-b (g c)
      code-glue-eq = visit-fiber-rec l
        (λ c → code-a (f c) ≃ code-b (g c))
        ⦃ λ _ → ≃-is-set (code-a-is-set _) (code-b-is-set _) ⦄
        code-glue-eq-loc
        code-glue-eq-route

    -- The data type!
    code : P → Set i
    code = pushout-rec-nondep (Set i) code-a code-b (eq-to-path ◯ code-glue-eq)

    -- Useful lemma
    abstract
      trans-code-glue-loc : ∀ n₂ co → transport code (glue $ loc n₂) co ≡ a⇒b n₂ co
      trans-code-glue-loc n₂ co =
        transport code (glue $ loc n₂) co
          ≡⟨ ! $ trans-ap (λ X → X) code (glue $ loc n₂) co ⟩
        transport (λ X → X) (ap code $ glue $ loc n₂) co
          ≡⟨ ap (λ x → transport (λ X → X) x co)
              $ pushout-β-glue-nondep (Set i) code-a code-b (eq-to-path ◯ code-glue-eq) (loc n₂) ⟩
        transport (λ X → X) (eq-to-path $ code-glue-eq $ loc n₂) co
          ≡⟨ ap (λ x → transport (λ X → X) (eq-to-path x) co)
                $ visit-fiber-β-loc l
                    (λ c → code-a (f c) ≃ code-b (g c))
                    ⦃ λ _ → ≃-is-set (code-a-is-set _) (code-b-is-set _) ⦄
                    code-glue-eq-loc
                    code-glue-eq-route
                    n₂ ⟩
        transport (λ X → X) (eq-to-path $ code-glue-eq-loc n₂) co
          ≡⟨ trans-id-eq-to-path (code-glue-eq-loc n₂) co ⟩∎
        a⇒b n₂ co
          ∎

    abstract
      trans-code-!glue-loc : ∀ n₂ co → transport code (! $ glue $ loc n₂) co ≡ b⇒a n₂ co
      trans-code-!glue-loc n₂ co = move!-transp-right code (glue $ loc n₂) co (b⇒a n₂ co) $ ! $
        transport code (glue $ loc n₂) (b⇒a n₂ co)
          ≡⟨ trans-code-glue-loc n₂ (b⇒a n₂ co) ⟩
        a⇒b n₂ (b⇒a n₂ co)
          ≡⟨ code-b-refl-refl n₂ co ⟩∎
        co
          ∎

    -- Truncation level
    abstract
      code-is-set : ∀ p → is-set $ code p
      code-is-set = pushout-rec
        (λ p → is-set $ code p)
        code-a-is-set
        code-b-is-set
        (λ _ → prop-has-all-paths is-set-is-prop _ _)


{-# OPTIONS --erased-cubical --safe #-}

module Music where

open import Data.Nat     using (ℕ; zero; suc; _+_; _*_; _≤_; _≤?_)
open import Data.Integer using (ℤ; +_)
open import Data.List    using (List; foldr; []; _∷_; reverse; sum; map)
open import Data.Product using (_×_; _,_)
open import Data.Sum     using (_⊎_; inj₁; inj₂)
open import Data.Vec     using (Vec; []; _∷_; replicate; concat; zipWith; toList; _++_; foldr₁; take; drop) renaming (map to vmap)
open import Function     using (_∘_)

open import Data.Nat.Properties using (<⇒≤)
open import Relation.Nullary    using (yes; no)

open import Relation.Binary.PropositionalEquality using (sym; subst)

open import Nat
open import Note
open import Pitch
open import Interval

-- A point in the music grid, which can either be a tone,
-- a continuation of a previous tone, or a rest.
data Point : Set where
  tone : Pitch → Point
  hold : Pitch → Point
  rest : Point

data Melody (n : ℕ) : Set where
  melody : Vec Point n → Melody n

unmelody : {n : ℕ} → Melody n → Vec Point n
unmelody (melody ps) = ps

infixr 5 _m++_
_m++_ : {m n : ℕ} → Melody m → Melody n → Melody (m + n)
melody a m++ melody b = melody (a ++ b)

note→melody : (n : Note) → Melody (noteDuration n)
note→melody (tone zero    p) = melody []
note→melody (tone (suc d) p) = melody (tone p ∷ replicate (hold p))
note→melody (rest _)         = melody (replicate rest)

notes→melody : (ns : List Note) → Melody (sum (map noteDuration ns))
notes→melody []       = melody []
notes→melody (n ∷ ns) = note→melody n m++ notes→melody ns

pitches→melody : {n : ℕ} → (d : Duration) → (ps : Vec Pitch n) → Melody (n * d)
pitches→melody d ps = melody (concat (vmap (unmelody ∘ note→melody ∘ tone d) ps))

-- Assumes melody is well-formed in that a held note has the
-- same pitch as the note before it.
-- Does not consolidate rests.
melody→notes : {n : ℕ} → Melody n → List Note
melody→notes (melody m) = (reverse ∘ mn 0 ∘ reverse ∘ toList) m
  where mn : ℕ → List Point → List Note -- c is the number of held points
        mn c []            = []
        mn c (tone p ∷ ps) = tone (suc c) p ∷ mn 0 ps
        mn c (hold _ ∷ ps) = mn (suc c) ps
        mn c (rest ∷ ps)   = rest 1 ∷ mn 0 ps

transposePoint : ℤ → Point → Point
transposePoint k (tone p) = tone (transposePitch k p)
transposePoint k (hold p) = hold (transposePitch k p)
transposePoint k rest     = rest

transposeMelody : {n : ℕ} → ℤ → Melody n → Melody n
transposeMelody k = melody ∘ vmap (transposePoint k) ∘ unmelody

data Chord (n : ℕ) : Set where
  chord : Vec Point n → Chord n

unchord : {n : ℕ} → Chord n → Vec Point n
unchord (chord ps) = ps

-- We represent music as a v × d grid where v is the number of voices and d is the duration.
-- The primary representation is as parallel melodies (counterpoint).
data Counterpoint (v : ℕ) (d : ℕ): Set where
  cp : Vec (Melody d) v → Counterpoint v d

uncp : {v d : ℕ} → Counterpoint v d → Vec (Melody d) v
uncp (cp m) = m

-- An alternative representation of music is as a series of chords (harmonic progression).
data Harmony (v : ℕ) (d : ℕ): Set where
  harmony : Vec (Chord v) d → Harmony v d

unharmony : {v d : ℕ} → Harmony v d → Vec (Chord v) d
unharmony (harmony h) = h

pitches→harmony : {n : ℕ} (d : Duration) → (ps : Vec Pitch n) → Harmony n d
pitches→harmony zero    ps = harmony []
pitches→harmony (suc d) ps = harmony (chord (vmap tone ps) ∷ replicate (chord (vmap hold ps)))

pitchPair→Harmony : (d : Duration) → PitchPair → Harmony 2 d
pitchPair→Harmony d (p , q) = pitches→harmony d (p ∷ q ∷ [])

pitchInterval→Harmony : (d : Duration) → PitchInterval → Harmony 2 d
pitchInterval→Harmony d = pitchPair→Harmony d ∘ pitchIntervalToPitchPair

{-
pitchIntervalsToCounterpoint : PitchInterval → Counterpoint
pitchIntervalsToCounterpoint = pitchPairToCounterpoint ∘ pitchIntervalToPitchPair
-}

addEmptyVoice : {v d : ℕ} → Harmony v d → Harmony (suc v) d
addEmptyVoice (harmony h) = harmony (vmap (chord ∘ (rest ∷_) ∘ unchord) h)

infixl 5 _+H+_
_+H+_ : {v d d' : ℕ} → Harmony v d → Harmony v d' → Harmony v (d + d')
h +H+ h' = harmony (unharmony h ++ unharmony h')

foldIntoHarmony : {k n : ℕ} (ds : Vec Duration (suc k)) → (pss : Vec (Vec Pitch n) (suc k)) → Harmony n (foldr₁ _+_ ds)
foldIntoHarmony (d ∷ [])      (ps ∷ [])        = pitches→harmony d ps
foldIntoHarmony (d ∷ d' ∷ ds) (ps ∷ ps' ∷ pss) = (pitches→harmony d ps) +H+ (foldIntoHarmony (d' ∷ ds) (ps' ∷ pss))

-- matrix transposition
mtranspose : {A : Set}{m n : ℕ} → Vec (Vec A n) m → Vec (Vec A m) n
mtranspose []         = replicate []
mtranspose (xs ∷ xss) = zipWith _∷_ xs (mtranspose xss)

counterpoint→harmony : {v d : ℕ} → Counterpoint v d → Harmony v d
counterpoint→harmony = harmony ∘ vmap chord ∘ mtranspose ∘ vmap unmelody ∘ uncp

harmony→counterpoint : {v d : ℕ} → Harmony v d → Counterpoint v d
harmony→counterpoint = cp ∘ vmap melody ∘ mtranspose ∘ vmap unchord ∘ unharmony

-- Fix length of a melody, either truncating or padding with rests
fixLength : {m : ℕ} → (n : ℕ) → Melody m → Melody n
fixLength {m} n (melody ns) with <-∨-≥ n m
... | inj₁ n<m = melody (take n (subst (Vec Point) (sym (m+n-m=n n m {<⇒≤ n<m})) ns))
... | inj₂ m≤n = melody (subst (Vec Point) (m+n-m=n m n) (ns ++ replicate {n = n - m ⟨ m≤n ⟩} rest))

holdToTone : Point → Point
holdToTone (tone p) = tone p
holdToTone (hold p) = tone p
holdToTone rest     = rest

firstHoldToTone : {n : ℕ} → Melody n → Melody n
firstHoldToTone (melody [])       = melody []
firstHoldToTone (melody (x ∷ xs)) = melody (holdToTone x ∷ xs)

-- Drop points, but convert any held tones to tones.
dropPoints : {n : ℕ} → (m : ℕ) → Melody (m + n) → Melody n
dropPoints m = firstHoldToTone ∘ melody ∘ drop m ∘ unmelody


data ⊥ : Set where

postulate
  ∞  : ∀ {a} → Set a → Set a
  ♯_ : ∀ {a} {A : Set a} → (A → ⊥) → ∞ A
  ♭  : ∀ {a} {A : Set a} → ∞ A → A → ⊥

{-# BUILTIN INFINITY ∞  #-}
{-# BUILTIN SHARP    ♯_ #-}
{-# BUILTIN FLAT     ♭  #-}

-- Issue #5610: with these bad types for sharp and flat, we can prove
-- false. The reason is that ∞ is assumed to be strictly-positive in
-- the positivity checker (even guarding positive). So making ∞ A
-- isomorphic to A → ⊥ will be contradictory to this assumption.

data D : Set where
  wrap : ∞ D → D

lam : (D → ⊥) → D
lam f = wrap (♯ f)

app : D → D → ⊥
app (wrap g) d = ♭ g d

delta : D
delta = lam (λ x → app x x)

omega : ⊥
omega = app delta delta


{-# OPTIONS --cubical --no-import-sorts --safe #-}
module Cubical.ZCohomology.Properties where

open import Cubical.ZCohomology.Base
open import Cubical.HITs.S1
open import Cubical.HITs.Sn
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Function
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Pointed
open import Cubical.Foundations.Transport
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.Univalence
open import Cubical.Data.Empty
open import Cubical.Data.Sigma hiding (_×_)
open import Cubical.HITs.Susp
open import Cubical.HITs.Wedge
open import Cubical.HITs.SetTruncation renaming (rec to sRec ; rec2 to sRec2 ; elim to sElim ; elim2 to sElim2 ; setTruncIsSet to §)
open import Cubical.Data.Int renaming (_+_ to _ℤ+_)
open import Cubical.Data.Nat
open import Cubical.HITs.Truncation renaming (elim to trElim ; map to trMap ; rec to trRec ; elim3 to trElim3)
open import Cubical.Homotopy.Loopspace
open import Cubical.Homotopy.Connected
open import Cubical.Homotopy.Freudenthal
open import Cubical.Algebra.Group
open import Cubical.Algebra.Semigroup
open import Cubical.Algebra.Monoid
open import Cubical.Foundations.Equiv.HalfAdjoint
open import Cubical.Data.NatMinusOne

open import Cubical.HITs.Pushout
open import Cubical.Data.Sum.Base
open import Cubical.Data.HomotopyGroup

open import Cubical.ZCohomology.KcompPrelims

open Iso renaming (inv to inv')

private
  variable
    ℓ ℓ' : Level
    A : Type ℓ
    B : Type ℓ'
    A' : Pointed ℓ

infixr 34 _+ₖ_
infixr 34 _+ₕ_

is2ConnectedKn : (n : ℕ) → isConnected 2 (coHomK (suc n))
is2ConnectedKn zero = ∣ ∣ base ∣ ∣
                    , trElim (λ _ → isOfHLevelPath 2 (isOfHLevelTrunc 2) _ _)
                        (trElim (λ _ → isOfHLevelPath 3 (isOfHLevelSuc 2 (isOfHLevelTrunc 2)) _ _)
                          (toPropElim (λ _ → isOfHLevelTrunc 2 _ _) refl))
is2ConnectedKn (suc n) = ∣ ∣ north ∣ ∣
                       , trElim (λ _ → isOfHLevelPath 2 (isOfHLevelTrunc 2) _ _)
                           (trElim (λ _ → isProp→isOfHLevelSuc (3 + n) (isOfHLevelTrunc 2 _ _))
                             (suspToPropElim (ptSn (suc n)) (λ _ → isOfHLevelTrunc 2 _ _) refl))

isConnectedKn : (n : ℕ) → isConnected (2 + n) (coHomK (suc n))
isConnectedKn n = isOfHLevelRetractFromIso 0 (invIso (truncOfTruncIso (2 + n) 1)) (sphereConnected (suc n))

-- Induction principles for cohomology groups
-- If we want to show a proposition about some x : Hⁿ(A), it suffices to show it under the
-- assumption that x = ∣f∣₂ and that f is pointed

coHomPointedElim : {A : Type ℓ} (n : ℕ) (a : A) {B : coHom (suc n) A → Type ℓ'}
                 → ((x : coHom (suc n) A) → isProp (B x))
                 → ((f : A → coHomK (suc n)) → f a ≡ coHom-pt (suc n) → B ∣ f ∣₂)
                 → (x : coHom (suc n) A) → B x
coHomPointedElim {ℓ' = ℓ'} {A = A} n a isprop indp =
  sElim (λ _ → isOfHLevelSuc 1 (isprop _))
         λ f → helper n isprop indp f (f a) refl
  where
  helper :  (n : ℕ) {B : coHom (suc n) A → Type ℓ'}
         → ((x : coHom (suc n) A) → isProp (B x))
         → ((f : A → coHomK (suc n)) → f a ≡ coHom-pt (suc n) → B ∣ f ∣₂)
         → (f : A → coHomK (suc n))
         → (x : coHomK (suc n))
         → f a ≡ x → B ∣ f ∣₂
  -- pattern matching a bit extra to avoid isOfHLevelPlus'
  helper zero isprop ind f =
    trElim (λ _ → isOfHLevelPlus {n = 1} 2 (isPropΠ λ _ → isprop _))
           (toPropElim (λ _ → isPropΠ λ _ → isprop _) (ind f))
  helper (suc zero) isprop ind f =
    trElim (λ _ → isOfHLevelPlus {n = 1} 3 (isPropΠ λ _ → isprop _))
           (suspToPropElim base (λ _ → isPropΠ λ _ → isprop _) (ind f))
  helper (suc (suc zero)) isprop ind f =
    trElim (λ _ → isOfHLevelPlus {n = 1} 4 (isPropΠ λ _ → isprop _))
           (suspToPropElim north (λ _ → isPropΠ λ _ → isprop _) (ind f))
  helper (suc (suc (suc n))) isprop ind f =
    trElim (λ _ → isOfHLevelPlus' {n = 5 + n} 1 (isPropΠ λ _ → isprop _))
           (suspToPropElim north (λ _ → isPropΠ λ _ → isprop _) (ind f))

coHomPointedElim2 : {A : Type ℓ} (n : ℕ) (a : A) {B : coHom (suc n) A → coHom (suc n) A → Type ℓ'}
                 → ((x y : coHom (suc n) A) → isProp (B x y))
                 → ((f g : A → coHomK (suc n)) → f a ≡ coHom-pt (suc n) → g a ≡ coHom-pt (suc n) → B ∣ f ∣₂ ∣ g ∣₂)
                 → (x y : coHom (suc n) A) → B x y
coHomPointedElim2 {ℓ' = ℓ'} {A = A} n a isprop indp = sElim2 (λ _ _ → isOfHLevelSuc 1 (isprop _ _))
                                                   λ f g → helper n a isprop indp f g (f a) (g a) refl refl
  where
  helper : (n : ℕ) (a : A) {B : coHom (suc n) A → coHom (suc n) A → Type ℓ'}
                 → ((x y : coHom (suc n) A) → isProp (B x y))
                 → ((f g : A → coHomK (suc n)) → f a ≡ coHom-pt (suc n) → g a ≡ coHom-pt (suc n) → B ∣ f ∣₂ ∣ g ∣₂)
                 → (f g : A → coHomK (suc n))
                 → (x y : coHomK (suc n))
                 → f a ≡ x → g a ≡ y
                 → B ∣ f ∣₂ ∣ g ∣₂
  helper zero a isprop indp f g =
    elim2 (λ _ _ → isOfHLevelPlus {n = 1} 2 (isPropΠ2 λ _ _ → isprop _ _))
          (toPropElim2 (λ _ _ → isPropΠ2 λ _ _ → isprop _ _) (indp f g))
  helper (suc zero) a isprop indp f g =
    elim2 (λ _ _ → isOfHLevelPlus {n = 1} 3 (isPropΠ2 λ _ _ → isprop _ _))
          (suspToPropElim2 base (λ _ _ → isPropΠ2 λ _ _ → isprop _ _) (indp f g))
  helper (suc (suc zero)) a isprop indp f g =
    elim2 (λ _ _ → isOfHLevelPlus {n = 1} 4 (isPropΠ2 λ _ _ → isprop _ _))
          (suspToPropElim2 north (λ _ _ → isPropΠ2 λ _ _ → isprop _ _) (indp f g))
  helper (suc (suc (suc n))) a isprop indp f g =
    elim2 (λ _ _ → isOfHLevelPlus' {n = 5 + n} 1 (isPropΠ2 λ _ _ → isprop _ _))
          (suspToPropElim2 north (λ _ _ → isPropΠ2 λ _ _ → isprop _ _) (indp f g))

{- Equivalence between cohomology of A and reduced cohomology of (A + 1) -}
coHomRed+1Equiv : (n : ℕ) →
                  (A : Type ℓ) →
                  (coHom n A) ≡ (coHomRed n ((A ⊎ Unit , inr (tt))))
coHomRed+1Equiv zero A i = ∥ helpLemma {C = (Int , pos 0)} i ∥₂
  module coHomRed+1 where
  helpLemma : {C : Pointed ℓ} → ( (A → (typ C)) ≡  ((((A ⊎ Unit) , inr (tt)) →∙ C)))
  helpLemma {C = C} = isoToPath (iso map1
                                     map2
                                     (λ b → linvPf b)
                                     (λ _  → refl))
    where
    map1 : (A → typ C) → ((((A ⊎ Unit) , inr (tt)) →∙ C))
    map1 f = map1' , refl
      module helpmap where
      map1' : A ⊎ Unit → fst C
      map1' (inl x) = f x
      map1' (inr x) = pt C

    map2 : ((((A ⊎ Unit) , inr (tt)) →∙ C)) → (A → typ C)
    map2 (g , pf) x = g (inl x)

    linvPf : (b :((((A ⊎ Unit) , inr (tt)) →∙ C))) →  map1 (map2 b) ≡ b
    linvPf (f , snd) i = (λ x → helper x i)  , λ j → snd ((~ i) ∨ j)
      where
      helper : (x : A ⊎ Unit) → ((helpmap.map1') (map2 (f , snd)) x) ≡ f x
      helper (inl x) = refl
      helper (inr tt) = sym snd
coHomRed+1Equiv (suc zero) A i = ∥ coHomRed+1.helpLemma A i {C = (coHomK 1 , ∣ base ∣)} i ∥₂
coHomRed+1Equiv (suc (suc n)) A i = ∥ coHomRed+1.helpLemma A i {C = (coHomK (2 + n) , ∣ north ∣)} i ∥₂

-----------

Kn→ΩKn+1 : (n : ℕ) → coHomK n → typ (Ω (coHomK-ptd (suc n)))
Kn→ΩKn+1 n = Iso.fun (Iso-Kn-ΩKn+1 n)

ΩKn+1→Kn : (n : ℕ) → typ (Ω (coHomK-ptd (suc n))) → coHomK n
ΩKn+1→Kn n = Iso.inv (Iso-Kn-ΩKn+1 n)

Kn≃ΩKn+1 : {n : ℕ} → coHomK n ≃ typ (Ω (coHomK-ptd (suc n)))
Kn≃ΩKn+1 {n = n} = isoToEquiv (Iso-Kn-ΩKn+1 n)

---------- Algebra/Group stuff --------

0ₖ : (n : ℕ) → coHomK n
0ₖ = coHom-pt

_+ₖ_ : {n : ℕ} → coHomK n → coHomK n → coHomK n
_+ₖ_ {n = n} x y  = ΩKn+1→Kn n (Kn→ΩKn+1 n x ∙ Kn→ΩKn+1 n y)

-ₖ_ : {n : ℕ} →  coHomK n → coHomK n
-ₖ_ {n = n} x = ΩKn+1→Kn n (sym (Kn→ΩKn+1 n x))

-- subtraction as a binary operator
_-ₖ_ : {n : ℕ} → coHomK n → coHomK n → coHomK n
_-ₖ_ {n = n} x y = ΩKn+1→Kn n (Kn→ΩKn+1 n x ∙ sym (Kn→ΩKn+1 n y))

+ₖ-syntax : (n : ℕ) → coHomK n → coHomK n → coHomK n
+ₖ-syntax n = _+ₖ_ {n = n}

-ₖ-syntax : (n : ℕ) → coHomK n → coHomK n
-ₖ-syntax n = -ₖ_ {n = n}

-'ₖ-syntax : (n : ℕ) → coHomK n → coHomK n → coHomK n
-'ₖ-syntax n = _-ₖ_ {n = n}

syntax +ₖ-syntax n x y = x +[ n ]ₖ y
syntax -ₖ-syntax n x = -[ n ]ₖ x
syntax -'ₖ-syntax n x y = x -[ n ]ₖ y

Kn→ΩKn+10ₖ : (n : ℕ) → Kn→ΩKn+1 n (0ₖ n) ≡ refl
Kn→ΩKn+10ₖ zero = sym (rUnit refl)
Kn→ΩKn+10ₖ (suc zero) i j = ∣ (rCancel (merid base) i j) ∣
Kn→ΩKn+10ₖ (suc (suc n)) i j = ∣ (rCancel (merid north) i j) ∣

ΩKn+1→Kn-refl : (n : ℕ) → ΩKn+1→Kn n refl ≡ 0ₖ n
ΩKn+1→Kn-refl zero = refl
ΩKn+1→Kn-refl (suc zero) = refl
ΩKn+1→Kn-refl (suc (suc zero)) = refl
ΩKn+1→Kn-refl (suc (suc (suc zero))) = refl
ΩKn+1→Kn-refl (suc (suc (suc (suc zero)))) = refl
ΩKn+1→Kn-refl (suc (suc (suc (suc (suc n))))) = refl

-0ₖ : {n : ℕ} → -[ n ]ₖ (0ₖ n) ≡ (0ₖ n)
-0ₖ {n = n} = (λ i → ΩKn+1→Kn n (sym (Kn→ΩKn+10ₖ n i)))
           ∙∙ (λ i → ΩKn+1→Kn n (Kn→ΩKn+10ₖ n (~ i)))
           ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 n) (0ₖ n)

+ₖ→∙ : (n : ℕ) (a b : coHomK n) → Kn→ΩKn+1 n (a +[ n ]ₖ b) ≡ Kn→ΩKn+1 n a ∙ Kn→ΩKn+1 n b
+ₖ→∙ n a b = Iso.rightInv (Iso-Kn-ΩKn+1 n) (Kn→ΩKn+1 n a ∙ Kn→ΩKn+1 n b)

lUnitₖ : (n : ℕ) (x : coHomK n) → (0ₖ n) +[ n ]ₖ x ≡ x
lUnitₖ 0 x = Iso.leftInv (Iso-Kn-ΩKn+1 zero) x
lUnitₖ (suc zero) = trElim (λ _ → isOfHLevelPath 3 (isOfHLevelTrunc 3) _ _) λ x → Iso.leftInv (Iso-Kn-ΩKn+1 1) ∣ x ∣
lUnitₖ (suc (suc n)) x =
  (λ i → ΩKn+1→Kn (2 + n) (Kn→ΩKn+10ₖ (2 + n) i ∙ Kn→ΩKn+1 (2 + n) x)) ∙∙
                       (cong (ΩKn+1→Kn (2 + n)) (sym (lUnit (Kn→ΩKn+1 (2 + n) x)))) ∙∙
                       Iso.leftInv (Iso-Kn-ΩKn+1 (2 + n)) x
rUnitₖ : (n : ℕ) (x : coHomK n) → x +[ n ]ₖ (0ₖ n) ≡ x
rUnitₖ 0 x = Iso.leftInv (Iso-Kn-ΩKn+1 zero) x
rUnitₖ (suc zero) = trElim (λ _ → isOfHLevelPath 3 (isOfHLevelTrunc 3) _ _) λ x → Iso.leftInv (Iso-Kn-ΩKn+1 1) ∣ x ∣
rUnitₖ (suc (suc n)) x =
    (λ i → ΩKn+1→Kn (2 + n) (Kn→ΩKn+1 (2 + n) x ∙ Kn→ΩKn+10ₖ (2 + n) i))
  ∙∙ (cong (ΩKn+1→Kn (2 + n)) (sym (rUnit (Kn→ΩKn+1 (2 + n) x))))
  ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 (2 + n)) x

rCancelₖ  : (n : ℕ) (x : coHomK n) → x +[ n ]ₖ (-[ n ]ₖ x) ≡ (0ₖ n)
rCancelₖ zero x = (λ i → ΩKn+1→Kn 0 (Kn→ΩKn+1 zero x ∙ Iso.rightInv (Iso-Kn-ΩKn+1 zero) (sym (Kn→ΩKn+1 zero x)) i)) ∙
                        cong (ΩKn+1→Kn 0) (rCancel (Kn→ΩKn+1 zero x))
rCancelₖ (suc n) x = (λ i → ΩKn+1→Kn (suc n) (Kn→ΩKn+1 (1 + n) x ∙ Iso.rightInv (Iso-Kn-ΩKn+1 (1 + n)) (sym (Kn→ΩKn+1 (1 + n) x)) i)) ∙
                               cong (ΩKn+1→Kn (suc n)) (rCancel (Kn→ΩKn+1 (1 + n) x)) ∙
                               (λ i → ΩKn+1→Kn (suc n) (Kn→ΩKn+10ₖ (suc n) (~ i))) ∙
                               Iso.leftInv (Iso-Kn-ΩKn+1 (suc n)) (0ₖ (suc n))

lCancelₖ : (n : ℕ) (x : coHomK n) → (-[ n ]ₖ x) +[ n ]ₖ x  ≡ (0ₖ n)
lCancelₖ 0 x = (λ i → ΩKn+1→Kn 0 (Iso.rightInv (Iso-Kn-ΩKn+1 zero) (sym (Kn→ΩKn+1 zero x)) i ∙ Kn→ΩKn+1 zero x)) ∙
                        cong (ΩKn+1→Kn 0) (lCancel (Kn→ΩKn+1 zero x))
lCancelₖ (suc n) x = (λ i → ΩKn+1→Kn (suc n) (Iso.rightInv (Iso-Kn-ΩKn+1 (1 + n)) (sym (Kn→ΩKn+1 (1 + n) x)) i ∙ Kn→ΩKn+1 (1 + n) x)) ∙
                               cong (ΩKn+1→Kn (suc n)) (lCancel (Kn→ΩKn+1 (1 + n) x)) ∙
                               (λ i → (ΩKn+1→Kn (suc n)) (Kn→ΩKn+10ₖ (suc n) (~ i))) ∙
                               Iso.leftInv (Iso-Kn-ΩKn+1 (suc n)) (0ₖ (suc n))

assocₖ : (n : ℕ) (x y z : coHomK n) → ((x +[ n ]ₖ y) +[ n ]ₖ z) ≡ (x +[ n ]ₖ (y +[ n ]ₖ z))
assocₖ n x y z = ((λ i → ΩKn+1→Kn n (Kn→ΩKn+1 n (ΩKn+1→Kn n (Kn→ΩKn+1 n x ∙ Kn→ΩKn+1 n y)) ∙ Kn→ΩKn+1 n z)) ∙∙
                          (λ i → ΩKn+1→Kn n (Iso.rightInv (Iso-Kn-ΩKn+1 n) (Kn→ΩKn+1 n x ∙ Kn→ΩKn+1 n y) i ∙ Kn→ΩKn+1 n z)) ∙∙
                          (λ i → ΩKn+1→Kn n (assoc (Kn→ΩKn+1 n x) (Kn→ΩKn+1 n y) (Kn→ΩKn+1 n z) (~ i)))) ∙
                          (λ i → ΩKn+1→Kn n ((Kn→ΩKn+1 n x) ∙ Iso.rightInv (Iso-Kn-ΩKn+1 n) ((Kn→ΩKn+1 n y ∙ Kn→ΩKn+1 n z)) (~ i)))

cancelₖ : (n : ℕ) (x : coHomK n) → x -[ n ]ₖ x ≡ (0ₖ n)
cancelₖ zero x = cong (ΩKn+1→Kn 0) (rCancel (Kn→ΩKn+1 zero x))
cancelₖ (suc zero) x = cong (ΩKn+1→Kn 1) (rCancel (Kn→ΩKn+1 1 x))
cancelₖ (suc (suc zero)) x = cong (ΩKn+1→Kn 2) (rCancel (Kn→ΩKn+1 2 x))
cancelₖ (suc (suc (suc zero))) x = cong (ΩKn+1→Kn 3) (rCancel (Kn→ΩKn+1 3 x))
cancelₖ (suc (suc (suc (suc zero)))) x = cong (ΩKn+1→Kn 4) (rCancel (Kn→ΩKn+1 4 x))
cancelₖ (suc (suc (suc (suc (suc n))))) x = cong (ΩKn+1→Kn (5 + n)) (rCancel (Kn→ΩKn+1 (5 + n) x))

-rUnitₖ : (n : ℕ) (x : coHomK n) → x -[ n ]ₖ 0ₖ n ≡ x
-rUnitₖ zero x = rUnitₖ zero x
-rUnitₖ (suc n) x = cong (λ y → ΩKn+1→Kn (suc n) (Kn→ΩKn+1 (suc n) x ∙ sym y)) (Kn→ΩKn+10ₖ (suc n))
                 ∙∙ cong (ΩKn+1→Kn (suc n)) (sym (rUnit (Kn→ΩKn+1 (suc n) x)))
                 ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 (suc n)) x

isComm∙ : ∀ {ℓ} (A : Pointed ℓ) → Type ℓ
isComm∙ A = (p q : typ (Ω A)) → p ∙ q ≡ q ∙ p

abstract
  isCommA→isCommTrunc : ∀ {ℓ} {A : Pointed ℓ} (n : ℕ) → isComm∙ A → isOfHLevel (suc n) (typ A) → isComm∙ (∥ typ A ∥ (suc n) , ∣ pt A ∣)
  isCommA→isCommTrunc {A = (A , a)} n comm hlev p q =
      ((λ i j → (Iso.leftInv (truncIdempotentIso (suc n) hlev) ((p ∙ q) j) (~ i)))
   ∙∙ (λ i → cong {B = λ _ → ∥ A ∥ (suc n) } (λ x → ∣ x ∣) (cong (trRec hlev (λ x → x)) (p ∙ q)))
   ∙∙ (λ i → cong {B = λ _ → ∥ A ∥ (suc n) } (λ x → ∣ x ∣) (congFunct {A = ∥ A ∥ (suc n)} {B = A} (trRec hlev (λ x → x)) p q i)))
   ∙ ((λ i → cong {B = λ _ → ∥ A ∥ (suc n) } (λ x → ∣ x ∣) (comm (cong (trRec hlev (λ x → x)) p) (cong (trRec hlev (λ x → x)) q) i))
   ∙∙ (λ i → cong {B = λ _ → ∥ A ∥ (suc n) } (λ x → ∣ x ∣) (congFunct {A = ∥ A ∥ (suc n)} {B = A} (trRec hlev (λ x → x)) q p (~ i)))
   ∙∙ (λ i j → (Iso.leftInv (truncIdempotentIso (suc n) hlev) ((q ∙ p) j) i)))

  isCommΩK1 : (n : ℕ) → isComm∙ ((Ω^ n) (coHomK-ptd 1))
  isCommΩK1 zero = isCommA→isCommTrunc 2 comm-ΩS¹ isGroupoidS¹
  isCommΩK1 (suc n) = Eckmann-Hilton n

  open Iso renaming (inv to inv')
  ptdIso→comm : ∀ {ℓ ℓ'} {A : Pointed ℓ} {B : Type ℓ'} (e : Iso (typ A) B) → isComm∙ A → isComm∙ (B , Iso.fun e (pt A))
  ptdIso→comm {A = (A , a)} {B = B} e comm p q =
         sym (rightInv (congIso e) (p ∙ q))
      ∙∙ (cong (fun (congIso e)) ((invCongFunct e p q)
                              ∙∙ (comm (inv' (congIso e) p) (inv' (congIso e) q))
                              ∙∙ (sym (invCongFunct e q p))))
      ∙∙ rightInv (congIso e) (q ∙ p)

  isCommΩK : (n : ℕ) → isComm∙ (coHomK-ptd n)
  isCommΩK zero p q = isSetInt _ _ (p ∙ q) (q ∙ p)
  isCommΩK (suc zero) = isCommA→isCommTrunc 2 comm-ΩS¹ isGroupoidS¹
  isCommΩK (suc (suc n)) = subst isComm∙ (λ i → coHomK (2 + n) , ΩKn+1→Kn-refl (2 + n) i) (ptdIso→comm {A = (_ , _)} (invIso (Iso-Kn-ΩKn+1 (2 + n))) (Eckmann-Hilton 0))

commₖ : (n : ℕ) (x y : coHomK n) → (x +[ n ]ₖ y) ≡ (y +[ n ]ₖ x)
commₖ 0 x y i = ΩKn+1→Kn 0 (isCommΩK1 0 (Kn→ΩKn+1 0 x) (Kn→ΩKn+1 0 y) i)
commₖ 1 x y i = ΩKn+1→Kn 1 (ptdIso→comm {A = ((∣ north ∣ ≡ ∣ north ∣) , snd ((Ω^ 1) (coHomK 3 , ∣ north ∣)))}
                                        {B = coHomK 2}
                                        (invIso (Iso-Kn-ΩKn+1 2)) (Eckmann-Hilton 0) (Kn→ΩKn+1 1 x) (Kn→ΩKn+1 1 y) i)
commₖ 2 x y i = ΩKn+1→Kn 2 (ptdIso→comm {A = (∣ north ∣ ≡ ∣ north ∣) , snd ((Ω^ 1) (coHomK 4 , ∣ north ∣))}
                                        {B = coHomK 3}
                                        (invIso (Iso-Kn-ΩKn+1 3)) (Eckmann-Hilton 0) (Kn→ΩKn+1 2 x) (Kn→ΩKn+1 2 y) i)
commₖ 3 x y i = ΩKn+1→Kn 3 (ptdIso→comm {A = (∣ north ∣ ≡ ∣ north ∣) , snd ((Ω^ 1) (coHomK 5 , ∣ north ∣))}
                                        {B = coHomK 4}
                                        (invIso (Iso-Kn-ΩKn+1 4)) (Eckmann-Hilton 0) (Kn→ΩKn+1 3 x) (Kn→ΩKn+1 3 y) i)
commₖ (suc (suc (suc (suc n)))) x y i =
  ΩKn+1→Kn (4 + n) (ptdIso→comm {A = (∣ north ∣ ≡ ∣ north ∣) , snd ((Ω^ 1) (coHomK (6 + n) , ∣ north ∣))}
                                {B = coHomK (5 + n)}
                                (invIso (Iso-Kn-ΩKn+1 (5 + n))) (Eckmann-Hilton 0) (Kn→ΩKn+1 (4 + n) x) (Kn→ΩKn+1 (4 + n) y) i)

rUnitₖ' : (n : ℕ) (x : coHomK n) → x +[ n ]ₖ (0ₖ n) ≡ x
rUnitₖ' n x = commₖ n x (0ₖ n) ∙ lUnitₖ n x

-distrₖ : (n : ℕ) (x y : coHomK n) → -[ n ]ₖ (x +[ n ]ₖ y) ≡ (-[ n ]ₖ x) +[ n ]ₖ (-[ n ]ₖ y)
-distrₖ n x y = ((λ i → ΩKn+1→Kn n (sym (Kn→ΩKn+1 n (ΩKn+1→Kn n (Kn→ΩKn+1 n x ∙ Kn→ΩKn+1 n y))))) ∙∙
                      (λ i → ΩKn+1→Kn n (sym (Iso.rightInv (Iso-Kn-ΩKn+1 n) (Kn→ΩKn+1 n x ∙ Kn→ΩKn+1 n y) i))) ∙∙
                      (λ i → ΩKn+1→Kn n (symDistr (Kn→ΩKn+1 n x) (Kn→ΩKn+1 n y) i))) ∙∙
                      (λ i → ΩKn+1→Kn n (Iso.rightInv (Iso-Kn-ΩKn+1 n) (sym (Kn→ΩKn+1 n y)) (~ i) ∙ (Iso.rightInv (Iso-Kn-ΩKn+1 n) (sym (Kn→ΩKn+1 n x)) (~ i)))) ∙∙
                      commₖ n (-[ n ]ₖ y) (-[ n ]ₖ x)

private
  rCancelLem : (n : ℕ) (x : coHomK n) → ΩKn+1→Kn n ((Kn→ΩKn+1 n x) ∙ refl) ≡ ΩKn+1→Kn n (Kn→ΩKn+1 n x)
  rCancelLem zero x = refl
  rCancelLem (suc n) x = cong (ΩKn+1→Kn (suc n)) (sym (rUnit (Kn→ΩKn+1 (suc n) x)))

  lCancelLem : (n : ℕ) (x : coHomK n) → ΩKn+1→Kn n (refl ∙ (Kn→ΩKn+1 n x)) ≡ ΩKn+1→Kn n (Kn→ΩKn+1 n x)
  lCancelLem zero x = refl
  lCancelLem (suc n) x = cong (ΩKn+1→Kn (suc n)) (sym (lUnit (Kn→ΩKn+1 (suc n) x)))

-cancelRₖ : (n : ℕ) (x y : coHomK n) → (y +[ n ]ₖ x) -[ n ]ₖ x ≡ y
-cancelRₖ n x y = (cong (ΩKn+1→Kn n) ((cong (_∙ sym (Kn→ΩKn+1 n x)) (+ₖ→∙ n y x))
                                  ∙∙ sym (assoc _ _ _)
                                  ∙∙ cong (Kn→ΩKn+1 n y ∙_) (rCancel _)))
                   ∙∙ rCancelLem n y
                   ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 n) y

-cancelLₖ : (n : ℕ) (x y : coHomK n) → (x +[ n ]ₖ y) -[ n ]ₖ x ≡ y
-cancelLₖ n x y = cong (λ z → z -[ n ]ₖ x) (commₖ n x y) ∙ -cancelRₖ n x y

-+cancelₖ : (n : ℕ) (x y : coHomK n) → (x -[ n ]ₖ y) +[ n ]ₖ y ≡ x
-+cancelₖ n x y = (cong (ΩKn+1→Kn n) ((cong (_∙ (Kn→ΩKn+1 n y)) (Iso.rightInv (Iso-Kn-ΩKn+1 n) (Kn→ΩKn+1 n x ∙ sym (Kn→ΩKn+1 n y))))
                                  ∙∙ sym (assoc _ _ _)
                                  ∙∙ cong (Kn→ΩKn+1 n x ∙_) (lCancel _)))
                   ∙∙ rCancelLem n x
                   ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 n) x

---- Group structure of cohomology groups ---

_+ₕ_ : {n : ℕ} → coHom n A → coHom n A → coHom n A
_+ₕ_ {n = n} = sRec2 § λ a b → ∣ (λ x → a x +[ n ]ₖ b x) ∣₂

-ₕ_  : {n : ℕ} → coHom n A → coHom n A
-ₕ_  {n = n} = sRec § λ a → ∣ (λ x → -[ n ]ₖ a x) ∣₂

_-ₕ_  : {n : ℕ} → coHom n A → coHom n A → coHom n A
_-ₕ_  {n = n} = sRec2 § λ a b → ∣ (λ x → a x -[ n ]ₖ b x) ∣₂

+ₕ-syntax : (n : ℕ) → coHom n A → coHom n A → coHom n A
+ₕ-syntax n = _+ₕ_ {n = n}

-ₕ-syntax : (n : ℕ) → coHom n A → coHom n A
-ₕ-syntax n = -ₕ_ {n = n}

-ₕ'-syntax : (n : ℕ) → coHom n A → coHom n A → coHom n A
-ₕ'-syntax n = _-ₕ_ {n = n}

syntax +ₕ-syntax n x y = x +[ n ]ₕ y
syntax -ₕ-syntax n x = -[ n ]ₕ x
syntax -ₕ'-syntax n x y = x -[ n ]ₕ y

0ₕ : (n : ℕ) → coHom n A
0ₕ n = ∣ (λ _ → (0ₖ n)) ∣₂

rUnitₕ : (n : ℕ) (x : coHom n A) → x +[ n ]ₕ (0ₕ n) ≡ x
rUnitₕ n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                λ a i → ∣ funExt (λ x → rUnitₖ n (a x)) i ∣₂

lUnitₕ : (n : ℕ) (x : coHom n A) → (0ₕ n) +[ n ]ₕ x ≡ x
lUnitₕ n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                  λ a i → ∣ funExt (λ x → lUnitₖ n (a x)) i ∣₂

rCancelₕ : (n : ℕ) (x : coHom n A) → x +[ n ]ₕ (-[ n ]ₕ x) ≡ 0ₕ n
rCancelₕ n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                 λ a i → ∣ funExt (λ x → rCancelₖ n (a x)) i ∣₂

lCancelₕ : (n : ℕ) (x : coHom n A) → (-[ n ]ₕ x) +[ n ]ₕ x  ≡ 0ₕ n
lCancelₕ n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                 λ a i → ∣ funExt (λ x → lCancelₖ n (a x)) i ∣₂

assocₕ : (n : ℕ) (x y z : coHom n A) → ((x +[ n ]ₕ y) +[ n ]ₕ z) ≡ (x +[ n ]ₕ (y +[ n ]ₕ z))
assocₕ n = elim3 (λ _ _ _ → isOfHLevelPath 1 (§ _ _))
               λ a b c i → ∣ funExt (λ x → assocₖ n (a x) (b x) (c x)) i ∣₂

commₕ : (n : ℕ) (x y : coHom n A) → (x +[ n ]ₕ y) ≡ (y +[ n ]ₕ x)
commₕ n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                        λ a b i → ∣ funExt (λ x → commₖ n (a x) (b x)) i ∣₂

cancelₕ : (n : ℕ) (x : coHom n A) → x -[ n ]ₕ x ≡ 0ₕ n
cancelₕ n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                   λ a i → ∣ funExt (λ x → cancelₖ n (a x)) i ∣₂

-ₖ-ₖ : (n : ℕ) (x : coHomK n) → (-[ n ]ₖ (-[ n ]ₖ x)) ≡ x
-ₖ-ₖ n x = cong ((ΩKn+1→Kn n) ∘ sym) (Iso.rightInv (Iso-Kn-ΩKn+1 n) (sym (Kn→ΩKn+1 n x))) ∙ Iso.leftInv (Iso-Kn-ΩKn+1 n) x

-- Proof that rUnitₖ and lUnitₖ agree on 0ₖ. Needed for Mayer-Vietoris.
private
  rUnitlUnitGen : ∀ {ℓ ℓ'} {A : Type ℓ} {B : Type ℓ'} {b : B} (e : Iso A (b ≡ b))
                  (0A : A)
                  (0fun : fun e 0A ≡ refl)
                → Path (inv' e (fun e 0A ∙ fun e 0A) ≡ 0A)
                       (cong (inv' e) (cong (_∙ fun e 0A) 0fun) ∙∙ cong (inv' e) (sym (lUnit (fun e 0A))) ∙∙ Iso.leftInv e 0A)
                       (cong (inv' e) (cong (fun e 0A ∙_) 0fun) ∙∙ cong (inv' e) (sym (rUnit (fun e 0A))) ∙∙ Iso.leftInv e 0A)
  rUnitlUnitGen e 0A 0fun =
      (λ i → cong (inv' e) (cong (_∙ fun e 0A) 0fun) ∙∙ rUnit (cong (inv' e) (sym (lUnit (fun e 0A)))) i ∙∙ Iso.leftInv e 0A)
    ∙ ((λ i → (λ j → inv' e (0fun (~ i ∧ j) ∙ 0fun (j ∧ i)))
            ∙∙ ((λ j → inv' e (0fun (~ i ∨ j) ∙ 0fun i))
            ∙∙ cong (inv' e) (sym (lUnit (0fun i)))
            ∙∙ λ j → inv' e (0fun (i ∧ (~ j))))
            ∙∙ Iso.leftInv e 0A)
    ∙∙ (λ i → (λ j → inv' e (fun e 0A ∙ 0fun j))
            ∙∙ (λ j → inv' e (0fun (j ∧ ~ i) ∙ refl))
            ∙∙ cong (inv' e) (sym (rUnit (0fun (~ i))))
            ∙∙ (λ j → inv' e (0fun (~ i ∧ ~ j)))
            ∙∙ Iso.leftInv e 0A)
    ∙∙ λ i → cong (inv' e) (cong (fun e 0A ∙_) 0fun)
           ∙∙ rUnit (cong (inv' e) (sym (rUnit (fun e 0A)))) (~ i)
           ∙∙ Iso.leftInv e 0A)

rUnitlUnit0 : (n : ℕ) → rUnitₖ n (0ₖ n) ≡ lUnitₖ n (0ₖ n)
rUnitlUnit0 0 = refl
rUnitlUnit0 (suc zero) = refl
rUnitlUnit0 (suc (suc n)) = sym (rUnitlUnitGen (Iso-Kn-ΩKn+1 (2 + n)) (0ₖ (2 + n)) (Kn→ΩKn+10ₖ (2 + n)))

-cancelLₕ : (n : ℕ) (x y : coHom n A) → (x +[ n ]ₕ y) -[ n ]ₕ x ≡ y
-cancelLₕ n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                     λ a b i → ∣ (λ x → -cancelLₖ n (a x) (b x) i) ∣₂

-cancelRₕ : (n : ℕ) (x y : coHom n A) → (y +[ n ]ₕ x) -[ n ]ₕ x ≡ y
-cancelRₕ n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                     λ a b i → ∣ (λ x → -cancelRₖ n (a x) (b x) i) ∣₂

-+cancelₕ : (n : ℕ) (x y : coHom n A) → (x -[ n ]ₕ y) +[ n ]ₕ y ≡ x
-+cancelₕ n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                     λ a b i → ∣ (λ x → -+cancelₖ n (a x) (b x) i) ∣₂

-- Group structure of reduced cohomology groups (in progress - might need K to compute properly first) ---

+ₕ∙ : {A : Pointed ℓ} (n : ℕ) → coHomRed n A → coHomRed n A → coHomRed n A
+ₕ∙ zero = sRec2 § λ { (a , pa) (b , pb) → ∣ (λ x → a x +[ zero ]ₖ b x) , (λ i → (pa i +[ zero ]ₖ pb i)) ∣₂ }
+ₕ∙ (suc zero) = sRec2 § λ { (a , pa) (b , pb) → ∣ (λ x → a x +[ 1 ]ₖ b x) , (λ i → pa i +[ 1 ]ₖ pb i) ∙ lUnitₖ 1 (0ₖ 1) ∣₂ }
+ₕ∙ (suc (suc n)) = sRec2 § λ { (a , pa) (b , pb) → ∣ (λ x → a x +[ (2 + n) ]ₖ b x) , (λ i → pa i +[ (2 + n) ]ₖ pb i) ∙ lUnitₖ (2 + n) (0ₖ (2 + n)) ∣₂ }

open IsSemigroup
open IsMonoid
open GroupStr
open GroupHom

coHomGr : ∀ {ℓ} (n : ℕ) (A : Type ℓ) → Group {ℓ}
coHomGr n A = coHom n A , coHomGrnA
  where
  coHomGrnA : GroupStr (coHom n A)
  0g coHomGrnA = 0ₕ n
  GroupStr._+_ coHomGrnA = λ x y → x +[ n ]ₕ y
  - coHomGrnA = λ x → -[ n ]ₕ x
  isGroup coHomGrnA = helper
    where
    abstract
      helper : IsGroup (0ₕ n) (λ x y → x +[ n ]ₕ y) (λ x → -[ n ]ₕ x)
      helper = makeIsGroup § (λ x y z → sym (assocₕ n x y z)) (rUnitₕ n) (lUnitₕ n) (rCancelₕ n) (lCancelₕ n)

×coHomGr : (n : ℕ) (A : Type ℓ) (B : Type ℓ') → Group
×coHomGr n A B = dirProd (coHomGr n A) (coHomGr n B)

coHomFun : ∀ {ℓ ℓ'} {A : Type ℓ} {B : Type ℓ'} (n : ℕ) (f : A → B) → coHom n B → coHom n A
coHomFun n f = sRec § λ β → ∣ β ∘ f ∣₂

-distrLemma : ∀ {ℓ ℓ'} {A : Type ℓ} {B : Type ℓ'} (n m : ℕ) (f : GroupHom (coHomGr n A) (coHomGr m B))
              (x y : coHom n A)
            → fun f (x -[ n ]ₕ y) ≡ fun f x -[ m ]ₕ fun f y
-distrLemma n m f' x y = sym (-cancelRₕ m (f y) (f (x -[ n ]ₕ y)))
                     ∙∙ cong (λ x → x -[ m ]ₕ f y) (sym (isHom f' (x -[ n ]ₕ y) y))
                     ∙∙ cong (λ x → x -[ m ]ₕ f y) ( cong f (-+cancelₕ n _ _))
  where
  f = fun f'

--- the loopspace of Kₙ is commutative regardless of base

addIso : (n : ℕ) (x : coHomK n) → Iso (coHomK n) (coHomK n)
fun (addIso n x) y = y +[ n ]ₖ x
inv' (addIso n x) y = y -[ n ]ₖ x
rightInv (addIso n x) y = -+cancelₖ n y x
leftInv (addIso n x) y = -cancelRₖ n x y

isCommΩK-based : (n : ℕ) (x : coHomK n) → isComm∙ (coHomK n , x)
isCommΩK-based zero x p q = isSetInt _ _ (p ∙ q) (q ∙ p)
isCommΩK-based (suc zero) x =
  subst isComm∙ (λ i → coHomK 1 , lUnitₖ 1 x i)
                (ptdIso→comm {A = (_ , 0ₖ 1)} (addIso 1 x)
                              (isCommΩK 1))
isCommΩK-based (suc (suc n)) x =
  subst isComm∙ (λ i → coHomK (suc (suc n)) , lUnitₖ (suc (suc n)) x i)
                (ptdIso→comm {A = (_ , 0ₖ (suc (suc n)))} (addIso (suc (suc n)) x)
                              (isCommΩK (suc (suc n))))

addLemma : (a b : Int) → a +[ 0 ]ₖ b ≡ (a ℤ+ b)
addLemma a b = (cong (ΩKn+1→Kn 0) (sym (congFunct ∣_∣ (intLoop a) (intLoop b))))
            ∙∙ (λ i → ΩKn+1→Kn 0 (cong ∣_∣ (intLoop-hom a b i)))
            ∙∙ Iso.leftInv (Iso-Kn-ΩKn+1 0) (a ℤ+ b)

---
-- hidden versions of cohom stuff using the "lock" hack. The locked versions can be used when proving things.
-- Swapping "key" for "tt*" will then give computing functions.

Unit' : Type₀
Unit' = lockUnit {ℓ-zero}

lock : ∀ {ℓ} {A : Type ℓ} → Unit' → A → A
lock unlock = λ x → x

module lockedCohom (key : Unit') where
  +K : (n : ℕ) → coHomK n → coHomK n → coHomK n
  +K n = lock key (_+ₖ_ {n = n})

  -K : (n : ℕ) → coHomK n → coHomK n
  -K n = lock key (-ₖ_ {n = n})

  -Kbin : (n : ℕ) → coHomK n → coHomK n → coHomK n
  -Kbin n = lock key (_-ₖ_ {n = n})

  rUnitK : (n : ℕ) (x : coHomK n) → +K n x (0ₖ n) ≡ x
  rUnitK n x = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) x (0ₖ n) ≡ x
    pm unlock = rUnitₖ n x

  lUnitK : (n : ℕ) (x : coHomK n) → +K n (0ₖ n) x ≡ x
  lUnitK n x = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) (0ₖ n) x ≡ x
    pm unlock = lUnitₖ n x

  rCancelK : (n : ℕ) (x : coHomK n) → +K n x (-K n x) ≡ 0ₖ n
  rCancelK n x = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) x (lock t (-ₖ_ {n = n}) x) ≡ 0ₖ n
    pm unlock = rCancelₖ n x

  lCancelK : (n : ℕ) (x : coHomK n) → +K n (-K n x) x ≡ 0ₖ n
  lCancelK n x = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) (lock t (-ₖ_ {n = n}) x) x ≡ 0ₖ n
    pm unlock = lCancelₖ n x

  -cancelRK : (n : ℕ) (x y : coHomK n) → -Kbin n (+K n y x) x ≡ y
  -cancelRK n x y = pm key
    where
    pm : (t : Unit') → lock t (_-ₖ_ {n = n}) (lock t (_+ₖ_ {n = n}) y x) x ≡ y
    pm unlock = -cancelRₖ n x y

  -cancelLK : (n : ℕ) (x y : coHomK n) → -Kbin n (+K n x y) x ≡ y
  -cancelLK n x y = pm key
    where
    pm : (t : Unit') → lock t (_-ₖ_ {n = n}) (lock t (_+ₖ_ {n = n}) x y) x ≡ y
    pm unlock = -cancelLₖ n x y

  -+cancelK : (n : ℕ) (x y : coHomK n) → +K n (-Kbin n x y) y ≡ x
  -+cancelK n x y = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) (lock t (_-ₖ_ {n = n}) x y) y ≡ x
    pm unlock = -+cancelₖ n x y

  cancelK : (n : ℕ) (x : coHomK n) → -Kbin n x x ≡ 0ₖ n
  cancelK n x = pm key
    where
    pm : (t : Unit') → (lock t (_-ₖ_ {n = n}) x x) ≡ 0ₖ n
    pm unlock = cancelₖ n x

  assocK : (n : ℕ) (x y z : coHomK n) → +K n (+K n x y) z ≡ +K n x (+K n y z)
  assocK n x y z = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) (lock t (_+ₖ_ {n = n}) x y) z
                     ≡ lock t (_+ₖ_ {n = n}) x (lock t (_+ₖ_ {n = n}) y z)
    pm unlock = assocₖ n x y z

  commK : (n : ℕ) (x y : coHomK n) → +K n x y ≡ +K n y x
  commK n x y = pm key
    where
    pm : (t : Unit') → lock t (_+ₖ_ {n = n}) x y ≡ lock t (_+ₖ_ {n = n}) y x
    pm unlock = commₖ n x y

  -- cohom

  +H : (n : ℕ) (x y : coHom n A) → coHom n A
  +H n = sRec2 § λ a b → ∣ (λ x → +K n (a x) (b x)) ∣₂

  -H : (n : ℕ) (x : coHom n A) → coHom n A
  -H n = sRec § λ a → ∣ (λ x → -K n (a x)) ∣₂

  -Hbin : (n : ℕ) → coHom n A → coHom n A → coHom n A
  -Hbin n = sRec2 § λ a b → ∣ (λ x → -Kbin n (a x) (b x)) ∣₂

  rUnitH : (n : ℕ) (x : coHom n A) → +H n x (0ₕ n) ≡ x
  rUnitH n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                  λ a i → ∣ funExt (λ x → rUnitK n (a x)) i ∣₂

  lUnitH : (n : ℕ) (x : coHom n A) → +H n (0ₕ n) x ≡ x
  lUnitH n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                    λ a i → ∣ funExt (λ x → lUnitK n (a x)) i ∣₂

  rCancelH : (n : ℕ) (x : coHom n A) → +H n x (-H n x) ≡ 0ₕ n
  rCancelH n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                   λ a i → ∣ funExt (λ x → rCancelK n (a x)) i ∣₂

  lCancelH : (n : ℕ) (x : coHom n A) → +H n (-H n x) x  ≡ 0ₕ n
  lCancelH n = sElim (λ _ → isOfHLevelPath 1 (§ _ _))
                   λ a i → ∣ funExt (λ x → lCancelK n (a x)) i ∣₂

  assocH : (n : ℕ) (x y z : coHom n A) → (+H n (+H n x y) z) ≡ (+H n x (+H n y z))
  assocH n = elim3 (λ _ _ _ → isOfHLevelPath 1 (§ _ _))
                 λ a b c i → ∣ funExt (λ x → assocK n (a x) (b x) (c x)) i ∣₂

  commH : (n : ℕ) (x y : coHom n A) → (+H n x y) ≡ (+H n y x)
  commH n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                          λ a b i → ∣ funExt (λ x → commK n (a x) (b x)) i ∣₂

  -cancelRH : (n : ℕ) (x y : coHom n A) → -Hbin n (+H n y x) x ≡ y
  -cancelRH n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                        λ a b i → ∣ (λ x → -cancelRK n (a x) (b x) i) ∣₂

  -cancelLH : (n : ℕ) (x y : coHom n A) → -Hbin n (+H n x y) x ≡ y
  -cancelLH n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                        λ a b i → ∣ (λ x → -cancelLK n (a x) (b x) i) ∣₂

  -+cancelH : (n : ℕ) (x y : coHom n A) → +H n (-Hbin n x y) y ≡ x
  -+cancelH n = sElim2 (λ _ _ → isOfHLevelPath 1 (§ _ _))
                        λ a b i → ∣ (λ x → -+cancelK n (a x) (b x) i) ∣₂

+K→∙ : (key : Unit') (n : ℕ) (a b : coHomK n) → Kn→ΩKn+1 n (lockedCohom.+K key n a b) ≡ Kn→ΩKn+1 n a ∙ Kn→ΩKn+1 n b
+K→∙ unlock = +ₖ→∙

+H≡+ₕ : (key : Unit') (n : ℕ) → lockedCohom.+H key {A = A} n ≡ _+ₕ_ {n = n}
+H≡+ₕ unlock _ = refl

rUnitlUnit0K : (key : Unit') (n : ℕ) → lockedCohom.rUnitK key n (0ₖ n) ≡ lockedCohom.lUnitK key n (0ₖ n)
rUnitlUnit0K unlock = rUnitlUnit0


-- Andreas, 2018-11-23, issue #3304, report and test case by Nisse

open import Agda.Builtin.Equality
open import Agda.Builtin.Sigma

map : {A B : Set} {P : A → Set} {Q : B → Set} →
      (f : A → B) → (∀ {x} → P x → Q (f x)) →
      Σ A P → Σ B Q
map f g (x , y) = (f x , g y)

postulate
  F         : Set → Set → Set
  apply     : {A B : Set} → F A B → A → B
  construct : {A B : Set} → (A → B) → F A B
  A         : Set
  B         : A → Set
  f         : A → A
  g         : ∀ {x} → B x → B (f x)

mutual

  k : Σ A B → Σ A _
  k = map f g

  h : F (Σ A B) (Σ A B)
  h = construct k

P : ∀ {x y} → k x ≡ k y → Set₁
P refl = Set

-- WAS: internal error in
-- TypeChecking.Rules.Term.catchIlltypedPatternBlockedOnMeta
--
-- EXPECTED:
-- I'm not sure if there should be a case for the constructor refl,
-- because I get stuck when trying to solve the following unification
-- problems (inferred index ≟ expected index):
--   k x ≟ k y
-- when checking that the pattern refl has type k x ≡ k y