{-# OPTIONS --without-K #-}
module well-typed-syntax-pre-interpreter where
open import common public
open import well-typed-syntax
open import well-typed-syntax-helpers

max-level : Level
max-level = lsuc lzero

module inner
  (context-pick-if' : ∀ ℓ (P : Context → Set ℓ)
                          (Γ : Context)
                          (dummy : P (ε ▻ ‘Σ’ ‘Context’ ‘Typ’))
                          (val : P Γ) →
                        P (ε ▻ ‘Σ’ ‘Context’ ‘Typ’))
  (context-pick-if-refl' : ∀ ℓ P dummy val →
                            context-pick-if' ℓ P (ε ▻ ‘Σ’ ‘Context’ ‘Typ’) dummy val ≡ val)
  where

  context-pick-if : ∀ {ℓ} {P : Context → Set ℓ}
                          {Γ : Context}
                          (dummy : P (ε ▻ ‘Σ’ ‘Context’ ‘Typ’))
                          (val : P Γ) →
                        P (ε ▻ ‘Σ’ ‘Context’ ‘Typ’)
  context-pick-if {P = P} dummy val = context-pick-if' _ P _ dummy val
  context-pick-if-refl : ∀ {ℓ P dummy val} →
                            context-pick-if {ℓ} {P} {ε ▻ ‘Σ’ ‘Context’ ‘Typ’} dummy val ≡ val
  context-pick-if-refl {P = P} = context-pick-if-refl' _ P _ _

  private
    dummy : Typ ε
    dummy = ‘Context’

  cast-helper : ∀ {X T A} {x : Term X} → A ≡ T → Term {ε} (T ‘’ x ‘→'’ A ‘’ x)
  cast-helper refl = ‘λ∙’ ‘VAR₀’

  cast'-proof : ∀ {T} → Term {ε} (context-pick-if {P = Typ} (W dummy) T ‘’ ‘existT’ ⌜ ε ▻ ‘Σ’ ‘Context’ ‘Typ’ ⌝c ⌜ T ⌝T
                ‘→'’ T ‘’ ‘existT’ ⌜ ε ▻ ‘Σ’ ‘Context’ ‘Typ’ ⌝c ⌜ T ⌝T)
  cast'-proof {T} = cast-helper {‘Σ’ ‘Context’ ‘Typ’}
                      {context-pick-if {P = Typ} {ε ▻ ‘Σ’ ‘Context’ ‘Typ’} (W dummy) T}
                      {T} (sym (context-pick-if-refl {P = Typ} {dummy = W dummy}))

  cast-proof : ∀ {T} → Term {ε} (T ‘’ ‘existT’ ⌜ ε ▻ ‘Σ’ ‘Context’ ‘Typ’ ⌝c ⌜ T ⌝T
                ‘→'’ context-pick-if {P = Typ} (W dummy) T ‘’ ‘existT’ ⌜ ε ▻ ‘Σ’ ‘Context’ ‘Typ’ ⌝c ⌜ T ⌝T)
  cast-proof {T} = cast-helper {‘Σ’ ‘Context’ ‘Typ’} {T}
                      {context-pick-if {P = Typ} {ε ▻ ‘Σ’ ‘Context’ ‘Typ’} (W dummy) T}
                      (context-pick-if-refl {P = Typ} {dummy = W dummy})

  ‘idfun’ : ∀ {T} → Term {ε} (T ‘→'’ T)
  ‘idfun’ = ‘λ∙’ ‘VAR₀’

  mutual
    Context⇓ : (Γ : Context) → Set (lsuc max-level)
    Typ⇓ : {Γ : Context} → Typ Γ → Context⇓ Γ → Set max-level

    Context⇓ ε = ⊤
    Context⇓ (Γ ▻ T) = Σ (Context⇓ Γ) (λ Γ' → Typ⇓ T Γ')

    Typ⇓ (T₁ ‘’ x) Γ⇓ = Typ⇓ T₁ (Γ⇓ , Term⇓ x Γ⇓)
    Typ⇓ (T₂ ‘’₁ a) (Γ⇓ , A⇓) = Typ⇓ T₂ ((Γ⇓ , Term⇓ a Γ⇓) , A⇓)
    Typ⇓ (T₃ ‘’₂ a) ((Γ⇓ , A⇓) , B⇓) = Typ⇓ T₃ (((Γ⇓ , Term⇓ a Γ⇓) , A⇓) , B⇓)
    Typ⇓ (T₃ ‘’₃ a) (((Γ⇓ , A⇓) , B⇓) , C⇓) = Typ⇓ T₃ ((((Γ⇓ , Term⇓ a Γ⇓) , A⇓) , B⇓) , C⇓)
    Typ⇓ (W T₁) (Γ⇓ , _) = Typ⇓ T₁ Γ⇓
    Typ⇓ (W1 T₂) ((Γ⇓ , A⇓) , B⇓) = Typ⇓ T₂ (Γ⇓ , B⇓)
    Typ⇓ (W2 T₃) (((Γ⇓ , A⇓) , B⇓) , C⇓) = Typ⇓ T₃ ((Γ⇓ , B⇓) , C⇓)
    Typ⇓ (T ‘→’ T₁) Γ⇓ = (T⇓ : Typ⇓ T Γ⇓) → Typ⇓ T₁ (Γ⇓ , T⇓)
    Typ⇓ ‘Context’ Γ⇓ = Lifted Context
    Typ⇓ ‘Typ’ (Γ⇓ , T⇓) = Lifted (Typ (lower T⇓))
    Typ⇓ ‘Term’ (Γ⇓ , T⇓ , t⇓) = Lifted (Term (lower t⇓))
    Typ⇓ (‘Σ’ T T₁) Γ⇓ = Σ (Typ⇓ T Γ⇓) (λ T⇓ → Typ⇓ T₁ (Γ⇓ , T⇓))

    Term⇓ : ∀ {Γ : Context} {T : Typ Γ} → Term T → (Γ⇓ : Context⇓ Γ) → Typ⇓ T Γ⇓
    Term⇓ (w t) (Γ⇓ , A⇓) = Term⇓ t Γ⇓
    Term⇓ (‘λ∙’ t) Γ⇓ T⇓ = Term⇓ t (Γ⇓ , T⇓)
    Term⇓ (t ‘’ₐ t₁) Γ⇓ = Term⇓ t Γ⇓ (Term⇓ t₁ Γ⇓)
    Term⇓ ‘VAR₀’ (Γ⇓ , A⇓) = A⇓
    Term⇓ (⌜ Γ ⌝c) Γ⇓ = lift Γ
    Term⇓ (⌜ T ⌝T) Γ⇓ = lift T
    Term⇓ (⌜ t ⌝t) Γ⇓ = lift t
    Term⇓ ‘quote-term’ Γ⇓ (lift T⇓) = lift ⌜ T⇓ ⌝t
    Term⇓ (‘quote-sigma’ {Γ₀} {Γ₁}) Γ⇓ (lift Γ , lift T) = lift (‘existT’ {Γ₁} ⌜ Γ ⌝c ⌜ T ⌝T)
    Term⇓ ‘cast’ Γ⇓ T⇓ = lift (context-pick-if
                              {P = Typ}
                              {lower (Σ.proj₁ T⇓)}
                              (W dummy)
                              (lower (Σ.proj₂ T⇓)))
    Term⇓ (SW t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (substTyp-weakenTyp1-VAR₀ t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (weakenTyp-tProd-inv t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (weakenTyp-weakenTyp-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (substTyp1-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (weakenTyp1-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (substTyp2-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (substTyp1-substTyp-weakenTyp-inv t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp1-substTyp-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-weakenTyp-substTyp1-substTyp-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp2-substTyp1-substTyp-weakenTyp-inv t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp2-substTyp1-substTyp-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp2-substTyp1-substTyp-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (weakenTyp2-weakenTyp1 t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp1-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp1-weakenTyp-inv t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp1-weakenTyp1-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp1-weakenTyp1 t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp1-substTyp-weakenTyp1-inv t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp1-substTyp-weakenTyp1 t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp-substTyp-weakenTyp1 t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp-substTyp-weakenTyp1-inv t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp-weakenTyp1-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp3-substTyp2-substTyp1-substTyp-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-substTyp2-substTyp1-substTyp-weakenTyp1 t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp1-substTyp-tProd t) Γ⇓ T⇓ = Term⇓ t Γ⇓ T⇓
    Term⇓ (substTyp2-substTyp-substTyp-weakenTyp1-weakenTyp-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (substTyp1-substTyp-weakenTyp2-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (weakenTyp-weakenTyp1-weakenTyp t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ (beta-under-subst t) Γ⇓ = Term⇓ t Γ⇓
    Term⇓ ‘proj₁'’ Γ⇓ (x , p) = x
    Term⇓ ‘proj₂'’ (Γ⇓ , (x , p)) = p
    Term⇓ (‘existT’ x p) Γ⇓ = Term⇓ x Γ⇓ , Term⇓ p Γ⇓
    Term⇓ (f ‘‘’’ x) Γ⇓ = lift (lower (Term⇓ f Γ⇓) ‘’ lower (Term⇓ x Γ⇓))
    Term⇓ (f w‘‘’’ x) Γ⇓ = lift (lower (Term⇓ f Γ⇓) ‘’ lower (Term⇓ x Γ⇓))
    Term⇓ (f ‘‘→'’’ x) Γ⇓ = lift (lower (Term⇓ f Γ⇓) ‘→'’ lower (Term⇓ x Γ⇓))
    Term⇓ (f w‘‘→'’’ x) Γ⇓ = lift (lower (Term⇓ f Γ⇓) ‘→'’ lower (Term⇓ x Γ⇓))
    Term⇓ (w→ x) Γ⇓ A⇓ = Term⇓ x (Σ.proj₁ Γ⇓) A⇓
    Term⇓ w‘‘→'’’→‘‘→'’’ Γ⇓ T⇓ = T⇓
    Term⇓ ‘‘→'’’→w‘‘→'’’ Γ⇓ T⇓ = T⇓
    Term⇓ ‘tApp-nd’ Γ⇓ f⇓ x⇓ = lift (SW (lower f⇓ ‘’ₐ lower x⇓))
    Term⇓ ⌜←'⌝ Γ⇓ T⇓ = T⇓
    Term⇓ ⌜→'⌝ Γ⇓ T⇓ = T⇓
    Term⇓ (‘‘fcomp-nd’’ {A} {B} {C}) Γ⇓ g⇓ f⇓ = lift (_‘∘’_ {ε} (lower g⇓) (lower f⇓))
    Term⇓ (⌜‘’⌝ {B} {A} {b}) Γ⇓ = lift (‘λ∙’ {ε} (‘VAR₀’ {ε} {_‘’_ {ε} A b}))
    Term⇓ (⌜‘’⌝' {B} {A} {b}) Γ⇓ = lift (‘λ∙’ {ε} (‘VAR₀’ {ε} {_‘’_ {ε} A b}))
    Term⇓ (‘cast-refl’ {T}) Γ⇓ = lift (cast-proof {T})
    Term⇓ (‘cast-refl'’ {T}) Γ⇓ = lift (cast'-proof {T})
    Term⇓ (‘s→→’ {T} {B} {b} {c} {v}) Γ⇓ = lift (‘idfun’ {_‘’_ {ε} (lower (Term⇓ b tt (Term⇓ v Γ⇓))) (lower (Term⇓ c tt (Term⇓ v Γ⇓)))})
    Term⇓ (‘s←←’ {T} {B} {b} {c} {v}) Γ⇓ = lift (‘idfun’ {_‘’_ {ε} (lower (Term⇓ b tt (Term⇓ v Γ⇓))) (lower (Term⇓ c tt (Term⇓ v Γ⇓)))})


------------------------------------------------------------------------
-- Most of a virtual machine
------------------------------------------------------------------------

{-# OPTIONS --sized-types #-}

module Lambda.Virtual-machine where

open import Equality.Propositional
open import Prelude

open import Vec.Function equality-with-J

open import Lambda.Syntax

------------------------------------------------------------------------
-- Instruction set

mutual

  -- Instructions.

  data Instr (n : ℕ) : Type where
    var : (x : Fin n) → Instr n
    con : (i : ℕ) → Instr n
    clo : (c : Code (suc n)) → Instr n
    app : Instr n
    ret : Instr n

  -- Code.

  Code : ℕ → Type
  Code n = List (Instr n)

-- Environments and values.

open Closure Code

------------------------------------------------------------------------
-- Stacks and states

-- Stacks.

data StackElement : Type where
  val : (v : Value) → StackElement
  ret : ∀ {n} (c : Code n) (ρ : Env n) → StackElement

Stack : Type
Stack = List StackElement

-- States.

data State : Type where
  ⟨_,_,_⟩ : ∀ {n} (c : Code n) (s : Stack) (ρ : Env n) → State

pattern ⟨_∣_,_,_⟩ n c s ρ = ⟨_,_,_⟩ {n} c s ρ

------------------------------------------------------------------------
-- A kind of small-step semantics for the virtual machine

-- The result of running the VM one step.

data Result : Type where
  continue : (s : State) → Result
  done     : (v : Value) → Result
  crash    : Result

-- A single step of the computation.

step : State → Result
step ⟨ var x  ∷ c ,                          s , ρ ⟩ = continue ⟨ c  , val (ρ x)    ∷ s ,        ρ  ⟩
step ⟨ con i  ∷ c ,                          s , ρ ⟩ = continue ⟨ c  , val (con i)  ∷ s ,        ρ  ⟩
step ⟨ clo c′ ∷ c ,                          s , ρ ⟩ = continue ⟨ c  , val (ƛ c′ ρ) ∷ s ,        ρ  ⟩
step ⟨ app    ∷ c , val v ∷ val (ƛ c′ ρ′) ∷  s , ρ ⟩ = continue ⟨ c′ , ret c ρ      ∷ s , cons v ρ′ ⟩
step ⟨ ret    ∷ c , val v ∷ ret c′ ρ′     ∷  s , ρ ⟩ = continue ⟨ c′ , val v        ∷ s ,        ρ′ ⟩
step ⟨ zero ∣ []  ,                 val v ∷ [] , ρ ⟩ = done v
step _                                               = crash


{- Byzantine Fault Tolerant Consensus Verification in Agda, version 0.9.

   Copyright (c) 2020 Oracle and/or its affiliates.
   Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.oracle.com/licenses/upl
-}
{-# OPTIONS --allow-unsolved-metas #-}
open import LibraBFT.Prelude
open import LibraBFT.Lemmas
open import LibraBFT.Abstract.Types

-- For each desired property (VotesOnce and LockedRoundRule), we have a
-- module containing a Type that defines a property that an implementation
-- should prove, and a proof that it implies the corresponding rule used by
-- the abstract proofs.  Then, we use those proofs to instantiate thmS5,
-- and the use thmS5 to prove a number of correctness conditions.
--
-- TODO-1: refactor this file to separate the definitions and proofs of
-- VotesOnce and LockedRoundRule from their use in proving the correctness
-- properties.

module LibraBFT.Abstract.Properties
  (𝓔 : EpochConfig)
  (UID    : Set)
  (_≟UID_ : (u₀ u₁ : UID) → Dec (u₀ ≡ u₁))
  (𝓥      : VoteEvidence 𝓔 UID)
  where

 open import LibraBFT.Abstract.Records 𝓔 UID _≟UID_ 𝓥
 open import LibraBFT.Abstract.Records.Extends 𝓔 UID _≟UID_ 𝓥
 open import LibraBFT.Abstract.RecordChain 𝓔 UID _≟UID_ 𝓥
 import LibraBFT.Abstract.RecordChain.Assumptions 𝓔 UID _≟UID_ 𝓥
   as StaticAssumptions
 open import LibraBFT.Abstract.System 𝓔 UID _≟UID_ 𝓥

 open EpochConfig 𝓔

 open import LibraBFT.Abstract.Obligations.VotesOnce 𝓔 UID _≟UID_ 𝓥 as VO
 open import LibraBFT.Abstract.Obligations.LockedRound 𝓔 UID _≟UID_ 𝓥 as LR

 --------------------------------------------------------------------------------------------
 -- * A /ValidSysState/ is one in which both peer obligations are obeyed by honest peers * --
 --------------------------------------------------------------------------------------------

 record ValidSysState {ℓ}(𝓢 : AbsSystemState ℓ) : Set (ℓ+1 ℓ0 ℓ⊔ ℓ) where
   field
     vss-votes-once   : VO.Type 𝓢
     vss-locked-round : LR.Type 𝓢
 open ValidSysState public

 -- And a valid system state offers the desired /CommitsDoNotConflict/ property
 -- and variants.
 module _ {ℓ}(𝓢 : AbsSystemState ℓ) (st-valid : ValidSysState 𝓢) where
   open AbsSystemState 𝓢
   open All-InSys-props InSys
   import LibraBFT.Abstract.RecordChain.Properties 𝓔 UID _≟UID_ 𝓥 as Props

   CommitsDoNotConflict : ∀{q q'}
        → {rc  : RecordChain (Q q)}  → All-InSys rc
        → {rc' : RecordChain (Q q')} → All-InSys rc'
        → {b b' : Block}
        → CommitRule rc  b
        → CommitRule rc' b'
        → NonInjective-≡ bId ⊎ ((B b) ∈RC rc' ⊎ (B b') ∈RC rc)
   CommitsDoNotConflict = Props.WithInvariants.thmS5 InSys
     (VO.proof 𝓢 (vss-votes-once st-valid))
     (LR.proof 𝓢 (vss-locked-round st-valid))

   -- When we are dealing with a /Complete/ AbsSystem, we can go a few steps
   -- further and prove that commits do not conflict even if we have only partial
   -- knowledge about Records represented in the system.
   module _ (∈QC⇒AllSent : Complete 𝓢) where

    -- For a /complete/ system we can go even further; if we have evidence that
    -- only the tip of the record chains is in the system, we can infer
    -- the rest of it is also in the system (or blockIDs are not injective).
    CommitsDoNotConflict'
      : ∀{q q'}{rc  : RecordChain (Q q)}{rc' : RecordChain (Q q')}{b b' : Block}
      → InSys (Q q) → InSys (Q q')
      → CommitRule rc  b
      → CommitRule rc' b'
      → NonInjective-≡ bId ⊎ ((B b) ∈RC rc' ⊎ (B b') ∈RC rc)
    CommitsDoNotConflict' {q} {q'} {step {r = B bb} rc b←q} {step {r = B bb'} rc' b←q'} {b} {b'} q∈sys q'∈sys cr cr'
       with bft-assumption (qVotes-C2 q) (qVotes-C2 q')
    ...| α , α∈qmem , α∈q'mem , hα
       with Any-sym (Any-map⁻ α∈qmem) | Any-sym (Any-map⁻ α∈q'mem)
    ...| α∈q | α∈q'
       with ∈QC⇒AllSent {q = q} hα α∈q q∈sys | ∈QC⇒AllSent {q = q'} hα α∈q' q'∈sys
    ...| ab , ab←q , arc , ais | ab' , ab←q' , arc' , ais'
       with RecordChain-irrelevant (step arc  ab←q)  (step rc  b←q) |
            RecordChain-irrelevant (step arc' ab←q') (step rc' b←q')
    ...| inj₁ hb     | _       = inj₁ hb
    ...| inj₂ _      | inj₁ hb = inj₁ hb
    ...| inj₂ arc≈rc | inj₂ arc'≈rc'
       with CommitsDoNotConflict
                 (All-InSys-step ais  ab←q  q∈sys )
                 (All-InSys-step ais' ab←q' q'∈sys)
                 (transp-CR (≈RC-sym arc≈rc  ) cr )
                 (transp-CR (≈RC-sym arc'≈rc') cr')
    ...| inj₁ hb = inj₁ hb
    ...| inj₂ (inj₁ b∈arc') = inj₂ (inj₁ (transp-B∈RC arc'≈rc' b∈arc'))
    ...| inj₂ (inj₂ b'∈arc) = inj₂ (inj₂ (transp-B∈RC arc≈rc   b'∈arc))

    -- The final property is even stronger; it states that even if an observer
    -- has access only to suffixes of record chains that match the commit rule,
    -- we can still guarantee that b and b' are non-conflicting blocks.  This
    -- will be important for showing that observers can have confidence in commit
    -- messages without participating in the protocol and without having access to
    -- all previously sent records.
    CommitsDoNotConflict''
      : ∀{o o' q q'}
      → {rcf  : RecordChainFrom o  (Q q)}
      → {rcf' : RecordChainFrom o' (Q q')}
      → {b b' : Block}
      → InSys (Q q)
      → InSys (Q q')
      → CommitRuleFrom rcf  b
      → CommitRuleFrom rcf' b'
      → NonInjective-≡ bId ⊎ Σ (RecordChain (Q q')) ((B b)  ∈RC_)
                           ⊎ Σ (RecordChain (Q q))  ((B b') ∈RC_)
    CommitsDoNotConflict'' {cb} {q = q} {q'} {rcf} {rcf'} q∈sys q'∈sys crf crf'
       with bft-assumption (qVotes-C2 q) (qVotes-C2 q')
    ...| α , α∈qmem , α∈q'mem , hα
       with Any-sym (Any-map⁻ α∈qmem) | Any-sym (Any-map⁻ α∈q'mem)
    ...| α∈q | α∈q'
       with ∈QC⇒AllSent {q = q} hα α∈q q∈sys | ∈QC⇒AllSent {q = q'} hα α∈q' q'∈sys
    ...| ab , ab←q , arc , ais | ab' , ab←q' , arc' , ais'
       with step arc  ab←q | step arc' ab←q'
    ...| rcq | rcq'
       with crf⇒cr rcf  rcq  crf | crf⇒cr rcf' rcq' crf'
    ...| inj₁ hb | _       = inj₁ hb
    ...| inj₂ _  | inj₁ hb = inj₁ hb
    ...| inj₂ cr | inj₂ cr'
       with CommitsDoNotConflict' q∈sys q'∈sys cr cr'
    ...| inj₁ hb = inj₁ hb
    ...| inj₂ (inj₁ b∈arc') = inj₂ (inj₁ (rcq' , b∈arc'))
    ...| inj₂ (inj₂ b'∈arc) = inj₂ (inj₂ (rcq  , b'∈arc))


open import Prelude

module Implicits.Semantics.RewriteContext where

open import Implicits.Syntax
open import Implicits.Substitutions
open import Implicits.Substitutions.Lemmas

open import Data.Vec
open import Data.List.All as All
open import Extensions.Vec

-- rewrite context (relation between implicit and explicit context)
_#_ : ∀ {ν n} (Γ : Ctx ν n) (Δ : ICtx ν) → Set
Γ # Δ = All (λ i → i ∈ Γ) Δ

K# : ∀ {ν n} (K : Ktx ν n) → Set
K# (Γ , Δ) = Γ # Δ

#tvar : ∀ {ν n} {K : Ktx ν n} → K# K → K# (ktx-weaken K)
#tvar All.[] = All.[]
#tvar (px All.∷ K#K) = (∈⋆map px (λ t → t tp/tp TypeLemmas.wk)) All.∷ (#tvar K#K)

#var : ∀ {ν n} {K : Ktx ν n} → (a : Type ν) → K# K → K# (a ∷Γ K)
#var a All.[] = All.[]
#var a (px All.∷ K#K) = there px All.∷ (#var a K#K)

#ivar : ∀ {ν n} {K : Ktx ν n} → (a : Type ν) → K# K → K# (a ∷K K)
#ivar a K#K = here All.∷ (All.map there K#K)


open import Agda.Primitive
open import Data.Unit
open import Data.Nat hiding (_⊔_ ; _^_)
open import Data.Integer hiding (_⊔_)
open import Data.List
open import Data.List.Relation.Unary.Any
open import Data.List.Relation.Unary.All as All
open import Data.List.Membership.Propositional
open import Data.Product
open import Data.Maybe hiding (_>>=_)
open import Data.List.Prefix
open import Data.List.Properties.Extra
open import Data.List.All.Properties.Extra
open import Function
open import Relation.Binary.PropositionalEquality hiding ([_])
open ≡-Reasoning
open import Common.Weakening

-- This file contains the definitional interpreter for STLC using
-- scopes and frames, described in Section 4 of the paper.

-- The scopes-and-frames library assumes that we are given a
-- particular scope graph, with `k` scopes.  The following module is
-- parameterized by a `k` representing the number of scopes that a
-- particular object language program has.

module STLCSF.Semantics (k : ℕ) where

-----------
-- TYPES --
-----------

data Ty : Set where
  unit : Ty
  _⇒_  : (a b : Ty) → Ty
  int  : Ty

-- The library is loaded and passed two arguments:
--
-- * `k` is the size of the scope graph for an object program
--
-- * `Ty` is the type of declarations in the scope graph

open import ScopesFrames.ScopesFrames k Ty

-- Our interpreter is parameterized by a scope graph, via the module
-- below.

module Syntax (g : Graph) where

  -- We load all the scope graph definitions in the scope graph
  -- library, by passing the object scope graph `g` as module
  -- parameter:
  open UsesGraph g

  ------------
  -- SYNTAX --
  ------------

  -- We can now define our well-typed syntax as described in the paper
  -- Section 4.2:
  data Expr (s : Scope) : Ty → Set where
    unit  : Expr s unit
    var   : ∀ {t} → (s ↦ t) → Expr s t
    ƛ     : ∀ {s' a b} → ⦃ shape : g s' ≡ ( [ a ] , [ s ] ) ⦄ → Expr s' b → Expr s (a ⇒ b)
    _·_   : ∀ {a b} → Expr s (a ⇒ b) → Expr s a → Expr s b
    num   : ℤ → Expr s int
    iop   : (ℤ → ℤ → ℤ) → (l r : Expr s int) → Expr s int

  ------------
  -- VALUES --
  ------------

  -- We can also define well-typed values as described in the paper
  -- Section 4.4:
  data Val : Ty → (Σ : HeapTy) → Set where
    unit   : ∀ {Σ} → Val unit Σ
    ⟨_,_⟩  : ∀ {Σ s s' a b}⦃ shape : g s' ≡ ( [ a ] , [ s ] ) ⦄ →
             Expr s' b → Frame s Σ → Val (a ⇒ b) Σ
    num    : ∀ {Σ} → ℤ → Val int Σ

  val-weaken : ∀ {t Σ Σ'} → Σ ⊑ Σ' → Val t Σ → Val t Σ'
  val-weaken ext ⟨ e , f ⟩ = ⟨ e , wk ext f ⟩
  val-weaken ext unit      = unit
  val-weaken ext (num z)   = num z

  -- We can now load the frames definitions of the scopes-and-frames
  -- library.  As described in Section 4.3 of the paper, our notion of
  -- frame assumes a notion of weakenable value, to be passed as
  -- module arguments to `UsesVal`:
  open UsesVal Val val-weaken renaming (getFrame to getFrame')

  -- We rename `getFrame` from the scopes-and-frames library so that
  -- we can use `getFrame` as the name of the monadic operation which
  -- returns the "current frame pointer" below.

  -----------
  -- MONAD --
  -----------

  -- These definitions correspond to Section 4.4.

  M : (s : Scope) → (HeapTy → Set) → HeapTy → Set
  M s p Σ = Frame s Σ → Heap Σ → Maybe (∃ λ Σ' → (Heap Σ' × p Σ' × Σ ⊑ Σ'))

  _>>=_       :  ∀ {s Σ}{p q : List Scope → Set} →
                 M s p Σ → (∀ {Σ'} → p Σ' → M s q Σ') → M s q Σ
  (a >>= b) f h
    with (a f h)
  ...  | nothing = nothing
  ...  | just (Σ , h' , v , ext)
       with (b v (wk ext f) h')
  ...     | nothing = nothing
  ...     | just (Σ' , h'' , v' , ext') = just (Σ' , h'' , v' , ext ⊚ ext')

  return      :  ∀ {s Σ}{p : List Scope → Set} → p Σ → M s p Σ
  return v f h = just (_ , h , v , ⊑-refl)

  getFrame    :  ∀ {s Σ} → M s (Frame s) Σ
  getFrame f = return f f

  usingFrame  :  ∀ {s s' Σ}{p : List Scope → Set} → Frame s Σ → M s p Σ → M s' p Σ
  usingFrame f a _ = a f

  timeout     :  ∀ {s Σ}{p : List Scope → Set} → M s p Σ
  timeout _ _ = nothing

  init        :  ∀ {Σ s' ds es}(s : Scope)⦃ shape : g s ≡ (ds , es) ⦄ →
                 Slots ds Σ → Links es Σ → M s' (Frame s) Σ
  init {Σ} s slots links _ h
    with (initFrame s slots links h)
  ...  | (f' , h') = just (_ , h' , f' , ∷ʳ-⊒ s Σ)

  getv        :  ∀ {s t Σ} → (s ↦ t) → M s (Val t) Σ
  getv p f h = return (getVal p f h) f h

  _^_         :  ∀ {Σ Γ}{p q : List Scope → Set} → ⦃ w : Weakenable q ⦄ →
                 M Γ p Σ → q Σ → M Γ (p ⊗ q) Σ
  (a ^ x) f h
    with (a f h)
  ...  | nothing = nothing
  ...  | just (Σ , h' , v , ext) = just (Σ , h' , (v , wk ext x) , ext)

  sₑ : ∀ {s t} → Expr s t → Scope
  sₑ {s} _ = s

  eval : ℕ → ∀ {s t Σ} → Expr s t → M s (Val t) Σ
  eval zero     _      =
    timeout
  eval (suc k) unit    =
    return unit
  eval (suc k) (var x) =
    getv x
  eval (suc k) (ƛ e)   =
    getFrame >>= λ f →
    return ⟨ e , f ⟩
  eval (suc k) (l · r) =
    eval k l >>= λ{ ⟨ e , f ⟩ →
    (eval k r ^ f) >>= λ{ (v , f) →
    init (sₑ e) (v ∷ []) (f ∷ []) >>= λ f' →
    usingFrame f' (eval k e) }}
  eval (suc k) (num z) =
    return (num z)
  eval (suc k) (iop f l r) =
    eval k l >>= λ{ (num z₁) →
    eval k r >>= λ{ (num z₂) →
    return (num (f z₁ z₂)) }}


{- wip, coming to realise that the instance of IsSetoid defined in Data.Equality.properties is over-eager (defining _≡_ for all (A : Set (a : Level)), _≡̇_ for ∀ (x : A : Set (a : Level)) → (B : A → Set (b : Level)) x -}
-- add samples of stuff from unification (AList, etc.)
module Oscar.Data5 where

record ⊤ : Set where
  constructor tt

Const_ : ∀ {a} {A : Set a} {b} {B : Set b} → B → A → B
Const_ x _ = x

infixr 20 _∷_

-- List
data ⟦_⟧ {a} (A : Set a) : Set a where
  ∅ : ⟦ A ⟧
  _∷_ : A → ⟦ A ⟧ → ⟦ A ⟧

-- Nat
⟦⟧ = ⟦ ⊤ ⟧

infix 21 ¡_
pattern ¡_ ⟦A⟧ = tt ∷ ⟦A⟧
--¡ : ⟦⟧ → ⟦⟧
--¡ ⟦A⟧ = tt ∷ ⟦A⟧

-- Fin (with a payload)
data ⟦_⟧[_] {a} (A : ⟦⟧ → Set a) : ⟦⟧ → Set a where
  ∅ : ∀ {n} → ⟦ A ⟧[ ¡ n ]
  _∷_ : ∀ {n} → A n → ⟦ A ⟧[ n ] → ⟦ A ⟧[ ¡ n ]

⟦⟧[_] = ⟦ Const ⊤ ⟧[_]

-- Vec (with an (optional) index)
data ⟦_⟧[_₀] {a} (A : ⟦⟧ → Set a) : ⟦⟧ → Set a where
  ∅ : ⟦ A ⟧[ ∅ ₀]
  _∷_ : ∀ {n} → A n → ⟦ A ⟧[ n ₀] → ⟦ A ⟧[ ¡ n ₀]

⟦⟧[_₀] = ⟦ Const ⊤ ⟧[_₀]

-- m ≤ n, counting down from n-1 to m
data ⟦_⟧[_≤_↓] {a} (A : ⟦⟧ → Set a) (m : ⟦⟧) : ⟦⟧ → Set a where
  ∅ : ⟦ A ⟧[ m ≤ m ↓]
  _∷_ : ∀ {n} → A n → ⟦ A ⟧[ m ≤ n ↓] → ⟦ A ⟧[ m ≤ ¡ n ↓]

⟦⟧[_≤_↓] = ⟦ Const ⊤ ⟧[_≤_↓]

-- m ≤ n, counting up from m to n-1
data ⟦_⟧[↑_≤_] {a} (A : ⟦⟧ → Set a) (m : ⟦⟧) : ⟦⟧ → Set a where
  ∅ : ⟦ A ⟧[↑ m ≤ m ]
  _∷_ : ∀ {n} → A m → ⟦ A ⟧[↑ ¡ m ≤ n ] → ⟦ A ⟧[↑ m ≤ n ]

⟦⟧[↑_≤_] = ⟦ Const ⊤ ⟧[↑_≤_]

-- Inj (almost)
data ⟦_⟧[↓_≤_↓] {a} (A : ⟦⟧ → ⟦⟧ → Set a) : ⟦⟧ → ⟦⟧ → Set a where
  ∅ : ∀ {n} → ⟦ A ⟧[↓ ∅ ≤ n ↓]
  _∷_ : ∀ {m n} → A m n → ⟦ A ⟧[↓ m ≤ n ↓] → ⟦ A ⟧[↓ ¡ m ≤ ¡ n ↓]

⟦⟧[↓_≤_↓] = ⟦ Const Const ⊤ ⟧[↓_≤_↓]

⟦_⟧[_⇒_]∞ : ∀ {a} (A : ⟦⟧ → Set a) → ⟦⟧ → ⟦⟧ → Set a
⟦ A ⟧[ m ⇒ n ]∞ = A m → A n

open import Oscar.Category.Semigroupoid
open import Oscar.Category.Category
open import Oscar.Category.Morphism
open import Oscar.Category.Setoid
--open import Oscar.Data.Equality
--open import Oscar.Data.Equality.properties
open import Oscar.Function

module _ {𝔬 𝔪 𝔮} (G : Semigroupoid 𝔬 𝔪 𝔮) where
  open Semigroupoid G

{-
  _≋̇_ : ∀ {a} {A : ⋆ → Set a} {m n : ⋆} → (A m → A n) → (A m → A n) → Set 𝔮
  _≋̇_ {A = A} {m = m} m→n₁ m→n₂ = {!∀ (am : A m) → m→n₁ am ≋ m→n₂ am!}

  cat2 : ∀ {a} (A : ⋆ → Set a) → Category {!!} {!!} {!!}
  Semigroupoid.⋆ (Category.semigroupoid (cat2 A)) = ⋆
  Setoid.⋆ ((Semigroupoid.𝔐 (Category.semigroupoid (cat2 A)) Morphism.⇒ m) n) = A m → A n
  IsSetoid._≋_ (Setoid.isSetoid ((Semigroupoid.𝔐 (Category.semigroupoid (cat2 A)) Morphism.⇒ m) n)) = _≡̇_
  IsSetoid.isEquivalence (Setoid.isSetoid ((Semigroupoid.𝔐 (Category.semigroupoid (cat2 A)) Morphism.⇒ m) n)) = it
  Semigroupoid._∙_ (Category.semigroupoid (cat2 A)) g f = g ∘ f
  Semigroupoid.isSemigroupoid (Category.semigroupoid (cat2 A)) = it
  Category.ε (cat2 A) = id
  Category.isCategory (cat2 A) = it
-}

  cat2G : ∀ {a} (A : ⋆ → Set a) → ∀ ℓ ⦃ _ : ∀ {n : ⋆} → IsSetoid (A n) ℓ ⦄ → Category {!!} {!!} {!!}
  Semigroupoid.⋆ (Category.semigroupoid (cat2G A ℓ)) = ⋆
  Setoid.⋆ ((Semigroupoid.𝔐 (Category.semigroupoid (cat2G A ℓ)) Morphism.⇒ m) n) = A m → A n
  IsSetoid._≋_ (Setoid.isSetoid ((Semigroupoid.𝔐 (Category.semigroupoid (cat2G A ℓ)) Morphism.⇒ m) n)) m↦n₁ m↦n₂ = ∀ (am : A m) → m↦n₁ am ≋ m↦n₂ am
  -- ∀ (am : A m) → m↦n₁ am ≋ m↦n₂ am
  {-
  _≋̇_ : ∀ {a} {A : ⋆ → Set a} {m n : ⋆} → (A m → A n) → (A m → A n) → Set 𝔮
  _≋̇_ {A = A} {m = m} m→n₁ m→n₂ = {!∀ (am : A m) → m→n₁ am ≋ m→n₂ am!}
  -}
  IsSetoid.isEquivalence (Setoid.isSetoid ((Semigroupoid.𝔐 (Category.semigroupoid (cat2G A ℓ)) Morphism.⇒ m) n)) = {!it!}
  Semigroupoid._∙_ (Category.semigroupoid (cat2G A ℓ)) g f = g ∘ f
  Semigroupoid.isSemigroupoid (Category.semigroupoid (cat2G A ℓ)) = {!it!}
  Category.ε (cat2G A ℓ) = id
  Category.isCategory (cat2G A ℓ) = {!it!}

infix 21 ‼_
pattern ‼_ ⟦A⟧ = tt ∷ ⟦A⟧ -- tricky, works for all above _∷_ constructors only because it is defined afterwards, won't work for any later-defined constructors

open import Agda.Primitive
open import Oscar.Data.Product
open import Oscar.Function

postulate
  ℓ : Level
  Term : ⟦⟧ → Set ℓ
  Step : ⟦⟧ → Set ℓ

Fin = ⟦⟧[_]

Inj = ⟦ Const Fin ⟧[↓_≤_↓]

Terms = λ N n → ⟦ Const (Term n) ⟧[ N ₀]

AList = ⟦ (λ n → Term n × Fin (¡ n)) ⟧[_≤_↓]

Fx : ∀ {a} {A : Set a} → A → A → ∀ {b₁} (B₁ : A → Set b₁) → ∀ {b₂} (B₂ : A → Set b₂) → Set (b₁ ⊔ b₂)
Fx m n B₁ B₂ = B₁ m → B₂ n

Fx₁ : ∀ {a} {A : Set a} → A → ∀ {b₁} (B₁ : A → Set b₁) → ∀ {b₂} (B₂ : A → Set b₂) → Set (b₁ ⊔ b₂)
Fx₁ n B₁ B₂ = Fx n n B₁ B₂

ListStep = λ n → ⟦ Step n ⟧

Rel : ∀ {a} {A : Set a} {b₁} (B₁ : A → Set b₁) {b₂} (B₂ : A → Set b₂) {c₁} (C₁ : A → Set c₁) {c₂} (C₂ : A → Set c₂) → Set (a ⊔ b₁ ⊔ b₂ ⊔ c₁ ⊔ c₂)
Rel B₁ B₂ C₁ C₂ = ∀ {m n} → Fx m n B₁ B₂ → Fx m n C₁ C₂

Morph : ∀ {a} {A : Set a} {b₁} {b₂} (B : (A → Set b₁) × (A → Set b₂)) {c₁} {c₂} (C₂ : (A → Set c₁) × (A → Set c₂)) → Set (a ⊔ b₁ ⊔ b₂ ⊔ c₁ ⊔ c₂)
Morph (B₁ , B₂) (C₁ , C₂) = ∀ {m n} → Fx m n B₁ B₂ → Fx m n C₁ C₂

-- functor mappings
postulate
  _◃_ : Morph (Fin , Term) (Term , Term)
  _◃s_ : ∀ N → Morph (Fin , Term) (Terms N , Terms N)
  sub : ∀ {m n} → AList m n → Fx m n Fin Term
  fmapS : Morph (Term , Term) (Step , Step)
  _◃S_ : Morph (Fin , Term) (ListStep , ListStep)

-- ?
postulate
  _⊹_ : ∀ {n} → ⟦ Step n ⟧ → Fx₁ n Term Term

testNat : ⟦⟧
testNat = ¡ ∅

testListNat : ⟦ ⟦⟧ ⟧
testListNat = ¡ ∅ ∷ ‼ ∅ ∷ ‼ ‼ ∅ ∷ ∅ ∷ ¡ ¡ ¡ ∅ ∷ ∅

testFin : ⟦⟧[ ¡ ¡ ∅ ]
testFin = ‼ ∅

test≤↓ : ⟦⟧[ ‼ ‼ ‼ ∅ ≤ ‼ ‼ ‼ ‼ ‼ ∅ ↓]
test≤↓ = ‼ ‼ ∅

⓪ ⑴ ⑵ ⑶ : ⟦⟧
⓪ = ∅
⑴ = ‼ ∅
⑵ = ‼ ⑴
⑶ = ‼ ⑵

testInj : Inj ⑵ ⑶
testInj = ‼ ∅ ∷ ∅ ∷ ∅


module STLC1.Kovacs.Substitution where

open import STLC1.Kovacs.Embedding public
open import Category

--------------------------------------------------------------------------------

-- Substitutions (Sub ; ∙ ; _,_)
infix 3 _⊢⋆_
data _⊢⋆_ : 𝒞 → 𝒞 → Set
  where
    ∅   : ∀ {Γ} → Γ ⊢⋆ ∅

    _,_ : ∀ {Γ Ξ A} → (σ : Γ ⊢⋆ Ξ) (M : Γ ⊢ A)
                    → Γ ⊢⋆ Ξ , A

-- (_ₛ∘ₑ_)
-- NOTE: _◐_ = ren⋆
_◐_ : ∀ {Γ Γ′ Ξ} → Γ ⊢⋆ Ξ → Γ′ ⊇ Γ → Γ′ ⊢⋆ Ξ
∅       ◐ η = ∅
(σ , M) ◐ η = σ ◐ η , ren η M

-- (_ₑ∘ₛ_)
_◑_ : ∀ {Γ Ξ Ξ′} → Ξ′ ⊇ Ξ → Γ ⊢⋆ Ξ′ → Γ ⊢⋆ Ξ
done    ◑ σ       = σ
wkₑ η   ◑ (σ , M) = η ◑ σ
liftₑ η ◑ (σ , M) = η ◑ σ , M

--------------------------------------------------------------------------------

-- (dropₛ)
wkₛ : ∀ {A Γ Ξ} → Γ ⊢⋆ Ξ → Γ , A ⊢⋆ Ξ
wkₛ σ = σ ◐ wkₑ idₑ

-- (keepₛ)
liftₛ : ∀ {A Γ Ξ} → Γ ⊢⋆ Ξ → Γ , A ⊢⋆ Ξ , A
liftₛ σ = wkₛ σ , 0

-- (⌜_⌝ᵒᵖᵉ)
⌊_⌋ : ∀ {Γ Γ′} → Γ′ ⊇ Γ → Γ′ ⊢⋆ Γ
⌊ done ⌋    = ∅
⌊ wkₑ η ⌋   = wkₛ ⌊ η ⌋
⌊ liftₑ η ⌋ = liftₛ ⌊ η ⌋

-- (∈ₛ)
getₛ : ∀ {Γ Ξ A} → Γ ⊢⋆ Ξ → Ξ ∋ A → Γ ⊢ A
getₛ (σ , M) zero    = M
getₛ (σ , M) (suc i) = getₛ σ i

-- (Tmₛ)
sub : ∀ {Γ Ξ A} → Γ ⊢⋆ Ξ → Ξ ⊢ A → Γ ⊢ A
sub σ (𝓋 i)   = getₛ σ i
sub σ (ƛ M)   = ƛ (sub (liftₛ σ) M)
sub σ (M ∙ N) = sub σ M ∙ sub σ N
sub σ (M , N) = sub σ M , sub σ N
sub σ (π₁ M)  = π₁ (sub σ M)
sub σ (π₂ M)  = π₂ (sub σ M)
sub σ τ       = τ

-- (idₛ)
idₛ : ∀ {Γ} → Γ ⊢⋆ Γ
idₛ {∅}     = ∅
idₛ {Γ , A} = liftₛ idₛ

cut : ∀ {Γ A B} → Γ ⊢ A → Γ , A ⊢ B → Γ ⊢ B
cut M N = sub (idₛ , M) N

-- (_∘ₛ_)
-- NOTE: _●_ = sub⋆
_●_ : ∀ {Γ Ξ Φ} → Ξ ⊢⋆ Φ → Γ ⊢⋆ Ξ → Γ ⊢⋆ Φ
∅        ● σ₁ = ∅
(σ₂ , M) ● σ₁ = σ₂ ● σ₁ , sub σ₁ M

--------------------------------------------------------------------------------

-- (assₛₑₑ)
comp◐○ : ∀ {Γ Γ′ Γ″ Ξ} → (η₁ : Γ″ ⊇ Γ′) (η₂ : Γ′ ⊇ Γ) (σ : Γ ⊢⋆ Ξ)
                       → (σ ◐ η₂) ◐ η₁ ≡ σ ◐ (η₂ ○ η₁)
comp◐○ η₁ η₂ ∅       = refl
comp◐○ η₁ η₂ (σ , M) = _,_ & comp◐○ η₁ η₂ σ
                           ⊗ (ren○ η₁ η₂ M ⁻¹)

-- (assₑₛₑ)
comp◑◐ : ∀ {Γ Γ′ Ξ Ξ′} → (η₁ : Γ′ ⊇ Γ) (σ : Γ ⊢⋆ Ξ′) (η₂ : Ξ′ ⊇ Ξ)
                       → (η₂ ◑ σ) ◐ η₁ ≡ η₂ ◑ (σ ◐ η₁)
comp◑◐ η₁ ∅       done       = refl
comp◑◐ η₁ (σ , M) (wkₑ η₂)   = comp◑◐ η₁ σ η₂
comp◑◐ η₁ (σ , M) (liftₑ η₂) = (_, ren η₁ M) & comp◑◐ η₁ σ η₂

--------------------------------------------------------------------------------

-- (idlₑₛ)
lid◑ : ∀ {Γ Ξ} → (σ : Γ ⊢⋆ Ξ)
               → idₑ ◑ σ ≡ σ
lid◑ ∅       = refl
lid◑ (σ , M) = (_, M) & lid◑ σ

-- (idlₛₑ)
lid◐ : ∀ {Γ Γ′} → (η : Γ′ ⊇ Γ)
                → idₛ ◐ η ≡ ⌊ η ⌋
lid◐ done      = refl
lid◐ (wkₑ η)   = ((idₛ ◐_) ∘ wkₑ) & rid○ η ⁻¹
               ⦙ comp◐○ (wkₑ idₑ) η idₛ ⁻¹
               ⦙ wkₛ & lid◐ η
lid◐ (liftₑ η) = (_, 0) & ( comp◐○ (liftₑ η) (wkₑ idₑ) idₛ
                          ⦙ ((idₛ ◐_) ∘ wkₑ) & ( lid○ η
                                               ⦙ rid○ η ⁻¹
                                               )
                          ⦙ comp◐○ (wkₑ idₑ) η idₛ ⁻¹
                          ⦙ (_◐ wkₑ idₑ) & lid◐ η
                          )

-- (idrₑₛ)
rid◑ : ∀ {Γ Γ′} → (η : Γ′ ⊇ Γ)
                → η ◑ idₛ ≡ ⌊ η ⌋
rid◑ done      = refl
rid◑ (wkₑ η)   = comp◑◐ (wkₑ idₑ) idₛ η ⁻¹
               ⦙ wkₛ & rid◑ η
rid◑ (liftₑ η) = (_, 0) & ( comp◑◐ (wkₑ idₑ) idₛ η ⁻¹
                          ⦙ (_◐ wkₑ idₑ) & rid◑ η
                          )

--------------------------------------------------------------------------------

-- (∈-ₑ∘ₛ)
get◑ : ∀ {Γ Ξ Ξ′ A} → (σ : Γ ⊢⋆ Ξ′) (η : Ξ′ ⊇ Ξ) (i : Ξ ∋ A)
                    → getₛ (η ◑ σ) i ≡ (getₛ σ ∘ getₑ η) i
get◑ σ       done      i       = refl
get◑ (σ , M) (wkₑ η)   i       = get◑ σ η i
get◑ (σ , M) (liftₑ η) zero    = refl
get◑ (σ , M) (liftₑ η) (suc i) = get◑ σ η i

-- (Tm-ₑ∘ₛ)
mutual
  sub◑ : ∀ {Γ Ξ Ξ′ A} → (σ : Γ ⊢⋆ Ξ′) (η : Ξ′ ⊇ Ξ) (M : Ξ ⊢ A)
                      → sub (η ◑ σ) M ≡ (sub σ ∘ ren η) M
  sub◑ σ η (𝓋 i)   = get◑ σ η i
  sub◑ σ η (ƛ M)   = ƛ & sublift◑ σ η M
  sub◑ σ η (M ∙ N) = _∙_ & sub◑ σ η M
                         ⊗ sub◑ σ η N
  sub◑ σ η (M , N) = _,_ & sub◑ σ η M
                         ⊗ sub◑ σ η N
  sub◑ σ η (π₁ M)  = π₁ & sub◑ σ η M
  sub◑ σ η (π₂ M)  = π₂ & sub◑ σ η M
  sub◑ σ η τ       = refl

  sublift◑ : ∀ {Γ Ξ Ξ′ A B} → (σ : Γ ⊢⋆ Ξ′) (η : Ξ′ ⊇ Ξ) (M : Ξ , B ⊢ A)
                            → sub (liftₛ {B} (η ◑ σ)) M ≡
                               (sub (liftₛ σ) ∘ ren (liftₑ η)) M
  sublift◑ σ η M = (λ σ′ → sub (σ′ , 0) M)
                   & comp◑◐ (wkₑ idₑ) σ η
                 ⦙ sub◑ (liftₛ σ) (liftₑ η) M

--------------------------------------------------------------------------------

-- (∈-ₛ∘ₑ)
get◐ : ∀ {Γ Γ′ Ξ A} → (η : Γ′ ⊇ Γ) (σ : Γ ⊢⋆ Ξ) (i : Ξ ∋ A)
                    → getₛ (σ ◐ η) i ≡ (ren η ∘ getₛ σ) i
get◐ η (σ , M) zero    = refl
get◐ η (σ , M) (suc i) = get◐ η σ i

-- (Tm-ₛ∘ₑ)
mutual
  sub◐ : ∀ {Γ Γ′ Ξ A} → (η : Γ′ ⊇ Γ) (σ : Γ ⊢⋆ Ξ) (M : Ξ ⊢ A)
                      → sub (σ ◐ η) M ≡ (ren η ∘ sub σ) M
  sub◐ η σ (𝓋 i)   = get◐ η σ i
  sub◐ η σ (ƛ M)   = ƛ & sublift◐ η σ M
  sub◐ η σ (M ∙ N) = _∙_ & sub◐ η σ M
                         ⊗ sub◐ η σ N
  sub◐ η σ (M , N) = _,_ & sub◐ η σ M
                         ⊗ sub◐ η σ N
  sub◐ η σ (π₁ M)  = π₁ & sub◐ η σ M
  sub◐ η σ (π₂ M)  = π₂ & sub◐ η σ M
  sub◐ η σ τ       = refl

  sublift◐ : ∀ {Γ Γ′ Ξ A B} → (η : Γ′ ⊇ Γ) (σ : Γ ⊢⋆ Ξ) (M : Ξ , B ⊢ A)
                            → sub (liftₛ {B} (σ ◐ η)) M ≡
                               (ren (liftₑ η) ∘ sub (liftₛ σ)) M
  sublift◐ η σ M = (λ σ′ → sub (σ′ , 0) M)
                   & ( comp◐○ (wkₑ idₑ) η σ
                     ⦙ (σ ◐_) & (wkₑ & ( rid○ η
                                       ⦙ lid○ η ⁻¹
                                       ))
                     ⦙ comp◐○ (liftₑ η) (wkₑ idₑ) σ ⁻¹
                     )
                 ⦙ sub◐ (liftₑ η) (liftₛ σ) M

--------------------------------------------------------------------------------

-- (assₛₑₛ)
comp●◑ : ∀ {Γ Ξ Ξ′ Φ} → (σ₁ : Γ ⊢⋆ Ξ′) (η : Ξ′ ⊇ Ξ) (σ₂ : Ξ ⊢⋆ Φ)
                      → (σ₂ ◐ η) ● σ₁ ≡ σ₂ ● (η ◑ σ₁)
comp●◑ σ₁ η ∅        = refl
comp●◑ σ₁ η (σ₂ , M) = _,_ & comp●◑ σ₁ η σ₂
                           ⊗ (sub◑ σ₁ η M ⁻¹)

-- (assₛₛₑ)
comp●◐ : ∀ {Γ Γ′ Ξ Φ} → (η : Γ′ ⊇ Γ) (σ₁ : Γ ⊢⋆ Ξ) (σ₂ : Ξ ⊢⋆ Φ)
                      → (σ₂ ● σ₁) ◐ η ≡ σ₂ ● (σ₁ ◐ η)
comp●◐ η σ₁ ∅        = refl
comp●◐ η σ₁ (σ₂ , M) = _,_ & comp●◐ η σ₁ σ₂
                           ⊗ (sub◐ η σ₁ M ⁻¹)

--------------------------------------------------------------------------------

-- (∈-∘ₛ)
get● : ∀ {Γ Ξ Φ A} → (σ₁ : Γ ⊢⋆ Ξ) (σ₂ : Ξ ⊢⋆ Φ) (i : Φ ∋ A)
                   → getₛ (σ₂ ● σ₁) i ≡ (sub σ₁ ∘ getₛ σ₂) i
get● σ₁ (σ₂ , M) zero    = refl
get● σ₁ (σ₂ , M) (suc i) = get● σ₁ σ₂ i

-- (Tm-∘ₛ)
mutual
  sub● : ∀ {Γ Ξ Φ A} → (σ₁ : Γ ⊢⋆ Ξ) (σ₂ : Ξ ⊢⋆ Φ) (M : Φ ⊢ A)
                     → sub (σ₂ ● σ₁) M ≡ (sub σ₁ ∘ sub σ₂) M
  sub● σ₁ σ₂ (𝓋 i)   = get● σ₁ σ₂ i
  sub● σ₁ σ₂ (ƛ M)   = ƛ & sublift● σ₁ σ₂ M
  sub● σ₁ σ₂ (M ∙ N) = _∙_ & sub● σ₁ σ₂ M
                           ⊗ sub● σ₁ σ₂ N
  sub● σ₁ σ₂ (M , N) = _,_ & sub● σ₁ σ₂ M
                           ⊗ sub● σ₁ σ₂ N
  sub● σ₁ σ₂ (π₁ M)  = π₁ & sub● σ₁ σ₂ M
  sub● σ₁ σ₂ (π₂ M)  = π₂ & sub● σ₁ σ₂ M
  sub● σ₁ σ₂ τ       = refl

  sublift● : ∀ {Γ Ξ Φ A B} → (σ₁ : Γ ⊢⋆ Ξ) (σ₂ : Ξ ⊢⋆ Φ) (M : Φ , B ⊢ A)
                           → sub (liftₛ {B} (σ₂ ● σ₁)) M ≡
                              (sub (liftₛ σ₁) ∘ sub (liftₛ σ₂)) M
  sublift● σ₁ σ₂ M = (λ σ′ → sub (σ′ , 0) M)
                     & ( comp●◐ (wkₑ idₑ) σ₁ σ₂
                       ⦙ (σ₂ ●_) & (lid◑ (wkₛ σ₁) ⁻¹)
                       ⦙ comp●◑ (liftₛ σ₁) (wkₑ idₑ) σ₂ ⁻¹
                       )
                   ⦙ sub● (liftₛ σ₁) (liftₛ σ₂) M

--------------------------------------------------------------------------------

-- (∈-idₛ)
idgetₛ : ∀ {Γ A} → (i : Γ ∋ A)
                 → getₛ idₛ i ≡ 𝓋 i
idgetₛ zero    = refl
idgetₛ (suc i) = get◐ (wkₑ idₑ) idₛ i
               ⦙ wk & idgetₛ i
               ⦙ 𝓋 ∘ suc & idgetₑ i

-- (Tm-idₛ)
idsub : ∀ {Γ A} → (M : Γ ⊢ A)
                → sub idₛ M ≡ M
idsub (𝓋 i)   = idgetₛ i
idsub (ƛ M)   = ƛ & idsub M
idsub (M ∙ N) = _∙_ & idsub M
                    ⊗ idsub N
idsub (M , N) = _,_ & idsub M
                    ⊗ idsub N
idsub (π₁ M)  = π₁ & idsub M
idsub (π₂ M)  = π₂ & idsub M
idsub τ       = refl

--------------------------------------------------------------------------------

-- (idrₛ)
rid● : ∀ {Γ Ξ} → (σ : Γ ⊢⋆ Ξ)
               → σ ● idₛ ≡ σ
rid● ∅       = refl
rid● (σ , M) = _,_ & rid● σ
                   ⊗ idsub M

-- (idlₛ)
lid● : ∀ {Γ Ξ} → (σ : Γ ⊢⋆ Ξ)
               → idₛ ● σ ≡ σ
lid● ∅       = refl
lid● (σ , M) = (_, M) & ( comp●◑ (σ , M) (wkₑ idₑ) idₛ
                        ⦙ lid● (idₑ ◑ σ)
                        ⦙ lid◑ σ
                        )

-- (assₛ)
assoc● : ∀ {Γ Ξ Φ Ψ} → (σ₁ : Γ ⊢⋆ Ξ) (σ₂ : Ξ ⊢⋆ Φ) (σ₃ : Φ ⊢⋆ Ψ)
                     → (σ₃ ● σ₂) ● σ₁ ≡ σ₃ ● (σ₂ ● σ₁)
assoc● σ₁ σ₂ ∅        = refl
assoc● σ₁ σ₂ (σ₃ , M) = _,_ & assoc● σ₁ σ₂ σ₃
                            ⊗ (sub● σ₁ σ₂ M ⁻¹)

--------------------------------------------------------------------------------

𝗦𝗧𝗟𝗖 : Category 𝒞 _⊢⋆_
𝗦𝗧𝗟𝗖 =
  record
    { idₓ    = idₛ
    ; _⋄_    = _●_
    ; lid⋄   = lid●
    ; rid⋄   = rid●
    ; assoc⋄ = assoc●
    }

subPsh : 𝒯 → Presheaf₀ 𝗦𝗧𝗟𝗖
subPsh A =
  record
    { Fₓ  = _⊢ A
    ; F   = sub
    ; idF = fext! idsub
    ; F⋄  = λ σ₁ σ₂ → fext! (sub● σ₂ σ₁)
    }

--------------------------------------------------------------------------------


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

open import HoTT
open import homotopy.CoHSpace

module homotopy.Cogroup where

record CogroupStructure {i} (X : Ptd i) : Type i where
  field
    co-h-struct : CoHSpaceStructure X
    ⊙inv : X ⊙→ X
  open CoHSpaceStructure co-h-struct public

  inv : de⊙ X → de⊙ X
  inv = fst ⊙inv

  field
    ⊙inv-l : ⊙Wedge-rec ⊙inv (⊙idf X) ⊙∘ ⊙coμ ⊙∼ ⊙cst
    ⊙assoc : ⊙–> (⊙∨-assoc X X X) ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
          ⊙∼ ⊙∨-fmap (⊙idf X) ⊙coμ ⊙∘ ⊙coμ

{-
module _ {i j : ULevel} {X : Ptd i} (CGS : CogroupStructure X) where

  open CogroupStructure CGS

  private
    lemma-inv : ⊙Wedge-rec (⊙Lift-fmap ⊙inv) (⊙idf (⊙Lift {j = j} X))
             ⊙∘ ⊙∨-fmap (⊙lift {j = j}) (⊙lift {j = j})
             ⊙∘ ⊙coμ
             ⊙∘ ⊙lower {j = j}
             == ⊙cst
  abstract
    lemma-inv =
        ! (⊙λ= (⊙∘-assoc
             (⊙Wedge-rec (⊙Lift-fmap ⊙inv) (⊙idf (⊙Lift {j = j} X)))
             (⊙∨-fmap ⊙lift ⊙lift)
             (⊙coμ ⊙∘ ⊙lower)))
      ∙ ap (_⊙∘ ⊙coμ ⊙∘ ⊙lower) (⊙λ= (⊙Wedge-rec-fmap (⊙Lift-fmap ⊙inv) (⊙idf _) ⊙lift ⊙lift))
      ∙ ap (_⊙∘ ⊙coμ ⊙∘ ⊙lower) (! (⊙λ= (⊙Wedge-rec-post∘ ⊙lift ⊙inv (⊙idf _))))
      ∙ ⊙λ= (⊙∘-assoc ⊙lift (⊙Wedge-rec ⊙inv (⊙idf X)) (⊙coμ ⊙∘ ⊙lower))
      ∙ ap ⊙Lift-fmap (⊙λ= ⊙inv-l)

  private
    ⊙coμ' : ⊙Lift {j = j} X ⊙→ ⊙Lift {j = j} X ⊙∨ ⊙Lift {j = j} X
    ⊙coμ' = ⊙∨-fmap (⊙lift {j = j}) (⊙lift {j = j}) ⊙∘ ⊙coμ ⊙∘ ⊙lower {j = j}

  private
    lemma-assoc' :
         ⊙–> (⊙∨-assoc (⊙Lift {j = j} X) (⊙Lift {j = j} X) (⊙Lift {j = j} X))
         ⊙∘ ⊙∨-fmap ⊙coμ' (⊙idf (⊙Lift {j = j} X))
         ⊙∘ ⊙∨-fmap (⊙lift {j = j}) (⊙lift {j = j}) ⊙∘ ⊙coμ
      == ⊙∨-fmap (⊙idf (⊙Lift {j = j} X)) ⊙coμ'
         ⊙∘ ⊙∨-fmap (⊙lift {j = j}) (⊙lift {j = j}) ⊙∘ ⊙coμ
  abstract
    lemma-assoc' =
      ⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X))
      ⊙∘ ⊙∨-fmap ⊙coμ' (⊙idf (⊙Lift {j = j} X))
      ⊙∘ ⊙∨-fmap ⊙lift ⊙lift ⊙∘ ⊙coμ
        =⟨ ! $ ap (⊙–> (⊙∨-assoc _ _ _) ⊙∘_) $ ⊙λ= $
             ⊙∘-assoc
              (⊙∨-fmap ⊙coμ' (⊙idf (⊙Lift X)))
              (⊙∨-fmap ⊙lift ⊙lift)
              ⊙coμ ⟩
      ⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X))
      ⊙∘ (⊙∨-fmap ⊙coμ' (⊙idf (⊙Lift X)) ⊙∘ ⊙∨-fmap ⊙lift ⊙lift)
      ⊙∘ ⊙coμ
        =⟨ ap (λ f → ⊙–> (⊙∨-assoc _ _ _ ) ⊙∘ f ⊙∘ ⊙coμ) $
               ! (⊙λ= $ ⊙∨-fmap-∘ ⊙coμ' ⊙lift (⊙idf (⊙Lift X)) ⊙lift)
               ∙ (⊙λ= $ ⊙∨-fmap-∘ (⊙∨-fmap ⊙lift ⊙lift) ⊙coμ ⊙lift (⊙idf X)) ⟩
      ⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X))
      ⊙∘ (⊙∨-fmap (⊙∨-fmap ⊙lift ⊙lift) ⊙lift ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X))
      ⊙∘ ⊙coμ
        =⟨ ap (⊙–> (⊙∨-assoc _ _ _ ) ⊙∘_) $ ⊙λ= $
            ⊙∘-assoc
              (⊙∨-fmap (⊙∨-fmap ⊙lift ⊙lift) ⊙lift)
              (⊙∨-fmap ⊙coμ (⊙idf X))
              ⊙coμ ⟩
      ⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X))
      ⊙∘ ⊙∨-fmap (⊙∨-fmap ⊙lift ⊙lift) ⊙lift
      ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
        =⟨ ! $ ⊙λ= $
            ⊙∘-assoc
              (⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X)))
              (⊙∨-fmap (⊙∨-fmap ⊙lift ⊙lift) ⊙lift)
              (⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ) ⟩
      (⊙–> (⊙∨-assoc (⊙Lift X) (⊙Lift X) (⊙Lift X)) ⊙∘ ⊙∨-fmap (⊙∨-fmap ⊙lift ⊙lift) ⊙lift)
      ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
        =⟨ ap (_⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ) $
            ⊙λ= $ ⊙∨-assoc-nat ⊙lift ⊙lift ⊙lift ⟩
      (⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift) ⊙∘ ⊙–> (⊙∨-assoc X X X))
      ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
        =⟨ ⊙λ= $ ⊙∘-assoc
            (⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift))
            (⊙–> (⊙∨-assoc X X X))
            (⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ) ⟩
      ⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift)
      ⊙∘ ⊙–> (⊙∨-assoc X X X) ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
        =⟨ ap (⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift) ⊙∘_) $ ⊙λ= ⊙assoc ⟩
      ⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift)
      ⊙∘ ⊙∨-fmap (⊙idf X) ⊙coμ ⊙∘ ⊙coμ
        =⟨ ! $ ⊙λ= $ ⊙∘-assoc
            (⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift))
            (⊙∨-fmap (⊙idf X) ⊙coμ)
            ⊙coμ ⟩
      (⊙∨-fmap ⊙lift (⊙∨-fmap ⊙lift ⊙lift)
       ⊙∘ ⊙∨-fmap (⊙idf X) ⊙coμ)
      ⊙∘ ⊙coμ
        =⟨ ap (_⊙∘ ⊙coμ) $
             ! (⊙λ= $ ⊙∨-fmap-∘ ⊙lift (⊙idf X) (⊙∨-fmap ⊙lift ⊙lift) ⊙coμ)
             ∙ (⊙λ= $ ⊙∨-fmap-∘ (⊙idf (⊙Lift X)) ⊙lift ⊙coμ' ⊙lift) ⟩
      (⊙∨-fmap (⊙idf (⊙Lift X)) ⊙coμ' ⊙∘ ⊙∨-fmap ⊙lift ⊙lift) ⊙∘ ⊙coμ
        =⟨ ⊙λ= $ ⊙∘-assoc (⊙∨-fmap (⊙idf (⊙Lift X)) ⊙coμ') (⊙∨-fmap ⊙lift ⊙lift) ⊙coμ ⟩
      ⊙∨-fmap (⊙idf (⊙Lift X)) ⊙coμ' ⊙∘ ⊙∨-fmap ⊙lift ⊙lift ⊙∘ ⊙coμ
        =∎

  private
    lemma-assoc :
         ⊙–> (⊙∨-assoc (⊙Lift {j = j} X) (⊙Lift {j = j} X) (⊙Lift {j = j} X))
         ⊙∘ ⊙∨-fmap ⊙coμ' (⊙idf (⊙Lift {j = j} X)) ⊙∘ ⊙coμ'
      == ⊙∨-fmap (⊙idf (⊙Lift {j = j} X)) ⊙coμ' ⊙∘ ⊙coμ'
  abstract
    lemma-assoc = ap (_⊙∘ ⊙lower) lemma-assoc'

  Lift-cogroup-structure : CogroupStructure (⊙Lift {j = j} X)
  Lift-cogroup-structure = record
    { co-h-struct = Lift-co-h-space-structure {j = j} co-h-struct
    ; ⊙inv = ⊙lift ⊙∘ ⊙inv ⊙∘ ⊙lower
    ; ⊙inv-l = ⊙app= lemma-inv
    ; ⊙assoc = ⊙app= lemma-assoc
    }
-}

module _ {i j} {X : Ptd i} (cogroup-struct : CogroupStructure X) (Y : Ptd j) where

  private
    module CGS = CogroupStructure cogroup-struct
    ⊙coμ = CGS.⊙coμ

    comp : (X ⊙→ Y) → (X ⊙→ Y) → (X ⊙→ Y)
    comp f g = ⊙Wedge-rec f g ⊙∘ ⊙coμ

    inv : (X ⊙→ Y) → (X ⊙→ Y)
    inv = _⊙∘ CGS.⊙inv

    abstract
      unit-l : ∀ f → comp ⊙cst f == f
      unit-l f =
        ⊙Wedge-rec ⊙cst f ⊙∘ ⊙coμ
          =⟨ ap2 (λ f g → ⊙Wedge-rec f g ⊙∘ ⊙coμ) (! $ ⊙λ= (⊙∘-cst-r f)) (! $ ⊙λ= (⊙∘-unit-r f)) ⟩
        ⊙Wedge-rec (f ⊙∘ ⊙cst) (f ⊙∘ ⊙idf X) ⊙∘ ⊙coμ
          =⟨ ap (_⊙∘ ⊙coμ) (! $ ⊙λ= $ ⊙Wedge-rec-post∘ f ⊙cst (⊙idf X)) ⟩
        (f ⊙∘ ⊙Wedge-rec ⊙cst (⊙idf X)) ⊙∘ ⊙coμ
          =⟨ ⊙λ= $ ⊙∘-assoc f (⊙Wedge-rec ⊙cst (⊙idf X)) ⊙coμ ⟩
        f ⊙∘ (⊙Wedge-rec ⊙cst (⊙idf X) ⊙∘ ⊙coμ)
          =⟨ ap (f ⊙∘_) (⊙λ= CGS.⊙unit-l) ⟩
        f ⊙∘ ⊙idf X
          =⟨ ⊙λ= (⊙∘-unit-r f) ⟩
        f
          =∎

      assoc : ∀ f g h → comp (comp f g) h == comp f (comp g h)
      assoc f g h =
        ⊙Wedge-rec (⊙Wedge-rec f g ⊙∘ ⊙coμ) h ⊙∘ ⊙coμ
          =⟨ ! $ ⊙λ= (⊙∘-unit-r h) |in-ctx (λ h → ⊙Wedge-rec (⊙Wedge-rec f g ⊙∘ ⊙coμ) h ⊙∘ ⊙coμ) ⟩
        ⊙Wedge-rec (⊙Wedge-rec f g ⊙∘ ⊙coμ) (h ⊙∘ ⊙idf X) ⊙∘ ⊙coμ
          =⟨ ! $ ⊙λ= (⊙Wedge-rec-fmap (⊙Wedge-rec f g) h ⊙coμ (⊙idf X)) |in-ctx _⊙∘ ⊙coμ ⟩
        (⊙Wedge-rec (⊙Wedge-rec f g) h ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X)) ⊙∘ ⊙coμ
          =⟨ ⊙λ= $ ⊙∘-assoc (⊙Wedge-rec (⊙Wedge-rec f g) h) (⊙∨-fmap ⊙coμ (⊙idf X)) ⊙coμ ⟩
        ⊙Wedge-rec (⊙Wedge-rec f g) h ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
          =⟨ ⊙λ= (⊙Wedge-rec-assoc f g h) |in-ctx _⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ ⟩
        (⊙Wedge-rec f (⊙Wedge-rec g h) ⊙∘ ⊙–> (⊙∨-assoc X X X)) ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
          =⟨ ⊙λ= $ ⊙∘-assoc (⊙Wedge-rec f (⊙Wedge-rec g h)) (⊙–> (⊙∨-assoc X X X)) (⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ) ⟩
        ⊙Wedge-rec f (⊙Wedge-rec g h) ⊙∘ ⊙–> (⊙∨-assoc X X X) ⊙∘ ⊙∨-fmap ⊙coμ (⊙idf X) ⊙∘ ⊙coμ
          =⟨ ⊙λ= CGS.⊙assoc |in-ctx ⊙Wedge-rec f (⊙Wedge-rec g h) ⊙∘_ ⟩
        ⊙Wedge-rec f (⊙Wedge-rec g h) ⊙∘ ⊙∨-fmap (⊙idf X) ⊙coμ ⊙∘ ⊙coμ
          =⟨ ! $ ⊙λ= $ ⊙∘-assoc (⊙Wedge-rec f (⊙Wedge-rec g h)) (⊙∨-fmap (⊙idf X) ⊙coμ) ⊙coμ ⟩
        (⊙Wedge-rec f (⊙Wedge-rec g h) ⊙∘ ⊙∨-fmap (⊙idf X) ⊙coμ) ⊙∘ ⊙coμ
          =⟨ ⊙λ= (⊙Wedge-rec-fmap f (⊙Wedge-rec g h) (⊙idf X) ⊙coμ) |in-ctx _⊙∘ ⊙coμ  ⟩
        ⊙Wedge-rec (f ⊙∘ ⊙idf X) (⊙Wedge-rec g h ⊙∘ ⊙coμ) ⊙∘ ⊙coμ
          =⟨ ⊙λ= (⊙∘-unit-r f) |in-ctx (λ f → ⊙Wedge-rec f (⊙Wedge-rec g h ⊙∘ ⊙coμ) ⊙∘ ⊙coμ) ⟩
        ⊙Wedge-rec f (⊙Wedge-rec g h ⊙∘ ⊙coμ) ⊙∘ ⊙coμ
          =∎

      inv-l : ∀ f → comp (inv f) f == ⊙cst
      inv-l f =
        ⊙Wedge-rec (f ⊙∘ CGS.⊙inv) f ⊙∘ ⊙coμ
          =⟨ ap (λ g → ⊙Wedge-rec (f ⊙∘ CGS.⊙inv) g ⊙∘ ⊙coμ) (! $ ⊙λ= (⊙∘-unit-r f)) ⟩
        ⊙Wedge-rec (f ⊙∘ CGS.⊙inv) (f ⊙∘ ⊙idf X) ⊙∘ ⊙coμ
          =⟨ ap (_⊙∘ ⊙coμ) (! $ ⊙λ= $ ⊙Wedge-rec-post∘ f CGS.⊙inv (⊙idf X)) ⟩
        (f ⊙∘ ⊙Wedge-rec CGS.⊙inv (⊙idf X)) ⊙∘ ⊙coμ
          =⟨ ⊙λ= $ ⊙∘-assoc f (⊙Wedge-rec CGS.⊙inv (⊙idf X)) ⊙coμ ⟩
        f ⊙∘ (⊙Wedge-rec CGS.⊙inv (⊙idf X) ⊙∘ ⊙coμ)
          =⟨ ap (f ⊙∘_) (⊙λ= CGS.⊙inv-l) ⟩
        f ⊙∘ ⊙cst
          =⟨ ⊙λ= (⊙∘-cst-r f) ⟩
        ⊙cst
          =∎

  cogroup⊙→-group-structure : GroupStructure (X ⊙→ Y)
  cogroup⊙→-group-structure = record
    { ident = ⊙cst
    ; inv = inv
    ; comp = comp
    ; unit-l = unit-l
    ; assoc = assoc
    ; inv-l = inv-l
    }


------------------------------------------------------------------------
-- A virtual machine
------------------------------------------------------------------------

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

module Lambda.Simplified.Partiality-monad.Inductive.Virtual-machine
  where

open import Equality.Propositional
open import Prelude hiding (⊥)

open import Monad equality-with-J

open import Partiality-monad.Inductive
open import Partiality-monad.Inductive.Fixpoints

open import Lambda.Simplified.Syntax
open import Lambda.Simplified.Virtual-machine

open Closure Code

-- A functional semantics for the VM.
--
-- For an alternative definition, see the semantics in
-- Lambda.Partiality-monad.Inductive.Virtual-machine, which is defined
-- without using a fixpoint combinator.

execP : State → Partial State (λ _ → Maybe Value) (Maybe Value)
execP s with step s
... | continue s′ = rec s′
... | done v      = return (just v)
... | crash       = return nothing

exec : State → Maybe Value ⊥
exec s = fixP execP s


-- Closed monoid in the skew-closed category of families
module SOAS.Abstract.Monoid {T : Set} where

open import SOAS.Common
open import SOAS.Context
open import SOAS.Variable
open import SOAS.ContextMaps.Combinators

open import SOAS.Construction.Structure as Structure

open import SOAS.Abstract.Hom {T}
open import SOAS.Abstract.Coalgebra using (module Sorted) ; open Sorted
open import SOAS.Families.Core {T}

open import Categories.Category.Equivalence using (StrongEquivalence)
open import Categories.NaturalTransformation.NaturalIsomorphism using (niHelper)

open import SOAS.Coalgebraic.Map {T}
open import SOAS.Coalgebraic.Lift {T}

private
  variable
    Γ Δ Θ : Ctx
    α β γ : T

record Mon (𝒳 : Familyₛ) : Set where
  field
    η : ℐ ⇾̣ 𝒳
    μ : 𝒳 ⇾̣ 〖 𝒳 , 𝒳 〗

    lunit : {σ : Γ ~[ 𝒳 ]↝ Δ}{v : ℐ α Γ}
          → μ (η v) σ ≡ σ v
    runit : {t : 𝒳 α Γ} → μ t η ≡ t
    assoc : {σ : Γ ~[ 𝒳 ]↝ Δ}{ς : Δ ~[ 𝒳 ]↝ Θ} {t : 𝒳 α Γ}
          → μ (μ t σ) ς ≡ μ t (λ v → μ (σ v) ς)

  -- Congruence in both arguments of the multiplication
  μ≈₁ : {t₁ t₂ : 𝒳 α Γ}{σ : Γ ~[ 𝒳 ]↝ Δ}
      → t₁ ≡ t₂
      → μ t₁ σ ≡ μ t₂ σ
  μ≈₁ refl = refl

  μ≈₂ : {t : 𝒳 α Γ}{σ ς : Γ ~[ 𝒳 ]↝ Δ}
      → ({τ : T}{v : ℐ τ Γ} → σ v ≡ ς v)
      → μ t σ ≡ μ t ς
  μ≈₂ {t = t} p = cong (μ t) (dext′ p)

  -- Monoids are pointed coalgebras
  ᵇ : Coalg 𝒳
  ᵇ = record { r = λ t ρ →  μ t (η ∘ ρ) ; counit = runit
    ; comult = λ { {t = t} → sym (trans assoc (μ≈₂ lunit)) } }

  ᴮ : Coalgₚ 𝒳
  ᴮ = record { ᵇ = ᵇ ; η = η ; r∘η = lunit }

  -- Single-variable substitution
  [_/] : 𝒳 α Γ → 𝒳 β (α ∙ Γ) → 𝒳 β Γ
  [ s /] t = μ t (add 𝒳 s η)

  -- Substitution for second variable
  [_/]′ : 𝒳 α Γ → 𝒳 γ (β ∙ α ∙ Γ) → 𝒳 γ (β ∙ Γ)
  [ s /]′ t = μ t (lift₁ ᴮ (add 𝒳 s η))

  -- Substitution for top two variables
  [_,_/]₂ : 𝒳 α Γ → 𝒳 β Γ → 𝒳 γ (α ∙ β ∙ Γ) → 𝒳 γ Γ
  [ s₁ , s₂ /]₂ t = μ t (add 𝒳 s₁ (add 𝒳 s₂ η))

  open Coalgₚ ᴮ public using (r ; r∘η)

  -- Multiplication is coalgebraic map
  μᶜ : Coalgebraic ᴮ ᴮ ᴮ μ
  μᶜ = record { r∘f = assoc ; f∘r = trans assoc (μ≈₂ lunit) ; f∘η = lunit }

-- Monoid homomorphisms
record Mon⇒ {𝒳 𝒴 : Familyₛ}(𝒳ᵐ : Mon 𝒳)(𝒴ᵐ : Mon 𝒴)
               (f : 𝒳 ⇾̣ 𝒴) : Set where

  private module 𝒳 = Mon 𝒳ᵐ
  private module 𝒴 = Mon 𝒴ᵐ

  field
    ⟨η⟩ : {v : ℐ α Γ} → f (𝒳.η v) ≡ 𝒴.η v
    ⟨μ⟩ : {σ : Γ ~[ 𝒳 ]↝ Δ}{t : 𝒳 α Γ}
        → f (𝒳.μ t σ) ≡ 𝒴.μ (f t) (f ∘ σ)

  ᵇ⇒ : Coalg⇒ 𝒳.ᵇ 𝒴.ᵇ f
  ᵇ⇒ = record { ⟨r⟩ = trans ⟨μ⟩ (𝒴.μ≈₂ ⟨η⟩) }

  ᴮ⇒ : Coalgₚ⇒ 𝒳.ᴮ 𝒴.ᴮ f
  ᴮ⇒ = record { ᵇ⇒ = ᵇ⇒ ; ⟨η⟩ = ⟨η⟩ }

  -- Preservation of multiplication and unit implies the semantic substitution
  -- lemma for single- and double-variable substitution
  sub-lemma : (s : 𝒳 α Γ)(t : 𝒳 β (α ∙ Γ)) →
              f (𝒳.[ s /] t) ≡ 𝒴.[ f s /] (f t)
  sub-lemma s t = trans ⟨μ⟩ (cong (𝒴.μ (f t))
                        (dext λ{ new → refl ; (old y) → ⟨η⟩}))

  sub-lemma₂ : (s₁ : 𝒳 α Γ)(s₂ : 𝒳 β Γ)(t : 𝒳 γ (α ∙ β ∙ Γ)) →
               f (𝒳.[ s₁ , s₂ /]₂ t) ≡ 𝒴.[ f s₁ , f s₂ /]₂ (f t)
  sub-lemma₂ s₁ s₂ t = trans ⟨μ⟩ (cong (𝒴.μ (f t))
                             (dext λ{ new → refl ; (old new) → refl
                                    ; (old (old y)) → ⟨η⟩}))

module MonoidStructure = Structure 𝔽amiliesₛ Mon

-- Category of substitution monoids
𝕄onoids : Category 1ℓ 0ℓ 0ℓ
𝕄onoids = MonoidStructure.StructCat record
  { IsHomomorphism = Mon⇒
  ; id-hom = record { ⟨η⟩ = refl ; ⟨μ⟩ = refl }
  ; comp-hom = λ g f gᵐ⇒ fᵐ⇒ → record
    { ⟨η⟩ = trans (cong g (⟨η⟩ fᵐ⇒)) (⟨η⟩ gᵐ⇒)
    ; ⟨μ⟩ = trans (cong g (⟨μ⟩ fᵐ⇒)) (⟨μ⟩ gᵐ⇒)
    }
  } where open Mon⇒

module 𝕄on = Category 𝕄onoids

Monoid : Set₁
Monoid = 𝕄on.Obj

Monoid⇒ : Monoid → Monoid → Set
Monoid⇒ = 𝕄on._⇒_

module AsMonoid (ℳᵐ : Monoid) where
  open Object ℳᵐ renaming (𝐶 to ℳ; ˢ to ᵐ) public
  open Mon ᵐ public

module AsMonoid⇒ {ℳᵐ 𝒩ᵐ : Monoid} (fᵐ⇒ : Monoid⇒ ℳᵐ 𝒩ᵐ) where
  open Morphism fᵐ⇒ renaming (𝑓 to f ; ˢ⇒ to ᵐ⇒) public
  open Mon⇒ ᵐ⇒ public


open import Data.Nat
open import Data.Fin
open import Data.List
open import Data.List.Membership.Propositional
open import Data.List.Relation.Unary.All as All
open import Data.List.All.Properties.Extra
open import Data.Vec hiding (_∷ʳ_ ; _>>=_ ; init)
open import Data.Product
open import Relation.Binary.PropositionalEquality hiding ([_])
open ≡-Reasoning
open import Data.Unit
open import Data.List.Prefix
open import Data.List.Properties.Extra
open import Data.Maybe
open import Common.Weakening

-- This file contains the Agda Scope Graph Library described in
-- Section 4 of the paper.

-- The definitions are parameterized by a scope graph, where a scope
-- graph is a finite map from scope identifiers to scope shape
-- descriptors.
--
-- The scope graph library is also parametric in what the notion of
-- type is for declarations in the scope graph.
--
-- We use modules for this library parameterization.  The `k`
-- parameter of the `ScopesFrames` module represents the number of
-- scope identifiers in the scope graph that all definitions are
-- parameterized over; and the `Ty` parameter is a language-specific
-- notion of type.

module ScopesFrames.ScopesFrames (k : ℕ) (Ty : Set) where

------------------
-- SCOPE GRAPHS --
------------------

-- Type aliases for scope identifiers (`Scope`s) and scope graphs
-- (`Graph`s).
--
-- As described in the paper, scope shapes are given by a list of
-- types corresponding to the declarations of the scope, and a list of
-- scopes, corresponding to the edges of the scope.

Scope = Fin k
Graph = Scope → (List Ty × List Scope)

-- All the following definitions are parameterized by a scope graph
-- `g`.

module UsesGraph (g : Graph) where

  -- Some projection functions:
  declsOf : Scope → List Ty     ;  declsOf s  = proj₁ (g s)
  edgesOf : Scope → List Scope  ;  edgesOf s  = proj₂ (g s)

  -- A resolution path is a witness that we can traverse a sequence of
  -- scope edges (`_⟶_`) in `g` to arrive at a declaration of type
  -- `t`:

  data _⟶_ : Scope → Scope → Set where
    []   :  ∀ {s} → s ⟶ s
    _∷_  :  ∀ {s s' s''} → s' ∈ edgesOf s → s' ⟶ s'' → s ⟶ s''

  -- path concatenation
  concatₚ : ∀ {s s' s''} → s ⟶ s' → s' ⟶ s'' → s ⟶ s''
  concatₚ [] p₂ = p₂
  concatₚ (x ∷ p₁) p₂ = x ∷ (concatₚ p₁ p₂)

  data _↦_ (s : Scope) (t : Ty) : Set where
    path : ∀{s'} → s ⟶ s' → t ∈ declsOf s' → s ↦ t

  prepend : ∀ {s s' t} → s ⟶ s' → s' ↦ t → s ↦ t
  prepend p (path p' d) = path (concatₚ p p') d

  -- The definitions above fully define scope graphs.  The remaining
  -- definitions in this file are intrinsically-typed by this notion
  -- of scope graph.

  ----------------------
  -- FRAMES AND HEAPS --
  ----------------------

  -- Heap types summarize what the scope is for each frame location in
  -- the heap:

  HeapTy = List Scope

  -- A frame pointer `Frame s Σ` is a witness that there is a frame
  -- location scoped by `s` in a heap typed by `Σ`:

  Frame : Scope → (Σ : HeapTy) → Set
  Frame s Σ = s ∈ Σ

  -- Concrete frames and heaps store values that are weakened when the
  -- heap is extended.  The following definitions are parameterized by
  -- a language-specific notion of weakenable value.

  module UsesVal (Val : Ty → HeapTy → Set)
                 (weaken-val : ∀ {t Σ Σ'} → Σ ⊑ Σ' → Val t Σ → Val t Σ') where

    -- Slots are given by a list of values that are in one-to-one
    -- correspondence with a list of declarations (types):

    Slots : (ds : List Ty) → (Σ : HeapTy) → Set
    Slots ds Σ = All (λ t → Val t Σ) ds

    -- Links are given by a list of frame pointers (links) that are in
    -- one-to-one correspondence with a list of edges (scopes):

    Links : (es : List Scope) → (Σ : HeapTy) → Set
    Links es Σ = All (λ s → Frame s Σ) es

    -- A heap frame for a scope `s` is given by a set of slots and
    -- links that are in one-to-one correspondence with the
    -- declarations and edges of the scope:

    HeapFrame : Scope → HeapTy → Set
    HeapFrame s Σ = Slots (declsOf s) Σ × Links (edgesOf s) Σ

    -- A heap typed by `Σ` is given by a list of heap frames such that
    -- each frame location is in the heap is typed by the
    -- corresponding location in `Σ`.

    Heap : (Σ : HeapTy) → Set
    Heap Σ = All (λ s → HeapFrame s Σ) Σ

    ----------------------
    -- FRAME OPERATIONS --
    ----------------------

    -- Frame initialization extends the heap, requiring heap type
    -- weakening.  The `Common.Weakening` module defines a generic
    -- `Weakenable` record, which we instantiate for each entity that
    -- is required to be weakenable.  We use Agda's instance arguments
    -- to automatically resolve the right notion of weakening where
    -- possible.

    -- We add some instances of the Weakenable typeclass
    -- to the instance argument scope:

    instance
      weaken-val' : ∀ {t} → Weakenable (λ Σ → Val t Σ)
      weaken-val' = record { wk = weaken-val }

      weaken-frame : ∀ {s} → Weakenable (Frame s)
      weaken-frame = record { wk = Weakenable.wk any-weakenable }

    -- Frame initialization takes as argument:
    --
    -- * a scope `s`;
    --
    -- * a scope shape witness which asserts that looking up `s` in
    --   the current scope graph `g` yields a scope shape where the
    --   declarations are given by a list of types `ds`, and scope
    --   edges are given by a list of scopes `es`;
    --
    -- * a set of slots typed by `ds`;
    --
    -- * a set of links typed by `es`; and
    --
    -- * a heap typed by the heap type `Σ` which also types the slots
    --   and links.
    --
    -- The operation returns an extended heap (`∷ʳ` appends a single
    -- element to a list, and is defined in `Data.List.Base` in the
    -- Agda Standard Library) and a frame pointer into the extended
    -- heap, i.e., the newly allocated and initialized frame.

    initFrame   :  (s : Scope) → ∀ {Σ ds es}⦃ shape : g s ≡ (ds , es) ⦄ →
                   Slots ds Σ → Links es Σ → Heap Σ → Frame s (Σ ∷ʳ s) × Heap (Σ ∷ʳ s)
    initFrame s {Σ} ⦃ refl ⦄ slots links h =
      let ext = ∷ʳ-⊒ s Σ -- heap extension fact
          f'  = ∈-∷ʳ Σ s -- updated frame pointer witness
          h'  = (wk ext h) all-∷ʳ (wk ext slots , wk ext links) -- extended heap
      in (f' , h')

    -- Frames may be self-referential.  For example, the values stored
    -- in the slots of a frame may contain pointers to the frame
    -- itself.  The `initFrame` function above does not support
    -- initializing such self-referential slots, since the slots are
    -- assumed to be typed under the unextended heap.
    --
    -- The `initFrameι` function we now define takes as argument a
    -- function to be used to initialize possibly-self-referential
    -- frame slots.  The function takes as argument a frame pointer
    -- into a heap extended by the scope that we are currently
    -- initializing, and slots are typed under this extended heap;
    -- hence the slots can have self-references to the frame currently
    -- being initialized.

    initFrameι : (s : Scope) → ∀ {Σ ds es}⦃ shape : g s ≡ (ds , es) ⦄ →
                 (slotsf : Frame s (Σ ∷ʳ s) → Slots ds (Σ ∷ʳ s)) → Links es Σ → Heap Σ →
                 Frame s (Σ ∷ʳ s) × Heap (Σ ∷ʳ s)
    initFrameι s {Σ} ⦃ refl ⦄ slotsf links h =
      let ext = ∷ʳ-⊒ s Σ -- heap extension fact
          f'  = ∈-∷ʳ Σ s -- updated frame pointer witness
          h'  = (wk ext h) all-∷ʳ (slotsf f' , wk ext links) -- extended heap
      in (f' , h')

    -- Given a witness that a declaration typed by `t` is in a scope,
    -- and a frame pointer, we can fetch the well-typed value stored
    -- in the corresponding frame slot:

    getSlot : ∀ {s t Σ} → t ∈ declsOf s → Frame s Σ → Heap Σ → Val t Σ
    getSlot d f h
      with (All.lookup h f)
    ...  | (slots , links) = All.lookup slots d

    -- Given a witness that a declaration typed by `t` is in a scope,
    -- and a frame pointer, we can mutate the corresponding slot in a
    -- type preserving manner:

    setSlot : ∀ {s t Σ} → t ∈ declsOf s → Val t Σ → Frame s Σ → Heap Σ → Heap Σ
    setSlot d v f h
      with (All.lookup h f)
    ...  | (slots , links) = h All.[ f ]≔ (slots All.[ d ]≔ v , links)

    -- ... and similarly for edges:

    getLink : ∀ {s s' Σ} → s' ∈ edgesOf s → Frame s Σ → Heap Σ → Frame s' Σ
    getLink e f h
      with (All.lookup h f)
    ...  | (slots , links) = All.lookup links e

    setLink : ∀ {s s' Σ} → s' ∈ edgesOf s → Frame s' Σ → Frame s Σ → Heap Σ → Heap Σ
    setLink e f' f h
      with (All.lookup h f)
    ...  | (slots , links) = h All.[ f ]≔ (slots , links All.[ e ]≔ f')

    -- Given a witness that there is a path through the scope graph
    -- from scope `s` to scope `s'`, a frame typed by `s`, and a
    -- well-typed heap, we can traverse the path by following the
    -- corresponding frame links in the heap to arrive at a frame
    -- scoped by `s'`:

    getFrame : ∀ {s s' Σ} → (s ⟶ s') → Frame s Σ → Heap Σ → Frame s' Σ
    getFrame []      f h = f
    getFrame (e ∷ p) f h
      with (All.lookup h f)
    ...  | (slots , links) = getFrame p (All.lookup links e) h

    -- Given the definitions above, we can define some shorthand
    -- functions for getting and setting values:

    getVal  :  ∀ {s t} → (s ↦ t) → ∀ {Σ} → Frame s Σ → Heap Σ → Val t Σ
    getVal (path p d) f h = getSlot d (getFrame p f h) h

    setVal  :  ∀ {s t Σ} → (s ↦ t) → Val t Σ → Frame s Σ → Heap Σ → Heap Σ
    setVal (path p d) v f h = setSlot d v (getFrame p f h) h


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

open import HoTT
open import cohomology.CupProduct.OnEM.InAllDegrees
open import cohomology.CupProduct.OnEM.CommutativityInAllDegrees
open import cohomology.EMModel
open import cohomology.Theory
open import groups.ToOmega
open import homotopy.EilenbergMacLane
open import homotopy.EilenbergMacLaneFunctor
open import homotopy.Freudenthal
open import homotopy.SuspensionLoopSpaceInverse

module cohomology.CupProduct.Definition {i} (X : Ptd i) where

private
  module M {k} (A : AbGroup k) = CohomologyTheory (EM-Cohomology A)
  open M

⊙×-diag : X ⊙→ X ⊙× X
⊙×-diag = (λ x → x , x) , idp

smin-map : ∀ {j k} {Y : Ptd j} {Z : Ptd k}
  → X ⊙→ Y
  → X ⊙→ Z
  → X ⊙→ Y ⊙× Z
smin-map f g = ⊙×-fmap f g ⊙∘ ⊙×-diag

smin-map-⊙×-swap : ∀ {j k} (Y : Ptd j) (Z : Ptd k)
  (f : X ⊙→ Y)
  (g : X ⊙→ Z)
  → ⊙×-swap ⊙∘ smin-map g f == smin-map f g
smin-map-⊙×-swap Y Z (_ , idp) (_ , idp) = idp

module _ (G : AbGroup i) (H : AbGroup i) where

  private
    module G⊗H = TensorProduct G H
    module H⊗G = TensorProduct H G
  open EMExplicit

  ⊙Ω×-cp-seq : ∀ (m n : ℕ) → (⊙Ω (⊙EM G (S m)) ⊙× ⊙Ω (⊙EM H (S n))) ⊙–→ ⊙Ω (⊙EM G⊗H.abgroup (S (m + n)))
  ⊙Ω×-cp-seq m n =
    ⊙<– (spectrum G⊗H.abgroup (m + n)) ◃⊙∘
    ⊙×-cp G H m n ◃⊙∘
    ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf

  ⊙Ω×-cp : ∀ (m n : ℕ) → ⊙Ω (⊙EM G (S m)) ⊙× ⊙Ω (⊙EM H (S n)) ⊙→ ⊙Ω (⊙EM G⊗H.abgroup (S (m + n)))
  ⊙Ω×-cp m n = ⊙compose (⊙Ω×-cp-seq m n)

  _∪_  : ∀ {m n : ℕ} → CEl G (pos m) X → CEl H (pos n) X → CEl G⊗H.abgroup (pos (m + n)) X
  _∪_ {m} {n} = Trunc-fmap2 {n = 0} (λ s' t' → ⊙Ω×-cp m n ⊙∘ smin-map s' t')

module _ (G : AbGroup i) (H : AbGroup i) where

  private
    module G⊗H = TensorProduct G H
    module H⊗G = TensorProduct H G
  open EMExplicit

  abstract
    ⊙Ω×-cp-comm : ∀ (m n : ℕ)
      → ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
        ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ◃⊙∘
        ⊙Ω×-cp G H m n ◃⊙idf
        =⊙∘
        ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
        ⊙Ω×-cp H G n m ◃⊙∘
        ⊙×-swap ◃⊙idf
    ⊙Ω×-cp-comm m n =
      ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
      ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ◃⊙∘
      ⊙Ω×-cp G H m n ◃⊙idf
        =⊙∘⟨ 2 & 1 & ⊙expand (⊙Ω×-cp-seq G H m n) ⟩
      ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
      ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ◃⊙∘
      ⊙<– (spectrum G⊗H.abgroup (m + n)) ◃⊙∘
      ⊙×-cp G H m n ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘⟨ 1 & 2 & !⊙∘ $ ⊙<–-spectrum-natural G⊗H.abgroup H⊗G.abgroup G⊗H.swap (m + n) ⟩
      ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
      ⊙<– (spectrum H⊗G.abgroup (m + n)) ◃⊙∘
      ⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (m + n) ◃⊙∘
      ⊙×-cp G H m n ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘⟨ 0 & 2 & !⊙∘ $ ⊙transport-natural-=⊙∘
               (+-comm m n)
               (λ k → ⊙<– (spectrum H⊗G.abgroup k)) ⟩
      ⊙<– (spectrum H⊗G.abgroup (n + m)) ◃⊙∘
      ⊙transport (λ k → ⊙EM H⊗G.abgroup k) (+-comm m n) ◃⊙∘
      ⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (m + n) ◃⊙∘
      ⊙×-cp G H m n ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘⟨ 1 & 3 & ⊙×-cp-comm G H m n ⟩
      ⊙<– (spectrum H⊗G.abgroup (n + m)) ◃⊙∘
      ⊙cond-neg H⊗G.abgroup (n + m) (and (odd m) (odd n)) ◃⊙∘
      ⊙×-cp H G n m ◃⊙∘
      ⊙×-swap ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘⟨ 0 & 2 & ⊙transport-natural-=⊙∘
               (Bool-elim (inv-path H⊗G.abgroup) idp (and (odd m) (odd n)))
               (λ A → ⊙<– (spectrum A (n + m))) ⟩
      ⊙transport (λ A → ⊙Ω (⊙EM A (S (n + m)))) neg ◃⊙∘
      ⊙<– (spectrum H⊗G.abgroup (n + m)) ◃⊙∘
      ⊙×-cp H G n m ◃⊙∘
      ⊙×-swap ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘₁⟨ 0 & 1 &
              ⊙transport-⊙coe (λ A → ⊙Ω (⊙EM A (S (n + m)))) neg ∙
              ap ⊙coe (ap-∘ ⊙Ω (λ A → ⊙EM A (S (n + m))) neg) ∙
              ! (⊙transport-⊙coe ⊙Ω (ap (λ A → ⊙EM A (S (n + m))) neg)) ∙
              ⊙transport-⊙Ω (ap (λ A → ⊙EM A (S (n + m))) neg) ∙
              ap ⊙Ω-fmap (! (⊙transport-⊙coe (λ A → ⊙EM A (S (n + m))) neg)) ⟩
      ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
      ⊙<– (spectrum H⊗G.abgroup (n + m)) ◃⊙∘
      ⊙×-cp H G n m ◃⊙∘
      ⊙×-swap ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ◃⊙idf
        =⊙∘⟨ 3 & 2 & =⊙∘-in {gs = ⊙×-fmap (⊙–> (spectrum H n)) (⊙–> (spectrum G m)) ◃⊙∘
                                  ⊙×-swap ◃⊙idf} $
                     ! $ ⊙λ= $ ⊙×-swap-natural (⊙–> (spectrum G m)) (⊙–> (spectrum H n)) ⟩
      ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
      ⊙<– (spectrum H⊗G.abgroup (n + m)) ◃⊙∘
      ⊙×-cp H G n m ◃⊙∘
      ⊙×-fmap (⊙–> (spectrum H n)) (⊙–> (spectrum G m)) ◃⊙∘
      ⊙×-swap ◃⊙idf
        =⊙∘⟨ 1 & 3 & ⊙contract ⟩
      ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
      ⊙Ω×-cp H G n m ◃⊙∘
      ⊙×-swap ◃⊙idf ∎⊙∘
      where
      neg : H⊗G.abgroup == H⊗G.abgroup
      neg = Bool-elim (inv-path H⊗G.abgroup) idp (and (odd m) (odd n))

  ∪-swap : ∀ (m n : ℕ)
    → CEl G⊗H.abgroup (pos (m + n)) X → CEl H⊗G.abgroup (pos (n + m)) X
  ∪-swap m n =
    transport (λ k → CEl H⊗G.abgroup (pos k) X) (+-comm m n) ∘
    EM-CEl-coeff-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (pos (m + n)) X

  maybe-inv : ∀ (n : ℤ) → Bool → CEl H⊗G.abgroup n X → CEl H⊗G.abgroup n X
  maybe-inv n = Bool-rec (Group.inv (C H⊗G.abgroup n X)) (idf _)

  private
    _G∪H_ = _∪_ G H
    _H∪G_ = _∪_ H G

  ∪-comm : ∀ {m n : ℕ}
    (s : CEl G (pos m) X)
    (t : CEl H (pos n) X)
    → ∪-swap m n (s G∪H t) ==
      maybe-inv (pos (n + m)) (and (odd m) (odd n)) (t H∪G s)
  ∪-comm {m} {n} =
    Trunc-elim {{λ s → Π-level (λ t → =-preserves-level Trunc-level)}} $ λ s' →
    Trunc-elim {{λ t → =-preserves-level Trunc-level}} $ λ t' →
    transport (λ k → CEl H⊗G.abgroup (pos k) X) (+-comm m n)
    [ ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ⊙∘
      ⊙Ω×-cp G H m n ⊙∘
      smin-map s' t' ]
      =⟨ app= step₁
         [ ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ⊙∘
           ⊙Ω×-cp G H m n ⊙∘
           smin-map s' t' ] ⟩
    [ ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ⊙∘
      ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ⊙∘
      ⊙Ω×-cp G H m n ⊙∘
      smin-map s' t' ]
      =⟨ ap [_] (=⊙∘-out (step₂ s' t')) ⟩
    Trunc-fmap (⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ⊙∘_)
    [ ⊙Ω×-cp H G n m ⊙∘ smin-map t' s' ]
      =⟨ app= (step₃ (n + m) (and (odd m) (odd n))) [ ⊙Ω×-cp H G n m ⊙∘ smin-map t' s' ] ⟩
    maybe-inv (pos (n + m)) (and (odd m) (odd n))
    [ ⊙Ω×-cp H G n m ⊙∘ smin-map t' s' ] =∎
    where
    step₁ : transport (λ k → CEl H⊗G.abgroup (pos k) X) (+-comm m n) ==
            Trunc-fmap (⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ⊙∘_)
    step₁ =
      transport (λ k → CEl H⊗G.abgroup (pos k) X) (+-comm m n)
        =⟨ ap coe (ap-∘ (Trunc 0) (λ k → X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)) ⟩
      transport (Trunc 0) (ap (λ k → X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n))
        =⟨ transport-Trunc (ap (λ k → X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)) ⟩
      Trunc-fmap (transport (λ k → X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n))
        =⟨ ap (Trunc-fmap ∘ coe) (ap-∘ (X ⊙→_) (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)) ⟩
      Trunc-fmap (transport (X ⊙→_) (ap (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)))
        =⟨ ap Trunc-fmap $ λ= $ transport-post⊙∘ X (ap (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)) ⟩
      Trunc-fmap (⊙coe (ap (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n)) ⊙∘_)
        =⟨ ap (λ g → Trunc-fmap (g ⊙∘_)) $
           ! $ ⊙transport-⊙coe (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ⟩
      Trunc-fmap (⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ⊙∘_) =∎
    step₂ : ∀ (s' : X ⊙→ ⊙Ω (⊙EM G (S m))) (t' : X ⊙→ ⊙Ω (⊙EM H (S n)))
      → ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
        ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ◃⊙∘
        ⊙Ω×-cp G H m n ◃⊙∘
        smin-map s' t' ◃⊙idf
        =⊙∘
        ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
        ⊙Ω×-cp H G n m ◃⊙∘
        smin-map t' s' ◃⊙idf
    step₂ s' t' =
      ⊙transport (λ k → ⊙Ω (⊙EM H⊗G.abgroup (S k))) (+-comm m n) ◃⊙∘
      ⊙Ω-fmap (⊙EM-fmap G⊗H.abgroup H⊗G.abgroup G⊗H.swap (S (m + n))) ◃⊙∘
      ⊙Ω×-cp G H m n ◃⊙∘
      smin-map s' t' ◃⊙idf
        =⊙∘⟨ 0 & 3 & ⊙Ω×-cp-comm m n ⟩
      ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
      ⊙Ω×-cp H G n m ◃⊙∘
      ⊙×-swap ◃⊙∘
      smin-map s' t' ◃⊙idf
        =⊙∘⟨ 2 & 2 & =⊙∘-in {gs = smin-map t' s' ◃⊙idf} $
             smin-map-⊙×-swap (⊙Ω (⊙EM H (S n))) (⊙Ω (⊙EM G (S m))) t' s' ⟩
      ⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S (n + m)) (and (odd m) (odd n))) ◃⊙∘
      ⊙Ω×-cp H G n m ◃⊙∘
      smin-map t' s' ◃⊙idf ∎⊙∘
    step₃ : ∀ (k : ℕ) (b : Bool) →
      Trunc-fmap (⊙Ω-fmap (⊙cond-neg H⊗G.abgroup (S k) b) ⊙∘_) ==
      maybe-inv (pos k) b
    step₃ k false =
      Trunc-fmap (⊙Ω-fmap (⊙idf (⊙EM H⊗G.abgroup (S k))) ⊙∘_)
        =⟨ ap (λ g → Trunc-fmap (g ⊙∘_)) ⊙Ω-fmap-idf ⟩
      Trunc-fmap (⊙idf (⊙Ω (⊙EM H⊗G.abgroup (S k))) ⊙∘_)
        =⟨ ap Trunc-fmap (λ= (⊙λ= ∘ ⊙∘-unit-l)) ⟩
      Trunc-fmap (idf (X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k))))
        =⟨ λ= Trunc-fmap-idf ⟩
      idf (Trunc 0 (X ⊙→ ⊙Ω (⊙EM H⊗G.abgroup (S k)))) =∎
    step₃ k true =
      Trunc-fmap (⊙Ω-fmap (⊙transport (λ A → ⊙EM A (S k)) (inv-path H⊗G.abgroup)) ⊙∘_)
        =⟨ ap (λ f → Trunc-fmap (⊙Ω-fmap f ⊙∘_)) $
           ⊙transport-⊙EM-uaᴬᴳ H⊗G.abgroup H⊗G.abgroup (inv-iso H⊗G.abgroup) (S k) ⟩
      Trunc-fmap (⊙Ω-fmap (⊙EM-fmap H⊗G.abgroup H⊗G.abgroup (inv-hom H⊗G.abgroup) (S k)) ⊙∘_)
        =⟨ ap GroupHom.f (EM-C-coeff-fmap-inv-hom H⊗G.abgroup (pos k) X) ⟩
      Group.inv (C H⊗G.abgroup (pos k) X) =∎


module DoBlock3 where

data Id
  (A : Set)
  : Set
  where

  id
    : A
    → Id A

_>>=_
  : {A B : Set}
  → Id A
  → (A → Id B)
  → Id B
id x >>= f
  = f x

_>>_
  : {A B : Set}
  → Id A
  → Id B
  → Id B
_ >> y
  = y

f
  : {A : Set}
  → A
  → Id A
f x
  = do
    id x
    id x


{-# 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 ; elim to sElim ; elim2 to sElim2 ; setTruncIsSet to §)
open import Cubical.HITs.Nullification
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.HITs.SmashProduct.Base
open import Cubical.Algebra.Group
open import Cubical.Algebra.Semigroup
open import Cubical.Algebra.Monoid
open import Cubical.Foundations.Equiv.HalfAdjoint

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

open import Cubical.ZCohomology.KcompPrelims

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

{- 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 n) A i = ∥ coHomRed+1.helpLemma A i {C = ((coHomK (suc n)) , ∣ north ∣)} i ∥₂

-----------

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

ΩKn+1→Kn : {n : ℕ} → typ (Ω (coHomK-ptd (suc n))) → coHomK n
ΩKn+1→Kn {n = n} = Iso.inv (Iso3-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ₖ {n = zero} = pt (coHomK-ptd 0)
0ₖ {n = suc n} = pt (coHomK-ptd (suc n))

infixr 35 _+ₖ_

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

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

Kn→ΩKn+10ₖ : (n : ℕ) → Kn→ΩKn+1 n 0ₖ ≡ refl
Kn→ΩKn+10ₖ zero = refl
Kn→ΩKn+10ₖ (suc n) = (λ i → cong ∣_∣ (rCancel (merid north) i)) -- could also use refl for n = 1, but for computational reasons I don't want to expand the definition if not necessary.

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

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

lUnitₖ : {n : ℕ} (x : coHomK n) → 0ₖ +ₖ x ≡ x
lUnitₖ {n = zero} x = cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 zero x))) ∙
                      Iso.leftInv (Iso3-Kn-ΩKn+1 zero) x
lUnitₖ {n = suc n} x = (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) i ∙ Kn→ΩKn+1 (suc n) x)) ∙
                       (cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 (suc n) x)))) ∙
                       Iso.leftInv (Iso3-Kn-ΩKn+1 (suc n)) x

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

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

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

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

commₖ : {n : ℕ} (x y : coHomK n) → (x +ₖ y) ≡ (y +ₖ x)
commₖ {n = n} x y i = ΩKn+1→Kn (EH-instance (Kn→ΩKn+1 n x) (Kn→ΩKn+1 n y) i)
  where
  EH-instance : (p q : typ (Ω ((∥ S₊ (suc n) ∥ (2 + (suc n))) , ∣ north ∣))) → p ∙ q ≡ q ∙ p
  EH-instance = transport (λ i → (p q : K-Id n (~ i)) → p ∙ q ≡ q ∙ p)
                          λ p q → Eckmann-Hilton 0 p q
    where
    K-Id : (n : HLevel) → typ (Ω (hLevelTrunc (3 + n) (S₊ (1 + n)) , ∣ north ∣)) ≡ typ ((Ω^ 2) (hLevelTrunc (4 + n) (S₊ (2 + n)) , ∣  north ∣ ))
    K-Id n = (λ i → typ (Ω (isoToPath (Iso2-Kn-ΩKn+1 (suc n)) i , hcomp (λ k → λ { (i = i0) → ∣ north ∣
                                                                                ;  (i = i1) → transportRefl (λ j → ∣ rCancel (merid north) k j ∣) k})
                                                                         (transp (λ j → isoToPath (Iso2-Kn-ΩKn+1 (suc n)) (i ∧ j)) (~ i)  ∣ north ∣))))

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

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

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

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

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

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

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

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

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

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

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

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

-- Proof that rUnitₖ and lUnitₖ agree on 0ₖ. Needed for Mayer-Vietoris.
rUnitlUnit0 : {n : ℕ} → rUnitₖ {n = n} 0ₖ ≡ lUnitₖ 0ₖ
rUnitlUnit0 {n = zero} = refl
rUnitlUnit0 {n = suc n} =
  (assoc (λ i → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) 0ₖ ∙ Kn→ΩKn+10ₖ (suc n) i))
         (cong ΩKn+1→Kn (sym (rUnit (Kn→ΩKn+1 (suc n) 0ₖ))))
         (Iso.leftInv (Iso3-Kn-ΩKn+1 (suc n)) 0ₖ))
 ∙∙ (λ j → helper j
         ∙ Iso.leftInv (Iso3-Kn-ΩKn+1 (suc n)) 0ₖ)
 ∙∙ sym (assoc (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) i ∙ Kn→ΩKn+1 (suc n) 0ₖ))
               (cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 (suc n) 0ₖ))))
               (Iso.leftInv (Iso3-Kn-ΩKn+1 (suc n)) 0ₖ))

  where
  helper : (λ i → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) 0ₖ ∙ Kn→ΩKn+10ₖ (suc n) i))
          ∙ (cong ΩKn+1→Kn (sym (rUnit (Kn→ΩKn+1 (suc n) 0ₖ))))
          ≡ (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) i ∙ Kn→ΩKn+1 (suc n) 0ₖ))
          ∙ (cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 (suc n) 0ₖ))))
  helper = ((λ j → lUnit (rUnit ((λ i → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) 0ₖ ∙ Kn→ΩKn+10ₖ (suc n) i))) j) j
                   ∙ rUnit (cong ΩKn+1→Kn (sym (rUnit (Kn→ΩKn+1 (suc n) 0ₖ)))) j)
         ∙∙ (λ j → ((λ z → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) ∣ north ∣ ∙ (λ i → ∣ rCancel (merid north) (z ∧ j) i ∣)))
                 ∙ (λ i → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) 0ₖ ∙ Kn→ΩKn+10ₖ (suc n) (i ∨ j)))
                 ∙ λ z → ΩKn+1→Kn (((λ i → ∣ rCancel (merid north) (z ∧ j) i ∣)) ∙ refl))
                 ∙ cong ΩKn+1→Kn (sym (rUnit (Kn→ΩKn+10ₖ (suc n) j)))
                 ∙ λ z → ΩKn+1→Kn (λ i → ∣ rCancel (merid north) ((~ z) ∧ j) i ∣))
         ∙∙ (λ j → (((λ z → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) ∣ north ∣ ∙ (λ i → ∣ rCancel (merid north) z i ∣))))
                  ∙ (λ i → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) 0ₖ ∙ refl))
                  ∙ λ z → ΩKn+1→Kn ((λ i → ∣ rCancel (merid north) z i ∣) ∙ λ i → ∣ rCancel (merid north) ((~ z) ∨ (~ j)) i ∣))
                 ∙ cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+10ₖ (suc n) (~ j))))
                 ∙ λ z → ΩKn+1→Kn (λ i → ∣ rCancel (merid north) (~ z ∧ (~ j)) i ∣)))
         ∙∙ (λ j → ((λ z → ΩKn+1→Kn (Kn→ΩKn+1 (suc n) ∣ north ∣  ∙ λ i → ∣ rCancel (merid north) (z ∧ (~ j)) i ∣))
                  ∙ (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) (i ∧ j) ∙ Kn→ΩKn+10ₖ (suc n) (~ j)))
                  ∙ λ z → ΩKn+1→Kn ((λ i → ∣ rCancel (merid north) (z ∨ j) i ∣) ∙ λ i → ∣ rCancel (merid north) (~ z ∧ ~ j) i ∣))
                 ∙ rUnit (cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 (suc n) 0ₖ)))) (~ j))
         ∙∙ (λ j → lUnit (rUnit (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) i ∙ Kn→ΩKn+1 (suc n) 0ₖ)) (~ j)) (~ j)
                 ∙ cong ΩKn+1→Kn (sym (lUnit (Kn→ΩKn+1 (suc n) 0ₖ))))

