module Compilation.Encode.Core where

open import Context
open import Type.Core
open import Compilation.Data

open import Function
open import Data.List.Base as List

{- Note [Compilation]
We compile data descriptions to System Fωμ in two steps:

1. Lambda-bind
     a) a tag to distinguish between the data types from the family
     b) a variable for each of the data types from the family
   then apply the tag to all of the data types after Scott-encoding them in order to be able to
   choose one particular data type from the family later. This way we get a valid System Fωμ type,
   but it's not a legal pattern functor yet, as it binds one tag and `n` variables responsible for
   recursion and we need to have only one variable responsible for recursion and one tag in order
   to be able to feed a type function to `μ` and actually tie the knot. Thus the next step is
2. Instantiate all the variables responsible for recursion using a single variable applied to
   distinct tags. This way we transform `1` (tag) + `n` (recursion) variables into
   `1` (recursion) + `1` (tag) variables, i.e. just two variables of right kinds, thus the result
   can be passed to `μ`.

We can now look at the `ProdTree`/`Tree` example.

After the first step (which is performed by the `encodeDataN` function, which calls `scottEncode`
internally) we get the following type (I'll save you from the De Bruijn reverse Turing test and
use the Agda notation instead, but note that this is still a System Fωμ type):

> prodTreeTreeReprN =
>   λ (tag : (Set -> Set -> Set) -> (Set -> Set) -> Set)
>       (prodTree : Set -> Set -> Set) (tree : Set -> Set) ->
>         ∀ (R : Set) ->                                     -- Binding the result variable.
>           tag                                              -- Choosing between the two data types.
>             (λ A B                                         -- The two types that `ProdTree` binds.
>                -> (tree (∀ Q -> (A -> B -> Q) -> Q) -> R)  -- Case over the `Prod` constructor.
>                -> R)
>             (λ A                                           -- The type that `Tree` binds.
>                -> (A -> R)                                 -- Case over the `Leaf` constructor.
>                -> (prodTree A A -> R)                      -- Case over the `Fork` constructor.
>                -> R)

Here we lambda-bind `prodTree` and `tree` directly, Scott-encode the definition of the data types
and use `tag` in order to choose between the two data types.

At the next step we instantiate `prodTree` and `tree` (`instantiations` computes the instantiations)
using a single `rec` (`packEncodedDataN` does this):

> prodTreeTreeRepr1 =
>   λ (rec : ((Set -> Set -> Set) -> (Set -> Set) -> Set) -> Set)
>       (tag : (Set -> Set -> Set) -> (Set -> Set) -> Set) ->
>         prodTreeTreeReptN
>           tag
>           (λ A B -> rec λ prodTree tree -> prodTree A B)
>           (λ A   -> rec λ prodTree tree -> tree A)

It only remains to take the fixed point of this pattern functor:

> prodTreeTree = λ tag -> μ prodTreeTreeRepr1 tag

And that's it, `prodTreeTree` is a valid System Fωμ type that encodes the `ProdTree`/`Tree` family.
-}

{- Note [Data description denotations]
Once we compile a data description to a System Fωμ type, we could already take the denotation of
the latter, but then we'd just get an Agda type that encodes the whole family of mutually recursive
data types, while what we really want is getting all the separate data types, not the whole family
at once.

Hence rather than take the denotation directly, we first instantiate the resulting System Fωμ type
using the tags that the data types from the family correspond to. I.e. we call `instantiations` once
again, get an environment of System Fωμ types and take denotations of those. The result then is an
environment of Agda types, each of which represents a data type from the original family of data
types. We only need to extract them from the environment. Unfortunately, Agda lacks top-level
matches, so we do it like this:

>  ProdTreeTree′ = Encoded.⟦ prodTreeTree ⟧ᵈ
>
>  ProdTree′ : Set -> Set -> Set
>  ProdTree′ with ProdTreeTree′
>  ... | ∅ ▷ PT′ ▷ T′ = PT′
>
>  Tree′ : Set -> Set
>  Tree′ with ProdTreeTree′
>  ... | ∅ ▷ PT′ ▷ T′ = T′
-}

-- | Get the kind of tags from the context of a data description.
--
-- > dataConToTagKind (ε ▻ (σ¹₁ ▻ … ▻ σ¹ₙ₁) ▻ … ▻ (σ¹ₘ ▻ … ▻ σᵐₙₘ)) τ =
-- >   (σ¹₁ ⇒ᵏ … ⇒ᵏ σ¹ₙ₁ ⇒ᵏ ⋆) ⇒ᵏ … ⇒ᵏ (σ¹₁ ⇒ᵏ … ⇒ᵏ σᵐₙₘ ⇒ᵏ ⋆) ⇒ᵏ τ
dataConToTagKind : Con Conᵏ -> Kind -> Kind
dataConToTagKind = conToKindBy $ λ Θ -> conToKind Θ ⋆

-- | Scott-encode a single data type. The type variable (denoted as `ρ`) that represents
-- the result is assumed to be in scope, because we Scott-encode a bunch of data types
-- at the same time and hence the resulting type variable has to be bound outside of all of them,
-- which is why we keep it implicit while encoding a single data type.
--
-- > scottEncode (ε ▻ α₁ ▻ … ▻ αₙ) [(β¹₁ ⇒ᶜ … ⇒ᶜ β¹ₙ₁ ⇒ᶜ endᶜ) , … , (βᵐ₁ ⇒ᶜ … ⇒ᶜ βᵐₙₘ ⇒ᶜ endᶜ)] =
-- >     λ α₁ … αₙ → (β¹₁ ⇒ … ⇒ β¹ₙ₁ ⇒ ρ) ⇒ … ⇒ (βᵐ₁ ⇒ … ⇒ βᵐₙₘ ⇒ ρ) ⇒ ρ
scottEncode : ∀ {Ξ} Θ -> List (Cons (Ξ ▻▻ Θ)) -> Ξ ▻ ⋆ ⊢ᵗ conToKind Θ ⋆
scottEncode Θ = Lamⁿ' Θ ∘ foldr (λ ξ γ -> consToType (renᶜ (keepⁿ Θ topᵒ) ξ) ρ ⇒ γ) ρ where
  ρ = renᵗ (extʳ Θ) $ Var vz

-- | Encode a `Data` description as an n-ary type function.
--
-- > encodeDataN (PackData [data¹ , … , dataᵐ]) =
-- >    λ tag δ¹ … δᵐ → tag (scott data¹) … (scott dataᵐ)
--
-- (`δᵢ` is the name of `dataᵢ` that is potentially used internally in any `dataⱼ`,
-- because the data types are allowed to be mutually recursive).
encodeDataN : ∀ {Ξ Θs} -> Data Ξ Θs -> Ξ ⊢ᵗ dataConToTagKind Θs ⋆ ⇒ᵏ dataConToTagKind Θs ⋆
encodeDataN {Ξ} {Θs} (PackData datas) =
  Lam_ ∘′ Lamⁿ Θs ∘′ π ⋆ $′
    applyᵗⁿ Θs
      (Var ∘ vsⁿ $ mapᶜ _ Θs ▻ ⋆)
      (mapˢ (λ {Θ} -> renᵗ (keepⁿ (mapᶜ _ Θs ▻ ⋆) topᵒ) ∘ scottEncode Θ) datas)

