{-

Definition of the integers as a HIT inspired by slide 10 of (original
idea due to Paolo Capriotti):

http://www.cs.nott.ac.uk/~psztxa/talks/bonn18.pdf

Disclaimer: this definition is very hard to work with and I have not
been able to prove that it is equivalent to nat + nat or that it is a
set.

For a variation that relies on a different notion of equivalence
(without any 2-cell) and which seems easier to work with see:

https://github.com/RedPRL/redtt/blob/master/library/cool/biinv-int.red

It might be interesting to port that example one day.

-}
{-# OPTIONS --cubical --no-import-sorts #-}
module Cubical.Experiments.HInt where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Isomorphism
open import Cubical.Data.Int
open import Cubical.Data.Nat

data ℤ : Type₀ where
  zero : ℤ
  suc  : ℤ → ℤ
  pred : ℤ → ℤ
  sucpred : (n : ℤ) → suc (pred n) ≡ n
  predsuc : (n : ℤ) → pred (suc n) ≡ n
  -- Coherence: could also be written "sucpred (suc n) ≡ cong (suc (predsuc n))"
  coh     : (n : ℤ) → Path (suc (pred (suc n)) ≡ suc n)
                           (sucpred (suc n))
                           (λ i → suc (predsuc n i))

-- This is equivalent to Int:
coherence : (n : Int) → Path (Path Int (sucInt (predInt (sucInt n))) (sucInt n))
                             (sucPred (sucInt n))
                             (cong sucInt (predSuc n))
coherence (pos zero) = refl
coherence (pos (suc n)) = refl
coherence (negsuc zero) = refl
coherence (negsuc (suc zero)) = refl
coherence (negsuc (suc (suc n))) = refl

ℤ→Int : ℤ → Int
ℤ→Int zero = pos 0
ℤ→Int (suc n) = sucInt (ℤ→Int n)
ℤ→Int (pred n) = predInt (ℤ→Int n)
ℤ→Int (sucpred n i) = sucPred (ℤ→Int n) i
ℤ→Int (predsuc n i) = predSuc (ℤ→Int n) i
ℤ→Int (coh n i j) = coherence (ℤ→Int n) i j

ℕ→ℤ : ℕ → ℤ
ℕ→ℤ zero = zero
ℕ→ℤ (suc n) = suc (ℕ→ℤ n)

negsucℕ→ℤ : ℕ → ℤ
negsucℕ→ℤ zero = pred zero
negsucℕ→ℤ (suc n) = pred (negsucℕ→ℤ n)

Int→ℤ : Int → ℤ
Int→ℤ (pos n) = ℕ→ℤ n
Int→ℤ (negsuc n) = negsucℕ→ℤ n

lem1 : ∀ n → Int→ℤ (sucInt n) ≡ suc (Int→ℤ n)
lem1 (pos n) = refl
lem1 (negsuc zero) = sym (sucpred zero)
lem1 (negsuc (suc n)) = sym (sucpred (negsucℕ→ℤ n))

lem2 : ∀ n → Int→ℤ (predInt n) ≡ pred (Int→ℤ n)
lem2 (pos zero) = refl
lem2 (pos (suc n)) = sym (predsuc (ℕ→ℤ n))
lem2 (negsuc n) = refl

-- I don't see how to fill these holes, especially the last one
ℤ→Int→ℤ : ∀ (n : ℤ) → Int→ℤ (ℤ→Int n) ≡ n
ℤ→Int→ℤ zero = refl
ℤ→Int→ℤ (suc n) = (lem1 (ℤ→Int n)) ∙ (cong suc (ℤ→Int→ℤ n))
ℤ→Int→ℤ (pred n) = (lem2 (ℤ→Int n)) ∙ (cong pred (ℤ→Int→ℤ n))
ℤ→Int→ℤ (sucpred n i) = {!!}
ℤ→Int→ℤ (predsuc n i) = {!!}
ℤ→Int→ℤ (coh n i j) = {!!}

Int→ℤ→Int : ∀ n → ℤ→Int (Int→ℤ n) ≡ n
Int→ℤ→Int (pos zero) = refl
Int→ℤ→Int (pos (suc n)) = cong sucInt (Int→ℤ→Int (pos n))
Int→ℤ→Int (negsuc zero) = refl
Int→ℤ→Int (negsuc (suc n)) = cong predInt (Int→ℤ→Int (negsuc n))

Int≡ℤ : Int ≡ ℤ
Int≡ℤ = isoToPath (iso Int→ℤ ℤ→Int ℤ→Int→ℤ Int→ℤ→Int)

isSetℤ : isSet ℤ
isSetℤ = subst isSet Int≡ℤ isSetInt


{-# OPTIONS --show-implicit #-}

module BottomUp where

open import Foundation.Primitive
open import Foundation.Bottom
open import Foundation.Equivalence

open import Agda.Builtin.List
open import Agda.Builtin.Equality
open import Agda.Builtin.Unit

module NegEquiv {ℓ'} (⊥ : Set ℓ') ⦃ _ : ∀ {b} → IsBottom ⊥ b ⦄ where

  _≉_ : ∀ {a} {A : Set a} {ℓ} ⦃ _ : Equivalence A ℓ ⦄ → A → A → Set (ℓ ⊔ ℓ')
  x ≉ y = x ≈ y → ⊥

module Data-⊥ where

  data Data⊥ : Set where

  instance bottom-rule : ∀ {b} → IsBottom Data⊥ b
  IsBottom.⊥-elim (bottom-rule {b}) () {B}

module IsDecisive-Module {ℓ'} (⊥ : Set ℓ') ⦃ _ : ∀ {b} → IsBottom ⊥ b ⦄ where

  open NegEquiv ⊥

  record IsDecisive a
                    (Decision : (A : Set a) → Set a)
                    (yes-c : {A : Set a} → A → Decision A)
                    (no-c : {A : Set a} → (A → ⊥) → Decision A)
                    ℓ
                    ⦃ equiv-decision : {A : Set a} → Equivalence (Decision A) ℓ ⦄
                    : Set (ℓ ⊔ ⟰ a ⊔ ℓ') where
    field
      yes-correct : {A : Set a} → (dec : Decision A) → (x : A) → yes-c x ≈ dec
      no-correct : {A : Set a} → (dec : Decision A) → (x : A → ⊥) → no-c x ≈ dec
      yes-no-incompat : {A : Set a} (x : A) (nx : A → ⊥) → yes-c x ≉ no-c nx

  open IsDecisive ⦃ … ⦄ public

record ⊤′ {a} : Set a where
  instance constructor tt

module Data-Decision {ℓ} (⊥ : Set ℓ) ⦃ _ : ∀ {b} → IsBottom ⊥ b ⦄ where

  data Decision {a} (A : Set a) : Set (a ⊔ ℓ) where
    yes : A → Decision A
    no : (A → ⊥) → Decision A

  open IsDecisive-Module ⊥

  instance dec-equiv : ∀ {a} {A : Set a} → Equivalence (Decision {a} A) a
  (dec-equiv {a} {A} Equivalence.≈ yes x₁) (yes x₂) = ⊤′
  (dec-equiv {a} {A} Equivalence.≈ yes x₁) (no x₂) = ⊥-elim (x₂ x₁)
  (dec-equiv {a} {A} Equivalence.≈ no x₁) (yes x₂) = ⊥-elim (x₁ x₂)
  (dec-equiv {a} {A} Equivalence.≈ no x₁) (no x₂) = ⊤′
  IsEquivalence.reflexivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) = tt
  IsEquivalence.reflexivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) = tt
  IsEquivalence.symmetry (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (yes x₂) x₃ = x₃
  IsEquivalence.symmetry (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (no x₂) x₃ = x₃
  IsEquivalence.symmetry (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (yes x₂) x₃ = x₃
  IsEquivalence.symmetry (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (no x₂) x₃ = x₃
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (yes x₂) (yes x₃) x₄ x₅ = tt
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (yes x₂) (no x₃) x₄ x₅ = ⊥-elim (x₃ x₂)
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (no x₂) (yes x₃) x₄ x₅ = tt
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (yes x₁) (no x₂) (no x₃) x₄ x₅ = ⊥-elim (x₂ x₁)
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (yes x₂) (yes x₃) x₄ x₅ = ⊥-elim (x₁ x₂)
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (yes x₂) (no x₃) x₄ x₅ = tt
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (no x₂) (yes x₃) x₄ x₅ = ⊥-elim (x₂ x₃)
  IsEquivalence.transitivity (Equivalence.isEquivalence (dec-equiv {a} {A})) (no x₁) (no x₂) (no x₃) x₄ x₅ = tt

  instance dec-instance : ∀ {a} → IsDecisive (a ⊔ ℓ) (Decision {a ⊔ ℓ}) yes no (a ⊔ ℓ)
  IsDecisive-Module.IsDecisive.yes-correct (dec-instance {a}) {A} (yes x₁) x₂ = tt
  IsDecisive-Module.IsDecisive.yes-correct (dec-instance {a}) {A} (no x₁) x₂ = ⊥-elim (x₁ x₂)
  IsDecisive-Module.IsDecisive.no-correct (dec-instance {a}) {A} (yes x₁) x₂ = ⊥-elim (x₂ x₁)
  IsDecisive-Module.IsDecisive.no-correct (dec-instance {a}) {A} (no x₁) x₂ = tt
  IsDecisive-Module.IsDecisive.yes-no-incompat (dec-instance {a}) x₁ nx x₂ = nx x₁

open Data-⊥

-- module DecisionModule (⊥ : Set) ⦃ _ : ∀ {a} {b} → IsBottom ⊥ a b ⦄ where

--   record IsDecidableEquivalence {a} {A : Set a} {ℓ} (_≈_ : A → A → Set ℓ) : Set (a ⊔ ℓ) where
--     infix 4 _≈?_
--     field
--       isEquivalence : IsEquivalence _≈_
--       _≈?_ : (x y : A) → Decision (x ≈ y)

--   record DecidableEquivalence {a} (A : Set a) ℓ : Set (lsuc (a ⊔ ℓ)) where
--     field
--       ⦃ equivalence ⦄ : Equivalence A ℓ
--     --open Equivalence equivalence
--     infix 4 _≈?_
--     field
--       _≈?_ : (x y : A) → Decision (x ≈ y)

--   open DecidableEquivalence ⦃ … ⦄ public

--   record DecidableEquivalence' {a} (A : Set a) : Set (lsuc a) where
--     field
--       ⦃ equivalence ⦄ : Equivalence A a
--     infix 4 _≈?_
--     field
--       _≈?_ : (x y : A) → Decision (x ≈ y)

-- data ⊥-fake : Set where
--   faker : ⊥-fake

-- ⊥fake-rule : ∀ {a} {A : Set a} → (A → ⊥-fake) → A → ∀ {b} {B : Set b} → B
-- ⊥fake-rule x x₁ with x x₁
-- ⊥fake-rule x x₁ | faker = {!!}

-- open DecisionModule ⊥

-- data Tree (A : Set) : Set where
--   value : A → Tree A
--   leaf : Tree A
--   branch : Tree A → Tree A → Tree A

-- substitute₁List : ∀ {a} {A : Set a} ⦃ _ : DecidableEquivalence A a ⦄ → A → A → List A → List A
-- substitute₁List x₁ y [] = []
-- substitute₁List x₁ y (x₂ ∷ xs) with x₁ ≈? x₂
-- substitute₁List x₁ y (x₂ ∷ xs) | yes x₃ = y ∷ substitute₁List x₁ y xs
-- substitute₁List x₁ y (x₂ ∷ xs) | no x₃ = x₂ ∷ substitute₁List x₁ y xs

-- open import Agda.Builtin.Nat
-- open import Agda.Builtin.Bool

-- instance EquivalenceNat : Equivalence Nat lzero
-- (EquivalenceNat Equivalence.≈ x) x₁ = (x == x₁) ≡ true
-- IsEquivalence.reflexivity (Equivalence.isEquivalence EquivalenceNat) = isrefl where
--   isrefl : ∀ x → (x == x) ≡ true
--   isrefl zero = refl
--   isrefl (suc x) = isrefl x
-- IsEquivalence.symmetry (Equivalence.isEquivalence EquivalenceNat) = issym where
--   issym : (x y : Nat) →
--             EquivalenceNat .Equivalence._≈_ x y →
--             EquivalenceNat .Equivalence._≈_ y x
--   issym zero zero x₁ = refl
--   issym zero (suc y) x₁ = x₁
--   issym (suc x) zero x₁ = x₁
--   issym (suc x) (suc y) x₁ = issym x y x₁
-- IsEquivalence.transitivity (Equivalence.isEquivalence EquivalenceNat) = {!!}

-- instance DecidableEquivalenceNat : DecidableEquivalence Nat lzero
-- DecisionModule.DecidableEquivalence.equivalence DecidableEquivalenceNat = EquivalenceNat
-- (DecidableEquivalenceNat DecisionModule.DecidableEquivalence.≈? zero) zero = yes refl
-- (DecidableEquivalenceNat DecisionModule.DecidableEquivalence.≈? zero) (suc y) = no (λ x → {!!})
-- (DecidableEquivalenceNat DecisionModule.DecidableEquivalence.≈? suc x) zero = {!!}
-- (DecidableEquivalenceNat DecisionModule.DecidableEquivalence.≈? suc x) (suc y) = {!!}

-- {-
-- $&[{}(=*)+]!#
-- ~%7531902468`
-- ;,.
-- :<>
-- -}


{-# OPTIONS --safe --experimental-lossy-unification #-}
{-
This file contains a proof that the generator of Π₃S² has
hopf invariant ±1.
-}
module Cubical.Homotopy.HopfInvariant.HopfMap where

open import Cubical.Homotopy.HopfInvariant.Base
open import Cubical.Homotopy.Hopf
open S¹Hopf
open import Cubical.Homotopy.Connected
open import Cubical.Homotopy.HSpace

open import Cubical.ZCohomology.Base
open import Cubical.ZCohomology.GroupStructure
open import Cubical.ZCohomology.Properties
open import Cubical.ZCohomology.Groups.Sn
open import Cubical.ZCohomology.RingStructure.CupProduct
open import Cubical.ZCohomology.RingStructure.RingLaws
open import Cubical.ZCohomology.Gysin

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Path
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Function
open import Cubical.Foundations.Pointed
open import Cubical.Foundations.Pointed.Homogeneous
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Univalence

open import Cubical.Data.Sum
open import Cubical.Data.Sigma
open import Cubical.Data.Int hiding (_+'_)
open import Cubical.Data.Nat renaming (_+_ to _+ℕ_ ; _·_ to _·ℕ_)
open import Cubical.Data.Unit

open import Cubical.Algebra.Group
open import Cubical.Algebra.Group.ZAction
open import Cubical.Algebra.Group.Exact
open import Cubical.Algebra.Group.Morphisms
open import Cubical.Algebra.Group.MorphismProperties
open import Cubical.Algebra.Group.Instances.Int
open import Cubical.Algebra.Group.GroupPath

open import Cubical.HITs.Pushout
open import Cubical.HITs.Join
open import Cubical.HITs.S1 renaming (_·_ to _*_)
open import Cubical.HITs.Sn
open import Cubical.HITs.Susp
open import Cubical.HITs.Truncation
  renaming (rec to trRec ; elim to trElim)
open import Cubical.HITs.SetTruncation
  renaming (rec to sRec ; rec2 to sRec2
          ; elim to sElim ; elim2 to sElim2 ; map to sMap)
open import Cubical.HITs.PropositionalTruncation
  renaming (rec to pRec)

HopfMap : S₊∙ 3 →∙ S₊∙ 2
fst HopfMap x = JoinS¹S¹→TotalHopf (Iso.inv (IsoSphereJoin 1 1) x) .fst
snd HopfMap = refl

-- We use the Hopf fibration in order to connect it to the Gysin Sequence
module hopfS¹ =
  Hopf S1-AssocHSpace (sphereElim2 _ (λ _ _ → squash₁) ∣ refl ∣₁)

S¹Hopf = hopfS¹.Hopf
E* = hopfS¹.TotalSpacePush²'
IsoE*join = hopfS¹.joinIso₂
IsoTotalHopf' = hopfS¹.joinIso₁
CP² = hopfS¹.TotalSpaceHopfPush²
fibr = hopfS¹.P

TotalHopf' : Type _
TotalHopf' = Σ (S₊ 2) S¹Hopf

IsoJoins : (join S¹ (join S¹ S¹)) ≡ join S¹ (S₊ 3)
IsoJoins = cong (join S¹) (isoToPath (IsoSphereJoin 1 1))

-- CP² is 1-connected
conCP² : (x y : CP²) → ∥ x ≡ y ∥₂
conCP² x y = sRec2 squash₂ (λ p q → ∣ p ∙ sym q ∣₂) (conCP²' x) (conCP²' y)
  where
  conCP²' : (x : CP²) → ∥ x ≡ inl tt ∥₂
  conCP²' (inl x) = ∣ refl ∣₂
  conCP²' (inr x) = sphereElim 1 {A = λ x → ∥ inr x ≡ inl tt ∥₂}
                                 (λ _ → squash₂) ∣ sym (push (inl base)) ∣₂ x
  conCP²' (push a i) = main a i
    where
    indLem : ∀ {ℓ} {A : hopfS¹.TotalSpaceHopfPush → Type ℓ}
        → ((a : _) → isProp (A a))
        → A (inl base)
        → ((a : hopfS¹.TotalSpaceHopfPush) → A a)
    indLem {A = A} p b =
      PushoutToProp p
        (sphereElim 0 (λ _ → p _) b)
        (sphereElim 0 (λ _ → p _) (subst A (push (base , base)) b))

    main : (a : hopfS¹.TotalSpaceHopfPush)
         → PathP (λ i → ∥ Path CP² (push a i) (inl tt) ∥₂)
                  (conCP²' (inl tt)) (conCP²' (inr (hopfS¹.induced a)))
    main = indLem (λ _ → isOfHLevelPathP' 1 squash₂ _ _)
                   λ j → ∣ (λ i → push (inl base) (~ i ∧ j)) ∣₂

module GysinS² = Gysin (CP² , inl tt) fibr conCP² 2 idIso refl

E'S⁴Iso : Iso GysinS².E' (S₊ 5)
E'S⁴Iso =
  compIso IsoE*join
    (compIso (Iso→joinIso idIso (IsoSphereJoin 1 1))
             (IsoSphereJoin 1 3))

isContrH³E : isContr (coHom 3 (GysinS².E'))
isContrH³E =
  subst isContr (sym (isoToPath
                      (fst (Hⁿ-Sᵐ≅0 2 4
                      λ p → snotz (sym (cong (predℕ ∘ predℕ) p)))))
               ∙ cong (coHom 3) (sym (isoToPath E'S⁴Iso)))
        isContrUnit

isContrH⁴E : isContr (coHom 4 (GysinS².E'))
isContrH⁴E =
  subst isContr (sym (isoToPath
                     (fst (Hⁿ-Sᵐ≅0 3 4
                       λ p → snotz (sym (cong (predℕ ∘ predℕ ∘ predℕ) p)))))
               ∙ cong (coHom 4) (sym (isoToPath E'S⁴Iso)))
        isContrUnit

-- We will need a bunch of elimination principles
joinS¹S¹→Groupoid : ∀ {ℓ} (P : join S¹ S¹ → Type ℓ)
            → ((x : _) → isGroupoid (P x))
            → P (inl base)
            → (x : _) → P x
joinS¹S¹→Groupoid P grp b =
  transport (λ i → (x : (isoToPath (invIso (IsoSphereJoin 1 1))) i)
        → P (transp (λ j → isoToPath (invIso (IsoSphereJoin 1 1)) (i ∨ j)) i x))
        (sphereElim _ (λ _ → grp _) b)

TotalHopf→Gpd : ∀ {ℓ} (P : hopfS¹.TotalSpaceHopfPush → Type ℓ)
            → ((x : _) → isGroupoid (P x))
            → P (inl base)
            → (x : _) → P x
TotalHopf→Gpd P grp b =
  transport (λ i → (x : sym (isoToPath IsoTotalHopf') i)
                 → P (transp (λ j → isoToPath IsoTotalHopf' (~ i ∧ ~ j)) i x))
    (joinS¹S¹→Groupoid _ (λ _ → grp _) b)

TotalHopf→Gpd' : ∀ {ℓ} (P : Σ (S₊ 2) hopfS¹.Hopf → Type ℓ)
            → ((x : _) → isGroupoid (P x))
            → P (north , base)
            → (x : _) → P x
TotalHopf→Gpd' P grp b =
  transport (λ i → (x : ua (_ , hopfS¹.isEquivTotalSpaceHopfPush→TotalSpace) i)
          → P (transp (λ j → ua (_ , hopfS¹.isEquivTotalSpaceHopfPush→TotalSpace)
                                  (i ∨ j)) i x))
          (TotalHopf→Gpd _ (λ _ → grp _) b)

CP²→Groupoid : ∀ {ℓ} {P : CP² → Type ℓ}
               → ((x : _) → is2Groupoid (P x))
               → (b : P (inl tt))
               → (s2 : ((x : _) → P (inr x)))
               → PathP (λ i → P (push (inl base) i)) b (s2 north)
               → (x : _) → P x
CP²→Groupoid {P = P} grp b s2 pp (inl x) = b
CP²→Groupoid {P = P} grp b s2 pp (inr x) = s2 x
CP²→Groupoid {P = P} grp b s2 pp (push a i₁) = lem a i₁
  where
  lem : (a : hopfS¹.TotalSpaceHopfPush) → PathP (λ i → P (push a i)) b (s2 _)
  lem = TotalHopf→Gpd _  (λ _ → isOfHLevelPathP' 3 (grp _) _ _) pp

-- The function inducing the iso H²(S²) ≅ H²(CP²)
H²S²→H²CP²-raw : (S₊ 2 → coHomK 2) → (CP² → coHomK 2)
H²S²→H²CP²-raw f (inl x) = 0ₖ _
H²S²→H²CP²-raw f (inr x) = f x -ₖ f north
H²S²→H²CP²-raw f (push a i) =
    TotalHopf→Gpd (λ x → 0ₖ 2
  ≡ f (hopfS¹.TotalSpaceHopfPush→TotalSpace x .fst) -ₖ f north)
      (λ _ → isOfHLevelTrunc 4 _ _)
      (sym (rCancelₖ 2 (f north)))
      a i

H²S²→H²CP² : coHomGr 2 (S₊ 2) .fst → coHomGr 2 CP² .fst
H²S²→H²CP² = sMap H²S²→H²CP²-raw

cancel-H²S²→H²CP² : (f : CP² → coHomK 2)
  → ∥ H²S²→H²CP²-raw (f ∘ inr) ≡ f ∥₁
cancel-H²S²→H²CP² f =
  pRec squash₁
    (λ p → pRec squash₁
                (λ f → ∣ funExt f ∣₁)
                (cancelLem p))
    (connLem (f (inl tt)))
  where
  connLem : (x : coHomK 2) →  ∥ x ≡ 0ₖ 2 ∥₁
  connLem = coHomK-elim _ (λ _ → isOfHLevelSuc 1 squash₁) ∣ refl ∣₁

  cancelLem : (p : f (inl tt) ≡ 0ₖ 2)
    → ∥ ((x : CP²) → H²S²→H²CP²-raw (f ∘ inr) x ≡ f x) ∥₁
  cancelLem p = trRec squash₁ (λ pp →
    ∣ CP²→Groupoid (λ _ → isOfHLevelPath 4 (isOfHLevelTrunc 4) _ _)
                 (sym p)
                 (λ x → (λ i → f (inr x) -ₖ f (push (inl base) (~ i)))
                       ∙∙ (λ i → f (inr x) -ₖ p i)
                       ∙∙ rUnitₖ 2 (f (inr x))) pp ∣₁)
                help
    where
    help :
      hLevelTrunc 1
        (PathP (λ i₁ → H²S²→H²CP²-raw (f ∘ inr) (push (inl base) i₁)
                     ≡ f (push (inl base) i₁))
              (sym p)
              (((λ i₁ → f (inr north) -ₖ f (push (inl base) (~ i₁))) ∙∙
                (λ i₁ → f (inr north) -ₖ p i₁) ∙∙ rUnitₖ 2 (f (inr north)))))
    help = isConnectedPathP 1 (isConnectedPath 2 (isConnectedKn 1) _ _) _ _ .fst

H²CP²≅H²S² : GroupIso (coHomGr 2 CP²) (coHomGr 2 (S₊ 2))
Iso.fun (fst H²CP²≅H²S²) = sMap (_∘ inr)
Iso.inv (fst H²CP²≅H²S²) = H²S²→H²CP²
Iso.rightInv (fst H²CP²≅H²S²) =
  sElim (λ _ → isOfHLevelPath 2 squash₂ _ _)
    λ f → trRec {B = Iso.fun (fst H²CP²≅H²S²) (Iso.inv (fst H²CP²≅H²S²) ∣ f ∣₂)
                   ≡ ∣ f ∣₂}
    (isOfHLevelPath 2 squash₂ _ _)
    (λ p → cong ∣_∣₂ (funExt λ x → cong (λ y → (f x) -ₖ y) p ∙ rUnitₖ 2 (f x)))
    (Iso.fun (PathIdTruncIso _)
             (isContr→isProp (isConnectedKn 1) ∣ f north ∣ ∣ 0ₖ 2 ∣))
Iso.leftInv (fst H²CP²≅H²S²) =
  sElim (λ _ → isOfHLevelPath 2 squash₂ _ _)
    λ f → pRec (squash₂ _ _) (cong ∣_∣₂) (cancel-H²S²→H²CP² f)
snd H²CP²≅H²S² =
  makeIsGroupHom
    (sElim2 (λ _ _ → isOfHLevelPath 2 squash₂ _ _) λ f g → refl)

H²CP²≅ℤ : GroupIso (coHomGr 2 CP²) ℤGroup
H²CP²≅ℤ = compGroupIso H²CP²≅H²S² (Hⁿ-Sⁿ≅ℤ 1)

-- ⌣ gives a groupEquiv H²(CP²) ≃ H⁴(CP²)
⌣Equiv : GroupEquiv (coHomGr 2 CP²) (coHomGr 4 CP²)
fst (fst ⌣Equiv) = GysinS².⌣-hom 2 .fst
snd (fst ⌣Equiv) = isEq⌣
  where
  isEq⌣ : isEquiv (GysinS².⌣-hom 2 .fst)
  isEq⌣ =
    SES→isEquiv
      (GroupPath _ _ .fst (invGroupEquiv
        (isContr→≃Unit isContrH³E
        , makeIsGroupHom λ _ _ → refl)))
      (GroupPath _ _ .fst (invGroupEquiv
        (isContr→≃Unit isContrH⁴E
        , makeIsGroupHom λ _ _ → refl)))
      (GysinS².susp∘ϕ 1)
      (GysinS².⌣-hom 2)
      (GysinS².p-hom 4)
      (GysinS².Ker-⌣e⊂Im-Susp∘ϕ _)
      (GysinS².Ker-p⊂Im-⌣e _)
snd ⌣Equiv = GysinS².⌣-hom 2 .snd

-- The generator of H²(CP²)
genCP² : coHom 2 CP²
genCP² = ∣ CP²→Groupoid (λ _ → isOfHLevelTrunc 4)
                      (0ₖ _)
                      ∣_∣
                      refl ∣₂

inrInjective : (f g : CP² → coHomK 2) → ∥ f ∘ inr ≡ g ∘ inr ∥₁
             → Path (coHom 2 CP²) ∣ f ∣₂ ∣ g ∣₂
inrInjective f g = pRec (squash₂ _ _)
  (λ p → pRec (squash₂ _ _) (λ id → trRec (squash₂ _ _)
               (λ pp → cong ∣_∣₂
                 (funExt (CP²→Groupoid
                           (λ _ → isOfHLevelPath 4 (isOfHLevelTrunc 4) _ _)
                         id
                         (funExt⁻ p)
                         (compPathR→PathP pp))))
                   (conn2 (f (inl tt)) (g (inl tt)) id
                     (cong f (push (inl base))
                       ∙ (funExt⁻ p north) ∙ cong g (sym (push (inl base))))))
               (conn (f (inl tt)) (g (inl tt))))

  where
  conn : (x y : coHomK 2) → ∥ x ≡ y ∥₁
  conn = coHomK-elim _ (λ _ → isSetΠ λ _ → isOfHLevelSuc 1 squash₁)
           (coHomK-elim _ (λ _ → isOfHLevelSuc 1 squash₁) ∣ refl ∣₁)

  conn2 : (x y : coHomK 2) (p q : x ≡ y) → hLevelTrunc 1 (p ≡ q)
  conn2 x y p q =
    Iso.fun (PathIdTruncIso _)
      (isContr→isProp (isConnectedPath _ (isConnectedKn 1) x y) ∣ p ∣ ∣ q ∣)

-- A couple of basic lemma concerning the hSpace structure on S¹
private
  invLooperLem₁ : (a x : S¹)
    → (invEq (hopfS¹.μ-eq a) x) * a ≡ (invLooper a * x) * a
  invLooperLem₁ a x =
       secEq (hopfS¹.μ-eq a) x
    ∙∙ cong (_* x) (rCancelS¹ a)
    ∙∙ AssocHSpace.μ-assoc S1-AssocHSpace a (invLooper a) x
     ∙ commS¹ _ _

  invLooperLem₂ : (a x : S¹) → invEq (hopfS¹.μ-eq a) x ≡ invLooper a * x
  invLooperLem₂ a x = sym (retEq (hopfS¹.μ-eq a) (invEq (hopfS¹.μ-eq a) x))
          ∙∙ cong (invEq (hopfS¹.μ-eq a)) (invLooperLem₁ a x)
          ∙∙ retEq (hopfS¹.μ-eq a) (invLooper a * x)

  rotLoop² : (a : S¹) → Path (a ≡ a) (λ i → rotLoop (rotLoop a i) (~ i)) refl
  rotLoop² =
    sphereElim 0 (λ _ → isGroupoidS¹ _ _ _ _)
      λ i j → hcomp (λ {k → λ { (i = i1) → base
                                ; (j = i0) → base
                                ; (j = i1) → base}})
                    base

-- We prove that the generator of CP² given by Gysin is the same one
-- as genCP², which is much easier to work with
Gysin-e≡genCP² : GysinS².e ≡ genCP²
Gysin-e≡genCP² =
  inrInjective _ _ ∣ funExt (λ x → funExt⁻ (cong fst (main x)) south) ∣₁
  where
  mainId : (x : Σ (S₊ 2) hopfS¹.Hopf)
         → Path (hLevelTrunc 4 _) ∣ fst x ∣ ∣ north ∣
  mainId = uncurry λ { north → λ y → cong ∣_∣ₕ (merid base ∙ sym (merid y))
                     ; south → λ y → cong ∣_∣ₕ (sym (merid y))
                     ; (merid a i) → main a i}
    where
    main : (a : S¹) → PathP (λ i → (y : hopfS¹.Hopf (merid a i))
                    → Path (HubAndSpoke (Susp S¹) 3) ∣ merid a i ∣ ∣ north ∣)
                            (λ y → cong ∣_∣ₕ (merid base ∙ sym (merid y)))
                            λ y → cong ∣_∣ₕ (sym (merid y))
    main a =
      toPathP
        (funExt λ x →
          cong (transport (λ i₁
               → Path (HubAndSpoke (Susp S¹) 3) ∣ merid a i₁ ∣ ∣ north ∣))
               ((λ i → (λ z → cong ∣_∣ₕ
                         (merid base
                        ∙ sym (merid (transport
                            (λ j → uaInvEquiv (hopfS¹.μ-eq a) (~ i) j) x))) z))
               ∙ λ i → cong ∣_∣ₕ (merid base
                     ∙ sym (merid (transportRefl (invEq (hopfS¹.μ-eq a) x) i))))
      ∙∙ (λ i → transp (λ i₁ → Path (HubAndSpoke (Susp S¹) 3)
                                      ∣ merid a (i₁ ∨ i) ∣ ∣ north ∣) i
                        (compPath-filler' (cong ∣_∣ₕ (sym (merid a)))
                          (cong ∣_∣ₕ (merid base
                                  ∙ sym (merid (invLooperLem₂ a x i)))) i))
      ∙∙ cong ((cong ∣_∣ₕ) (sym (merid a)) ∙_)
          (cong (cong ∣_∣ₕ) (cong sym (symDistr (merid base)
                                               (sym (merid (invLooper a * x)))))
         ∙ cong sym (SuspS¹-hom (invLooper a) x)
         ∙ symDistr ((cong ∣_∣ₕ) (merid (invLooper a) ∙ sym (merid base)))
                     ((cong ∣_∣ₕ) (merid x ∙ sym (merid base)))
         ∙ isCommΩK 2 (sym (λ i₁ → ∣ (merid x
                                   ∙ (λ i₂ → merid base (~ i₂))) i₁ ∣))
                      (sym (λ i₁ → ∣ (merid (invLooper a)
                                   ∙ (λ i₂ → merid base (~ i₂))) i₁ ∣))
         ∙ cong₂ _∙_ (cong sym (SuspS¹-inv a)
                       ∙ cong-∙ ∣_∣ₕ (merid a) (sym (merid base)))
                     (cong (cong ∣_∣ₕ) (symDistr (merid x) (sym (merid base)))
                       ∙ cong-∙ ∣_∣ₕ (merid base) (sym (merid x))))
      ∙∙ (λ j → (λ i₁ → ∣ merid a (~ i₁ ∨ j) ∣) ∙ ((λ i₁ → ∣ merid a (i₁ ∨ j) ∣)
                 ∙ (λ i₁ → ∣ merid base (~ i₁ ∨ j) ∣))
                 ∙ (λ i₁ → ∣ merid base (i₁ ∨ j) ∣)
                 ∙ (λ i₁ → ∣ merid x (~ i₁) ∣ₕ))
      ∙∙ sym (lUnit _)
      ∙∙ sym (assoc _ _ _)
      ∙∙ (sym (lUnit _) ∙ sym (lUnit _) ∙ sym (lUnit _)))

  gen' : (x : S₊ 2) → preThom.Q (CP² , inl tt) fibr (inr x) →∙ coHomK-ptd 2
  fst (gen' x) north = ∣ north ∣
  fst (gen' x) south = ∣ x ∣
  fst (gen' x) (merid a i₁) = mainId (x , a) (~ i₁)
  snd (gen' x) = refl

  gen'Id : GysinS².c (inr north) .fst ≡ gen' north .fst
  gen'Id = cong fst (GysinS².cEq (inr north) ∣ push (inl base) ∣₂)
           ∙ (funExt lem)
    where
    lem : (qb : _) →
       ∣ (subst (fst ∘ preThom.Q (CP² , inl tt) fibr)
                (sym (push (inl base))) qb) ∣
      ≡ gen' north .fst qb
    lem north = refl
    lem south = cong ∣_∣ₕ (sym (merid base))
    lem (merid a i) j =
      hcomp (λ k →
        λ { (i = i0) → ∣ merid a (~ k ∧ j) ∣
          ; (i  = i1) → ∣ merid base (~ j) ∣
          ; (j = i0) → ∣ transportRefl (merid a i) (~ k) ∣
          ; (j = i1) → ∣ compPath-filler
                          (merid base) (sym (merid a)) k (~ i) ∣ₕ})
            (hcomp (λ k →
              λ { (i = i0) → ∣ merid a (j ∨ ~ k) ∣
                ; (i  = i1) → ∣ merid base (~ j ∨ ~ k) ∣
                ; (j = i0) → ∣ merid a (~ k ∨ i) ∣
                ; (j = i1) → ∣ merid base (~ i ∨ ~ k) ∣ₕ})
             ∣ south ∣)

  setHelp : (x : S₊ 2)
    → isSet (preThom.Q (CP² , inl tt) fibr (inr x) →∙ coHomK-ptd 2)
  setHelp = sphereElim _ (λ _ → isProp→isOfHLevelSuc 1 (isPropIsOfHLevel 2))
                            (isOfHLevel↑∙' 0 1)

  main : (x : S₊ 2) →  (GysinS².c (inr x) ≡ gen' x)
  main = sphereElim _ (λ x → isOfHLevelPath 2 (setHelp x) _ _)
         (→∙Homogeneous≡ (isHomogeneousKn _) gen'Id)

isGenerator≃ℤ : ∀ {ℓ} (G : Group ℓ) (g : fst G) → Type ℓ
isGenerator≃ℤ G g =
  Σ[ e ∈ GroupIso G ℤGroup ] abs (Iso.fun (fst e) g) ≡ 1

isGenerator≃ℤ-e : isGenerator≃ℤ (coHomGr 2 CP²) GysinS².e
isGenerator≃ℤ-e =
  subst (isGenerator≃ℤ (coHomGr 2 CP²)) (sym Gysin-e≡genCP²)
    (H²CP²≅ℤ , refl)

-- Alternative definition of the hopfMap
HopfMap' : S₊ 3 → S₊ 2
HopfMap' x =
  hopfS¹.TotalSpaceHopfPush→TotalSpace
    (Iso.inv IsoTotalHopf'
      (Iso.inv (IsoSphereJoin 1 1) x)) .fst

hopfMap≡HopfMap' : HopfMap ≡ (HopfMap' , refl)
hopfMap≡HopfMap' =
  ΣPathP ((funExt (λ x →
            cong (λ x → JoinS¹S¹→TotalHopf x .fst)
              (sym (Iso.rightInv IsoTotalHopf'
                   (Iso.inv (IsoSphereJoin 1 1) x)))
             ∙ sym (lem (Iso.inv IsoTotalHopf'
                   (Iso.inv (IsoSphereJoin 1 1) x)))))
         , flipSquare (sym (rUnit refl) ◁ λ _ _ → north))
  where
  lem : (x : _) → hopfS¹.TotalSpaceHopfPush→TotalSpace x .fst
               ≡ JoinS¹S¹→TotalHopf (Iso.fun IsoTotalHopf' x) .fst
  lem (inl x) = refl
  lem (inr x) = refl
  lem (push (base , snd₁) i) = refl
  lem (push (loop i₁ , a) i) k = merid (rotLoop² a (~ k) i₁) i

CP²' : Type _
CP²' = Pushout {A = S₊ 3} (λ _ → tt) HopfMap'

PushoutReplaceBase : ∀ {ℓ ℓ' ℓ''} {A B : Type ℓ} {C : Type ℓ'} {D : Type ℓ''}
       {f : A → C} {g : A → D} (e : B ≃ A)
    → Pushout (f ∘ fst e) (g ∘ fst e) ≡ Pushout f g
PushoutReplaceBase {f = f} {g = g} =
  EquivJ (λ _ e → Pushout (f ∘ fst e) (g ∘ fst e) ≡ Pushout f g) refl

CP2≡CP²' : CP²' ≡ CP²
CP2≡CP²' =
  PushoutReplaceBase
    (isoToEquiv (compIso (invIso (IsoSphereJoin 1 1)) (invIso IsoTotalHopf')))

-- packaging everything up:
⌣equiv→pres1 : ∀ {ℓ} {G H : Type ℓ} → (G ≡ H)
        → (g₁ : coHom 2 G) (h₁ : coHom 2 H)
        → (fstEq : (Σ[ ϕ ∈ GroupEquiv (coHomGr 2 G) ℤGroup ]
                      abs (fst (fst ϕ) g₁) ≡ 1))
        → (sndEq : ((Σ[ ϕ ∈ GroupEquiv (coHomGr 2 H) ℤGroup ]
                      abs (fst (fst ϕ) h₁) ≡ 1)))
        → isEquiv {A = coHom 2 G} {B = coHom 4 G} (_⌣ g₁)
        → (3rdEq : GroupEquiv (coHomGr 4 H) ℤGroup)
        → abs (fst (fst 3rdEq) (h₁ ⌣ h₁)) ≡ 1
⌣equiv→pres1 {G = G} = J (λ H _ → (g₁ : coHom 2 G) (h₁ : coHom 2 H)
        → (fstEq : (Σ[ ϕ ∈ GroupEquiv (coHomGr 2 G) ℤGroup ]
                      abs (fst (fst ϕ) g₁) ≡ 1))
        → (sndEq : ((Σ[ ϕ ∈ GroupEquiv (coHomGr 2 H) ℤGroup ]
                       abs (fst (fst ϕ) h₁) ≡ 1)))
        → isEquiv {A = coHom 2 G} {B = coHom 4 G} (_⌣ g₁)
        → (3rdEq : GroupEquiv (coHomGr 4 H) ℤGroup)
        → abs (fst (fst 3rdEq) (h₁ ⌣ h₁)) ≡ 1)
              help
  where
  help : (g₁ h₁ : coHom 2 G)
        → (fstEq : (Σ[ ϕ ∈ GroupEquiv (coHomGr 2 G) ℤGroup ]
                      abs (fst (fst ϕ) g₁) ≡ 1))
        → (sndEq : ((Σ[ ϕ ∈ GroupEquiv (coHomGr 2 G) ℤGroup ]
                       abs (fst (fst ϕ) h₁) ≡ 1)))
        → isEquiv {A = coHom 2 G} {B = coHom 4 G} (_⌣ g₁)
        → (3rdEq : GroupEquiv (coHomGr 4 G) ℤGroup)
        → abs (fst (fst 3rdEq) (h₁ ⌣ h₁)) ≡ 1
  help g h (ϕ , idg) (ψ , idh) ⌣eq ξ =
    ⊎→abs _ _
      (groupEquivPresGen _
        (compGroupEquiv main (compGroupEquiv (invGroupEquiv main) ψ))
        h (abs→⊎ _ _ (cong abs (cong (fst (fst ψ)) (retEq (fst main) h))
                     ∙ idh)) (compGroupEquiv main ξ))
    where
    lem₁ : ((fst (fst ψ) h) ≡ 1) ⊎ (fst (fst ψ) h ≡ -1)
      → abs (fst (fst ϕ) h) ≡ 1
    lem₁ p = ⊎→abs _ _ (groupEquivPresGen _ ψ h p ϕ)

    lem₂ : ((fst (fst ϕ) h) ≡ 1) ⊎ (fst (fst ϕ) h ≡ -1)
          → ((fst (fst ϕ) g) ≡ 1) ⊎ (fst (fst ϕ) g ≡ -1)
          → (h ≡ g) ⊎ (h ≡ (-ₕ g))
    lem₂ (inl x) (inl x₁) =
      inl (sym (retEq (fst ϕ) h)
        ∙∙ cong (invEq (fst ϕ)) (x ∙ sym x₁)
        ∙∙ retEq (fst ϕ) g)
    lem₂ (inl x) (inr x₁) =
      inr (sym (retEq (fst ϕ) h)
        ∙∙ cong (invEq (fst ϕ)) x
         ∙ IsGroupHom.presinv (snd (invGroupEquiv ϕ)) (negsuc zero)
         ∙∙ cong (-ₕ_) (cong (invEq (fst ϕ)) (sym x₁) ∙ (retEq (fst ϕ) g)))
    lem₂ (inr x) (inl x₁) =
      inr (sym (retEq (fst ϕ) h)
        ∙∙ cong (invEq (fst ϕ)) x
        ∙∙ (IsGroupHom.presinv (snd (invGroupEquiv ϕ)) 1
          ∙ cong (-ₕ_) (cong (invEq (fst ϕ)) (sym x₁) ∙ (retEq (fst ϕ) g))))
    lem₂ (inr x) (inr x₁) =
      inl (sym (retEq (fst ϕ) h)
        ∙∙ cong (invEq (fst ϕ)) (x ∙ sym x₁)
        ∙∙ retEq (fst ϕ) g)

    -ₕeq : ∀ {ℓ} {A : Type ℓ} (n : ℕ) → Iso (coHom n A) (coHom n A)
    Iso.fun (-ₕeq n) = -ₕ_
    Iso.inv (-ₕeq n) = -ₕ_
    Iso.rightInv (-ₕeq n) =
      sElim (λ _ → isOfHLevelPath 2 squash₂ _ _)
        λ f → cong ∣_∣₂ (funExt λ x → -ₖ^2 (f x))
    Iso.leftInv (-ₕeq n) =
      sElim (λ _ → isOfHLevelPath 2 squash₂ _ _)
        λ f → cong ∣_∣₂ (funExt λ x → -ₖ^2 (f x))

    theEq : coHom 2 G ≃ coHom 4 G
    theEq = compEquiv (_ , ⌣eq) (isoToEquiv (-ₕeq 4))

    lem₃ : (h ≡ g) ⊎ (h ≡ (-ₕ g)) → isEquiv {A = coHom 2 G} (_⌣ h)
    lem₃ (inl x) = subst isEquiv (λ i → _⌣ (x (~ i))) ⌣eq
    lem₃ (inr x) =
      subst isEquiv (funExt (λ x → -ₕDistᵣ 2 2 x g) ∙ (λ i → _⌣ (x (~ i))))
            (theEq .snd)

    main : GroupEquiv (coHomGr 2 G) (coHomGr 4 G)
    fst main = _ ,
      (lem₃ (lem₂ (abs→⊎ _ _ (lem₁ (abs→⊎ _ _ idh))) (abs→⊎ _ _ idg)))
    snd main =
      makeIsGroupHom λ g1 g2 → rightDistr-⌣ _ _ g1 g2 h

-- The hopf invariant is ±1 for both definitions of the hopf map
HopfInvariant-HopfMap' :
  abs (HopfInvariant zero (HopfMap' , λ _ → HopfMap' (snd (S₊∙ 3)))) ≡ 1
HopfInvariant-HopfMap' =
  cong abs (cong (Iso.fun (fst (Hopfβ-Iso zero (HopfMap' , refl))))
           (transportRefl (⌣-α 0 (HopfMap' , refl))))
            ∙ ⌣equiv→pres1 (sym CP2≡CP²')
                GysinS².e (Hopfα zero (HopfMap' , refl))
           (l isGenerator≃ℤ-e)
           (GroupIso→GroupEquiv (Hopfα-Iso 0 (HopfMap' , refl)) , refl)
           (snd (fst ⌣Equiv))
           (GroupIso→GroupEquiv (Hopfβ-Iso zero (HopfMap' , refl)))
  where
  l : Σ[ ϕ ∈ GroupIso (coHomGr 2 CP²) ℤGroup ]
       (abs (Iso.fun (fst ϕ) GysinS².e) ≡ 1)
      → Σ[ ϕ ∈ GroupEquiv (coHomGr 2 CP²) ℤGroup ]
          (abs (fst (fst ϕ) GysinS².e) ≡ 1)
  l p = (GroupIso→GroupEquiv (fst p)) , (snd p)

HopfInvariant-HopfMap : abs (HopfInvariant zero HopfMap) ≡ 1
HopfInvariant-HopfMap = cong abs (cong (HopfInvariant zero) hopfMap≡HopfMap')
                      ∙ HopfInvariant-HopfMap'


{-

Maybe structure: X ↦ Maybe (S X)

-}
{-# OPTIONS --cubical --no-import-sorts --no-exact-split --safe #-}
module Cubical.Structures.Relational.Maybe where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Function
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Structure
open import Cubical.Foundations.RelationalStructure
open import Cubical.Data.Unit
open import Cubical.Data.Empty
open import Cubical.Data.Maybe
open import Cubical.Data.Sigma
open import Cubical.HITs.PropositionalTruncation as Trunc
open import Cubical.HITs.SetQuotients

open import Cubical.Structures.Maybe

private
  variable
    ℓ ℓ₁ ℓ₁' ℓ₁'' : Level

-- Structured relations

MaybeRelStr : {S : Type ℓ → Type ℓ₁} {ℓ₁' : Level}
  → StrRel S ℓ₁' → StrRel (λ X → Maybe (S X)) ℓ₁'
MaybeRelStr ρ R = MaybeRel (ρ R)

maybeSuitableRel : {S : Type ℓ → Type ℓ₁} {ρ : StrRel S ℓ₁'}
  → SuitableStrRel S ρ
  → SuitableStrRel (MaybeStructure S) (MaybeRelStr ρ)
maybeSuitableRel θ .quo (X , nothing) R _ .fst = nothing , _
maybeSuitableRel θ .quo (X , nothing) R _ .snd (nothing , _) = refl
maybeSuitableRel θ .quo (X , just s) R c .fst =
  just (θ .quo (X , s) R c .fst .fst) , θ .quo (X , s) R c .fst .snd
maybeSuitableRel θ .quo (X , just s) R c .snd (just s' , r) =
  cong (λ {(t , r') → just t , r'}) (θ .quo (X , s) R c .snd (s' , r))
maybeSuitableRel θ .symmetric R {nothing} {nothing} r = _
maybeSuitableRel θ .symmetric R {just s} {just t} r = θ .symmetric R r
maybeSuitableRel θ .transitive R R' {nothing} {nothing} {nothing} r r' = _
maybeSuitableRel θ .transitive R R' {just s} {just t} {just u} r r' = θ .transitive R R' r r'
maybeSuitableRel θ .set setX = isOfHLevelMaybe 0 (θ .set setX)
maybeSuitableRel θ .prop propR nothing nothing = isOfHLevelLift 1 isPropUnit
maybeSuitableRel θ .prop propR nothing (just y) = isOfHLevelLift 1 isProp⊥
maybeSuitableRel θ .prop propR (just x) nothing = isOfHLevelLift 1 isProp⊥
maybeSuitableRel θ .prop propR (just x) (just y) = θ .prop propR x y

maybeRelMatchesEquiv : {S : Type ℓ → Type ℓ₁} (ρ : StrRel S ℓ₁') {ι : StrEquiv S ℓ₁''}
  → StrRelMatchesEquiv ρ ι
  → StrRelMatchesEquiv (MaybeRelStr ρ) (MaybeEquivStr ι)
maybeRelMatchesEquiv ρ μ (X , nothing) (Y , nothing) _ = Lift≃Lift (idEquiv _)
maybeRelMatchesEquiv ρ μ (X , nothing) (Y , just y) _ = Lift≃Lift (idEquiv _)
maybeRelMatchesEquiv ρ μ (X , just x) (Y , nothing) _ = Lift≃Lift (idEquiv _)
maybeRelMatchesEquiv ρ μ (X , just x) (Y , just y) = μ (X , x) (Y , y)

maybeRelAction :
  {S : Type ℓ → Type ℓ₁} {ρ : StrRel S ℓ₁'}
  → StrRelAction ρ
  → StrRelAction (MaybeRelStr ρ)
maybeRelAction α .actStr f = map-Maybe (α .actStr f)
maybeRelAction α .actStrId s =
  funExt⁻ (cong map-Maybe (funExt (α .actStrId))) s ∙ map-Maybe-id s
maybeRelAction α .actRel h nothing nothing = _
maybeRelAction α .actRel h (just s) (just t) r = α .actRel h s t r

maybePositiveRel :
  {S : Type ℓ → Type ℓ₁} {ρ : StrRel S ℓ₁'} {θ : SuitableStrRel S ρ}
  → PositiveStrRel θ
  → PositiveStrRel (maybeSuitableRel θ)
maybePositiveRel σ .act = maybeRelAction (σ .act)
maybePositiveRel σ .reflexive nothing = _
maybePositiveRel σ .reflexive (just s) = σ .reflexive s
maybePositiveRel σ .detransitive R R' {nothing} {nothing} r = ∣ nothing , _ , _ ∣
maybePositiveRel σ .detransitive R R' {just s} {just u} rr' =
  Trunc.map (λ {(t , r , r') → just t , r , r'}) (σ .detransitive R R' rr')
maybePositiveRel {S = S} {ρ = ρ} {θ = θ} σ .quo {X} R =
  subst isEquiv
    (funExt
      (elimProp (λ _ → maybeSuitableRel θ .set squash/ _ _)
        (λ {nothing → refl; (just _) → refl})))
    (compEquiv (isoToEquiv isom) (congMaybeEquiv (_ , σ .quo R)) .snd)
  where
  fwd : Maybe (S X) / MaybeRel (ρ (R .fst .fst)) → Maybe (S X / ρ (R .fst .fst))
  fwd [ nothing ] = nothing
  fwd [ just s ] = just [ s ]
  fwd (eq/ nothing nothing r i) = nothing
  fwd (eq/ (just s) (just t) r i) = just (eq/ s t r i)
  fwd (squash/ _ _ p q i j) =
    isOfHLevelMaybe 0 squash/ _ _ (cong fwd p) (cong fwd q) i j

  bwd : Maybe (S X / ρ (R .fst .fst)) → Maybe (S X) / MaybeRel (ρ (R .fst .fst))
  bwd nothing = [ nothing ]
  bwd (just [ s ]) = [ just s ]
  bwd (just (eq/ s t r i)) = eq/ (just s) (just t) r i
  bwd (just (squash/ _ _ p q i j)) =
    squash/ _ _ (cong (bwd ∘ just) p) (cong (bwd ∘ just) q) i j

  open Iso
  isom : Iso (Maybe (S X) / MaybeRel (ρ (R .fst .fst))) (Maybe (S X / ρ (R .fst .fst)))
  isom .fun = fwd
  isom .inv = bwd
  isom .rightInv nothing = refl
  isom .rightInv (just x) =
    elimProp {B = λ x → fwd (bwd (just x)) ≡ just x}
      (λ _ → isOfHLevelMaybe 0 squash/ _ _)
      (λ _ → refl)
      x
  isom .leftInv = elimProp (λ _ → squash/ _ _) (λ {nothing → refl; (just _) → refl})

maybeRelMatchesTransp : {S : Type ℓ → Type ℓ₁}
  (ρ : StrRel S ℓ₁') (α : EquivAction S)
  → StrRelMatchesEquiv ρ (EquivAction→StrEquiv α)
  → StrRelMatchesEquiv (MaybeRelStr ρ) (EquivAction→StrEquiv (maybeEquivAction α))
maybeRelMatchesTransp _ _ μ (X , nothing) (Y , nothing) _ =
  isContr→Equiv (isOfHLevelLift 0 isContrUnit) isContr-nothing≡nothing
maybeRelMatchesTransp _ _ μ (X , nothing) (Y , just y) _ =
  uninhabEquiv lower ¬nothing≡just
maybeRelMatchesTransp _ _ μ (X , just x) (Y , nothing) _ =
  uninhabEquiv lower ¬just≡nothing
maybeRelMatchesTransp _ _ μ (X , just x) (Y , just y) e =
  compEquiv (μ (X , x) (Y , y) e) (_ , isEmbedding-just _ _)


module Issue206 where

postulate
  I : Set
  P : I → Set
  i : I
  Q : P i → Set

Foo : (p : P i) → Q p → Set₁
Foo p q with i
Foo p q | i′ = Set

-- Now better error message:
-- Issue206.agda:11,1-19
-- w != i of type I
-- when checking that the type of the generated with function
-- (w : I) (p : P w) (q : Q p) → Set₁ is well-formed


{-# OPTIONS --safe #-}

open import Generics.Prelude hiding (lookup; pi; curry)
open import Generics.Telescope
open import Generics.Desc
open import Generics.All
open import Generics.HasDesc
import Generics.Helpers as Helpers

module Generics.Constructions.Fold
  {P I ℓ} {A : Indexed P I ℓ} (H : HasDesc {P} {I} {ℓ} A)
  {p c} {X : Pred′ I λ i → Set c}
  where

open HasDesc H

private
  variable
    V : ExTele P
    i : ⟦ I ⟧tel p
    v : ⟦ V ⟧tel p

X′ : ⟦ I ⟧tel p → Set c
X′ i = unpred′ I _ X i

------------------------
-- Types of the algebra

levelAlg : ConDesc P V I → Level
levelAlg (var _) = c
levelAlg (π {ℓ} _ _ C) = ℓ ⊔ levelAlg C
levelAlg (A ⊗ B) = levelAlg A ⊔ levelAlg B

AlgIndArg : (C : ConDesc P V I) → ⟦ V ⟧tel p → Set (levelAlg C)
AlgIndArg (var f) v = X′ (f (p , v))
AlgIndArg (π ia S C) v = Π< ia > (S (p , v)) λ s → AlgIndArg C (v , s)
AlgIndArg (A ⊗ B) v = AlgIndArg A v × AlgIndArg B v

AlgCon : (C : ConDesc P V I) → ⟦ V ⟧tel p → Set (levelAlg C)
AlgCon (var f) v = X′ (f (p , v))
AlgCon (π ia S C) v = Π< ia > (S (p , v)) λ s → AlgCon C (v , s)
AlgCon (A ⊗ B) v = AlgIndArg A v → AlgCon B v

Algebra : ∀ k → Set (levelAlg (lookupCon D k))
Algebra k = AlgCon (lookupCon D k) tt

----------------
-- Generic fold

module _ (algs : Els Algebra) where

  fold-wf : (x : A′ (p , i)) → Acc x → X′ i

  foldData-wf : (x : ⟦ D ⟧Data A′ (p , i)) → AllDataω Acc D x → X′ i
  foldData-wf {i} (k , x) = foldCon (lookupCon D k) (algs k) x
    where
      foldIndArg : (C : ConDesc P V I)
                   (x : ⟦ C ⟧IndArg A′ (p , v))
                 → AllIndArgω Acc C x
                 → AlgIndArg C v
      foldIndArg (var f) x a = fold-wf x a
      foldIndArg (π ia S C) x a = fun< ia > λ s → foldIndArg C (app< ia > x s) (a s)
      foldIndArg (A ⊗ B) (xa , xb) (aa , ab)
        = foldIndArg A xa aa
        , foldIndArg B xb ab

      foldCon : (C   : ConDesc P V I)
                (alg : AlgCon C v)
                (x   : ⟦ C ⟧Con A′ (p , v , i))
              → AllConω Acc C x
              → X′ i
      foldCon (var _) alg refl _ = alg
      foldCon (π ia S C) alg (s , x) a = foldCon C (app< ia > alg s) x a
      foldCon (A ⊗ B) alg (xa , xb) (aa , ab)
        = foldCon B (alg (foldIndArg A xa aa)) xb ab

  fold-wf x (acc a) = foldData-wf (split x) a

  fold : A′ (p , i) → X′ i
  fold x = fold-wf x (wf x)

deriveFold : Arrows Algebra (Pred′ I λ i → A′ (p , i) → X′ i)
deriveFold = curryₙ λ m → pred′ I _ λ i → fold m


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

open import lib.Basics
open import lib.types.Coproduct
open import lib.types.Paths
open import lib.types.Pointed
open import lib.types.Pushout
open import lib.types.PushoutFlattening
open import lib.types.Span
open import lib.types.Unit

-- Wedge of two pointed types is defined as a particular case of pushout

module lib.types.Wedge where

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

  wedge-span : Span
  wedge-span = span (fst X) (fst Y) Unit (λ _ → snd X) (λ _ → snd Y)

  Wedge : Type (lmax i j)
  Wedge = Pushout wedge-span

  infix 80 _∨_
  _∨_ = Wedge

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

  winl : fst X → X ∨ Y
  winl x = left x

  winr : fst Y → X ∨ Y
  winr y = right y

  wglue : winl (snd X) == winr (snd Y)
  wglue = glue tt

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

  ⊙Wedge : Ptd (lmax i j)
  ⊙Wedge = ⊙[ Wedge X Y , winl (snd X) ]

  infix 80 _⊙∨_
  _⊙∨_ = ⊙Wedge

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

  ⊙winl : fst (X ⊙→ X ⊙∨ Y)
  ⊙winl = (winl , idp)

  ⊙winr : fst (Y ⊙→ X ⊙∨ Y)
  ⊙winr = (winr , ! wglue)

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

  module WedgeElim {k} {P : X ∨ Y → Type k}
    (winl* : (x : fst X) → P (winl x)) (winr* : (y : fst Y) → P (winr y))
    (wglue* : winl* (snd X) == winr* (snd Y) [ P ↓ wglue ]) where

    private
      module M = PushoutElim winl* winr* (λ _ → wglue*)

    f = M.f
    glue-β = M.glue-β unit

  open WedgeElim public using () renaming (f to Wedge-elim)

  module WedgeRec {k} {C : Type k} (winl* : fst X → C) (winr* : fst Y → C)
    (wglue* : winl* (snd X) == winr* (snd Y)) where

    private
      module M = PushoutRec {d = wedge-span X Y} winl* winr* (λ _ → wglue*)

    f = M.f
    glue-β = M.glue-β unit

add-wglue : ∀ {i j} {X : Ptd i} {Y : Ptd j}
  → fst (X ⊙⊔ Y) → X ∨ Y
add-wglue (inl x) = winl x
add-wglue (inr y) = winr y

⊙add-wglue : ∀ {i j} {X : Ptd i} {Y : Ptd j}
  → fst (X ⊙⊔ Y ⊙→ X ⊙∨ Y)
⊙add-wglue = (add-wglue , idp)

module ⊙WedgeRec {i j k} {X : Ptd i} {Y : Ptd j} {Z : Ptd k}
  (g : fst (X ⊙→ Z)) (h : fst (Y ⊙→ Z)) where

  open WedgeRec (fst g) (fst h) (snd g ∙ ! (snd h)) public

  ⊙f : fst (X ⊙∨ Y ⊙→ Z)
  ⊙f = (f , snd g)

  ⊙winl-β : ⊙f ⊙∘ ⊙winl == g
  ⊙winl-β = idp

  ⊙winr-β : ⊙f ⊙∘ ⊙winr == h
  ⊙winr-β = ⊙λ= (λ _ → idp) $
    ap (λ w → w ∙ snd g)
       (ap-! f wglue ∙ ap ! glue-β ∙ !-∙ (snd g) (! (snd h)))
    ∙ ∙-assoc (! (! (snd h))) (! (snd g)) (snd g)
    ∙ ap (λ w → ! (! (snd h)) ∙ w) (!-inv-l (snd g))
    ∙ ∙-unit-r (! (! (snd h)))
    ∙ !-! (snd h)

⊙wedge-rec = ⊙WedgeRec.⊙f

⊙wedge-rec-post∘ : ∀ {i j k l} {X : Ptd i} {Y : Ptd j} {Z : Ptd k} {W : Ptd l}
  (k : fst (Z ⊙→ W)) (g : fst (X ⊙→ Z)) (h : fst (Y ⊙→ Z))
  → k ⊙∘ ⊙wedge-rec g h == ⊙wedge-rec (k ⊙∘ g) (k ⊙∘ h)
⊙wedge-rec-post∘ k g h = ⊙λ=
  (Wedge-elim (λ _ → idp) (λ _ → idp)
    (↓-='-in $ ⊙WedgeRec.glue-β (k ⊙∘ g) (k ⊙∘ h)
               ∙ lemma (fst k) (snd g) (snd h) (snd k)
               ∙ ! (ap (ap (fst k)) (⊙WedgeRec.glue-β g h))
               ∙ ∘-ap (fst k) (fst (⊙wedge-rec g h)) wglue))
  idp
  where
  lemma : ∀ {i j} {A : Type i} {B : Type j} (f : A → B) {x y z : A} {w : B}
    (p : x == z) (q : y == z) (r : f z == w)
    → (ap f p ∙ r) ∙ ! (ap f q ∙ r) == ap f (p ∙ ! q)
  lemma f idp idp idp = idp

⊙wedge-rec-η : ∀ {i j} {X : Ptd i} {Y : Ptd j}
  → ⊙wedge-rec ⊙winl ⊙winr == ⊙idf (X ⊙∨ Y)
⊙wedge-rec-η = ⊙λ=
  (Wedge-elim (λ _ → idp) (λ _ → idp)
    (↓-='-in $ ap-idf wglue
               ∙ ! (!-! wglue)
               ∙ ! (⊙WedgeRec.glue-β ⊙winl ⊙winr)))
  idp

module Fold {i} {X : Ptd i} = ⊙WedgeRec (⊙idf X) (⊙idf X)
fold = Fold.f
⊙fold = Fold.⊙f

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

  module Projl = ⊙WedgeRec (⊙idf X) (⊙cst {X = Y})
  module Projr = ⊙WedgeRec (⊙cst {X = X}) (⊙idf Y)

  projl = Projl.f
  projr = Projr.f
  ⊙projl = Projl.⊙f
  ⊙projr = Projr.⊙f


-- Andreas, 2013-10-26, reported by Jesper Cockx

{-# OPTIONS --cubical-compatible #-}

module Issue920a where

import Common.Level
open import Common.Equality

record ⊥ : Set where

data Bool : Set where
  true false : Bool

-- Standard eliminator for ≡
J : ∀ {a b} {A : Set a} {x : A} {Φ : (y : A) → x ≡ y → Set b} →
            Φ x refl → {y : A} → (e : x ≡ y) → Φ y e
J φ refl = φ

-- A kind of heterogeneous equality
_≃_ : {A : Set} (x : A) {A' : Set} → A' → Set _
_≃_ {A} x {A'} x' = (E : A ≡ A') → J x E ≡ x'

-- It shouldn't be possible to define this without K
≃refl : {A : Set} {x : A} → x ≃ x
≃refl {x = x} = λ E → J {Φ = λ A' E' → J x E' ≡ _} refl E

-- These can be given using univalence
postulate Swap : Bool ≡ Bool
postulate swap : true ≡ J {Φ = λ A _ → A} false Swap

-- Univalence and ≃refl don't play nice together
right : (true ≡ false) → ⊥
right ()

wrong : true ≡ false
wrong = trans swap (≃refl Swap)

madness : ⊥
madness = right wrong


module Printf where
import AlonzoPrelude
import PreludeList
import PreludeShow
import PreludeString
import PreludeNat

open AlonzoPrelude
open PreludeList hiding (_++_)
open PreludeShow
open PreludeString
open PreludeNat

data Unit : Set where
  unit : Unit

data Format : Set where
  stringArg : Format
  natArg    : Format
  intArg    : Format
  floatArg  : Format
  charArg   : Format
  litChar   : Char -> Format
  badFormat : Char -> Format

data BadFormat (c : Char) : Set where

format : String -> List Format
format s = format' (toList s)
  where
    format' : List Char -> List Format
    format' ('%' :: 's' :: fmt) = stringArg   :: format' fmt
    format' ('%' :: 'n' :: fmt) = natArg      :: format' fmt
    format' ('%' :: 'd' :: fmt) = intArg      :: format' fmt
    format' ('%' :: 'f' :: fmt) = floatArg    :: format' fmt
    format' ('%' :: 'c' :: fmt) = charArg     :: format' fmt
    format' ('%' :: '%' :: fmt) = litChar '%' :: format' fmt
    format' ('%' ::  c  :: fmt) = badFormat c :: format' fmt
    format' (c		:: fmt) = litChar c   :: format' fmt
    format'  []		= []

Printf' : List Format -> Set
Printf' (stringArg   :: fmt) = String  × Printf' fmt
Printf' (natArg      :: fmt) = Nat     × Printf' fmt
Printf' (intArg      :: fmt) = Int     × Printf' fmt
Printf' (floatArg    :: fmt) = Float   × Printf' fmt
Printf' (charArg     :: fmt) = Char    × Printf' fmt
Printf' (badFormat c :: fmt) = BadFormat c
Printf' (litChar _   :: fmt) = Printf' fmt
Printf'  []		     = Unit

Printf : String -> Set
Printf fmt = Printf' (format fmt)

{-
printf' : (fmt : List Format) -> Printf' fmt -> String
printf' (stringArg   :: fmt) < s , args > = s			 ++ printf' fmt args
printf' (natArg      :: fmt) < n , args > = showNat n		 ++ printf' fmt args
printf' (intArg      :: fmt) < n , args > = showInt n		 ++ printf' fmt args
printf' (floatArg    :: fmt) < x , args > = showFloat x	 ++ printf' fmt args
printf' (charArg     :: fmt) < c , args > = showChar c	 ++ printf' fmt args
printf' (litChar c   :: fmt) args = fromList (c :: []) ++ printf' fmt args
printf' (badFormat _ :: fmt) ()
printf'  []		 unit = ""
-}
printf : (fmt : String) -> Printf fmt -> String
printf fmt = printf' (format fmt)
  where
    printf' : (fmt : List Format) -> Printf' fmt -> String
    printf' (stringArg   :: fmt) < s , args > = s			 ++ printf' fmt args
    printf' (natArg      :: fmt) < n , args > = showNat n		 ++ printf' fmt args
    printf' (intArg      :: fmt) < n , args > = showInt n		 ++ printf' fmt args
    printf' (floatArg    :: fmt) < x , args > = showFloat x	 ++ printf' fmt args
    printf' (charArg     :: fmt) < c , args > = showChar c	 ++ printf' fmt args
    printf' (litChar c   :: fmt) args	    = fromList (c :: []) ++ printf' fmt args
    printf' (badFormat _ :: fmt) ()
    printf'  []		 unit 	    = ""

mainS : String
-- mainS = printf  "pi = %f"  < 3.14 , unit >
mainS = printf "Answer is %n, pi = %f %% %s"
	< 42 , < 3.14159 , < "Alonzo" , unit > > >


------------------------------------------------------------------------
-- Trees
------------------------------------------------------------------------

-- This example is based on one in Wadler's "A prettier printer".

{-# OPTIONS --guardedness #-}

module Examples.Tree where

open import Codata.Musical.Notation
open import Data.List
open import Data.List.NonEmpty as List⁺
open import Relation.Binary.PropositionalEquality using (_≡_; refl)

open import Examples.Identifier
import Grammar.Infinite
import Pretty
open import Renderer
open import Utilities

data Tree : Set where
  node : Identifier → List Tree → Tree

module _ where

  open Grammar.Infinite

  mutual

    tree : Grammar Tree
    tree = node <$> identifier-w ⊛ brackets

    brackets : Grammar (List Tree)
    brackets = return []
             ∣ List⁺.toList <$> (symbol′ "[" ⊛> ♯ trees <⊛ symbol′ "]")

    trees : Grammar (List⁺ Tree)
    trees = _∷_ <$> tree ⊛ commas-and-trees

    commas-and-trees : Grammar (List Tree)
    commas-and-trees = (symbol′ "," ⊛> tree) ⋆

open Pretty

-- Wadler presents two pretty-printers for trees in his final code
-- listing (§11.7). I've included corresponding, but not quite
-- identical, implementations here.
--
-- (One of Wadler's implementations is buggy: recursive calls to
-- showTree/showTrees should most likely have referred to
-- showTree'/showTrees'. The code below is intended to match a
-- corrected version of Wadler's.)

module Printer₁ where

  mutual

    tree-printer : Pretty-printer tree
    tree-printer (node s ts) =
      group (<$> identifier-w-printer s ⊛
             nest (List⁺.length s) (brackets-printer ts))

    brackets-printer : Pretty-printer brackets
    brackets-printer []       = left nil
    brackets-printer (t ∷ ts) =
      right (<$> (symbol ⊛> nest 1 (trees-printer (t ∷ ts)) <⊛ symbol))

    trees-printer : Pretty-printer trees
    trees-printer (t ∷ ts) =
      <$> tree-printer t ⊛ commas-and-trees-printer ts

    commas-and-trees-printer : Pretty-printer commas-and-trees
    commas-and-trees-printer []       = nil-⋆
    commas-and-trees-printer (t ∷ ts) =
      cons-⋆ (symbol-line ⊛> tree-printer t)
             (commas-and-trees-printer ts)

module Printer₂ where

  mutual

    tree-printer : Pretty-printer tree
    tree-printer (node s ts) =
      <$> identifier-w-printer s ⊛ brackets-printer ts

    brackets-printer : Pretty-printer brackets
    brackets-printer []       = left nil
    brackets-printer (t ∷ ts) =
      right (<$> bracket 7 (trees-printer (t ∷ ts)))

    trees-printer : Pretty-printer trees
    trees-printer (t ∷ ts) =
      <$> tree-printer t ⊛ commas-and-trees-printer ts

    commas-and-trees-printer : Pretty-printer commas-and-trees
    commas-and-trees-printer []       = nil-⋆
    commas-and-trees-printer (t ∷ ts) =
      cons-⋆ (symbol-line ⊛> tree-printer t)
             (commas-and-trees-printer ts)

t : Tree
t = node (str⁺ "aaa")
      (node (str⁺ "bbbbb")
         (node (str⁺ "ccc") [] ∷
          node (str⁺ "dd") [] ∷
          []) ∷
       node (str⁺ "eee") [] ∷
       node (str⁺ "ffff")
         (node (str⁺ "gg") [] ∷
          node (str⁺ "hhh") [] ∷
          node (str⁺ "ii") [] ∷
          []) ∷
       [])

test₁ : render 0 (Printer₁.tree-printer t) ≡
        "aaa[bbbbb[ccc,\n          dd],\n    eee,\n    ffff[gg,\n         hhh,\n         ii]]"
test₁ = refl

test₂ : render 30 (Printer₁.tree-printer t) ≡
        "aaa[bbbbb[ccc, dd],\n    eee,\n    ffff[gg, hhh, ii]]"
test₂ = refl

test₃ : render 0 (Printer₂.tree-printer t) ≡
        "aaa[\n  bbbbb[\n    ccc,\n    dd\n  ],\n  eee,\n  ffff[\n    gg,\n    hhh,\n    ii\n  ]\n]"
test₃ = refl

test₄ : render 80 (Printer₂.tree-printer t) ≡
        "aaa[ bbbbb[ ccc, dd ], eee, ffff[ gg, hhh, ii ] ]"
test₄ = refl


module Issue292-19 where

postulate
  I     : Set
  i₁ i₂ : I
  J     : Set
  j     : I → J

data D : I → Set where
  d₁ : D i₁
  d₂ : D i₂

data P : ∀ i → D i → Set where
  p₁ : P i₁ d₁
  p₂ : P i₂ d₂

data P′ : ∀ i → D i → Set where
  p₁ : P′ i₁ d₁

data E : J → Set where
  e₁ : E (j i₁)
  e₂ : E (j i₂)

data Q : ∀ i → E i → Set where
  q₁ : Q (j i₁) e₁
  q₂ : Q (j i₂) e₂

Ok : Q (j i₁) e₁ → Set₁
Ok q₁ = Set

AlsoOk : P i₁ d₁ → Set₁
AlsoOk p₁ = Set

Foo : ∀ {i} (d : D i) → P′ i d → Set₁
Foo d₁ _  = Set
Foo d₂ ()


-- Andreas, 2019-12-03, issue #4200 reported and testcase by nad

open import Agda.Builtin.Bool

data D : Set where
  c₁ : D
  @0 c₂ : D

f : D → Bool
f c₁ = true
f c₂ = false

@0 _ : D
_ = c₂  -- OK.

_ : D
_ = c₂  -- Not allowed.

-- Expected error:

-- Identifier c₂ is declared erased, so it cannot be used here
-- when checking that the expression c₂ has type D


open import Nat
open import Prelude
open import List
open import judgemental-erase
open import lemmas-matching
open import statics-core
open import synth-unicity

module moveerase where
  -- type actions don't change the term other than moving the cursor
  -- around
  moveeraset : {t t' : ztyp} {δ : direction} →
               (t + move δ +> t') →
               (t ◆t) == (t' ◆t)
  moveeraset TMArrChild1 = refl
  moveeraset TMArrChild2 = refl
  moveeraset TMArrParent1 = refl
  moveeraset TMArrParent2 = refl
  moveeraset (TMArrZip1 {t2 = t2} m) = ap1 (λ x → x ==> t2) (moveeraset m)
  moveeraset (TMArrZip2 {t1 = t1} m) = ap1 (λ x → t1 ==> x) (moveeraset m)
  moveeraset TMPlusChild1 = refl
  moveeraset TMPlusChild2 = refl
  moveeraset TMPlusParent1 = refl
  moveeraset TMPlusParent2 = refl
  moveeraset (TMPlusZip1 {t2 = t2} m) = ap1 (λ x → x ⊕ t2) (moveeraset m)
  moveeraset (TMPlusZip2 {t1 = t1} m) = ap1 (λ x → t1 ⊕ x) (moveeraset m)
  moveeraset TMProdChild1 = refl
  moveeraset TMProdChild2 = refl
  moveeraset TMProdParent1 = refl
  moveeraset TMProdParent2 = refl
  moveeraset (TMProdZip1 {t2 = t2} m) = ap1 (λ x → x ⊠ t2) (moveeraset m)
  moveeraset (TMProdZip2 {t1 = t1} m) = ap1 (λ x → t1 ⊠ x) (moveeraset m)

  -- the actual movements don't change the erasure
  moveerase : {e e' : zexp} {δ : direction} →
              (e + move δ +>e e') →
              (e ◆e) == (e' ◆e)
  moveerase EMAscChild1 = refl
  moveerase EMAscChild2 = refl
  moveerase EMAscParent1 = refl
  moveerase EMAscParent2 = refl
  moveerase EMLamChild1 = refl
  moveerase EMLamParent = refl
  moveerase EMHalfLamChild1 = refl
  moveerase EMHalfLamChild2 = refl
  moveerase EMHalfLamParent1 = refl
  moveerase EMHalfLamParent2 = refl
  moveerase EMPlusChild1 = refl
  moveerase EMPlusChild2 = refl
  moveerase EMPlusParent1 = refl
  moveerase EMPlusParent2 = refl
  moveerase EMApChild1 = refl
  moveerase EMApChild2 = refl
  moveerase EMApParent1 = refl
  moveerase EMApParent2 = refl
  moveerase EMNEHoleChild1 = refl
  moveerase EMNEHoleParent = refl
  moveerase EMInlChild1 = refl
  moveerase EMInlParent = refl
  moveerase EMInrChild1 = refl
  moveerase EMInrParent = refl
  moveerase EMCaseParent1 = refl
  moveerase EMCaseParent2 = refl
  moveerase EMCaseParent3 = refl
  moveerase EMCaseChild1 = refl
  moveerase EMCaseChild2 = refl
  moveerase EMCaseChild3 = refl
  moveerase EMPairChild1 = refl
  moveerase EMPairChild2 = refl
  moveerase EMPairParent1 = refl
  moveerase EMPairParent2 = refl
  moveerase EMFstChild1 = refl
  moveerase EMFstParent = refl
  moveerase EMSndChild1 = refl
  moveerase EMSndParent = refl

  -- this form is essentially the same as above, but for judgemental
  -- erasure, which is sometimes more convenient.
  moveerasee' : {e e' : zexp} {e◆ : hexp} {δ : direction} →
                erase-e e e◆ →
                e + move δ +>e e' →
                erase-e e' e◆
  moveerasee' er1 m with erase-e◆ er1
  ... | refl = ◆erase-e _ _ (! (moveerase m))

  moveeraset' : ∀{t t◆ t'' δ} →
                erase-t t t◆ →
                t + move δ +> t'' →
                erase-t t'' t◆
  moveeraset' er m with erase-t◆ er
  moveeraset' er m | refl = ◆erase-t _ _ (! (moveeraset m))

  -- movements don't change either the type or expression under expression
  -- actions
  mutual
    moveerase-synth : ∀{Γ e e' e◆ t t' δ } →
                      erase-e e e◆ →
                      Γ ⊢ e◆ => t →
                      Γ ⊢ e => t ~ move δ ~> e' => t' →
                      (e ◆e) == (e' ◆e) × t == t'
    moveerase-synth er wt (SAMove x) = moveerase x , refl
    moveerase-synth (EEPlusL er) (SPlus x x₁) (SAZipPlus1 x₂) =
      ap1 (λ q → q ·+ _) (moveerase-ana er x x₂)  , refl
    moveerase-synth (EEPlusR er) (SPlus x x₁) (SAZipPlus2 x₂) =
      ap1 (λ q → _ ·+ q) (moveerase-ana er x₁ x₂) , refl
    moveerase-synth (EEAscL er) (SAsc x) (SAZipAsc1 x₁) =
      ap1 (λ q → q ·: _) (moveerase-ana er x x₁) , refl
    moveerase-synth er wt (SAZipAsc2 x x₁ x₂ x₃)
      with moveeraset x
    ... | qq = ap1 (λ q → _ ·: q) qq , eq-ert-trans qq x₂ x₁
    moveerase-synth (EEHalfLamL x) (SLam x₁ wt) (SAZipLam1 x₂ x₃ x₄ x₅ x₆ x₇)
      with erase-t◆ x₃
    ... | refl with erase-t◆ x₄
    ... | refl with moveeraset x₅
    ... | qq rewrite qq with synthunicity x₇ x₆
    ... | refl = refl , refl
    moveerase-synth (EEHalfLamR er) (SLam x wt) (SAZipLam2 x₁ x₂ x₃ x₄)
      with moveerase-synth x₂ x₃ x₄
    ... | qq , refl rewrite qq = refl , refl
    moveerase-synth (EEApL er) (SAp wt x x₁) (SAZipApArr x₂ x₃ x₄ d x₅)
      with erasee-det x₃ er
    ... | refl with synthunicity wt x₄
    ... | refl with moveerase-synth er x₄ d
    ... | pp , refl with ▸arr-unicity x x₂
    ... | refl = (ap1 (λ q → q ∘ _) pp ) , refl
    moveerase-synth (EEApR er) (SAp wt x x₁) (SAZipApAna x₂ x₃ x₄)
      with synthunicity x₃ wt
    ... | refl with ▸arr-unicity x x₂
    ... | refl = ap1 (λ q → _ ∘ q)  (moveerase-ana er x₁ x₄ ) , refl
    moveerase-synth er wt (SAZipNEHole x x₁ d) =
      ap1 ⦇⌜_⌟⦈[ _ ] (π1 (moveerase-synth x x₁ d)) , refl
    moveerase-synth er wt (SAZipPair1 x x₁ x₂ x₃) with moveerase-synth x x₁ x₂
    ... | π1 , refl = (ap1 (λ q → ⟨ q , _ ⟩) π1) , refl
    moveerase-synth er wt (SAZipPair2 x x₁ x₂ x₃) with moveerase-synth x₁ x₂ x₃
    ... | π1 , refl = (ap1 (λ q → ⟨ _ , q ⟩) π1) , refl
    moveerase-synth er wt (SAZipFst x x₁ x₂ x₃ x₄)
      with moveerase-synth x₂ x₃ x₄
    ... | π1 , refl with ▸prod-unicity x₁ x
    ... | refl = (ap1 fst π1) , refl
    moveerase-synth er wt (SAZipSnd x x₁ x₂ x₃ x₄)
      with moveerase-synth x₂ x₃ x₄
    ... | π1 , refl with ▸prod-unicity x₁ x
    ... | refl = (ap1 snd π1) , refl

    moveerase-ana : ∀{Γ e e' e◆ t δ } →
                    erase-e e e◆ →
                    Γ ⊢ e◆ <= t →
                    Γ ⊢ e ~ move δ ~> e' ⇐ t →
                    (e ◆e) == (e' ◆e)
    moveerase-ana er wt (AASubsume x x₁ x₂ x₃) = π1 (moveerase-synth x x₁ x₂)
    moveerase-ana er wt (AAMove x) = moveerase x
    moveerase-ana (EELam er) (ASubsume () x₂) _
    moveerase-ana (EELam er) (ALam x₁ x₂ wt) (AAZipLam x₃ x₄ d) with ▸arr-unicity x₂ x₄
    ... | refl =  ap1 (λ q → ·λ _ q) (moveerase-ana er wt d)
    moveerase-ana (EEInl er) (ASubsume () x₁) (AAZipInl x₂ d)
    moveerase-ana (EEInl er) (AInl x wt) (AAZipInl x₁ d) with ▸sum-unicity x x₁
    ... | refl =  ap1 inl (moveerase-ana er wt d)
    moveerase-ana (EEInr er) (ASubsume () x₁) (AAZipInr x₂ d)
    moveerase-ana (EEInr er) (AInr x wt) (AAZipInr x₁ d) with ▸sum-unicity x x₁
    ... | refl = ap1 inr (moveerase-ana er wt d)
    moveerase-ana er wt (AAZipCase1 x₁ x₂ x₃ x₄ x₅ x₆ x₇ x₈) =
      ap1 (λ q → case q _ _ _ _) (π1(moveerase-synth x₃ x₄ x₅))
    moveerase-ana (EECase2 er) (ASubsume () x₂) (AAZipCase2 x₃ x₄ x₅ x₆ d)
    moveerase-ana (EECase2 er) (ACase x₁ x₂ x₃ x₄ wt wt₁) (AAZipCase2 x₅ x₆ x₇ x₈ d)
      with synthunicity x₄ x₇
    ... | refl with ▸sum-unicity x₃ x₈
    ... | refl = ap1 (λ q → case _ _ q _ _) (moveerase-ana er wt d)
    moveerase-ana (EECase3 er) (ASubsume () x₂) (AAZipCase3 x₃ x₄ x₅ x₆ d)
    moveerase-ana (EECase3 er) (ACase x₁ x₂ x₃ x₄ wt wt₁) (AAZipCase3 x₅ x₆ x₇ x₈ d)
      with synthunicity x₄ x₇
    ... | refl with ▸sum-unicity x₃ x₈
    ... | refl = ap1 (λ q → case _ _ _ _ q) (moveerase-ana er wt₁ d)


------------------------------------------------------------------------
-- Encoders and decoders
------------------------------------------------------------------------

open import Atom

module Coding (atoms : χ-atoms) where

open import Equality.Propositional
open import Prelude hiding (const)
open import Tactic.By.Propositional

open import Bijection equality-with-J as Bijection using (_↔_)
open import Equality.Decision-procedures equality-with-J
open import Function-universe equality-with-J hiding (id; _∘_)
open import Injection equality-with-J using (Injective)
open import List equality-with-J using (foldr)
open import Maybe equality-with-J
open import Monad equality-with-J

open import Chi            atoms
open import Constants      atoms
open import Free-variables atoms
open import Values         atoms

open χ-atoms atoms

------------------------------------------------------------------------
-- General definitions

-- Representation functions.

record Rep {a b} (A : Type a) (B : Type b) : Type (a ⊔ b) where
  field
    -- Representation function.
    ⌜_⌝ : A → B

    -- ⌜_⌝ is injective.
    rep-injective : Injective ⌜_⌝

open Rep ⦃ … ⦄ public

-- An identity encoder.

id-rep : ∀ {a} {A : Type a} → Rep A A
id-rep = record
  { ⌜_⌝           = id
  ; rep-injective = id
  }

-- Composition of representation functions.

infixr 9 _∘-rep_

_∘-rep_ : ∀ {a b c} {A : Type a} {B : Type b} {C : Type c} →
          Rep B C → Rep A B → Rep A C
r₁ ∘-rep r₂ = record
  { ⌜_⌝           = R₁.⌜_⌝ ∘ R₂.⌜_⌝
  ; rep-injective = λ {x y} →
      R₁.⌜ R₂.⌜ x ⌝ ⌝ ≡ R₁.⌜ R₂.⌜ y ⌝ ⌝  ↝⟨ R₁.rep-injective ⟩
      R₂.⌜ x ⌝ ≡ R₂.⌜ y ⌝                ↝⟨ R₂.rep-injective ⟩□
      x ≡ y                              □
  }
  where
  module R₁ = Rep r₁
  module R₂ = Rep r₂

-- Encoder/decoder pairs.

record Code {a b} (A : Type a) (B : Type b) : Type (a ⊔ b) where
  field
    -- Encoder.
    code : A → B

    -- Partial decoder.
    decode : B → Maybe A

    -- The decoder must decode encoded values successfully.
    decode∘code : ∀ x → decode (code x) ≡ just x

  -- The encoder is injective.

  code-injective : Injective code
  code-injective {x} {y} eq = ⊎.cancel-inj₂ (
    just x           ≡⟨ by decode∘code ⟩
    decode (code x)  ≡⟨ by eq ⟩
    decode (code y)  ≡⟨ by decode∘code ⟩∎
    just y           ∎)

  -- Encoder/decoder pairs can be turned into representation
  -- functions.

  rep : Rep A B
  rep = record
    { ⌜_⌝           = code
    ; rep-injective = code-injective
    }

open Code ⦃ … ⦄ public

-- Converts bijections to encoders.

↔→Code : ∀ {a b} {A : Type a} {B : Type b} → A ↔ B → Code A B
↔→Code A↔B = record
  { code        = to
  ; decode      = λ b → just (from b)
  ; decode∘code = λ a →
      just (from (to a))  ≡⟨ cong just (left-inverse-of a) ⟩
      just a              ∎
  }
  where
  open _↔_ A↔B

-- An identity encoder.

id-code : ∀ {a} {A : Type a} → Code A A
id-code = ↔→Code Bijection.id

-- Composition of encoders.

infixr 9 _∘-code_

_∘-code_ : ∀ {a b c} {A : Type a} {B : Type b} {C : Type c} →
           Code B C → Code A B → Code A C
c₁ ∘-code c₂ = record
  { code        = C₁.code ∘ C₂.code
  ; decode      = λ c → C₁.decode c >>=′ C₂.decode
  ; decode∘code = λ a →
      C₁.decode (C₁.code (C₂.code a)) >>=′ C₂.decode  ≡⟨ cong (_>>=′ _) (C₁.decode∘code (C₂.code a)) ⟩
      return (C₂.code a) >>=′ C₂.decode               ≡⟨⟩
      C₂.decode (C₂.code a)                           ≡⟨ by C₂.decode∘code ⟩∎
      return a                                        ∎
  }
  where
  module C₁ = Code c₁
  module C₂ = Code c₂

------------------------------------------------------------------------
-- Some general definitions related to χ

-- Constructor applications can be encoded as expressions (that are
-- also constructor applications).

code-Consts : ∃ λ (cd : Code Consts Exp) →
                ∀ c → Constructor-application (Code.code cd c)
code-Consts = record
  { code        = cd
  ; decode      = dc
  ; decode∘code = dc∘cd
  } , cd-cs
  where
  mutual

    cd : Consts → Exp
    cd (const c cs) = const c (cd⋆ cs)

    cd⋆ : List Consts → List Exp
    cd⋆ []       = []
    cd⋆ (c ∷ cs) = cd c ∷ cd⋆ cs

  mutual

    cd-cs : ∀ c → Constructor-application (cd c)
    cd-cs (const c cs) = const c (cd⋆-cs cs)

    cd⋆-cs : ∀ cs → Constructor-applications (cd⋆ cs)
    cd⋆-cs []       = []
    cd⋆-cs (c ∷ cs) = cd-cs c ∷ cd⋆-cs cs

  mutual

    dc : Exp → Maybe Consts
    dc (const c es) = const c ⟨$⟩ dc⋆ es
    dc _            = nothing

    dc⋆ : List Exp → Maybe (List Consts)
    dc⋆ []       = return []
    dc⋆ (e ∷ es) = _∷_ ⟨$⟩ dc e ⊛ dc⋆ es

  mutual

    dc∘cd : ∀ c → dc (cd c) ≡ just c
    dc∘cd (const c cs) =
      const c ⟨$⟩ ⟨ dc⋆ (cd⋆ cs) ⟩  ≡⟨ ⟨by⟩ (dc∘cd⋆ cs) ⟩
      const c ⟨$⟩ return cs         ≡⟨ refl ⟩∎
      return (const c cs)           ∎

    dc∘cd⋆ : ∀ cs → dc⋆ (cd⋆ cs) ≡ just cs
    dc∘cd⋆ [] = refl
    dc∘cd⋆ (c ∷ cs) =
      _∷_ ⟨$⟩ ⟨ dc (cd c) ⟩ ⊛ dc⋆ (cd⋆ cs)  ≡⟨ ⟨by⟩ (dc∘cd c) ⟩
      _∷_ ⟨$⟩ return c ⊛ ⟨ dc⋆ (cd⋆ cs) ⟩   ≡⟨ ⟨by⟩ (dc∘cd⋆ cs) ⟩
      _∷_ ⟨$⟩ return c ⊛ return cs          ≡⟨⟩
      return (c ∷ cs)                       ∎

-- Converts instances of the form Rep A Consts to Rep A Exp.

rep-Consts-Exp : ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ →
                 Rep A Exp
rep-Consts-Exp ⦃ r ⦄ = rep ⦃ proj₁ code-Consts ⦄ ∘-rep r

-- Represents something as an expression.

rep-as-Exp : ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ → A → Exp
rep-as-Exp = ⌜_⌝ ⦃ rep-Consts-Exp ⦄

-- rep-as-Exp returns constructor applications.

rep-const :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄
  (x : A) → Constructor-application (rep-as-Exp x)
rep-const = proj₂ code-Consts ∘ ⌜_⌝

-- rep-as-Exp returns closed expressions.

rep-closed :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄
  (x : A) → Closed (rep-as-Exp x)
rep-closed = const→closed ∘ rep-const

-- Constructor applications can be encoded as closed expressions.

code-Consts-Closed : Code Consts Closed-exp
Code.code code-Consts-Closed c =
  code ⦃ r = proj₁ code-Consts ⦄ c , const→closed (proj₂ code-Consts c)

Code.decode code-Consts-Closed (e , _) =
  decode ⦃ r = proj₁ code-Consts ⦄ e

Code.decode∘code code-Consts-Closed c =
  decode ⦃ r = proj₁ code-Consts ⦄ (code ⦃ r = proj₁ code-Consts ⦄ c)  ≡⟨ decode∘code ⦃ r = proj₁ code-Consts ⦄ c ⟩∎
  just c                                                              ∎

-- Converts instances of the form Rep A Consts to Rep A Closed-exp.

rep-Consts-Closed-exp :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ → Rep A Closed-exp
rep-Consts-Closed-exp ⦃ r ⦄ = rep ⦃ r = code-Consts-Closed ⦄ ∘-rep r

-- rep-as-Exp returns values.

rep-value :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄
  (x : A) → Value (rep-as-Exp x)
rep-value = const→value ∘ rep-const

-- Some derived lemmas.

subst-rep :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ (x : A) {y e} →
  rep-as-Exp x [ y ← e ] ≡ rep-as-Exp x
subst-rep x = subst-closed _ _ (rep-closed x)

substs-rep :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ (x : A) ps →
  foldr (λ ye → _[ proj₁ ye ← proj₂ ye ]) (rep-as-Exp x) ps ≡
  rep-as-Exp x
substs-rep x = substs-closed (rep-as-Exp x) (rep-closed x)

rep⇓rep :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄
  (x : A) → rep-as-Exp x ⇓ rep-as-Exp x
rep⇓rep x = values-compute-to-themselves (rep-value x)

rep⇓≡rep :
  ∀ {a} {A : Type a} ⦃ r : Rep A Consts ⦄ {v}
  (x : A) → rep-as-Exp x ⇓ v → rep-as-Exp x ≡ v
rep⇓≡rep x = values-only-compute-to-themselves (rep-value x)

------------------------------------------------------------------------
-- Specifications of how a number of types are encoded as χ
-- constructor applications

-- Encoder for booleans.

code-Bool : Code Bool Consts
code-Bool = record
  { code        = cd
  ; decode      = dc
  ; decode∘code = dc∘cd
  }
  where
  cd : Bool → Consts
  cd true  = const c-true  []
  cd false = const c-false []

  dc : Consts → Maybe Bool
  dc (const c args) with c-true C.≟ c | c-false C.≟ c
  dc (const c [])   | yes _ | _     = return true
  dc (const c [])   | _     | yes _ = return false
  dc (const c _)    | _     | _     = nothing

  dc∘cd : ∀ b → dc (cd b) ≡ just b
  dc∘cd true with c-true C.≟ c-true
  ... | yes _   = refl
  ... | no  t≢t = ⊥-elim (t≢t refl)
  dc∘cd false with c-true C.≟ c-false | c-false C.≟ c-false
  ... | no  _   | no f≢f = ⊥-elim (f≢f refl)
  ... | yes t≡f | _      = ⊥-elim (C.distinct-codes→distinct-names
                                     (λ ()) t≡f)
  ... | no _    | yes _  = refl

-- Encoder for natural numbers.

code-ℕ : Code ℕ Consts
code-ℕ = record
  { code        = cd
  ; decode      = dc
  ; decode∘code = dc∘cd
  }
  where
  cd : ℕ → Consts
  cd zero    = const c-zero []
  cd (suc n) = const c-suc (cd n ∷ [])

  dc : Consts → Maybe ℕ
  dc (const c args)     with c-zero C.≟ c | c-suc C.≟ c
  dc (const c [])       | yes eq | _      = return zero
  dc (const c (n ∷ [])) | _      | yes eq = map suc (dc n)
  dc (const c _)        | _      | _      = nothing

  dc∘cd : ∀ n → dc (cd n) ≡ just n
  dc∘cd zero with c-zero C.≟ c-zero
  ... | yes _   = refl
  ... | no  z≢z = ⊥-elim (z≢z refl)
  dc∘cd (suc n) with c-zero C.≟ c-suc | c-suc C.≟ c-suc
  ... | no  _   | no s≢s = ⊥-elim (s≢s refl)
  ... | yes z≡s | _      = ⊥-elim (C.distinct-codes→distinct-names
                                     (λ ()) z≡s)
  ... | no _    | yes _  =
    map suc ⟨ dc (cd n) ⟩  ≡⟨ ⟨by⟩ (dc∘cd n) ⟩
    map suc (return n)     ≡⟨ refl ⟩∎
    return (suc n)         ∎

-- Encoder for variables.

code-Var : Code Var Consts
code-Var = code-ℕ ∘-code ↔→Code V.countably-infinite

-- Encoder for constants.

code-Const : Code Const Consts
code-Const = code-ℕ ∘-code ↔→Code C.countably-infinite

-- Encoders for products.

module _ {a b} {A : Type a} {B : Type b} where

  private

    encode : (A → Consts) → (B → Consts) → A × B → Consts
    encode encA encB (x , y) = const c-pair (encA x ∷ encB y ∷ [])

  rep-× : ⦃ r : Rep A Consts ⦄ ⦃ s : Rep B Consts ⦄ →
          Rep (A × B) Consts
  rep-× ⦃ r ⦄ ⦃ s ⦄ = record
    { ⌜_⌝           = encode ⌜_⌝ ⌜_⌝
    ; rep-injective = λ { {x₁ , x₂} {y₁ , y₂} →
        const c-pair (⌜ x₁ ⌝ ∷ ⌜ x₂ ⌝ ∷ []) ≡
        const c-pair (⌜ y₁ ⌝ ∷ ⌜ y₂ ⌝ ∷ [])    ↝⟨ lemma ⟩

        ⌜ x₁ ⌝ ≡ ⌜ y₁ ⌝ × ⌜ x₂ ⌝ ≡ ⌜ y₂ ⌝      ↝⟨ Σ-map rep-injective rep-injective ⟩

        x₁ ≡ y₁ × x₂ ≡ y₂                      ↝⟨ uncurry (cong₂ _,_) ⟩□

        (x₁ , x₂) ≡ (y₁ , y₂)                  □ }
    }
    where
    lemma :
      ∀ {c₁ c₂ x₁ x₂ y₁ y₂} →
      Consts.const c₁ (x₁ ∷ x₂ ∷ []) ≡ const c₂ (y₁ ∷ y₂ ∷ []) →
      x₁ ≡ y₁ × x₂ ≡ y₂
    lemma refl = refl , refl

  code-× : ⦃ c : Code A Consts ⦄ ⦃ d : Code B Consts ⦄ →
           Code (A × B) Consts
  code-× = record
    { code        = cd
    ; decode      = dc
    ; decode∘code = dc∘cd
    }
    where
    cd : A × B → Consts
    cd = encode code code

    dc : Consts → Maybe (A × B)
    dc (const c args)         with c-pair C.≟ c
    dc (const c (x ∷ y ∷ [])) | yes _ = decode x >>=′ λ x →
                                        decode y >>=′ λ y →
                                        just (x , y)
    dc (const c args)         | _     = nothing

    dc∘cd : ∀ x → dc (cd x) ≡ just x
    dc∘cd (x , y) with c-pair C.≟ c-pair
    ... | no p≢p = ⊥-elim (p≢p refl)
    ... | yes _  =
      (⟨ decode (code x) ⟩ >>=′ λ x →
       decode (code y) >>=′ λ y →
       just (x , y))                      ≡⟨ ⟨by⟩ decode∘code ⟩

      (return x >>=′ λ x →
       decode (code y) >>=′ λ y →
       just (x , y))                      ≡⟨⟩

      (⟨ decode (code y) ⟩ >>=′ λ y →
       just (x , y))                      ≡⟨ ⟨by⟩ decode∘code ⟩

      (return y >>=′ λ y → just (x , y))  ≡⟨ refl ⟩∎

      return (x , y)                      ∎

-- Encoders for lists.

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

  private

    encode : (A → Consts) → List A → Consts
    encode enc []       = const c-nil []
    encode enc (x ∷ xs) = const c-cons (enc x ∷ encode enc xs ∷ [])

  rep-List : ⦃ r : Rep A Consts ⦄ → Rep (List A) Consts
  rep-List = record
    { ⌜_⌝           = encode ⌜_⌝
    ; rep-injective = injective
    }
    where
    injective : ∀ {xs ys} → encode ⌜_⌝ xs ≡ encode ⌜_⌝ ys → xs ≡ ys
    injective {[]}     {[]}     = λ _ → refl
    injective {[]}     {_ ∷ _}  = λ ()
    injective {_ ∷ _}  {[]}     = λ ()
    injective {x ∷ xs} {y ∷ ys} =
      const c-cons (⌜ x ⌝ ∷ encode ⌜_⌝ xs ∷ []) ≡
      const c-cons (⌜ y ⌝ ∷ encode ⌜_⌝ ys ∷ [])      ↝⟨ lemma ⟩

      ⌜ x ⌝ ≡ ⌜ y ⌝ × encode ⌜_⌝ xs ≡ encode ⌜_⌝ ys  ↝⟨ Σ-map rep-injective injective ⟩

      x ≡ y × xs ≡ ys                                ↝⟨ uncurry (cong₂ _∷_) ⟩□

      x ∷ xs ≡ y ∷ ys                                □
      where
      lemma :
        ∀ {c₁ x₁ y₁ c₂ x₂ y₂} →
        Consts.const c₁ (x₁ ∷ y₁ ∷ []) ≡ const c₂ (x₂ ∷ y₂ ∷ []) →
        x₁ ≡ x₂ × y₁ ≡ y₂
      lemma refl = refl , refl

  code-List : ⦃ c : Code A Consts ⦄ → Code (List A) Consts
  code-List = record
    { code        = cd
    ; decode      = dc
    ; decode∘code = dc∘cd
    }
    where
    cd : List A → Consts
    cd = encode code

    dc : Consts → Maybe (List A)
    dc (const c args)           with c-nil C.≟ c | c-cons C.≟ c
    dc (const c [])             | yes eq | _      = return []
    dc (const c′ (x ∷ xs ∷ [])) | _      | yes eq =
      _∷_ ⟨$⟩ decode x ⊛ dc xs
    dc (const c args) | _ | _ = nothing

    dc∘cd : ∀ x → dc (cd x) ≡ just x
    dc∘cd [] with c-nil C.≟ c-nil
    ... | yes _   = refl
    ... | no  n≢n = ⊥-elim (n≢n refl)
    dc∘cd (x ∷ xs) with c-nil C.≟ c-cons | c-cons C.≟ c-cons
    ... | no _    | no c≢c = ⊥-elim (c≢c refl)
    ... | yes n≡c | _      = ⊥-elim (C.distinct-codes→distinct-names
                                       (λ ()) n≡c)
    ... | no _    | yes _  =
      _∷_ ⟨$⟩ ⟨ decode (code x) ⟩ ⊛ dc (cd xs)  ≡⟨ ⟨by⟩ decode∘code ⟩
      _∷_ ⟨$⟩ return x ⊛ ⟨ dc (cd xs) ⟩         ≡⟨ ⟨by⟩ (dc∘cd xs) ⟩
      _∷_ ⟨$⟩ return x ⊛ return xs              ≡⟨⟩
      return (x ∷ xs)                           ∎

-- Encoder for lists of variables.

code-Var⋆ : Code (List Var) Consts
code-Var⋆ = code-List ⦃ code-Var ⦄

private

  module Var   = Code code-Var
  module Var⋆  = Code code-Var⋆
  module Const = Code code-Const

  -- Encoder for the abstract syntax.

  mutual

    code-E : Exp → Consts
    code-E (apply e₁ e₂) = const c-apply (code-E e₁ ∷ code-E e₂ ∷ [])
    code-E (lambda x e)  = const c-lambda (code ⦃ code-Var ⦄ x ∷ code-E e ∷ [])
    code-E (case e bs)   = const c-case (code-E e ∷ code-B⋆ bs ∷ [])
    code-E (rec x e)     = const c-rec (code ⦃ code-Var ⦄ x ∷ code-E e ∷ [])
    code-E (var x)       = const c-var (code ⦃ code-Var ⦄ x ∷ [])
    code-E (const c es)  = const c-const (code ⦃ code-Const ⦄ c ∷ code-⋆ es ∷ [])

    code-B : Br → Consts
    code-B (branch c xs e) =
      const c-branch
        (code ⦃ code-Const ⦄ c ∷ code ⦃ code-Var⋆ ⦄ xs ∷ code-E e ∷ [])

    -- TODO: One could presumably use sized types to avoid repetitive
    -- code. However, I did not want to use sized types in the
    -- specification of χ.

    code-⋆ : List Exp → Consts
    code-⋆ []       = const c-nil []
    code-⋆ (e ∷ es) = const c-cons (code-E e ∷ code-⋆ es ∷ [])

    code-B⋆ : List Br → Consts
    code-B⋆ []       = const c-nil []
    code-B⋆ (b ∷ bs) = const c-cons (code-B b ∷ code-B⋆ bs ∷ [])

  mutual

    decode-E : Consts → Maybe Exp
    decode-E (const c args) with c-apply  C.≟ c
                               | c-lambda C.≟ c
                               | c-case   C.≟ c
                               | c-rec    C.≟ c
                               | c-var    C.≟ c
                               | c-const  C.≟ c

    decode-E (const c (e₁ ∷ e₂ ∷ [])) | yes eq | _ | _ | _ | _ | _ =
      apply ⟨$⟩ decode-E e₁ ⊛ decode-E e₂

    decode-E (const c (x ∷ e ∷ [])) | _ | yes eq | _ | _ | _ | _ =
      lambda ⟨$⟩ decode ⦃ code-Var ⦄ x ⊛ decode-E e

    decode-E (const c (e ∷ bs ∷ [])) | _ | _ | yes eq | _ | _ | _ =
      case ⟨$⟩ decode-E e ⊛ decode-B⋆ bs

    decode-E (const c (x ∷ e ∷ [])) | _ | _ | _ | yes eq | _ | _ =
      rec ⟨$⟩ decode ⦃ code-Var ⦄ x ⊛ decode-E e

    decode-E (const c (x ∷ [])) | _ | _ | _ | _ | yes eq | _ =
      var ⟨$⟩ decode ⦃ code-Var ⦄ x

    decode-E (const c (c′ ∷ es ∷ [])) | _ | _ | _ | _ | _ | yes eq =
      const ⟨$⟩ decode ⦃ code-Const ⦄ c′ ⊛ decode-⋆ es

    decode-E (const c args) | _ | _ | _ | _ | _ | _ = nothing

    decode-B : Consts → Maybe Br
    decode-B (const c args)               with c-branch C.≟ c
    decode-B (const c (c′ ∷ xs ∷ e ∷ [])) | yes eq =
      branch ⟨$⟩ decode ⦃ code-Const ⦄ c′
               ⊛ decode ⦃ code-Var⋆ ⦄ xs
               ⊛ decode-E e
    decode-B (const c args) | _ = nothing

    decode-⋆ : Consts → Maybe (List Exp)
    decode-⋆ (const c args)           with c-nil C.≟ c | c-cons C.≟ c
    decode-⋆ (const c [])             | yes eq | _      = return []
    decode-⋆ (const c′ (e ∷ es ∷ [])) | _      | yes eq =
      _∷_ ⟨$⟩ decode-E e ⊛ decode-⋆ es
    decode-⋆ (const c args) | _ | _ = nothing

    decode-B⋆ : Consts → Maybe (List Br)
    decode-B⋆ (const c args)           with c-nil C.≟ c | c-cons C.≟ c
    decode-B⋆ (const c [])             | yes eq | _      = return []
    decode-B⋆ (const c′ (b ∷ bs ∷ [])) | _      | yes eq =
      _∷_ ⟨$⟩ decode-B b ⊛ decode-B⋆ bs
    decode-B⋆ (const c args) | _ | _ = nothing

  mutual

    decode∘code-E : ∀ e → decode-E (code-E e) ≡ just e

    decode∘code-E (apply e₁ e₂) with c-apply C.≟ c-apply
    ... | no c≢c = ⊥-elim (c≢c refl)
    ... | yes _  =
      apply ⟨$⟩ ⟨ decode-E (code-E e₁) ⟩ ⊛ decode-E (code-E e₂)  ≡⟨ ⟨by⟩ (decode∘code-E e₁) ⟩
      apply ⟨$⟩ return e₁ ⊛ ⟨ decode-E (code-E e₂) ⟩             ≡⟨ ⟨by⟩ (decode∘code-E e₂) ⟩
      apply ⟨$⟩ return e₁ ⊛ return e₂                            ≡⟨⟩
      return (apply e₁ e₂)                                       ∎

    decode∘code-E (lambda x e) with c-apply  C.≟ c-lambda
                                  | c-lambda C.≟ c-lambda
    ... | _       | no l≢l = ⊥-elim (l≢l refl)
    ... | yes a≡l | _      = ⊥-elim (C.distinct-codes→distinct-names
                                       (λ ()) a≡l)
    ... | no _    | yes _  =
      lambda ⟨$⟩ ⟨ Var.decode (code ⦃ code-Var ⦄ x) ⟩ ⊛ decode-E (code-E e)  ≡⟨ ⟨by⟩ Var.decode∘code ⟩
      lambda ⟨$⟩ return x ⊛ ⟨ decode-E (code-E e) ⟩                          ≡⟨ ⟨by⟩ (decode∘code-E e) ⟩
      lambda ⟨$⟩ return x ⊛ return e                                         ≡⟨⟩
      return (lambda x e)                                                    ∎

    decode∘code-E (case e bs) with c-apply  C.≟ c-case
                                 | c-lambda C.≟ c-case
                                 | c-case   C.≟ c-case
    ... | _       | _       | no c≢c = ⊥-elim (c≢c refl)
    ... | _       | yes l≡c | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                 (λ ()) l≡c)
    ... | yes a≡c | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                 (λ ()) a≡c)
    ... | no _    | no _    | yes _  =
      case ⟨$⟩ ⟨ decode-E (code-E e) ⟩ ⊛ decode-B⋆ (code-B⋆ bs)  ≡⟨ ⟨by⟩ (decode∘code-E e) ⟩
      case ⟨$⟩ return e ⊛ ⟨ decode-B⋆ (code-B⋆ bs) ⟩             ≡⟨ ⟨by⟩ (decode∘code-B⋆ bs) ⟩
      case ⟨$⟩ return e ⊛ return bs                              ≡⟨⟩
      return (case e bs)                                         ∎

    decode∘code-E (rec x e) with c-apply  C.≟ c-rec
                               | c-lambda C.≟ c-rec
                               | c-case   C.≟ c-rec
                               | c-rec    C.≟ c-rec
    ... | _       | _       | _       | no r≢r = ⊥-elim (r≢r refl)
    ... | _       | _       | yes c≡r | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                           (λ ()) c≡r)
    ... | _       | yes l≡r | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                           (λ ()) l≡r)
    ... | yes a≡r | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                           (λ ()) a≡r)
    ... | no _    | no _    | no _    | yes _  =
      rec ⟨$⟩ ⟨ Var.decode (code ⦃ code-Var ⦄ x) ⟩ ⊛ decode-E (code-E e)  ≡⟨ ⟨by⟩ Var.decode∘code ⟩
      rec ⟨$⟩ return x ⊛ ⟨ decode-E (code-E e) ⟩                          ≡⟨ ⟨by⟩ (decode∘code-E e) ⟩
      rec ⟨$⟩ return x ⊛ return e                                         ≡⟨⟩
      return (rec x e)                                                    ∎

    decode∘code-E (var x) with c-apply  C.≟ c-var
                             | c-lambda C.≟ c-var
                             | c-case   C.≟ c-var
                             | c-rec    C.≟ c-var
                             | c-var    C.≟ c-var
    ... | _       | _       | _       | _       | no v≢v = ⊥-elim (v≢v refl)
    ... | _       | _       | _       | yes r≡v | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                     (λ ()) r≡v)
    ... | _       | _       | yes c≡v | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                     (λ ()) c≡v)
    ... | _       | yes l≡v | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                     (λ ()) l≡v)
    ... | yes a≡v | _       | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                     (λ ()) a≡v)
    ... | no _    | no _    | no _    | no _    | yes _  =
      var ⟨$⟩ ⟨ Var.decode (code ⦃ code-Var ⦄ x) ⟩  ≡⟨ ⟨by⟩ Var.decode∘code ⟩
      var ⟨$⟩ return x                              ≡⟨ refl ⟩∎
      return (var x)                                ∎

    decode∘code-E (const c es) with c-apply  C.≟ c-const
                                  | c-lambda C.≟ c-const
                                  | c-case   C.≟ c-const
                                  | c-rec    C.≟ c-const
                                  | c-var    C.≟ c-const
                                  | c-const  C.≟ c-const
    ... | _       | _       | _       | _       | _       | no c≢c = ⊥-elim (c≢c refl)
    ... | _       | _       | _       | _       | yes v≡c | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                               (λ ()) v≡c)
    ... | _       | _       | _       | yes r≡c | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                               (λ ()) r≡c)
    ... | _       | _       | yes c≡c | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                               (λ ()) c≡c)
    ... | _       | yes l≡c | _       | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                               (λ ()) l≡c)
    ... | yes a≡c | _       | _       | _       | _       | _      = ⊥-elim (C.distinct-codes→distinct-names
                                                                               (λ ()) a≡c)
    ... | no _    | no _    | no _    | no _    | no _    | yes _  =
      const ⟨$⟩ ⟨ Const.decode (code ⦃ code-Const ⦄ c) ⟩ ⊛
                decode-⋆ (code-⋆ es)                       ≡⟨ ⟨by⟩ Const.decode∘code ⟩

      const ⟨$⟩ return c ⊛ ⟨ decode-⋆ (code-⋆ es) ⟩        ≡⟨ ⟨by⟩ (decode∘code-⋆ es) ⟩

      const ⟨$⟩ return c ⊛ return es                       ≡⟨⟩

      return (const c es)                                  ∎

    decode∘code-B : ∀ b → decode-B (code-B b) ≡ just b
    decode∘code-B (branch c xs e) with c-branch C.≟ c-branch
    ... | no b≢b = ⊥-elim (b≢b refl)
    ... | yes _  =
      branch ⟨$⟩ ⟨ Const.decode (code ⦃ code-Const ⦄ c) ⟩ ⊛
                 Var⋆.decode (code ⦃ code-Var⋆ ⦄ xs) ⊛
                 decode-E (code-E e)                             ≡⟨ ⟨by⟩ Const.decode∘code ⟩

      branch ⟨$⟩ return c ⊛
                 ⟨ Var⋆.decode (code ⦃ code-Var⋆ ⦄ xs) ⟩ ⊛
                 decode-E (code-E e)                             ≡⟨ ⟨by⟩ Var⋆.decode∘code ⟩

      branch ⟨$⟩ return c ⊛ return xs ⊛ ⟨ decode-E (code-E e) ⟩  ≡⟨ ⟨by⟩ (decode∘code-E e) ⟩

      branch ⟨$⟩ return c ⊛ return xs ⊛ return e                 ≡⟨⟩

      return (branch c xs e)                                     ∎

    decode∘code-⋆ : ∀ es → decode-⋆ (code-⋆ es) ≡ just es
    decode∘code-⋆ [] with c-nil C.≟ c-nil
    ... | no  n≢n = ⊥-elim (n≢n refl)
    ... | yes _   = refl
    decode∘code-⋆ (e ∷ es) with c-nil C.≟ c-cons | c-cons C.≟ c-cons
    ... | no _    | no c≢c = ⊥-elim (c≢c refl)
    ... | yes n≡c | _      = ⊥-elim (C.distinct-codes→distinct-names
                                       (λ ()) n≡c)
    ... | no _    | yes _  =
      _∷_ ⟨$⟩ ⟨ decode-E (code-E e) ⟩ ⊛ decode-⋆ (code-⋆ es)  ≡⟨ ⟨by⟩ (decode∘code-E e) ⟩
      _∷_ ⟨$⟩ return e ⊛ ⟨ decode-⋆ (code-⋆ es) ⟩             ≡⟨ ⟨by⟩ (decode∘code-⋆ es) ⟩
      _∷_ ⟨$⟩ return e ⊛ return es                            ≡⟨⟩
      return (e ∷ es)                                         ∎

    decode∘code-B⋆ : ∀ bs → decode-B⋆ (code-B⋆ bs) ≡ just bs
    decode∘code-B⋆ [] with c-nil C.≟ c-nil
    ... | no  n≢n = ⊥-elim (n≢n refl)
    ... | yes _   = refl
    decode∘code-B⋆ (b ∷ bs) with c-nil C.≟ c-cons | c-cons C.≟ c-cons
    ... | no _    | no c≢c = ⊥-elim (c≢c refl)
    ... | yes n≡c | _      = ⊥-elim (C.distinct-codes→distinct-names
                                       (λ ()) n≡c)
    ... | no _    | yes _  =
      _∷_ ⟨$⟩ ⟨ decode-B (code-B b) ⟩ ⊛ decode-B⋆ (code-B⋆ bs)  ≡⟨ ⟨by⟩ (decode∘code-B b) ⟩
      _∷_ ⟨$⟩ return b ⊛ ⟨ decode-B⋆ (code-B⋆ bs) ⟩             ≡⟨ ⟨by⟩ (decode∘code-B⋆ bs) ⟩
      _∷_ ⟨$⟩ return b ⊛ return bs                              ≡⟨⟩
      return (b ∷ bs)                                           ∎

code-Exp : Code Exp Consts
Code.code        code-Exp = code-E
Code.decode      code-Exp = decode-E
Code.decode∘code code-Exp = decode∘code-E

code-Br : Code Br Consts
Code.code        code-Br = code-B
Code.decode      code-Br = decode-B
Code.decode∘code code-Br = decode∘code-B

code-Exps : Code (List Exp) Consts
Code.code        code-Exps = code-⋆
Code.decode      code-Exps = decode-⋆
Code.decode∘code code-Exps = decode∘code-⋆

code-Brs : Code (List Br) Consts
Code.code        code-Brs = code-B⋆
Code.decode      code-Brs = decode-B⋆
Code.decode∘code code-Brs = decode∘code-B⋆

-- Encoder for closed expressions.

code-Closed : Code Closed-exp Consts
Code.code   code-Closed = code ⦃ code-Exp ⦄ ∘ proj₁
Code.decode code-Closed c with decode ⦃ code-Exp ⦄ c
... | nothing = nothing
... | just e  with closed? e
...   | yes cl = just (e , cl)
...   | no _   = nothing
Code.decode∘code code-Closed (c , cl)
  with decode ⦃ r = code-Exp ⦄ (code ⦃ code-Exp ⦄ c)
     | decode∘code ⦃ r = code-Exp ⦄ c
... | .(just c) | refl with closed? c
...   | no  ¬cl = ⊥-elim (¬cl cl)
...   | yes cl′ =
  cong just (closed-equal-if-expressions-equal refl)


open import Auto.Core
open import Data.List    using (_∷_; []; length)
open import Data.Nat     using (ℕ; zero; suc)
open import Data.Product using (_,_)
open import Data.Sum     using (inj₁; inj₂)
open import Reflection   using (Term; Name; lam; visible; abs; TC; returnTC; bindTC)

module Auto.Extensible (instHintDB : IsHintDB) where

open IsHintDB     instHintDB public
open PsExtensible instHintDB public
open Auto.Core               public using (dfs; bfs; Exception; throw; searchSpaceExhausted; unsupportedSyntax)

auto : Strategy → ℕ → HintDB → Term → TC Term
auto search depth db type
  with agda2goal×premises type
... | inj₁ msg = returnTC (quoteError msg)
... | inj₂ ((n , g) , args)
  with search (suc depth) (solve g (fromRules args ∙ db))
... | []      = returnTC (quoteError searchSpaceExhausted)
... | (p ∷ _) = bindTC (reify p) (λ rp → returnTC (intros rp))
  where
    intros : Term → Term
    intros = introsAcc (length args)
      where
        introsAcc : ℕ → Term → Term
        introsAcc  zero   t = t
        introsAcc (suc k) t = lam visible (abs "TODO" (introsAcc k t))

infixl 5 _<<_

_<<_ : HintDB → Name → TC HintDB
db << n = bindTC (name2rule n) (λ
  {(inj₁ msg) → returnTC db
  ; (inj₂ (k , r)) → returnTC (db ∙ return r)})
-- db << n with (name2rule n)
-- db << n | inj₁ msg     = db
-- db << n | inj₂ (k , r) = db ∙ return r


module Impure.LFRef.Properties.Decidable where

open import Prelude
open import Relation.Unary
open import Relation.Nullary.Decidable as DecM
open import Data.Fin.Properties as FinP using ()
open import Data.List
open import Data.Vec
open import Data.Vec.Properties
open import Function.Inverse hiding (_∘_; sym)
open import Extensions.List as List↑ using ()
open import Extensions.Vec as Vec↑ using ()
open import Relation.Binary.List.Pointwise using (decidable-≡)
open import Relation.Binary.HeterogeneousEquality as Het using ()

open import Impure.LFRef.Syntax hiding (subst)
open import Impure.LFRef.Welltyped

module DecidableEquality where
  -- termination checker has trouble with mapping
  -- recursively over the arguments of constructors; inlining would prove this terminating
  {-# TERMINATING #-}
  _tm≟_ : ∀ {n} (a b : Term n) → Dec (a ≡ b)
  var x tm≟ var x' with (x FinP.≟ x')
  ... | yes refl = yes refl
  ... | no neq = no (λ{ refl → neq refl })
  var x tm≟ loc x₁ = no (λ ())
  var x tm≟ unit = no (λ ())
  var x tm≟ con fn ts = no (λ ())
  loc x tm≟ var x₁ = no (λ ())
  loc x tm≟ loc x' with x ≟ x'
  ... | yes refl = yes refl
  ... | no neq = no (λ{ refl → neq refl })
  loc x tm≟ unit = no (λ ())
  loc x tm≟ con fn ts = no (λ ())
  unit tm≟ var x = no (λ ())
  unit tm≟ loc x = no (λ ())
  unit tm≟ unit = yes refl
  unit tm≟ con fn ts = no (λ ())
  con fn ts tm≟ var x = no (λ ())
  con fn ts tm≟ loc x = no (λ ())
  con fn ts tm≟ unit = no (λ ())
  con fn ts tm≟ con fn' ts' with fn ≟ fn'
  ... | no neq = no (λ{ refl → neq refl })
  ... | yes refl with decidable-≡ _tm≟_ ts ts'
  ... | no neq = no (λ{ refl → neq refl })
  ... | yes refl = yes refl

  -- decidable type equality
  _ty≟_ : ∀ {n} (a b : Type n) → Dec (a ≡ b)
  (x [ ts ]) ty≟ (x' [ ts' ]) with x ≟ x'
  ... | no neq = no (λ{ refl → neq refl })
  ... | yes refl with decidable-≡ _tm≟_ ts ts'
  ... | yes refl = yes refl
  ... | no neq = no (λ{ refl → neq refl })
  (x [ ts ]) ty≟ Ref b = no (λ ())
  (x [ ts ]) ty≟ Unit = no (λ ())
  Ref a ty≟ (x [ ts ]) = no (λ ())
  Ref a ty≟ Ref b with a ty≟ b
  ... | yes refl = yes refl
  ... | no neq = no (λ{ refl → neq refl })
  Ref a ty≟ Unit = no (λ ())
  Unit ty≟ (x [ ts ]) = no (λ ())
  Unit ty≟ Ref b = no (λ ())
  Unit ty≟ Unit = yes refl

module UniqueTypings where
  unique-tm-type : ∀ {n a b 𝕊 Σ Γ} {t : Term n} → 𝕊 , Σ , Γ ⊢ t ∶ a → 𝕊 , Σ , Γ ⊢ t ∶ b → a ≡ b
  unique-tm-type unit unit = refl
  unique-tm-type (var x) (var x') with Vec↑.[]=-functional _ _ x x'
  ... | refl = refl
  unique-tm-type (loc x) (loc x') with List↑.[]=-functional _ _ x x'
  ... | refl = refl
  unique-tm-type (con c ts refl) (con c' ts' refl)
    with List↑.[]=-functional _ _ c c'
  ... | refl = refl

  unique-type : ∀ {n a b 𝕊 Σ Γ} {e : Exp n} → 𝕊 , Σ , Γ ⊢ₑ e ∶ a → 𝕊 , Σ , Γ ⊢ₑ e ∶ b → a ≡ b
  unique-type (tm x) (tm y) = unique-tm-type x y
  unique-type (fn ·★[ refl ] ts) (fn' ·★[ refl ] ts') with List↑.[]=-functional _ _ fn fn'
  ... | refl = refl
  unique-type (ref p) (ref q) = cong Ref (unique-type p q)
  unique-type (! p) (! q) with unique-type p q
  ... | refl = refl
  unique-type (p ≔ q) (p' ≔ q') = refl

  elim-mistype : ∀ {n a b 𝕊 Σ Γ} {e : Exp n} →
                   𝕊 , Σ , Γ ⊢ₑ e ∶ a → 𝕊 , Σ , Γ ⊢ₑ e ∶ b → ¬ (a ≢ b)
  elim-mistype p q with unique-type p q
  ... | refl = λ neq → neq refl

module DecidableTypability where
  open UniqueTypings
  open DecidableEquality

  mutual
    type-tm : ∀ {n} 𝕊 Σ Γ (t : Term n) → Dec (∃ λ a → 𝕊 , Σ , Γ ⊢ t ∶ a)
    type-tm 𝕊 Σ Γ (var x) =
      yes (, var (proj₂ (Vec↑.strong-lookup _ _)))
    type-tm 𝕊 Σ Γ (loc x) =
      DecM.map′
        (λ{ (_ , p)  → Ref _ , loc p})
        (λ{ (_ , loc p) → _ , p })
        (List↑.dec-lookup x Σ)
    type-tm 𝕊 Σ Γ unit = yes (Unit , unit)
    type-tm 𝕊 Σ Γ (con c ts) with (List↑.dec-lookup c (Sig.constructors 𝕊))
    ... | no ¬p = no (λ{ (._ , con p _ _) → ¬p (, p)})
    ... | yes (p , z) with typecheck-tele 𝕊 Σ Γ ts (weaken+-tele _ (ConType.args p))
    ... | no ¬q = no lem
      where
        lem : ¬ ∃ λ ty → 𝕊 , Σ , Γ ⊢ (con c ts) ∶ ty
        lem (._ , con x q p) with List↑.[]=-functional _ _ z x
        ... | refl = ¬q q
    ... | yes q = yes (, con z q (tele-fit-length q))

    -- deciding whether a term matches a given type follows from
    -- typability with unique type assignments
    typecheck-tm : ∀ {n} 𝕊 Σ Γ (t : Term n) a → Dec (𝕊 , Σ , Γ ⊢ t ∶ a)
    typecheck-tm 𝕊 Σ Γ t a with type-tm 𝕊 Σ Γ t
    ... | no nwt = no (λ wta → nwt (, wta))
    ... | yes (a' , wta') with a ty≟ a'
    ... | yes refl = yes wta'
    ... | no neq = no (λ wta → neq (unique-tm-type wta wta'))

    typecheck-tele : ∀ {n m } 𝕊 Σ Γ (ts : List (Term n)) → (T : Tele n m) → Dec (𝕊 , Σ , Γ ⊢ ts ∶ⁿ T)
    typecheck-tele 𝕊 Σ Γ [] ε = yes ε
    typecheck-tele 𝕊 Σ Γ [] (x ⟶ T) = no (λ ())
    typecheck-tele 𝕊 Σ Γ (x ∷ ts) ε = no (λ ())
    typecheck-tele 𝕊 Σ Γ (x ∷ ts) (ty ⟶ T)
      with typecheck-tm 𝕊 Σ Γ x ty
    ... | no ¬x∶ty = no (λ{ (x∶ty ⟶ _) → ¬x∶ty x∶ty })
    ... | yes x∶ty with typecheck-tele 𝕊 Σ Γ ts (T tele/ (sub x))
    ... | yes ts∶T = yes (x∶ty ⟶ ts∶T)
    ... | no ¬ts∶T = no (λ{ (_ ⟶ ts∶T) → ¬ts∶T ts∶T })

  type : ∀ {n} 𝕊 Σ Γ (e : Exp n) → Dec (∃ λ a → 𝕊 , Σ , Γ ⊢ₑ e ∶ a)
  type 𝕊 Σ Γ (tm t) = DecM.map′
    (λ{ (_ , x ) → , (tm x)})
    (λ{ (_ , tm x) → , x})
    (type-tm 𝕊 Σ Γ t)
  type {n} 𝕊 Σ Γ (fn ·★ ts) with List↑.dec-lookup fn (Sig.funs 𝕊)
  ... | no ¬fn! = no (λ { (_ , (fn! ·★[ q ] _)) → ¬fn! (, fn!) })
  ... | yes (φ , fn!) with typecheck-tele 𝕊 Σ Γ ts (weaken+-tele _ (Fun.args φ))
  ... | no ¬ts∶T = no lem
    where
      lem : ¬ ∃ λ a → 𝕊 , Σ , Γ ⊢ₑ (fn ·★ ts) ∶ a
      lem (_ , (fn!' ·★[ q ] ts∶T)) with List↑.[]=-functional _ _ fn! fn!'
      ... | refl = ¬ts∶T ts∶T
  ... | yes ts∶T = yes (, fn! ·★[ tele-fit-length ts∶T ] ts∶T)
  type 𝕊 Σ Γ (ref e) with (type 𝕊 Σ Γ e)
  ... | no ¬wte = no (λ{ (.(Ref _) , ref wte) → ¬wte (, wte)})
  ... | yes (a , wte) = yes (, ref wte)
  type 𝕊 Σ Γ (! e) with (type 𝕊 Σ Γ e)
  ... | no ¬wte = no ((λ{ (x , (! wte)) → ¬wte (, wte) }))
  type 𝕊 Σ Γ (! e) | yes (x [ ts ] , wte) =
    no λ{ (_ , ! wte') → elim-mistype wte  wte' (λ ()) }
  type 𝕊 Σ Γ (! e) | yes (Unit , wte) =
    no λ{ (_ , ! wte' ) → elim-mistype wte wte' (λ ()) }
  type 𝕊 Σ Γ (! e) | yes (Ref a , wte) = yes (_ , (! wte))
  type 𝕊 Σ Γ (l ≔ r) with type 𝕊 Σ Γ l | type 𝕊 Σ Γ r
  ... | no ¬wtl | _ = no (λ{ (_ , wtl ≔ _ ) → ¬wtl (, wtl) })
  ... | _ | no ¬wtr = no (λ{ (_ , _ ≔ wtr ) → ¬wtr (, wtr) })
  ... | yes (x [ ts ] , wtl) | (yes (b , wtr)) =
    no (λ{ (_ , wtl' ≔ wtr) → elim-mistype wtl wtl' (λ ())})
  ... | yes (Unit , wtl) | (yes (b , wtr)) =
    no (λ{ (_ , wtl' ≔ wtr) → elim-mistype wtl wtl' (λ ())})
  ... | yes (Ref a , wtl) | yes (b , wtr) with a ty≟ b
  ... | yes refl = yes (, wtl ≔ wtr)
  ... | no neq = no lem
    where
      lem : ¬ ∃ λ a → 𝕊 , Σ , Γ ⊢ₑ (l ≔ r) ∶ a
      lem (.Unit , (wtl' ≔ wtr')) with unique-type wtl wtl'
      ... | refl = elim-mistype wtr wtr' (neq ∘ sym)

  typecheck : ∀ {n} 𝕊 Σ Γ (e : Exp n) a → Dec (𝕊 , Σ , Γ ⊢ₑ e ∶ a)
  typecheck 𝕊 Σ Γ e a with type 𝕊 Σ Γ e
  ... | no ¬wte = no (λ wte → ¬wte (, wte))
  ... | yes (a' , wte) with a' ty≟ a
  ... | yes refl = yes wte
  ... | no neq = no (λ{ wte' → elim-mistype wte wte' neq })

  typecheck-seq : ∀ {n} 𝕊 Σ Γ (e : SeqExp n) a → Dec (𝕊 , Σ , Γ ⊢ₛ e ∶ a)
  typecheck-seq 𝕊 Σ Γ (lett e c) a with type 𝕊 Σ Γ e
  ... | no ¬wte = no (λ{ (lett wte _ ) → ¬wte (, wte)})
  ... | yes (b , wte) with typecheck-seq 𝕊 Σ (b :+: Γ) c (weaken₁-tp a)
  ... | yes wtc = yes (lett wte wtc)
  ... | no ¬wtc = no lem
    where
      lem : ¬ 𝕊 , Σ , Γ ⊢ₛ lett e c ∶ a
      lem (lett wte' wtc) with unique-type wte wte'
      ... | refl = ¬wtc wtc
  typecheck-seq 𝕊 Σ Γ (ret e) a = DecM.map′ ret (λ{ (ret wte) → wte }) (typecheck 𝕊 Σ Γ e a)