---- 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 = sElim2 (λ _ _ → §) λ { (a , pa) (b , pb) → ∣ (λ x → a x +ₖ b x) , (λ i → (pa i +ₖ pb i)) ∣₂ }
+ₕ∙ (suc n) = sElim2 (λ _ _ → §) λ { (a , pa) (b , pb) → ∣ (λ x → a x +ₖ b x) , (λ i → pa i +ₖ pb i) ∙ lUnitₖ 0ₖ ∣₂ }

-ₕ∙  : {A : Pointed ℓ} (n : ℕ) → coHomRed n A → coHomRed n A
-ₕ∙ zero = sRec § λ {(a , pt) → ∣ (λ x → -ₖ a x ) , (λ i → -ₖ (pt i)) ∣₂}
-ₕ∙ (suc n) = sRec § λ {(a , pt) → ∣ (λ x → -ₖ a x ) , (λ i → -ₖ (pt i)) ∙
                                                              (λ i → ΩKn+1→Kn (sym (Kn→ΩKn+10ₖ (suc n) i))) ∙
                                                              (λ i → ΩKn+1→Kn (Kn→ΩKn+10ₖ (suc n) (~ i))) ∙
                                                              Iso.leftInv (Iso3-Kn-ΩKn+1 (suc n)) ∣ north ∣ ∣₂}