-- | Compute all instantiations of a thing of kind `dataConToTagKind Θs ⋆ ⇒ᵏ ⋆`, which is either
-- the first variable that a packed pattern functor binds (i.e. the one responsible for recursion)
-- or the final data type representing a family of mutually recursive data types.
-- "Instantiations" then are particular data types from a family of mutually recursive data types
-- (or represent them if we're are inside a pattern functor).
-- For example, the instantiations of `treeForest` are `tree` and `forest`, i.e. precisely those
-- data types that form the family.
--
-- > instantiations ψ
-- >     = ∅
-- >     ▷ (λ α¹₁ … α¹ₙ₁ → ψ λ β¹ … βᵐ → β¹ α¹₁ … α¹ₙ₁)
-- >     ▷ …
-- >     ▷ (λ αᵐ₁ … αᵐₙₘ → ψ λ β¹ … βᵐ → βᵐ αᵐ₁ … αᵐₙₘ)
instantiations : ∀ {Θs Ξ} -> Ξ ⊢ᵗ dataConToTagKind Θs ⋆ ⇒ᵏ ⋆ -> Seq (λ Θ -> Ξ ⊢ᵗ conToKind Θ ⋆) Θs
instantiations {ε}      ψ = ø
instantiations {Θs ▻ Θ} ψ
  = instantiations {Θs} (Lam_ $ shiftᵗ ψ ∙
      Lamⁿ (Θs ▻ Θ) (applyᵗⁿ Θs (Var (vsⁿ (mapᶜ _ (Θs ▻ Θ)))) (mapˢ shiftᵗ subVars)))
  ▶ Lamⁿ' Θ (shiftᵗⁿ Θ ψ ∙
      Lamⁿ (Θs ▻ Θ) (applyᵗⁿ Θ (Var vz) (mapˢ (shiftᵗⁿ (mapᶜ _ $ Θs ▻ Θ)) subVars')))

-- | Pack an encoded family of mutually recursive data types as an 1-ary pattern functor.
-- The encoded family is of this form (see `encodeDataN`):
--
-- > φ : ((σ¹₁ ⇒ᵏ … ⇒ᵏ σ¹ₙ₁ ⇒ᵏ ⋆) ⇒ᵏ … ⇒ᵏ (σᵐ₁ ⇒ᵏ … ⇒ᵏ σᵐₙₘ ⇒ᵏ ⋆) ⇒ᵏ ⋆)
-- >   ⇒ᵏ (σ¹₁ ⇒ᵏ … ⇒ᵏ σ¹ₙ₁ ⇒ᵏ ⋆) ⇒ᵏ … ⇒ᵏ (σᵐ₁ ⇒ᵏ … ⇒ᵏ σᵐₙₘ ⇒ᵏ ⋆) ⇒ᵏ ⋆
-- > φ = λ tag δ¹ … δᵐ → <body>
--
-- Where `tag` is a tag and `δʲ` is a variable that stands for a data type from the family.
--
-- The definition of `packEncodedDataN` then is
--
-- > packEncodedDataN _ φ =
-- >     λ rec tag → φ tag
-- >         (λ α¹₁ … α¹ₙ₁ → rec λ ψ¹ … ψᵐ → ψ¹ α¹₁ … α¹ₙ₁)
-- >         …
-- >         (λ αᵐ₁ … αᵐₙₘ → rec λ ψ¹ … ψᵐ → ψᵐ αᵐ₁ … αᵐₙₘ)
--
-- where `αʲᵢ` is a parameter of a data type and `λ ψ¹ … ψᵐ → ψʲ αʲ₁ … αʲᵢ` is the tag of
-- the data type. That is, we put parameters of data types into their corresponding tags.
--
-- So we instantiate each data type argument of `φ` as `rec` applied to an appropriate tag. This
-- way we represent all of the data types using just a single `rec` applied to different tags.
packEncodedDataN
  : ∀ {Ξ} Θs
  -> Ξ ⊢ᵗ dataConToTagKind Θs ⋆ ⇒ᵏ dataConToTagKind Θs ⋆
  -> Ξ ⊢ᵗ (dataConToTagKind Θs ⋆ ⇒ᵏ ⋆) ⇒ᵏ dataConToTagKind Θs ⋆ ⇒ᵏ ⋆
packEncodedDataN Θs φ =
  Lam_ ∘′ Lam_ ∘′ applyᵗⁿ Θs (shiftᵗⁿ (ε ▻ _ ▻ _) φ ∙ Var vz) ∘′ instantiations $ Var (vs vz)

-- | Encode a `Data` as a 1-ary pattern functor.
encodeData1
  : ∀ {Ξ Θs} -> Data Ξ Θs -> Ξ ⊢ᵗ (dataConToTagKind Θs ⋆ ⇒ᵏ ⋆) ⇒ᵏ dataConToTagKind Θs ⋆ ⇒ᵏ ⋆
encodeData1 {Θs = Θs} = packEncodedDataN Θs ∘ encodeDataN

-- | Compile a `Data` to System Fωμ.
compileData : ∀ {Ξ Θs} -> Data Ξ Θs -> Ξ ⊢ᵗ dataConToTagKind Θs ⋆ ⇒ᵏ ⋆
compileData d = Lam μ (shiftᵗ $ encodeData1 d) (Var vz)

compileDatas : ∀ {Ξ Θs} -> Data Ξ Θs -> Seq (λ Θ -> Ξ ⊢ᵗ conToKind Θ ⋆) Θs
compileDatas = instantiations ∘ compileData

-- | Return the denotations of all the data types from a family of mutually recursive data types
-- encoded as a `Data`.
⟦_⟧ᵈ : ∀ {Θs} -> Data ε Θs -> Seq (λ Θ -> ⟦ conToKind Θ ⋆ ⟧ᵏ) Θs
⟦_⟧ᵈ = mapˢ ⟦_⟧ᵗ ∘ compileDatas

module Unused where
  constructorTypes : ∀ {Θs Ξ} -> Data Ξ Θs -> Con (Star (Ξ ▻▻ mapᶜ (λ Θᵢ -> conToKind Θᵢ ⋆) Θs))
  constructorTypes {Θs} {Ξ} (PackData constructors) =
    joinᶜ ∘′ eraseˢ $′ mapElˢ consesToCon constructors where
      consesToCon
        : ∀ {Θ}
        -> Θ ∈ Θs
        -> List (Cons (Ξ ▻▻ mapᶜ (λ Θᵢ -> conToKind Θᵢ ⋆) Θs ▻▻ Θ))
        -> Con (Star (Ξ ▻▻ mapᶜ (λ Θᵢ -> conToKind Θᵢ ⋆) Θs))
      consesToCon {Θ} v = listToCon ∘ List.map (λ ξ -> πⁿ' Θ $ consToType ξ ρ) where
        ρ = applyᵗⁿ Θ (Var ∘ renᵛ (skipⁿ Θ extˡ) $ mapᵛ v) subVars'

  constructorFullTypes : ∀ {Θs Ξ} -> Data Ξ Θs -> Con (Star Ξ)
  constructorFullTypes δ = mapᶜ (instᵗⁿ $ compileDatas δ) $ constructorTypes δ


open import OutsideIn.Prelude
open import OutsideIn.X
module OutsideIn.Expressions(x : X) where
  import OutsideIn.TypeSchema as TS
  open TS(x)
  open X(x)

  {- SYNTAX -}
  data Name (n : Set) : NameType → Set where
    N : n → Name n Regular
    DC : ∀ {x} → dc x → Name n (Datacon x)

  mutual
    data Alternative (ev tv : Set) : Shape → Set where
      _→′_ : ∀ {n : ℕ}{r : Shape}→ Name ev (Datacon n) → Expression (ev ⨁ n) tv r
           → Alternative ev tv (Unary r)

    infixr 5 _∣_

    data Alternatives (ev tv : Set) : Shape → Set where
      esac : Alternatives ev tv Nullary
      _∣_  : ∀ {r₁ r₂} → Alternative ev tv r₁ → Alternatives ev tv r₂
           → Alternatives ev tv (Binary r₁ r₂)

    data Expression (ev tv : Set) : Shape → Set where
      Var : ∀ {x} → Name ev x → Expression ev tv Nullary
      λ′_ : ∀ {a} → Expression (Ⓢ ev) tv a → Expression ev tv (Unary a)
      _·_ : ∀ {a a′} → Expression ev tv a → Expression ev tv a′ → Expression ev tv (Binary a a′)
      let₁_in′_ : ∀ {a a′} → Expression ev tv a → Expression (Ⓢ ev) tv a′
                → Expression ev tv (Binary a a′)
      let₂_∷_in′_ : ∀ {a a′} → Expression ev tv a → Type tv → Expression (Ⓢ ev) tv a′
                  → Expression ev tv (Binary a a′)
      let₃_·_∷_⇒_in′_ : ∀ {a a′} → (n : ℕ) → Expression ev (tv ⨁ n) a
                                  → QConstraint (tv ⨁ n)
                                  → Type (tv ⨁ n)
                                  → Expression (Ⓢ ev) tv a′
                                  → Expression ev tv (Binary a a′)
      case_of_ : ∀ {r₁ r₂} → Expression ev tv r₁ → Alternatives ev tv r₂
               → Expression ev tv (Binary r₁ r₂)

  private
    module PlusN-f n        = Functor (Monad.is-functor (PlusN-is-monad {n}))
    module Ⓢ-f             = Functor Ⓢ-is-functor
    module Type-f           = Functor (type-is-functor)
    module TypeSchema-f {n} = Functor (type-schema-is-functor {n})
    module QC-f             = Functor (qconstraint-is-functor)

  private
    fmap-alt₁ : ∀ {a b tv}{r} → (a → b) → Alternative a tv r → Alternative b tv r
    fmap-exp₁ : ∀ {a b tv}{r} → (a → b) → Expression a tv r → Expression b tv r
    map-fmap-alt₁ : ∀ {a b tv}{r} → (a → b) → Alternatives a tv r → Alternatives b tv r
    fmap-alt₁ f (_→′_ {n} (DC c) expr) = DC c →′ fmap-exp₁ (pn.map f) expr
       where module pn = PlusN-f n
    fmap-exp₁ f (Var (DC c)) = Var (DC c)
    fmap-exp₁ f (Var (N x)) = Var (N (f x))
    fmap-exp₁ f (λ′ x) = λ′ (fmap-exp₁ (Ⓢ-f.map f) x)
    fmap-exp₁ f (x · y) = (fmap-exp₁ f x) · (fmap-exp₁ f y)
    fmap-exp₁ f (let₁ x in′ y) = let₁ (fmap-exp₁ f x) in′ (fmap-exp₁ (Ⓢ-f.map f) y)
    fmap-exp₁ f (let₂ x ∷ τ in′ y) = let₂ (fmap-exp₁ f x) ∷ τ in′ (fmap-exp₁ (Ⓢ-f.map f) y)
    fmap-exp₁ f (let₃ n · x ∷ Q ⇒ τ in′ y) = let₃  n · fmap-exp₁ f x ∷ Q ⇒ τ
                                              in′ fmap-exp₁ (Ⓢ-f.map f) y
    fmap-exp₁ f (case x of alts) = case (fmap-exp₁ f x) of map-fmap-alt₁ f alts
    map-fmap-alt₁ f esac = esac
    map-fmap-alt₁ f (x ∣ xs) = fmap-alt₁ f x ∣ map-fmap-alt₁ f xs

  private
    fmap-alt₂ : ∀ {a b ev}{r} → (a → b) → Alternative ev a r → Alternative ev b r
    fmap-exp₂ : ∀ {a b ev}{r} → (a → b) → Expression ev a r → Expression ev b r
    map-fmap-alt₂ : ∀ {a b ev}{r} → (a → b) → Alternatives ev a r → Alternatives ev b r
    fmap-alt₂ f (p →′ expr) = p →′ fmap-exp₂ f expr
    fmap-exp₂ f (Var x) = Var x
    fmap-exp₂ f (λ′ x) = λ′ (fmap-exp₂ f x)
    fmap-exp₂ f (x · y) = fmap-exp₂ f x · fmap-exp₂ f y
    fmap-exp₂ f (let₁ x in′ y) = let₁ fmap-exp₂ f x in′ fmap-exp₂ f y
    fmap-exp₂ f (let₂ x ∷ τ in′ y) = let₂ fmap-exp₂ f x ∷ Type-f.map f τ in′ fmap-exp₂ f y
    fmap-exp₂ f (let₃ n · x ∷ Q ⇒ τ in′ y) = let₃ n · fmap-exp₂ (pn.map f) x
                                                ∷ QC-f.map (pn.map f) Q ⇒ Type-f.map (pn.map f) τ
                                               in′ fmap-exp₂ f y
      where module pn = PlusN-f n
    fmap-exp₂ f (case x of alts) = case (fmap-exp₂ f x) of map-fmap-alt₂ f alts
    map-fmap-alt₂ f esac = esac
    map-fmap-alt₂ f (x ∣ xs) = fmap-alt₂ f x ∣ map-fmap-alt₂ f xs

  private
    fmap-alt-id₁ : {A tv : Set} {f : A → A}{r : Shape}
                 → isIdentity f → isIdentity (fmap-alt₁ {A}{A}{tv}{r} f)
    map-fmap-alt-id₁ : ∀ {a}{tv}{f : a → a}{r : Shape}
                     → isIdentity f → isIdentity (map-fmap-alt₁  {a}{a}{tv}{r} f)
    fmap-exp-id₁ : ∀{A tv : Set}{r : Shape} {f : A → A} → isIdentity f
                 → isIdentity (fmap-exp₁ {A}{A}{tv}{r} f)
    fmap-alt-id₁ {A}{f} f-is-id {_→′_ {n} (DC p) x} = cong (_→′_ (DC p)) (fmap-exp-id₁ (pn.identity f-is-id))
      where module pn = PlusN-f n
    map-fmap-alt-id₁ {r = Unary _} f-is-id {}
    map-fmap-alt-id₁ {r = Nullary} f-is-id {esac} = refl
    map-fmap-alt-id₁ {r = Binary r₁ r₂} f-is-id {x ∣ xs} = cong₂ _∣_ (fmap-alt-id₁ f-is-id)
                                                                     (map-fmap-alt-id₁ f-is-id)
    fmap-exp-id₁ {r = Nullary} f-is-id {Var (DC x)} = refl
    fmap-exp-id₁ {r = Nullary} f-is-id {Var (N x)} = cong Var (cong N f-is-id)
    fmap-exp-id₁ {r = Unary r′} f-is-id {λ′ x} = cong λ′_ (fmap-exp-id₁ (Ⓢ-f.identity f-is-id))
    fmap-exp-id₁ {r = Binary r₁ r₂} f-is-id {x · y} = cong₂ _·_ (fmap-exp-id₁ f-is-id)
                                                                (fmap-exp-id₁ f-is-id)
    fmap-exp-id₁ {r = Binary r₁ r₂} f-is-id {let₁ x in′ y}
      = cong₂ let₁_in′_ (fmap-exp-id₁ f-is-id) (fmap-exp-id₁ (Ⓢ-f.identity f-is-id))
    fmap-exp-id₁ {r = Binary r₁ r₂} f-is-id {let₂ x ∷ τ in′ y}
      = cong₂ (λ a b → let₂ a ∷ τ in′ b) (fmap-exp-id₁ f-is-id)
                                         (fmap-exp-id₁ (Ⓢ-f.identity f-is-id))
    fmap-exp-id₁ {r = Binary r₁ r₂} f-is-id {let₃ n · x ∷ Q ⇒ τ in′ y}
      = cong₂ (λ a b → let₃ n · a ∷ Q ⇒ τ in′ b) (fmap-exp-id₁ f-is-id)
                                                 (fmap-exp-id₁ (Ⓢ-f.identity f-is-id))
    fmap-exp-id₁ {r = Binary r₁ r₂} f-is-id {case x of alts}
      = cong₂ case_of_ (fmap-exp-id₁ f-is-id) (map-fmap-alt-id₁ f-is-id)

  private
    fmap-alt-id₂ : {A ev : Set}{r : Shape}{f : A → A}
                 → isIdentity f → isIdentity (fmap-alt₂ {A}{A}{ev}{r} f)
    map-fmap-alt-id₂ : ∀ {a}{ev}{r : Shape}{f : a → a}
                     → isIdentity f → isIdentity (map-fmap-alt₂  {a}{a}{ev}{r} f)
    fmap-exp-id₂ : ∀{A ev : Set}{r : Shape} {f : A → A}
                 → isIdentity f → isIdentity (fmap-exp₂ {A}{A}{ev}{r} f)
    fmap-alt-id₂ f-is-id {_→′_ {n} p x} = cong (_→′_ p) (fmap-exp-id₂ f-is-id)
    map-fmap-alt-id₂ f-is-id {esac} = refl
    map-fmap-alt-id₂ f-is-id {x ∣ xs} = cong₂ _∣_ (fmap-alt-id₂ f-is-id) (map-fmap-alt-id₂ f-is-id)
    fmap-exp-id₂ {r = Nullary}      f-is-id {Var x} = refl
    fmap-exp-id₂ {r = Unary r′}     f-is-id {λ′ x} = cong λ′_ (fmap-exp-id₂ f-is-id)
    fmap-exp-id₂ {r = Binary r₁ r₂} f-is-id {x · y} = cong₂ _·_ (fmap-exp-id₂ f-is-id)
                                                                (fmap-exp-id₂ f-is-id)
    fmap-exp-id₂ {r = Binary r₁ r₂} f-is-id {let₁ x in′ y} = cong₂ let₁_in′_ (fmap-exp-id₂ f-is-id)
                                                                             (fmap-exp-id₂ f-is-id)
    fmap-exp-id₂ {r = Binary r₁ r₂} f-is-id {let₂ x ∷ τ in′ y} = cong₃ let₂_∷_in′_
                                                                       (fmap-exp-id₂ f-is-id)
                                                                       (Type-f.identity f-is-id)
                                                                       (fmap-exp-id₂ f-is-id)
    fmap-exp-id₂ {r = Binary r₁ r₂} f-is-id {let₃ n · x ∷ Q ⇒ τ in′ y}
      = cong₄ (let₃_·_∷_⇒_in′_ n) (fmap-exp-id₂ (pn.identity f-is-id))
                                  (QC-f.identity (pn.identity f-is-id))
                                  (Type-f.identity (pn.identity f-is-id))
                                  (fmap-exp-id₂ f-is-id)
      where module pn = PlusN-f n
    fmap-exp-id₂ {r = Binary r₁ r₂} f-is-id {case x of alts}
      = cong₂ case_of_ (fmap-exp-id₂ f-is-id) (map-fmap-alt-id₂ f-is-id)

  private
    fmap-alt-comp₁ : {r : Shape} {A B C tv : Set} {f : A → B}{g : B → C} {x : Alternative A tv r}
                   → fmap-alt₁ (g ∘ f) x ≡ fmap-alt₁ g (fmap-alt₁ f x)
    fmap-exp-comp₁ : {r : Shape}{tv A B C : Set} {f : A → B} {g : B → C} {x : Expression A tv r}
                   → fmap-exp₁ (g ∘ f) x ≡ fmap-exp₁ g (fmap-exp₁ f x)
    map-fmap-alt-comp₁ : {r : Shape}{A B C tv : Set}{f : A → B}{g : B → C}{l : Alternatives A tv r}
                       → map-fmap-alt₁ (g ∘ f) l ≡ map-fmap-alt₁ g (map-fmap-alt₁ f l)
    fmap-alt-comp₁ {Nullary}   {x = ()}
    fmap-alt-comp₁ {Binary _ _}{x = ()}
    fmap-alt-comp₁ {Unary r}{f = f}{g}{x = _→′_ {n} (DC p) expr}
      = cong (_→′_ (DC p))
             (combine-composite′ ⦃ Monad.is-functor (PlusN-is-monad {n}) ⦄ {expr}
                                 fmap-exp₁
                                 (fmap-exp-comp₁ {f = pn.map f}{g = pn.map g}{expr}))
      where module pn = PlusN-f n
    map-fmap-alt-comp₁ {l = esac} = refl
    map-fmap-alt-comp₁ {Binary a b}{A}{B}{C}{tv}{l = x ∣ xs} = cong₂ _∣_ (fmap-alt-comp₁ {x = x}) (map-fmap-alt-comp₁)

    fmap-exp-comp₁ {x = Var (N x)} = cong Var (cong N (refl))
    fmap-exp-comp₁ {x = Var (DC x)} = refl
    fmap-exp-comp₁ {f = f}{g}{x = λ′ x }
      = cong λ′_ (combine-composite′ ⦃ Monad.is-functor (Ⓢ-is-monad) ⦄ {x}
                                     fmap-exp₁
                                     (fmap-exp-comp₁ {f = Ⓢ-f.map f}{g = Ⓢ-f.map g}{x}))
    fmap-exp-comp₁ {f = f}{g}{x = x · y } = cong₂ _·_ (fmap-exp-comp₁ {f = f}{g}{x})
                                                      (fmap-exp-comp₁ {f = f}{g}{y})
    fmap-exp-comp₁ {f = f}{g}{x = let₁ x in′ y }
      = cong₂ let₁_in′_ (fmap-exp-comp₁ {f = f}{g}{x})
                        (combine-composite′ ⦃ Monad.is-functor (Ⓢ-is-monad) ⦄ {y}
                                     fmap-exp₁
                                     (fmap-exp-comp₁ {f = Ⓢ-f.map f}{g = Ⓢ-f.map g}{y}))
    fmap-exp-comp₁ {f = f}{g}{x = let₂ x ∷ τ in′ y}
      = cong₂ (λ a b → let₂ a ∷ τ in′ b) (fmap-exp-comp₁ {f = f}{g}{x})
                                         (combine-composite′ ⦃ Monad.is-functor (Ⓢ-is-monad) ⦄ {y}
                                                             fmap-exp₁
                                                             (fmap-exp-comp₁ {f = Ⓢ-f.map f}
                                                                             {g = Ⓢ-f.map g}{y}))
    fmap-exp-comp₁ {f = f}{g}{x = let₃ n · x ∷ Q ⇒ τ in′ y}
      = cong₂ (λ a b → let₃ n · a ∷ Q ⇒ τ in′ b)
              (fmap-exp-comp₁ {f = f}{g}{x})
              (combine-composite′ ⦃ Monad.is-functor (Ⓢ-is-monad) ⦄ {y}
                                  fmap-exp₁
                                  (fmap-exp-comp₁ {f = Ⓢ-f.map f}{g = Ⓢ-f.map g}{y}))
    fmap-exp-comp₁ {f = f}{g}{x = case x of alts } = cong₂ case_of_
                                                           (fmap-exp-comp₁ {f = f}{g}{x})
                                                           map-fmap-alt-comp₁

  private
    fmap-alt-comp₂ : {A B C ev : Set}{r : Shape} {f : A → B} {g : B → C} {x : Alternative ev A r}
                   → fmap-alt₂ (g ∘ f) x ≡ fmap-alt₂ g (fmap-alt₂ f x)
    fmap-exp-comp₂ : {A B C ev : Set}{r : Shape} {f : A → B} {g : B → C} {x : Expression ev A r}
                   → fmap-exp₂ (g ∘ f) x ≡ fmap-exp₂ g (fmap-exp₂ f x)
    map-fmap-alt-comp₂ : ∀{r}{A B C ev : Set} {f : A → B} {g : B → C} {alts : Alternatives ev A r}
                       → map-fmap-alt₂ (g ∘ f) alts ≡ map-fmap-alt₂ g (map-fmap-alt₂ f alts)
    fmap-alt-comp₂ {x = _→′_ {n} p expr} = cong (_→′_ p) fmap-exp-comp₂
    map-fmap-alt-comp₂ {alts = esac} = refl
    map-fmap-alt-comp₂ {alts = x ∣ xs} = cong₂ _∣_ fmap-alt-comp₂ map-fmap-alt-comp₂

    fmap-exp-comp₂ {x = Var a} = refl
    fmap-exp-comp₂ {x = λ′ x } = cong λ′_ fmap-exp-comp₂
    fmap-exp-comp₂ {x = x · y } = cong₂ _·_ fmap-exp-comp₂ fmap-exp-comp₂
    fmap-exp-comp₂ {x = let₁ x in′ y } = cong₂ let₁_in′_ fmap-exp-comp₂ fmap-exp-comp₂
    fmap-exp-comp₂ {x = let₂ x ∷ τ in′ y } = cong₃ let₂_∷_in′_ fmap-exp-comp₂ Type-f.composite fmap-exp-comp₂
    fmap-exp-comp₂ {x = let₃ n · x ∷ Q ⇒ τ in′ y }
      = cong₄ (λ a b c d → let₃ n · a ∷ b ⇒ c in′ d)
              (combine-composite′ ⦃ pnf ⦄ fmap-exp₂ fmap-exp-comp₂)
              (combine-composite ⦃ qconstraint-is-functor ⦄ ⦃ pnf ⦄)
              (combine-composite ⦃ type-is-functor        ⦄ ⦃ pnf ⦄)
              fmap-exp-comp₂
      where pnf = Monad.is-functor (PlusN-is-monad {n})
    fmap-exp-comp₂ {x = case x of alts } = cong₂ case_of_ fmap-exp-comp₂ map-fmap-alt-comp₂

  alternatives-is-functor₁ : ∀{tv}{r} → Functor (λ x → Alternatives x tv r)
  alternatives-is-functor₁ = record { map = map-fmap-alt₁
                                    ; identity = map-fmap-alt-id₁
                                    ; composite = map-fmap-alt-comp₁
                                    }
  alternatives-is-functor₂ : ∀{ev}{r} → Functor (λ x → Alternatives ev x r)
  alternatives-is-functor₂ = record { map = map-fmap-alt₂
                                    ; identity = map-fmap-alt-id₂
                                    ; composite = map-fmap-alt-comp₂
                                    }
  alternative-is-functor₁ : ∀{tv}{r} → Functor (λ x → Alternative x tv r)
  alternative-is-functor₁ = record { map = fmap-alt₁
                                   ; identity = fmap-alt-id₁
                                   ; composite = λ { {x = x} → fmap-alt-comp₁ {x = x} }
                                   }
  alternative-is-functor₂ : ∀{ev}{r} → Functor (λ x → Alternative ev x r)
  alternative-is-functor₂ = record { map = fmap-alt₂
                                   ; identity = fmap-alt-id₂
                                   ; composite = fmap-alt-comp₂
                                   }
  expression-is-functor₁ : ∀{tv}{r} → Functor (λ x → Expression x tv r)
  expression-is-functor₁ {tv}{r} = record { map = fmap-exp₁
                                          ; identity = fmap-exp-id₁
                                          ; composite = λ {A}{B}{C}{f}{g}{x}
                                                        → fmap-exp-comp₁ {r}{tv}{A}{B}{C}{f}{g}{x}
                                          }
  expression-is-functor₂ : ∀{ev}{r} → Functor (λ x → Expression ev x r)
  expression-is-functor₂ = record { map = fmap-exp₂
                                  ; identity = fmap-exp-id₂
                                  ; composite = fmap-exp-comp₂
                                  }


------------------------------------------------------------------------
-- The Agda standard library
--
-- Pretty Printing
-- This module is based on Jean-Philippe Bernardy's functional pearl
-- "A Pretty But Not Greedy Printer"
------------------------------------------------------------------------

{-# OPTIONS --with-K #-}

module Text.Pretty.Core where

import Level

open import Data.Bool.Base using (Bool)
open import Data.Erased    as Erased using (Erased) hiding (module Erased)
open import Data.List.Base as List   using (List; []; _∷_)
open import Data.Nat.Base            using (ℕ; zero; suc; _+_; _⊔_; _≤_; z≤n)
open import Data.Nat.Properties
open import Data.Product as Prod using (_×_; _,_; uncurry; proj₁; proj₂)
import Data.Product.Relation.Unary.All as Allᴾ

open import Data.Tree.Binary as Tree using (Tree; leaf; node)
open import Data.Tree.Binary.Relation.Unary.All as Allᵀ using (leaf; node)
import Data.Tree.Binary.Relation.Unary.All.Properties as Allᵀₚ
import Data.Tree.Binary.Properties as Treeₚ

open import Data.Maybe.Base as Maybe using (Maybe; nothing; just; maybe′)
open import Data.Maybe.Relation.Unary.All as Allᴹ using (nothing; just)

open import Data.String.Base as String
open import Data.String.Unsafe as Stringₚ
open import Function.Base
open import Relation.Nullary using (Dec)
open import Relation.Unary using (IUniversal; _⇒_)
open import Relation.Binary.PropositionalEquality

open import Data.Refinement hiding (map)
import Data.Refinement.Relation.Unary.All as Allᴿ

------------------------------------------------------------------------
-- Block of text

-- Content is a representation of the first line and the middle of the block.
-- We use a tree rather than a list for the middle of the block so that we can
-- extend it with lines on the left and on the line for free. We will ultimately
-- render the block by traversing the tree left to right in a depth-first manner.

Content : Set
Content = Maybe (String × Tree String)

size : Content → ℕ
size = maybe′ (suc ∘ Tree.size ∘ proj₂) 0

All : ∀ {p} (P : String → Set p) → (Content → Set p)
All P = Allᴹ.All (Allᴾ.All P (Allᵀ.All P))

All≤ : ℕ → Content → Set
All≤ n = All (λ s → length s ≤ n)

record Block : Set where
  field
    height    : ℕ
    block     : [ xs ∈ Content ∣ size xs ≡ height ]
  -- last line
    lastWidth : ℕ
    last      : [ s ∈ String ∣ length s ≡ lastWidth ]
  -- max of all the widths
    maxWidth  : [ n ∈ ℕ ∣ lastWidth ≤ n × All≤ n (block .value) ]

------------------------------------------------------------------------
-- Raw string

text : String → Block
text s = record
  { height    = 0
  ; block     = nothing , ⦇ refl ⦈
  ; lastWidth = width
  ; last      = s , ⦇ refl ⦈
  ; maxWidth  = width , ⦇ (≤-refl , nothing) ⦈
  } where width = length s; open Erased

------------------------------------------------------------------------
-- Empty

empty : Block
empty = text ""

------------------------------------------------------------------------
-- Helper functions

node? : Content → String → Tree String → Content
node? (just (x , xs)) y ys = just (x , node xs y ys)
node? nothing         y ys = just (y , ys)

∣node?∣ : ∀ b y ys → size (node? b y ys)
                   ≡ size b + suc (Tree.size ys)
∣node?∣ (just (x , xs)) y ys = refl
∣node?∣ nothing         y ys = refl

≤-Content : ∀ {m n} {b : Content} → m ≤ n → All≤ m b → All≤ n b
≤-Content {m} {n} m≤n = Allᴹ.map (Prod.map step (Allᵀ.map step))

  where

  step : ∀ {p} → p ≤ m → p ≤ n
  step = flip ≤-trans m≤n

All≤-node? : ∀ {l m r n} →
             All≤ n l → length m ≤ n → Allᵀ.All (λ s → length s ≤ n) r →
             All≤ n (node? l m r)
All≤-node? nothing           py pys = just (py , pys)
All≤-node? (just (px , pxs)) py pys = just (px , node pxs py pys)

------------------------------------------------------------------------
-- Appending two documents

private
  module append (x y : Block) where

    module x = Block x
    module y = Block y

    blockx = x.block .value
    blocky = y.block .value

    widthx = x.maxWidth .value
    widthy = y.maxWidth .value

    lastx = x.last .value
    lasty = y.last .value

    height : ℕ
    height = (_+_ on Block.height) x y

    lastWidth : ℕ
    lastWidth = (_+_ on Block.lastWidth) x y

    pad : Maybe String
    pad with x.lastWidth
    ... | 0 = nothing
    ... | l = just (replicate l ' ')

    size-pad : maybe′ length 0 pad ≡ x.lastWidth
    size-pad with x.lastWidth
    ... | 0         = refl
    ... | l@(suc _) = length-replicate l

    indent : Maybe String → String → String
    indent = maybe′ _++_ id

    size-indent : ∀ ma str → length (indent ma str)
                ≡ maybe′ length 0 ma + length str
    size-indent nothing    str = refl
    size-indent (just pad) str = length-++ pad str

    indents : Maybe String → Tree String → Tree String
    indents = maybe′ (Tree.map ∘ _++_) id

    size-indents : ∀ ma t → Tree.size (indents ma t) ≡ Tree.size t
    size-indents nothing    t = refl
    size-indents (just pad) t = Treeₚ.size-map (pad ++_) t

    unfold-indents : ∀ ma t → indents ma t ≡ Tree.map (indent ma) t
    unfold-indents nothing    t = sym (Treeₚ.map-id t)
    unfold-indents (just pad) t = refl

    vContent : Content × String
    vContent with blocky
    ... | nothing        = blockx
                         , lastx ++ lasty
    ... | just (hd , tl) = node?
      {-,--------------,-}
      {-|-} blockx   {-|-}
      {-|-}          {-'---,-}    {-,------------------,-}
      {-|-} (lastx       {-|-} ++ {-|-}  hd)         {-|-}
      {-'------------------'-}    {-|-}              {-|-}
      (indents pad                {-|-}  tl)    {-,----'-}
      , indent pad                {-|-} lasty   {-|-}
                                  {-'-------------'-}

    vBlock = proj₁ vContent
    vLast  = proj₂ vContent

    isBlock : size blockx ≡ x.height → size blocky ≡ y.height →
              size vBlock ≡ height
    isBlock ∣x∣ ∣y∣ with blocky
    ... | nothing        = begin
      size blockx         ≡⟨ ∣x∣ ⟩
      x.height            ≡˘⟨ +-identityʳ x.height ⟩
      x.height + 0        ≡⟨ cong (_ +_) ∣y∣ ⟩
      x.height + y.height ∎ where open ≡-Reasoning
    ... | just (hd , tl) = begin
      ∣node∣                          ≡⟨ ∣node?∣ blockx middle rest ⟩
      ∣blockx∣ + suc (Tree.size rest) ≡⟨ cong ((size blockx +_) ∘′ suc) ∣rest∣ ⟩
      ∣blockx∣ + suc (Tree.size tl)   ≡⟨ cong₂ _+_ ∣x∣ ∣y∣ ⟩
      x.height + y.height             ∎ where

      open ≡-Reasoning
      ∣blockx∣ = size blockx
      middle   = lastx ++ hd
      rest     = indents pad tl
      ∣rest∣   = size-indents pad tl
      ∣node∣   = size (node? blockx middle rest)

    block : [ xs ∈ Content ∣ size xs ≡ height ]
    block .value = vBlock
    block .proof = ⦇ isBlock (Block.block x .proof) (Block.block y .proof) ⦈
      where open Erased

    isLastLine : length lastx ≡ x.lastWidth →
                 length lasty ≡ y.lastWidth →
                 length vLast ≡ lastWidth
    isLastLine ∣x∣ ∣y∣ with blocky
    ... | nothing        = begin
      length (lastx ++ lasty)       ≡⟨ length-++ lastx lasty ⟩
      length lastx + length lasty   ≡⟨ cong₂ _+_ ∣x∣ ∣y∣ ⟩
      x.lastWidth + y.lastWidth     ∎ where open ≡-Reasoning
    ... | just (hd , tl) = begin
      length (indent pad lasty)          ≡⟨ size-indent pad lasty ⟩
      maybe′ length 0 pad + length lasty ≡⟨ cong₂ _+_ size-pad ∣y∣ ⟩
      x.lastWidth + y.lastWidth          ∎ where open ≡-Reasoning

    last : [ s ∈ String ∣ length s ≡ lastWidth ]
    last .value = vLast
    last .proof = ⦇ isLastLine (Block.last x .proof) (Block.last y .proof) ⦈
      where open Erased

    vMaxWidth : ℕ
    vMaxWidth = widthx ⊔ (x.lastWidth + widthy)

    isMaxWidth₁ : y.lastWidth ≤ widthy → lastWidth ≤ vMaxWidth
    isMaxWidth₁ p = begin
      lastWidth            ≤⟨ +-monoʳ-≤ x.lastWidth p ⟩
      x.lastWidth + widthy ≤⟨ n≤m⊔n _ _ ⟩
      vMaxWidth            ∎ where open ≤-Reasoning

    isMaxWidth₂ : length lastx ≡ x.lastWidth →
                  x.lastWidth ≤ widthx →
                  All≤ widthx blockx →
                  All≤ widthy blocky →
                  All≤ vMaxWidth vBlock
    isMaxWidth₂ ∣x∣≡ ∣x∣≤ ∣xs∣ ∣ys∣ with blocky
    ... | nothing        = ≤-Content (m≤m⊔n _ _) ∣xs∣
    isMaxWidth₂ ∣x∣≡ ∣x∣≤ ∣xs∣ (just (∣hd∣ , ∣tl∣))
        | just (hd , tl) =
      All≤-node? (≤-Content (m≤m⊔n _ _) ∣xs∣)
                 middle
                 (subst (Allᵀ.All _) (sym $ unfold-indents pad tl)
                 $ Allᵀₚ.map⁺ (indent pad) (Allᵀ.map (indented _) ∣tl∣))
      where

      middle : length (lastx ++ hd) ≤ vMaxWidth
      middle = begin
        length (lastx ++ hd)     ≡⟨ length-++ lastx hd ⟩
        length lastx + length hd ≡⟨ cong (_+ _) ∣x∣≡ ⟩
        x.lastWidth + length hd  ≤⟨ +-monoʳ-≤ x.lastWidth ∣hd∣ ⟩
        x.lastWidth + widthy     ≤⟨ n≤m⊔n _ _ ⟩
        vMaxWidth                ∎ where open ≤-Reasoning

      indented : ∀ s → length s ≤ widthy →
                 length (indent pad s) ≤ vMaxWidth
      indented s ∣s∣ = begin
        length (indent pad s)          ≡⟨ size-indent pad s ⟩
        maybe′ length 0 pad + length s ≡⟨ cong (_+ _) size-pad ⟩
        x.lastWidth + length s         ≤⟨ +-monoʳ-≤ x.lastWidth ∣s∣ ⟩
        x.lastWidth + widthy           ≤⟨ n≤m⊔n (widthx) _ ⟩
        vMaxWidth                      ∎ where open ≤-Reasoning

    maxWidth : [ n ∈ ℕ ∣ lastWidth ≤ n × All≤ n vBlock ]
    maxWidth .value = vMaxWidth
    maxWidth .proof =
      ⦇ _,_ ⦇ isMaxWidth₁ (map proj₁ (Block.maxWidth y .proof)) ⦈
            ⦇ isMaxWidth₂ (Block.last x .proof)
                          (map proj₁ (Block.maxWidth x .proof))
                          (map proj₂ (Block.maxWidth x .proof))
                          (map proj₂ (Block.maxWidth y .proof))
            ⦈
      ⦈ where open Erased

infixl 4 _<>_
_<>_ : Block → Block → Block
x <> y = record { append x y }

------------------------------------------------------------------------
-- Flush (introduces a new line)

private
  module flush (x : Block) where

    module x = Block x
    blockx  = x.block .value
    lastx   = x.last .value
    widthx  = x.maxWidth .value
    heightx = x.height

    height    = suc heightx
    lastWidth = 0
    vMaxWidth = widthx

    last : [ s ∈ String ∣ length s ≡ lastWidth ]
    last = "" , ⦇ refl ⦈ where open Erased

    vContent = node? blockx lastx leaf

    isBlock : size blockx ≡ heightx → size vContent ≡  height
    isBlock ∣x∣ = begin
      size vContent   ≡⟨ ∣node?∣ blockx lastx leaf ⟩
      size blockx + 1 ≡⟨ cong (_+ 1) ∣x∣ ⟩
      heightx + 1     ≡⟨ +-comm heightx 1 ⟩
      height          ∎ where open ≡-Reasoning

    block : [ xs ∈ Content ∣ size xs ≡ height ]
    block .value = vContent
    block .proof = Erased.map isBlock $ Block.block x .proof

    maxWidth : [ n ∈ ℕ ∣ lastWidth ≤ n × All≤ n vContent ]
    maxWidth .value = widthx
    maxWidth .proof = map (z≤n ,_)
      ⦇ All≤-node? ⦇ proj₂ (Block.maxWidth x .proof) ⦈
                   ⦇ middle (Block.last x .proof) ⦇ proj₁ (Block.maxWidth x .proof) ⦈ ⦈
                   (pure leaf)
      ⦈ where

      open Erased

      middle : length lastx ≡ x.lastWidth → x.lastWidth ≤ vMaxWidth →
               length lastx ≤ vMaxWidth
      middle p q = begin
        length lastx ≡⟨ p ⟩
        x.lastWidth  ≤⟨ q ⟩
        vMaxWidth    ∎ where open ≤-Reasoning

flush : Block → Block
flush x = record { flush x }

------------------------------------------------------------------------
-- Other functions

render : Block → String
render x = unlines
  $ maybe′ (uncurry (λ hd tl → hd ∷ Tree.Infix.toList tl)) []
  $ node? (Block.block x .value) (Block.last x .value) leaf

valid : (width : ℕ) (b : Block) → Dec (Block.maxWidth b .value ≤ width)
valid width b = Block.maxWidth b .value ≤? width


------------------------------------------------------------------------
-- The two methods used to specify the grammars are (language)
-- equivalent (for acyclic graphs)
------------------------------------------------------------------------

open import Mixfix.Expr
open import Mixfix.Acyclic.PrecedenceGraph
  using (acyclic; precedence)

module Mixfix.Equivalence
  (g : PrecedenceGraphInterface.PrecedenceGraph acyclic)
  where

open import Function using (_∘_)
open import Data.List using (List; []; _∷_)
open import Data.List.NonEmpty using (_∷_)
open import Data.Nat using (ℕ; zero; suc; _+_)
open import Data.Product
open import Data.Vec using (Vec; []; _∷_)

open import TotalParserCombinators.Parser
open import TotalParserCombinators.Semantics
open import TotalParserCombinators.Semantics.Continuation as ContSem
import StructurallyRecursiveDescentParsing.Simplified as Simplified
import StructurallyRecursiveDescentParsing.Simplified.Semantics as SSem

open import Mixfix.Fixity
open import Mixfix.Operator
import Mixfix.Acyclic.Grammar
import Mixfix.Cyclic.Grammar
import Mixfix.Acyclic.Lemma
private
  module Acyclic = Mixfix.Acyclic.Grammar        g
  module ALemma  = Mixfix.Acyclic.Lemma          g
  module Cyclic  = Mixfix.Cyclic.Grammar acyclic g
open import Mixfix.Acyclic.Lib as ALib
open ALib.Semantics-⊕ renaming (_⊕_∈⟦_⟧·_ to _⊕_∈⟦_⟧A·_)
open import Mixfix.Cyclic.Lib as CLib renaming (⟦_⟧ to ⟦_⟧C)
open CLib.Semantics-⊕ renaming (_⊕_∈⟦_⟧·_ to _⊕_∈⟦_⟧C·_)

open PrecedenceCorrect acyclic g

⟦_⟧A : ∀ {R} → ALib.ParserProg R → Parser NamePart R []
⟦ p ⟧A = Simplified.⟦_⟧ (ALib.⟦_⟧ p)

module AcyclicToCyclic where

  mutual

    precs : ∀ ps {s s′ e} →
            e ⊕ s′ ∈⟦ Acyclic.precs ps ⟧A· s →
            e ⊕ s′ ∈⟦  Cyclic.precs ps ⟧C· s
    precs []       ()
    precs (p ∷ ps) (∣ˡ (<$> e∈p))  = ∣ˡ (<$> prec  p  e∈p)
    precs (p ∷ ps) (∣ʳ (<$> e∈ps)) = ∣ʳ (<$> precs ps e∈ps)

    prec : ∀ p {s s′ e} →
           e ⊕ s′ ∈⟦ Acyclic.prec p ⟧A· s →
           e ⊕ s′ ∈⟦  Cyclic.prec p ⟧C· s
    prec (precedence ops sucs) e∈ = prec′ e∈
      where
      p = precedence ops sucs

      module A = Acyclic.Prec ops sucs
      module C =  Cyclic.Prec p

      preRight : ∀ {s s′ f} →
                 f ⊕ s′ ∈⟦ A.preRight ⟧A· s →
                 f ⊕ s′ ∈⟦ C.preRight ⟧C· s
      preRight (∣ˡ (<$>      i∈)) = ∣ˡ (<$>              inner _ i∈)
      preRight (∣ʳ (<$> ↑∈ ⊛ i∈)) = ∣ʳ (<$> precs _ ↑∈ ⊛ inner _ i∈)

      preRight⁺ : ∀ {s s′ s″ fs e} →
                 fs          ⊕ s′ ∈⟦ A.preRight +       ⟧A· s  →
                 e           ⊕ s″ ∈⟦ Acyclic.precs sucs ⟧A· s′ →
                 A.appʳ fs e ⊕ s″ ∈⟦ C.preRight⁺        ⟧C· s
      preRight⁺ {fs = _ ∷ ._} (+-[] f∈)     ↑∈ = preRight f∈ ⊛∞ ∣ʳ (<$> precs _ ↑∈)
      preRight⁺ {fs = _ ∷ ._} (+-∷  f∈ fs∈) ↑∈ = preRight f∈ ⊛∞ ∣ˡ (<$> preRight⁺ fs∈ ↑∈)

      postLeft : ∀ {s s′ f} →
                 f ⊕ s′ ∈⟦ A.postLeft ⟧A· s →
                 f ⊕ s′ ∈⟦ C.postLeft ⟧C· s
      postLeft (∣ˡ (<$> i∈     )) = ∣ˡ (<$> inner _ i∈)
      postLeft (∣ʳ (<$> i∈ ⊛ ↑∈)) = ∣ʳ (<$> inner _ i∈ ⊛ precs _ ↑∈)

      postLeft⁺ : ∀ {s s′ s″ o fs} →
                  o                ⊕ s′ ∈⟦ similar <$> C.postLeft⁺
                                         ∣ tighter <$> C.p↑ ⟧C· s →
                  fs               ⊕ s″ ∈⟦ A.postLeft +       ⟧A· s′ →
                  ALemma.appˡ o fs ⊕ s″ ∈⟦ C.postLeft⁺        ⟧C· s
      postLeft⁺ {fs = _ ∷ ._} o∈ (+-[] f∈)     =                     <$> o∈ ⊛∞ postLeft f∈
      postLeft⁺ {fs = _ ∷ ._} o∈ (+-∷  f∈ fs∈) = postLeft⁺ (∣ˡ (<$> (<$> o∈ ⊛∞ postLeft f∈))) fs∈

      prec′ : ∀ {s s′ assoc} {e : ExprIn p assoc} →
              (-, e) ⊕ s′ ∈⟦ Acyclic.prec p ⟧A· s →
              (-, e) ⊕ s′ ∈⟦  Cyclic.prec p ⟧C· s
      prec′ (∥ˡ (<$> i∈))                      = ∥ˡ (<$> inner _ i∈)
      prec′ (∥ʳ (∥ˡ (<$> ↑₁∈ ⊛ i∈ ⊛ ↑₂∈)))     = ∥ʳ (∥ˡ (<$> precs _ ↑₁∈ ⊛ inner _ i∈ ⊛∞ precs _ ↑₂∈ ))
      prec′ (∥ʳ (∥ʳ (∥ˡ (<$> fs∈ ⊛ ↑∈))))      = ∥ʳ (∥ʳ (∥ˡ (preRight⁺ fs∈ ↑∈)))
      prec′ (∥ʳ (∥ʳ (∥ʳ (∥ˡ (<$> ↑∈ ⊛ fs∈))))) = ∥ʳ (∥ʳ (∥ʳ (∥ˡ (postLeft⁺ (∣ʳ (<$> precs _ ↑∈)) fs∈))))
      prec′ (∥ʳ (∥ʳ (∥ʳ (∥ʳ ()))))

    inner : ∀ {fix} (ops : List (∃ (Operator fix))) {s s′ i} →
            i ⊕ s′ ∈⟦ Acyclic.inner ops ⟧A· s →
            i ⊕ s′ ∈⟦  Cyclic.inner ops ⟧C· s
    inner []               ()
    inner ((_ , op) ∷ ops) (∣ˡ (<$> i∈)) = ∣ˡ (<$> inner′ _ i∈)
    inner ((_ , op) ∷ ops) (∣ʳ (<$> i∈)) = ∣ʳ (<$> inner ops i∈)

    inner′ : ∀ {arity} (ns : Vec NamePart (1 + arity)) {s s′ i} →
             i ⊕ s′ ∈⟦ Acyclic.expr between ns ⟧A· s →
             i ⊕ s′ ∈⟦  Cyclic.expr between ns ⟧C· s
    inner′ (n ∷ .[]) between-[]           = between-[]
    inner′ (n ∷ ns)  (between-∷ e∈g es∈g) =
      between-∷ (precs g e∈g) (inner′ ns es∈g)

module CyclicToAcyclic where

  mutual

    precs : ∀ ps {s s′ e} →
            e ⊕ s′ ∈⟦  Cyclic.precs ps ⟧C· s →
            e ⊕ s′ ∈⟦ Acyclic.precs ps ⟧A· s
    precs []       ()
    precs (p ∷ ps) (∣ˡ (<$> e∈p))  = ∣ˡ (<$> prec  p  e∈p)
    precs (p ∷ ps) (∣ʳ (<$> e∈ps)) = ∣ʳ (<$> precs ps e∈ps)

    prec : ∀ p {s s′ e} →
           e ⊕ s′ ∈⟦  Cyclic.prec p ⟧C· s →
           e ⊕ s′ ∈⟦ Acyclic.prec p ⟧A· s
    prec (precedence ops sucs) e∈ = prec′ e∈
      where
      p = precedence ops sucs

      module A = Acyclic.Prec ops sucs
      module C =  Cyclic.Prec p

      preRight : ∀ {s s′ f} →
                 f ⊕ s′ ∈⟦ C.preRight ⟧C· s →
                 f ⊕ s′ ∈⟦ A.preRight ⟧A· s
      preRight (∣ˡ (<$>      i∈)) = ∣ˡ (<$>              inner _ i∈)
      preRight (∣ʳ (<$> ↑∈ ⊛ i∈)) = ∣ʳ (<$> precs _ ↑∈ ⊛ inner _ i∈)

      preRight⁺ : ∀ {s s′ e} →
                  e ⊕ s′ ∈⟦ C.preRight⁺                    ⟧C· s →
                  e ⊕ s′ ∈⟦ A.appʳ <$> A.preRight + ⊛ A.p↑ ⟧A· s
      preRight⁺ (f∈ ⊛∞ ∣ˡ (<$> pre∈)) with preRight⁺ pre∈
      preRight⁺ (f∈ ⊛∞ ∣ˡ (<$> pre∈)) | <$> fs∈ ⊛ ↑∈ = <$> +-∷  (preRight f∈) fs∈ ⊛ ↑∈
      preRight⁺ (f∈ ⊛∞ ∣ʳ (<$> ↑∈)  ) =                <$> +-[] (preRight f∈)     ⊛ precs _ ↑∈

      postLeft : ∀ {s s′ f} →
                 f ⊕ s′ ∈⟦ C.postLeft ⟧C· s →
                 f ⊕ s′ ∈⟦ A.postLeft ⟧A· s
      postLeft (∣ˡ (<$> i∈     )) = ∣ˡ (<$> inner _ i∈)
      postLeft (∣ʳ (<$> i∈ ⊛ ↑∈)) = ∣ʳ (<$> inner _ i∈ ⊛ precs _ ↑∈)

      postLeft⁺ : ∀ {s s′ e} →
                  e ⊕ s′ ∈⟦ C.postLeft⁺                    ⟧C· s →
                  e ⊕ s′ ∈⟦ A.appˡ <$> A.p↑ ⊛ A.postLeft + ⟧A· s
      postLeft⁺ (<$> ∣ˡ (<$> post∈) ⊛∞ f∈) with postLeft⁺ post∈
      postLeft⁺ (<$> ∣ˡ (<$> post∈) ⊛∞ f∈)
        | _⊛_ {x = fs} (<$> ↑∈) fs∈ = AS.cast∈ (ALemma.appˡ-∷ʳ _ fs _) (
                                             <$>         ↑∈ ⊛ AS.+-∷ʳ fs∈ (postLeft f∈))
        where module AS = ALib.Semantics-⊕
      postLeft⁺ (<$> ∣ʳ (<$> ↑∈)    ⊛∞ f∈) = <$> precs _ ↑∈ ⊛    +-[]     (postLeft f∈)

      prec′ : ∀ {s s′ assoc} {e : ExprIn p assoc} →
              (-, e) ⊕ s′ ∈⟦  Cyclic.prec p ⟧C· s →
              (-, e) ⊕ s′ ∈⟦ Acyclic.prec p ⟧A· s
      prec′ (∥ˡ (<$> i∈))                   = ∥ˡ (<$> inner _ i∈)
      prec′ (∥ʳ (∥ˡ (<$> ↑₁∈ ⊛ i∈ ⊛∞ ↑₂∈))) = ∥ʳ (∥ˡ (<$> precs _ ↑₁∈ ⊛ inner _ i∈ ⊛ precs _ ↑₂∈ ))
      prec′ (∥ʳ (∥ʳ (∥ˡ pre∈)))             = ∥ʳ (∥ʳ (∥ˡ (preRight⁺ pre∈)))
      prec′ (∥ʳ (∥ʳ (∥ʳ (∥ˡ post∈))))       = ∥ʳ (∥ʳ (∥ʳ (∥ˡ (postLeft⁺ post∈))))
      prec′ (∥ʳ (∥ʳ (∥ʳ (∥ʳ ()))))

    inner : ∀ {fix} (ops : List (∃ (Operator fix))) {s s′ i} →
            i ⊕ s′ ∈⟦  Cyclic.inner ops ⟧C· s →
            i ⊕ s′ ∈⟦ Acyclic.inner ops ⟧A· s
    inner []               ()
    inner ((_ , op) ∷ ops) (∣ˡ (<$> i∈)) = ∣ˡ (<$> inner′ _ i∈)
    inner ((_ , op) ∷ ops) (∣ʳ (<$> i∈)) = ∣ʳ (<$> inner ops i∈)

    inner′ : ∀ {arity} (ns : Vec NamePart (1 + arity)) {s s′ i} →
             i ⊕ s′ ∈⟦  Cyclic.expr between ns ⟧C· s →
             i ⊕ s′ ∈⟦ Acyclic.expr between ns ⟧A· s
    inner′ (n ∷ .[]) between-[]           = between-[]
    inner′ (n ∷ ns)  (between-∷ e∈g es∈g) =
      between-∷ (precs g e∈g) (inner′ ns es∈g)

acyclicToCyclic
  : ∀ {e s} → e ∈ Simplified.⟦_⟧ Acyclic.expression · s →
              e ∈                 Cyclic.expression · s
acyclicToCyclic =
  ContSem.sound               ∘
  CLib.Semantics-⊕.sound      ∘
  AcyclicToCyclic.precs _     ∘
  ALib.Semantics-⊕.complete _ ∘
  SSem.⊕-complete             ∘
  SSem.complete _

cyclicToAcyclic
  : ∀ {e s} → e ∈                 Cyclic.expression · s →
              e ∈ Simplified.⟦_⟧ Acyclic.expression · s
cyclicToAcyclic =
  SSem.sound                  ∘
  SSem.⊕-sound                ∘
  ALib.Semantics-⊕.sound      ∘
  CyclicToAcyclic.precs _     ∘
  CLib.Semantics-⊕.complete _ ∘
  ContSem.complete


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

open import lib.Basics
open import lib.NType2
open import lib.types.Bool
open import lib.types.Empty
open import lib.types.Paths
open import lib.types.Pi
open import lib.types.Sigma

{-
This file contains various lemmas that rely on lib.types.Paths or
functional extensionality for pointed maps.
-}

module lib.types.Pointed where

{- Pointed maps -}

⊙app= : ∀ {i j} {X : Ptd i} {Y : Ptd j} {f g : X ⊙→ Y}
  → f == g → f ⊙∼ g
⊙app= {X = X} {Y = Y} p =
  app= (fst= p) , ↓-ap-in (_== pt Y) (λ u → u (pt X)) (snd= p)

-- function extensionality for pointed maps
⊙λ= : ∀ {i j} {X : Ptd i} {Y : Ptd j} {f g : X ⊙→ Y}
  → f ⊙∼ g → f == g
⊙λ= {g = g} (p , α) = pair= (λ= p)
  (↓-app=cst-in (↓-idf=cst-out α ∙ ap (_∙ snd g) (! (app=-β p _))))

⊙λ=' : ∀ {i j} {X : Ptd i} {Y : Ptd j} {f g : X ⊙→ Y}
  (p : fst f ∼ fst g)
  (α : snd f == snd g [ (λ y → y == pt Y) ↓ p (pt X) ])
  → f == g
⊙λ=' {g = g} = curry ⊙λ=

-- associativity of pointed maps
⊙∘-assoc-pt : ∀ {i j k} {A : Type i} {B : Type j} {C : Type k}
  {a₁ a₂ : A} (f : A → B) {b : B} (g : B → C) {c : C}
  (p : a₁ == a₂) (q : f a₂ == b) (r : g b == c)
  → ⊙∘-pt (g ∘ f) p (⊙∘-pt g q r) == ⊙∘-pt g (⊙∘-pt f p q) r
⊙∘-assoc-pt _ _ idp _ _ = idp

⊙∘-assoc : ∀ {i j k l} {X : Ptd i} {Y : Ptd j} {Z : Ptd k} {W : Ptd l}
  (h : Z ⊙→ W) (g : Y ⊙→ Z) (f : X ⊙→ Y)
  → ((h ⊙∘ g) ⊙∘ f) ⊙∼ (h ⊙∘ (g ⊙∘ f))
⊙∘-assoc (h , hpt) (g , gpt) (f , fpt) = (λ _ → idp) , ⊙∘-assoc-pt g h fpt gpt hpt

⊙∘-cst-l : ∀ {i j k} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  → (f : X ⊙→ Y) → (⊙cst :> (Y ⊙→ Z)) ⊙∘ f ⊙∼ ⊙cst
⊙∘-cst-l {Z = Z} f = (λ _ → idp) , ap (_∙ idp) (ap-cst (pt Z) (snd f))

⊙∘-cst-r : ∀ {i j k} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  → (f : Y ⊙→ Z) → f ⊙∘ (⊙cst :> (X ⊙→ Y)) ⊙∼ ⊙cst
⊙∘-cst-r {X = X} f = (λ _ → snd f) , ↓-idf=cst-in' idp

{- Pointed equivalences -}

-- Extracting data from an pointed equivalence
module _ {i j} {X : Ptd i} {Y : Ptd j} (⊙e : X ⊙≃ Y) where

  ⊙≃-to-≃ : de⊙ X ≃ de⊙ Y
  ⊙≃-to-≃ = fst (fst ⊙e) , snd ⊙e

  ⊙–> : X ⊙→ Y
  ⊙–> = fst ⊙e

  ⊙–>-pt = snd ⊙–>

  ⊙<– : Y ⊙→ X
  ⊙<– = is-equiv.g (snd ⊙e) , lemma ⊙e where
    lemma : {Y : Ptd j} (⊙e : X ⊙≃ Y) → is-equiv.g (snd ⊙e) (pt Y) == pt X
    lemma ((f , idp) , f-ise) = is-equiv.g-f f-ise (pt X)

  ⊙<–-pt = snd ⊙<–

  infix 120 _⊙⁻¹
  _⊙⁻¹ : Y ⊙≃ X
  _⊙⁻¹ = ⊙<– , is-equiv-inverse (snd ⊙e)

module _ {i j} {X : Ptd i} {Y : Ptd j} where

  ⊙<–-inv-l : (⊙e : X ⊙≃ Y) → ⊙<– ⊙e ⊙∘ ⊙–> ⊙e ⊙∼ ⊙idf _
  ⊙<–-inv-l ⊙e = <–-inv-l (⊙≃-to-≃ ⊙e) , ↓-idf=cst-in' (lemma ⊙e) where
    lemma : {Y : Ptd j} (⊙e : X ⊙≃ Y)
      → snd (⊙<– ⊙e ⊙∘ ⊙–> ⊙e) == is-equiv.g-f (snd ⊙e) (pt X)
    lemma ((f , idp) , f-ise) = idp

  ⊙<–-inv-r : (⊙e : X ⊙≃ Y) → ⊙–> ⊙e ⊙∘ ⊙<– ⊙e ⊙∼ ⊙idf _
  ⊙<–-inv-r ⊙e = <–-inv-r (⊙≃-to-≃ ⊙e) , ↓-idf=cst-in' (lemma ⊙e) where
    lemma : {Y : Ptd j} (⊙e : X ⊙≃ Y)
      → snd (⊙–> ⊙e ⊙∘ ⊙<– ⊙e) == is-equiv.f-g (snd ⊙e) (pt Y)
    lemma ((f , idp) , f-ise) = ∙-unit-r _ ∙ is-equiv.adj f-ise (pt X)

module _ {i j k} {X : Ptd i} {Y : Ptd j} {Z : Ptd k} (⊙e : X ⊙≃ Y) where

  post⊙∘-is-equiv : is-equiv (λ (k : Z ⊙→ X) → ⊙–> ⊙e ⊙∘ k)
  post⊙∘-is-equiv = is-eq (⊙–> ⊙e ⊙∘_) (⊙<– ⊙e ⊙∘_) (to-from ⊙e) (from-to ⊙e) where
    abstract
      to-from : ∀ {Y} (⊙e : X ⊙≃ Y) (k : Z ⊙→ Y) → ⊙–> ⊙e ⊙∘ (⊙<– ⊙e ⊙∘ k) == k
      to-from ((f , idp) , f-ise) (k , k-pt) = ⊙λ=' (f.f-g ∘ k) (↓-idf=cst-in' $ lemma k-pt)
        where
          module f = is-equiv f-ise
          lemma : ∀ {y₀} (k-pt : y₀ == f (pt X))
            → ⊙∘-pt f (⊙∘-pt f.g k-pt (f.g-f _)) idp == f.f-g y₀ ∙' k-pt
          lemma idp = ∙-unit-r _ ∙ f.adj _

      from-to : ∀ {Y} (⊙e : X ⊙≃ Y) (k : Z ⊙→ X) → ⊙<– ⊙e ⊙∘ (⊙–> ⊙e ⊙∘ k) == k
      from-to ((f , idp) , f-ise) (k , idp) = ⊙λ=' (f.g-f ∘ k) $ ↓-idf=cst-in' idp
        where module f = is-equiv f-ise

  post⊙∘-equiv : (Z ⊙→ X) ≃ (Z ⊙→ Y)
  post⊙∘-equiv = _ , post⊙∘-is-equiv

  pre⊙∘-is-equiv : is-equiv (λ (k : Y ⊙→ Z) → k ⊙∘ ⊙–> ⊙e)
  pre⊙∘-is-equiv = is-eq (_⊙∘ ⊙–> ⊙e) (_⊙∘ ⊙<– ⊙e) (to-from ⊙e) (from-to ⊙e) where
    abstract
      to-from : ∀ {Z} (⊙e : X ⊙≃ Y) (k : X ⊙→ Z) → (k ⊙∘ ⊙<– ⊙e) ⊙∘ ⊙–> ⊙e == k
      to-from ((f , idp) , f-ise) (k , idp) = ⊙λ=' (ap k ∘ f.g-f) $ ↓-idf=cst-in' $ ∙-unit-r _
        where module f = is-equiv f-ise

      from-to : ∀ {Z} (⊙e : X ⊙≃ Y) (k : Y ⊙→ Z) → (k ⊙∘ ⊙–> ⊙e) ⊙∘ ⊙<– ⊙e == k
      from-to ((f , idp) , f-ise) (k , idp) = ⊙λ=' (ap k ∘ f.f-g) $ ↓-idf=cst-in' $
        ∙-unit-r _ ∙ ap-∘ k f (f.g-f (pt X)) ∙ ap (ap k) (f.adj (pt X))
        where module f = is-equiv f-ise

  pre⊙∘-equiv : (Y ⊙→ Z) ≃ (X ⊙→ Z)
  pre⊙∘-equiv = _ , pre⊙∘-is-equiv

{- Pointed maps out of bool -}

-- intuition : [f true] is fixed and the only changable part is [f false].

⊙Bool→-to-idf : ∀ {i} {X : Ptd i}
  → ⊙Bool ⊙→ X → de⊙ X
⊙Bool→-to-idf (h , _) = h false

⊙Bool→-equiv-idf : ∀ {i} (X : Ptd i)
  → (⊙Bool ⊙→ X) ≃ de⊙ X
⊙Bool→-equiv-idf {i} X = equiv ⊙Bool→-to-idf g f-g g-f
  where
  g : de⊙ X → ⊙Bool ⊙→ X
  g x = Bool-rec (pt X) x , idp

  abstract
    f-g : ∀ x → ⊙Bool→-to-idf (g x) == x
    f-g x = idp

    g-f : ∀ H → g (⊙Bool→-to-idf H) == H
    g-f (h , hpt) = pair=
      (λ= lemma)
      (↓-app=cst-in $
        idp
          =⟨ ! (!-inv-l hpt) ⟩
        ! hpt ∙ hpt
          =⟨ ! (app=-β lemma true) |in-ctx (λ w → w ∙ hpt) ⟩
        app= (λ= lemma) true ∙ hpt
          =∎)
      where lemma : ∀ b → fst (g (h false)) b == h b
            lemma true = ! hpt
            lemma false = idp

⊙Bool→-equiv-idf-nat : ∀ {i j} {X : Ptd i} {Y : Ptd j} (F : X ⊙→ Y)
  → CommSquareEquiv
      (F ⊙∘_)
      (fst F)
      ⊙Bool→-to-idf
      ⊙Bool→-to-idf
⊙Bool→-equiv-idf-nat F = (comm-sqr λ _ → idp) ,
  snd (⊙Bool→-equiv-idf _) , snd (⊙Bool→-equiv-idf _)


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

open import Prelude
open import GSeTT.Syntax
open import GSeTT.Rules
open import GSeTT.Disks
open import GSeTT.Uniqueness-Derivations
open import GSeTT.Dec-Type-Checking
open import CaTT.Ps-contexts
open import CaTT.Relation
open import CaTT.Uniqueness-Derivations-Ps

module CaTT.Decidability-ps where
  ∅-is-not-ps : ∀ x A → ¬ (nil ⊢ps x # A)
  ∅-is-not-ps x A ∅⊢psx with psvar ∅⊢psx
  ... | var _ ()

  ps-carrier : ∀{Γ A B C x y z} → ((Γ :: (x , A)) :: (y , B)) ⊢ps z # C → ((Σ Pre-Tm λ a → (⇒ A a (Var x) == B)) × (x == length Γ)) × (y == S (length Γ))
  ps-carrier (psd Γ⊢ps) = ps-carrier Γ⊢ps
  ps-carrier (pse _ idp idp idp idp idp) = ((_ , idp) , idp) , idp

  Γ+⊢ps→Γ⊢ps : ∀ {Γ x A a y B z} → ((Γ :: (y , B)) :: (z , ⇒ B (Var a) (Var y))) ⊢ps x # A → Γ ⊢ps a # B
  Γ+⊢ps→Γ⊢ps (psd Γ⊢ps) = Γ+⊢ps→Γ⊢ps Γ⊢ps
  Γ+⊢ps→Γ⊢ps (pse Γ⊢ps idp idp p idp idp) = transport ((=Var (snd (fst (=⇒ p)))) ^) Γ⊢ps

  𝔻0-var : ∀ x A → Pre-𝔻 0 ⊢t (Var x) # A → x == 0
  𝔻0-var x A (var _ (inr (idp , _))) = idp

  𝔻0-type : ∀ x A → Pre-𝔻 0 ⊢t (Var x) # A → A == ∗
  𝔻0-type x A (var _ (inr (idp , idp))) = idp

  ⊢psx→⊢ps : ∀ {Γ x A} → Γ ⊢ps x # A → Γ ⊢ps
  ⊢psx→⊢ps {A = ∗} Γ⊢psx = ps Γ⊢psx
  ⊢psx→⊢ps {A = ⇒ A (Var _) (Var _)} Γ⊢psx = ⊢psx→⊢ps (psd Γ⊢psx)

  dec-⊢-dim : ∀ {Γ} → Γ ⊢C → (n : ℕ) → dec (Σ (ℕ × Pre-Ty) (λ (x , A) → (Γ ⊢t (Var x) # A) × (dim A == n)))
  dec-⊢-dim {nil} _ n = inr λ{((x , A) , ((var _ ())  , _))}
  dec-⊢-dim {Γ :: (x , A)} Γ+⊢@(cc Γ⊢ _ _) n with eqdecℕ (dim A) n
  ... | inl idp = inl ((x , A) , (var Γ+⊢ (inr (idp , idp)) , idp))
  ... | inr dimA≠n with dec-⊢-dim Γ⊢ n
  ... | inl ((y , B) , ((var _ y∈Γ) , dimB=n)) = inl ((y , B ), (var Γ+⊢ (inl y∈Γ) , dimB=n))
  ... | inr ¬∃y = inr λ{((z , C) , ((var _ (inl z∈Γ)) , idp)) → ¬∃y ((z , C) , (var Γ⊢ z∈Γ , idp));
                        ((z , C) , ((var _ (inr (idp , idp))) , idp)) → dimA≠n idp}

  dim-pse : ∀ {Γ x A y B} → ((Γ :: (x , A)) :: (y , B)) ⊢ps y # B → (∀ z C → ((Γ :: (x , A)) :: (y , B)) ⊢ps z # C → dim C ≤ dim B)
  dim-pse Γ⊢psy z C (psd Γ⊢psf) = Sn≤m→n≤m (dim-pse Γ⊢psy _ _ Γ⊢psf)
  dim-pse Γ⊢psy z C (pse Γ⊢psz idp idp idp idp idp) = n≤n _

  private
    tgt-⟿ : ∀ {Γ x A y z a} → Γ ⊢t Var x # ⇒ A (Var y) (Var z) → Γ , x ⟿ a → z ≠ a → Γ , z ⟿ a
    tgt-⟿ Γ⊢x (∂⁺⟿ Γ⊢'x) z≠a with unique-type Γ⊢x Γ⊢'x idp
    ... | idp = ⊥-elim (z≠a idp)
    tgt-⟿ Γ⊢x (x⟿∂⁺ Γ⊢'x x⟿a) z≠a with unique-type Γ⊢x Γ⊢'x idp
    ... | idp = x⟿a

  ⇒≠∗ : ∀ {A t u} → ⇒ A t u ≠ ∗
  ⇒≠∗ ()

  dec-Σ⊢T : ∀ {Γ t} → dec (Σ Pre-Ty λ A → Γ ⊢t t # A)
  dec-Σ⊢T {nil} {Var x} = inr λ{(_ , var _ ())}
  dec-Σ⊢T {Γ+@(Γ :: (y , B))} {Var x} with dec-⊢C Γ+ | eqdecℕ x y
  ... | inr Γ+⊬ | _ = inr λ (A , Γ+⊢x) → Γ+⊬ (Γ⊢t:A→Γ⊢ Γ+⊢x)
  ... | inl Γ+⊢ | inl idp = inl (B , var Γ+⊢ (inr (idp , idp)))
  ... | inl Γ+⊢ | inr x≠y with dec-Σ⊢T {Γ} {Var x}
  ... | inl (A , Γ⊢x)= inl (A , wkt Γ⊢x Γ+⊢)
  ... | inr Γ⊬x = inr λ{ (B , var _ (inr (idp , idp))) → x≠y idp;
                         (B , var (cc Γ⊢ _ _) (inl x#B∈Γ)) → Γ⊬x (B , var Γ⊢ x#B∈Γ)}

  dec-⟿ : ∀ Γ x y → dec (Γ , x ⟿ y)
  dec-⟿-aux : ∀ Γ x A y → Γ ⊢t (Var x) # A → dec (Γ , x ⟿ y)
  dec-⟿-aux Γ x ∗ y Γ⊢x = inr λ{(∂⁺⟿ Γ⊢'x) → ⇒≠∗ (unique-type Γ⊢'x Γ⊢x idp) ; (x⟿∂⁺ Γ⊢'x _) → ⇒≠∗ (unique-type Γ⊢'x Γ⊢x idp)}
  dec-⟿-aux Γ x (⇒ A (Var _) (Var z)) y Γ⊢x with eqdecℕ z y | dec-⟿-aux Γ z A y (Γ⊢tgt (Γ⊢t:A→Γ⊢A Γ⊢x))
  ... | inl idp | _ = inl (∂⁺⟿ Γ⊢x)
  ... | inr _ | inl x⟿z = inl (x⟿∂⁺ Γ⊢x x⟿z)
  ... | inr z≠y | inr ¬x⟿z = inr λ x⟿y → ¬x⟿z (tgt-⟿ Γ⊢x x⟿y z≠y)
  dec-⟿ Γ x y with dec-Σ⊢T {Γ} {Var x}
  ... | inr Γ⊬x = inr λ{(∂⁺⟿ Γ⊢x) → Γ⊬x (_ , Γ⊢x) ; (x⟿∂⁺ Γ⊢x _) → Γ⊬x (_ , Γ⊢x)}
  ... | inl (A , Γ⊢x) = dec-⟿-aux Γ x A y Γ⊢x

  Γ⊢psx-dim-⟿ : ∀ {Γ x A y B} → Γ ⊢ps x # A → Γ ⊢ps y # B → dim B < dim A → Γ , x ⟿ y
  Γ⊢psx-dim-⟿ Γ⊢psx pss dB<dA with 𝔻0-type _ _ (psvar Γ⊢psx)
  ... | idp = ⊥-elim (Sn≰n _ dB<dA)
  Γ⊢psx-dim-⟿ {Γ} {x} {A} {y} {B} Γ⊢psx (psd Γ⊢psf) dB<dA with eqdecℕ (S (dim B)) (dim A)
  ... | inl SdB=dA = transport {B = λ a → Γ , a ⟿ y} (Γ⊢psx-dim Γ⊢psf Γ⊢psx SdB=dA) (∂⁺⟿ (psvar Γ⊢psf))
  ... | inr SdB≠dA = T⟿ (Γ⊢psx-dim-⟿ Γ⊢psx Γ⊢psf (≤×≠→< dB<dA SdB≠dA)) (∂⁺⟿ (psvar Γ⊢psf))
  Γ⊢psx-dim-⟿ Γ⊢psx Γ⊢psy@(pse _ idp idp idp idp idp) dB<dA = ⊥-elim (Sn≰n _ (≤T dB<dA (dim-pse Γ⊢psy _ _ Γ⊢psx)))

  ⟿→psx : ∀ {Γ x A y} → Γ ⊢ps x # A → Γ , x ⟿ y → Σ Pre-Ty λ B → Γ ⊢ps y # B
  ⟿→psx Γ⊢psx (∂⁺⟿ Γ⊢x) with unique-type (psvar Γ⊢psx) Γ⊢x idp
  ... | idp = _ , (psd Γ⊢psx)
  ⟿→psx Γ⊢psx (x⟿∂⁺ Γ⊢x z⟿y) with unique-type (psvar Γ⊢psx) Γ⊢x idp
  ... | idp = ⟿→psx (psd Γ⊢psx) z⟿y

  pse-max : ∀ {Γ x A y B} → ((Γ :: (x , A)) :: (y , B)) ⊢ps y # B → (∀ z C → ((Γ :: (x , A)) :: (y , B)) ⊢ps z # C → (y == z) + (((Γ :: (x , A)) :: (y , B)) , y ⟿ z))
  pse-max {B = B} Γ⊢psy z C Γ⊢psz with ℕ-trichotomy (dim B) (dim C)
  ... | inl (inl dimB<dimC) = ⊥-elim (Sn≰n _ (≤T dimB<dimC (dim-pse Γ⊢psy _ _ Γ⊢psz)) )
  ... | inl (inr dimC<dimB) = inr (Γ⊢psx-dim-⟿ Γ⊢psy Γ⊢psz dimC<dimB)
  ... | inr dimB=dimC = inl (Γ⊢psx-dim Γ⊢psy Γ⊢psz dimB=dimC)

  ⊢psx→max : ∀ {Γ x A} → Γ ⊢ps x # A → (Σ (ℕ × Pre-Ty) λ (y , B) → (Γ ⊢ps y # B) × (∀ z C → Γ ⊢ps z # C → (y == z) + (Γ , y ⟿ z)))
  ⊢psx→max pss = (0 , ∗) , (pss , λ z C 𝔻0⊢psx → inl (𝔻0-var z C (psvar 𝔻0⊢psx) ^))
  ⊢psx→max (psd Γ⊢psx) = ⊢psx→max Γ⊢psx
  ⊢psx→max {((Γ :: (_ , B)) :: (_ , C))} {x} {A} Γ+⊢ps@(pse Γ⊢psx idp idp idp idp idp) = (S (length Γ) , C) , (Γ+⊢ps , pse-max Γ+⊢ps)

  -- TODO : move case_of_ in Prelude
  case_of_ : ∀ {a b} {A : Set a} {B : Set b} → A → (A → B) → B
  case x of f = f x

  ill-formed-1st-var : ∀ {y A} → ¬ ((nil :: (S y , A)) ⊢C)
  ill-formed-1st-var (cc _ _ ())

  ill-formed-1st-type : ∀ {y A a b} → ¬ ((nil :: (y , ⇒ A a b)) ⊢C)
  ill-formed-1st-type (cc _ (ar (var _ ()) _) _)

  dec-⊢psx-max : ∀ {Γ} → dec (Σ (ℕ × Pre-Ty) λ (x , A) → (Γ ⊢ps x # A) × (∀ y B → Γ ⊢ps y # B → (x == y) + (Γ , x ⟿ y)))
  dec-⊢psx-max {nil} = inr λ ((x , A) , (nil⊢psx , _ )) → ∅-is-not-ps _ _ nil⊢psx
  dec-⊢psx-max {nil :: (O , ∗)} = inl ((0 , ∗) , (pss , λ y B  𝔻0⊢psy → inl (𝔻0-var _ _ (psvar 𝔻0⊢psy) ^)))
  dec-⊢psx-max {nil :: (O , ⇒ _ _ _)} = inr λ ((x , A) , (Γ⊢psx , _ )) → ill-formed-1st-type (psv Γ⊢psx)
  dec-⊢psx-max {nil :: (S z , C)} = inr λ ((x , A) , (Γ⊢psx , _ )) → ill-formed-1st-var (psv Γ⊢psx)
  dec-⊢psx-max {(Γ :: (z , C)) :: (f , ∗)} = inr λ ((x , A) , (Γ+⊢psx , _)) → ⇒≠∗ (snd (fst (fst (ps-carrier Γ+⊢psx))))
  dec-⊢psx-max {(Γ :: (z , C)) :: (f , ⇒ D (Var a) (Var b))} with eqdec-PreTy C D | eqdecℕ z (length Γ) | eqdecℕ (length Γ) b | eqdecℕ f (S (length Γ))
  ... | inr C≠D | _ | _ | _  = inr λ ((x , A) , (Γ+⊢psx , _)) → C≠D (fst (fst (=⇒ (snd (fst (fst (ps-carrier Γ+⊢psx)))))))
  ... | inl idp | inr z≠l | _ | _  = inr λ ((x , A) , (Γ+⊢psx , _)) → z≠l (snd (fst (ps-carrier Γ+⊢psx)))
  ... | inl idp | inl idp | inr l≠b | _  = inr λ ((x , A) , (Γ+⊢psx , _)) → l≠b (=Var (snd (=⇒ (snd (fst (fst (ps-carrier Γ+⊢psx)))))))
  ... | inl idp | inl idp | inl idp | inr f≠Sl  = inr λ ((x , A) , (Γ+⊢psx , _)) → f≠Sl (snd (ps-carrier Γ+⊢psx))
  ... | inl idp | inl idp | inl idp | inl idp with dec-⊢psx-max {Γ}
  ... | inr ¬Γ⊢ps = inr λ ((x , A) , (Γ+⊢psx , _)) → ¬Γ⊢ps (⊢psx→max (Γ+⊢ps→Γ⊢ps Γ+⊢psx))
  ... | inl ((c , E) , (Γ⊢psc , cmax)) with eqdecℕ c a | eqdec-PreTy E C
  ... | inl idp | inl idp = let Γ+⊢ps = pse Γ⊢psc idp idp idp idp idp in inl ((S (length Γ) , ⇒ C (Var a) (Var (length Γ))) , (Γ+⊢ps , pse-max Γ+⊢ps))
  ... | inl idp | inr E≠C = inr λ ((x , A) , (Γ+⊢psx , _)) → E≠C (unique-type (psvar Γ⊢psc) (psvar (Γ+⊢ps→Γ⊢ps Γ+⊢psx)) idp)
  ... | inr c≠a | _ with dec-⟿ Γ c a
  ... | inr ¬c⟿a = inr λ ((x , A) , (Γ+⊢psx , _)) → case cmax _ _ (Γ+⊢ps→Γ⊢ps Γ+⊢psx) of λ{(inl c=a) → c≠a c=a; (inr c⟿a) → ¬c⟿a c⟿a }
  ... | inl c⟿a with (⟿→psx Γ⊢psc c⟿a)
  ... | (F , Γ⊢psa) with eqdec-PreTy F C
  ... | inl idp = let Γ+⊢ps = pse Γ⊢psa idp idp idp idp idp in inl ((S (length Γ) , ⇒ C (Var a) (Var (length Γ))) , (Γ+⊢ps  , pse-max Γ+⊢ps))
  ... | inr F≠C = inr λ ((x , A) , (Γ+⊢psx , _)) → F≠C (unique-type (psvar Γ⊢psa) (psvar (Γ+⊢ps→Γ⊢ps Γ+⊢psx)) idp)

  dec-⊢ps : ∀ Γ → dec(Γ ⊢ps)
  dec-⊢ps Γ with dec-⊢psx-max {Γ}
  ... | inl ((x , A) , (Γ⊢psx , _)) = inl (⊢psx→⊢ps Γ⊢psx)
  ... | inr ¬Γ⊢psx = inr λ {(ps Γ⊢psy) → ¬Γ⊢psx (⊢psx→max Γ⊢psy)}


{-# OPTIONS --safe #-}
{-
Builds bismulation for the cumulative hierarchy and shows that it
is equivalent to equality though it lives in a lower universe.
-}

module Cubical.HITs.CumulativeHierarchy.Properties where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Univalence
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Function
open import Cubical.Foundations.Transport
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Structure
open import Cubical.Functions.Embedding
open import Cubical.Functions.Logic as L
open import Cubical.Data.Sigma
open import Cubical.HITs.PropositionalTruncation as P hiding (elim; elim2)
open import Cubical.HITs.SetQuotients as Q using (_/_; setQuotUniversal; eq/; squash/)

open import Cubical.HITs.CumulativeHierarchy.Base
  renaming (elim to elimInternal)
import Cubical.HITs.PropositionalTruncation.Monad as PropMonad

private
  variable
    ℓ ℓ' : Level
    X Y : Type ℓ
    a b : V ℓ

infix 4 _≊_
infix 7 _∈ₛ_ _⊆_ _⊆ₛ_

------------
-- "bisimulation"
-----------

-- bisimulation is needed to define a quotient in the correct universe when
-- implementing the map : V ℓ → Σ[ X : Type ℓ ] (X → V ℓ)
-- Quotienting by Path (V ℓ) or via eqImage would lead to X : Type (ℓ-suc ℓ)

_∼_ : (s t : V ℓ) → hProp ℓ
_∼_ = elim2 eliminator where
  goalProp : (X : Type ℓ) (ix : X → V ℓ)
           → (Y : Type ℓ) (iy : Y → V ℓ)
           → (rec : X → Y → hProp ℓ)
           → hProp ℓ
  goalProp X ix Y iy rec = (∀[ a ∶ X ] ∃[ b ∶ Y ] rec a b) ⊓ (∀[ b ∶ Y ] ∃[ a ∶ X ] rec a b)

  ⇔-swap : X ⊓′ Y → Y ⊓′ X
  ⇔-swap (p , q) = (q , p)

  open PropMonad
  lemma : {X₁ X₂ Y : Type ℓ} {ix₁ : X₁ → V ℓ} {ix₂ : X₂ → V ℓ} (iy : Y → V ℓ)
        → {rec₁ : X₁ → Y → hProp ℓ} {rec₂ : X₂ → Y → hProp ℓ}
        → (rec₁→₂ : (x₁ : X₁) → ∃[ (x₂ , p) ∈ fiber ix₂ (ix₁ x₁) ] rec₂ x₂ ≡ rec₁ x₁)
        → (rec₂→₁ : (x₂ : X₂) → ∃[ (x₁ , p) ∈ fiber ix₁ (ix₂ x₂) ] rec₁ x₁ ≡ rec₂ x₂)
        → ⟨ goalProp X₁ ix₁ Y iy rec₁ ⇒ goalProp X₂ ix₂ Y iy rec₂ ⟩
  lemma _ rec₁→₂ rec₂→₁ (X₁→Y , Y→X₁) =
    (λ x₂ → do ((x₁ , c_) , xr₁) ← rec₂→₁ x₂
               (y , yr) ← X₁→Y x₁
               ∣ y , subst fst (λ i → xr₁ i y) yr ∣₁
    ) ,
    (λ y → do (x₁ , xr₁) ← Y→X₁ y
              ((x₂ , _) , xr₂) ← rec₁→₂ x₁
              ∣ x₂ , subst fst (λ i → xr₂ (~ i) y) xr₁ ∣₁
    )

  open Elim2Set
  eliminator : Elim2Set (λ _ _ → isSetHProp)
  ElimSett2 eliminator = goalProp
  ElimEqSnd eliminator X ix Y₁ Y₂ iy₁ iy₂ eq rec₁ rec₂ rec₁→₂ rec₂→₁ =
    ⇔toPath (⇔-swap ∘ lemma ix rec₁→₂ rec₂→₁ ∘ ⇔-swap)
            (⇔-swap ∘ lemma ix rec₂→₁ rec₁→₂ ∘ ⇔-swap)
  ElimEqFst eliminator X₁ X₂ ix₁ ix₂ eq Y iy rec₁ rec₂ rec₁→₂ rec₂→₁ =
    ⇔toPath (lemma iy rec₁→₂ rec₂→₁)
            (lemma iy rec₂→₁ rec₁→₂)

_≊_ : (s t : V ℓ) → Type ℓ
s ≊ t = ⟨ s ∼ t ⟩

∼refl : (a : V ℓ) → a ≊ a
∼refl = elimProp (λ a → isProp⟨⟩ (a ∼ a))
                 (λ X ix rec → (λ x → ∣ x , rec x ∣₁) , (λ x → ∣ x , rec x ∣₁))

-- keep in mind that the left and right side here live in different universes
identityPrinciple : (a ≊ b) ≃ (a ≡ b)
identityPrinciple {a = a} {b = b} =
  isoToEquiv (iso from into (λ _ → setIsSet _ _ _ _) (λ _ → isProp⟨⟩ (a ∼ b) _ _))
  where
  open PropMonad

  eqImageXY : {X Y : Type ℓ} {ix : X → V ℓ} {iy : Y → V ℓ} → (∀ x y → ⟨ ix x ∼ iy y ⟩ → ix x ≡ iy y)
            → ⟨ sett X ix ∼ sett Y iy ⟩ → eqImage ix iy
  eqImageXY rec rel = (λ x → do (y , y∼x) ← fst rel x ; ∣ y , sym (rec _ _ y∼x) ∣₁)
                    , (λ y → do (x , x∼y) ← snd rel y ; ∣ x ,      rec _ _ x∼y  ∣₁)

  from : a ≊ b → a ≡ b
  from = elimProp propB eliminator a b where
    prop∼ : {a : V ℓ} → ∀ b → isProp (a ≊ b → a ≡ b)
    prop∼ {a = a} b = isPropΠ λ _ → setIsSet a b
    propB : (a : V ℓ) → isProp (∀ b → a ≊ b → a ≡ b)
    propB a = isPropΠ prop∼
    eliminator :
      ∀ (X : Type _) (ix : X → V _)
      → ((x : X) (b₁ : V _) → ix x ≊ b₁ → ix x ≡ b₁)
      → (b₁ : V _) → sett X ix ≊ b₁ → sett X ix ≡ b₁
    eliminator X ix rec =
      elimProp prop∼ λ Y iy _ → seteq X Y ix iy ∘ eqImageXY (λ x y → rec x (iy y))

  into : a ≡ b → a ≊ b
  into = J (λ b _ → a ≊ b) (∼refl a)

------------
-- Monic presentation of sets
-----------

-- like fiber, but in a lower universe
repFiber : (f : X → V ℓ) (b : V ℓ) → Type _
repFiber f b = Σ[ a ∈ _ ] f a ≊ b

repFiber≃fiber : (f : X → V ℓ) (b : V ℓ) → repFiber f b ≃ fiber f b
repFiber≃fiber f b = Σ-cong-equiv (idEquiv _) (λ _ → identityPrinciple)

-- projecting out a representing type together with the embedding
MonicPresentation : (a : V ℓ) → Type (ℓ-suc ℓ)
MonicPresentation {ℓ} a =  Σ[ (X , ix , _) ∈ Embedding (V ℓ) ℓ ] (a ≡ sett X ix)

isPropMonicPresentation : (a : V ℓ) → isProp (MonicPresentation a)
isPropMonicPresentation a ((X₁ , ix₁ , isEmb₁) , p) ((X₂ , ix₂ , isEmb₂) , q) =
  ΣPathP ( equivFun (EmbeddingIP _ _) (fiberwise1 , fiberwise2)
         , isProp→PathP (λ i → setIsSet a _) p q)
  where
  open PropMonad
  fiberwise1 : ∀ b → fiber ix₁ b → fiber ix₂ b
  fiberwise1 b fbx₁ =
    proof (_ , isEmbedding→hasPropFibers isEmb₂ b)
    by subst (λ A → ⟨ b ∈ A ⟩) (sym p ∙ q) ∣ fbx₁ ∣₁

  fiberwise2 : ∀ b → fiber ix₂ b → fiber ix₁ b
  fiberwise2 b fbx₂ =
    proof (_ , isEmbedding→hasPropFibers isEmb₁ b)
    by subst (λ A → ⟨ b ∈ A ⟩) (sym q ∙ p) ∣ fbx₂ ∣₁

sett-repr : (X : Type ℓ) (ix : X → V ℓ) → MonicPresentation (sett X ix)
sett-repr {ℓ} X ix = (Rep , ixRep , isEmbIxRep) , seteq X Rep ix ixRep eqImIxRep where
  Kernel : X → X → Type ℓ
  Kernel x y = ix x ≊ ix y
  Rep : Type ℓ
  Rep = X / Kernel
  ixRep : Rep → V ℓ
  ixRep = invEq (setQuotUniversal setIsSet) (ix , λ _ _ → equivFun identityPrinciple)
  isEmbIxRep : isEmbedding ixRep
  isEmbIxRep = hasPropFibers→isEmbedding propFibers where
    propFibers : ∀ y → (a b : Σ[ p ∈ Rep ] (ixRep p ≡ y)) → a ≡ b
    propFibers y (p₁ , m) (p₂ , n) =
      ΣPathP {B = λ _ p → ixRep p ≡ y} (goal , isProp→PathP (λ _ → setIsSet _ _) _ _)
      where
      lemma : ∀ {p₁ p₂} → (p : ixRep Q.[ p₁ ] ≡ y) (q : ixRep Q.[ p₂ ] ≡ y) → Kernel p₁ p₂
      lemma m n = invEquiv identityPrinciple .fst (m ∙ sym n)
      prop₁ : ∀ p₁ → isProp ((ixRep p₁ ≡ y) → p₁ ≡ p₂)
      prop₁ p₁ = isPropΠ λ eq → squash/ p₁ p₂
      prop₂ : ∀ {p₁} p₂ → isProp ((ixRep p₂ ≡ y) → Q.[ p₁ ] ≡ p₂)
      prop₂ {p₁} p₂ = isPropΠ λ eq → squash/ Q.[ p₁ ] p₂
      goal : p₁ ≡ p₂
      goal = Q.elimProp prop₁ (λ p₁ m → Q.elimProp prop₂ (λ p₂ n → eq/ p₁ p₂ (lemma m n)) p₂ n) p₁ m

  eqImIxRep : eqImage ix ixRep
  eqImIxRep = (λ x → ∣ Q.[ x ] , refl ∣₁) , Q.elimProp (λ _ → P.squash₁) (λ b → ∣ b , refl ∣₁)

data DeepMonicPresentation (a : V ℓ) : Type (ℓ-suc ℓ) where
  dmp : (mp@((_ , ix , _) , _) : MonicPresentation a)
      → (rec : ∀ x → DeepMonicPresentation (ix x))
      → DeepMonicPresentation a

isPropDeepMonicPresentation : (a : V ℓ) → isProp (DeepMonicPresentation a)
isPropDeepMonicPresentation a (dmp mx rx) (dmp my ry) i = dmp (mx≡my i) (recprop i) where
  mx≡my : mx ≡ my
  mx≡my = isPropMonicPresentation a mx my
  recprop : PathP (λ i → (x : mx≡my i .fst .fst) → DeepMonicPresentation (mx≡my i .fst .snd .fst x)) rx ry
  recprop = toPathP (funExt λ x → isPropDeepMonicPresentation _ _ _)

V-deeprepr : (a : V ℓ) → DeepMonicPresentation a
V-deeprepr = elimProp isPropDeepMonicPresentation λ X ix rec → dmp (sett-repr X ix) (Q.elimProp (λ _ → isPropDeepMonicPresentation _) rec)

V-repr : (a : V ℓ) → MonicPresentation a
-- "Cannot eliminate fibrant type DeepMonicPresentation a
--  unless target type is also fibrant"
-- V-repr = let (dmp mp _) = (V-deeprepr a) in mp
V-repr a = case (V-deeprepr a) return (λ _ → MonicPresentation a) of λ { (dmp mp _) → mp }

private
  MonicDataF : Type (ℓ-suc ℓ) → Type (ℓ-suc ℓ)
  MonicDataF {ℓ} T = Embedding T ℓ

  V-fixpoint : V ℓ ≃ MonicDataF (V ℓ)
  V-fixpoint {ℓ} =
    V ℓ ≃⟨ invEquiv (Σ-contractSnd λ a → inhProp→isContr (V-repr a) (isPropMonicPresentation a)) ⟩
    (Σ[ a ∈ V ℓ ] MonicPresentation a) ≃⟨ boringswap ⟩
    (Σ[ (X , ix , _) ∈ MonicDataF (V ℓ) ] singl (sett X ix)) ≃⟨ Σ-contractSnd (λ _ → isContrSingl _) ⟩
    MonicDataF (V ℓ) ■ where
    boringswap : (Σ[ a ∈ V ℓ ] MonicPresentation a) ≃ (Σ[ (X , ix , _) ∈ MonicDataF (V ℓ) ] singl (sett X ix))
    boringswap = isoToEquiv (iso
      (λ (a , (X , ix , emb) , p) → (X , ix , emb) , a , sym p)
      (λ ((X , ix , emb) , a , p) → a , (X , ix , emb) , sym p)
      (λ _ → refl)
      λ _ → refl)

  -- note the problem of making this a datatype directly: MonicDataF is *not* strictly positive!

-- an elimination principle based on the monic presentation
elim : (B : V ℓ → Type ℓ')
     → ((X : Type ℓ) (ix : X → V ℓ) (emb : isEmbedding ix) (rec : ∀ x → B (ix x)) → B (sett X ix))
     → (a : V ℓ) → B a
elim B alg = elimDMP ∘ V-deeprepr where
  elimDMP : ∀ {a} → DeepMonicPresentation a → B a
  elimDMP (dmp ((X , ix , emb) , p) rec) = subst B (sym p) (alg X ix emb (λ x → elimDMP (rec x)))

⟪_⟫ : (s : V ℓ) → Type ℓ
⟪ X ⟫ = V-repr X .fst .fst

⟪_⟫↪ : (s : V ℓ) → ⟪ s ⟫ → V ℓ
⟪ X ⟫↪ = V-repr X .fst .snd .fst

isEmb⟪_⟫↪ : (s : V ℓ) → isEmbedding ⟪ s ⟫↪
isEmb⟪ X ⟫↪ = V-repr X .fst .snd .snd

⟪_⟫-represents : (s : V ℓ) → s ≡ sett ⟪ s ⟫ ⟪ s ⟫↪
⟪ X ⟫-represents = V-repr X .snd

isPropRepFiber : (a b : V ℓ) → isProp (repFiber ⟪ a ⟫↪ b)
isPropRepFiber a b =
  Embedding-into-isProp→isProp
    (Equiv→Embedding (repFiber≃fiber ⟪ a ⟫↪ b))
    (isEmbedding→hasPropFibers isEmb⟪ a ⟫↪ b)

-- while ∈ is hProp (ℓ-suc ℓ), ∈ₛ is in ℓ
_∈ₛ_ : (a b : V ℓ) → hProp ℓ
a ∈ₛ b = repFiber ⟪ b ⟫↪ a , isPropRepFiber b a

∈-asFiber : {a b : V ℓ} → ⟨ a ∈ b ⟩ → fiber ⟪ b ⟫↪ a
∈-asFiber {a = a} {b = b} =
  subst (λ br → ⟨ a ∈ br ⟩ → fiber ⟪ b ⟫↪ a) (sym ⟪ b ⟫-represents) asRep
  where
  asRep : ⟨ a ∈ sett ⟪ b ⟫ ⟪ b ⟫↪ ⟩ → fiber ⟪ b ⟫↪ a
  asRep = P.propTruncIdempotent≃ (isEmbedding→hasPropFibers isEmb⟪ b ⟫↪ a) .fst
∈-fromFiber : {a b : V ℓ} → fiber ⟪ b ⟫↪ a → ⟨ a ∈ b ⟩
∈-fromFiber {a = a} {b = b} = subst (λ br → ⟨ a ∈ br ⟩) (sym ⟪ b ⟫-represents) ∘ ∣_∣₁

∈∈ₛ : {a b : V ℓ} → ⟨ a ∈ b ⇔ a ∈ₛ b ⟩
∈∈ₛ {a = a} {b = b} = leftToRight , rightToLeft where
  repEquiv : repFiber ⟪ b ⟫↪ a ≃ fiber ⟪ b ⟫↪ a
  repEquiv = repFiber≃fiber ⟪ b ⟫↪ a
  leftToRight : ⟨ (a ∈ b) ⇒ a ∈ₛ b ⟩
  leftToRight a∈b = invEq repEquiv (∈-asFiber {b = b} a∈b)
  rightToLeft : ⟨ a ∈ₛ b ⇒ (a ∈ b) ⟩
  rightToLeft a∈ₛb = ∈-fromFiber {b = b} (equivFun repEquiv a∈ₛb)

ix∈ₛ : {X : Type ℓ} {ix : X → V ℓ}
     → (x : X) → ⟨ ix x ∈ₛ sett X ix ⟩
ix∈ₛ {X = X} {ix = ix} x = ∈∈ₛ {a = ix x} {b = sett X ix} .fst ∣ x , refl ∣₁

∈ₛ⟪_⟫↪_ : (a : V ℓ) (ix : ⟪ a ⟫) → ⟨ ⟪ a ⟫↪ ix ∈ₛ a ⟩
∈ₛ⟪ a ⟫↪ ix = ix , ∼refl (⟪ a ⟫↪ ix)

-- also here, the left side is in level (ℓ-suc ℓ) while the right is in ℓ
presentation : (a : V ℓ) → (Σ[ v ∈ V ℓ ] ⟨ v ∈ₛ a ⟩) ≃ ⟪ a ⟫
presentation a = isoToEquiv (iso into from (λ _ → refl) retr) where
  into : Σ[ v ∈ V _ ] ⟨ v ∈ₛ a ⟩ → ⟪ a ⟫
  into = fst ∘ snd
  from : ⟪ a ⟫ → Σ[ v ∈ V _ ] ⟨ v ∈ₛ a ⟩
  from ⟪a⟫ = ⟪ a ⟫↪ ⟪a⟫ , ∈ₛ⟪ a ⟫↪ ⟪a⟫
  retr : retract into from
  retr s = Σ≡Prop (λ v → (v ∈ₛ a) .snd) (equivFun identityPrinciple (s .snd .snd))

-- subset relation, once in level (ℓ-suc ℓ) and once in ℓ
_⊆_ : (a b : V ℓ) → hProp (ℓ-suc ℓ)
a ⊆ b = ∀[ x ] x ∈ₛ a ⇒ x ∈ₛ b

⊆-refl : (a : V ℓ) → ⟨ a ⊆ a ⟩
⊆-refl a = λ _ xa → xa

_⊆ₛ_ : (a b : V ℓ) → hProp ℓ
a ⊆ₛ b = ∀[ x ] ⟪ a ⟫↪ x ∈ₛ b

⊆⇔⊆ₛ : (a b : V ℓ) → ⟨ a ⊆ b ⇔ a ⊆ₛ b ⟩
⊆⇔⊆ₛ a b =
    (λ s → invEq curryEquiv s ∘ invEq (presentation a))
  , (λ s x xa → subst (λ x → ⟨ x ∈ₛ b ⟩) (equivFun identityPrinciple (xa .snd)) (s (xa .fst)))

-- the homotopy definition of equality as an hProp, we know this is equivalent to bisimulation
infix 4 _≡ₕ_
_≡ₕ_ : (a b : V ℓ) → hProp (ℓ-suc ℓ)
_≡ₕ_ a b = (a ≡ b) , setIsSet a b

-- extensionality
extensionality : ⟨ ∀[ a ∶ V ℓ ] ∀[ b ] (a ⊆ b) ⊓ (b ⊆ a) ⇒ (a ≡ₕ b) ⟩
extensionality {ℓ = ℓ} a b imeq = ⟪ a ⟫-represents ∙∙ settab ∙∙ sym ⟪ b ⟫-represents where
  abpth : Path (Embedding _ _) (⟪ a ⟫ , ⟪ a ⟫↪ , isEmb⟪ a ⟫↪) (⟪ b ⟫ , ⟪ b ⟫↪ , isEmb⟪ b ⟫↪)
  abpth = equivFun (EmbeddingIP _ _)
    ( (λ p → equivFun (repFiber≃fiber ⟪ b ⟫↪ p) ∘ imeq .fst p ∘ invEq (repFiber≃fiber ⟪ a ⟫↪ p))
    , (λ p → equivFun (repFiber≃fiber ⟪ a ⟫↪ p) ∘ imeq .snd p ∘ invEq (repFiber≃fiber ⟪ b ⟫↪ p))
    )
  settab : sett ⟪ a ⟫ ⟪ a ⟫↪ ≡ sett ⟪ b ⟫ ⟪ b ⟫↪
  settab i = sett (abpth i .fst) (abpth i .snd .fst)

extInverse : ⟨ ∀[ a ∶ V ℓ ] ∀[ b ] (a ≡ₕ b) ⇒ (a ⊆ b) ⊓ (b ⊆ a) ⟩
extInverse a b a≡b = subst (λ b → ⟨ (a ⊆ b) ⊓ (b ⊆ a) ⟩) a≡b (⊆-refl a , ⊆-refl a)

set≡-characterization : {a b : V ℓ} → (a ≡ₕ b) ≡ (a ⊆ b) ⊓ (b ⊆ a)
set≡-characterization = ⇔toPath (extInverse _ _) (extensionality _ _)


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

open import Definition.Typed.EqualityRelation

module Definition.LogicalRelation.Properties.Reduction {{eqrel : EqRelSet}} where
open EqRelSet {{...}}

open import Definition.Typed
open import Definition.Typed.Properties
import Definition.Typed.Weakening as Wk
open import Definition.Typed.RedSteps
open import Definition.LogicalRelation
open import Definition.LogicalRelation.Properties.Reflexivity
open import Definition.LogicalRelation.Properties.Universe
open import Definition.LogicalRelation.Properties.Escape

open import Tools.Product
import Tools.PropositionalEquality as PE

-- Weak head expansion of reducible types.
redSubst* : ∀ {A B l Γ}
          → Γ ⊢ A ⇒* B
          → Γ ⊩⟨ l ⟩ B
          → ∃ λ ([A] : Γ ⊩⟨ l ⟩ A)
          → Γ ⊩⟨ l ⟩ A ≡ B / [A]
redSubst* D (Uᵣ′ l′ l< ⊢Γ) rewrite redU* D =
  Uᵣ′ l′ l< ⊢Γ , PE.refl
redSubst* D (ℕᵣ [ ⊢B , ⊢ℕ , D′ ]) =
  let ⊢A = redFirst* D
  in  ℕᵣ ([ ⊢A , ⊢ℕ , D ⇨* D′ ]) , D′
redSubst* D (Emptyᵣ [ ⊢B , ⊢Empty , D′ ]) =
  let ⊢A = redFirst* D
  in  Emptyᵣ ([ ⊢A , ⊢Empty , D ⇨* D′ ]) , D′
redSubst* D (Unitᵣ [ ⊢B , ⊢Unit , D′ ]) =
  let ⊢A = redFirst* D
  in  Unitᵣ ([ ⊢A , ⊢Unit , D ⇨* D′ ]) , D′
redSubst* D (ne′ K [ ⊢B , ⊢K , D′ ] neK K≡K) =
  let ⊢A = redFirst* D
  in  (ne′ K [ ⊢A , ⊢K , D ⇨* D′ ] neK K≡K)
  ,   (ne₌ _ [ ⊢B , ⊢K , D′ ] neK K≡K)
redSubst* D (Bᵣ′ W F G [ ⊢B , ⊢ΠFG , D′ ] ⊢F ⊢G A≡A [F] [G] G-ext) =
  let ⊢A = redFirst* D
  in  (Bᵣ′ W F G [ ⊢A , ⊢ΠFG , D ⇨* D′ ] ⊢F ⊢G A≡A [F] [G] G-ext)
  ,   (B₌ _ _ D′ A≡A (λ ρ ⊢Δ → reflEq ([F] ρ ⊢Δ))
        (λ ρ ⊢Δ [a] → reflEq ([G] ρ ⊢Δ [a])))
redSubst* D (emb 0<1 x) with redSubst* D x
redSubst* D (emb 0<1 x) | y , y₁ = emb 0<1 y , y₁

-- Weak head expansion of reducible terms.
redSubst*Term : ∀ {A t u l Γ}
              → Γ ⊢ t ⇒* u ∷ A
              → ([A] : Γ ⊩⟨ l ⟩ A)
              → Γ ⊩⟨ l ⟩ u ∷ A / [A]
              → Γ ⊩⟨ l ⟩ t ∷ A / [A]
              × Γ ⊩⟨ l ⟩ t ≡ u ∷ A / [A]
redSubst*Term t⇒u (Uᵣ′ .⁰ 0<1 ⊢Γ) (Uₜ A [ ⊢t , ⊢u , d ] typeA A≡A [u]) =
  let [d]  = [ ⊢t , ⊢u , d ]
      [d′] = [ redFirst*Term t⇒u , ⊢u , t⇒u ⇨∷* d ]
      q = redSubst* (univ* t⇒u) (univEq (Uᵣ′ ⁰ 0<1 ⊢Γ) (Uₜ A [d] typeA A≡A [u]))
  in Uₜ A [d′] typeA A≡A (proj₁ q)
  ,  Uₜ₌ A A [d′] [d] typeA typeA A≡A (proj₁ q) [u] (proj₂ q)
redSubst*Term t⇒u (ℕᵣ D) (ℕₜ n [ ⊢u , ⊢n , d ] n≡n prop) =
  let A≡ℕ  = subset* (red D)
      ⊢t   = conv (redFirst*Term t⇒u) A≡ℕ
      t⇒u′ = conv* t⇒u A≡ℕ
  in  ℕₜ n [ ⊢t , ⊢n , t⇒u′ ⇨∷* d ] n≡n prop
  ,   ℕₜ₌ n n [ ⊢t , ⊢n , t⇒u′ ⇨∷* d ] [ ⊢u , ⊢n , d ]
          n≡n (reflNatural-prop prop)
redSubst*Term t⇒u (Emptyᵣ D) (Emptyₜ n [ ⊢u , ⊢n , d ] n≡n prop) =
  let A≡Empty  = subset* (red D)
      ⊢t   = conv (redFirst*Term t⇒u) A≡Empty
      t⇒u′ = conv* t⇒u A≡Empty
  in  Emptyₜ n [ ⊢t , ⊢n , t⇒u′ ⇨∷* d ] n≡n prop
  ,   Emptyₜ₌ n n [ ⊢t , ⊢n , t⇒u′ ⇨∷* d ] [ ⊢u , ⊢n , d ]
          n≡n (reflEmpty-prop prop)
redSubst*Term t⇒u (Unitᵣ D) (Unitₜ n [ ⊢u , ⊢n , d ] prop) =
  let A≡Unit  = subset* (red D)
      ⊢t   = conv (redFirst*Term t⇒u) A≡Unit
      t⇒u′ = conv* t⇒u A≡Unit
  in  Unitₜ n [ ⊢t , ⊢n , t⇒u′ ⇨∷* d ] prop
  ,   Unitₜ₌ ⊢t ⊢u
redSubst*Term t⇒u (ne′ K D neK K≡K) (neₜ k [ ⊢t , ⊢u , d ] (neNfₜ neK₁ ⊢k k≡k)) =
  let A≡K  = subset* (red D)
      [d]  = [ ⊢t , ⊢u , d ]
      [d′] = [ conv (redFirst*Term t⇒u) A≡K , ⊢u , conv* t⇒u A≡K ⇨∷* d ]
  in  neₜ k [d′] (neNfₜ neK₁ ⊢k k≡k) , neₜ₌ k k [d′] [d] (neNfₜ₌ neK₁ neK₁ k≡k)
redSubst*Term {A} {t} {u} {l} {Γ} t⇒u (Πᵣ′ F G D ⊢F ⊢G A≡A [F] [G] G-ext)
                  [u]@(Πₜ f [d]@([ ⊢t , ⊢u , d ]) funcF f≡f [f] [f]₁) =
  let A≡ΠFG = subset* (red D)
      t⇒u′  = conv* t⇒u A≡ΠFG
      [d′] = [ conv (redFirst*Term t⇒u) A≡ΠFG , ⊢u , conv* t⇒u A≡ΠFG ⇨∷* d ]
      [u′] = Πₜ f [d′] funcF f≡f [f] [f]₁
  in  [u′]
  ,   Πₜ₌ f f [d′] [d] funcF funcF f≡f [u′] [u]
          (λ [ρ] ⊢Δ [a] → reflEqTerm ([G] [ρ] ⊢Δ [a]) ([f]₁ [ρ] ⊢Δ [a]))
redSubst*Term {A} {t} {u} {l} {Γ} t⇒u (Σᵣ′ F G D ⊢F ⊢G A≡A [F] [G] G-ext)
                  [u]@(Σₜ p [d]@([ ⊢t , ⊢u , d ]) pProd p≅p [fst] [snd]) =
  let A≡ΣFG = subset* (red D)
      t⇒u′  = conv* t⇒u A≡ΣFG
      [d′] = [ conv (redFirst*Term t⇒u) A≡ΣFG , ⊢u , conv* t⇒u A≡ΣFG ⇨∷* d ]
      [u′] = Σₜ p [d′] pProd p≅p [fst] [snd]
  in  [u′]
  ,   Σₜ₌ p p [d′] [d] pProd pProd p≅p [u′] [u] [fst] [fst]
          (reflEqTerm ([F] Wk.id (wf ⊢F)) [fst])
          (reflEqTerm ([G] Wk.id (wf ⊢F) [fst]) [snd])
redSubst*Term t⇒u (emb 0<1 x) [u] = redSubst*Term t⇒u x [u]

-- Weak head expansion of reducible types with single reduction step.
redSubst : ∀ {A B l Γ}
         → Γ ⊢ A ⇒ B
         → Γ ⊩⟨ l ⟩ B
         → ∃ λ ([A] : Γ ⊩⟨ l ⟩ A)
         → Γ ⊩⟨ l ⟩ A ≡ B / [A]
redSubst A⇒B [B] = redSubst* (A⇒B ⇨ id (escape [B])) [B]

-- Weak head expansion of reducible terms with single reduction step.
redSubstTerm : ∀ {A t u l Γ}
             → Γ ⊢ t ⇒ u ∷ A
             → ([A] : Γ ⊩⟨ l ⟩ A)
             → Γ ⊩⟨ l ⟩ u ∷ A / [A]
             → Γ ⊩⟨ l ⟩ t ∷ A / [A]
             × Γ ⊩⟨ l ⟩ t ≡ u ∷ A / [A]
redSubstTerm t⇒u [A] [u] = redSubst*Term (t⇒u ⇨ id (escapeTerm [A] [u])) [A] [u]


------------------------------------------------------------------------
-- "Equational" reasoning combinator setup
------------------------------------------------------------------------

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

open import Prelude

open import Labelled-transition-system

module Bisimilarity.Classical.Equational-reasoning-instances
         {ℓ} {lts : LTS ℓ} where

open import Bisimilarity.Classical lts

open import Equational-reasoning

instance

  reflexive∼ : Reflexive _∼_
  reflexive∼ = is-reflexive reflexive-∼

  symmetric∼ : Symmetric _∼_
  symmetric∼ = is-symmetric symmetric-∼

  trans∼∼ : Transitive _∼_ _∼_
  trans∼∼ = is-transitive transitive-∼

  convert∼∼ : Convertible _∼_ _∼_
  convert∼∼ = is-convertible id


{-

This file contains:
  - Path lemmas used in the colimit-equivalence proof.

Verbose, indeed. But should be simple. The length mainly thanks to:
  - Degenerate cubes that seem "obvious", but have to be constructed manually;
  - J rule is cubersome to use, especially when iteratively applied,
    also it is overcomplicated to construct JRefl in nested cases.

-}
{-# OPTIONS --safe --experimental-lossy-unification #-}
module Cubical.HITs.James.Inductive.Coherence where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.Function

private
  variable
    ℓ ℓ' : Level

-- Lots of degenerate cubes used as intial input to J rule

private
  module _
    {A : Type ℓ}(a : A) where

    degenerate1 : (i j k : I) → A
    degenerate1 i j k =
      hfill (λ k → λ
        { (i = i0) → a
        ; (i = i1) → doubleCompPath-filler (refl {x = a}) refl refl k j
        ; (j = i0) → a
        ; (j = i1) → a})
      (inS a) k

    degenerate1' : (i j k : I) → A
    degenerate1' i j k =
      hfill (λ k → λ
        { (i = i0) → a
        ; (i = i1) → compPath-filler (refl {x = a}) refl k j
        ; (j = i0) → a
        ; (j = i1) → a})
      (inS a) k

    degenerate1'' : (i j k : I) → A
    degenerate1'' i j k =
      hfill (λ k → λ
        { (i = i0) → a
        ; (i = i1) → compPath-filler (refl {x = a}) (refl ∙ refl) k j
        ; (j = i0) → a
        ; (j = i1) → degenerate1 i k i1})
      (inS a) k

    module _
      {B : Type ℓ'}(f : A → B) where

      degenerate2 : (i j k : I) → B
      degenerate2 i j k =
        hfill (λ k → λ
          { (i = i0) → f a
          ; (i = i1) → doubleCompPath-filler (refl {x = f a}) refl refl k j
          ; (j = i0) → f a
          ; (j = i1) → f a })
        (inS (f a)) k

      degenerate3 : (i j k : I) → B
      degenerate3 i j k =
        hfill (λ k → λ
          { (i = i0) → f (doubleCompPath-filler (refl {x = a}) refl refl k j)
          ; (i = i1) → doubleCompPath-filler (refl {x = f a}) refl refl k j
          ; (j = i0) → f a
          ; (j = i1) → f a })
        (inS (f a)) k

      someCommonDegenerateCube : (i j k : I) → B
      someCommonDegenerateCube i j k =
        hcomp (λ l → λ
          { (i = i0) → f a
          ; (i = i1) → degenerate3 k j l
          ; (j = i0) → f a
          ; (j = i1) → f a
          ; (k = i0) → f (degenerate1 i j l)
          ; (k = i1) → degenerate2 i j l })
        (f a)

    degenerate4 : (i j k : I) → A
    degenerate4 i j k =
      hfill (λ k → λ
        { (i = i0) → compPath-filler (refl {x = a}) (refl ∙∙ refl ∙∙ refl) k j
        ; (i = i1) → doubleCompPath-filler (refl {x = a}) refl refl j k
        ; (j = i0) → a
        ; (j = i1) → (refl {x = a} ∙∙ refl ∙∙ refl) k })
      (inS a) k

    degenerate5 : (i j k : I) → A
    degenerate5 i j k =
      hcomp (λ l → λ
        { (i = i0) → compPath-filler (refl {x = a}) (refl ∙∙ refl ∙∙ refl) k j
        ; (i = i1) → doubleCompPath-filler (refl {x = a}) refl refl (j ∧ ~ l) k
        ; (j = i0) → a
        ; (j = i1) → doubleCompPath-filler (refl {x = a}) refl refl (~ i ∨ ~ l) k
        ; (k = i0) → a
        ; (k = i1) → degenerate4 i j i1 })
      (degenerate4 i j k)

    degenerate5' : (i j k : I) → A
    degenerate5' i j k =
      hfill (λ k → λ
        { (i = i0) → doubleCompPath-filler (refl {x = a}) refl (refl ∙ refl) k j
        ; (i = i1) → a
        ; (j = i0) → a
        ; (j = i1) → compPath-filler (refl {x = a}) refl (~ i) k })
      (inS a) k

-- Cubes of which mostly are constructed by J rule

module _
  {A : Type ℓ}{a : A} where

  coh-helper-refl :
    (q' : a ≡ a)(h : refl ≡ q')
    → refl ≡ refl ∙∙ refl ∙∙ q'
  coh-helper-refl q' h i j =
    hcomp (λ k → λ
      { (i = i0) → a
      ; (i = i1) → doubleCompPath-filler refl refl q' k j
      ; (j = i0) → a
      ; (j = i1) → h i k })
    a

  coh-helper' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (q' : b ≡ c)(r : PathP (λ i → p i ≡ q i) p q')
    → refl ≡ (sym q) ∙∙ refl ∙∙ q'
  coh-helper' = J> J> coh-helper-refl

  coh-helper'-Refl1 : coh-helper' _ refl ≡ J> coh-helper-refl
  coh-helper'-Refl1 = transportRefl _

  coh-helper'-Refl2 : coh-helper' _ refl _ refl ≡ coh-helper-refl
  coh-helper'-Refl2 = (λ i → coh-helper'-Refl1 i _ refl) ∙ transportRefl _

  coh-helper :
    {b c : A}(p : a ≡ b)(q q' : b ≡ c)
    (h : PathP (λ i → p i ≡ q i) p q')
    → refl ≡ (sym q) ∙∙ refl ∙∙ q'
  coh-helper p = coh-helper' _ p _

  coh-helper-Refl : coh-helper-refl ≡ coh-helper refl refl
  coh-helper-Refl = sym coh-helper'-Refl2

module _
  {A : Type ℓ}{B : Type ℓ'}{a b c d : A} where

  doubleCompPath-cong-filler :
    {a' b' c' d' : B} (f : A → B)
    {pa : f a ≡ a'}{pb : f b ≡ b'}{pc : f c ≡ c'}{pd : f d ≡ d'}
    (p : a ≡ b)(q : b ≡ c)(r : c ≡ d)
    {p' : a' ≡ b'}{q' : b' ≡ c'}{r' : c' ≡ d'}
    (h   : PathP (λ i → pa i ≡ pb i) (cong f p) p')
    (h'  : PathP (λ i → pb i ≡ pc i) (cong f q) q')
    (h'' : PathP (λ i → pc i ≡ pd i) (cong f r) r')
    → (i j k : I) → B
  doubleCompPath-cong-filler f p q r {p' = p'} {q' = q'} {r' = r'}  h h' h'' i j k =
    hfill (λ k → λ
      { (i = i0) → f (doubleCompPath-filler p q r k j)
      ; (i = i1) → doubleCompPath-filler p' q' r' k j
      ; (j = i0) → h   i (~ k)
      ; (j = i1) → h'' i  k })
    (inS (h' i j)) k

  doubleCompPath-cong : (f : A → B)
    (p : a ≡ b)(q : b ≡ c)(r : c ≡ d)
    → cong f (p ∙∙ q ∙∙ r) ≡ cong f p ∙∙ cong f q ∙∙ cong f r
  doubleCompPath-cong f p q r i j =
    doubleCompPath-cong-filler f
      {pa = refl} {pb = refl} {pc = refl} {pd = refl}
      p q r refl refl refl i j i1

module _
  {A : Type ℓ}{a b c : A} where

  comp-cong-square' :
    (p : a ≡ b)(q : a ≡ c)
    (r : b ≡ c)(h : r ≡ sym p ∙∙ refl ∙∙ q)
    → p ∙ r ≡ q
  comp-cong-square' p q r h i j =
    hcomp (λ k → λ
      { (i = i0) → compPath-filler p r k j
      ; (i = i1) → doubleCompPath-filler (sym p) refl q j k
      ; (j = i0) → a
      ; (j = i1) → h i k })
    (p j)

  module _
    {B : Type ℓ'} where

    comp-cong-square : (f : A → B)
      (p : a ≡ b)(q : b ≡ c)
      → cong f (p ∙ q) ≡ cong f p ∙ cong f q
    comp-cong-square f p q i j =
      hcomp (λ k → λ
        { (i = i0) → f (compPath-filler p q k j)
        ; (i = i1) → compPath-filler (cong f p) (cong f q) k j
        ; (j = i0) → f a
        ; (j = i1) → f (q k) })
      (f (p j))

module _
  {A : Type ℓ}{B : Type ℓ'}{a b c : A}
  (f : A → B)(p : a ≡ b)
  (q : f a ≡ f c)(r : b ≡ c)
  (h : cong f r ≡ sym (cong f p) ∙∙ refl ∙∙ q) where

  comp-cong-helper-filler : (i j k : I) → B
  comp-cong-helper-filler i j k =
    hfill (λ k → λ
      { (i = i0) → comp-cong-square f p r (~ k) j
      ; (i = i1) → q j
      ; (j = i0) → f a
      ; (j = i1) → f c })
    (inS (comp-cong-square' _ _ _ h i j)) k

  comp-cong-helper : cong f (p ∙ r) ≡ q
  comp-cong-helper i j =
    comp-cong-helper-filler i j i1

module _
  {A : Type ℓ}{a : A} where

  push-helper-refl :
    (q' : a ≡ a)(h : refl ≡ q')
    → refl ≡ refl ∙ q'
  push-helper-refl q' h i j =
    hcomp (λ k → λ
      { (i = i0) → a
      ; (i = i1) → compPath-filler refl q' k j
      ; (j = i0) → a
      ; (j = i1) → h i k })
    a

  push-helper' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (q' : c ≡ c)(h : refl ≡ q')
    → PathP (λ i → p i ≡ q i) p (q ∙ q')
  push-helper' = J> J> push-helper-refl

  push-helper'-Refl1 : push-helper' _ refl ≡ J> push-helper-refl
  push-helper'-Refl1 = transportRefl _

  push-helper'-Refl2 : push-helper' _ refl _ refl ≡ push-helper-refl
  push-helper'-Refl2 = (λ i → push-helper'-Refl1 i _ refl) ∙ transportRefl _

  push-helper : {b c : A}
    (p : a ≡ b)(q : b ≡ c)(q' : c ≡ c)(h : refl ≡ q')
    → PathP (λ i → p i ≡ q i) p (q ∙ q')
  push-helper p = push-helper' _ p _

  push-helper-Refl : push-helper-refl ≡ push-helper refl refl
  push-helper-Refl = sym push-helper'-Refl2

module _
  {A : Type ℓ}{B : Type ℓ'}{a : A}(f : A → B) where

  push-helper-cong-Type : {b c : A}
    (p : a ≡ b)(q : b ≡ c)
    (q' : c ≡ c)(sqr : refl ≡ q')
    → Type _
  push-helper-cong-Type p q q' sqr =
    SquareP
      (λ i j → f (push-helper p q _ sqr i j)
          ≡ push-helper (cong f p) (cong f q) _ (λ i j → f (sqr i j)) i j)
      (λ i j → f (p i))
      (λ i j → comp-cong-square f q q' j i)
      (λ i j → f (p i)) (λ i j → f (q i))

  push-helper-cong-refl : push-helper-cong-Type refl refl refl refl
  push-helper-cong-refl =
    transport (λ t →
      SquareP
        (λ i j → f (push-helper-Refl t _ (λ i j → a) i j)
          ≡ push-helper-Refl t _ (λ i j → f a) i j)
        (λ i j → f a)
        (λ i j → comp-cong-square f (refl {x = a}) refl j i)
        (λ i j → f a) (λ i j → f a))
        (λ i j k → someCommonDegenerateCube a f i j k)

  push-helper-cong' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (q' : c ≡ c)(sqr : refl ≡ q')
    → push-helper-cong-Type p q q' sqr
  push-helper-cong' = J> J> J> push-helper-cong-refl

  push-helper-cong : ∀ {b c} p q q' sqr → push-helper-cong-Type {b = b} {c = c} p q q' sqr
  push-helper-cong p = push-helper-cong' _ p _

module _
  {A : Type ℓ}{a : A} where

  push-coh-helper-Type : {b c : A}
    (p : a ≡ b)(q q' : b ≡ c)
    (sqr : PathP (λ i → p i ≡ q i) p q')
    → Type _
  push-coh-helper-Type p q q' sqr =
    SquareP
      (λ i j → push-helper p q _ (coh-helper _ _ _ sqr) i j ≡ sqr i j)
      (λ i j → p i)
      (λ i j → comp-cong-square' q q' _ refl j i)
      (λ i j → p i) (λ i j → q i)

  push-coh-helper-refl' :
    SquareP
      (λ i j → push-helper-refl _ (coh-helper-refl _ (λ i j → a)) i j ≡ a)
      (λ i j → a)
      (λ i j → comp-cong-square' (refl {x = a}) refl _ refl j i)
      (λ i j → a)
      (λ i j → a)
  push-coh-helper-refl' i j k =
    hcomp (λ l → λ
      { (i = i0) → a
      ; (i = i1) → degenerate5 a k j l
      ; (j = i0) → a
      ; (j = i1) → degenerate1 a i l (~ k)
      ; (k = i0) → degenerate1'' a i j l
      ; (k = i1) → a })
    a

  push-coh-helper-refl : push-coh-helper-Type refl refl refl refl
  push-coh-helper-refl =
    transport (λ t →
      SquareP
      (λ i j → push-helper-Refl t _ (coh-helper-Refl t _ (λ i j → a)) i j ≡ a)
      (λ i j → a)
      (λ i j → comp-cong-square' (refl {x = a}) refl _ refl j i)
      (λ i j → a) (λ i j → a)) push-coh-helper-refl'

  push-coh-helper' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (q' : b ≡ c)(sqr : PathP (λ i → p i ≡ q i) p q')
    → push-coh-helper-Type p q q' sqr
  push-coh-helper' = J> J> J> push-coh-helper-refl

  push-coh-helper : ∀ {b c} p q q' sqr → push-coh-helper-Type {b = b} {c = c} p q q' sqr
  push-coh-helper p q q' sqr = push-coh-helper' _ p _ q q' sqr

module _
  {A : Type ℓ}{a : A} where

  push-square-helper-refl :
    refl ∙∙ refl ∙∙ (refl ∙ refl) ≡ refl {x = a}
  push-square-helper-refl i j = degenerate5' a i j i1

  push-square-helper' :
    (b : A)(q  : a ≡ b)
    (c : A)(q' : b ≡ c)
    → sym q ∙∙ refl ∙∙ (q ∙ q') ≡ q'
  push-square-helper' = J> J> push-square-helper-refl

  push-square-helper'-Refl1 : push-square-helper' _ refl ≡ J> push-square-helper-refl
  push-square-helper'-Refl1 = transportRefl _

  push-square-helper'-Refl2 : push-square-helper' _ refl _ refl ≡ push-square-helper-refl
  push-square-helper'-Refl2 = (λ i → push-square-helper'-Refl1 i _ refl) ∙ transportRefl _

  push-square-helper : {b c : A}
    (q : a ≡ b)(q' : b ≡ c)
    → sym q ∙∙ refl ∙∙ (q ∙ q') ≡ q'
  push-square-helper p = push-square-helper' _ p _

  push-square-helper-Refl : push-square-helper-refl ≡ push-square-helper refl refl
  push-square-helper-Refl = sym push-square-helper'-Refl2

module _
  {A : Type ℓ}{a : A} where

  coh-cube-helper-Type :
    {b c : A}(p : a ≡ b)(q : b ≡ c)
    (q' : c ≡ c)(sqr : refl ≡ q')
    → Type _
  coh-cube-helper-Type {c = c} p q q' sqr =
    SquareP
      (λ i j → coh-helper _ _ _ (push-helper p q q' sqr) i j ≡ sqr i j)
      (λ i j → c)
      (λ i j → push-square-helper q q' j i)
      (λ i j → c) (λ i j → c)

  coh-cube-helper-refl' :
    SquareP
      (λ i j → coh-helper-refl _ (push-helper-refl refl (λ i j → a)) i j ≡ a)
      (λ i j → a)
      (λ i j → push-square-helper-refl {a = a} j i)
      (λ i j → a) (λ i j → a)
  coh-cube-helper-refl' i j k =
    hcomp (λ l → λ
      { (i = i0) → a
      ; (i = i1) → degenerate5' a k j l
      ; (j = i0) → a
      ; (j = i1) → degenerate1' a i l (~ k)
      ; (k = i0) → degenerate1'' a i j l
      ; (k = i1) → a })
    a

  coh-cube-helper-refl : coh-cube-helper-Type refl refl refl refl
  coh-cube-helper-refl =
    transport (λ t →
      SquareP
      (λ i j → coh-helper-Refl t _ (push-helper-Refl t refl (λ i j → a)) i j ≡ a)
      (λ i j → a)
      (λ i j → push-square-helper-Refl {a = a} t j i)
      (λ i j → a) (λ i j → a)) coh-cube-helper-refl'

  coh-cube-helper' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (q' : c ≡ c)(sqr : refl ≡ q')
    → coh-cube-helper-Type p q q' sqr
  coh-cube-helper' = J> J> J> coh-cube-helper-refl

  coh-cube-helper : ∀ {b c} p q q' sqr → coh-cube-helper-Type {b = b} {c = c} p q q' sqr
  coh-cube-helper p q q' sqr = coh-cube-helper' _ p _ q q' sqr

module _
  {A : Type ℓ}{B : Type ℓ'}{a : A}(f : A → B) where

  coh-helper-cong-Type : {b c : A}{a' b' c' : B}
    (pa : f a ≡ a')(pb : f b ≡ b')(pc : f c ≡ c')
    {p  : a  ≡ b }(q  r  : b  ≡ c )
    {p' : a' ≡ b'}{q' r' : b' ≡ c'}
    (h   : PathP (λ i → pa i ≡ pb i) (cong f p) p')
    (h'  : PathP (λ i → pb i ≡ pc i) (cong f q) q')
    (h'' : PathP (λ i → pb i ≡ pc i) (cong f r) r')
    (sqr  : PathP (λ i → p  i ≡ q  i) p  r)
    (sqr' : PathP (λ i → p' i ≡ q' i) p' r')
    → Type _
  coh-helper-cong-Type pa pb pc q r h h' h'' sqr sqr' =
    SquareP
      (λ i j → f (coh-helper _ _ _ sqr i j) ≡ coh-helper _ _ _ (λ i j → sqr' i j) i j)
      (λ i j → pc j)
      (λ i j → doubleCompPath-cong-filler f (sym q) refl r (λ i j → h' i (~ j)) (λ i j → pb i) h'' j i i1)
      (λ i j → pc j) (λ i j → pc j)

  coh-helper-cong-refl : coh-helper-cong-Type refl refl refl refl refl refl refl refl refl refl
  coh-helper-cong-refl =
    transport (λ t →
      SquareP
      (λ i j → f (coh-helper-Refl t _ (λ i j → a) i j) ≡ coh-helper-Refl t _ (λ i j → f a) i j)
      (λ i j → f a)
      (λ i j → doubleCompPath-cong-filler f refl refl refl (λ i j → f a) (λ i j → f a) (λ i j → f a) j i i1)
      (λ i j → f a) (λ i j → f a))
      (λ i j k → someCommonDegenerateCube a f i j k)

  coh-helper-cong' :
    (b : A)(p : a ≡ b)
    (c : A)(q : b ≡ c)
    (a' : B)(pa : f a ≡ a')
    (b' : B)(pb : f b ≡ b')
    (c' : B)(pc : f c ≡ c')
    (r : b ≡ c)(sqr  : PathP (λ i → p  i ≡ q  i) p r)
    (p' : a' ≡ b')(h   : PathP (λ i → pa i ≡ pb i) (cong f p) p')
    (q' : b' ≡ c')(h'  : PathP (λ i → pb i ≡ pc i) (cong f q) q')
    (r' : b' ≡ c')(h'' : PathP (λ i → pb i ≡ pc i) (cong f r) r')
    (sqr' : PathP (λ i → p' i ≡ q' i) p' r')
    (hsqr : SquareP (λ i j → h i j ≡ h' i j) (λ i j → f (sqr i j)) sqr' h h'')
    → coh-helper-cong-Type pa pb pc q r h h' h'' sqr sqr'
  coh-helper-cong' = J> J> J> J> J> J> J> J> J> J> coh-helper-cong-refl

  coh-helper-cong :
    ∀ {b c a' b' c' pa pb pc} p q r {p' q' r' h h' h''} sqr {sqr'}
    (hsqr : SquareP (λ i j → f (sqr i j) ≡ sqr' i j)
      (λ i j → h j i) (λ i j → h'' j i) (λ i j → h j i) (λ i j → h' j i))
    → coh-helper-cong-Type {b = b} {c = c} {a' = a'} {b' = b'} {c' = c'}
        pa pb pc q r {p' = p'} {q' = q'} {r' = r'} h h' h'' sqr sqr'
  coh-helper-cong p q r sqr hsqr =
    coh-helper-cong' _ p _ q _ _ _ _ _ _ r sqr _ _ _ _ _ _ _ (λ i j k → hsqr j k i)


------------------------------------------------------------------------
-- Compiler correctness
------------------------------------------------------------------------

{-# OPTIONS --erased-cubical --sized-types #-}

module Lambda.Simplified.Delay-monad.Compiler-correctness where

import Equality.Propositional as E
open import Prelude
open import Prelude.Size

open import Monad E.equality-with-J
open import Vec.Function E.equality-with-J

open import Delay-monad
open import Delay-monad.Bisimilarity
open import Delay-monad.Monad

open import Lambda.Simplified.Compiler
open import Lambda.Simplified.Delay-monad.Interpreter
open import Lambda.Simplified.Delay-monad.Virtual-machine
open import Lambda.Simplified.Syntax
open import Lambda.Simplified.Virtual-machine

private
  module C = Closure Code
  module T = Closure Tm

-- Compiler correctness.

mutual

  ⟦⟧-correct :
    ∀ {i n} t {ρ : T.Env n} {c s}
      {k : T.Value → Delay (Maybe C.Value) ∞} →
    (∀ v → [ i ] exec ⟨ c , val (comp-val v) ∷ s , comp-env ρ ⟩ ≈ k v) →
    [ i ] exec ⟨ comp t c , s , comp-env ρ ⟩ ≈ ⟦ t ⟧ ρ >>= k

  ⟦⟧-correct (var x) {ρ} {c} {s} {k} hyp =
    exec ⟨ var x ∷ c , s , comp-env ρ ⟩                 ≳⟨⟩
    exec ⟨ c , val (comp-val (ρ x)) ∷ s , comp-env ρ ⟩  ≈⟨ hyp (ρ x) ⟩∼
    k (ρ x)                                             ∼⟨⟩
    ⟦ var x ⟧ ρ >>= k                                   ∎

  ⟦⟧-correct (ƛ t) {ρ} {c} {s} {k} hyp =
    exec ⟨ clo (comp t (ret ∷ [])) ∷ c , s , comp-env ρ ⟩   ≳⟨⟩
    exec ⟨ c , val (comp-val (T.ƛ t ρ)) ∷ s , comp-env ρ ⟩  ≈⟨ hyp (T.ƛ t ρ) ⟩∼
    k (T.ƛ t ρ)                                             ∼⟨⟩
    ⟦ ƛ t ⟧ ρ >>= k                                         ∎

  ⟦⟧-correct (t₁ · t₂) {ρ} {c} {s} {k} hyp =
    exec ⟨ comp t₁ (comp t₂ (app ∷ c)) , s , comp-env ρ ⟩      ≈⟨ (⟦⟧-correct t₁ λ v₁ → ⟦⟧-correct t₂ λ v₂ → ∙-correct v₁ v₂ hyp) ⟩∼

    (⟦ t₁ ⟧ ρ >>= λ v₁ → ⟦ t₂ ⟧ ρ >>= λ v₂ → v₁ ∙ v₂ >>= k)    ∼⟨ (⟦ t₁ ⟧ ρ ∎) >>=-cong (λ _ → associativity′ (⟦ t₂ ⟧ ρ) _ _) ⟩

    (⟦ t₁ ⟧ ρ >>= λ v₁ → (⟦ t₂ ⟧ ρ >>= λ v₂ → v₁ ∙ v₂) >>= k)  ∼⟨ associativity′ (⟦ t₁ ⟧ ρ) _ _ ⟩

    ⟦ t₁ · t₂ ⟧ ρ >>= k                                        ∎

  ∙-correct :
    ∀ {i n} v₁ v₂ {ρ : T.Env n} {c s}
      {k : T.Value → Delay (Maybe C.Value) ∞} →
    (∀ v → [ i ] exec ⟨ c , val (comp-val v) ∷ s , comp-env ρ ⟩ ≈ k v) →
    [ i ] exec ⟨ app ∷ c
               , val (comp-val v₂) ∷ val (comp-val v₁) ∷ s
               , comp-env ρ
               ⟩ ≈
          v₁ ∙ v₂ >>= k
  ∙-correct (T.ƛ t₁ ρ₁) v₂ {ρ} {c} {s} {k} hyp =
    exec ⟨ app ∷ c
         , val (comp-val v₂) ∷ val (comp-val (T.ƛ t₁ ρ₁)) ∷ s
         , comp-env ρ
         ⟩                                                     ≈⟨ later (λ { .force →

      exec ⟨ comp t₁ (ret ∷ [])
           , ret c (comp-env ρ) ∷ s
           , cons (comp-val v₂) (comp-env ρ₁)
           ⟩                                                        ≡⟨ E.cong (λ ρ′ →
                                                                                exec ⟨ comp t₁ (ret ∷ []) , ret c (comp-env ρ) ∷ s , ρ′ ⟩) $
                                                                              E.sym comp-cons ⟩
      exec ⟨ comp t₁ (ret ∷ [])
           , ret c (comp-env ρ) ∷ s
           , comp-env (cons v₂ ρ₁)
           ⟩                                                        ≈⟨ ⟦⟧-correct t₁ (λ v →

        exec ⟨ ret ∷ []
             , val (comp-val v) ∷ ret c (comp-env ρ) ∷ s
             , comp-env (cons v₂ ρ₁)
             ⟩                                                           ≳⟨⟩

        exec ⟨ c , val (comp-val v) ∷ s , comp-env ρ ⟩                   ≈⟨ hyp v ⟩∎

        k v                                                              ∎) ⟩∼

      ⟦ t₁ ⟧ (cons v₂ ρ₁) >>= k                                     ∎ }) ⟩∎

    T.ƛ t₁ ρ₁ ∙ v₂ >>= k                                       ∎

-- Note that the equality that is used here is syntactic.

correct :
  ∀ t →
  exec ⟨ comp t [] , [] , nil ⟩ ≈
  ⟦ t ⟧ nil >>= λ v → return (just (comp-val v))
correct t =
  exec ⟨ comp t [] , [] , nil ⟩                     ≡⟨ E.cong (λ ρ → exec ⟨ comp t [] , [] , ρ ⟩) $ E.sym comp-nil ⟩
  exec ⟨ comp t [] , [] , comp-env nil ⟩            ≈⟨ ⟦⟧-correct t (λ v → return (just (comp-val v)) ∎) ⟩
  (⟦ t ⟧ nil >>= λ v → return (just (comp-val v)))  ∎