{-# OPTIONS --safe --no-qualified-instances #-}
module CF.Transform.Compile.Statements where

open import Function using (_∘_)
open import Data.Unit using (⊤; tt)
open import Data.Product
open import Data.List hiding (null; [_])
open import Relation.Binary.PropositionalEquality hiding ([_])
open import Relation.Unary
open import Relation.Unary.PredicateTransformer using (Pt)
open import Relation.Ternary.Core
open import Relation.Ternary.Structures
open import Relation.Ternary.Structures.Syntax
open import Relation.Ternary.Monad

open import CF.Syntax.DeBruijn
open import CF.Transform.Compile.Expressions
open import CF.Types
open import CF.Transform.Compile.ToJVM

open import JVM.Types
open import JVM.Compiler
open import JVM.Contexts
open import JVM.Model StackTy
open import JVM.Syntax.Values
open import JVM.Syntax.Instructions

mutual
  {- Compiling statements -}
  compileₛ : ∀ {ψ : StackTy} {Γ r} → Stmt r Γ → ε[ Compiler ⟦ Γ ⟧ ψ ψ Emp  ]

  compileₛ (asgn x e) = do
    compileₑ e
    code (store ⟦ x ⟧)

  compileₛ (run e) = do
    compileₑ e
    code pop

  compileₛ (block x) = do
    compiler _ x

  compileₛ (while e body) = do
    -- condition
    lcond⁺ ∙⟨ σ ⟩ lcond⁻    ← freshLabel
    refl ∙⟨ σ ⟩ lcond⁻      ← attachTo lcond⁺ ⟨ ∙-idʳ ⟩ compileₑ e ⟨ Down _ # σ ⟩& lcond⁻
    (↓ lend⁻) ∙⟨ σ ⟩ labels ← (✴-rotateₗ ∘ ✴-assocᵣ) ⟨$⟩ (freshLabel ⟨ Down _ # σ ⟩& lcond⁻)
    (↓ lcond⁻) ∙⟨ σ ⟩ lend⁺ ← ✴-id⁻ˡ ⟨$⟩ (code (if eq lend⁻) ⟨ _ ✴ _  # σ ⟩& labels)

    -- body
    compileₛ body
    lend⁺                   ← ✴-id⁻ˡ ⟨$⟩ (code (goto lcond⁻) ⟨ Up _   # σ ⟩& lend⁺)
    attach lend⁺

  compileₛ (ifthenelse c e₁ e₂) = do
    -- condition
    compileₑ c
    lthen+ ∙⟨ σ ⟩ ↓ lthen-  ← freshLabel
    lthen+                  ← ✴-id⁻ˡ ⟨$⟩ (code (if ne lthen-) ⟨ Up _  # ∙-comm σ     ⟩& lthen+)

    -- else
    compileₛ e₂
    ↓ lend- ∙⟨ σ ⟩ labels   ← (✴-rotateₗ ∘ ✴-assocᵣ) ⟨$⟩ (freshLabel ⟨ Up _  # ∙-idˡ        ⟩& lthen+)

    -- then
    lthen+ ∙⟨ σ ⟩ lend+     ← ✴-id⁻ˡ ⟨$⟩ (code (goto lend-) ⟨ _ ✴ _ # σ ⟩& labels)
    lend+                   ← ✴-id⁻ˡ ⟨$⟩ (attach lthen+ ⟨ Up _  # σ ⟩& lend+)
    compileₛ e₁

    -- label the end
    attach lend+

  {- Compiling blocks -}
  compiler : ∀ (ψ : StackTy) {Γ r} → Block r Γ → ε[ Compiler ⟦ Γ ⟧ ψ ψ Emp ]

  compiler ψ (nil) = do
    return refl

  compiler ψ (s ⍮⍮ b) = do
    compileₛ s
    compiler _ b


------------------------------------------------------------------------
-- Parallel substitutions (defined using an inductive family)
------------------------------------------------------------------------

open import Data.Universe.Indexed

module deBruijn.Substitution.Data
  {i u e} {Uni : IndexedUniverse i u e} where

import deBruijn.Context; open deBruijn.Context Uni
open import Function as F using (_$_)
open import Level using (_⊔_)
import Relation.Binary.PropositionalEquality as P

open P.≡-Reasoning

-- This module reexports some other modules.

open import deBruijn.Substitution.Data.Application public
open import deBruijn.Substitution.Data.Basics      public
open import deBruijn.Substitution.Data.Map         public
open import deBruijn.Substitution.Data.Simple      public

------------------------------------------------------------------------
-- Instantiations and code for facilitating instantiations

-- Renamings (variable substitutions).

module Renaming where

  -- Variables support all of the operations above.

  simple : Simple Var
  simple = record
    { weaken     = weaken∋
    ; var        = [id]
    ; weaken-var = λ _ → P.refl
    }

  application : Application Var Var
  application = record { app = app∋ }

  application₁ : Application₁ Var
  application₁ = record
    { simple        = simple
    ; application₂₂ = record
      { application₂₁ = record
        { application  = application
        ; trans-weaken = λ _ → P.refl
        ; trans-var    = λ _ → P.refl
        ; var-/⊢       = λ _ _ → P.refl
        }
      ; var-/⊢⋆-↑⁺⋆-⇒-/⊢⋆-↑⁺⋆ = λ _ _ hyp → hyp
      ; /⊢-wk                 = Simple./∋-wk simple
      }
    }

  open Application₁ application₁ public hiding (simple; application)

  ≅-∋-⇒-≅-⊢ : ∀ {Γ₁ σ₁} {x₁ : Γ₁ ∋ σ₁}
                {Γ₂ σ₂} {x₂ : Γ₂ ∋ σ₂} →
              x₁ ≅-∋ x₂ → Term-like._≅-⊢_ Var x₁ x₂
  ≅-∋-⇒-≅-⊢ P.refl = P.refl

  ≅-⊢-⇒-≅-∋ : ∀ {Γ₁ σ₁} {x₁ : Γ₁ ∋ σ₁}
                {Γ₂ σ₂} {x₂ : Γ₂ ∋ σ₂} →
              Term-like._≅-⊢_ Var x₁ x₂ → x₁ ≅-∋ x₂
  ≅-⊢-⇒-≅-∋ P.refl = P.refl

-- A translation of T₁'s to T₂'s, plus a bit more.

record _↦_ {t₁} (T₁ : Term-like t₁)
           {t₂} (T₂ : Term-like t₂) : Set (i ⊔ u ⊔ e ⊔ t₁ ⊔ t₂) where
  field
    -- Translates T₁'s to T₂'s.
    trans : [ T₁ ⟶⁼ T₂ ]

    -- Simple substitutions for T₁'s.
    simple : Simple T₁

  open Simple simple public

  open Term-like T₁ renaming (⟦_⟧ to ⟦_⟧₁)
  open Term-like T₂ renaming (⟦_⟧ to ⟦_⟧₂)

  /̂∋-⟦⟧⇨ : ∀ {Γ Δ σ} {ρ̂ : Γ ⇨̂ Δ} (x : Γ ∋ σ) (ρ : Sub T₁ ρ̂) →
           x /̂∋ ⟦ ρ ⟧⇨ ≅-Value ⟦ trans · (x /∋ ρ) ⟧₂
  /̂∋-⟦⟧⇨ x ρ = begin
    [ x /̂∋ ⟦ ρ ⟧⇨           ]  ≡⟨ corresponds (app∋ ρ) x ⟩
    [ ⟦ x /∋ ρ ⟧₁           ]  ≡⟨ corresponds trans (x /∋ ρ) ⟩
    [ ⟦ trans · (x /∋ ρ) ⟧₂ ]  ∎

-- "Term" substitutions.

-- For simplicity I have chosen to use the universe level (i ⊔ u ⊔ e)
-- here; this is the level of Var. The code could perhaps be made more
-- general.

record Substitution₁ (T : Term-like (i ⊔ u ⊔ e))
                     : Set (Level.suc (i ⊔ u ⊔ e)) where

  open Term-like T

  field
    -- Takes variables to terms.
    var : [ Var ⟶⁼ T ]

    -- Applies substitutions, which contain things which can be
    -- translated to terms, to terms.
    app′ : {T′ : Term-like (i ⊔ u ⊔ e)} → T′ ↦ T →
           ∀ {Γ Δ} {ρ̂ : Γ ⇨̂ Δ} → Sub T′ ρ̂ → [ T ⟶ T ] ρ̂

    -- A property relating app′ and var.
    app′-var :
      ∀ {T′ : Term-like (i ⊔ u ⊔ e)} {Γ Δ σ} {ρ̂ : Γ ⇨̂ Δ}
      (T′↦T : T′ ↦ T) (x : Γ ∋ σ) (ρ : Sub T′ ρ̂) →
      app′ T′↦T ρ · (var · x) ≅-⊢ _↦_.trans T′↦T · (x /∋ ρ)

  -- Variables can be translated into terms.

  private

    Var-↦′ : Var ↦ T
    Var-↦′ = record
      { trans  = var
      ; simple = Renaming.simple
      }

  -- Renamings can be applied to terms.

  app-renaming : ∀ {Γ Δ} {ρ̂ : Γ ⇨̂ Δ} → Sub Var ρ̂ → [ T ⟶ T ] ρ̂
  app-renaming = app′ Var-↦′

  -- This gives us a way to define the weakening operation.

  simple : Simple T
  simple = record
    { weaken     = app-renaming Renaming.wk
    ; var        = var
    ; weaken-var = weaken-var
    }
    where
    abstract
      weaken-var : ∀ {Γ σ τ} (x : Γ ∋ τ) →
                   app-renaming (Renaming.wk[_] σ) · (var · x) ≅-⊢
                   var · suc[ σ ] x
      weaken-var x = begin
        [ app-renaming Renaming.wk · (var · x) ]  ≡⟨ app′-var Var-↦′ x Renaming.wk ⟩
        [ var · (x /∋ Renaming.wk)             ]  ≡⟨ ·-cong (var ∎-⟶) (Renaming./∋-wk x) ⟩
        [ var · suc x                          ]  ∎

  -- A translation of T′'s to T's, plus a bit more.

  record Translation-from (T′ : Term-like (i ⊔ u ⊔ e))
                          : Set (i ⊔ u ⊔ e) where

    field
      translation : T′ ↦ T

    open _↦_ translation
      using (trans)
      renaming (simple to simple′; var to var′; weaken[_] to weaken′[_])

    field
      trans-weaken : ∀ {Γ σ τ} (t : Term-like._⊢_ T′ Γ τ) →
                     trans · (weaken′[ σ ] · t) ≅-⊢
                     Simple.weaken[_] simple σ · (trans · t)
      trans-var    : ∀ {Γ σ} (x : Γ ∋ σ) →
                     trans · (var′ · x) ≅-⊢ var · x

    -- An Application₂₁ record.

    application₂₁ : Application₂₁ simple′ simple trans
    application₂₁ = record
      { application  = record { app = app′ translation }
      ; trans-weaken = trans-weaken
      ; trans-var    = trans-var
      ; var-/⊢       = app′-var translation
      }

    open _↦_ translation public
    open Application₂₁ application₂₁ public
      hiding (trans-weaken; trans-var)

  -- Variables can be translated into terms.

  Var-↦ : Translation-from Var
  Var-↦ = record
    { translation  = Var-↦′
    ; trans-weaken = trans-weaken
    ; trans-var    = λ _ → P.refl
    }
    where
    abstract
      trans-weaken :
        ∀ {Γ σ τ} (x : Γ ∋ τ) →
        var · suc[ σ ] x ≅-⊢
        Simple.weaken[_] simple σ · (var · x)
      trans-weaken x = P.sym $ Simple.weaken-var simple x

  -- Terms can be translated into terms.

  no-translation : Translation-from T
  no-translation = record
    { translation  = record { trans = [id]; simple = simple }
    ; trans-weaken = λ _ → P.refl
    ; trans-var    = λ _ → P.refl
    }

record Substitution₂ (T : Term-like (i ⊔ u ⊔ e))
                     : Set (Level.suc (i ⊔ u ⊔ e)) where

  open Term-like T

  field
    substitution₁ : Substitution₁ T

  open Substitution₁ substitution₁

  field
    -- Lifts equalities valid for all variables and liftings to
    -- arbitrary terms.
    var-/⊢⋆-↑⁺⋆-⇒-/⊢⋆-↑⁺⋆′ :
      {T₁ T₂ : Term-like (i ⊔ u ⊔ e)}
      (T₁↦T : Translation-from T₁) (T₂↦T : Translation-from T₂) →

      let open Translation-from T₁↦T
            using () renaming (_↑⁺⋆_ to _↑⁺⋆₁_; _/⊢⋆_ to _/⊢⋆₁_)
          open Translation-from T₂↦T
            using () renaming (_↑⁺⋆_ to _↑⁺⋆₂_; _/⊢⋆_ to _/⊢⋆₂_) in

      ∀ {Γ Δ} {ρ̂ : Γ ⇨̂ Δ} (ρs₁ : Subs T₁ ρ̂) (ρs₂ : Subs T₂ ρ̂) →
      (∀ Γ⁺ {σ} (x : Γ ++⁺ Γ⁺ ∋ σ) →
         var · x /⊢⋆₁ ρs₁ ↑⁺⋆₁ Γ⁺ ≅-⊢ var · x /⊢⋆₂ ρs₂ ↑⁺⋆₂ Γ⁺) →
      ∀ Γ⁺ {σ} (t : Γ ++⁺ Γ⁺ ⊢ σ) →
      t /⊢⋆₁ ρs₁ ↑⁺⋆₁ Γ⁺ ≅-⊢ t /⊢⋆₂ ρs₂ ↑⁺⋆₂ Γ⁺

  -- Given a well-behaved translation from something with simple
  -- substitutions one can define an Application₂₂ record.

  application₂₂ :
    {T′ : Term-like (i ⊔ u ⊔ e)} (T′↦T : Translation-from T′) →
    let open Translation-from T′↦T
          using (trans) renaming (simple to simple′) in
    Application₂₂ simple′ simple trans
  application₂₂ T′↦T = record
    { application₂₁         = Translation-from.application₂₁ T′↦T
    ; var-/⊢⋆-↑⁺⋆-⇒-/⊢⋆-↑⁺⋆ = var-/⊢⋆-↑⁺⋆-⇒-/⊢⋆-↑⁺⋆′ T′↦T T′↦T
    ; /⊢-wk                 = /⊢-wk
    }
    where
    open Translation-from T′↦T
      using ()
      renaming ( _↑⁺_ to _↑⁺′_; _↑⁺⋆_ to _↑⁺⋆′_
               ; wk to wk′; wk[_] to wk′[_]
               ; _/⊢_ to _/⊢′_; _/⊢⋆_ to _/⊢⋆′_
               )
    open Translation-from Var-↦
      using ()
      renaming ( _↑⁺_ to _↑⁺-renaming_; _↑⁺⋆_ to _↑⁺⋆-renaming_
               ; _/⊢_ to _/⊢-renaming_; _/⊢⋆_ to _/⊢⋆-renaming_
               )

    abstract
      /⊢-wk : ∀ {Γ σ τ} (t : Γ ⊢ τ) →
              t /⊢′ wk′[ σ ] ≅-⊢ t /⊢-renaming Renaming.wk[_] σ
      /⊢-wk =
        var-/⊢⋆-↑⁺⋆-⇒-/⊢⋆-↑⁺⋆′ T′↦T Var-↦ (ε ▻ wk′) (ε ▻ Renaming.wk)
          (λ Γ⁺ x → begin
             [ var · x /⊢⋆′ (ε ▻ wk′) ↑⁺⋆′ Γ⁺                         ]  ≡⟨ Translation-from./⊢⋆-ε-▻-↑⁺⋆ T′↦T Γ⁺ (var · x) wk′ ⟩
             [ var · x /⊢′ wk′ ↑⁺′ Γ⁺                                 ]  ≡⟨ Translation-from.var-/⊢-wk-↑⁺ T′↦T Γ⁺ x ⟩
             [ var · (lift weaken∋ Γ⁺ · x)                            ]  ≡⟨ P.sym $ Translation-from.var-/⊢-wk-↑⁺ Var-↦ Γ⁺ x ⟩
             [ var · x /⊢-renaming Renaming.wk ↑⁺-renaming Γ⁺         ]  ≡⟨ P.sym $ Translation-from./⊢⋆-ε-▻-↑⁺⋆
                                                                                Var-↦ Γ⁺ (var · x) Renaming.wk ⟩
             [ var · x /⊢⋆-renaming (ε ▻ Renaming.wk) ↑⁺⋆-renaming Γ⁺ ]  ∎)
          ε

  -- An Application₂₂ record for renamings.

  application₂₂-renaming : Application₂₂ Renaming.simple simple var
  application₂₂-renaming = application₂₂ Var-↦

  -- We can apply substitutions to terms.

  application₁ : Application₁ T
  application₁ = record
    { simple        = simple
    ; application₂₂ = application₂₂ no-translation
    }

  open Application₁ application₁ public
    hiding (var; simple; application₂₂)
  open Substitution₁ substitution₁ public


-- Andreas, 2020-02-15, issue #4447, reported by zraffer

record Wrap : Set₁ where
  constructor ↑
  field ↓ : Set
open Wrap public

data Unit : Set

-- The type checker sees through this definition,
-- thus, the positivity checker should as well:
𝕌nit = ↑ Unit

data Unit where
  unit : ↓ 𝕌nit

-- WAS: internal error in the positivity checker.


------------------------------------------------------------------------
-- Strict ω-continuous functions
------------------------------------------------------------------------

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

module Partiality-monad.Inductive.Strict-omega-continuous where

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

open import Bijection equality-with-J using (_↔_)
open import Function-universe equality-with-J hiding (_∘_)
open import Monad equality-with-J

import Partiality-algebra.Strict-omega-continuous as S
open import Partiality-monad.Inductive
open import Partiality-monad.Inductive.Eliminators
open import Partiality-monad.Inductive.Monad
open import Partiality-monad.Inductive.Monotone
open import Partiality-monad.Inductive.Omega-continuous

-- Definition of strict ω-continuous functions.

[_⊥→_⊥]-strict : ∀ {a b} → Type a → Type b → Type (a ⊔ b)
[ A ⊥→ B ⊥]-strict = S.[ partiality-algebra A ⟶ partiality-algebra B ]⊥

module [_⊥→_⊥]-strict
         {a b} {A : Type a} {B : Type b}
         (f : [ A ⊥→ B ⊥]-strict) =
         S.[_⟶_]⊥ f

open [_⊥→_⊥]-strict

-- Identity.

id-strict : ∀ {a} {A : Type a} → [ A ⊥→ A ⊥]-strict
id-strict = S.id⊥

-- Composition.

infixr 40 _∘-strict_

_∘-strict_ :
  ∀ {a b c} {A : Type a} {B : Type b} {C : Type c} →
  [ B ⊥→ C ⊥]-strict → [ A ⊥→ B ⊥]-strict → [ A ⊥→ C ⊥]-strict
_∘-strict_ = S._∘⊥_

-- Equality characterisation lemma for strict ω-continuous functions.

equality-characterisation-strict :
  ∀ {a b} {A : Type a} {B : Type b} {f g : [ A ⊥→ B ⊥]-strict} →
  (∀ x → function f x ≡ function g x) ↔ f ≡ g
equality-characterisation-strict =
  S.equality-characterisation-strict

-- Composition is associative.

∘-strict-assoc :
  ∀ {a b c d} {A : Type a} {B : Type b} {C : Type c} {D : Type d}
  (f : [ C ⊥→ D ⊥]-strict) (g : [ B ⊥→ C ⊥]-strict)
  (h : [ A ⊥→ B ⊥]-strict) →
  f ∘-strict (g ∘-strict h) ≡ (f ∘-strict g) ∘-strict h
∘-strict-assoc = S.∘⊥-assoc

-- Strict ω-continuous functions satisfy an extra monad law.

>>=-∘-return :
  ∀ {a b} {A : Type a} {B : Type b} →
  (f : [ A ⊥→ B ⊥]-strict) →
  ∀ x → x >>=′ (function f ∘ return) ≡ function f x
>>=-∘-return fs = ⊥-rec-⊥
  (record
     { P  = λ x → x >>=′ (f ∘ return) ≡ f x
     ; pe = never >>=′ f ∘ return  ≡⟨ never->>= ⟩
            never                  ≡⟨ sym (strict fs) ⟩∎
            f never                ∎
     ; po = λ x →
              now x >>=′ f ∘ return  ≡⟨ now->>= ⟩∎
              f (now x)              ∎
     ; pl = λ s p →
              ⨆ s >>=′ (f ∘ return)     ≡⟨ ⨆->>= ⟩
              ⨆ ((f ∘ return) ∗-inc s)  ≡⟨ cong ⨆ (_↔_.to equality-characterisation-increasing λ n →

                (f ∘ return) ∗-inc s [ n ]   ≡⟨ p n ⟩∎
                [ f⊑ $ s ]-inc [ n ]         ∎) ⟩

              ⨆ [ f⊑ $ s ]-inc          ≡⟨ sym $ ω-continuous fs s ⟩∎
              f (⨆ s)                   ∎
     ; pp = λ _ → ⊥-is-set
     })
  where
  f⊑ = monotone-function fs
  f  = function fs

-- Strict ω-continuous functions from A ⊥ to B ⊥ are isomorphic to
-- functions from A to B ⊥.

partial↔strict :
  ∀ {a b} {A : Type a} {B : Type b} →
  (A → B ⊥) ↔ [ A ⊥→ B ⊥]-strict
partial↔strict {a} = record
  { surjection = record
    { logical-equivalence = record
      { to   = λ f → record
                 { ω-continuous-function = f ∗
                 ; strict                =
                     never >>=′ f  ≡⟨ never->>= ⟩∎
                     never         ∎
                 }
      ; from = λ f x → function f (return x)
      }
    ; right-inverse-of = λ f →
        _↔_.to equality-characterisation-strict λ x →
          x >>=′ (function f ∘ return)  ≡⟨ >>=-∘-return f x ⟩∎
          function f x                  ∎
    }
  ; left-inverse-of = λ f → ⟨ext⟩ λ x →
      return x >>=′ f  ≡⟨ Monad-laws.left-identity x f ⟩∎
      f x              ∎
  }


{-# OPTIONS --rewriting #-}

module Correctness where

open import Convertibility public
open import Semantics public

{-# BUILTIN REWRITE _≡_ #-}
{-# REWRITE idmono⊩⋆ #-}

-- TODO: Naming things.

module _ {{_ : Model}} where
  ⟦_≔_⟧∈ : ∀ {C Γ w} → (i : C ∈ Γ) → w ⟪ Γ ∖ i ⊫ C ⟫ →
           ∀ {A} → A ∈ Γ → w ⟪ Γ ∖ i ⊫ A ⟫
  ⟦ i ≔ ⟪c⟫ ⟧∈ j  with i ≟∈ j
  ⟦ i ≔ ⟪c⟫ ⟧∈ .i | same   = ⟪c⟫
  ⟦ i ≔ ⟪c⟫ ⟧∈ ._ | diff j = ⟪var⟫ j

  lem≔∈ : ∀ {A C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (j : A ∈ Γ) →
          ⟦ i ≔ eval c ⟧∈ j ≡ eval ([ i ≔ c ]∈ j) {w}
  lem≔∈ i c j  with i ≟∈ j
  lem≔∈ i c .i | same   = refl
  lem≔∈ i c ._ | diff j = refl

-- TODO: Naming things.

module _ {{_ : Model}} where
  plug : ∀ {C Γ w} → (i : C ∈ Γ) → w ⊩⋆ Γ ∖ i → w ⊩ C → w ⊩⋆ Γ
  plug top     γ       s = γ , s
  plug (pop i) (γ , r) s = plug i γ s , r

  ⟦_≔_⟧ : ∀ {C Γ w} → (i : C ∈ Γ) → w ⟪ Γ ∖ i ⊫ C ⟫ →
          ∀ {A} → (∀ {w′} → w′ ⟪ Γ ⊫ A ⟫) → w ⟪ Γ ∖ i ⊫ A ⟫
  ⟦ i ≔ ⟪c⟫ ⟧ ⟪d⟫ = λ γ → ⟪d⟫ (plug i γ (⟪c⟫ γ))

  -- TODO: The var case in lem≔.

  postulate
    oops≔var₁ : ∀ {C Γ w} → (i : C ∈ Γ) (⟪c⟫ : w ⟪ Γ ∖ i ⊫ C ⟫) (γ : w ⊩⋆ Γ ∖ i) →
                ⟪var⟫ {C} i (plug i γ (⟪c⟫ γ)) ≡ ⟪c⟫ γ
  -- oops≔var₁ top     ⟪c⟫ γ       = refl
  -- oops≔var₁ (pop i) ⟪c⟫ (γ , s) = {!!}

  postulate
    oops≔var₂ : ∀ {A C Γ w} → (i : C ∈ Γ) (⟪c⟫ : w ⟪ Γ ∖ i ⊫ C ⟫) (j : A ∈ Γ ∖ i) (γ : w ⊩⋆ Γ ∖ i) →
                ⟪var⟫ {A} (mono∈ (thin⊆ i) j) (plug i γ (⟪c⟫ γ)) ≡ ⟪var⟫ {A} j γ
  -- oops≔var₂ top     ⟪c⟫ j γ       = cong² ⟪var⟫ (idmono∈ j) refl
  -- oops≔var₂ (pop i) ⟪c⟫ j (γ , s) = {!!}

  oops≔var : ∀ {A C Γ w} → (i : C ∈ Γ) (⟪c⟫ : w ⟪ Γ ∖ i ⊫ C ⟫) (j : A ∈ Γ) →
             ⟦ i ≔ ⟪c⟫ ⟧ {A} (⟪var⟫ {A} j) ≡ ⟦ i ≔ ⟪c⟫ ⟧∈ {A} j
  oops≔var i ⟪c⟫ j  with i ≟∈ j
  oops≔var i ⟪c⟫ .i | same   = funext (λ γ → oops≔var₁ i ⟪c⟫ γ)
  oops≔var i ⟪c⟫ ._ | diff j = funext (λ γ → oops≔var₂ i ⟪c⟫ j γ)

  -- TODO: The lam case in lem≔.

  lem₁ : ∀ {C Γ w w′} → (i : C ∈ Γ) (γ : w ⊩⋆ Γ ∖ i) (s : w ⊩ C) (p : w ≤ w′) →
         mono⊩⋆ p (plug i γ s) ≡ plug i (mono⊩⋆ p γ) (mono⊩ {C} p s)
  lem₁ top     γ       s p = refl
  lem₁ (pop i) (γ , r) s p = cong² _,_ (lem₁ i γ s p) refl

  mutual
    postulate
      lem₂ : ∀ {A Γ w w′} → (p : w ≤ w′) (d : Γ ⊢ A) (γ : w ⊩⋆ Γ) →
             mono⊩ {A} p (eval d γ) ≡ eval d (mono⊩⋆ p γ)
    -- lem₂ p (var {A} j)        γ = {!!}
    -- lem₂ p (lam {A} {B} d)    γ = {!!}
    -- lem₂ p (app {A} {B} d e)  γ = {!!}
    -- lem₂ p (pair {A} {B} d e) γ = lem₂pair p d e γ
    -- lem₂ p (fst {A} {B} d)    γ = lem₂fst p d γ
    -- lem₂ p (snd {A} {B} d)    γ = lem₂snd p d γ
    -- lem₂ p unit               γ = refl

    lem₂pair : ∀ {A B Γ w w′} → (p : w ≤ w′) (d : Γ ⊢ A) (e : Γ ⊢ B) (γ : w ⊩⋆ Γ) →
               mono⊩ {A ⩕ B} p (⟪pair⟫ {A} {B} (eval d) (eval e) γ) ≡ ⟪pair⟫ {A} {B} (eval d) (eval e) (mono⊩⋆ p γ)
    lem₂pair {A} {B} p d e γ =
      begin
        mono⊩ {A ⩕ B} p (⟪pair⟫ {A} {B} (eval d) (eval e) γ)
      ≡⟨⟩
        ⟪pair⟫ {A} {B} (λ γ′ → mono⊩ {A} p (eval d γ)) (λ γ′ → mono⊩ {B} p (eval e γ)) (mono⊩⋆ p γ)
      ≡⟨ cong² (λ ⟪d⟫ ⟪e⟫ → ⟪pair⟫ {A} {B} ⟪d⟫ ⟪e⟫ (mono⊩⋆ p γ)) (funext (λ γ′ → lem₂ p d γ)) (funext (λ γ′ → lem₂ p e γ)) ⟩
        ⟪pair⟫ {A} {B} (λ γ′ → eval d (mono⊩⋆ p γ)) (λ γ′ → eval e (mono⊩⋆ p γ)) (mono⊩⋆ p γ)
      ≡⟨⟩
        ⟪pair⟫ {A} {B} (eval d) (eval e) (mono⊩⋆ p γ)
      ∎

    lem₂fst : ∀ {A B Γ w w′} → (p : w ≤ w′) (d : Γ ⊢ A ⩕ B) (γ : w ⊩⋆ Γ) →
              mono⊩ {A} p (⟪fst⟫ {A} {B} (eval d) γ) ≡ ⟪fst⟫ {A} {B} (eval d) (mono⊩⋆ p γ)
    lem₂fst {A} {B} p d γ =
      begin
        mono⊩ {A} p (⟪fst⟫ {A} {B} (eval d) γ)
      ≡⟨⟩
        ⟪fst⟫ {A} {B} (λ γ′ → mono⊩ {A ⩕ B} p (eval d γ)) (mono⊩⋆ p γ)
      ≡⟨ cong (λ ⟪d⟫ → ⟪fst⟫ {A} {B} ⟪d⟫ (mono⊩⋆ p γ)) (funext (λ γ′ → lem₂ p d γ)) ⟩
        ⟪fst⟫ {A} {B} (λ γ′ → eval d (mono⊩⋆ p γ)) (mono⊩⋆ p γ)
      ≡⟨⟩
        ⟪fst⟫ {A} {B} (eval d) (mono⊩⋆ p γ)
      ∎

    lem₂snd : ∀ {A B Γ w w′} → (p : w ≤ w′) (d : Γ ⊢ A ⩕ B) (γ : w ⊩⋆ Γ) →
              mono⊩ {B} p (⟪snd⟫ {A} {B} (eval d) γ) ≡ ⟪snd⟫ {A} {B} (eval d) (mono⊩⋆ p γ)
    lem₂snd {A} {B} p d γ =
      begin
        mono⊩ {B} p (⟪snd⟫ {A} {B} (eval d) γ)
      ≡⟨⟩
        ⟪snd⟫ {A} {B} (λ γ′ → mono⊩ {A ⩕ B} p (eval d γ)) (mono⊩⋆ p γ)
      ≡⟨ cong (λ ⟪d⟫ → ⟪snd⟫ {A} {B} ⟪d⟫ (mono⊩⋆ p γ)) (funext (λ γ′ → lem₂ p d γ)) ⟩
        ⟪snd⟫ {A} {B} (λ γ′ → eval d (mono⊩⋆ p γ)) (mono⊩⋆ p γ)
      ≡⟨⟩
        ⟪snd⟫ {A} {B} (eval d) (mono⊩⋆ p γ)
      ∎

  postulate
    oops≔lam₂ : ∀ {A C Γ w w′} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (γ : w ⊩⋆ Γ ∖ i) (p : w ≤ w′) (s : w′ ⊩ A) →
                mono⊩ {C} p (eval c γ) ≡ eval (mono⊢ (weak⊆ {A = A}) c) (mono⊩⋆ p γ , s)
  -- oops≔lam₂ i c γ p s = {!!}

  oops≔lam₁ : ∀ {A B C Γ w w′} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ , A ⊢ B) (γ : w ⊩⋆ Γ ∖ i) (p : w ≤ w′) (s : w′ ⊩ A) →
              eval d (mono⊩⋆ p (plug i γ (eval c γ)) , s) ≡ eval d (plug i (mono⊩⋆ p γ) (eval (mono⊢ (weak⊆ {A = A}) c) (mono⊩⋆ p γ , s)) , s)
  oops≔lam₁ {A} {B} {C} i c d γ p s =
    begin
      eval d (mono⊩⋆ p (plug i γ (eval c γ)) , s)
    ≡⟨ cong (eval d) (cong² _,_ (lem₁ i γ (eval c γ) p) refl) ⟩
      eval d (plug i (mono⊩⋆ p γ) (mono⊩ {C} p (eval c γ)) , s)
    ≡⟨ cong (eval d) (cong² _,_ (cong² (plug i) refl (oops≔lam₂ i c γ p s)) refl) ⟩
      eval d (plug i (mono⊩⋆ p γ) (eval (mono⊢ weak⊆ c) (mono⊩⋆ p γ , s)) , s)
    ∎

  oops≔lam : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ , A ⊢ B) →
             ⟦ i ≔ eval c {w} ⟧ {A ⇒ B} (⟪lam⟫ {A} {B} (eval d)) ≡
               ⟪lam⟫ {A} {B} (⟦ pop {A = C} {B = A} i ≔ eval (mono⊢ (weak⊆ {A = A}) c) ⟧ {B} (eval d))
  oops≔lam i c d = funext (λ γ → ⟨funext⟩ (funext (λ p → funext (λ s → oops≔lam₁ i c d γ p s))))

  mutual
    lem≔ : ∀ {A C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ ⊢ A) →
           ⟦ i ≔ eval c ⟧ {A} (eval d) ≡ eval ([ i ≔ c ] d) {w}
    lem≔ i c (var j)    = lem≔var i c j
    lem≔ i c (lam d)    = lem≔lam i c d
    lem≔ i c (app d e)  = lem≔app i c d e
    lem≔ i c (pair d e) = lem≔pair i c d e
    lem≔ i c (fst d)    = lem≔fst i c d
    lem≔ i c (snd d)    = lem≔snd i c d
    lem≔ i c unit       = lem≔unit i c

    lem≔var : ∀ {A C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (j : A ∈ Γ) →
              ⟦ i ≔ eval c ⟧ {A} (⟪var⟫ {A} j) ≡ eval ([ i ≔ c ] (var j)) {w}
    lem≔var {A} i c j =
      begin
        ⟦ i ≔ eval c ⟧ {A} (⟪var⟫ {A} j)
      ≡⟨ oops≔var i (eval c) j ⟩
        ⟦ i ≔ eval c ⟧∈ {A} j
      ≡⟨ lem≔∈ i c j ⟩
        eval ([ i ≔ c ]∈ j)
      ≡⟨⟩
        eval ([ i ≔ c ] (var j))
      ∎

    lem≔lam : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ , A ⊢ B) →
              ⟦ i ≔ eval c ⟧ {A ⇒ B} (⟪lam⟫ {A} {B} (eval d)) ≡ eval ([ i ≔ c ] (lam d)) {w}
    lem≔lam {A} {B} {C} i c d =
      begin
        ⟦ i ≔ eval c ⟧ {A ⇒ B} (⟪lam⟫ {A} {B} (eval d))
      ≡⟨ oops≔lam i c d ⟩
        ⟪lam⟫ {A} {B} (⟦ pop {A = C} {B = A} i ≔ eval (mono⊢ (weak⊆ {A = A}) c) ⟧ {B} (eval d))
      ≡⟨ cong (⟪lam⟫ {A} {B}) (⟨funext⟩ (lem≔ (pop i) (mono⊢ (weak⊆ {A = A}) c) d)) ⟩
        ⟪lam⟫ {A} {B} (eval ([ pop {A = C} {B = A} i ≔ mono⊢ (weak⊆ {A = A}) c ] d))
      ≡⟨⟩
        eval (lam ([ pop i ≔ mono⊢ weak⊆ c ] d))
      ≡⟨⟩
        eval ([ i ≔ c ] (lam d))
      ∎

    lem≔app : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ ⊢ A ⇒ B) (e : Γ ⊢ A) →
              ⟦ i ≔ eval c ⟧ {B} (⟪app⟫ {A} {B} (eval d) (eval e)) ≡ eval ([ i ≔ c ] (app d e)) {w}
    lem≔app {A} {B} i c d e =
      begin
        ⟦ i ≔ eval c ⟧ {B} (⟪app⟫ {A} {B} (eval d) (eval e))
      ≡⟨⟩
        ⟪app⟫ {A} {B} (⟦ i ≔ eval c ⟧ {A ⇒ B} (eval d)) (⟦ i ≔ eval c ⟧ {A} (eval e))
      ≡⟨ cong² (⟪app⟫ {A} {B}) (lem≔ i c d) (funext (λ γ → extfun (lem≔ i c e) γ)) ⟩
        ⟪app⟫ {A} {B} (eval ([ i ≔ c ] d)) (eval ([ i ≔ c ] e))
      ≡⟨⟩
        eval (app ([ i ≔ c ] d) ([ i ≔ c ] e))
      ≡⟨⟩
        eval ([ i ≔ c ] (app d e))
      ∎

    lem≔pair : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ ⊢ A) (e : Γ ⊢ B) →
               ⟦ i ≔ eval c ⟧ {A ⩕ B} (⟪pair⟫ {A} {B} (eval d) (eval e)) ≡ eval ([ i ≔ c ] (pair d e)) {w}
    lem≔pair {A} {B} i c d e =
      begin
        ⟦ i ≔ eval c ⟧ {A ⩕ B} (⟪pair⟫ {A} {B} (eval d) (eval e))
      ≡⟨⟩
        ⟪pair⟫ {A} {B} (⟦ i ≔ eval c ⟧ {A} (eval d)) (⟦ i ≔ eval c ⟧ {B} (eval e))
      ≡⟨ cong² (⟪pair⟫ {A} {B}) (lem≔ i c d) (lem≔ i c e) ⟩
        ⟪pair⟫ {A} {B} (eval ([ i ≔ c ] d)) (eval ([ i ≔ c ] e))
      ≡⟨⟩
        eval (pair ([ i ≔ c ] d) ([ i ≔ c ] e))
      ≡⟨⟩
        eval ([ i ≔ c ] (pair d e))
      ∎

    lem≔fst : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ ⊢ A ⩕ B) →
              ⟦ i ≔ eval c ⟧ {A} (⟪fst⟫ {A} {B} (eval d)) ≡ eval ([ i ≔ c ] (fst d)) {w}
    lem≔fst {A} {B} i c d =
      begin
        ⟦ i ≔ eval c ⟧ {A} (⟪fst⟫ {A} {B} (eval d))
      ≡⟨⟩
        ⟪fst⟫ {A} {B} (⟦ i ≔ eval c ⟧ {A ⩕ B} (eval d))
      ≡⟨ cong (⟪fst⟫ {A} {B}) (lem≔ i c d) ⟩
        ⟪fst⟫ {A} {B} (eval ([ i ≔ c ] d))
      ≡⟨⟩
        eval (fst ([ i ≔ c ] d))
      ≡⟨⟩
        eval ([ i ≔ c ] (fst d))
      ∎

    lem≔snd : ∀ {A B C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) (d : Γ ⊢ A ⩕ B) →
              ⟦ i ≔ eval c ⟧ {B} (⟪snd⟫ {A} {B} (eval d)) ≡ eval ([ i ≔ c ] (snd d)) {w}
    lem≔snd {A} {B} i c d =
      begin
        ⟦ i ≔ eval c ⟧ {B} (⟪snd⟫ {A} {B} (eval d))
      ≡⟨⟩
        ⟪snd⟫ {A} {B} (⟦ i ≔ eval c ⟧ {A ⩕ B} (eval d))
      ≡⟨ cong (⟪snd⟫ {A} {B}) (lem≔ i c d) ⟩
        ⟪snd⟫ {A} {B} (eval ([ i ≔ c ] d))
      ≡⟨⟩
        eval (snd ([ i ≔ c ] d))
      ≡⟨⟩
        eval ([ i ≔ c ] (snd d))
      ∎

    lem≔unit : ∀ {C Γ w} → (i : C ∈ Γ) (c : Γ ∖ i ⊢ C) →
               ⟦ i ≔ eval c ⟧ {⫪} ⟪unit⟫ ≡ eval ([ i ≔ c ] unit) {w}
    lem≔unit i c = refl

-- TODO: Naming things.

-- NOTE: This is using {-# REWRITE idmono⊩⋆ #-}.

module _ {{_ : Model}} where
  lemreduce⇒ : ∀ {A B Γ w} → (d : Γ , A ⊢ B) (e : Γ ⊢ A) →
                ⟪app⟫ {A} {B} (⟪lam⟫ {A} {B} (eval d)) (eval e {w}) ≡ eval ([ top ≔ e ] d) {w}
  lemreduce⇒ d e = lem≔ top e d

-- TODO: The expand⇒ case in eval✓.

module _ {{_ : Model}} where
  postulate
    lemexpand⇒ : ∀ {A B Γ w} → (d : Γ ⊢ A ⇒ B) →
                  eval d {w} ≡ ⟪lam⟫ {A} {B} (⟪app⟫ {A} {B} {Γ , A} (eval (mono⊢ {A ⇒ B} (weak⊆ {A = A}) d)) (⟪v₀⟫ {A}))
  -- lemexpand⇒ d = {!!}

-- Correctness of evaluation with respect to β-η-equivalence.

module _ {{_ : Model}} where
  eval✓ : ∀ {A Γ} {d d′ : Γ ⊢ A} {w} → d ≡ᵝᵑ d′ → eval d {w} ≡ eval d′ {w}
  eval✓ refl≡ᵝᵑ                   = refl
  eval✓ (trans≡ᵝᵑ p q)            = trans (eval✓ p) (eval✓ q)
  eval✓ (sym≡ᵝᵑ p)                = sym (eval✓ p)
  eval✓ (cong≡ᵝᵑlam {A} {B} p)    = cong (⟪lam⟫ {A} {B}) (⟨funext⟩ (eval✓ p))
  eval✓ (cong≡ᵝᵑapp {A} {B} p q)  = cong² (⟪app⟫ {A} {B}) (eval✓ p) (eval✓ q)
  eval✓ (cong≡ᵝᵑpair {A} {B} p q) = cong² (⟪pair⟫ {A} {B}) (eval✓ p) (eval✓ q)
  eval✓ (cong≡ᵝᵑfst {A} {B} p)    = cong (⟪fst⟫ {A} {B}) (eval✓ p)
  eval✓ (cong≡ᵝᵑsnd {A} {B} p)    = cong (⟪snd⟫ {A} {B}) (eval✓ p)
  eval✓ (reduce⇒ d e)            = lemreduce⇒ d e
  eval✓ (reduce⩕₁ d e)            = refl
  eval✓ (reduce⩕₂ d e)            = refl
  eval✓ (expand⇒ d)              = lemexpand⇒ d
  eval✓ (expand⩕ d)               = refl
  eval✓ (expand⫪ d)              = refl


open import Data.Nat hiding (_^_)
open import Data.List as List hiding (null)
open import Data.List.Membership.Propositional
open import Data.List.Relation.Unary.Any
open import Data.List.Relation.Unary.All
open import Data.List.Prefix
open import Data.Product hiding (map)
open import Data.Unit
open import Relation.Binary.PropositionalEquality hiding ([_])
open ≡-Reasoning

-- This file contains the definition of monads used for computation in
-- the definitional interpreter for MJ using scopes and frames,
-- described in Section 5 of the paper.

module MJSF.Monad (k : ℕ) where

open import MJSF.Syntax k
open import MJSF.Values k
open import ScopesFrames.ScopesFrames k Ty

module MonadG (g : Graph) where

  open SyntaxG g
  open ValuesG g
  open UsesVal Valᵗ valᵗ-weaken renaming (getFrame to getFrame')
  open import Common.Weakening

  -- Computations may either time out, raise a null-pointer exception,
  -- or successfully terminate to produce a result:

  data Res (a : Set) : Set where
    timeout : Res a
    nullpointer : Res a
    ok : (x : a) → Res a

  -- The monad is similar to the monad used for STLCSF, except it uses
  -- `Res` instead of `Maybe`:

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

  -- We define some usual monad operations:

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

  fmap : ∀ {A B : List Scope → Set}{Γ Σ} → (∀ {Σ} → A Σ → B Σ) → M Γ A Σ → M Γ B Σ
  fmap g m f h
    with (m f h)
  ...  | timeout = timeout
  ...  | nullpointer = nullpointer
  ...  | ok (Σ' , h' , v' , ext') = ok (Σ' , h' , g v' , ext')

  join : ∀ {A : List Scope → Set}{Γ Σ} → M Γ (M Γ A) Σ → M Γ A Σ
  join m f h
    with (m f h)
  ...  | timeout = timeout
  ...  | nullpointer = nullpointer
  ...  | ok (Σ' , h' , m' , ext')
       with (m' (wk ext' f) h')
  ...     | timeout = timeout
  ...     | nullpointer = nullpointer
  ...     | ok (Σ'' , h'' , v'' , ext'') = ok ((Σ'' , h'' , v'' , ext' ⊚ ext''))

  _>>=_     :  ∀ {s Σ}{p q : List Scope → Set} →
               M s p Σ → (∀ {Σ'} → p Σ' → M s q Σ') → M s q Σ
  (a >>= b) = join (fmap b a)

  -- To program in dependent-passing style, we use the variant of
  -- monadic strength also used for STLCSF.

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

  -- The remaining definitions in this file are straightforward
  -- monadic liftings of the coercion function from `MJSF.Values` and
  -- of the frame operations.

  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ᴹ _ _ = timeout

  raise : ∀ {s Σ}{p : List Scope → Set} → M s p Σ
  raise _ _ = nullpointer

  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') = ok (_ , h' , f' , ∷ʳ-⊒ s Σ)

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

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

  getf : ∀ {s s' Σ} → (s ⟶ s')  → M s (Frame s') Σ
  getf p f h = return (getFrame' p f h) f h

  getd : ∀ {s t Σ} → t ∈ declsOf s → M s (Valᵗ t) Σ
  getd d f h = return (getSlot d f h) f h

  getl : ∀ {s s' Σ} → s' ∈ edgesOf s → M s (Frame s') Σ
  getl e f h = return (getLink e f h) f h

  setd  :  ∀ {s t Σ} → t ∈ declsOf s → Valᵗ t Σ → M s (λ _ → ⊤) Σ
  setd d v f h with (setSlot d v f h)
  ...             | h' = return tt f h'

  setv  :  ∀ {s t Σ} → (s ↦ t) → Valᵗ t Σ → M s (λ _ → ⊤) Σ
  setv p v f h with (setVal p v f h)
  ...             | h' = return tt f h'


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

open import Definition.Typed.EqualityRelation

module Definition.LogicalRelation.Substitution.Introductions.Lambda {{eqrel : EqRelSet}} where
open EqRelSet {{...}}

open import Definition.Untyped as U hiding (wk)
open import Definition.Untyped.Properties
open import Definition.Typed
open import Definition.Typed.Properties
open import Definition.Typed.Weakening as T hiding (wk; wkTerm; wkEqTerm)
open import Definition.Typed.RedSteps
open import Definition.LogicalRelation
open import Definition.LogicalRelation.ShapeView
open import Definition.LogicalRelation.Irrelevance
open import Definition.LogicalRelation.Weakening
open import Definition.LogicalRelation.Properties
open import Definition.LogicalRelation.Application
open import Definition.LogicalRelation.Substitution
open import Definition.LogicalRelation.Substitution.Properties
open import Definition.LogicalRelation.Substitution.Introductions.Pi

open import Tools.Product
import Tools.PropositionalEquality as PE

-- Valid lambda term construction.
lamᵛ : ∀ {F G t Γ l}
       ([Γ] : ⊩ᵛ Γ)
       ([F] : Γ ⊩ᵛ⟨ l ⟩ F / [Γ])
       ([G] : Γ ∙ F ⊩ᵛ⟨ l ⟩ G / ([Γ] ∙″ [F]))
       ([t] : Γ ∙ F ⊩ᵛ⟨ l ⟩ t ∷ G / [Γ] ∙″ [F] / [G])
     → Γ ⊩ᵛ⟨ l ⟩ lam t ∷ Π F ▹ G / [Γ] / Πᵛ {F} {G} [Γ] [F] [G]
lamᵛ {F} {G} {t} {Γ} {l} [Γ] [F] [G] [t] {Δ = Δ} {σ = σ} ⊢Δ [σ] =
  let ⊢F = escape (proj₁ ([F] ⊢Δ [σ]))
      [liftσ] = liftSubstS {F = F} [Γ] ⊢Δ [F] [σ]
      [ΠFG] = Πᵛ {F} {G} [Γ] [F] [G]
      Πᵣ F′ G′ D′ ⊢F′ ⊢G′ A≡A′ [F]′ [G]′ G-ext =
        extractMaybeEmbΠ (Π-elim (proj₁ ([ΠFG] ⊢Δ [σ])))
      lamt : ∀ {Δ σ} (⊢Δ : ⊢ Δ) ([σ] : Δ ⊩ˢ σ ∷ Γ / [Γ] / ⊢Δ)
           → Δ ⊩⟨ l ⟩ subst σ (lam t) ∷ subst σ (Π F ▹ G) / proj₁ ([ΠFG] ⊢Δ [σ])
      lamt {Δ} {σ} ⊢Δ [σ] =
        let [liftσ] = liftSubstS {F = F} [Γ] ⊢Δ [F] [σ]
            [σF] = proj₁ ([F] ⊢Δ [σ])
            ⊢F = escape [σF]
            ⊢wk1F = T.wk (step id) (⊢Δ ∙ ⊢F) ⊢F
            [σG] = proj₁ ([G] (⊢Δ ∙ ⊢F) [liftσ])
            ⊢G = escape [σG]
            [σt] = proj₁ ([t] (⊢Δ ∙ ⊢F) [liftσ])
            ⊢t = escapeTerm [σG] [σt]
            wk1t[0] = irrelevanceTerm″
                        PE.refl
                        (PE.sym (wkSingleSubstId (subst (liftSubst σ) t)))
                        [σG] [σG] [σt]
            β-red′ = PE.subst (λ x → _ ⊢ _ ⇒ _ ∷ x)
                              (wkSingleSubstId (subst (liftSubst σ) G))
                              (β-red ⊢wk1F (T.wkTerm (lift (step id))
                                                     (⊢Δ ∙ ⊢F ∙ ⊢wk1F) ⊢t)
                                                     (var (⊢Δ ∙ ⊢F) here))
            Πᵣ F′ G′ D′ ⊢F′ ⊢G′ A≡A′ [F]′ [G]′ G-ext =
              extractMaybeEmbΠ (Π-elim (proj₁ ([ΠFG] ⊢Δ [σ])))
        in  Πₜ (lam (subst (liftSubst σ) t)) (idRedTerm:*: (lamⱼ ⊢F ⊢t)) lamₙ
               (≅-η-eq ⊢F (lamⱼ ⊢F ⊢t) (lamⱼ ⊢F ⊢t) lamₙ lamₙ
                       (escapeTermEq [σG]
                         (reflEqTerm [σG]
                           (proj₁ (redSubstTerm β-red′ [σG] wk1t[0])))))
               (λ {_} {Δ₁} {a} {b} ρ ⊢Δ₁ [a] [b] [a≡b] →
                  let [ρσ] = wkSubstS [Γ] ⊢Δ ⊢Δ₁ ρ [σ]
                      [a]′ = irrelevanceTerm′ (wk-subst F) ([F]′ ρ ⊢Δ₁)
                                              (proj₁ ([F] ⊢Δ₁ [ρσ])) [a]
                      [b]′ = irrelevanceTerm′ (wk-subst F) ([F]′ ρ ⊢Δ₁)
                                              (proj₁ ([F] ⊢Δ₁ [ρσ])) [b]
                      [a≡b]′ = irrelevanceEqTerm′ (wk-subst F) ([F]′ ρ ⊢Δ₁)
                                                  (proj₁ ([F] ⊢Δ₁ [ρσ])) [a≡b]
                      ⊢F₁′ = escape (proj₁ ([F] ⊢Δ₁ [ρσ]))
                      ⊢F₁ = escape ([F]′ ρ ⊢Δ₁)
                      [G]₁ = proj₁ ([G] (⊢Δ₁ ∙ ⊢F₁′)
                                        (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ]))
                      [G]₁′ = irrelevanceΓ′
                                (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                                (PE.sym (wk-subst-lift G)) [G]₁
                      [t]′ = irrelevanceTermΓ″
                               (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                               (PE.sym (wk-subst-lift G))
                               (PE.sym (wk-subst-lift t))
                               [G]₁ [G]₁′
                               (proj₁ ([t] (⊢Δ₁ ∙ ⊢F₁′)
                                           (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ])))
                      ⊢a = escapeTerm ([F]′ ρ ⊢Δ₁) [a]
                      ⊢b = escapeTerm ([F]′ ρ ⊢Δ₁) [b]
                      ⊢t = escapeTerm [G]₁′ [t]′
                      G[a]′ = proj₁ ([G] ⊢Δ₁ ([ρσ] , [a]′))
                      G[a] = [G]′ ρ ⊢Δ₁ [a]
                      t[a] = irrelevanceTerm″
                               (PE.sym (singleSubstWkComp a σ G))
                               (PE.sym (singleSubstWkComp a σ t))
                               G[a]′ G[a]
                               (proj₁ ([t] ⊢Δ₁ ([ρσ] , [a]′)))
                      G[b]′ = proj₁ ([G] ⊢Δ₁ ([ρσ] , [b]′))
                      G[b] = [G]′ ρ ⊢Δ₁ [b]
                      t[b] = irrelevanceTerm″
                               (PE.sym (singleSubstWkComp b σ G))
                               (PE.sym (singleSubstWkComp b σ t))
                               G[b]′ G[b]
                               (proj₁ ([t] ⊢Δ₁ ([ρσ] , [b]′)))
                      lamt∘a≡t[a] = proj₂ (redSubstTerm (β-red ⊢F₁ ⊢t ⊢a) G[a] t[a])
                      G[a]≡G[b] = G-ext ρ ⊢Δ₁ [a] [b] [a≡b]
                      t[a]≡t[b] = irrelevanceEqTerm″
                                    (PE.sym (singleSubstWkComp a σ t))
                                    (PE.sym (singleSubstWkComp b σ t))
                                    (PE.sym (singleSubstWkComp a σ G))
                                    G[a]′ G[a]
                                    (proj₂ ([t] ⊢Δ₁ ([ρσ] , [a]′)) ([ρσ] , [b]′)
                                                (reflSubst [Γ] ⊢Δ₁ [ρσ] , [a≡b]′))
                      t[b]≡lamt∘b =
                        convEqTerm₂ G[a] G[b] G[a]≡G[b]
                          (symEqTerm G[b] (proj₂ (redSubstTerm (β-red ⊢F₁ ⊢t ⊢b)
                                                               G[b] t[b])))
                  in  transEqTerm G[a] lamt∘a≡t[a]
                             (transEqTerm G[a] t[a]≡t[b] t[b]≡lamt∘b))
               (λ {_} {Δ₁} {a} ρ ⊢Δ₁ [a] →
                  let [ρσ] = wkSubstS [Γ] ⊢Δ ⊢Δ₁ ρ [σ]
                      [a]′ = irrelevanceTerm′ (wk-subst F) ([F]′ ρ ⊢Δ₁)
                                              (proj₁ ([F] ⊢Δ₁ [ρσ])) [a]
                      ⊢F₁′ = escape (proj₁ ([F] ⊢Δ₁ [ρσ]))
                      ⊢F₁ = escape ([F]′ ρ ⊢Δ₁)
                      [G]₁ = proj₁ ([G] (⊢Δ₁ ∙ ⊢F₁′)
                                        (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ]))
                      [G]₁′ = irrelevanceΓ′
                                (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                                (PE.sym (wk-subst-lift G)) [G]₁
                      [t]′ = irrelevanceTermΓ″
                               (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                               (PE.sym (wk-subst-lift G))
                               (PE.sym (wk-subst-lift t))
                               [G]₁ [G]₁′
                               (proj₁ ([t] (⊢Δ₁ ∙ ⊢F₁′)
                                           (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ])))
                      ⊢a = escapeTerm ([F]′ ρ ⊢Δ₁) [a]
                      ⊢t = escapeTerm [G]₁′ [t]′
                      G[a]′ = proj₁ ([G] ⊢Δ₁ ([ρσ] , [a]′))
                      G[a] = [G]′ ρ ⊢Δ₁ [a]
                      t[a] = irrelevanceTerm″ (PE.sym (singleSubstWkComp a σ G))
                                               (PE.sym (singleSubstWkComp a σ t))
                                               G[a]′ G[a]
                                               (proj₁ ([t] ⊢Δ₁ ([ρσ] , [a]′)))
                  in  proj₁ (redSubstTerm (β-red ⊢F₁ ⊢t ⊢a) G[a] t[a]))
  in  lamt ⊢Δ [σ]
  ,   (λ {σ′} [σ′] [σ≡σ′] →
         let [liftσ′] = liftSubstS {F = F} [Γ] ⊢Δ [F] [σ′]
             Πᵣ F″ G″ D″ ⊢F″ ⊢G″ A≡A″ [F]″ [G]″ G-ext′ =
               extractMaybeEmbΠ (Π-elim (proj₁ ([ΠFG] ⊢Δ [σ′])))
             ⊢F′ = escape (proj₁ ([F] ⊢Δ [σ′]))
             [G]₁ = proj₁ ([G] (⊢Δ ∙ ⊢F) [liftσ])
             [G]₁′ = proj₁ ([G] (⊢Δ ∙ ⊢F′) [liftσ′])
             [σΠFG≡σ′ΠFG] = proj₂ ([ΠFG] ⊢Δ [σ]) [σ′] [σ≡σ′]
             ⊢t = escapeTerm [G]₁ (proj₁ ([t] (⊢Δ ∙ ⊢F) [liftσ]))
             ⊢t′ = escapeTerm [G]₁′ (proj₁ ([t] (⊢Δ ∙ ⊢F′) [liftσ′]))
             neuVar = neuTerm ([F]′ (step id) (⊢Δ ∙ ⊢F))
                              (var 0) (var (⊢Δ ∙ ⊢F) here)
                              (~-var (var (⊢Δ ∙ ⊢F) here))
             σlamt∘a≡σ′lamt∘a : ∀ {ρ Δ₁ a} → ([ρ] : ρ ∷ Δ₁ ⊆ Δ) (⊢Δ₁ : ⊢ Δ₁)
                 → ([a] : Δ₁ ⊩⟨ l ⟩ a ∷ U.wk ρ (subst σ F) / [F]′ [ρ] ⊢Δ₁)
                 → Δ₁ ⊩⟨ l ⟩ U.wk ρ (subst σ (lam t)) ∘ a
                           ≡ U.wk ρ (subst σ′ (lam t)) ∘ a
                           ∷ U.wk (lift ρ) (subst (liftSubst σ) G) [ a ]
                           / [G]′ [ρ] ⊢Δ₁ [a]
             σlamt∘a≡σ′lamt∘a {_} {Δ₁} {a} ρ ⊢Δ₁ [a] =
                let [ρσ] = wkSubstS [Γ] ⊢Δ ⊢Δ₁ ρ [σ]
                    [ρσ′] = wkSubstS [Γ] ⊢Δ ⊢Δ₁ ρ [σ′]
                    [ρσ≡ρσ′] = wkSubstSEq [Γ] ⊢Δ ⊢Δ₁ ρ [σ] [σ≡σ′]
                    ⊢F₁′ = escape (proj₁ ([F] ⊢Δ₁ [ρσ]))
                    ⊢F₁ = escape ([F]′ ρ ⊢Δ₁)
                    ⊢F₂′ = escape (proj₁ ([F] ⊢Δ₁ [ρσ′]))
                    ⊢F₂ = escape ([F]″ ρ ⊢Δ₁)
                    [σF≡σ′F] = proj₂ ([F] ⊢Δ₁ [ρσ]) [ρσ′] [ρσ≡ρσ′]
                    [a]′ = irrelevanceTerm′ (wk-subst F) ([F]′ ρ ⊢Δ₁)
                                            (proj₁ ([F] ⊢Δ₁ [ρσ])) [a]
                    [a]″ = convTerm₁ (proj₁ ([F] ⊢Δ₁ [ρσ]))
                                      (proj₁ ([F] ⊢Δ₁ [ρσ′]))
                                      [σF≡σ′F] [a]′
                    ⊢a = escapeTerm ([F]′ ρ ⊢Δ₁) [a]
                    ⊢a′ = escapeTerm ([F]″ ρ ⊢Δ₁)
                            (irrelevanceTerm′ (PE.sym (wk-subst F))
                                              (proj₁ ([F] ⊢Δ₁ [ρσ′]))
                                              ([F]″ ρ ⊢Δ₁)
                                              [a]″)
                    G[a]′ = proj₁ ([G] ⊢Δ₁ ([ρσ] , [a]′))
                    G[a]₁′ = proj₁ ([G] ⊢Δ₁ ([ρσ′] , [a]″))
                    G[a] = [G]′ ρ ⊢Δ₁ [a]
                    G[a]″ = [G]″ ρ ⊢Δ₁
                                   (irrelevanceTerm′ (PE.sym (wk-subst F))
                                                     (proj₁ ([F] ⊢Δ₁ [ρσ′]))
                                                     ([F]″ ρ ⊢Δ₁)
                                                     [a]″)
                    [σG[a]≡σ′G[a]] = irrelevanceEq″
                                       (PE.sym (singleSubstWkComp a σ G))
                                       (PE.sym (singleSubstWkComp a σ′ G))
                                       G[a]′ G[a]
                                       (proj₂ ([G] ⊢Δ₁ ([ρσ] , [a]′))
                                              ([ρσ′] , [a]″)
                                              (consSubstSEq {t = a} {A = F}
                                                [Γ] ⊢Δ₁ [ρσ] [ρσ≡ρσ′] [F] [a]′))
                    [G]₁ = proj₁ ([G] (⊢Δ₁ ∙ ⊢F₁′)
                                      (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ]))
                    [G]₁′ = irrelevanceΓ′
                              (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                              (PE.sym (wk-subst-lift G)) [G]₁
                    [G]₂ = proj₁ ([G] (⊢Δ₁ ∙ ⊢F₂′)
                                      (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ′]))
                    [G]₂′ = irrelevanceΓ′
                              (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                              (PE.sym (wk-subst-lift G)) [G]₂
                    [t]′ = irrelevanceTermΓ″
                             (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                             (PE.sym (wk-subst-lift G)) (PE.sym (wk-subst-lift t))
                             [G]₁ [G]₁′
                             (proj₁ ([t] (⊢Δ₁ ∙ ⊢F₁′)
                                         (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ])))
                    [t]″ = irrelevanceTermΓ″
                              (PE.cong (λ x → _ ∙ x) (PE.sym (wk-subst F)))
                              (PE.sym (wk-subst-lift G)) (PE.sym (wk-subst-lift t))
                              [G]₂ [G]₂′
                              (proj₁ ([t] (⊢Δ₁ ∙ ⊢F₂′)
                                          (liftSubstS {F = F} [Γ] ⊢Δ₁ [F] [ρσ′])))
                    ⊢t = escapeTerm [G]₁′ [t]′
                    ⊢t′ = escapeTerm [G]₂′ [t]″
                    t[a] = irrelevanceTerm″
                             (PE.sym (singleSubstWkComp a σ G))
                             (PE.sym (singleSubstWkComp a σ t)) G[a]′ G[a]
                             (proj₁ ([t] ⊢Δ₁ ([ρσ] , [a]′)))
                    t[a]′ = irrelevanceTerm″
                              (PE.sym (singleSubstWkComp a σ′ G))
                              (PE.sym (singleSubstWkComp a σ′ t))
                              G[a]₁′ G[a]″
                              (proj₁ ([t] ⊢Δ₁ ([ρσ′] , [a]″)))
                    [σlamt∘a≡σt[a]] = proj₂ (redSubstTerm (β-red ⊢F₁ ⊢t ⊢a)
                                                          G[a] t[a])
                    [σ′t[a]≡σ′lamt∘a] =
                      convEqTerm₂ G[a] G[a]″ [σG[a]≡σ′G[a]]
                        (symEqTerm G[a]″
                           (proj₂ (redSubstTerm (β-red ⊢F₂ ⊢t′ ⊢a′)
                                                G[a]″ t[a]′)))
                    [σt[a]≡σ′t[a]] = irrelevanceEqTerm″
                                       (PE.sym (singleSubstWkComp a σ t))
                                       (PE.sym (singleSubstWkComp a σ′ t))
                                       (PE.sym (singleSubstWkComp a σ G))
                                       G[a]′ G[a]
                                       (proj₂ ([t] ⊢Δ₁ ([ρσ] , [a]′))
                                              ([ρσ′] , [a]″)
                                              (consSubstSEq {t = a} {A = F}
                                                [Γ] ⊢Δ₁ [ρσ] [ρσ≡ρσ′] [F] [a]′))
                in  transEqTerm G[a] [σlamt∘a≡σt[a]]
                                (transEqTerm G[a] [σt[a]≡σ′t[a]]
                                             [σ′t[a]≡σ′lamt∘a])
         in  Πₜ₌ (lam (subst (liftSubst σ) t)) (lam (subst (liftSubst σ′) t))
                 (idRedTerm:*: (lamⱼ ⊢F ⊢t))
                 (idRedTerm:*: (conv (lamⱼ ⊢F′ ⊢t′)
                                     (sym (≅-eq (escapeEq (proj₁ ([ΠFG] ⊢Δ [σ]))
                                                              [σΠFG≡σ′ΠFG])))))
                 lamₙ lamₙ
                 (≅-η-eq ⊢F (lamⱼ ⊢F ⊢t)
                      (conv (lamⱼ ⊢F′ ⊢t′)
                            (sym (≅-eq (escapeEq (proj₁ ([ΠFG] ⊢Δ [σ]))
                                              [σΠFG≡σ′ΠFG]))))
                      lamₙ lamₙ
                      (escapeTermEq
                        (proj₁ ([G] (⊢Δ ∙ ⊢F) [liftσ]))
                        (irrelevanceEqTerm′
                          (idWkLiftSubstLemma σ G)
                          ([G]′ (step id) (⊢Δ ∙ ⊢F) neuVar)
                          (proj₁ ([G] (⊢Δ ∙ ⊢F) [liftσ]))
                          (σlamt∘a≡σ′lamt∘a (step id) (⊢Δ ∙ ⊢F) neuVar))))
                  (lamt ⊢Δ [σ])
                  (convTerm₂ (proj₁ ([ΠFG] ⊢Δ [σ]))
                             (proj₁ ([ΠFG] ⊢Δ [σ′]))
                             [σΠFG≡σ′ΠFG]
                             (lamt ⊢Δ [σ′]))
                  σlamt∘a≡σ′lamt∘a)

-- Reducibility of η-equality under a valid substitution.
η-eqEqTerm : ∀ {f g F G Γ Δ σ l}
             ([Γ] : ⊩ᵛ Γ)
             ([F] : Γ ⊩ᵛ⟨ l ⟩ F / [Γ])
             ([G] : Γ ∙ F ⊩ᵛ⟨ l ⟩ G / ([Γ] ∙″ [F]))
           → let [ΠFG] = Πᵛ {F} {G} [Γ] [F] [G] in
             Γ ∙ F ⊩ᵛ⟨ l ⟩ wk1 f ∘ var 0 ≡ wk1 g ∘ var 0 ∷ G
                          / [Γ] ∙″ [F] / [G]
           → (⊢Δ   : ⊢ Δ)
             ([σ]  : Δ ⊩ˢ σ ∷ Γ / [Γ] / ⊢Δ)
           → Δ ⊩⟨ l ⟩ subst σ f ∷ Π subst σ F ▹ subst (liftSubst σ) G
               / proj₁ ([ΠFG] ⊢Δ [σ])
           → Δ ⊩⟨ l ⟩ subst σ g ∷ Π subst σ F ▹ subst (liftSubst σ) G
               / proj₁ ([ΠFG] ⊢Δ [σ])
           → Δ ⊩⟨ l ⟩ subst σ f ≡ subst σ g ∷ Π subst σ F ▹ subst (liftSubst σ) G
               / proj₁ ([ΠFG] ⊢Δ [σ])
η-eqEqTerm {f} {g} {F} {G} {Γ} {Δ} {σ} [Γ] [F] [G] [f0≡g0] ⊢Δ [σ]
           (Πₜ f₁ [ ⊢t , ⊢u , d ] funcF f≡f [f] [f]₁)
           (Πₜ g₁ [ ⊢t₁ , ⊢u₁ , d₁ ] funcG g≡g [g] [g]₁) =
  let [d]  = [ ⊢t , ⊢u , d ]
      [d′] = [ ⊢t₁ , ⊢u₁ , d₁ ]
      [ΠFG] = Πᵛ {F} {G} [Γ] [F] [G]
      [σΠFG] = proj₁ ([ΠFG] ⊢Δ [σ])
      Πᵣ F′ G′ D′ ⊢F ⊢G A≡A [F]′ [G]′ G-ext = extractMaybeEmbΠ (Π-elim [σΠFG])
      [σF] = proj₁ ([F] ⊢Δ [σ])
      [wk1F] = wk (step id) (⊢Δ ∙ ⊢F) [σF]
      var0′ = var (⊢Δ ∙ ⊢F) here
      var0 = neuTerm [wk1F] (var 0) var0′ (~-var var0′)
      var0≡0 = neuEqTerm [wk1F] (var 0) (var 0) var0′ var0′ (~-var var0′)
      [σG]′ = [G]′ (step id) (⊢Δ ∙ ⊢F) var0
      [σG] = proj₁ ([G] (⊢Δ ∙ ⊢F) (liftSubstS {F = F} [Γ] ⊢Δ [F] [σ]))
      σf0≡σg0 = escapeTermEq [σG]
                                 ([f0≡g0] (⊢Δ ∙ ⊢F)
                                          (liftSubstS {F = F} [Γ] ⊢Δ [F] [σ]))
      σf0≡σg0′ =
        PE.subst₂
          (λ x y → Δ ∙ subst σ F ⊢ x ≅ y ∷ subst (liftSubst σ) G)
          (PE.cong₂ _∘_ (PE.trans (subst-wk f) (PE.sym (wk-subst f))) PE.refl)
          (PE.cong₂ _∘_ (PE.trans (subst-wk g) (PE.sym (wk-subst g))) PE.refl)
          σf0≡σg0
      ⊢ΠFG = escape [σΠFG]
      f≡f₁′ = proj₂ (redSubst*Term d [σΠFG] (Πₜ f₁ (idRedTerm:*: ⊢u) funcF f≡f [f] [f]₁))
      g≡g₁′ = proj₂ (redSubst*Term d₁ [σΠFG] (Πₜ g₁ (idRedTerm:*: ⊢u₁) funcG g≡g [g] [g]₁))
      eq′  = irrelevanceEqTerm′ (cons0wkLift1-id σ G) [σG]′ [σG]
                                (app-congTerm [wk1F] [σG]′ (wk (step id) (⊢Δ ∙ ⊢F) [σΠFG])
                                              (wkEqTerm (step id) (⊢Δ ∙ ⊢F) [σΠFG] f≡f₁′) var0 var0 var0≡0)
      eq₁′ = irrelevanceEqTerm′ (cons0wkLift1-id σ G) [σG]′ [σG]
                                (app-congTerm [wk1F] [σG]′ (wk (step id) (⊢Δ ∙ ⊢F) [σΠFG])
                                              (wkEqTerm (step id) (⊢Δ ∙ ⊢F) [σΠFG] g≡g₁′) var0 var0 var0≡0)
      eq   = escapeTermEq [σG] eq′
      eq₁  = escapeTermEq [σG] eq₁′
  in  Πₜ₌ f₁ g₁ [d] [d′] funcF funcG
          (≅-η-eq ⊢F ⊢u ⊢u₁ funcF funcG
                  (≅ₜ-trans (≅ₜ-sym eq) (≅ₜ-trans σf0≡σg0′ eq₁)))
          (Πₜ f₁ [d] funcF f≡f [f] [f]₁)
          (Πₜ g₁ [d′] funcG g≡g [g] [g]₁)
          (λ {ρ} {Δ₁} {a} [ρ] ⊢Δ₁ [a] →
             let [F]″ = proj₁ ([F] ⊢Δ₁ (wkSubstS [Γ] ⊢Δ ⊢Δ₁ [ρ] [σ]))
                 [a]′ = irrelevanceTerm′
                          (wk-subst F) ([F]′ [ρ] ⊢Δ₁)
                          [F]″ [a]
                 fEq = PE.cong₂ _∘_ (PE.trans (subst-wk f) (PE.sym (wk-subst f))) PE.refl
                 gEq = PE.cong₂ _∘_ (PE.trans (subst-wk g) (PE.sym (wk-subst g))) PE.refl
                 GEq = PE.sym (PE.trans (subst-wk (subst (liftSubst σ) G))
                                        (PE.trans (substCompEq G)
                                                  (cons-wk-subst ρ σ a G)))
                 f≡g = irrelevanceEqTerm″ fEq gEq GEq
                         (proj₁ ([G] ⊢Δ₁ (wkSubstS [Γ] ⊢Δ ⊢Δ₁ [ρ] [σ] , [a]′)))
                         ([G]′ [ρ] ⊢Δ₁ [a])
                         ([f0≡g0] ⊢Δ₁ (wkSubstS [Γ] ⊢Δ ⊢Δ₁ [ρ] [σ] , [a]′))
                 [ρσΠFG] = wk [ρ] ⊢Δ₁ [σΠFG]
                 [f]′ : Δ ⊩⟨ _ ⟩ f₁ ∷ Π F′ ▹ G′ / [σΠFG]
                 [f]′ = Πₜ f₁ (idRedTerm:*: ⊢u) funcF f≡f [f] [f]₁
                 [ρf]′ = wkTerm [ρ] ⊢Δ₁ [σΠFG] [f]′
                 [g]′ : Δ ⊩⟨ _ ⟩ g₁ ∷ Π F′ ▹ G′ / [σΠFG]
                 [g]′ = Πₜ g₁ (idRedTerm:*: ⊢u₁) funcG g≡g [g] [g]₁
                 [ρg]′ = wkTerm [ρ] ⊢Δ₁ [σΠFG] [g]′
                 [f∘u] = appTerm ([F]′ [ρ] ⊢Δ₁) ([G]′ [ρ] ⊢Δ₁ [a]) [ρσΠFG] [ρf]′ [a]
                 [g∘u] = appTerm ([F]′ [ρ] ⊢Δ₁) ([G]′ [ρ] ⊢Δ₁ [a]) [ρσΠFG] [ρg]′ [a]
                 [tu≡fu] = proj₂ (redSubst*Term (app-subst* (wkRed*Term [ρ] ⊢Δ₁ d)
                                                            (escapeTerm ([F]′ [ρ] ⊢Δ₁) [a]))
                                                ([G]′ [ρ] ⊢Δ₁ [a]) [f∘u])
                 [gu≡t′u] = proj₂ (redSubst*Term (app-subst* (wkRed*Term [ρ] ⊢Δ₁ d₁)
                                                             (escapeTerm ([F]′ [ρ] ⊢Δ₁) [a]))
                                                 ([G]′ [ρ] ⊢Δ₁ [a]) [g∘u])
             in  transEqTerm ([G]′ [ρ] ⊢Δ₁ [a]) (symEqTerm ([G]′ [ρ] ⊢Δ₁ [a]) [tu≡fu])
                             (transEqTerm ([G]′ [ρ] ⊢Δ₁ [a]) f≡g [gu≡t′u]))

-- Validity of η-equality.
η-eqᵛ : ∀ {f g F G Γ l}
        ([Γ] : ⊩ᵛ Γ)
        ([F] : Γ ⊩ᵛ⟨ l ⟩ F / [Γ])
        ([G] : Γ ∙ F ⊩ᵛ⟨ l ⟩ G / ([Γ] ∙″ [F]))
      → let [ΠFG] = Πᵛ {F} {G} [Γ] [F] [G] in
        Γ ⊩ᵛ⟨ l ⟩ f ∷ Π F ▹ G / [Γ] / [ΠFG]
      → Γ ⊩ᵛ⟨ l ⟩ g ∷ Π F ▹ G / [Γ] / [ΠFG]
      → Γ ∙ F ⊩ᵛ⟨ l ⟩ wk1 f ∘ var 0 ≡ wk1 g ∘ var 0 ∷ G
                     / [Γ] ∙″ [F] / [G]
      → Γ ⊩ᵛ⟨ l ⟩ f ≡ g ∷ Π F ▹ G / [Γ] / [ΠFG]
η-eqᵛ {f} {g} {F} {G} [Γ] [F] [G] [f] [g] [f0≡g0] {Δ} {σ} ⊢Δ [σ] =
  η-eqEqTerm {f} {g} {F} {G} [Γ] [F] [G] [f0≡g0] ⊢Δ [σ]
                (proj₁ ([f] ⊢Δ [σ])) (proj₁ ([g] ⊢Δ [σ]))


{-# OPTIONS --cubical --safe #-}
module Cubical.Data.Empty.Base where

open import Cubical.Core.Everything

data ⊥ : Type₀ where

⊥-elim : ∀ {ℓ} {A : Type ℓ} → ⊥ → A
⊥-elim ()


module RandomAccessList.Redundant.Core.Properties where

open import BuildingBlock
open import BuildingBlock.BinaryLeafTree using (BinaryLeafTree; Node; Leaf)
open import RandomAccessList.Redundant.Core

open import Data.Num.Redundant
open import Data.Num.Redundant.Properties

open import Data.Nat using (ℕ; zero; suc)
open import Data.Nat.Etc
open import Data.Nat.Properties.Simple
open import Relation.Binary.PropositionalEquality as PropEq
    using (_≡_; _≢_; refl; cong; cong₂; trans; sym; inspect)
open PropEq.≡-Reasoning

⟦[]⟧≈zero∷[] : ∀ {n A} → (xs : 0-2-RAL A n) → {xs≣[] : xs ≡ []} → ⟦ xs ⟧ ≈ zero ∷ []
⟦[]⟧≈zero∷[] {n} {A} [] = <<<-zero n [] {eq refl}
⟦[]⟧≈zero∷[] (      0∷ xs) {()}
⟦[]⟧≈zero∷[] (x     1∷ xs) {()}
⟦[]⟧≈zero∷[] (x , y 2∷ xs) {()}

--------------------------------------------------------------------------------
-- On ⟦_⟧ and ⟦_⟧ₙ

{-
⟦[]⟧ₙ≡0 : ∀ {A n} → (xs : 0-2-RAL A n) → {xs≣[] : xs ≡ []} → ⟦ xs ⟧ₙ ≡ zero ∷ []
⟦[]⟧ₙ≡0 []                 = refl
⟦[]⟧ₙ≡0 (      0∷ xs) {()}
⟦[]⟧ₙ≡0 (x     1∷ xs) {()}
⟦[]⟧ₙ≡0 (x , y 2∷ xs) {()}

⟦[]⟧≡0 : ∀ {n A} → (xs : 0-2-RAL A n) → {xs≣[] : xs ≡ []} → ⟦ xs ⟧ ≡ zero ∷ []
⟦[]⟧≡0 {n}    []     {p}  =
    begin
        n <<< (zero ∷ [])
    ≡⟨ {!   !} ⟩
        zero ∷ []
    ∎
⟦[]⟧≡0 (      0∷ xs) {()}
⟦[]⟧≡0 (x     1∷ xs) {()}
⟦[]⟧≡0 (x , y 2∷ xs) {()}
-- identity
⟦[]⟧ₙ≡0 : ∀ {A n} → (xs : 0-2-RAL A n) → xs ≡ [] → ⟦ xs ⟧ₙ ≡ zero
⟦[]⟧ₙ≡0 []            p  = refl
⟦[]⟧ₙ≡0 (      0∷ xs) ()
⟦[]⟧ₙ≡0 (x     1∷ xs) ()
⟦[]⟧ₙ≡0 (x , y 2∷ xs) ()

⟦[]⟧≡0 : ∀ {n A} → (xs : 0-2-RAL A n) → xs ≡ [] → ⟦ xs ⟧ ≡ 0
⟦[]⟧≡0 {zero }    [] p  = refl
⟦[]⟧≡0 {suc n}    [] p  = *-right-zero (2 * 2 ^ n)
⟦[]⟧≡0 (      0∷ xs) ()
⟦[]⟧≡0 (x     1∷ xs) ()
⟦[]⟧≡0 (x , y 2∷ xs) ()

⟦0∷xs⟧≡⟦xs⟧ : ∀ {n A} → (xs : 0-2-RAL A (suc n)) → ⟦ 0∷ xs ⟧ ≡ ⟦ xs ⟧
⟦0∷xs⟧≡⟦xs⟧ {zero} xs = +-right-identity (2 * ⟦ xs ⟧ₙ)
⟦0∷xs⟧≡⟦xs⟧ {suc n} xs =
    begin
        2 * 2 ^ n * (2 * ⟦ xs ⟧ₙ)
    ≡⟨ sym (*-assoc (2 * 2 ^ n) 2 ⟦ xs ⟧ₙ) ⟩
        ((2 * 2 ^ n) * 2) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ x → x * ⟦ xs ⟧ₙ) (*-comm (2 * 2 ^ n) 2) ⟩
        (2 * (2 * 2 ^ n)) * ⟦ xs ⟧ₙ
    ∎

⟦[]⟧≡⟦[]⟧ : ∀ {m n A} → ⟦ [] {A} {m} ⟧ ≡ ⟦ [] {A} {n} ⟧
⟦[]⟧≡⟦[]⟧ {m} {n} =
    begin
        2 ^ m * 0
    ≡⟨ *-right-zero (2 ^ m) ⟩
        0
    ≡⟨ sym (*-right-zero (2 ^ n)) ⟩
        2 ^ n * 0
    ∎

splitIndex : ∀ {n A} → (x : BinaryLeafTree A n) → (xs : 0-2-RAL A (suc n)) → ⟦ x 1∷ xs ⟧ ≡ 1 * (2 ^ n) + ⟦ xs ⟧
splitIndex {n} x xs =
    begin
        2 ^ n * suc (2 * ⟦ xs ⟧ₙ)
    ≡⟨ +-*-suc (2 ^ n) (2 * ⟦ xs ⟧ₙ) ⟩
        2 ^ n + 2 ^ n * (2 * ⟦ xs ⟧ₙ)
    ≡⟨ cong (λ w → 2 ^ n + w) (sym (*-assoc (2 ^ n) 2 ⟦ xs ⟧ₙ)) ⟩
        2 ^ n + (2 ^ n * 2) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ w → 2 ^ n + w * ⟦ xs ⟧ₙ) (*-comm (2 ^ n) 2) ⟩
        2 ^ n + (2 * 2 ^ n) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ w → w + (2 * 2 ^ n) * ⟦ xs ⟧ₙ) (sym (+-right-identity (2 ^ n))) ⟩
        2 ^ n + 0 + (2 * 2 ^ n) * ⟦ xs ⟧ₙ
    ∎

splitIndex2 : ∀ {n A} → (x : BinaryLeafTree A n) → (y : BinaryLeafTree A n) → (xs : 0-2-RAL A (suc n)) → ⟦ x , y 2∷ xs ⟧ ≡ 2 * (2 ^ n) + ⟦ xs ⟧
splitIndex2 {n} x y xs =
    begin
        2 ^ n * (1 + (1 + (2 * ⟦ xs ⟧ₙ)))
    ≡⟨ +-*-suc (2 ^ n) ((1 + (2 * ⟦ xs ⟧ₙ))) ⟩
        2 ^ n + 2 ^ n * (1 + (2 * ⟦ xs ⟧ₙ))
    ≡⟨ cong (λ w → 2 ^ n + w) (+-*-suc (2 ^ n) (2 * ⟦ xs ⟧ₙ)) ⟩
        2 ^ n + (2 ^ n + 2 ^ n * (2 * ⟦ xs ⟧ₙ))
    ≡⟨ sym (+-assoc (2 ^ n) (2 ^ n) (2 ^ n * (2 * ⟦ xs ⟧ₙ))) ⟩
        2 ^ n + 2 ^ n + 2 ^ n * (2 * ⟦ xs ⟧ₙ)
    ≡⟨ cong (λ w → 2 ^ n + 2 ^ n + w) (sym (*-assoc (2 ^ n) 2 ⟦ xs ⟧ₙ)) ⟩
        2 ^ n + 2 ^ n + (2 ^ n * 2) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ w → 2 ^ n + 2 ^ n + w * ⟦ xs ⟧ₙ) (*-comm (2 ^ n) 2) ⟩
        2 ^ n + 2 ^ n + (2 * 2 ^ n) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ w → 2 ^ n + w + (2 * 2 ^ n) * ⟦ xs ⟧ₙ) (sym (+-right-identity (2 ^ n))) ⟩
        2 ^ n + (2 ^ n + 0) + (2 * 2 ^ n) * ⟦ xs ⟧ₙ
    ≡⟨ refl ⟩
        2 * 2 ^ n + (2 * 2 ^ n) * ⟦ xs ⟧ₙ
    ∎

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

{-
⟦[]⟧≡⟦[]⟧ : ∀ {n A} → ⟦ [] {A} {suc n} ⟧ ≡ ⟦ [] {A} {n} ⟧
⟦[]⟧≡⟦[]⟧ {n} =
    begin
        (2 * 2 ^ n) * 0
    ≡⟨ *-right-zero (2 * 2 ^ n) ⟩
        0
    ≡⟨ sym (*-right-zero (2 ^ n)) ⟩
        2 ^ n * 0
    ∎

⟦0∷xs⟧≡⟦xs⟧ {zero} xs = +-right-identity (2 * ⟦ xs ⟧ₙ)
⟦0∷xs⟧≡⟦xs⟧ {suc n} xs =
    begin
        2 * 2 ^ n * (2 * ⟦ xs ⟧ₙ)
    ≡⟨ sym (*-assoc (2 * 2 ^ n) 2 ⟦ xs ⟧ₙ) ⟩
        2 * 2 ^ n * 2 * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ x → x * ⟦ xs ⟧ₙ) (*-assoc 2 (2 ^ n) 2) ⟩
        2 * (2 ^ n * 2) * ⟦ xs ⟧ₙ
    ≡⟨ cong (λ x → 2 * x * ⟦ xs ⟧ₙ) (*-comm (2 ^ n) 2) ⟩
        (2 * (2 * 2 ^ n)) * ⟦ xs ⟧ₙ
    ∎

⟦0∷xs⟧≢0⇒⟦xs⟧≢0  : ∀ {n A}
                → (xs : 0-1-RAL A (suc n))
                → ⟦ 0∷ xs ⟧ ≢ 0
                → ⟦ xs ⟧ ≢ 0
⟦0∷xs⟧≢0⇒⟦xs⟧≢0 xs p = contraposition (trans (⟦0∷xs⟧≡⟦xs⟧ xs)) p
-}


------------------------------------------------------------------------
-- Recognisers form a Kleene algebra
------------------------------------------------------------------------

module TotalRecognisers.LeftRecursion.KleeneAlgebra (Tok : Set) where

open import Algebra
import Algebra.Properties.BooleanAlgebra
open import Codata.Musical.Notation
open import Data.Bool hiding (_∧_; _≤_)
import Data.Bool.Properties as Bool
private
  module BoolCS = CommutativeSemiring Bool.∨-∧-commutativeSemiring
  module BoolBA = Algebra.Properties.BooleanAlgebra
                    Bool.∨-∧-booleanAlgebra
open import Function.Base
open import Function.Equality using (_⟨$⟩_)
open import Function.Equivalence as Eq using (_⇔_; equivalence)
open import Data.List
open import Data.List.Properties
private
  module ListMonoid {A : Set} = Monoid (++-monoid A)
open import Data.Nat using (ℕ; zero; suc)
open import Data.Product as Prod
open import Relation.Binary.HeterogeneousEquality using (_≅_; refl)
open import Relation.Binary.PropositionalEquality as P
  using (_≡_; refl; _≗_)
open import Relation.Nullary

import TotalRecognisers.LeftRecursion
open TotalRecognisers.LeftRecursion Tok hiding (left-zero)
import TotalRecognisers.LeftRecursion.Lib
open TotalRecognisers.LeftRecursion.Lib Tok
open ⊙ using (_⊙′_)
open KleeneStar₂

------------------------------------------------------------------------
-- The relation _≤_ is a partial order with respect to _≈_

module PartialOrder where

  reflexive : ∀ {n} {p : P n} → p ≤ p
  reflexive = id

  trans : ∀ {n₁ n₂ n₃} {p₁ : P n₁} {p₂ : P n₂} {p₃ : P n₃} →
          p₁ ≤ p₂ → p₂ ≤ p₃ → p₁ ≤ p₃
  trans p₁≤p₂ p₂≤p₃ = p₂≤p₃ ∘ p₁≤p₂

  antisym : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} →
            p₁ ≤ p₂ → p₂ ≤ p₁ → p₁ ≈ p₂
  antisym p₁≤p₂ p₂≤p₁ = equivalence p₁≤p₂ p₂≤p₁

------------------------------------------------------------------------
-- The relation _≈_ is an equality, i.e. a congruential equivalence
-- relation

module Equivalence where

  reflexive : ∀ {n} {p : P n} → p ≈ p
  reflexive = Eq.id

  sym : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} → p₁ ≈ p₂ → p₂ ≈ p₁
  sym p₁≈p₂ = Eq.sym p₁≈p₂

  trans : ∀ {n₁ n₂ n₃} {p₁ : P n₁} {p₂ : P n₂} {p₃ : P n₃} →
          p₁ ≈ p₂ → p₂ ≈ p₃ → p₁ ≈ p₃
  trans p₁≈p₂ p₂≈p₃ = Eq._∘_ p₂≈p₃ p₁≈p₂

♭♯-cong : ∀ {n₁ n₂} b₁ b₂ {p₁ : P n₁} {p₂ : P n₂} →
          p₁ ≈ p₂ → ♭? (♯? {b₁} p₁) ≈ ♭? (♯? {b₂} p₂)
♭♯-cong b₁ b₂ {p₁} {p₂} rewrite ♭?♯? b₁ {p = p₁} | ♭?♯? b₂ {p = p₂} = id

fail-cong : fail ≈ fail
fail-cong = Equivalence.reflexive

empty-cong : empty ≈ empty
empty-cong = Equivalence.reflexive

sat-cong : {f₁ f₂ : Tok → Bool} → f₁ ≗ f₂ → sat f₁ ≈ sat f₂
sat-cong f₁≗f₂ = equivalence (helper f₁≗f₂) (helper (P.sym ∘ f₁≗f₂))
  where
  helper : {f₁ f₂ : Tok → Bool} → f₁ ≗ f₂ → sat f₁ ≤ sat f₂
  helper f₁≗f₂ (sat ok) = sat (P.subst T (f₁≗f₂ _) ok)

∣-cong : ∀ {n₁ n₂ n₃ n₄}
           {p₁ : P n₁} {p₂ : P n₂} {p₃ : P n₃} {p₄ : P n₄} →
         p₁ ≈′ p₃ → p₂ ≈′ p₄ → p₁ ∣ p₂ ≈′ p₃ ∣ p₄
∣-cong (refl ∷ rest₁) (refl ∷ rest₂) =
  refl ∷ λ t → ♯ ∣-cong (♭ (rest₁ t)) (♭ (rest₂ t))

·-cong : ∀ {n₁ n₂ n₃ n₄}
           {p₁ : ∞⟨ n₂ ⟩P n₁} {p₂ : ∞⟨ n₁ ⟩P n₂}
           {p₃ : ∞⟨ n₄ ⟩P n₃} {p₄ : ∞⟨ n₃ ⟩P n₄} →
         ♭? p₁ ≈ ♭? p₃ → ♭? p₂ ≈ ♭? p₄ → p₁ · p₂ ≈ p₃ · p₄
·-cong p₁≈p₃ p₂≈p₄ =
  Eq.Equivalence.from ≈⇔≤≥ ⟨$⟩
    Prod.zip helper helper
             (Eq.Equivalence.to ≈⇔≤≥ ⟨$⟩ p₁≈p₃)
             (Eq.Equivalence.to ≈⇔≤≥ ⟨$⟩ p₂≈p₄)
  where
  helper : ∀ {n₁ n₂ n₃ n₄}
             {p₁ : ∞⟨ n₂ ⟩P n₁} {p₂ : ∞⟨ n₁ ⟩P n₂}
             {p₃ : ∞⟨ n₄ ⟩P n₃} {p₄ : ∞⟨ n₃ ⟩P n₄} →
           ♭? p₁ ≤ ♭? p₃ → ♭? p₂ ≤ ♭? p₄ → p₁ · p₂ ≤ p₃ · p₄
  helper ₁≤₃ ₂≤₄ (∈p₁ · ∈p₂) = ₁≤₃ ∈p₁ · ₂≤₄ ∈p₂

⊙-cong : ∀ {n₁ n₂ n₃ n₄}
           {p₁ : P n₁} {p₂ : P n₂} {p₃ : P n₃} {p₄ : P n₄} →
         p₁ ≈ p₃ → p₂ ≈ p₄ → p₁ ⊙ p₂ ≈ p₃ ⊙ p₄
⊙-cong {n₁} {n₂} {n₃} {n₄} ₁≈₃ ₂≈₄ =
  ·-cong (♭♯-cong n₂ n₄ ₁≈₃) (♭♯-cong n₁ n₃ ₂≈₄)

nonempty-cong : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} →
                p₁ ≈′ p₂ → nonempty p₁ ≈′ nonempty p₂
nonempty-cong (_ ∷ rest) = refl ∷ rest

cast-cong : ∀ {n₁ n₂ n₁′ n₂′}
              {p₁ : P n₁} {p₂ : P n₂}
              {eq₁ : n₁ ≡ n₁′} {eq₂ : n₂ ≡ n₂′} →
            p₁ ≈′ p₂ → cast eq₁ p₁ ≈′ cast eq₂ p₂
cast-cong {eq₁ = refl} {refl} (init ∷ rest) = init ∷ rest

⋆-cong : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} →
         p₁ ≈ p₂ → p₁ ⋆ ≈ p₂ ⋆
⋆-cong p₁≈p₂ = Eq.Equivalence.from ≈⇔≤≥ ⟨$⟩
                 Prod.map helper helper
                   (Eq.Equivalence.to ≈⇔≤≥ ⟨$⟩ p₁≈p₂)
  where
  helper : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} →
           p₁ ≤ p₂ → p₁ ⋆ ≤ p₂ ⋆
  helper p₁≤p₂ (∣-left  empty)                 = ∣-left empty
  helper p₁≤p₂ (∣-right (nonempty ∈p₁ · ∈p₁⋆)) =
    ∣-right {p₁ = empty} (nonempty (p₁≤p₂ ∈p₁) · helper p₁≤p₂ ∈p₁⋆)

^-cong : ∀ {n₁ n₂ i₁ i₂} {p₁ : P n₁} {p₂ : P n₂} →
         p₁ ≈ p₂ → i₁ ≡ i₂ → p₁ ^ i₁ ≈ p₂ ^ i₂
^-cong {i₁ = i} p₁≈p₂ refl =
  Eq.Equivalence.from ≈⇔≤≥ ⟨$⟩
    Prod.map (helper i) (helper i)
             (Eq.Equivalence.to ≈⇔≤≥ ⟨$⟩ p₁≈p₂)
  where
  helper : ∀ {n₁ n₂} {p₁ : P n₁} {p₂ : P n₂} i →
           p₁ ≤ p₂ → p₁ ^ i ≤ p₂ ^ i
  helper           zero    p₁≤p₂ empty   = empty
  helper {n₁} {n₂} (suc i) p₁≤p₂ ∈p₁⊙p₁ⁱ
    with ⊙.sound ⟨ n₁ ^ i ⟩-nullable ∈p₁⊙p₁ⁱ
  ... | ∈p₁ ⊙′ ∈p₁ⁱ = ⊙.complete (p₁≤p₂ ∈p₁) (helper i p₁≤p₂ ∈p₁ⁱ)

------------------------------------------------------------------------
-- A variant of _≤_

infix 4 _≲_

-- If _∣_ is viewed as the join operation of a join-semilattice, then
-- the following definition of order is natural.

_≲_ : ∀ {n₁ n₂} → P n₁ → P n₂ → Set
p₁ ≲ p₂ = p₁ ∣ p₂ ≈ p₂

-- The order above coincides with _≤_.

≤⇔≲ : ∀ {n₁ n₂} (p₁ : P n₁) (p₂ : P n₂) → p₁ ≤ p₂ ⇔ p₁ ≲ p₂
≤⇔≲ {n₁} p₁ p₂ =
  equivalence
    (λ (p₁≤p₂ : p₁ ≤ p₂) {_} →
       equivalence (helper p₁≤p₂) (∣-right {n₁ = n₁}))
    (λ (p₁≲p₂ : p₁ ≲ p₂) s∈p₁ → Eq.Equivalence.to p₁≲p₂ ⟨$⟩ ∣-left s∈p₁)
  where
  helper : p₁ ≤ p₂ → p₁ ∣ p₂ ≤ p₂
  helper p₁≤p₂ (∣-left  s∈p₁) = p₁≤p₂ s∈p₁
  helper p₁≤p₂ (∣-right s∈p₂) = s∈p₂

------------------------------------------------------------------------
-- Recognisers form a *-continuous Kleene algebra

-- The definition of *-continuous Kleene algebras used here is the one
-- given by Kozen in "On Kleene Algebras and Closed Semirings", except
-- for the presence of the recogniser indices. Kozen used the order
-- _≲_ in his definition, but as shown above this order is equivalent
-- to _≤_.

-- Additive idempotent commutative monoid. (One of the identity lemmas
-- could be omitted.)

∣-commutative : ∀ {n₁ n₂} (p₁ : P n₁) (p₂ : P n₂) → p₁ ∣ p₂ ≈′ p₂ ∣ p₁
∣-commutative {n₁} {n₂} p₁ p₂ =
  BoolCS.+-comm n₁ n₂ ∷ λ t → ♯ ∣-commutative (D t p₁) (D t p₂)

fail-left-identity : ∀ {n} (p : P n) → fail ∣ p ≈′ p
fail-left-identity p = refl ∷ λ t → ♯ fail-left-identity (D t p)

fail-right-identity : ∀ {n} (p : P n) → p ∣ fail ≈′ p
fail-right-identity {n} p =
  proj₂ BoolCS.+-identity n ∷ λ t → ♯ fail-right-identity (D t p)

∣-associative : ∀ {n₁ n₂ n₃} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) →
                p₁ ∣ (p₂ ∣ p₃) ≈′ (p₁ ∣ p₂) ∣ p₃
∣-associative {n₁} {n₂} {n₃} p₁ p₂ p₃ =
  P.sym (BoolCS.+-assoc n₁ n₂ n₃) ∷ λ t →
    ♯ ∣-associative (D t p₁) (D t p₂) (D t p₃)

∣-idempotent : ∀ {n} (p : P n) → p ∣ p ≈′ p
∣-idempotent {n} p =
  BoolBA.∨-idempotent n ∷ λ t → ♯ ∣-idempotent (D t p)

-- Multiplicative monoid.

empty-left-identity : ∀ {n} (p : P n) → empty ⊙ p ≈ p
empty-left-identity {n} p =
  equivalence helper (λ s∈p → ⊙.complete empty s∈p)
  where
  helper : empty ⊙ p ≤ p
  helper ∈empty⊙p with ⊙.sound n ∈empty⊙p
  ... | empty ⊙′ s∈p = s∈p

empty-right-identity : ∀ {n} (p : P n) → p ⊙ empty ≈ p
empty-right-identity {n} p =
  equivalence
    helper
    (λ s∈p → cast∈ (proj₂ ListMonoid.identity _) refl
                   (⊙.complete s∈p empty))
  where
  helper : ∀ {s} → s ∈ p ⊙ empty → s ∈ p
  helper ∈p⊙empty with ⊙.sound true ∈p⊙empty
  helper ∈p⊙empty | ∈p ⊙′ empty =
    cast∈ (P.sym (proj₂ ListMonoid.identity _)) refl ∈p

·-associative : ∀ {n₁ n₂ n₃} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) →
                p₁ ⊙ (p₂ ⊙ p₃) ≈ (p₁ ⊙ p₂) ⊙ p₃
·-associative {n₁} {n₂} {n₃} p₁ p₂ p₃ = equivalence helper₁ helper₂
  where
  helper₁ : p₁ ⊙ (p₂ ⊙ p₃) ≤ (p₁ ⊙ p₂) ⊙ p₃
  helper₁ ∈⊙⊙ with ⊙.sound (n₂ ∧ n₃) ∈⊙⊙
  ... | _⊙′_ {s₁ = s₁} ∈p₁ ∈⊙ with ⊙.sound n₃ ∈⊙
  ...   | ∈p₂ ⊙′ ∈p₃ =
    cast∈ (ListMonoid.assoc s₁ _ _) refl $
      ⊙.complete (⊙.complete ∈p₁ ∈p₂) ∈p₃

  helper₂ : (p₁ ⊙ p₂) ⊙ p₃ ≤ p₁ ⊙ (p₂ ⊙ p₃)
  helper₂ ∈⊙⊙ with ⊙.sound n₃ ∈⊙⊙
  ... | ∈⊙ ⊙′ ∈p₃ with ⊙.sound n₂ ∈⊙
  ...   | _⊙′_ {s₁ = s₁} ∈p₁ ∈p₂ =
    cast∈ (P.sym $ ListMonoid.assoc s₁ _ _) refl $
      ⊙.complete ∈p₁ (⊙.complete ∈p₂ ∈p₃)

-- Distributivity.

left-distributive :
  ∀ {n₁ n₂ n₃} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) →
  p₁ ⊙ (p₂ ∣ p₃) ≈ p₁ ⊙ p₂ ∣ p₁ ⊙ p₃
left-distributive {n₁} {n₂} {n₃} p₁ p₂ p₃ = equivalence helper₁ helper₂
  where
  helper₁ : p₁ ⊙ (p₂ ∣ p₃) ≤ p₁ ⊙ p₂ ∣ p₁ ⊙ p₃
  helper₁ ∈⊙∣ with ⊙.sound (n₂ ∨ n₃) ∈⊙∣
  ... | ∈p₁ ⊙′ ∣-left  ∈p₂ = ∣-left $ ⊙.complete ∈p₁ ∈p₂
  ... | ∈p₁ ⊙′ ∣-right ∈p₃ = ∣-right {n₁ = n₁ ∧ n₂} $ ⊙.complete ∈p₁ ∈p₃

  helper₂ : p₁ ⊙ p₂ ∣ p₁ ⊙ p₃ ≤ p₁ ⊙ (p₂ ∣ p₃)
  helper₂ (∣-left ∈⊙) with ⊙.sound n₂ ∈⊙
  ... | ∈p₁ ⊙′ ∈p₂ = ⊙.complete ∈p₁ (∣-left ∈p₂)
  helper₂ (∣-right ∈⊙) with ⊙.sound n₃ ∈⊙
  ... | ∈p₁ ⊙′ ∈p₃ = ⊙.complete ∈p₁ (∣-right {n₁ = n₂} ∈p₃)

right-distributive :
  ∀ {n₁ n₂ n₃} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) →
  (p₁ ∣ p₂) ⊙ p₃ ≈ p₁ ⊙ p₃ ∣ p₂ ⊙ p₃
right-distributive {n₁} {n₂} {n₃} p₁ p₂ p₃ = equivalence helper₁ helper₂
  where
  helper₁ : (p₁ ∣ p₂) ⊙ p₃ ≤ p₁ ⊙ p₃ ∣ p₂ ⊙ p₃
  helper₁ ∈∣⊙ with ⊙.sound n₃ ∈∣⊙
  ... | ∣-left  ∈p₁ ⊙′ ∈p₃ = ∣-left $ ⊙.complete ∈p₁ ∈p₃
  ... | ∣-right ∈p₂ ⊙′ ∈p₃ = ∣-right {n₁ = n₁ ∧ n₃} $ ⊙.complete ∈p₂ ∈p₃

  helper₂ : p₁ ⊙ p₃ ∣ p₂ ⊙ p₃ ≤ (p₁ ∣ p₂) ⊙ p₃
  helper₂ (∣-left ∈⊙) with ⊙.sound n₃ ∈⊙
  ... | ∈p₁ ⊙′ ∈p₃ = ⊙.complete (∣-left ∈p₁) ∈p₃
  helper₂ (∣-right ∈⊙) with ⊙.sound n₃ ∈⊙
  ... | ∈p₂ ⊙′ ∈p₃ = ⊙.complete (∣-right {n₁ = n₁} ∈p₂) ∈p₃

-- Zero.

left-zero : ∀ {n} (p : P n) → fail ⊙ p ≈ fail
left-zero {n} p = equivalence helper (λ ())
  where
  helper : fail ⊙ p ≤ fail
  helper ∈fail⊙ with ⊙.sound n ∈fail⊙
  ... | () ⊙′ _

right-zero : ∀ {n} (p : P n) → p ⊙ fail ≈ fail
right-zero {n} p = equivalence helper (λ ())
  where
  helper : p ⊙ fail ≤ fail
  helper ∈⊙fail with ⊙.sound false ∈⊙fail
  ... | _ ⊙′ ()

-- *-continuity.

*-continuity-upper-bound :
  ∀ {n₁ n₂ n₃} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) →
  ∀ i → p₁ ⊙ p₂ ^ i ⊙ p₃ ≤ p₁ ⊙ p₂ ⋆ ⊙ p₃
*-continuity-upper-bound {n₁} {n₂} {n₃} _ _ _ i ∈⊙ⁱ⊙
  with ⊙.sound n₃ ∈⊙ⁱ⊙
... | ∈⊙ⁱ ⊙′ ∈p₃ with ⊙.sound ⟨ n₂ ^ i ⟩-nullable ∈⊙ⁱ
...   | ∈p₁ ⊙′ ∈p₂ⁱ = ⊙.complete (⊙.complete ∈p₁ (^≤⋆ i ∈p₂ⁱ)) ∈p₃

*-continuity-least-upper-bound :
  ∀ {n₁ n₂ n₃ n} (p₁ : P n₁) (p₂ : P n₂) (p₃ : P n₃) (p : P n) →
  (∀ i → p₁ ⊙ p₂ ^ i ⊙ p₃ ≤ p) → p₁ ⊙ p₂ ⋆ ⊙ p₃ ≤ p
*-continuity-least-upper-bound {n₁} {n₂} {n₃} {n} p₁ p₂ p₃ p ub =
  helper ∘ _⟨$⟩_ (Eq.Equivalence.from $ ·-associative p₁ (p₂ ⋆) p₃)
  where
  helper : p₁ ⊙ (p₂ ⋆ ⊙ p₃) ≤ p
  helper ∈⊙⋆⊙ with ⊙.sound (true ∧ n₃) ∈⊙⋆⊙
  ... | _⊙′_ {s₁ = s₁} ∈p₁ ∈⋆⊙ with ⊙.sound n₃ ∈⋆⊙
  ...   | ∈p₂⋆ ⊙′ ∈p₃ with ⋆≤^ ∈p₂⋆
  ...     | (i , ∈p₂ⁱ) =
    cast∈ (ListMonoid.assoc s₁ _ _) refl $
      ub i $ ⊙.complete (⊙.complete ∈p₁ ∈p₂ⁱ) ∈p₃


module Effect where

open import Data.List
open import Data.List.All
open import Data.List.Any
open import Level
open import Relation.Binary.PropositionalEquality hiding ([_])
open import Function
open import Category.Monad
open import Data.Product
open import EffectUtil
open import Membership-equality hiding (_⊆_; set)

Effect : ∀ f → Set (suc f)
Effect f = (result : Set f) (i : Set f) (o : result → Set f) → Set f

record EFFECT (f : Level) : Set (suc f) where
  constructor mkEff
  field S : Set f
        E : Effect f

updateResTy : ∀ {f A E i o} → (val : A) →
              (es : List (EFFECT f)) →
              (prf : (mkEff i E) ∈ es) →
              (eff : E A i o) →
              List (EFFECT f)
updateResTy {o = o} val (mkEff i e ∷ es) (here px) eff = mkEff (o val) e ∷ es
updateResTy val (e ∷ es) (there prf) eff = e ∷ updateResTy val es prf eff

updateWith : ∀ {f ys} (ys' : List (EFFECT f)) → (xs : List (EFFECT f)) →
             (ys ⊆ xs) → List (EFFECT f)
updateWith [] xs inc = xs
updateWith (y' ∷ ys') xs [] = xs
updateWith (y' ∷ ys') (_ ∷ xs) (keep inc) = y' ∷ updateWith ys' xs inc
updateWith (y' ∷ ys') (x ∷ xs) (skip inc) = x ∷ updateWith (y' ∷ ys') xs inc

Handler : ∀ {f} → (Set f → Set f) → Effect f → Set (suc f)
Handler M e = ∀ {A a o res} → (r : res) → (eff : e A res o) →
              (k : ((x : A) → o x → M a)) → M a

-- @ A  The return type of the result
-- @ es The list of allowed side-effects
-- @ ce The function to compute a new list of allowed side-effects
data EffM {f : Level} (m : Set f → Set f) : (A : Set f) →
             (es : List (EFFECT f)) →
             (ce : A → List (EFFECT f)) → Set (suc f) where
  return : ∀ {A ce} (val : A) → EffM m A (ce val) ce
  _>>=_  : ∀ {A B es ce₁ ce₂} → EffM m A es ce₁ → (∀ x → EffM m B (ce₁ x) ce₂) → EffM m B es ce₂
  effect : ∀ {A E i es o} (prf : (mkEff i E) ∈ es) →
             (eff : E A i o) →
             EffM m A es (λ v → updateResTy v es prf eff)
  -- use to invoke sub-programs that use some or all of the available effects
  lift   : ∀ {A ys ys' xs} → (inc : ys ⊆ xs) →
           EffM m A ys ys' →
           EffM m A xs (λ v → updateWith (ys' v) xs inc)
  new : ∀ {A es} →
    (e : EFFECT f) →
    (val : EFFECT.S e) →
    Handler m (EFFECT.E e) →
    EffM {f} m A (e ∷ es) (λ v → e ∷ es) →
    EffM m A es (λ v → es)

pure : ∀ {l m A es} → A → EffM {l} m A es (λ v → es)
pure = return

_<*>_ : ∀ {l m A B es} → EffM {l} m (A → B) es (λ v → es) →
                    EffM {l} m A es (λ v → es) →
                    EffM {l} m B es (λ v → es)
_<*>_ prog v = prog >>= λ fn →
               v >>= λ arg →
               pure (fn arg)

_<$>_ : ∀ {l m A B es} → (f : A → B) → EffM {l} m A es (λ v → es) → EffM {l} m B es (λ v → es)
_<$>_ f m = pure f <*> m

data Env {f : Level} : (m : Set f → Set f) → List (EFFECT f) → Set (suc f) where
  [] : ∀ {m} → Env m []
  _∷_ : ∀ {m eff es a} → (el : Handler m eff × a) → Env m es → Env m (mkEff a eff ∷ es)

execEff : ∀ {f m A es E i} {o : A → Set f} {B} →
  Env {f} m es →
  (prf : (mkEff i E) ∈ es) →
  (eff : E A i o) →
  ((v : A) → Env {f} m (updateResTy v es prf eff) → m B) → m B
execEff {m = m} {A = A} {o = o} {B = B} ((handle , val) ∷ env) (here refl) eff k = handle val eff cont
  where
    cont : (v : A) → o v → m B
    cont v res = k v ((handle , res) ∷ env)
execEff (e ∷ env) (there prf) eff k = execEff env prf eff λ v → λ env' → k v (e ∷ env')

dropEnv : ∀ {f m ys xs} → Env {f} m ys → xs ⊆ ys → Env m xs
dropEnv [] [] = []
dropEnv (el ∷ env) [] = []
dropEnv (el ∷ env) (keep sub) = el ∷ (dropEnv env sub)
dropEnv (el ∷ env) (skip sub) = dropEnv env sub

rebuildEnv : ∀ {f m ys' ys xs} → Env {f} m ys' → (prf : ys ⊆ xs) →
             Env m xs → Env m (updateWith ys' xs prf)
rebuildEnv [] [] env = env
rebuildEnv (_ ∷ _) [] env = env
rebuildEnv [] (keep prf) env = env
rebuildEnv (el ∷ els) (keep prf) (_ ∷ env) = el ∷ rebuildEnv els prf env
rebuildEnv [] (skip prf) env = env
rebuildEnv (el ∷ els) (skip prf) (en ∷ env) = en ∷ rebuildEnv (el ∷ els) prf env

eff : ∀ {f m es A B ce} → Env {f} m es → EffM m A es ce → ((v : A) → Env m (ce v) → m B) → m B
eff env (return val) k = k val env
eff env (effM >>= c) k = eff env effM λ p' → λ env' → eff env' ((c p')) k
eff env (effect prf effP) k = execEff env prf effP k
eff env (lift inc effM) k = eff env' effM λ p' → λ envk → k p' (rebuildEnv envk inc env)
  where
    env' = dropEnv env inc
eff {f} {m} {es} {A} {B} env (new e val handler effM) k = eff ((handler , val) ∷ env) effM cont
  where
    cont : (v : A) → Env {f} m (e ∷ es) → m B
    cont v (val ∷ envk) = k v envk

run : ∀ {f a xs xs'} → ∀ m → (mon : RawMonad m) → (prog : EffM {f} m a xs xs') → Env m xs → m a
run m mon prog env = eff env prog λ r → λ env' → RawMonad.return mon r


{-# OPTIONS --allow-unsolved-metas #-}

module TemporalOps.Linear where

open import CategoryTheory.Instances.Reactive
open import CategoryTheory.Functor
open import CategoryTheory.CartesianStrength
open import CategoryTheory.NatTrans
open import CategoryTheory.Monad
open import CategoryTheory.Linear

open import TemporalOps.Next
open import TemporalOps.Delay
open import TemporalOps.Diamond
open import TemporalOps.OtherOps
open import TemporalOps.Common.Other
open import TemporalOps.Common.Compare
open import TemporalOps.Common.Rewriting

open import Data.Product
open import Data.Product.Properties
open import Data.Sum
open import Data.Nat hiding (_*_)
open import Data.Nat.Properties
            using (+-identityʳ ; +-comm ; +-suc ; +-assoc)

open import Relation.Binary.PropositionalEquality hiding (inspect)

open import Holes.Term using (⌞_⌟)
open import Holes.Cong.Propositional

open ≡-Reasoning

open L ℝeactive-BCCC M-◇ public

ℝeactive-linear : Linear ℝeactive-BCCC M-◇
ℝeactive-linear = record
    { linprod = λ A B →
    record { ⟪_,_⟫ = prod-◇
           ; *π₁-comm = λ {C} {l₁} {l₂} {n} {a} →
                        *π₁-comm-◇ {A}{B}{C} {l₁} {l₂} {n} {a}
           ; *π₂-comm = {!   !}
           ; ⊛-unique = {!   !}
           }
    }
    where
    open Functor F-▹
    open Functor F-◇ renaming (fmap to ◇-f)
    private module ▹ᵏ-F k = Functor (F-delay k)
    private module ▹ᵏ-◇ k = _⟹_ (▹ᵏ-to-◇ k)
    private module ▹ᵏ-C k = CartesianFunctor (F-cart-delay k)
    open Monad M-◇
    open CartesianFunctor F-cart-▹ renaming (m to m-▹)

    pair-delay-◇₁ : ∀{A B : τ} -> (k l : ℕ) -> (delay A by suc (k + l) ⊗ delay B by k)
                                   ⇴ delay (◇ A ⊗ B) by k
    pair-delay-◇₁ zero l n (dA , dB) = (suc l , dA) , dB
    pair-delay-◇₁ {A}{B} (suc k) l n p = fmap (pair-delay-◇₁ k l) n
        (m-▹ (delay A by suc (k + l)) (delay B by k) n p)

    pair-delay-◇₂ : ∀{A B : τ} -> (k l : ℕ) -> (delay A by k ⊗ delay B by suc (k + l))
                                   ⇴ delay (A ⊗ ◇ B) by k
    pair-delay-◇₂ zero l n (dA , dB) = dA , (suc l , dB)
    pair-delay-◇₂ {A}{B} (suc k) l n p = fmap (pair-delay-◇₂ k l) n
        (m-▹ (delay A by k) (delay B by suc (k + l)) n p)

    prod-◇-compare : ∀{A B : τ} -> (k₁ k₂ n : ℕ)
                  -> (a₁ : delay A by k₁ at n)(a₂ : delay B by k₂ at n)
                  -> Ordering k₁ k₂ -> ◇ (A ⊛ B) at n
    prod-◇-compare {A} {B} k₁ .(suc (k₁ + l)) n a₁ a₂ (less .k₁ l) =
        k₁ , ▹ᵏ-F.fmap k₁ ι₁ n (▹ᵏ-F.fmap k₁ ι₁ n (pair-delay-◇₂ k₁ l n (a₁ , a₂)))
    prod-◇-compare {A} {B} .(suc (k₂ + l)) k₂ n a₁ a₂ (greater .k₂ l) =
        k₂ , ▹ᵏ-F.fmap k₂ ι₁ n (▹ᵏ-F.fmap k₂ ι₂ n (pair-delay-◇₁ k₂ l n (a₁ , a₂)))
    prod-◇-compare {A} {B} k₁ .k₁ n a₁ a₂ (equal .k₁) =
        k₁ , ▹ᵏ-F.fmap k₁ ι₂ n (▹ᵏ-C.m k₁ A B n (a₁ , a₂))

    ◇-select : ∀{A B : τ} -> (◇ A ⊗ ◇ B) ⇴ ◇ (A ⊛ B)
    ◇-select {A}{B} n ((k₁ , a₁) , (k₂ , a₂)) =
        prod-◇-compare {A}{B} k₁ k₂ n a₁ a₂ (compare k₁ k₂)

    prod-◇ : ∀{A B L : τ} -> (L ⇴ ◇ A) -> (L ⇴ ◇ B) -> (L ⇴ ◇ (A ⊛ B))
    prod-◇ fa fb n lp = ◇-select n (fa n lp , fb n lp)

    *π₁-comm-◇ : ∀{A B L} -> {l₁ : L ⇴ ◇ A} {l₂ : L ⇴ ◇ B}
           -> (μ.at A ∘ ◇-f *π₁) ∘ (prod-◇ l₁ l₂) ≈ l₁
    *π₁-comm-◇ {A}{B}{L}{l₁}{l₂} {n} {a} with inspect (l₁ n a , l₂ n a)
    *π₁-comm-◇ | ((k₁ , a₁) , (k₂ , a₂)) with≡ pf with inspect (compare k₁ k₂)
    *π₁-comm-◇ {A}{B}{L}{l₁}{l₂}{n}{a} | ((k₁ , a₁) , .(suc (k₁ + l)) , a₂) with≡ pf
                                           | less .k₁ l with≡ cpf =
        begin
            (μ.at A n (◇-f *π₁ n (◇-select n ⌞ (l₁ n a , l₂ n a) ⌟)))
        ≡⟨ cong! pf ⟩
            μ.at A n (◇-f *π₁ n
                (prod-◇-compare k₁ (suc (k₁ + l)) n a₁ a₂ (⌞ compare k₁ (suc (k₁ + l)) ⌟)))
        ≡⟨ cong! cpf ⟩
            μ.at A n (◇-f *π₁ n
                (prod-◇-compare k₁ (suc (k₁ + l)) n a₁ a₂ (less k₁ l)))
        ≡⟨⟩
            μ.at A n (k₁ ,
                ▹ᵏ-F.fmap k₁ *π₁ n ⌞ (▹ᵏ-F.fmap k₁ ι₁ n (▹ᵏ-F.fmap k₁ ι₁ n
                    (pair-delay-◇₂ k₁ l n (a₁ , a₂)))) ⌟)
        ≡⟨ cong! (▹ᵏ-F.fmap-∘ k₁ {g = ι₁ {A ⊗ ◇ B ⊕ ◇ A ⊗ B} {A ⊗ B}}
                                    {ι₁ {A ⊗ ◇ B} {◇ A ⊗ B}}{n} {pair-delay-◇₂ k₁ l n (a₁ , a₂)}) ⟩
            μ.at A n (k₁ ,
                ⌞ ▹ᵏ-F.fmap k₁ *π₁ n (▹ᵏ-F.fmap k₁ (ι₁ ∘ ι₁) n
                    (pair-delay-◇₂ k₁ l n (a₁ , a₂))) ⌟)
        ≡⟨ cong! (▹ᵏ-F.fmap-∘ k₁ {g = *π₁} {ι₁ ∘ ι₁} {n} {pair-delay-◇₂ k₁ l n (a₁ , a₂)}) ⟩
            μ.at A n (k₁ ,
                ▹ᵏ-F.fmap k₁ ([ η.at A ∘ π₁ ⁏ π₁ {B = L} ⁏ η.at A ∘ π₁ {B = L} ] ∘ ι₁ ∘ ι₁) n
                    (pair-delay-◇₂ k₁ l n (a₁ , a₂)))
        ≡⟨⟩
            μ.at A n (k₁ ,
                ⌞ ▹ᵏ-F.fmap k₁ (η.at A ∘ π₁) n
                    (pair-delay-◇₂ k₁ l n (a₁ , a₂)) ⌟)
        ≡⟨ cong! (▹ᵏ-F.fmap-∘ k₁) ⟩
            μ.at A n (k₁ , ▹ᵏ-F.fmap k₁ (η.at A) n
                ⌞ ▹ᵏ-F.fmap k₁ π₁ n (pair-delay-◇₂ k₁ l n (a₁ , a₂)) ⌟)
        ≡⟨ cong! (lemma k₁ l {n} {a₁ , a₂}) ⟩
            μ.at A n (k₁ , ▹ᵏ-F.fmap k₁ (η.at A) n a₁)
        ≡⟨ η-unit2 ⟩
            k₁ , a₁
        ≡⟨ sym (,-injectiveˡ pf) ⟩
            l₁ n a
        ∎
        where
        lemma : ∀ (k l : ℕ) -> ▹ᵏ-F.fmap k π₁ ∘ pair-delay-◇₂ k l ≈ π₁
        lemma zero l = refl
        lemma (suc k) l {zero} = refl
        lemma (suc k) l {suc n} = lemma k l {n}

    *π₁-comm-◇ {A}{B}{L}{l₁}{l₂}{n}{a} | ((.(suc (k₂ + l)) , a₁) , k₂ , a₂) with≡ pf
                                      | greater .k₂ l  with≡ cpf  =
        begin
            (μ.at A n (◇-f *π₁ n (◇-select n ⌞ (l₁ n a , l₂ n a) ⌟)))
        ≡⟨ cong! pf ⟩
            μ.at A n (◇-f *π₁ n
                (prod-◇-compare (suc (k₂ + l)) k₂ n a₁ a₂ (⌞ compare (suc (k₂ + l)) k₂ ⌟)))
        ≡⟨ cong! cpf ⟩
            μ.at A n (◇-f *π₁ n
                (prod-◇-compare (suc (k₂ + l)) k₂ n a₁ a₂ (greater k₂ l)))
        ≡⟨⟩
            μ.at A n (k₂ ,
                ▹ᵏ-F.fmap k₂ *π₁ n ⌞ (▹ᵏ-F.fmap k₂ ι₁ n (▹ᵏ-F.fmap k₂ ι₂ n
                    (pair-delay-◇₁ k₂ l n (a₁ , a₂)))) ⌟)
        ≡⟨ cong! (▹ᵏ-F.fmap-∘ k₂ {g = ι₁ {A ⊗ ◇ B ⊕ ◇ A ⊗ B} {A ⊗ B}}
                                    {ι₂ {A ⊗ ◇ B} {◇ A ⊗ B}}{n} {pair-delay-◇₁ k₂ l n (a₁ , a₂)}) ⟩
            μ.at A n (k₂ ,
                ⌞ ▹ᵏ-F.fmap k₂ *π₁ n (▹ᵏ-F.fmap k₂ (ι₁ ∘ ι₂) n
                    (pair-delay-◇₁ k₂ l n (a₁ , a₂))) ⌟)
        ≡⟨ cong! (▹ᵏ-F.fmap-∘ k₂ {g = *π₁} {ι₁ ∘ ι₂} {n} {pair-delay-◇₁ k₂ l n (a₁ , a₂)}) ⟩
            μ.at A n (k₂ ,
                ▹ᵏ-F.fmap k₂ ([ η.at A ∘ π₁ {B = L} ⁏ π₁ {B = B} ⁏ η.at A ∘ π₁ {B = L} ] ∘ ι₁ ∘ ι₂) n
                    (pair-delay-◇₁ k₂ l n (a₁ , a₂)))
        ≡⟨⟩
            μ.at A n (k₂ , ⌞ ▹ᵏ-F.fmap k₂ π₁ n (pair-delay-◇₁ k₂ l n (a₁ , a₂)) ⌟)
        ≡⟨ refl ⟩
            μ.at A n (◇-f π₁ n (k₂ , pair-delay-◇₁ k₂ l n (a₁ , a₂)))
        ≡⟨ {!   !} ⟩
        -- ≡⟨ cong! (lemma k₂ l {n} {a₁ , a₂}) ⟩
        --     μ.at A n (k₂ , ▹ᵏ-F.fmap k₂ (▹ᵏ-◇.at (suc l) A) n (split-▹ᵏ k₂ l n a₁))
            suc (k₂ + l) , a₁
        ≡⟨ sym (,-injectiveˡ pf) ⟩
            l₁ n a
        ∎
        where
        split-▹ᵏ : ∀ {A} k l -> delay A by suc (k + l) ⇴ delay (delay A by suc l) by k
        split-▹ᵏ zero l n a = a
        split-▹ᵏ (suc k) l zero a = top.tt
        split-▹ᵏ (suc k) l (suc n) a = split-▹ᵏ k l n a

        lemma : ∀ {A B} (k l  : ℕ)
             -> ▹ᵏ-F.fmap k (π₁ {B = B}) ∘ pair-delay-◇₁ {A} k l
              ≈ ▹ᵏ-F.fmap k (▹ᵏ-◇.at (suc l) A) ∘ split-▹ᵏ k l ∘ π₁
        lemma zero l {n} = refl
        lemma (suc k) l {zero} = refl
        lemma (suc k) l {suc n} = lemma k l

        lemma2 : ∀ {A B} k l n (a₁ : delay A by suc (k + l) at n)
                               (a₂ : delay B by k at n)
              -> μ.at A n (◇-f π₁ n (k , pair-delay-◇₁ k l n (a₁ , a₂)))
               ≡ (suc (k + l) , a₁)
        lemma2 zero l n a₁ a₂ = refl
        lemma2 (suc k) l zero a₁ a₂ = {!   !}
        lemma2 (suc k) l (suc n) a₁ a₂ = {!   !}

    *π₁-comm-◇ {l₁ = l₁} {l₂} {n} {a} | ((k₁ , a₁) , .k₁ , a₂) with≡ pf
                                      | equal .k₁  with≡ cpf = {!   !}

open Linear ℝeactive-linear public

-- Handle a linear product with three continuations
handle : ∀ {A B C D : τ}
          -> (A ⊗   B ⊗ ◇ C ⇴ ◇ D)
          -> (A ⊗ ◇ B ⊗   C ⇴ ◇ D)
          -> (A ⊗   B ⊗   C ⇴ ◇ D)
          -> A ⊗ (B ⊛ C)    ⇴ ◇ D
handle a b c = [ a ∘ assoc-left ⁏ b ∘ assoc-left ⁏ c ∘ assoc-left ] ∘ dist2


------------------------------------------------------------------------
-- Some results about various forms of coinductively defined weak
-- bisimilarity for the delay monad
------------------------------------------------------------------------

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

open import Prelude

module Bisimilarity.Weak.Delay-monad {a} {A : Type a} where

open import Delay-monad
open import Delay-monad.Bisimilarity as DB using (force)
import Delay-monad.Bisimilarity.Negative as DN
open import Equality.Propositional
open import Logical-equivalence using (_⇔_)
open import Prelude.Size

open import Function-universe equality-with-J hiding (id; _∘_)
open import Function-universe.Size equality-with-J

import Bisimilarity.Delay-monad as SD
import Bisimilarity.Weak.Alternative.Equational-reasoning-instances
import Bisimilarity.Weak.Equational-reasoning-instances
open import Bisimilarity.Weak.Equivalent
open import Equational-reasoning
import Expansion.Delay-monad as ED
open import Labelled-transition-system.Delay-monad A

open import Bisimilarity delay-monad using ([_]_∼_)
open import Bisimilarity.Weak delay-monad
open import Bisimilarity.Weak.Alternative delay-monad as AW
  using (force)
open import Expansion delay-monad as BE using ([_]_≳_; _≳_)

------------------------------------------------------------------------
-- Several definitions of weak bisimilarity are pointwise logically
-- equivalent

-- Emulations of the constructors DW.later, DW.laterˡ and DW.laterʳ.

later-cong : ∀ {i x y} →
             [ i ] force x ≈′ force y → [ i ] later x ≈ later y
later-cong x≈′y =
  ⟨ (λ { later → _ , ⟶→⇒̂ later , x≈′y })
  , (λ { later → _ , ⟶→⇒̂ later , x≈′y })
  ⟩

laterˡ : ∀ {i x y} → [ i ] force x ≈ y → [ i ] later x ≈ y
laterˡ x≈y =
  ⟨ (λ { later → _ , silent _ done , convert {a = a} x≈y })
  , Σ-map id (Σ-map later⇒̂ id) ∘ right-to-left x≈y
  ⟩

laterʳ : ∀ {i x y} → [ i ] x ≈ force y → [ i ] x ≈ later y
laterʳ x≈y =
  ⟨ Σ-map id (Σ-map later⇒̂ id) ∘ left-to-right x≈y
  , (λ { later → _ , silent _ done , convert {a = a} x≈y })
  ⟩

-- The direct definition of weak bisimilarity is contained in the form
-- of weak bisimilarity obtained from the transition relation.

direct→indirect : ∀ {i x y} →
                  DB.[ i ] x ≈ y → [ i ] x ≈ y
direct→indirect DB.now        = reflexive
direct→indirect (DB.later  p) = later-cong λ { .force →
                                  direct→indirect (force p) }
direct→indirect (DB.laterˡ p) = laterˡ (direct→indirect p)
direct→indirect (DB.laterʳ p) = laterʳ (direct→indirect p)

mutual

  -- The definition of weak bisimilarity obtained from the transition
  -- relation is contained in the direct one.

  indirect→direct : ∀ {i} x y → [ i ] x ≈ y → DB.[ i ] x ≈ y
  indirect→direct {i} (now x) y =
    [ i ] now x ≈ y                                  ↝⟨ (λ p → left-to-right p now) ⟩
    (∃ λ y′ → y [ just x ]⇒̂ y′ × [ i ] now x ≈′ y′)  ↝⟨ DB.≳→ ∘ ED.[just]⇒̂→≳now ∘ proj₁ ∘ proj₂ ⟩
    DB.[ i ] y ≈ now x                               ↝⟨ DB.symmetric ⟩□
    DB.[ i ] now x ≈ y                               □

  indirect→direct {i} x (now y) =
    [ i ] x ≈ now y                                  ↝⟨ (λ p → right-to-left p now) ⟩
    (∃ λ x′ → x [ just y ]⇒̂ x′ × [ i ] x′ ≈′ now y)  ↝⟨ DB.≳→ ∘ ED.[just]⇒̂→≳now ∘ proj₁ ∘ proj₂ ⟩□
    DB.[ i ] x ≈ now y                               □

  indirect→direct (later x) (later y) lx≈ly
    with left-to-right lx≈ly later

  ... | y′ , non-silent contradiction _ , _ =
    ⊥-elim (contradiction _)

  ... | y′ , silent _ (step _ later y⇒y′) , x≈′y′ =
    DB.later λ { .force →
      indirect→direct′ y⇒y′ (force x≈′y′) }

  ... | y′ , silent _ done , x≈′ly
    with right-to-left lx≈ly later

  ...   | x′ , non-silent contradiction _ , _ =
    ⊥-elim (contradiction _)

  ...   | x′ , silent _ (step _ later x⇒x′) , x′≈′y =
    DB.later λ { .force →
      DB.symmetric $
      indirect→direct′ x⇒x′ $
      symmetric (force x′≈′y) }

  ...   | x′ , silent _ done , lx≈′y =
    DB.later λ { .force →
      DB.laterˡʳ⁻¹
        (indirect→direct _ _ (force lx≈′y))
        (indirect→direct _ _ (force x≈′ly)) }

  private

    -- A lemma used by indirect→direct.

    indirect→direct′ : ∀ {i x y y′} →
                       y ⇒ y′ → [ i ] x ≈ y′ → DB.[ i ] x ≈ y
    indirect→direct′ done               p = indirect→direct _ _ p
    indirect→direct′ (step _  later tr) p = DB.laterʳ (indirect→direct′ tr p)
    indirect→direct′ (step () now   _)

-- The direct definition of weak bisimilarity is logically equivalent
-- to the one obtained from the transition relation.
--
-- TODO: Are the two definitions isomorphic?

direct⇔indirect : ∀ {i x y} → DB.[ i ] x ≈ y ⇔ [ i ] x ≈ y
direct⇔indirect = record
  { to   = direct→indirect
  ; from = indirect→direct _ _
  }

-- The direct definition of weak bisimilarity is logically equivalent
-- to the alternative, coinductive form of weak bisimilarity obtained
-- from the transition relation. Note that this proof is not
-- size-preserving.

direct⇔alternative : ∀ {x y} → x DB.≈ y ⇔ x AW.≈ y
direct⇔alternative {x} {y} =
  x DB.≈ y  ↝⟨ direct⇔indirect ⟩
  x ≈ y     ↝⟨ inverse alternative⇔ ⟩□
  x AW.≈ y  □

------------------------------------------------------------------------
-- A non-existence result for the alternative, coinductive indirect
-- definition of weak bisimilarity

private

  -- If A is uninhabited, then AW._≈_ is trivial.

  uninhabited→trivial : ¬ A → ∀ x y → x AW.≈ y
  uninhabited→trivial =
    ¬ A                 ↝⟨ DB.uninhabited→trivial ⟩
    (∀ x y → x DB.≈ y)  ↝⟨ ∀-cong _ (λ _ → ∀-cong _ λ _ → _⇔_.to direct⇔alternative) ⟩
    (∀ x y → x AW.≈ y)  □

-- One can define a size-preserving "later-cong" function iff A is
-- uninhabited.

Later-cong =
  ∀ {i x y} →
  AW.[ i ] force x ≈′ force y → AW.[ i ] later x ≈ later y

size-preserving-later-cong⇔uninhabited : Later-cong ⇔ ¬ A
size-preserving-later-cong⇔uninhabited = record
  { to   = Later-cong                ↝⟨ (λ later-cong → now≈never (λ {i} → later-cong {i})) ⟩
           (∀ x → now x AW.≈ never)  ↝⟨ ∀-cong _ (λ _ → _⇔_.from direct⇔alternative) ⟩
           (∀ x → now x DB.≈ never)  ↝⟨ (λ hyp → DB.now≉never ∘ hyp) ⟩□
           ¬ A                       □
  ; from = ¬ A                 ↝⟨ uninhabited→trivial ⟩
           (∀ x y → x AW.≈ y)  ↝⟨ (λ trivial {_ _ _} _ → trivial _ _) ⟩□
           Later-cong          □
  }
  where

  module _ (later-cong : Later-cong) where

    now≈never : ∀ {i} x → AW.[ i ] now x ≈ never
    now≈never x =
      now x                         ∼⟨ ⇒alternative (laterʳ reflexive) ⟩
      later (λ { .force → now x })  ∼⟨ later-cong (λ { .force → now≈never x }) ⟩■
      never

------------------------------------------------------------------------
-- Some non-existence results for the standard indirect definition of
-- weak bisimilarity

-- There is a transitivity proof that takes weak bisimilarity and
-- expansion to expansion iff A is uninhabited.

Transitivity-≈≳≳ = {x y z : Delay A ∞} → x ≈ y → y ≳ z → x ≳ z
Transitivity-≳≈≳ = {x y z : Delay A ∞} → x ≳ y → y ≈ z → x ≳ z

transitive-≈≳≳⇔uninhabited : Transitivity-≈≳≳ ⇔ ¬ A
transitive-≈≳≳⇔uninhabited =
  Transitivity-≈≳≳     ↝⟨ inverse $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ direct⇔indirect (→-cong _ ED.direct⇔indirect ED.direct⇔indirect) ⟩
  DN.Transitivity-≈≳≳  ↝⟨ DN.transitive-≈≳≳⇔uninhabited ⟩□
  ¬ A                  □

transitive-≳≈≳⇔uninhabited : Transitivity-≳≈≳ ⇔ ¬ A
transitive-≳≈≳⇔uninhabited =
  Transitivity-≳≈≳     ↝⟨ inverse $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ ED.direct⇔indirect (→-cong _ direct⇔indirect ED.direct⇔indirect) ⟩
  DN.Transitivity-≳≈≳  ↝⟨ DN.transitive-≳≈≳⇔uninhabited ⟩□
  ¬ A                  □

-- There is a transitivity proof that preserves the size of the second
-- argument iff A is uninhabited.

Transitivityʳ =
  ∀ {i} {x y z : Delay A ∞} →
  x ≈ y → [ i ] y ≈ z → [ i ] x ≈ z

size-preserving-transitivityʳ⇔uninhabited : Transitivityʳ ⇔ ¬ A
size-preserving-transitivityʳ⇔uninhabited =
  Transitivityʳ       ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                         →-cong _ direct⇔indirect $ →-cong _ direct⇔indirect direct⇔indirect ⟩
  DN.Transitivity-≈ʳ  ↝⟨ DN.size-preserving-transitivity-≈ʳ⇔uninhabited ⟩□
  ¬ A                 □

-- There is a transitivity proof that preserves the size of the first
-- argument iff A is uninhabited.

Transitivityˡ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ≈ y → y ≈ z → [ i ] x ≈ z

size-preserving-transitivityˡ⇔uninhabited : Transitivityˡ ⇔ ¬ A
size-preserving-transitivityˡ⇔uninhabited =
  Transitivityˡ       ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                         →-cong _ direct⇔indirect $ →-cong _ direct⇔indirect direct⇔indirect ⟩
  DN.Transitivity-≈ˡ  ↝⟨ DN.size-preserving-transitivity-≈ˡ⇔uninhabited ⟩□
  ¬ A                 □

-- There is a transitivity proof that preserves the size of the second
-- argument, a strong bisimilarity, iff A is uninhabited.

Transitivity-≈∼ʳ =
  ∀ {i} {x y z : Delay A ∞} →
  x ≈ y → [ i ] y ∼ z → [ i ] x ≈ z

size-preserving-transitivity-≈∼ʳ⇔uninhabited : Transitivity-≈∼ʳ ⇔ ¬ A
size-preserving-transitivity-≈∼ʳ⇔uninhabited =
  Transitivity-≈∼ʳ     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ direct⇔indirect $ →-cong _ SD.direct⇔indirect direct⇔indirect ⟩
  DN.Transitivity-≈∼ʳ  ↝⟨ DN.size-preserving-transitivity-≈∼ʳ⇔uninhabited ⟩□
  ¬ A                  □

-- There is a transitivity proof that preserves the size of the first
-- argument, a strong bisimilarity, iff A is uninhabited.

Transitivity-∼≈ˡ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ∼ y → y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-∼≈ˡ⇔uninhabited : Transitivity-∼≈ˡ ⇔ ¬ A
size-preserving-transitivity-∼≈ˡ⇔uninhabited =
  Transitivity-∼≈ˡ     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ SD.direct⇔indirect $ →-cong _ direct⇔indirect direct⇔indirect ⟩
  DN.Transitivity-∼≈ˡ  ↝⟨ DN.size-preserving-transitivity-∼≈ˡ⇔uninhabited ⟩□
  ¬ A                  □

-- There is a transitivity-like proof that preserves the size of the
-- first argument, an expansion, iff A is uninhabited.

Transitivity-≳≈ˡ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ≳ y → y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-≳≈ˡ⇔uninhabited : Transitivity-≳≈ˡ ⇔ ¬ A
size-preserving-transitivity-≳≈ˡ⇔uninhabited =
  Transitivity-≳≈ˡ     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ ED.direct⇔indirect (→-cong _ direct⇔indirect direct⇔indirect) ⟩
  DN.Transitivity-≳≈ˡ  ↝⟨ DN.size-preserving-transitivity-≳≈ˡ⇔uninhabited ⟩□
  ¬ A                  □

-- There is a transitivity-like proof that takes an expansion as the
-- first argument, and preserves the size of both arguments, iff A is
-- uninhabited.

Transitivity-≳≈ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ≳ y → [ i ] y ≈ z → [ i ] x ≈ z

size-preserving-transitivity-≳≈⇔uninhabited : Transitivity-≳≈ ⇔ ¬ A
size-preserving-transitivity-≳≈⇔uninhabited =
  Transitivity-≳≈     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                         →-cong _ ED.direct⇔indirect (→-cong _ direct⇔indirect direct⇔indirect) ⟩
  DN.Transitivity-≳≈  ↝⟨ DN.size-preserving-transitivity-≳≈⇔uninhabited ⟩□
  ¬ A                 □

-- There is a transitivity-like proof that preserves the size of the
-- second argument, an expansion (with the arguments swapped), iff A
-- is uninhabited.

Transitivity-≈≲ʳ =
  ∀ {i} {x y z : Delay A ∞} →
  x ≈ y → [ i ] z ≳ y → [ i ] x ≈ z

size-preserving-transitivity-≈≲ʳ⇔uninhabited : Transitivity-≈≲ʳ ⇔ ¬ A
size-preserving-transitivity-≈≲ʳ⇔uninhabited =
  Transitivity-≈≲ʳ     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ direct⇔indirect (→-cong _ ED.direct⇔indirect direct⇔indirect) ⟩
  DN.Transitivity-≈≲ʳ  ↝⟨ DN.size-preserving-transitivity-≈≲ʳ⇔uninhabited ⟩□
  ¬ A                  □

-- There is a transitivity-like proof that takes an expansion (with
-- the arguments swapped) as the second argument, and preserves the
-- size of both arguments, iff A is uninhabited.

Transitivity-≈≲ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ≈ y → [ i ] z ≳ y → [ i ] x ≈ z

size-preserving-transitivity-≈≲⇔uninhabited : Transitivity-≈≲ ⇔ ¬ A
size-preserving-transitivity-≈≲⇔uninhabited =
  Transitivity-≈≲     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                         →-cong _ direct⇔indirect (→-cong _ ED.direct⇔indirect direct⇔indirect) ⟩
  DN.Transitivity-≈≲  ↝⟨ DN.size-preserving-transitivity-≈≲⇔uninhabited ⟩□
  ¬ A                 □

-- There is a transitivity-like proof that preserves the size of the
-- first argument, and takes an expansion as the second argument, iff
-- A is uninhabited.

Transitivity-≈≳ˡ =
  ∀ {i} {x y z : Delay A ∞} →
  [ i ] x ≈ y → y ≳ z → [ i ] x ≈ z

size-preserving-transitivity-≈≳ˡ⇔uninhabited : Transitivity-≈≳ˡ ⇔ ¬ A
size-preserving-transitivity-≈≳ˡ⇔uninhabited =
  Transitivity-≈≳ˡ     ↝⟨ inverse $ implicit-∀-size-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $ implicit-∀-cong _ $
                          →-cong _ direct⇔indirect (→-cong _ ED.direct⇔indirect direct⇔indirect) ⟩
  DN.Transitivity-≈≳ˡ  ↝⟨ DN.size-preserving-transitivity-≈≳ˡ⇔uninhabited ⟩□
  ¬ A                  □

------------------------------------------------------------------------
-- A non-existence result for both indirect definitions of weak
-- bisimilarity

-- The function ⇒alternative translating from the standard indirect
-- definition of weak bisimilarity to the alternative, coinductive one
-- can be made size-preserving iff A is uninhabited.

size-preserving-⇒alternative⇔uninhabited :
  (∀ {i p q} → [ i ] p ≈ q → AW.[ i ] p ≈ q) ⇔ ¬ A
size-preserving-⇒alternative⇔uninhabited = record
  { to =
      (∀ {i p q} → [ i ] p ≈ q → AW.[ i ] p ≈ q)  ↝⟨ (λ ⇒alternative x≈y y≈z →
                                                        alternative⇒ (transitive {a = a} (⇒alternative x≈y) (⇒alternative y≈z))) ⟩
      Transitivityʳ                               ↝⟨ _⇔_.to size-preserving-transitivityʳ⇔uninhabited ⟩□
      ¬ A                                         □
  ; from =
      ¬ A                                         ↝⟨ uninhabited→trivial ⟩
      (∀ x y → x AW.≈ y)                          ↝⟨ (λ trivial {_ _ _} _ → trivial _ _) ⟩□
      (∀ {i p q} → [ i ] p ≈ q → AW.[ i ] p ≈ q)  □
  }

------------------------------------------------------------------------
-- More lemmas

-- If x and y both make non-silent transitions of the same kind,
-- then they are weakly bisimilar.

⟶-with-equal-labels→≈ :
  ∀ {x x′ y y′ μ} →
  ¬ Silent μ → x [ μ ]⟶ x′ → y [ μ ]⟶ y′ → x ≈ y
⟶-with-equal-labels→≈ _  now   now = reflexive
⟶-with-equal-labels→≈ ¬s later _   = ⊥-elim (¬s _)

[]⇒-with-equal-labels→≈ :
  ∀ {x x′ y y′ μ} →
  ¬ Silent μ → x [ μ ]⇒ x′ → y [ μ ]⇒ y′ → x ≈ y
[]⇒-with-equal-labels→≈
  {x = x} {y = y} ¬s
  (steps {p′ = x′} x⇒x′ x′⟶x″ x″⇒x‴)
  (steps {p′ = y′} y⇒y′ y′⟶y″ y″⇒y‴) =

  x   ∼⟨ direct→indirect (DB.≳→ (ED.⇒→≳ x⇒x′)) ⟩
  x′  ∼⟨ ⟶-with-equal-labels→≈ ¬s x′⟶x″ y′⟶y″ ⟩
  y′  ∼⟨ symmetric (direct→indirect (DB.≳→ (ED.⇒→≳ y⇒y′))) ⟩■
  y

⇒̂-with-equal-labels→≈ :
  ∀ {x x′ y y′ μ} →
  ¬ Silent μ → x [ μ ]⇒̂ x′ → y [ μ ]⇒̂ y′ → x ≈ y
⇒̂-with-equal-labels→≈ ¬s (non-silent _ x⇒x′) (non-silent _ y⇒y′) =
  []⇒-with-equal-labels→≈ ¬s x⇒x′ y⇒y′
⇒̂-with-equal-labels→≈ ¬s   (silent s _) = ⊥-elim (¬s s)
⇒̂-with-equal-labels→≈ ¬s _ (silent s _) = ⊥-elim (¬s s)

⟶̂-with-equal-labels→≈ :
  ∀ {x x′ y y′ μ} →
  ¬ Silent μ → x [ μ ]⟶̂ x′ → y [ μ ]⟶̂ y′ → x ≈ y
⟶̂-with-equal-labels→≈ ¬s x⟶̂x′ y⟶̂y′ =
  ⇒̂-with-equal-labels→≈ ¬s (⟶̂→⇒̂ x⟶̂x′) (⟶̂→⇒̂ y⟶̂y′)


data ⊥ : Set where

record ⊤ : Set where
  constructor unit

data T : ⊥ → Set where
  con : (i : ⊥) → T i

module M (x : ⊤) where
  bar : (i : ⊥) → T i → ⊥
  bar .i (con i) = i
    module N where
      foo : ⊥
      foo = i

  -- Should not be accepted.
  -- Type signature should be `test : ⊥ → ⊥`
  test : ⊥ → ⊥
  test = N.foo

false : ⊥
false = M.test unit unit