0ₕ∙ : {A : Pointed ℓ} (n : ℕ) → coHomRed n A
0ₕ∙ zero = ∣ (λ _ → 0ₖ) , refl ∣₂
0ₕ∙ (suc n) = ∣ (λ _ → 0ₖ) , refl ∣₂

open IsSemigroup
open IsMonoid
open Group
coHomGr : ∀ {ℓ} (n : ℕ) (A : Type ℓ) → Group
Carrier (coHomGr n A) = coHom n A
0g (coHomGr n A) = 0ₕ
Group._+_ (coHomGr n A) = _+ₕ_
Group.- coHomGr n A = -ₕ
is-set (isSemigroup (IsGroup.isMonoid (Group.isGroup (coHomGr n A)))) = §
IsSemigroup.assoc (isSemigroup (IsGroup.isMonoid (Group.isGroup (coHomGr n A)))) x y z = sym (assocₕ x y z)
identity (IsGroup.isMonoid (Group.isGroup (coHomGr n A))) x = (rUnitₕ x) , (lUnitₕ x)
IsGroup.inverse (Group.isGroup (coHomGr n A)) x = (rCancelₕ x) , (lCancelₕ x)

×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 ∣₂

--- ΩKₙ is commutative
isCommΩK : (n : ℕ) → (p q : typ (Ω (coHomK n , coHom-pt n))) → p ∙ q ≡ q ∙ p
isCommΩK zero p q = isSetInt _ _ (p ∙ q) (q ∙ p)
isCommΩK (suc n) p q =
  cong₂ _∙_ (sym (Iso.rightInv (Iso3-Kn-ΩKn+1 n) p))
             (sym (Iso.rightInv (Iso3-Kn-ΩKn+1 n) q))
  ∙∙ (sym (Iso.rightInv (Iso3-Kn-ΩKn+1 n) (Kn→ΩKn+1 n (ΩKn+1→Kn p) ∙ Kn→ΩKn+1 n (ΩKn+1→Kn q)))
  ∙∙ cong (Kn→ΩKn+1 n) (commₖ (ΩKn+1→Kn p) (ΩKn+1→Kn q))
  ∙∙ Iso.rightInv (Iso3-Kn-ΩKn+1 n) (Kn→ΩKn+1 n (ΩKn+1→Kn q) ∙ Kn→ΩKn+1 n (ΩKn+1→Kn p)))
  ∙∙ sym (cong₂ _∙_ (sym (Iso.rightInv (Iso3-Kn-ΩKn+1 n) q))
                   (sym (Iso.rightInv (Iso3-Kn-ΩKn+1 n) p)))

--- the loopspace of Kₙ is commutative regardless of base
isCommΩK-based : (n : ℕ) (x : coHomK n) (p q : x ≡ x) → p ∙ q ≡ q ∙ p
isCommΩK-based zero x p q = isSetInt _ _ (p ∙ q) (q ∙ p)
isCommΩK-based (suc n) x p q =
      sym (transport⁻Transport (typId x) (p ∙ q))
  ∙∙ (cong (transport (λ i → typId x (~ i))) (transpTypId p q)
  ∙∙  (λ i → transport (λ i → typId x (~ i)) (isCommΩK (suc n) (transport (λ i → typId x i) p) (transport (λ i → typId x i) q) i))
  ∙∙  cong (transport (λ i → typId x (~ i))) (sym (transpTypId q p)))
  ∙∙  transport⁻Transport (typId x) (q ∙ p)
  where
  congIsoHelper : (x : coHomK (suc n)) → Iso (coHomK (suc n)) (coHomK (suc n))
  Iso.fun (congIsoHelper x) = _+ₖ x
  Iso.inv (congIsoHelper x) = _+ₖ (-ₖ x)
  Iso.rightInv (congIsoHelper x) a = assocₖ a (-ₖ x) x ∙∙ cong (a +ₖ_) (lCancelₖ x) ∙∙ rUnitₖ a
  Iso.leftInv (congIsoHelper x) a = assocₖ a x (-ₖ x) ∙∙ cong (a +ₖ_) (rCancelₖ x) ∙∙ rUnitₖ a

  typId : (x : coHomK (suc n)) → (x ≡ x) ≡ Path (coHomK (suc n)) 0ₖ 0ₖ
  typId x = isoToPath (congIso (invIso (congIsoHelper x))) ∙ λ i → rCancelₖ x i ≡ rCancelₖ x i

  transpTypId : (p q : (x ≡ x)) → transport (λ i → typId x i) (p ∙ q) ≡ transport (λ i → typId x i) p ∙ transport (λ i → typId x i) q
  transpTypId p q =
      ((substComposite (λ x → x) (isoToPath ((congIso (invIso (congIsoHelper x)))))
                                 (λ i → rCancelₖ x i ≡ rCancelₖ x i) (p ∙ q))
    ∙∙ (λ i → transport (λ i → rCancelₖ x i ≡ rCancelₖ x i) (transportRefl (congFunct (_+ₖ (-ₖ x)) p q i) i))
    ∙∙ overPathFunct (cong (_+ₖ (-ₖ x)) p) (cong (_+ₖ (-ₖ x)) q) (rCancelₖ x))
    ∙∙ cong₂ (λ y z → transport (λ i → rCancelₖ x i ≡ rCancelₖ x i) y ∙ transport (λ i → rCancelₖ x i ≡ rCancelₖ x i) z)
             (sym (transportRefl (cong (_+ₖ (-ₖ x)) p)))
             (sym (transportRefl (cong (_+ₖ (-ₖ x)) q)))
    ∙∙ cong₂ (_∙_) (sym (substComposite (λ x → x) (isoToPath ((congIso (invIso (congIsoHelper x))))) (λ i → rCancelₖ x i ≡ rCancelₖ x i) p))
                   (sym (substComposite (λ x → x) (isoToPath ((congIso (invIso (congIsoHelper x))))) (λ i → rCancelₖ x i ≡ rCancelₖ x i) q))

addLemma : (a b : Int) → a +ₖ b ≡ (a ℤ+ b)
addLemma a b = (cong ΩKn+1→Kn (sym (congFunct ∣_∣ (looper a) (looper b)))
              ∙∙ cong₂ (λ x y → ΩKn+1→Kn (cong ∣_∣ (x ∙ y))) (looper≡looper2 a) (looper≡looper2 b)
              ∙∙ (λ i → ΩKn+1→Kn (cong ∣_∣ (cong SuspBool→S1 (cong S¹→SuspBool (intLoop a)) ∙ cong SuspBool→S1 (cong S¹→SuspBool (intLoop b))))))
              ∙∙ (cong (λ x → ΩKn+1→Kn (cong ∣_∣ x)) (sym (congFunct SuspBool→S1 (cong S¹→SuspBool (intLoop a)) (cong S¹→SuspBool (intLoop b))))
              ∙∙ cong (λ x → ΩKn+1→Kn (cong ∣_∣ (cong SuspBool→S1 x))) (sym (congFunct S¹→SuspBool (intLoop a) (intLoop b)))
              ∙∙ cong (λ x → ΩKn+1→Kn (cong ∣_∣ (cong SuspBool→S1 (cong S¹→SuspBool x)))) (intLoop-hom a b))
              ∙∙ (cong (λ x → ΩKn+1→Kn (cong ∣_∣ x)) (sym (looper≡looper2 (a ℤ+ b)))
                ∙ Iso.leftInv (Iso3-Kn-ΩKn+1 zero) (a ℤ+ b))


module Data.Num.Injection where

open import Data.Num.Core

open import Data.Nat hiding (compare)
open import Data.Nat.Properties
open import Data.Nat.Properties.Simple
open import Data.Nat.Properties.Extra

open import Data.Fin as Fin
    using (Fin; fromℕ≤; inject≤)
    renaming (zero to z; suc to s)
open import Data.Fin.Properties
    using (toℕ-fromℕ≤; to-from; bounded; inject≤-lemma)
    renaming (toℕ-injective to Fin→ℕ-injective; _≟_ to _Fin≟_)
open import Data.Fin.Properties.Extra
open import Data.Sum
open import Data.Product
open import Data.Empty using (⊥)
open import Data.Unit using (⊤; tt)

open import Function
open import Function.Injection
open Injection
-- open import Function.Equality using (_⟶_; _⟨$⟩_)
open import Relation.Nullary.Decidable
open import Relation.Nullary
open import Relation.Nullary.Negation
open import Relation.Binary
open import Relation.Binary.PropositionalEquality

open ≡-Reasoning
open ≤-Reasoning renaming (begin_ to start_; _∎ to _□; _≡⟨_⟩_ to _≈⟨_⟩_)
open DecTotalOrder decTotalOrder using (reflexive) renaming (refl to ≤-refl)
open StrictTotalOrder strictTotalOrder using (compare)

data InjCond : ℕ → ℕ → ℕ → Set where
    Ordinary  : ∀ {b d o} → (b≥d : b ≥ d) → (o≥1 : o ≥ 1) → InjCond b d o
    Digitless : ∀ {b d o}                 → (d≡0 : d ≡ 0) → InjCond b 0 o -- having no digits at all

data NonInjCond : ℕ → ℕ → ℕ → Set where
    Redundant : ∀ {b d o}                  → (d>b : d > b) → NonInjCond b d o -- having to many digits
    WithZeros : ∀ {b d o} → (o≡0 : o ≡ 0) → (d≢0 : d ≢ 0) → NonInjCond b d 0 -- with leading zeroes

data InjectionView : ℕ → ℕ → ℕ → Set where
    Inj    : ∀ {b d o} → InjCond    b d o → InjectionView b d o
    NonInj : ∀ {b d o} → NonInjCond b d o → InjectionView b d o

injectionView : (b d o : ℕ) → InjectionView b d o
injectionView b zero    zero    = Inj    (Digitless refl)
injectionView b (suc d) zero    = NonInj (WithZeros refl (λ ()))
injectionView b d       (suc o) with d ≤? b
injectionView b d       (suc o) | yes p = Inj    (Ordinary p (s≤s z≤n))
injectionView b d       (suc o) | no ¬p = NonInj (Redundant (≰⇒> ¬p))

IsInjective : ℕ → ℕ → ℕ → Set
IsInjective b d o with injectionView b d o
IsInjective b d o | Inj    _ = ⊤
IsInjective b d o | NonInj _ = ⊥

InjCond⇒IsInj : ∀ {b d o} → InjCond b d o → IsInjective b d o
InjCond⇒IsInj {b} {d} {o} cond with injectionView b d o
InjCond⇒IsInj _                  | Inj _ = tt
InjCond⇒IsInj (Ordinary b≥d o≥1) | NonInj (Redundant d>b) = contradiction b≥d (>⇒≰ d>b)
InjCond⇒IsInj (Ordinary b≥d ())  | NonInj (WithZeros o≡0 d≢0)
InjCond⇒IsInj (Digitless d≡0)    | NonInj (Redundant ())
InjCond⇒IsInj (Digitless d≡0)    | NonInj (WithZeros refl d≢0) = contradiction refl d≢0

-- InjCond⇒b≥1 : ∀ {b d o} → InjCond b (suc d) o → b ≥ 1
-- InjCond⇒b≥1 {zero}  (bijCond b≥d: o≥1:) = contradiction b≥d: (>⇒≰ (s≤s z≤n))
-- InjCond⇒b≥1 {suc b} (bijCond b≥d: o≥1:) = s≤s z≤n
--

NonInjCond⇏IsInj : ∀ {b d o} → NonInjCond b d o → ¬ IsInjective b d o
NonInjCond⇏IsInj {b} {d} {o} reason claim with injectionView b d o
NonInjCond⇏IsInj (Redundant d>b)     claim | Inj (Ordinary b≥d o≥1) = contradiction b≥d (>⇒≰ d>b)
NonInjCond⇏IsInj (Redundant ())      claim | Inj (Digitless d≡0)
NonInjCond⇏IsInj (WithZeros o≡0 d≢0) claim | Inj (Ordinary b≥d ())
NonInjCond⇏IsInj (WithZeros o≡0 d≢0) claim | Inj (Digitless d≡0) = contradiction refl d≢0
NonInjCond⇏IsInj reason              ()    | NonInj _

Digit-toℕ-injective : ∀ {d} o (x y : Fin d)
    → Digit-toℕ x o ≡ Digit-toℕ y o -- o + toℕ x ≡ o + toℕ y
    → x ≡ y
Digit-toℕ-injective {_} o x y eq =
    Fin→ℕ-injective         -- cancels `Fin.toℕ`
        $ cancel-+-left o    -- cancels `o +`
        $ eq

n∷-mono-strict : ∀ {b d o} (x y : Fin d) (xs ys : Num b d o)
    → InjCond b d o
    → toℕ xs < toℕ ys
    → toℕ (x ∷ xs) < toℕ (y ∷ ys)
n∷-mono-strict {b} {_} {o} () () xs ys (Digitless d≡0)    ⟦xs⟧<⟦ys⟧
n∷-mono-strict {b} {d} {o} x  y  xs ys (Ordinary b≥d o≥1) ⟦xs⟧<⟦ys⟧ =
    start
        suc (Digit-toℕ x o) + toℕ xs * b
    ≤⟨ +n-mono (toℕ xs * b) $
        start
            suc (Digit-toℕ x o)
        ≈⟨ sym (+-suc o (Fin.toℕ x)) ⟩
            -- push `o` to the left
            o + suc (Fin.toℕ x)
        ≤⟨ n+-mono o $
            start
                suc (Fin.toℕ x)
            ≤⟨ bounded x ⟩
                d
            ≤⟨ b≥d ⟩
                b
            ≤⟨ n≤m+n (Fin.toℕ y) b ⟩
                Fin.toℕ y + b
        □ ⟩
            o + (Fin.toℕ y + b)
        ≈⟨ sym (+-assoc o (Fin.toℕ y) b) ⟩
            Digit-toℕ y o + b
    □ ⟩
        Digit-toℕ y o + b + toℕ xs * b
    ≈⟨ +-assoc (Digit-toℕ y o) b (toℕ xs * b) ⟩
        Digit-toℕ y o + (b + toℕ xs * b)
    ≤⟨ n+-mono (Digit-toℕ y o) (*n-mono b ⟦xs⟧<⟦ys⟧) ⟩  -- replace `xs` with `ys`
        Digit-toℕ y o + toℕ ys * b
    □

∷ns-mono-strict : ∀ {b d o} (x y : Fin d) (xs ys : Num b d o)
    → toℕ xs ≡ toℕ ys
    → Digit-toℕ x o < Digit-toℕ y o
    → toℕ (x ∷ xs) < toℕ (y ∷ ys)
∷ns-mono-strict {b} {d} {o} x y xs ys ⟦xs⟧≡⟦ys⟧ ⟦x⟧<⟦y⟧ = start
        suc (Digit-toℕ x o + toℕ xs * b)
    ≈⟨ cong (λ w → suc (Digit-toℕ x o + w * b)) ⟦xs⟧≡⟦ys⟧ ⟩
        suc (Digit-toℕ x o + toℕ ys * b)
    ≤⟨ +n-mono (toℕ ys * b) ⟦x⟧<⟦y⟧ ⟩
        Digit-toℕ y o + toℕ ys * b
    □

toℕ-injective-⟦x∷xs⟧>0-lemma : ∀ {b d o}
    → o ≥ 1
    → (x : Fin d)
    → (xs : Num b d o)
    → toℕ (x ∷ xs) > 0
toℕ-injective-⟦x∷xs⟧>0-lemma {b} {d} {o} o≥1 x xs =
    start
        suc zero
    ≤⟨ o≥1 ⟩
        o
    ≤⟨ m≤m+n o (Fin.toℕ x) ⟩
        o + Fin.toℕ x
    ≤⟨ m≤m+n (o + Fin.toℕ x) (toℕ xs * b) ⟩
        o + Fin.toℕ x + toℕ xs * b
    □

toℕ-injective : ∀ {b d o}
    → {isInj : IsInjective b d o}
    → (xs ys : Num b d o)
    → toℕ xs ≡ toℕ ys
    → xs ≡ ys
toℕ-injective {b} {d} {o} xs ys eq with injectionView b d o
toℕ-injective                 ∙        ∙        eq | Inj (Ordinary b≥d o≥1) = refl
toℕ-injective                 ∙        (y ∷ ys) eq | Inj (Ordinary b≥d o≥1) = contradiction eq (<⇒≢ (toℕ-injective-⟦x∷xs⟧>0-lemma o≥1 y ys))
toℕ-injective                 (x ∷ xs) ∙        eq | Inj (Ordinary b≥d o≥1) = contradiction eq (>⇒≢ (toℕ-injective-⟦x∷xs⟧>0-lemma o≥1 x xs))
toℕ-injective {b} {zero} {o} (() ∷ xs) (y ∷ ys) eq | Inj cond
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond with compare (toℕ xs) (toℕ ys)
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri< ⟦xs⟧<⟦ys⟧ _ _ = contradiction eq (<⇒≢ (n∷-mono-strict x y xs ys cond ⟦xs⟧<⟦ys⟧))
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri≈ _ ⟦xs⟧≡⟦ys⟧ _ with compare (Digit-toℕ x o) (Digit-toℕ y o)
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri≈ _ ⟦xs⟧≡⟦ys⟧ _ | tri< ⟦x⟧<⟦y⟧ _ _ = contradiction eq (<⇒≢ (∷ns-mono-strict x y xs ys ⟦xs⟧≡⟦ys⟧ ⟦x⟧<⟦y⟧))
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri≈ _ ⟦xs⟧≡⟦ys⟧ _ | tri≈ _ ⟦x⟧≡⟦y⟧ _ = cong₂ _∷_ (Digit-toℕ-injective o x y ⟦x⟧≡⟦y⟧) (toℕ-injective {isInj = InjCond⇒IsInj cond} xs ys ⟦xs⟧≡⟦ys⟧)
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri≈ _ ⟦xs⟧≡⟦ys⟧ _ | tri> _ _ ⟦x⟧>⟦y⟧ = contradiction eq (>⇒≢ (∷ns-mono-strict y x ys xs (sym ⟦xs⟧≡⟦ys⟧) ⟦x⟧>⟦y⟧))
toℕ-injective {b} {suc d} {o} (x ∷ xs) (y ∷ ys) eq | Inj cond | tri> _ _ ⟦xs⟧>⟦ys⟧ = contradiction eq (>⇒≢ ((n∷-mono-strict y x ys xs cond ⟦xs⟧>⟦ys⟧)))
toℕ-injective                 ∙        ∙        eq | Inj (Digitless d≡0) = refl
toℕ-injective                 ∙       (() ∷ ys) eq | Inj (Digitless d≡0)
toℕ-injective                 (() ∷ xs) ys      eq | Inj (Digitless d≡0)
toℕ-injective {isInj = ()}    xs        ys      eq | NonInj reason

InjCond⇒Injective : ∀ {b} {d} {o} → InjCond b d o → Injective (Num⟶ℕ b d o)
InjCond⇒Injective condition {x} {y} = toℕ-injective {isInj = InjCond⇒IsInj condition} x y

NonInjCond⇏Injective : ∀ {b} {d} {o} → NonInjCond b d o → ¬ (Injective (Num⟶ℕ b d o))
NonInjCond⇏Injective {zero} {zero}      (Redundant ()) claim
NonInjCond⇏Injective {zero} {suc d} {o} (Redundant d>b) claim =
    contradiction
        (claim
            {z ∷ ∙}
            {z ∷ z ∷ ∙}
            ⟦1∷∙⟧≡⟦1∷1∷∙⟧)
        (λ ())
    where   ⟦1∷∙⟧≡⟦1∷1∷∙⟧ : toℕ {zero} {suc d} {o} (z ∷ ∙) ≡ toℕ {zero} {suc d} {o} (z ∷ z ∷ ∙)
            ⟦1∷∙⟧≡⟦1∷1∷∙⟧ = cong (λ w → o + 0 + w) (sym (*-right-zero (o + 0 + 0)))
NonInjCond⇏Injective {suc b} {zero} (Redundant ()) claim
NonInjCond⇏Injective {suc b} {suc zero} (Redundant (s≤s ())) claim
NonInjCond⇏Injective {suc b} {suc (suc d)} {o} (Redundant d>b) claim =
    contradiction
        (claim
            {z ∷ s z ∷ ∙}
            {fromℕ≤ d>b ∷ z ∷ ∙}
            ⟦1∷2⟧≡⟦b+1∷1⟧)
        (λ ())
    where   ⟦1∷2⟧≡⟦b+1∷1⟧ : toℕ {suc b} {suc (suc d)} {o} (z ∷ s z ∷ ∙) ≡ toℕ {suc b} {suc (suc d)} {o} (fromℕ≤ {suc b} d>b ∷ z ∷ ∙)
            ⟦1∷2⟧≡⟦b+1∷1⟧ =
                begin
                    o + zero + (o + suc zero + zero) * suc b
                ≡⟨ cong (λ x → x + (o + suc zero + zero) * suc b) (+-right-identity o) ⟩
                    o + (o + suc zero + zero) * suc b
                ≡⟨ cong (λ x → o + (x + zero) * suc b) (+-suc o zero) ⟩
                    o + (suc (o + zero) + zero) * suc b
                ≡⟨ refl ⟩
                    o + (suc b + (o + zero + zero) * suc b)
                ≡⟨ sym (+-assoc o (suc b) ((o + zero + zero) * suc b)) ⟩
                    o + suc b + (o + zero + zero) * suc b
                ≡⟨ cong (λ x → o + x + (o + zero + zero) * suc b) (sym (toℕ-fromℕ≤ d>b)) ⟩
                    o + Fin.toℕ (fromℕ≤ d>b) + (o + zero + zero) * suc b
                ∎
NonInjCond⇏Injective {b} {zero} {_} (WithZeros o≡0 d≢0) claim = contradiction refl d≢0
NonInjCond⇏Injective {b} {suc d} {_} (WithZeros o≡0 d≢0) claim =
    contradiction
        (claim
            {z ∷ ∙}
            {∙}
            ⟦0∷∙⟧≡⟦∙⟧)
        (λ ())
    where   ⟦0∷∙⟧≡⟦∙⟧ : toℕ {b} {suc d} {0} (z ∷ ∙) ≡ toℕ {b} {suc d} {0} ∙
            ⟦0∷∙⟧≡⟦∙⟧ = refl

Injective? : ∀ b d o → Dec (Injective (Num⟶ℕ b d o))
Injective? b d o with injectionView b d o
Injective? b d o | Inj condition = yes (InjCond⇒Injective condition)
Injective? b d o | NonInj reason = no (NonInjCond⇏Injective reason)

-- begin
--     {!   !}
-- ≡⟨ {!   !} ⟩
--     {!   !}
-- ≡⟨ {!   !} ⟩
--     {!   !}
-- ≡⟨ {!   !} ⟩
--     {!   !}
-- ≡⟨ {!   !} ⟩
--     {!   !}
-- ∎