{-# OPTIONS --cubical --no-import-sorts --safe #-}
module Cubical.HITs.Rationals.QuoQ.Properties where

open import Cubical.Foundations.Everything hiding (_⁻¹)

open import Cubical.HITs.Ints.QuoInt as ℤ using (ℤ; Sign; signed; pos; neg; posneg; sign)
open import Cubical.HITs.SetQuotients as SetQuotient using () renaming (_/_ to _//_)

open import Cubical.Data.Nat as ℕ using (ℕ; zero; suc)
open import Cubical.Data.NatPlusOne
open import Cubical.Data.Sigma

open import Cubical.Data.Sum
open import Cubical.Relation.Nullary

open import Cubical.HITs.Rationals.QuoQ.Base

ℚ-cancelˡ : ∀ {a b} (c : ℕ₊₁) → [ ℕ₊₁→ℤ c ℤ.· a / c ·₊₁ b ] ≡ [ a / b ]
ℚ-cancelˡ {a} {b} c = eq/ _ _
  (cong (ℤ._· ℕ₊₁→ℤ b) (ℤ.·-comm (ℕ₊₁→ℤ c) a) ∙ sym (ℤ.·-assoc a (ℕ₊₁→ℤ c) (ℕ₊₁→ℤ b)))

ℚ-cancelʳ : ∀ {a b} (c : ℕ₊₁) → [ a ℤ.· ℕ₊₁→ℤ c / b ·₊₁ c ] ≡ [ a / b ]
ℚ-cancelʳ {a} {b} c = eq/ _ _
  (sym (ℤ.·-assoc a (ℕ₊₁→ℤ c) (ℕ₊₁→ℤ b)) ∙ cong (a ℤ.·_) (ℤ.·-comm (ℕ₊₁→ℤ c) (ℕ₊₁→ℤ b)))

-- useful functions for defining operations on ℚ

onCommonDenom :
  (g : ℤ × ℕ₊₁ → ℤ × ℕ₊₁ → ℤ)
  (g-eql : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : a ℤ.· ℕ₊₁→ℤ d ≡ c ℤ.· ℕ₊₁→ℤ b)
           → ℕ₊₁→ℤ d ℤ.· (g (a , b) (e , f)) ≡ ℕ₊₁→ℤ b ℤ.· (g (c , d) (e , f)))
  (g-eqr : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : c ℤ.· ℕ₊₁→ℤ f ≡ e ℤ.· ℕ₊₁→ℤ d)
           → (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f ≡ (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d)
  → ℚ → ℚ → ℚ
onCommonDenom g g-eql g-eqr = SetQuotient.rec2 isSetℚ
  (λ { (a , b) (c , d) → [ g (a , b) (c , d) / b ·₊₁ d ] })
  (λ { (a , b) (c , d) (e , f) p → eql (a , b) (c , d) (e , f) p })
  (λ { (a , b) (c , d) (e , f) p → eqr (a , b) (c , d) (e , f) p })
  where eql : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : a ℤ.· ℕ₊₁→ℤ d ≡ c ℤ.· ℕ₊₁→ℤ b)
              → [ g (a , b) (e , f) / b ·₊₁ f ] ≡ [ g (c , d) (e , f) / d ·₊₁ f ]
        eql (a , b) (c , d) (e , f) p =
          [ g (a , b) (e , f) / b ·₊₁ f ]
            ≡⟨ sym (ℚ-cancelˡ d) ⟩
          [ ℕ₊₁→ℤ d ℤ.· (g (a , b) (e , f)) / d ·₊₁ (b ·₊₁ f) ]
            ≡[ i ]⟨ [ ℕ₊₁→ℤ d ℤ.· (g (a , b) (e , f)) / ·₊₁-assoc d b f i ] ⟩
          [ ℕ₊₁→ℤ d ℤ.· (g (a , b) (e , f)) / (d ·₊₁ b) ·₊₁ f ]
            ≡[ i ]⟨ [ g-eql (a , b) (c , d) (e , f) p i / ·₊₁-comm d b i ·₊₁ f ] ⟩
          [ ℕ₊₁→ℤ b ℤ.· (g (c , d) (e , f)) / (b ·₊₁ d) ·₊₁ f ]
            ≡[ i ]⟨ [ ℕ₊₁→ℤ b ℤ.· (g (c , d) (e , f)) / ·₊₁-assoc b d f (~ i) ] ⟩
          [ ℕ₊₁→ℤ b ℤ.· (g (c , d) (e , f)) / b ·₊₁ (d ·₊₁ f) ]
            ≡⟨ ℚ-cancelˡ b ⟩
          [ g (c , d) (e , f) / d ·₊₁ f ] ∎
        eqr : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : c ℤ.· ℕ₊₁→ℤ f ≡ e ℤ.· ℕ₊₁→ℤ d)
             → [ g (a , b) (c , d) / b ·₊₁ d ] ≡ [ g (a , b) (e , f) / b ·₊₁ f ]
        eqr (a , b) (c , d) (e , f) p =
          [ g (a , b) (c , d) / b ·₊₁ d ]
            ≡⟨ sym (ℚ-cancelʳ f) ⟩
          [ (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f / (b ·₊₁ d) ·₊₁ f ]
            ≡[ i ]⟨ [ (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f / ·₊₁-assoc b d f (~ i) ] ⟩
          [ (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f / b ·₊₁ (d ·₊₁ f) ]
            ≡[ i ]⟨ [ g-eqr (a , b) (c , d) (e , f) p i / b ·₊₁ ·₊₁-comm d f i ] ⟩
          [ (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d / b ·₊₁ (f ·₊₁ d) ]
            ≡[ i ]⟨ [ (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d / ·₊₁-assoc b f d i ] ⟩
          [ (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d / (b ·₊₁ f) ·₊₁ d ]
            ≡⟨ ℚ-cancelʳ d ⟩
          [ g (a , b) (e , f) / b ·₊₁ f ] ∎

onCommonDenomSym :
  (g : ℤ × ℕ₊₁ → ℤ × ℕ₊₁ → ℤ)
  (g-sym : ∀ x y → g x y ≡ g y x)
  (g-eql : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : a ℤ.· ℕ₊₁→ℤ d ≡ c ℤ.· ℕ₊₁→ℤ b)
           → ℕ₊₁→ℤ d ℤ.· (g (a , b) (e , f)) ≡ ℕ₊₁→ℤ b ℤ.· (g (c , d) (e , f)))
  → ℚ → ℚ → ℚ
onCommonDenomSym g g-sym g-eql = onCommonDenom g g-eql q-eqr
  where q-eqr : ∀ ((a , b) (c , d) (e , f) : ℤ × ℕ₊₁) (p : c ℤ.· ℕ₊₁→ℤ f ≡ e ℤ.· ℕ₊₁→ℤ d)
                → (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f ≡ (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d
        q-eqr (a , b) (c , d) (e , f) p =
          (g (a , b) (c , d)) ℤ.· ℕ₊₁→ℤ f ≡[ i ]⟨ ℤ.·-comm (g-sym (a , b) (c , d) i) (ℕ₊₁→ℤ f) i ⟩
          ℕ₊₁→ℤ f ℤ.· (g (c , d) (a , b)) ≡⟨ g-eql (c , d) (e , f) (a , b) p ⟩
          ℕ₊₁→ℤ d ℤ.· (g (e , f) (a , b)) ≡[ i ]⟨ ℤ.·-comm (ℕ₊₁→ℤ d) (g-sym (e , f) (a , b) i) i ⟩
          (g (a , b) (e , f)) ℤ.· ℕ₊₁→ℤ d ∎

onCommonDenomSym-comm : ∀ {g} g-sym {g-eql} (x y : ℚ)
                        → onCommonDenomSym g g-sym g-eql x y ≡
                          onCommonDenomSym g g-sym g-eql y x
onCommonDenomSym-comm g-sym = SetQuotient.elimProp2 (λ _ _ → isSetℚ _ _)
  (λ { (a , b) (c , d) i → [ g-sym (a , b) (c , d) i / ·₊₁-comm b d i ] })

-- basic arithmetic operations on ℚ

infixl 6 _+_
infixl 7 _·_

private
  lem₁ : ∀ a b c d e (p : a ℤ.· b ≡ c ℤ.· d) → b ℤ.· (a ℤ.· e) ≡ d ℤ.· (c ℤ.· e)
  lem₁ a b c d e p =   ℤ.·-assoc b a e
                     ∙ cong (ℤ._· e) (ℤ.·-comm b a ∙ p ∙ ℤ.·-comm c d)
                     ∙ sym (ℤ.·-assoc d c e)

  lem₂ : ∀ a b c → a ℤ.· (b ℤ.· c) ≡ c ℤ.· (b ℤ.· a)
  lem₂ a b c =   cong (a ℤ.·_) (ℤ.·-comm b c) ∙ ℤ.·-assoc a c b
               ∙ cong (ℤ._· b) (ℤ.·-comm a c) ∙ sym (ℤ.·-assoc c a b)
               ∙ cong (c ℤ.·_) (ℤ.·-comm a b)

_+_ : ℚ → ℚ → ℚ
_+_ = onCommonDenomSym
  (λ { (a , b) (c , d) → a ℤ.· (ℕ₊₁→ℤ d) ℤ.+ c ℤ.· (ℕ₊₁→ℤ b) })
  (λ { (a , b) (c , d) → ℤ.+-comm (a ℤ.· (ℕ₊₁→ℤ d)) (c ℤ.· (ℕ₊₁→ℤ b)) })
  (λ { (a , b) (c , d) (e , f) p →
    ℕ₊₁→ℤ d ℤ.· (a ℤ.· ℕ₊₁→ℤ f ℤ.+ e ℤ.· ℕ₊₁→ℤ b)
      ≡⟨ sym (ℤ.·-distribˡ (ℕ₊₁→ℤ d) (a ℤ.· ℕ₊₁→ℤ f) (e ℤ.· ℕ₊₁→ℤ b)) ⟩
    ℕ₊₁→ℤ d ℤ.· (a ℤ.· ℕ₊₁→ℤ f) ℤ.+ ℕ₊₁→ℤ d ℤ.· (e ℤ.· ℕ₊₁→ℤ b)
      ≡[ i ]⟨ lem₁ a (ℕ₊₁→ℤ d) c (ℕ₊₁→ℤ b) (ℕ₊₁→ℤ f) p i ℤ.+ lem₂ (ℕ₊₁→ℤ d) e (ℕ₊₁→ℤ b) i ⟩
    ℕ₊₁→ℤ b ℤ.· (c ℤ.· ℕ₊₁→ℤ f) ℤ.+ ℕ₊₁→ℤ b ℤ.· (e ℤ.· ℕ₊₁→ℤ d)
      ≡⟨ ℤ.·-distribˡ (ℕ₊₁→ℤ b) (c ℤ.· ℕ₊₁→ℤ f) (e ℤ.· ℕ₊₁→ℤ d) ⟩
    ℕ₊₁→ℤ b ℤ.· (c ℤ.· ℕ₊₁→ℤ f ℤ.+ e ℤ.· ℕ₊₁→ℤ d) ∎ })

+-comm : ∀ x y → x + y ≡ y + x
+-comm = onCommonDenomSym-comm
  (λ { (a , b) (c , d) → ℤ.+-comm (a ℤ.· (ℕ₊₁→ℤ d)) (c ℤ.· (ℕ₊₁→ℤ b)) })

+-identityˡ : ∀ x → 0 + x ≡ x
+-identityˡ = SetQuotient.elimProp (λ _ → isSetℚ _ _)
  (λ { (a , b) i → [ ℤ.·-identityʳ a i / ·₊₁-identityˡ b i ] })

+-identityʳ : ∀ x → x + 0 ≡ x
+-identityʳ x = +-comm x _ ∙ +-identityˡ x

+-assoc : ∀ x y z → x + (y + z) ≡ (x + y) + z
+-assoc = SetQuotient.elimProp3 (λ _ _ _ → isSetℚ _ _)
  (λ { (a , b) (c , d) (e , f) i → [ eq a (ℕ₊₁→ℤ b) c (ℕ₊₁→ℤ d) e (ℕ₊₁→ℤ f) i / ·₊₁-assoc b d f i ] })
  where eq₁ : ∀ a b c → (a ℤ.· b) ℤ.· c ≡ a ℤ.· (c ℤ.· b)
        eq₁ a b c = sym (ℤ.·-assoc a b c) ∙ cong (a ℤ.·_) (ℤ.·-comm b c)
        eq₂ : ∀ a b c → (a ℤ.· b) ℤ.· c ≡ (a ℤ.· c) ℤ.· b
        eq₂ a b c = eq₁ a b c ∙ ℤ.·-assoc a c b

        eq : ∀ a b c d e f → Path ℤ _ _
        eq a b c d e f =
          a ℤ.· (d ℤ.· f) ℤ.+ (c ℤ.· f ℤ.+ e ℤ.· d) ℤ.· b
            ≡[ i ]⟨ a ℤ.· (d ℤ.· f) ℤ.+ ℤ.·-distribʳ (c ℤ.· f) (e ℤ.· d) b (~ i) ⟩
          a ℤ.· (d ℤ.· f) ℤ.+ ((c ℤ.· f) ℤ.· b ℤ.+ (e ℤ.· d) ℤ.· b)
            ≡[ i ]⟨ ℤ.+-assoc (ℤ.·-assoc a d f i) (eq₂ c f b i) (eq₁ e d b i) i ⟩
          ((a ℤ.· d) ℤ.· f ℤ.+ (c ℤ.· b) ℤ.· f) ℤ.+ e ℤ.· (b ℤ.· d)
            ≡[ i ]⟨ ℤ.·-distribʳ (a ℤ.· d) (c ℤ.· b) f i ℤ.+ e ℤ.· (b ℤ.· d) ⟩
          (a ℤ.· d ℤ.+ c ℤ.· b) ℤ.· f ℤ.+ e ℤ.· (b ℤ.· d) ∎

_·_ : ℚ → ℚ → ℚ
_·_ = onCommonDenomSym
  (λ { (a , _) (c , _) → a ℤ.· c })
  (λ { (a , _) (c , _) → ℤ.·-comm a c })
  (λ { (a , b) (c , d) (e , _) p → lem₁ a (ℕ₊₁→ℤ d) c (ℕ₊₁→ℤ b) e p })

·-comm : ∀ x y → x · y ≡ y · x
·-comm = onCommonDenomSym-comm (λ { (a , _) (c , _) → ℤ.·-comm a c })

·-identityˡ : ∀ x → 1 · x ≡ x
·-identityˡ = SetQuotient.elimProp (λ _ → isSetℚ _ _)
  (λ { (a , b) i → [ ℤ.·-identityˡ a i / ·₊₁-identityˡ b i ] })

·-identityʳ : ∀ x → x · 1 ≡ x
·-identityʳ = SetQuotient.elimProp (λ _ → isSetℚ _ _)
  (λ { (a , b) i → [ ℤ.·-identityʳ a i / ·₊₁-identityʳ b i ] })

·-zeroˡ : ∀ x → 0 · x ≡ 0
·-zeroˡ = SetQuotient.elimProp (λ _ → isSetℚ _ _)
  (λ { (a , b) → (λ i → [ p a b i / 1 ·₊₁ b ]) ∙ ℚ-cancelʳ b })
  where p : ∀ a b → 0 ℤ.· a ≡ 0 ℤ.· ℕ₊₁→ℤ b
        p a b = ℤ.·-zeroˡ {ℤ.spos} a ∙ sym (ℤ.·-zeroˡ {ℤ.spos} (ℕ₊₁→ℤ b))

·-zeroʳ : ∀ x → x · 0 ≡ 0
·-zeroʳ = SetQuotient.elimProp (λ _ → isSetℚ _ _)
  (λ { (a , b) → (λ i → [ p a b i / b ·₊₁ 1 ]) ∙ ℚ-cancelˡ b })
  where p : ∀ a b → a ℤ.· 0 ≡ ℕ₊₁→ℤ b ℤ.· 0
        p a b = ℤ.·-zeroʳ {ℤ.spos} a ∙ sym (ℤ.·-zeroʳ {ℤ.spos} (ℕ₊₁→ℤ b))

·-assoc : ∀ x y z → x · (y · z) ≡ (x · y) · z
·-assoc = SetQuotient.elimProp3 (λ _ _ _ → isSetℚ _ _)
  (λ { (a , b) (c , d) (e , f) i → [ ℤ.·-assoc a c e i / ·₊₁-assoc b d f i ] })

·-distribˡ : ∀ x y z → (x · y) + (x · z) ≡ x · (y + z)
·-distribˡ = SetQuotient.elimProp3 (λ _ _ _ → isSetℚ _ _)
  (λ { (a , b) (c , d) (e , f) → eq a b c d e f })
  where lem : ∀ {ℓ} {A : Type ℓ} (_·_ : A → A → A)
                (·-comm : ∀ x y → x · y ≡ y · x)
                (·-assoc : ∀ x y z → x · (y · z) ≡ (x · y) · z)
                a c b d
              → (a · c) · (b · d) ≡ (a · (c · d)) · b
        lem _·_ ·-comm ·-assoc a c b d =
          (a · c) · (b · d) ≡[ i ]⟨ (a · c) · ·-comm b d i ⟩
          (a · c) · (d · b) ≡⟨ ·-assoc (a · c) d b ⟩
          ((a · c) · d) · b ≡[ i ]⟨ ·-assoc a c d (~ i) · b ⟩
          (a · (c · d)) · b ∎

        lemℤ   = lem ℤ._·_ ℤ.·-comm ℤ.·-assoc
        lemℕ₊₁ = lem _·₊₁_ ·₊₁-comm ·₊₁-assoc

        eq : ∀ a b c d e f →
               [ (a ℤ.· c) ℤ.· ℕ₊₁→ℤ (b ·₊₁ f) ℤ.+ (a ℤ.· e) ℤ.· ℕ₊₁→ℤ (b ·₊₁ d)
                 / (b ·₊₁ d) ·₊₁ (b ·₊₁ f) ]
             ≡ [ a ℤ.· (c ℤ.· ℕ₊₁→ℤ f ℤ.+ e ℤ.· ℕ₊₁→ℤ d)
                / b ·₊₁ (d ·₊₁ f) ]
        eq a b c d e f =
          (λ i → [ lemℤ a c (ℕ₊₁→ℤ b) (ℕ₊₁→ℤ f) i ℤ.+ lemℤ a e (ℕ₊₁→ℤ b) (ℕ₊₁→ℤ d) i
                   / lemℕ₊₁ b d b f i ]) ∙
          (λ i → [ ℤ.·-distribʳ (a ℤ.· (c ℤ.· ℕ₊₁→ℤ f)) (a ℤ.· (e ℤ.· ℕ₊₁→ℤ d)) (ℕ₊₁→ℤ b) i
                   / (b ·₊₁ (d ·₊₁ f)) ·₊₁ b ]) ∙
          ℚ-cancelʳ {a ℤ.· (c ℤ.· ℕ₊₁→ℤ f) ℤ.+ a ℤ.· (e ℤ.· ℕ₊₁→ℤ d)} {b ·₊₁ (d ·₊₁ f)} b ∙
          (λ i → [ ℤ.·-distribˡ a (c ℤ.· ℕ₊₁→ℤ f) (e ℤ.· ℕ₊₁→ℤ d) i
                   / b ·₊₁ (d ·₊₁ f) ])

·-distribʳ : ∀ x y z → (x · z) + (y · z) ≡ (x + y) · z
·-distribʳ x y z = (λ i → ·-comm x z i + ·-comm y z i) ∙ ·-distribˡ z x y ∙ ·-comm z (x + y)

-_ : ℚ → ℚ
- x = -1 · x

negate-invol : ∀ x → - - x ≡ x
negate-invol x = ·-assoc -1 -1 x ∙ ·-identityˡ x

negateEquiv : ℚ ≃ ℚ
negateEquiv = isoToEquiv (iso -_ -_ negate-invol negate-invol)

negateEq : ℚ ≡ ℚ
negateEq = ua negateEquiv

+-inverseˡ : ∀ x → (- x) + x ≡ 0
+-inverseˡ x = (λ i → (-1 · x) + ·-identityˡ x (~ i)) ∙ ·-distribʳ -1 1 x ∙ ·-zeroˡ x

_-_ : ℚ → ℚ → ℚ
x - y = x + (- y)

+-inverseʳ : ∀ x → x - x ≡ 0
+-inverseʳ x = +-comm x (- x) ∙ +-inverseˡ x

+-injˡ : ∀ x y z → x + y ≡ x + z → y ≡ z
+-injˡ x y z p = sym (q y) ∙ cong ((- x) +_) p ∙ q z
  where q : ∀ y → (- x) + (x + y) ≡ y
        q y = +-assoc (- x) x y ∙ cong (_+ y) (+-inverseˡ x) ∙ +-identityˡ y

+-injʳ : ∀ x y z → x + y ≡ z + y → x ≡ z
+-injʳ x y z p = +-injˡ y x z (+-comm y x ∙ p ∙ +-comm z y)


----------------------------------------------------------------------
-- Functional big-step evaluation of terms in the partiality monad
-- (alternative version not productivity checker workarounds)
----------------------------------------------------------------------

module SystemF.Eval.NoWorkarounds where

open import Codata.Musical.Notation using (∞; ♯_; ♭)
open import Category.Monad
open import Category.Monad.Partiality.All
open import Data.Fin using (Fin; zero; suc)
open import Data.Maybe as Maybe using (just; nothing)
open import Data.Maybe.Relation.Unary.Any as MaybeAny using (just)
open import Data.Nat using (_+_)
open import Data.Vec using ([])
open import Relation.Binary.PropositionalEquality as P using (_≡_)
open import Relation.Nullary

open import PartialityAndFailure as PF
open PF.Equality hiding (fail)
private
  open module M {f} = RawMonad (PF.monad {f}) using (return; _>>=_)

open import SystemF.Type
open import SystemF.Term
open import SystemF.WtTerm

open TypeSubst       using () renaming (_[/_]  to _[/tp_])
open TermTypeSubst   using () renaming (_[/_]  to _[/tmTp_])
open TermTermSubst   using () renaming (_[/_]  to _[/tmTm_])
open WtTermTypeSubst using () renaming (_[/_]′ to _[/⊢tmTp_])
open WtTermTermSubst using () renaming (_[/_]  to _[/⊢tmTm_])

open import SystemF.Eval as E
  using (Comp; _⊢comp_∈_; does-not-fail)

----------------------------------------------------------------------
-- Functional big-step semantics (alternative version)
--
-- The evaluation function _⇓ given below differs from that in
-- SystemF.Eval in that it evaluates terms directly in the
-- partiality-and-failure monad (rather than first evaluating them in
-- PartialityAndFailure.Workaround._?⊥P and subsequently interpreting
-- the result in PartialityAndFailure._?⊥).  This is achieved by
-- manually expanding and inlining every application of the monadic
-- bind operation.
--
-- We give a separate type soundness proof formulated with respect to
-- the alternative semantics, as well as a proof that the two
-- semantics are equivalent (i.e. the two evaluations of any given
-- term are bisimilar).
--
-- The definition of the alternative semantics is more verbose and
-- arguably less readable.  However the associated type soundness
-- proof is simpler in that it requires no additional compositionality
-- lemmas.

module _ where
  open M

  mutual

    infix 7 _⇓ _[_]⇓ _·⇓_

    -- Evaluation of untyped (open) terms.
    _⇓ : ∀ {m n} → Term m n → Comp m n
    var x ⇓      = fail
    Λ t ⇓        = return (Λ t)
    λ' a t ⇓     = return (λ' a t)
    μ a t ⇓      = later (♯ (t [/tmTm μ a t ] ⇓))
    (t [ a ]) ⇓  with t ⇓
    ... | c      = c [ a ]⇓
    (s · t) ⇓    with s ⇓ | t ⇓
    ... | f | c  = f ·⇓ c
    fold a t ⇓   with t ⇓
    ... | c      = fold⇓ a c
    unfold a t ⇓ with t ⇓
    ... | c      = unfold⇓ a c

    -- Evaluation of type application.
    _[_]⇓ : ∀ {m n} → Comp m n → Type n → Comp m n
    now (just (Λ t)) [ a ]⇓ = later (♯ (t [/tmTp a ] ⇓))
    now (just _)     [ _ ]⇓ = fail
    now nothing      [ _ ]⇓ = fail
    later c          [ a ]⇓ = later (♯ ((♭ c) [ a ]⇓))

    -- Evaluation of term application.
    _·⇓_ : ∀ {m n} → Comp m n → Comp m n → Comp m n
    now (just (λ' _ t)) ·⇓ now (just v) = later (♯ (t [/tmTm ⌜ v ⌝ ] ⇓))
    now (just _)        ·⇓ now _        = fail
    now nothing         ·⇓ _            = fail
    now f               ·⇓ later c      = later (♯ (now f ·⇓ (♭ c)))
    later f             ·⇓ c            = later (♯ ((♭ f) ·⇓ c))

    -- Evaluation of recursive type folding.
    fold⇓ : ∀ {m n} → Type (1 + n) → Comp m n → Comp m n
    fold⇓ a (now (just v)) = now (just (fold a v))
    fold⇓ _ (now nothing)  = fail
    fold⇓ a (later c)      = later (♯ fold⇓ a (♭ c))

    -- Evaluation of recursive type unfolding.
    unfold⇓ : ∀ {m n} → Type (1 + n) → Comp m n → Comp m n
    unfold⇓ _ (now (just (fold _ v))) = now (just v)
    unfold⇓ a (now (just _))          = fail
    unfold⇓ a (now nothing)           = fail
    unfold⇓ a (later c)               = later (♯ unfold⇓ a (♭ c))

----------------------------------------------------------------------
-- Type soundness wrt. to the alternative semantics

infix 4 ⊢comp_∈_

-- Short hand for closed well-typed computations.
⊢comp_∈_ : Comp 0 0 → Type 0 → Set
⊢comp c ∈ a = [] ⊢comp c ∈ a

mutual

  infix 7 ⊢_⇓ ⊢_[_]⇓ ⊢_·⇓_

  -- Evaluation of closed terms preserves well-typedness.
  ⊢_⇓ : ∀ {t a} → [] ⊢ t ∈ a → ⊢comp t ⇓ ∈ a
  ⊢ var ()      ⇓
  ⊢ Λ ⊢t        ⇓ = now (just (Λ ⊢t))
  ⊢ λ' a ⊢t     ⇓ = now (just (λ' a ⊢t))
  ⊢ μ a ⊢t      ⇓ = later (♯ ⊢ ⊢t [/⊢tmTm μ a ⊢t ] ⇓)
  ⊢ ⊢t [ a ]    ⇓ with ⊢ ⊢t ⇓
  ... | ⊢c        = ⊢ ⊢c [ a ]⇓
  ⊢ ⊢s · ⊢t     ⇓ with ⊢ ⊢s ⇓ | ⊢ ⊢t ⇓
  ... | ⊢f | ⊢c   = ⊢ ⊢f ·⇓ ⊢c
  ⊢ fold a ⊢t   ⇓ with ⊢ ⊢t ⇓
  ... | ⊢c        = ⊢fold⇓ a ⊢c
  ⊢ unfold a ⊢t ⇓ with ⊢ ⊢t ⇓
  ... | ⊢c        = ⊢unfold⇓ a ⊢c

  -- Evaluation of type application preserves well-typedness.
  ⊢_[_]⇓ : ∀ {c a} → ⊢comp c ∈ ∀' a → ∀ b → ⊢comp c [ b ]⇓ ∈ a [/tp b ]
  ⊢ now (just (Λ ⊢t)) [ a ]⇓ = later (♯ ⊢ ⊢t [/⊢tmTp a ] ⇓)
  ⊢ later ⊢c          [ a ]⇓ = later (♯ ⊢ (♭ ⊢c) [ a ]⇓)

  -- Evaluation of term application preserves well-typedness.
  ⊢_·⇓_ : ∀ {f c a b} → ⊢comp f ∈ a →' b → ⊢comp c ∈ a → ⊢comp f ·⇓ c ∈ b
  ⊢ now (just (λ' a ⊢t)) ·⇓ now (just ⊢v) =
    later (♯ (⊢ ⊢t [/⊢tmTm ⊢⌜ ⊢v ⌝ ] ⇓))
  ⊢ now (just (λ' a ⊢t)) ·⇓ later ⊢c =
    later (♯ (⊢ now (just (λ' a ⊢t)) ·⇓ ♭ ⊢c))
  ⊢ later ⊢f ·⇓ ⊢c = later (♯ (⊢ ♭ ⊢f ·⇓ ⊢c))

  -- Evaluation of recursive type folding preserves well-typedness.
  ⊢fold⇓ : ∀ {c} a → ⊢comp c ∈ a [/tp μ a ] → ⊢comp fold⇓ a c ∈ μ a
  ⊢fold⇓ a (now (just ⊢v)) = now (just (fold a ⊢v))
  ⊢fold⇓ a (later ⊢c)      = later (♯ ⊢fold⇓ a (♭ ⊢c))

  -- Evaluation of recursive type unfolding preserves well-typedness.
  ⊢unfold⇓ : ∀ {c} a → ⊢comp c ∈ μ a → ⊢comp unfold⇓ a c ∈ a [/tp μ a ]
  ⊢unfold⇓ _ (now (just (fold ._ ⊢v))) = now (just ⊢v)
  ⊢unfold⇓ a (later ⊢c)                = later (♯ ⊢unfold⇓ a (♭ ⊢c))

-- Type soundness: evaluation of well-typed terms does not fail.
type-soundness : ∀ {t a} → [] ⊢ t ∈ a → ¬ t ⇓ ≈ fail
type-soundness ⊢t = does-not-fail ⊢ ⊢t ⇓

----------------------------------------------------------------------
-- Equivalence (bisimilarity) of big-step semantics

open PF.Workaround using (⟦_⟧P)
open PF.Equivalence hiding (sym)
open PF.AlternativeEquality
  renaming (return to returnP; fail to failP; _>>=_ to _>>=P_)

-- Bind-expanded variants of the various helpers of of _⇓.

_[_]⇓′ : ∀ {m n} → Comp m n → Type n → Comp m n
c [ a ]⇓′ = c >>= λ v → (return v) [ a ]⇓

_·⇓′_ : ∀ {m n} → Comp m n → Comp m n → Comp m n
c ·⇓′ d = c >>= λ f → d >>= λ v → (return f) ·⇓ (return v)

fold⇓′ : ∀ {m n} → Type (1 + n) → Comp m n → Comp m n
fold⇓′ a c = c >>= λ v → fold⇓ a (return v)

unfold⇓′ : ∀ {m n} → Type (1 + n) → Comp m n → Comp m n
unfold⇓′ a c = c >>= λ v → unfold⇓ a (return v)

-- The two variants of _[_]⇓ are strongly bisimilar.
_[_]⇓≅ : ∀ {m n} (c : Comp m n) (a : Type n) → c [ a ]⇓ ≅ c [ a ]⇓′
now (just _) [ _ ]⇓≅ = refl P.refl
now nothing  [ _ ]⇓≅ = now P.refl
later c      [ a ]⇓≅ = later (♯ ((♭ c) [ a ]⇓≅))

-- The two variants of _·⇓_ are strongly bisimilar.
_·⇓≅_ : ∀ {m n} (c : Comp m n) (d : Comp m n) → c ·⇓ d ≅ c ·⇓′ d
now (just _)          ·⇓≅ now (just _) = refl P.refl
now (just (Λ t))      ·⇓≅ now nothing  = now P.refl
now (just (λ' a t))   ·⇓≅ now nothing  = now P.refl
now (just (fold a t)) ·⇓≅ now nothing  = now P.refl
now nothing           ·⇓≅ now v   = now P.refl
now (just (Λ t))      ·⇓≅ later d = later (♯ (now (just (Λ t))      ·⇓≅ ♭ d))
now (just (λ' a t))   ·⇓≅ later d = later (♯ (now (just (λ' a t))   ·⇓≅ ♭ d))
now (just (fold a t)) ·⇓≅ later d = later (♯ (now (just (fold a t)) ·⇓≅ ♭ d))
now nothing           ·⇓≅ later d = now P.refl
later c               ·⇓≅ d       = later (♯ ((♭ c) ·⇓≅ d))

-- The two variants of fold⇓ are strongly bisimilar.
fold⇓≅ : ∀ {m n} (a : Type (1 + n)) (c : Comp m n) → fold⇓ a c ≅ fold⇓′ a c
fold⇓≅ a (now (just v)) = now P.refl
fold⇓≅ a (now nothing)  = now P.refl
fold⇓≅ a (later c)      = later (♯ fold⇓≅ a (♭ c))

-- The two variants of unfold⇓ are strongly bisimilar.
unfold⇓≅ : ∀ {m n} (a : Type (1 + n)) (c : Comp m n) →
           unfold⇓ a c ≅ unfold⇓′ a c
unfold⇓≅ a (now (just v)) = refl P.refl
unfold⇓≅ a (now nothing)  = now P.refl
unfold⇓≅ a (later c)      = later (♯ unfold⇓≅ a (♭ c))

mutual

  -- Helper lemma relating the two semantics of type application.
  []-E⇓≅⇓′ : ∀ {m n} (v : Val m n) (a : Type n) →
             ⟦ v E.[ a ]′ ⟧P ≅P (return v) [ a ]⇓
  []-E⇓≅⇓′ (Λ t)      a = later (♯ E⇓≅⇓′ (t [/tmTp a ] ))
  []-E⇓≅⇓′ (λ' _ _)   _ = failP
  []-E⇓≅⇓′ (fold _ _) _ = failP

  -- Helper lemma relating the two semantics of term application.
  ·-E⇓≅⇓′ : ∀ {m n} (f : Val m n) (v : Val m n) →
            ⟦ f E.·′ v ⟧P ≅P (return f) ·⇓ (return v)
  ·-E⇓≅⇓′ (Λ _)      _ = failP
  ·-E⇓≅⇓′ (λ' a t)   v = later (♯ E⇓≅⇓′ (t [/tmTm ⌜ v ⌝ ]))
  ·-E⇓≅⇓′ (fold _ _) _ = failP

  -- Helper lemma relating the two semantics of recursive type
  -- unfolding.
  unfold-E⇓≅⇓′ : ∀ {m n} (a : Type (1 + n)) (v : Val m n) →
                 ⟦ E.unfold′ a v ⟧P ≅P unfold⇓ a (return v)
  unfold-E⇓≅⇓′ _ (Λ _)      = failP
  unfold-E⇓≅⇓′ _ (λ' _ _)   = failP
  unfold-E⇓≅⇓′ _ (fold _ _) = returnP P.refl

  -- The two semantics are ≅P-equivalent.
  E⇓≅⇓′ : ∀ {m n} (t : Term m n) → t E.⇓ ≅P t ⇓
  E⇓≅⇓′ (var x)           = failP
  E⇓≅⇓′ (Λ t)             = returnP P.refl
  E⇓≅⇓′ (λ' a t)          = returnP P.refl
  E⇓≅⇓′ (μ a t)           = later (♯ E⇓≅⇓′ (t [/tmTm μ a t ]))
  E⇓≅⇓′ (t [ a ])         =
    t [ a ] E.⇓           ≅⟨ complete (E.[]-comp t a) ⟩
    (t E.⇓) E.[ a ]⇓      ≅⟨ (E⇓≅⇓′ t >>=P λ v → []-E⇓≅⇓′ v a) ⟩
    (t ⇓) [ a ]⇓′         ≅⟨ sym (complete ((t ⇓) [ a ]⇓≅)) ⟩
    t [ a ] ⇓             ∎
  E⇓≅⇓′ (s · t)           =
    s · t E.⇓             ≅⟨ complete (E.·-comp s t) ⟩
    (s E.⇓) E.·⇓ (t E.⇓)  ≅⟨ (E⇓≅⇓′ s >>=P λ f → E⇓≅⇓′ t >>=P λ v →
                              ·-E⇓≅⇓′ f v) ⟩
    (s ⇓) ·⇓′ (t ⇓)       ≅⟨ sym (complete ((s ⇓) ·⇓≅ (t ⇓))) ⟩
    s · t ⇓               ∎
  E⇓≅⇓′ (fold a t)        =
    fold a t E.⇓          ≅⟨ complete (E.fold-comp a t) ⟩
    E.fold⇓ a (t E.⇓)     ≅⟨ (E⇓≅⇓′ t >>=P λ v → return (fold a v) ∎) ⟩
    fold⇓′ a (t ⇓)        ≅⟨ sym (complete (fold⇓≅ a (t ⇓))) ⟩
    fold a t ⇓            ∎
  E⇓≅⇓′ (unfold a t)      =
    unfold a t E.⇓        ≅⟨ complete (E.unfold-comp a t) ⟩
    E.unfold⇓ a (t E.⇓)   ≅⟨ (E⇓≅⇓′ t >>=P λ v → unfold-E⇓≅⇓′ a v) ⟩
    unfold⇓′ a (t ⇓)      ≅⟨ sym (complete (unfold⇓≅ a (t ⇓))) ⟩
    unfold a t ⇓          ∎

-- The two big-step semantics are strongly bisimliar.
E⇓≅⇓ : ∀ {m n} (t : Term m n) → t E.⇓ ≅ t ⇓
E⇓≅⇓ t = sound (E⇓≅⇓′ t)


module Tensor where

open import Basics
open import Ix
open import All
open import Perm
open import Cutting
open import Recutter

open _|>_
open RECUTTER
open SUBCOLLECTLEMMA

_><_ : forall {I J} -> (I |> I) -> (J |> J) -> (I * J) |> (I * J)
Cuts   (C >< D) (i , j) = Cuts C i + Cuts D j
inners (C >< D) {i , j} (inl c) = list (_, j) (inners C c)
inners (C >< D) {i , j} (inr d) = list (i ,_) (inners D d)

module TENSORRECUT  {I J}(C : I |> I)(D : J |> J)(rec : Recutter C)(red : Recutter D) where

  cartLeftLemma : (is : List I)(j : J)(d : Cuts D j) -> subCollect (C >< D) (list (_, j) is)
                (allRe (_, j) is
                   (allPu (\ _ -> inr (inr d)) is)) == cart is (inners D d)
  cartLeftLemma [] j d = refl []
  cartLeftLemma (i ,- is) j d = refl (list (i ,_) (inners D d) +L_) =$= cartLeftLemma is j d

  cartRightLemma : (i : I)(c : Cuts C i)(js : List J) ->
     subCollect (C >< D) (list (i ,_) js)
                 (allRe (i ,_) js (allPu (\ _ -> inr (inl c)) js)) ==
                 list swap (cart js (inners C c))
  cartRightLemma i c [] = refl []
  cartRightLemma i c (j ,- js)
    rewrite sym (catNatural swap (list (j ,_) (inners C c)) (cart js (inners C c)))
                | listlist swap (j ,_) (_, j) (\ i -> refl (i , j)) (inners C c)
                = refl (list (_, j) (inners C c) +L_) =$= cartRightLemma i c js

  TensorRecut : Recutter (C >< D)

  -- both cuts in left dimension
  TensorRecut (i , j) (inl c) (inl c') with rec i c c'
  ... | x , y , m
      = allRe (_, j) (inners C c) (all (\ _ -> id +map inl) _ x)
      , allRe (_, j) (inners C c') (all (\ _ -> id +map inl) _ y)
      , lemma where
        lemma : subCollect (C >< D) (list (_, j) (inners C c))
                 (allRe (_, j) (inners C c) (all (\ _ -> id +map inl) (inners C c) x))
                ~
                subCollect (C >< D) (list (_, j) (inners C c'))
                 (allRe (_, j) (inners C c') (all (\ _ -> id +map inl) (inners C c') y))
        lemma rewrite subCollectLemma C (C >< D) (_, j) (\ _ -> inl) (\ i c -> refl _)
                        (inners C c) x
                    | subCollectLemma C (C >< D) (_, j) (\ _ -> inl) (\ i c -> refl _)
                        (inners C c') y
          = permap (_, j) m

  -- left to right
  fst (TensorRecut (i , j) (inl c) (inr d)) =
    allRe (_, j) (inners C c) (allPu (\ i -> inr (inr d)) (inners C c))
  fst (snd (TensorRecut (i , j) (inl c) (inr d))) =
    allRe (i ,_) (inners D d) (allPu (\ i -> inr (inl c)) (inners D d))
  snd (snd (TensorRecut (i , j) (inl c) (inr d)))
    rewrite cartLeftLemma (inners C c) j d
          | cartRightLemma i c (inners D d)
          = cartLemma (inners C c) (inners D d)

  -- right to left
  fst (TensorRecut (i , j) (inr d) (inl c)) =
    allRe (i ,_) (inners D d) (allPu (\ i -> inr (inl c)) (inners D d))
  fst (snd (TensorRecut (i , j) (inr d) (inl c))) =
    allRe (_, j) (inners C c) (allPu (\ i -> inr (inr d)) (inners C c))
  snd (snd (TensorRecut (i , j) (inr d) (inl c)))
    rewrite cartLeftLemma (inners C c) j d
          | cartRightLemma i c (inners D d)
          = symP (cartLemma (inners C c) (inners D d))

  -- both cuts in right dimension
  TensorRecut (i , j) (inr d) (inr d') with red j d d'
  ... | x , y , m
    = allRe (i ,_) (inners D d) (all (\ _ -> id +map inr) _ x)
    , allRe (i ,_) (inners D d') (all (\ _ -> id +map inr) _ y)
    , lemma where
        lemma : subCollect (C >< D) (list (i ,_) (inners D d))
                 (allRe (i ,_) (inners D d) (all (\ _ -> id +map inr) (inners D d) x))
                ~
                subCollect (C >< D) (list (i ,_) (inners D d'))
                 (allRe (i ,_) (inners D d') (all (\ _ -> id +map inr) (inners D d') y))
        lemma rewrite subCollectLemma D (C >< D) (i ,_) (\ _ -> inr) (\ i d -> refl _)
                        (inners D d) x
                    | subCollectLemma D (C >< D) (i ,_) (\ _ -> inr) (\ i d -> refl _)
                        (inners D d') y
                    = permap (i ,_) m

module RECTANGLE where

  RectCut = NatCut >< NatCut

  open NATRECUT
  open TENSORRECUT NatCut NatCut NatRecut NatRecut

  RectRecut = TensorRecut


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

module Definition.Conversion.Universe where

open import Definition.Untyped
open import Definition.Typed
open import Definition.Typed.Properties
open import Definition.Typed.RedSteps
open import Definition.Conversion
open import Definition.Conversion.Reduction
open import Definition.Conversion.Lift

import Tools.PropositionalEquality as PE

-- Algorithmic equality of terms in WHNF of type U are equal as types.
univConv↓ : ∀ {A B Γ}
          → Γ ⊢ A [conv↓] B ∷ U
          → Γ ⊢ A [conv↓] B
univConv↓ (ne-ins t u () x)
univConv↓ (univ x x₁ x₂) = x₂

-- Algorithmic equality of terms of type U are equal as types.
univConv↑ : ∀ {A B Γ}
      → Γ ⊢ A [conv↑] B ∷ U
      → Γ ⊢ A [conv↑] B
univConv↑ ([↑]ₜ B₁ t′ u′ D d d′ whnfB whnft′ whnfu′ t<>u)
      rewrite PE.sym (whnfRed* D Uₙ) =
  reductionConv↑ (univ* d) (univ* d′) (liftConv (univConv↓ t<>u))


module Models where

open import Data.Nat
open import Terms

-- The (complete) Herbrand universe for Σ
U' : {n m : ℕ} → (Signature n m) → Set
U' Σ = GTerm Σ

-- The (complete) μ-Herbrand base for Σ

B'μ : {n m : ℕ} → (Signature n m) → Set
B'μ Σ = GAtom Σ μ

B'ν : {n m : ℕ} → (Signature n m) → Set
B'ν Σ = GAtom Σ ν

open import Relation.Unary as U
open import Level

-- a (complete) Herbrand interpretation
record Interp {n m : ℕ} (Σ : Signature n m) : Set₁ where
  field
    Carrier-μ : Pred (B'μ Σ) Level.zero
    Carrier-ν : Pred (B'ν Σ) Level.zero
open Interp

_∩ᵢ_ : {n m : ℕ} → {Σ : Signature n m} → Interp Σ → Interp Σ
  → Interp Σ
i₁ ∩ᵢ i₂ = record
  { Carrier-μ = Carrier-μ i₁ ∩ Carrier-μ i₂
  ; Carrier-ν = Carrier-ν i₁ ∩ Carrier-ν i₂
  }

_∪ᵢ_ : {n m : ℕ} → {Σ : Signature n m} → Interp Σ → Interp Σ
  → Interp Σ
i₁ ∪ᵢ i₂ = record
  { Carrier-μ = Carrier-μ i₁ ∪ Carrier-μ i₂
  ; Carrier-ν = Carrier-ν i₁ ∪ Carrier-ν i₂
  }

open import Data.List as L
open import Data.List.All as LAl
open import Data.List.Any as LAn
open import Relation.Binary.Core

open Program

-- | Inductive model
record IsIModel {n m : ℕ} {Σ : Signature n m} {var : Set}
       (i : Interp Σ) (P : Program Σ var) : Set₂ where
  field
    forwClosed :
      (bs'μ : List (GAtom Σ μ))
      → All (λ b → b ∈ Carrier-μ i) bs'μ
      → (bs'ν : List (GAtom Σ ν))
      → All (λ b → b ∈ Carrier-ν i) bs'ν
      → (σ : GSubst Σ var)
      → (a : Atom Σ var μ)
      → (bsμ : List (Atom Σ var μ))
      → (bsν : List (Atom Σ var ν))
      → Any (λ cl → cl ≡ (a :- bsμ , bsν)) (prg-μ P)
      → (L.map (appA σ) bsμ) ≡ bs'μ
      → (L.map (appA σ) bsν) ≡ bs'ν
      → (appA σ a ∈ Carrier-μ i)
open IsIModel
open import Data.Product

hA : {A : Set} {B : Set₁}  → A × B → A
hA = proj₁

hBsμ : {A B : Set} {C : Set₁} → A × B × C → B
hBsμ (_ , a , _) = a

hBsν : {A B C : Set} {D : Set₁} → A × B × C × D → C
hBsν (_ , _ , a , _) = a

hσ : {A B C : Set} {D : Set₁} → A × B × C × D → D
hσ (_ , _ , _ , a) = a
open import Data.List.All.Properties -- using (All-map)

-- | model intersection property for a pair of models
prop_model_intersection_pair : {n m : ℕ} → {Σ : Signature n m} {var : Set} →
  (P : Program Σ var)
  → (m₁ : Interp Σ)
  → (m₂ : Interp Σ)
  → Carrier-ν m₁ ≡ Carrier-ν m₂
  → (mp₁ : IsIModel m₁ P)
  → (mp₂ : IsIModel m₂ P)
  → IsIModel (m₁ ∩ᵢ m₂) P
prop_model_intersection_pair P m₁ m₂ eq mp₁ mp₂ =
  record { forwClosed = λ bs'μ x bs'ν y σ a bsμ bsν pcls pμ pν →
    ( forwClosed mp₁ bs'μ (LAl.map proj₁ x) bs'ν
                 (LAl.map proj₁ y) σ a bsμ bsν pcls pμ pν
    , forwClosed mp₂ bs'μ (LAl.map proj₂ x) bs'ν
                 (LAl.map proj₂ y) σ a bsμ bsν pcls pμ pν
    )}

-- | Coinductive model
record IsCModel {n m : ℕ} {Σ : Signature n m} {var : Set}
       (i : Interp Σ) (P : Program Σ var) : Set₁ where
  field
    backClosed :
      --→
      (a' : GAtom Σ ν)
      → a' ∈ Carrier-ν i
      → ∃ (λ ( w : (Atom Σ var ν) -- a
          × (List (Atom Σ var μ)) -- bs-μ
          × (List (Atom Σ var ν)) -- bs-ν
          × GSubst Σ var) →            -- σ

          (Any (λ c → (hA w) :- (hBsμ w) , (hBsν w) ≡ c) (prg-ν P))
          × appA (hσ w) (hA w) ≡ a'
          × All (λ c → c ∈ Carrier-μ i) (L.map (appA (hσ w)) (hBsμ w))
          × All (λ c → c ∈ Carrier-ν i) (L.map (appA (hσ w)) (hBsν w))
     )
open IsCModel

open import Data.Sum
open import Data.Product as DP
open Interp

open import Relation.Binary.PropositionalEquality

-- | model union property for a pair of models
prop_model_union_pair : {n m : ℕ} → {Σ : Signature n m} → {var : Set}
  (P : Program Σ var)
  → (m₁ : Interp Σ)
  → (m₂ : Interp Σ)
  → Carrier-μ m₁ ≡ Carrier-μ m₂
  → (mp₁ : IsCModel m₁ P)
  → (mp₂ : IsCModel m₂ P)
  → IsCModel (m₁ ∪ᵢ m₂) P
prop_model_union_pair P m₁ m₂ eq mp₁ mp₂ {-with (sym eq) -- | (Carrier-μ m₁)
... | eq' {-| dm₁-}-} = record { backClosed = λ
  { a' (inj₁ x) → DP.map
       (λ atm → atm)
       (DP.map
         (λ any → any)
         (λ w → DP.map (λ eq₁ → eq₁) (λ x₃
           → LAl.map inj₁ (proj₁ (proj₂ w))
           , LAl.map inj₁ (proj₂ (proj₂ w))) w))
       (backClosed mp₁ a' x)
  ; a' (inj₂ y) → DP.map
       (λ atm → atm)
       (DP.map
         (λ any → any)
         (λ w → DP.map (λ eq → eq) (λ x₁
           → LAl.map inj₂ (proj₁ (proj₂ w))
           , LAl.map inj₂ (proj₂ (proj₂ w))) w))
       (backClosed mp₂ a' y)
  } }


module test where

open import Relation.Binary.PropositionalEquality

module NewCodata where
  open import Data.Nat
  open import Data.List
  open import Data.Maybe
  record Pair (A B : Set) : Set where
    field
      fst : A
      snd : B

  record Stream (A : Set) : Set where
    coinductive
    field
      hd : A
      tl : Stream A

  open Stream

  repeat : ∀ {A : Set} → A → Stream A
  hd (repeat x) = x
  tl (repeat x) = repeat x

  nth : ∀ {A : Set} → ℕ → Stream A → A
  nth zero s = hd s
  nth (suc n) s = nth n (tl s)

  mutual
    data μColist (A : Set) : Set where
      []   : μColist A
      _∷_ : (x : A) (xs : νColist A) → μColist A
    record νColist (A : Set) : Set where
      coinductive
      field out : μColist A

  open νColist

  record νTree (A : Set) : Set where
    coinductive
    field label : A
          subs  : List (νTree A)
  open νTree

  append : {A : Set} → List A → νColist A → νColist A
  out (append []        ys) = out ys
  out (append (x ∷ xs) ys) = x ∷ append xs ys

  appendS : {A : Set} → List A → Stream A → Stream A
  hd (appendS [] s) = hd s
  hd (appendS (x ∷ xs) s) = x
  tl (appendS [] s) = tl s
  tl (appendS (x ∷ xs) s) = tl (appendS xs s)

  record Pi (A : Set) (B : A → Set) : Set where
    field _$_ : (x : A) → B x
    infixl 20 _$_

  open Pi

  _→'_ : Set → Set → Set
  A →' B = Pi A (λ _ → B)

  infixr 20 _→'_

  plus2 : ℕ →' ℕ
  plus2 $ x = suc (suc x)

  plus : ℕ →' ℕ →' ℕ
  plus $ 0       $ m = m
  plus $ (suc n) $ m = suc (plus $ n $ m)

  data Vec (A : Set) : ℕ → Set where
    nil : Vec A 0
    _:::_ : {n : ℕ} → A → Vec A n → Vec A (suc n)

  test : Vec ℕ 2
  test = 2 ::: (2 ::: nil)

  repeat2V : Pi ℕ (λ n → Vec ℕ n)
  repeat2V $ 0     = nil
  repeat2V $ suc n =  2 ::: (repeat2V $ n)

  {-
  bf : {A : Set} → List (νTree A) → νColist A
  out (bf [])        = []
  out (bf (t ∷ ts)) = label t ∷
                       append (map label ts)
                              (bf (concatMap subs (t ∷ ts)))
  -}

  record ℕ∞ : Set where
    coinductive
    field
      pred∞ : Maybe ℕ∞
  open ℕ∞

  infinity : ℕ∞
  pred∞ infinity = just infinity

  zero∞ : ℕ∞
  pred∞ zero∞ = nothing

  one∞ : ℕ∞
  pred∞ one∞ = just zero∞

  suc∞ : ℕ∞ → ℕ∞
  pred∞ (suc∞ x) = just x

  plus∞ : ℕ∞ → ℕ∞ → ℕ∞
  pred∞ (plus∞ n m) with pred∞ n
  ...               | nothing = pred∞ m
  ...               | just n' = just (plus∞ n' m)

  {-
  record PStr (A : Set) (n : ℕ∞) : Set where
    coinductive
    field
      phd : (k : ℕ∞) → PStr A (suc∞ k) → A
      ptl : (k : ℕ∞) → PStr A (suc∞ k) → PStr A k
  -}

module OldCodata where
  open import Codata.Musical.Notation
  open import Data.Nat

  data Stream (A : Set) : Set where
     cons : A → ∞ (Stream A) → Stream A

  repeat : {A : Set} → A → Stream A
  repeat x = cons x (♯ (repeat x))

  nth : {A : Set} → ℕ → Stream A → A
  nth 0       (cons x _)  = x
  nth (suc n) (cons _ xs) = nth n (♭ xs)

  data ℕ∞ : Set where
    zero∞ : ℕ∞
    suc∞  : ∞ (ℕ∞) → ℕ∞

  infinity : ℕ∞
  infinity = suc∞ (♯ infinity)

module NonTerminating where
  open import Data.Nat

  {-
  _/_ :  ℕ → ℕ → ℕ
  zero / y = zero
  suc x / y = suc ( (x ∸ y) / y)
  -}

module PositiveSizedTypes where
  open import Agda.Builtin.Size

  data ℕ : Size → Set where
    zero : {i : Size} → ℕ i
    suc : {i : Size} → ℕ i → ℕ (↑ i)

  one : ℕ ∞
  one = suc zero
  two : ℕ ∞
  two = suc one
  three : ℕ ∞
  three = suc two
  four : ℕ ∞
  four = suc three
  five : ℕ ∞
  five = suc four

  _-_ : {i : Size} → ℕ i → ℕ ∞ → ℕ i
  zero    - _      = zero
  n       - zero   = n
  (suc n) - (suc m) = n - m
  infixl 20 _-_

  -test₁ : five - two ≡ three
  -test₁ = refl

  _/_ : {i : Size} → ℕ i → ℕ ∞ → ℕ i
  zero  / _ = zero
  suc x / y = suc ( (x - y) / y)
  infixl 30 _/_

  _+_ : ℕ ∞ → ℕ ∞ → ℕ ∞
  zero + y  = y
  suc x + y = suc (x + y)
  infixl 20 _+_

  _*_ : ℕ ∞ → ℕ ∞ → ℕ ∞
  zero * _ = zero
  suc x * y = x + x * y
  infixl 30 _*_

  min : {i : Size} → ℕ i → ℕ i → ℕ i
  min zero     _        = zero
  min _        zero     = zero
  min (suc  m) (suc  n) = suc (min  m n)

  test₁ : zero / zero ≡ zero
  test₁ = refl
  test₂ : zero / five ≡ zero
  test₂ = refl
  test₃ : one / one ≡ one
  test₃ = refl
  test₄ : two / two ≡ one
  test₄ = refl
  test₅ : five / five ≡ one
  test₅ = refl
  test₆ : one / zero ≡ one
  test₆ = refl
  test₇ : two / one ≡ one
  test₇ = refl
  test₈ : four / two ≡ two
  test₈ = refl

module NegativSizedTypes where
  open import Agda.Builtin.Size
  open import Data.Nat
  open import Data.Bool using (if_then_else_)
  open import Relation.Nullary.Decidable using (⌊_⌋)

  record Stream (i : Size) (A : Set) : Set where
    coinductive
    field
      hd : A
      tl : ∀ {j : Size< i} → Stream j A
  open Stream

  cons : {i : Size} {A : Set} → A -> Stream i A → Stream i A
  hd (cons x _)  = x
  tl (cons _ xs) = xs

  map : {A B : Set} {i : Size} → (A → B) → Stream i A → Stream i B
  hd (map f xs) = f (hd xs)
  tl (map f xs) = map f (tl xs)

  merge : {i : Size} → Stream i ℕ → Stream i ℕ → Stream i ℕ
  hd (merge xs ys) = hd xs ⊓ hd ys
  tl (merge xs ys) = if ⌊ hd xs ≤? hd ys ⌋
                     then cons (hd ys) (merge (tl xs) (tl ys))
                     else cons (hd xs) (merge (tl xs) (tl ys))

  ham : {i : Size} → Stream i ℕ
  hd ham = 1
  tl ham = (merge (map (_*_ 2) ham) (map (_*_ 3) ham))


-- Type-checker for the simply-typed lambda calculus
--
-- Where we make sure that failing to typecheck a term is justified by
-- an "ill-typing judgment", which erases to the original term.

open import Data.Empty
open import Data.Unit hiding (_≟_)
open import Data.List hiding ([_])
open import Data.Nat hiding (_*_ ; _+_ ; _≟_)
open import Data.Product

open import Relation.Nullary
open import Relation.Binary hiding (_⇒_)
open import Relation.Binary.PropositionalEquality hiding ([_])

infix 5 _⊢?_∋_
infix 5 _⊢?_∈
infix 19 _↪_
infixr 30 _+_
infixr 35 _*_
infixr 40 _⇒_
infix 50 _∈_
infix 50 _∈
infix 50 _∋_
infixl 150 _▹_

-- * Types

data type : Set where
  unit nat : type
  _*_ _+_ _⇒_ : (A B : type) → type

bool : type
bool = unit + unit

-- TODO: automate this definition using reflection of Agda in Agda
-- see https://github.com/UlfNorell/agda-prelude/blob/master/src/Tactic/Deriving/Eq.agda
_≟_ : Decidable {A = type} _≡_
unit      ≟ unit          = yes refl
nat       ≟ nat           = yes refl
(A₁ + B₁) ≟ (A₂ + B₂)
  with A₁ ≟ A₂ | B₁ ≟ B₂
... | yes refl | yes refl = yes refl
... | yes refl | no ¬p    = no (λ { refl → ¬p refl })
... | no ¬p | _           = no (λ { refl → ¬p refl })
(A₁ ⇒ B₁) ≟ (A₂ ⇒ B₂)
  with A₁ ≟ A₂ | B₁ ≟ B₂
... | yes refl | yes refl = yes refl
... | yes _    | no ¬p    = no λ { refl → ¬p refl }
... | no ¬p    | _        = no λ { refl → ¬p refl }
(A₁ * B₁) ≟ (A₂ * B₂)
  with A₁ ≟ A₂ | B₁ ≟ B₂
... | yes refl | yes refl = yes refl
... | yes _    | no ¬p    = no λ { refl → ¬p refl }
... | no ¬p    | q₂       = no λ { refl → ¬p refl }
unit      ≟ (_ ⇒ _)       = no λ {()}
unit      ≟ (_ * _)       = no λ {()}
unit      ≟ nat           = no λ {()}
unit      ≟ (_ + _)       = no λ {()}
nat       ≟ (_ ⇒ _)       = no λ {()}
nat       ≟ (_ * _)       = no λ {()}
nat       ≟ unit          = no λ {()}
nat       ≟ (_ + _)       = no λ {()}
(_ + _)   ≟ (_ ⇒ _)       = no λ {()}
(_ + _)   ≟ (_ * _)       = no λ {()}
(_ + _)   ≟ nat           = no λ {()}
(_ + _)   ≟ unit          = no λ {()}
(_ ⇒ _)   ≟ unit          = no λ {()}
(_ ⇒ _)   ≟ nat           = no λ {()}
(_ ⇒ _)   ≟ (_ * _)       = no λ {()}
(_ ⇒ _)   ≟ (_ + _)       = no λ {()}
(_ * _)   ≟ unit          = no λ {()}
(_ * _)   ≟ nat           = no λ {()}
(_ * _)   ≟ (_ ⇒ _)       = no λ {()}
(_ * _)   ≟ (_ + _)       = no λ {()}

-- * Syntax of terms

data dir : Set where
  ⇑ ⇓ : dir

data can (T : Set) : Set where
  tt        :               can T
  pair      : (t₁ t₂ : T) → can T
  lam       : (b : T)     → can T
  ze        :               can T
  su        : (t : T)     → can T
  inj₁ inj₂ : (t : T)     → can T

data elim (T : Set) : dir → Set where
  apply   : (s : T)     → elim T ⇑
  fst snd :               elim T ⇑
  split   : (c₁ c₂ : T) → elim T ⇓

data term : dir → Set  where
  C       : (c : can (term ⇓))                           → term ⇓
  inv     : (t : term ⇑)                                 → term ⇓
  var     : (k : ℕ)                                      → term ⇑
  _#_     : ∀ {d} → (n : term ⇑)(args : elim (term ⇓) d) → term d
  [_:∋:_] : (T : type)(t : term ⇓)                       → term ⇑

pattern Ctt       = C tt
pattern Cze       = C ze
pattern Csu x     = C (su x)
pattern Cpair x y = C (pair x y)
pattern Clam b    = C (lam b)
pattern Cinj₁ x   = C (inj₁ x)
pattern Cinj₂ x   = C (inj₂ x)

-- ** Tests

true : term ⇓
true = Cinj₁ Ctt

false : term ⇓
false = Cinj₂ Ctt

t1 : term ⇓
t1 = inv ([ nat ⇒ nat :∋: Clam {- x -} (inv (var {- x -} 0)) ] # apply (Csu (Csu Cze)))

t2 : term ⇓
t2 = Clam {-x-} (var {- x -} 0 # split true false)

-- * Type system

context = List type

pattern _▹_ Γ T = T ∷ Γ
pattern ε       = []

data _∈_ (T : type) : context → Set where
  here : ∀ {Γ} →

      ---------
      T ∈ Γ ▹ T

  there : ∀ {Γ T'} →

      T ∈ Γ
    → ----------
      T ∈ Γ ▹ T'

mutual

  data _C⊢[_]_ : context → dir → type → Set where
    lam : ∀ {Γ A B} →

        Γ ▹ A ⊢[ ⇓ ] B
      → ---------------
        Γ C⊢[ ⇓ ] A ⇒ B

    tt : ∀ {Γ} →

        --------------
        Γ C⊢[ ⇓ ] unit

    ze : ∀ {Γ} →

        -------------
        Γ C⊢[ ⇓ ] nat

    su : ∀ {Γ} →

        Γ ⊢[ ⇓ ] nat
      → -------------
        Γ C⊢[ ⇓ ] nat

    inj₁ : ∀ {Γ A B} →

        Γ ⊢[ ⇓ ] A
      → ---------------
        Γ C⊢[ ⇓ ] A + B

    inj₂ : ∀ {Γ A B} →

        Γ ⊢[ ⇓ ] B
      → ---------------
        Γ C⊢[ ⇓ ] A + B

    pair : ∀ {Γ A B} →

        Γ ⊢[ ⇓ ] A →
        Γ ⊢[ ⇓ ] B
      → ---------------
        Γ C⊢[ ⇓ ] A * B

  data _E⊢[_]_↝_ : context → dir → type → type → Set where
    apply : ∀ {Γ A B} →

        Γ ⊢[ ⇓ ] A
      → -------------------
        Γ E⊢[ ⇑ ] A ⇒ B ↝ B

    fst : ∀ {Γ A B} →

        -------------------
        Γ E⊢[ ⇑ ] A * B ↝ A

    snd : ∀ {Γ A B} →

        -------------------
        Γ E⊢[ ⇑ ] A * B ↝ B

    iter : ∀ {Γ A} →

        Γ ▹ A ⊢[ ⇓ ] A →
        Γ ⊢[ ⇓ ] A
      → -----------------
        Γ E⊢[ ⇓ ] nat ↝ A

    case : ∀ {Γ A B C} →

        Γ ▹ A ⊢[ ⇓ ] C →
        Γ ▹ B ⊢[ ⇓ ] C
      → -------------------
        Γ E⊢[ ⇓ ] A + B ↝ C

  data _⊢[_]_ : context → dir → type → Set where

    C : ∀ {Γ d T} →

        Γ C⊢[ d ] T
      → -----------
        Γ ⊢[ d ] T

    inv : ∀ {Γ T} →

        Γ ⊢[ ⇑ ] T
      → ----------
        Γ ⊢[ ⇓ ] T

    var : ∀ {Γ T} →

        T ∈ Γ
      → ----------
        Γ ⊢[ ⇑ ] T

    _#_ : ∀ {Γ d I O} →

        Γ ⊢[ ⇑ ] I →
        Γ E⊢[ d ] I ↝ O
      → ---------------
        Γ ⊢[ d ] O

    [_:∋:_by_] : ∀ {Γ A} →

        (B : type) → Γ ⊢[ ⇓ ] B → A ≡ B
      → -------------------------------
        Γ ⊢[ ⇑ ] A

-- ** Tests

⊢true : [] ⊢[ ⇓ ] bool
⊢true = C (inj₁ (C tt))

⊢false : [] ⊢[ ⇓ ] bool
⊢false = C (inj₂ (C tt))

⊢t1 : [] ⊢[ ⇓ ] nat
⊢t1 = inv ([ (nat ⇒ nat) :∋: (C (lam (inv (var here)))) by refl ]
          # (apply (C (su (C (su (C ze)))))))

-- * Relating typing and terms

record _↪_ (S T : Set) : Set where
  field
    ⌊_⌋ : S → T

open _↪_ {{...}} public

instance
  VarRaw : ∀ {T Γ} → T ∈ Γ ↪ ℕ
  ⌊_⌋ {{ VarRaw }} here      = zero
  ⌊_⌋ {{ VarRaw }} (there x) = suc ⌊ x ⌋

  OTermRaw : ∀ {Γ T d} → Γ ⊢[ d ] T ↪ term d
  ⌊_⌋ {{OTermRaw}} (C (lam b))         = C (lam ⌊ b ⌋)
  ⌊_⌋ {{OTermRaw}} (C tt)              = C tt
  ⌊_⌋ {{OTermRaw}} (C ze)              = C ze
  ⌊_⌋ {{OTermRaw}} (C (su t))          = C (su ⌊ t ⌋)
  ⌊_⌋ {{OTermRaw}} (C (inj₁ t))        = C (inj₁ ⌊ t ⌋)
  ⌊_⌋ {{OTermRaw}} (C (inj₂ t))        = C (inj₂ ⌊ t ⌋)
  ⌊_⌋ {{OTermRaw}} (C (pair t₁ t₂))    = C (pair ⌊ t₁ ⌋ ⌊ t₂ ⌋)
  ⌊_⌋ {{OTermRaw}} (inv t)             = inv ⌊ t ⌋
  ⌊_⌋ {{OTermRaw}} (var x)             = var ⌊ x ⌋
  ⌊_⌋ {{OTermRaw}} (f # (apply s))     = ⌊ f ⌋ # apply ⌊ s ⌋
  ⌊_⌋ {{OTermRaw}} (p # fst)           = ⌊ p ⌋ # fst
  ⌊_⌋ {{OTermRaw}} (p # snd)           = ⌊ p ⌋ # snd
  ⌊_⌋ {{OTermRaw}} (t # case x y)      = ⌊ t ⌋ # split ⌊ x ⌋ ⌊ y ⌋
  ⌊_⌋ {{OTermRaw}} (t # iter fs fz)    = ⌊ t ⌋ # split ⌊ fs ⌋ ⌊ fz ⌋
  ⌊_⌋ {{OTermRaw}} [ T :∋: t by refl ] = [ T :∋: ⌊ t ⌋ ]

data _⊢_∋_ (Γ : context)(T : type){d} : term d → Set where
  well-typed : (Δ : Γ ⊢[ d ] T ) → Γ ⊢ T ∋ ⌊ Δ ⌋

-- TODO: one could prove that `Γ ⊢ T ∋ t` is H-prop when `t : term ⇓`, ie. we have
--     lemma-proof-irr : ∀ {Γ T}{t : term ⇓} → ∀ (pf₁ pf₂ : Γ ⊢ T ∋ t) → → pf₁ ≅ pf₂
-- but this requires proving that `⌊_⌋` is injective.

-- TODO: conversely, one should be able to prove that `Γ ⊢ T ∋ t` is
-- equivalent to `type` when `t : term ⇑` but I haven't tried.

-- ** Tests

bool∋true : [] ⊢ bool ∋ true
bool∋true = well-typed ⊢true

bool∋false : [] ⊢ bool ∋ false
bool∋false = well-typed ⊢false

nat∋t1 : [] ⊢ nat ∋ t1
nat∋t1 = well-typed ⊢t1

-- * Ill-type system

data Canonical {X} : type → can X → Set where
  can-unit-tt   :               Canonical unit tt
  can-nat-ze    :               Canonical nat ze
  can-nat-su    : ∀ {a}       → Canonical nat (su a)
  can-sum-inj₁  : ∀ {A B a}   → Canonical (A + B) (inj₁ a)
  can-sum-inj₂  : ∀ {A B b}   → Canonical (A + B) (inj₂ b)
  can-prod-pair : ∀ {A B a b} → Canonical (A * B) (pair a b)

data IsProduct : type → Set where
  is-product : ∀ {A B} → IsProduct (A * B)

data IsArrow : type → Set where
  is-arrow : ∀ {A B} → IsArrow (A ⇒ B)

data IsSplit : type → Set where
  is-split-nat :           IsSplit nat
  is-split-sum : ∀ {A B} → IsSplit (A + B)

mtype : dir → Set
mtype ⇑ = ⊤
mtype ⇓ = type

data _B⊬[_]_ (Γ : context) : (d : dir) → mtype d → Set where
  not-canonical : ∀ {c : can (term ⇓)}{T} →

      ¬ Canonical T c
    → ---------------
      Γ B⊬[ ⇓ ] T

  unsafe-inv : ∀ {A B} →

      Γ ⊢[ ⇑ ] A → A ≢ B
    → -------------------
      Γ B⊬[ ⇓ ] B

  bad-split : ∀ {A B}{c₁ c₂ : term ⇓} →

      Γ ⊢[ ⇑ ] A  → ¬ IsSplit A
    → -------------------------
      Γ B⊬[ ⇓ ] B

  out-of-scope : ∀ {x : ℕ} →

      x ≥ length Γ
    → ------------
      Γ B⊬[ ⇑ ] _

  bad-function : ∀ {T}{s : term ⇓} →

      Γ ⊢[ ⇑ ] T → ¬ IsArrow T
    → ------------------------
      Γ B⊬[ ⇑ ] _

  bad-fst : ∀ {T} →

      Γ ⊢[ ⇑ ] T → ¬ IsProduct T
    → --------------------------
      Γ B⊬[ ⇑ ] _

  bad-snd : ∀ {T} →

      Γ ⊢[ ⇑ ] T → ¬ IsProduct T
    → --------------------------
      Γ B⊬[ ⇑ ] _

-- TODO: automate this "trisection & free monad" construction by meta-programming
-- see: "The gentle art of levitation", Chapman et al. for the free monad
-- see: "Clowns to the left of me, jokers to the right", McBride for the dissection
mutual
  data _C⊬[_]_ : context → (d : dir) → mtype d → Set where
    lam   : ∀ {Γ A B} → Γ ▹ A ⊬[ ⇓ ] B           → Γ C⊬[ ⇓ ] A ⇒ B
    su    : ∀ {Γ} → Γ ⊬[ ⇓ ] nat                 → Γ C⊬[ ⇓ ] nat
    inj₁  : ∀ {Γ A B} → Γ ⊬[ ⇓ ] A               → Γ C⊬[ ⇓ ] A + B
    inj₂  : ∀ {Γ A B} → Γ ⊬[ ⇓ ] B               → Γ C⊬[ ⇓ ] A + B
    pair₁ : ∀ {Γ A B} → Γ ⊬[ ⇓ ] A → term ⇓      → Γ C⊬[ ⇓ ] A * B
    pair₂ : ∀ {Γ A B} → Γ ⊢[ ⇓ ] A → Γ ⊬[ ⇓ ] B  → Γ C⊬[ ⇓ ] A * B

  data _E⊬[_]_↝_ : context → (d : dir) → type → mtype d → Set where
    apply : ∀ {Γ A B} → Γ ⊬[ ⇓ ] A                        → Γ E⊬[ ⇑ ] A ⇒ B ↝ _
    iter₁ : ∀ {Γ T} → Γ ▹ T ⊬[ ⇓ ] T → term ⇓             → Γ E⊬[ ⇓ ] nat ↝ T
    iter₂ : ∀ {Γ T} → Γ ▹ T ⊢[ ⇓ ] T → Γ ⊬[ ⇓ ] T         → Γ E⊬[ ⇓ ] nat ↝ T
    case₁ : ∀ {Γ A B C} → Γ ▹ A ⊬[ ⇓ ] C → term ⇓         → Γ E⊬[ ⇓ ] A + B ↝ C
    case₂ : ∀ {Γ A B C} → Γ ▹ A ⊢[ ⇓ ] C → Γ ▹ B ⊬[ ⇓ ] C → Γ E⊬[ ⇓ ] A + B ↝ C

  data _⊬[_]_ : context → (d : dir) → mtype d → Set where
    because  : ∀ {Γ d T} → Γ B⊬[ d ] T                    → Γ ⊬[ d ] T
    C        : ∀ {Γ d T} → Γ C⊬[ d ] T                    → Γ ⊬[ d ] T
    inv      : ∀ {Γ T} → Γ ⊬[ ⇑ ] _                       → Γ ⊬[ ⇓ ] T
    _#₁_     : ∀ {Γ d T} → Γ ⊬[ ⇑ ] _ → elim (term ⇓) d   → Γ ⊬[ d ] T
    _#₂_     : ∀ {Γ d I O} → Γ ⊢[ ⇑ ] I → Γ E⊬[ d ] I ↝ O → Γ ⊬[ d ] O
    [_:∋:_]  : ∀ {Γ} → (T : type) → Γ ⊬[ ⇓ ] T            → Γ ⊬[ ⇑ ] _

instance
  BTermRaw : ∀ {Γ d T} → Γ B⊬[ d ] T ↪ term d
  ⌊_⌋ {{BTermRaw}} (not-canonical {c} x) = C c
  ⌊_⌋ {{BTermRaw}} (unsafe-inv q _)      = inv ⌊ q ⌋
  ⌊_⌋ {{BTermRaw}} (bad-split {c₁        = c₁} {c₂} t _) = ⌊ t ⌋ # split c₁ c₂
  ⌊_⌋ {{BTermRaw}} (out-of-scope {x} _)  = var x
  ⌊_⌋ {{BTermRaw}} (bad-function {s      = s} f _) = ⌊ f ⌋ # apply s
  ⌊_⌋ {{BTermRaw}} (bad-fst p _)         = ⌊ p ⌋ # fst
  ⌊_⌋ {{BTermRaw}} (bad-snd p _)         = ⌊ p ⌋ # snd

  ETermRaw : ∀ {Γ d T} → Γ ⊬[ d ] T ↪ term d
  ⌊_⌋ {{ETermRaw}} (because e)        = ⌊ e ⌋
  ⌊_⌋ {{ETermRaw}} (C (lam b))        = C (lam ⌊ b ⌋)
  ⌊_⌋ {{ETermRaw}} (C (su t))         = C (su ⌊ t ⌋)
  ⌊_⌋ {{ETermRaw}} (C (inj₁ t))       = C (inj₁ ⌊ t ⌋)
  ⌊_⌋ {{ETermRaw}} (C (inj₂ t))       = C (inj₂ ⌊ t ⌋)
  ⌊_⌋ {{ETermRaw}} (C (pair₁ t₁ t₂))  = C (pair ⌊ t₁ ⌋ t₂)
  ⌊_⌋ {{ETermRaw}} (C (pair₂ t₁ t₂))  = C (pair ⌊ t₁ ⌋ ⌊ t₂ ⌋)
  ⌊_⌋ {{ETermRaw}} (inv t)            = inv ⌊ t ⌋
  ⌊_⌋ {{ETermRaw}} [ T :∋: t ]        = [ T :∋: ⌊ t ⌋ ]
  ⌊_⌋ {{ETermRaw}} (t #₁ e)           = ⌊ t ⌋ # e
  ⌊_⌋ {{ETermRaw}} (t #₂ apply x)     = ⌊ t ⌋ # apply ⌊ x ⌋
  ⌊_⌋ {{ETermRaw}} (t #₂ iter₁ fs fz) = ⌊ t ⌋ # split ⌊ fs ⌋ fz
  ⌊_⌋ {{ETermRaw}} (t #₂ iter₂ fs fz) = ⌊ t ⌋ # split ⌊ fs ⌋ ⌊ fz ⌋
  ⌊_⌋ {{ETermRaw}} (t #₂ case₁ cX cY) = ⌊ t ⌋ # split ⌊ cX ⌋ cY
  ⌊_⌋ {{ETermRaw}} (t #₂ case₂ cX cY) = ⌊ t ⌋ # split ⌊ cX ⌋ ⌊ cY ⌋

-- * Type-checking

-- ** View on variable lookup

data _∈-view_ : ℕ → context → Set where
  yes : ∀ {T Γ} → (x : T ∈ Γ)  → ⌊ x ⌋ ∈-view Γ
  no  : ∀ {Γ n} → n ≥ length Γ → n ∈-view Γ

_∈?_ : ∀ n Γ → n ∈-view Γ
_     ∈? ε      = no z≤n
zero  ∈? Γ ▹ T  = yes here
suc n ∈? Γ ▹ T
  with n ∈? Γ
... | yes t     = yes (there t)
... | no q      = no (s≤s q)

-- ** View on typing

data Dir : dir → Set where
  _∈  : term ⇑        → Dir ⇑
  _∋_ : type → term ⇓ → Dir ⇓

instance
  DirRaw : ∀ {Γ d T} → Γ ⊢[ d ] T ↪ Dir d
  ⌊_⌋ {{DirRaw {d = ⇑}}} e    = ⌊ e ⌋ ∈
  ⌊_⌋ {{DirRaw {d = ⇓}{T}}} e = T ∋ ⌊ e ⌋

  EDirRaw : ∀ {Γ d T} → Γ ⊬[ d ] T ↪ Dir d
  ⌊_⌋ {{EDirRaw {d = ⇑}}} e    = ⌊ e ⌋ ∈
  ⌊_⌋ {{EDirRaw {d = ⇓}{T}}} e = T ∋ ⌊ e ⌋

data _⊢[_]-view_ (Γ : context)(d : dir) : Dir d → Set where
  yes : ∀ {T} (Δ : Γ ⊢[ d ] T)   → Γ ⊢[ d ]-view ⌊ Δ ⌋
  no  : ∀ {T} (¬Δ : Γ ⊬[ d ] T)  → Γ ⊢[ d ]-view ⌊ ¬Δ ⌋

isYes : ∀ {Γ T t} → Γ ⊢[ ⇓ ]-view T ∋ t → Set
isYes (yes Δ) = ⊤
isYes (no ¬Δ) = ⊥

lemma : ∀ {Γ T t} → (pf : Γ ⊢[ ⇓ ]-view T ∋ t) → isYes pf → Γ ⊢ T ∋ t
lemma (yes Δ) tt = well-typed Δ
lemma (no _) ()

-- XXX: Mutually-recursive to please the termination checker
_⊢?_∋_ : (Γ : context)(T : type)(t : term ⇓) → Γ ⊢[ ⇓ ]-view T ∋ t
_⊢?_∈  : (Γ : context)(t : term ⇑) → Γ ⊢[ ⇑ ]-view t ∈

_⊢?_∋C_ : (Γ : context)(T : type)(t : can (term ⇓)) → Γ ⊢[ ⇓ ]-view T ∋ C t

_!_∋_⊢?_∋#_  : (Γ : context)(I : type)(Δt : Γ ⊢[ ⇑ ] I)(T : type)(e : elim (term ⇓) ⇓) → Γ ⊢[ ⇓ ]-view T ∋ (⌊ Δt ⌋ # e)
_!_∋_⊢?_∈#   : (Γ : context)(T : type)(Δt : Γ ⊢[ ⇑ ] T)(e : elim (term ⇓) ⇑) → Γ ⊢[ ⇑ ]-view (⌊ Δt ⌋ # e) ∈

Γ ⊢? T ∋ C t      = Γ ⊢? T ∋C t
Γ ⊢? T ∋ inv t
  with Γ ⊢? t ∈
... | no ¬Δ       = no (inv ¬Δ)
... | yes {T'} Δ
    with T' ≟ T
... | yes refl    = yes (inv Δ)
... | no ¬p       = no (because (unsafe-inv Δ ¬p))
Γ ⊢? A ∋ t # e
  with Γ ⊢? t ∈
... | no ¬Δt      = no (¬Δt #₁ e)
... | yes {T} Δt  = Γ ! T ∋ Δt ⊢? A ∋# e

Γ ⊢? var k ∈
  with k ∈? Γ
... | yes x      = yes (var x)
... | no ¬q      = no (because (out-of-scope ¬q))
Γ ⊢? t # e ∈
  with Γ ⊢? t ∈
... | no ¬Δt     = no (¬Δt #₁ e)
... | yes {T} Δt = Γ ! T ∋ Δt ⊢? e ∈#
Γ ⊢? [ T :∋: t ] ∈
  with Γ ⊢? T ∋ t
... | yes Δt     = yes [ T :∋: Δt by refl ]
... | no ¬Δt     = no [ T :∋: ¬Δt ]

Γ ⊢? unit ∋C tt       = yes (C tt)
Γ ⊢? unit ∋C pair _ _ = no (because (not-canonical (λ {()})))
Γ ⊢? unit ∋C lam _    = no (because (not-canonical (λ {()})))
Γ ⊢? unit ∋C ze       = no (because (not-canonical (λ {()})))
Γ ⊢? unit ∋C su _     = no (because (not-canonical (λ {()})))
Γ ⊢? unit ∋C inj₁ _   = no (because (not-canonical (λ {()})))
Γ ⊢? unit ∋C inj₂ _   = no (because (not-canonical (λ {()})))

Γ ⊢? A * B ∋C pair t₁ t₂
  with Γ ⊢? A ∋ t₁ | Γ ⊢? B ∋ t₂
... | yes Δ₁ | yes Δ₂ = yes (C (pair Δ₁ Δ₂))
... | yes Δ₁ | no ¬Δ₂ = no (C (pair₂ Δ₁ ¬Δ₂))
... | no ¬Δ₁ | _      = no (C (pair₁ ¬Δ₁ t₂))
Γ ⊢? A * B ∋C tt      = no (because (not-canonical (λ {()})))
Γ ⊢? A * B ∋C lam _   = no (because (not-canonical (λ {()})))
Γ ⊢? A * B ∋C ze      = no (because (not-canonical (λ {()})))
Γ ⊢? A * B ∋C su _    = no (because (not-canonical (λ {()})))
Γ ⊢? A * B ∋C inj₁ _  = no (because (not-canonical (λ {()})))
Γ ⊢? A * B ∋C inj₂ _  = no (because (not-canonical (λ {()})))

Γ ⊢? A ⇒ B ∋C lam b
  with Γ ▹ A ⊢? B ∋ b
... | yes Δ            = yes (C (lam Δ))
... | no ¬Δ            = no (C (lam ¬Δ))
Γ ⊢? A ⇒ B ∋C tt       = no (because (not-canonical (λ {()})))
Γ ⊢? A ⇒ B ∋C ze       = no (because (not-canonical (λ {()})))
Γ ⊢? A ⇒ B ∋C su x     = no (because (not-canonical (λ {()})))
Γ ⊢? A ⇒ B ∋C pair _ _ = no (because (not-canonical (λ {()})))
Γ ⊢? A ⇒ B ∋C inj₁ _   = no (because (not-canonical (λ {()})))
Γ ⊢? A ⇒ B ∋C inj₂ _   = no (because (not-canonical (λ {()})))

Γ ⊢? nat ∋C ze       = yes (C ze)
Γ ⊢? nat ∋C su n
  with Γ ⊢? nat ∋ n
... | yes Δ          = yes (C (su Δ))
... | no ¬Δ          = no (C (su ¬Δ))
Γ ⊢? nat ∋C tt       = no (because (not-canonical (λ {()})))
Γ ⊢? nat ∋C pair _ _ = no (because (not-canonical (λ {()})))
Γ ⊢? nat ∋C lam _    = no (because (not-canonical (λ {()})))
Γ ⊢? nat ∋C inj₁ _   = no (because (not-canonical (λ {()})))
Γ ⊢? nat ∋C inj₂ _   = no (because (not-canonical (λ {()})))

Γ ⊢? A + B ∋C inj₁ t
  with Γ ⊢? A ∋ t
... | yes Δ            = yes (C (inj₁ Δ))
... | no ¬Δ            = no (C (inj₁ ¬Δ))
Γ ⊢? A + B ∋C inj₂ t
  with Γ ⊢? B ∋ t
... | yes Δ            = yes (C (inj₂ Δ))
... | no ¬Δ            = no (C (inj₂ ¬Δ))
Γ ⊢? A + B ∋C  tt      = no (because (not-canonical (λ {()})))
Γ ⊢? A + B ∋C pair _ _ = no (because (not-canonical (λ {()})))
Γ ⊢? A + B ∋C lam _    = no (because (not-canonical (λ {()})))
Γ ⊢? A + B ∋C ze       = no (because (not-canonical (λ {()})))
Γ ⊢? A + B ∋C su _     = no (because (not-canonical (λ {()})))

Γ ! nat ∋ Δt ⊢? A ∋# split fs fz
    with Γ ▹ A ⊢? A ∋ fs | Γ ⊢? A ∋ fz
... | yes Δfs | yes Δfz           = yes (Δt # iter Δfs Δfz)
... | yes Δfs | no ¬Δfz           = no (Δt #₂ iter₂ Δfs ¬Δfz)
... | no ¬Δfs | _                 = no (Δt #₂ iter₁ ¬Δfs fz)
Γ ! X + Y ∋ Δt ⊢? A ∋# split cX cY
  with (X ∷ Γ) ⊢? A ∋ cX | (Y ∷ Γ) ⊢? A ∋ cY
... | yes ΔcX | yes ΔcY           = yes (Δt # case ΔcX ΔcY)
... | yes ΔcX | no ¬ΔcY           = no (Δt #₂ case₂ ΔcX ¬ΔcY)
... | no ¬ΔcX | _                 = no (Δt #₂ case₁ ¬ΔcX cY)
Γ ! unit ∋ Δt ⊢? A ∋# split _ _   = no (because (bad-split Δt (λ {()})))
Γ ! _ ⇒ _ ∋ Δt ⊢? A ∋# split _ _  = no (because (bad-split Δt (λ {()})))
Γ ! _ * _ ∋ Δt ⊢? A ∋# split _ _  = no (because (bad-split Δt (λ {()})))

Γ ! A ⇒ B ∋ Δf ⊢? apply s ∈#
    with Γ ⊢? A ∋ s
... | yes Δs                  = yes (Δf # apply Δs)
... | no ¬Δs                  = no (Δf #₂ apply ¬Δs)
Γ ! unit ∋ Δf ⊢? apply _ ∈#   = no (because (bad-function Δf λ {()}))
Γ ! nat ∋ Δf ⊢? apply _ ∈#    = no (because (bad-function Δf λ {()}))
Γ ! _ + _ ∋ Δf ⊢? apply _ ∈#  = no (because (bad-function Δf λ {()}))
Γ ! _ * _ ∋ Δf ⊢? apply _ ∈#  = no (because (bad-function Δf λ {()}))

Γ ! A * B ∋ Δp ⊢? fst ∈#  = yes (Δp # fst)
Γ ! unit ∋ Δp ⊢? fst ∈#   = no (because (bad-fst Δp (λ {()})))
Γ ! nat ∋ Δp ⊢? fst ∈#    = no (because (bad-fst Δp (λ {()})))
Γ ! _ + _ ∋ Δp ⊢? fst ∈#  = no (because (bad-fst Δp (λ {()})))
Γ ! _ ⇒ _ ∋ Δp ⊢? fst ∈#  = no (because (bad-fst Δp (λ {()})))

Γ ! A * B ∋ Δp ⊢? snd ∈#  = yes (Δp # snd)
Γ ! unit ∋ Δp ⊢? snd ∈#   = no (because (bad-snd Δp (λ {()})))
Γ ! nat ∋ Δp ⊢? snd ∈#    = no (because (bad-snd Δp (λ {()})))
Γ ! _ + _ ∋ Δp ⊢? snd ∈#  = no (because (bad-snd Δp (λ {()})))
Γ ! _ ⇒ _ ∋ Δp ⊢? snd ∈#  = no (because (bad-snd Δp (λ {()})))

-- ** Tests

nat∋t1' : [] ⊢ nat ∋ t1
nat∋t1' = lemma ([] ⊢? nat ∋ t1) tt

T1 : type
T1 = nat ⇒ (unit + unit)

T2 : type
T2 = (nat + unit) ⇒ (unit + unit)

T1∋t2 : [] ⊢ T1 ∋ t2
T1∋t2 = lemma ([] ⊢? T1 ∋ t2) tt

T2∋t2 : [] ⊢ T2 ∋ t2
T2∋t2 = lemma ([] ⊢? T2 ∋ t2) tt


------------------------------------------------------------------------
-- A relational semantics which uses closures and environments
------------------------------------------------------------------------

module Lambda.Closure.Relational where

open import Codata.Musical.Notation
open import Data.Empty
open import Data.List hiding (lookup)
open import Data.Product
open import Data.Vec using (Vec; _∷_; []; lookup)
open import Function.Base
open import
  Relation.Binary.Construct.Closure.ReflexiveTransitive.Properties
open import Relation.Binary.PropositionalEquality as P using (_≡_)
open import Relation.Nullary

open import Lambda.Syntax using (Tm; con; var; ƛ; _·_)
open Lambda.Syntax.Closure Tm
open import Lambda.VirtualMachine as VM
open VM.Relational
open StarReasoning _⟶_
open InfiniteSequence

------------------------------------------------------------------------
-- Big-step semantics

-- For terminating computations.

infix 4 _⊢_⇓_

data _⊢_⇓_ {n} (ρ : Env n) : Tm n → Value → Set where
  var : ∀ {x} → ρ ⊢ var x ⇓ lookup ρ x
  con : ∀ {i} → ρ ⊢ con i ⇓ con i
  ƛ   : ∀ {t} → ρ ⊢ ƛ t ⇓ ƛ t ρ
  app : ∀ {t₁ t₂ n t} {ρ′ : Env n} {v v′}
        (t₁⇓   : ρ ⊢ t₁ ⇓ ƛ t ρ′)
        (t₂⇓   : ρ ⊢ t₂ ⇓ v′)
        (t₁t₂⇓ : v′ ∷ ρ′ ⊢ t ⇓ v) →
        ρ ⊢ t₁ · t₂ ⇓ v

-- For non-terminating computations.

infix 4 _⊢_⇑

data _⊢_⇑ {n} (ρ : Env n) : Tm n → Set where
  app : ∀ {t₁ t₂ n t} {ρ′ : Env n} {v′}
        (t₁⇓   : ρ ⊢ t₁ ⇓ ƛ t ρ′)
        (t₂⇓   : ρ ⊢ t₂ ⇓ v′)
        (t₁t₂⇑ : ∞ (v′ ∷ ρ′ ⊢ t ⇑)) →
        ρ ⊢ t₁ · t₂ ⇑
  ·ˡ  : ∀ {t₁ t₂}
        (t₁⇑ : ∞ (ρ ⊢ t₁ ⇑)) →
        ρ ⊢ t₁ · t₂ ⇑
  ·ʳ  : ∀ {t₁ t₂ v}
        (t₁⇓ : ρ ⊢ t₁ ⇓ v)
        (t₂⇑ : ∞ (ρ ⊢ t₂ ⇑)) →
        ρ ⊢ t₁ · t₂ ⇑

-- For crashing computations.

infix 4 _⊢_↯

_⊢_↯ : ∀ {n} → Env n → Tm n → Set
ρ ⊢ t ↯ = ¬ (∃ λ v → ρ ⊢ t ⇓ v) × ¬ (ρ ⊢ t ⇑)

------------------------------------------------------------------------
-- The semantics is deterministic

deterministic⇓ : ∀ {n} {ρ : Env n} {t v₁ v₂} →
                 ρ ⊢ t ⇓ v₁ → ρ ⊢ t ⇓ v₂ → v₁ ≡ v₂
deterministic⇓ var                 var                    = P.refl
deterministic⇓ con                 con                    = P.refl
deterministic⇓ ƛ                   ƛ                      = P.refl
deterministic⇓ (app t₁⇓ t₂⇓ t₁t₂⇓) (app t₁⇓′ t₂⇓′ t₁t₂⇓′)
  with deterministic⇓ t₁⇓ t₁⇓′ | deterministic⇓ t₂⇓ t₂⇓′
... | P.refl | P.refl = deterministic⇓ t₁t₂⇓ t₁t₂⇓′

deterministic⇓⇑ : ∀ {n} {ρ : Env n} {t v} →
                  ρ ⊢ t ⇓ v → ρ ⊢ t ⇑ → ⊥
deterministic⇓⇑ var ()
deterministic⇓⇑ con ()
deterministic⇓⇑ ƛ   ()
deterministic⇓⇑ (app t₁⇓ t₂⇓ t₁t₂⇓) (app t₁⇓′ t₂⇓′ t₁t₂⇑)
  with deterministic⇓ t₁⇓ t₁⇓′ | deterministic⇓ t₂⇓ t₂⇓′
... | P.refl | P.refl = deterministic⇓⇑ t₁t₂⇓ (♭ t₁t₂⇑)
deterministic⇓⇑ (app t₁⇓ t₂⇓ t₁t₂⇓) (·ˡ t₁⇑)      =
  deterministic⇓⇑ t₁⇓ (♭ t₁⇑)
deterministic⇓⇑ (app t₁⇓ t₂⇓ t₁t₂⇓) (·ʳ t₁⇓′ t₂⇑) =
  deterministic⇓⇑ t₂⇓ (♭ t₂⇑)

------------------------------------------------------------------------
-- Compiler "correctness"

-- The proofs below establish that the compiler in
-- Lambda.VirtualMachine preserves the semantics above (_⊢_⇓_ and
-- _⊢_⇑, not necessarily _⊢_↯), given that the virtual machine is
-- deterministic.

correct⇓′ : ∀ {n ρ c s v} {t : Tm n} →
            ρ ⊢ t ⇓ v →
            ⟨ comp t c , s , comp-env ρ ⟩ ⟶⋆
            ⟨ c , val (comp-val v) ∷ s , comp-env ρ ⟩
correct⇓′ {ρ = ρ} {c} {s} (var {x}) = begin
  ⟨ var x ∷ c , s                               , comp-env ρ ⟩ ⟶⟨ var ⟩
  ⟨         c , val (lookup (comp-env ρ) x) ∷ s , comp-env ρ ⟩ ≡⟨ P.cong (λ v → ⟨ c , val v ∷ s , comp-env ρ ⟩)
                                                                         (lookup-hom x ρ) ⟩
  ⟨         c , val (comp-val (lookup ρ x)) ∷ s , comp-env ρ ⟩ ∎
correct⇓′ {ρ = ρ} {c} {s} (con {i}) = begin
  ⟨ con i ∷ c ,                                     s , comp-env ρ ⟩ ⟶⟨ con ⟩
  ⟨         c , val (Lambda.Syntax.Closure.con i) ∷ s , comp-env ρ ⟩ ∎
correct⇓′ {ρ = ρ} {c} {s} (ƛ {t}) = begin
  ⟨ clo (comp t [ ret ]) ∷ c ,                          s , comp-env ρ ⟩ ⟶⟨ clo ⟩
  ⟨                        c , val (comp-val (ƛ t ρ)) ∷ s , comp-env ρ ⟩ ∎
correct⇓′ {ρ = ρ} {c} {s} {v} (app {t₁} {t₂} {t = t} {ρ′} {v′ = v′} t₁⇓ t₂⇓ t₁t₂⇓) = begin
  ⟨ comp t₁ (comp t₂ (app ∷ c)) ,                                               s ,               comp-env ρ  ⟩ ⟶⋆⟨ correct⇓′ t₁⇓ ⟩
  ⟨          comp t₂ (app ∷ c)  ,                     val (comp-val (ƛ t ρ′)) ∷ s ,               comp-env ρ  ⟩ ⟶⋆⟨ correct⇓′ t₂⇓ ⟩
  ⟨                   app ∷ c   , val (comp-val v′) ∷ val (comp-val (ƛ t ρ′)) ∷ s ,               comp-env ρ  ⟩ ⟶⟨ app ⟩
  ⟨ comp t [ ret ]              ,                          ret c (comp-env ρ) ∷ s , comp-val v′ ∷ comp-env ρ′ ⟩ ⟶⋆⟨ correct⇓′ t₁t₂⇓ ⟩
  ⟨        [ ret ]              ,       val (comp-val v) ∷ ret c (comp-env ρ) ∷ s , comp-val v′ ∷ comp-env ρ′ ⟩ ⟶⟨ ret ⟩
  ⟨                         c   ,       val (comp-val v) ∷                      s ,               comp-env ρ  ⟩ ∎

correct⇑′ : ∀ {n ρ c s} {t : Tm n} →
            ρ ⊢ t ⇑ → ⟨ comp t c , s , comp-env ρ ⟩ ⟶∞′
correct⇑′ {ρ = ρ} {c} {s} (app {t₁} {t₂} {t = t} {ρ′} {v′} t₁⇓ t₂⇓ t₁t₂⇑) =
  ⟨ comp t₁ (comp t₂ (app ∷ c)) ,                                               s ,               comp-env ρ  ⟩ ⟶⋆⟨ correct⇓′ t₁⇓ ⟩′
  ⟨          comp t₂ (app ∷ c)  ,                     val (comp-val (ƛ t ρ′)) ∷ s ,               comp-env ρ  ⟩ ⟶⋆⟨ correct⇓′ t₂⇓ ⟩′
  ⟨                   app ∷ c   , val (comp-val v′) ∷ val (comp-val (ƛ t ρ′)) ∷ s ,               comp-env ρ  ⟩ ⟶⟨ app ⟩′ ♯
 (⟨ comp t [ ret ]              ,                          ret c (comp-env ρ) ∷ s , comp-val v′ ∷ comp-env ρ′ ⟩ ⟶∞⟨ correct⇑′ (♭ t₁t₂⇑) ⟩)
correct⇑′ {ρ = ρ} {c} {s} (·ˡ {t₁} {t₂} t₁⇑) =
  ⟨ comp t₁ (comp t₂ (app ∷ c)) , s , comp-env ρ ⟩ ⟶∞⟨ correct⇑′ (♭ t₁⇑) ⟩
correct⇑′ {ρ = ρ} {c} {s} (·ʳ {t₁} {t₂} {v} t₁⇓ t₂⇑) =
  ⟨ comp t₁ (comp t₂ (app ∷ c)) ,                    s , comp-env ρ ⟩ ⟶⋆⟨ correct⇓′ t₁⇓ ⟩′
  ⟨          comp t₂ (app ∷ c)  , val (comp-val v) ∷ s , comp-env ρ ⟩ ⟶∞⟨ correct⇑′ (♭ t₂⇑) ⟩

correct⇓ : ∀ {t v} →
           [] ⊢ t ⇓ v →
           ∃ λ s → ⟨ comp t [] , [] , [] ⟩ ⟶⋆ s × s ⇓ comp-val v
correct⇓ t⇓ = (_ , correct⇓′ t⇓ , final)

correct⇑ : ∀ {t} → [] ⊢ t ⇑ → ⟨ comp t [] , [] , [] ⟩ ⟶∞
correct⇑ = InfiniteSequence.sound ∘ correct⇑′


-- {-# OPTIONS --show-implicit --show-irrelevant #-}
module Data.QuadTree.LensProofs.Valid-LensGo where

open import Haskell.Prelude renaming (zero to Z; suc to S)
open import Data.Lens.Lens
open import Data.Logic
open import Data.QuadTree.InternalAgda
open import Agda.Primitive
open import Data.Lens.Proofs.LensLaws
open import Data.Lens.Proofs.LensPostulates
open import Data.Lens.Proofs.LensComposition
open import Data.QuadTree.LensProofs.Valid-LensLeaf
open import Data.QuadTree.LensProofs.Valid-LensA
open import Data.QuadTree.LensProofs.Valid-LensBCD
open import Axiom.Extensionality.Propositional
open import Data.QuadTree.Implementation.QuadrantLenses
open import Data.QuadTree.Implementation.Definition
open import Data.QuadTree.Implementation.ValidTypes
open import Data.QuadTree.Implementation.SafeFunctions
open import Data.QuadTree.Implementation.PublicFunctions
open import Data.QuadTree.Implementation.DataLenses

{-
I spend close to 50 hours on this file, at the time of writing more than the rest of the lens proofs combined.
The file still looks horrible and there is very serious code duplication, but I honestly wish to never look at this file again.

In order to proof this we need some form of Extensionality
Because this is quite a complicated application of Extensionality, I did not proof it from generic Extensionality, but just postulated it seperately.
It is needed because Agda eagerly applies implicit arguments at the start of a function, which I don't want it to do
This can be used to reverse agda doing that
-}

postulate
    FunctorExtensionality :
        {t : Set} {{eqT : Eq t}} -> {deps : Nat} ->
        {la lb : Lens (VQuadrant t {S deps}) t}
        -> ({f : Set -> Set} -> {{ff : Functor f}} -> (la {f} {{ff}} ≡ lb {f} {{ff}}))
        -> (λ {f} ⦃ ff ⦄ → la {f} {{ff}}) ≡ (λ {f} ⦃ ff ⦄ → lb {f} {{ff}})

---- Lens laws for go

ValidLens-go-ViewSet :
    {t : Set} {{eqT : Eq t}}
    -> (loc : Nat × Nat) -> (dep : Nat)
    -> {ins : IsTrue (isInsidePow loc dep)}
    -> ViewSet (go {t} loc dep {ins})
ValidLens-go-SetView :
    {t : Set} {{eqT : Eq t}}
    -> (loc : Nat × Nat) -> (dep : Nat)
    -> {ins : IsTrue (isInsidePow loc dep)}
    -> SetView (go {t} loc dep {ins})
ValidLens-go-SetSet :
    {t : Set} {{eqT : Eq t}}
    -> (loc : Nat × Nat) -> (dep : Nat)
    -> {ins : IsTrue (isInsidePow loc dep)}
    -> SetSet (go {t} loc dep {ins})

ValidLens-go : {t : Set} {{eqT : Eq t}}
    -> (loc : Nat × Nat) -> (dep : Nat)
    -> {IsTrue (isInsidePow loc dep)}
    -> ValidLens (VQuadrant t {dep}) t
ValidLens-go {t} {{eqT}} loc dep {ins} = CValidLens (go loc dep {ins}) (ValidLens-go-ViewSet loc dep {ins}) (ValidLens-go-SetView loc dep {ins}) (ValidLens-go-SetSet loc dep {ins})

ValidLens-go-ViewSet loc Z v cvq@(CVQuadrant (Leaf x) {p}) = refl
ValidLens-go-ViewSet {t} (x , y) dep@(S deps) {ins} v cvq@(CVQuadrant qd {p}) =
    ifc y < mid then (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> view g (set g v cvq))
                    -- Select only the lensA branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifTrue (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-ViewSet (ValidLens-LensA) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) v cvq)
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> view g (set g v cvq))
                    -- Select only the lensB branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifFalse (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-ViewSet (ValidLens-LensB) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) v cvq)
        )
    ) else (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> view g (set g v cvq))
                    -- Select only the lensC branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifTrue (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-ViewSet (ValidLens-LensC) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) v cvq)
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> view g (set g v cvq))
                    -- Select only the lensD branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifFalse (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-ViewSet (ValidLens-LensD) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) v cvq)
        )
    )
    where
        mid = pow 2 deps

ValidLens-go-SetView loc Z cvq@(CVQuadrant (Leaf x) {p}) = refl
ValidLens-go-SetView {t} (x , y) dep@(S deps) {ins} cvq@(CVQuadrant qd {p}) =
    ifc y < mid then (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g (view g cvq) cvq)
                    -- Select only the lensA branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifTrue (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-SetView (ValidLens-LensA) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) cvq)
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g (view g cvq) cvq)
                    -- Select only the lensB branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifFalse (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-SetView (ValidLens-LensB) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) cvq)
        )
    ) else (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g (view g cvq) cvq)
                    -- Select only the lensC branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifTrue (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-SetView (ValidLens-LensC) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) cvq)
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g (view g cvq) cvq)
                    -- Select only the lensD branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifFalse (x < mid) px))))
                -- Then use the property of composition to proof the law
                (prop-Composition-SetView (ValidLens-LensD) (ValidLens-go (mod x mid , mod y mid) deps {andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})}) cvq)
        )
    )
    where
        mid = pow 2 deps

ValidLens-go-SetSet loc Z v1 v2 cvq@(CVQuadrant (Leaf x) {p}) = refl
ValidLens-go-SetSet {t} (x , y) dep@(S deps) {ins} v1 v2 cvq@(CVQuadrant qd {p}) =
    ifc y < mid then (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 (set g v1 cvq))
                    -- Select only the lensA branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifTrue (x < mid) px))))
                (trans
                    -- Then use the property of composition to proof the law
                    (prop-Composition-SetSet (ValidLens-LensA) (ValidLens-go (mod x mid , mod y mid) deps {modproof}) v1 v2 cvq)
                    -- Finally, cong back over the law, applying the transformation to go
                    (sym $ cong {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}}}
                        (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 cvq)
                        (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifTrue (x < mid) px)))))
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 (set g v1 cvq))
                    -- Select only the lensB branch
                    (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifFalse (x < mid) px))))
                (trans
                    -- Then use the property of composition to proof the law
                    (prop-Composition-SetSet (ValidLens-LensB) (ValidLens-go (mod x mid , mod y mid) deps {modproof}) v1 v2 cvq)
                    -- Finally, cong back over the law, applying the transformation to go
                    (sym $ cong {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}}}
                        (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 cvq)
                        (FunctorExtensionality (trans (ifTrue (y < mid) py) (ifFalse (x < mid) px)))))
        )
    ) else (λ {{py}} ->
        ifc x < mid then (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 (set g v1 cvq))
                    -- Select only the lensC branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifTrue (x < mid) px))))
                (trans
                    -- Then use the property of composition to proof the law
                    (prop-Composition-SetSet (ValidLens-LensC) (ValidLens-go (mod x mid , mod y mid) deps {modproof}) v1 v2 cvq)
                    -- Finally, cong back over the law, applying the transformation to go
                    (sym $ cong {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}}}
                        (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 cvq)
                        (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifTrue (x < mid) px)))))
        ) else (λ {{px}} ->
            trans
                (cong
                    -- We need to explicitly provide the x, otherwise agda gets confused
                    {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}} }
                    -- We cong over the law, applying the transformation to go
                    (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 (set g v1 cvq))
                    -- Select only the lensD branch
                    (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifFalse (x < mid) px))))
                (trans
                    -- Then use the property of composition to proof the law
                    (prop-Composition-SetSet (ValidLens-LensD) (ValidLens-go (mod x mid , mod y mid) deps {modproof}) v1 v2 cvq)
                    -- Finally, cong back over the law, applying the transformation to go
                    (sym $ cong {x = λ {f} ⦃ ff ⦄ → go (x , y) (S deps) {ins} {f} {{ff}}}
                        (λ (g : Lens (VQuadrant t {dep}) t) -> set g v2 cvq)
                        (FunctorExtensionality (trans (ifFalse (y < mid) py) (ifFalse (x < mid) px)))))
        )
    )

    where
        mid = pow 2 deps
        modproof = andCombine (modLt x mid {pow_not_zero_cv deps}) (modLt y mid {pow_not_zero_cv deps})


{-# OPTIONS --without-K --exact-split #-}

module free-group where

import 15-groups
open 15-groups public

{- We state the universal property of the free group on a set X -}

precomp-universal-property-free-group :
  {l1 l2 l3 : Level} (X : UU-Set l1) (F : Group l2) (G : Group l3)
  (f : type-hom-Set X (set-Group F)) →
  hom-Group F G → type-hom-Set X (set-Group G)
precomp-universal-property-free-group X F G f g = (map-hom-Group F G g) ∘ f

universal-property-free-group :
  (l : Level) {l1 l2 : Level} (X : UU-Set l1) (F : Group l2)
  (f : type-hom-Set X (set-Group F)) → UU (lsuc l ⊔ l1 ⊔ l2)
universal-property-free-group l X F f =
  (G : Group l) → is-equiv (precomp-universal-property-free-group X F G f)

{- We state the universal property of the initial set equipped with a point
   and an automorphism for each x : X. -}

universal-property-initial-pset-with-automorphisms :
  (l : Level) {l1 l2 : Level} (X : UU-Set l1) (Y : UU-Set l2)
  (pt-Y : type-Set Y) (f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  UU (lsuc l ⊔ l1 ⊔ l2)
universal-property-initial-pset-with-automorphisms l X Y pt-Y f =
  (Z : UU-Set l) (pt-Z : type-Set Z) (g : type-Set X → (type-Set Z ≃ type-Set Z)) →
  is-contr
    ( Σ ( type-hom-Set Y Z)
        ( λ h →
          ( Id (h pt-Y) pt-Z) ×
          ( (x : type-Set X) (y : type-Set Y) →
            Id (h (map-equiv (f x) y)) (map-equiv (g x) (h y)))))

map-initial-pset-with-automorphisms :
  { l1 l2 l3 : Level} (X : UU-Set l1) (Y : UU-Set l2) (pt-Y : type-Set Y)
  ( f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  ( Z : UU-Set l3) (pt-Z : type-Set Z)
  ( g : type-Set X → (type-Set Z ≃ type-Set Z)) →
  type-hom-Set Y Z
map-initial-pset-with-automorphisms
  X Y pt-Y f up-Y Z pt-Z g = pr1 (center (up-Y Z pt-Z g))

preserves-point-map-initial-pset-with-automorphisms :
  { l1 l2 l3 : Level} (X : UU-Set l1) (Y : UU-Set l2) (pt-Y : type-Set Y)
  ( f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( up-Y : {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  ( Z : UU-Set l3) (pt-Z : type-Set Z)
  ( g : type-Set X → (type-Set Z ≃ type-Set Z)) →
  Id (map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g pt-Y) pt-Z
preserves-point-map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g =
  pr1 (pr2 (center (up-Y Z pt-Z g)))

htpy-map-initial-pset-with-automorphisms :
  { l1 l2 l3 : Level} (X : UU-Set l1) (Y : UU-Set l2) (pt-Y : type-Set Y)
  ( f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( up-Y : {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  ( Z : UU-Set l3) (pt-Z : type-Set Z)
  ( g : type-Set X → (type-Set Z ≃ type-Set Z)) →
  ( x : type-Set X) (y : type-Set Y) →
  Id ( map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g
       ( map-equiv (f x) y))
     ( map-equiv (g x)
       ( map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g y))
htpy-map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g =
  pr2 (pr2 (center (up-Y Z pt-Z g)))

{- We show a dependent elimination property for the initial pointed set with
   an automorphism for each x : X. -}

dependent-up-initial-pset-with-automorphisms :
  { l1 l2 l3 l4 : Level} (X : UU-Set l1) (Y : UU-Set l2) (pt-Y : type-Set Y)
  ( f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( up-Y : {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  ( Z : UU-Set l3) (pt-Z : type-Set Z)
  ( g : type-Set X → (type-Set Z ≃ type-Set Z)) →
  ( P : type-Set Z → UU-Set l4)
  ( p : type-Set (P pt-Z))
  ( e : (x : type-Set X) (z : type-Set Z) →
        type-Set (P z) ≃ type-Set (P (map-equiv (g x) z))) →
  ( y : type-Set Y) →
  type-Set (P (map-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g y))
dependent-up-initial-pset-with-automorphisms X Y pt-Y f up-Y Z pt-Z g P p e =
  {!!}

{- We show that the initial set equipped with a point and an automorphism for
   each x : X is a group. -}

mul-initial-pset-with-automorphisms :
  {l1 l2 : Level} (X : UU-Set l1) (Y : UU-Set l2)
  (pt-Y : type-Set Y) (f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  type-Set Y → type-Set Y → type-Set Y
mul-initial-pset-with-automorphisms {l1} {l2} X Y pt-Y f up-Y =
  map-initial-pset-with-automorphisms
    X Y pt-Y f up-Y (hom-Set Y Y) id
    ( λ x → equiv-postcomp (type-Set Y) (f x))

associative-mul-initial-pset-with-automorphisms :
  {l1 l2 : Level} (X : UU-Set l1) (Y : UU-Set l2)
  (pt-Y : type-Set Y) (f : type-Set X → (type-Set Y ≃ type-Set Y)) →
  ( up-Y : {l : Level} →
    universal-property-initial-pset-with-automorphisms l X Y pt-Y f) →
  ( x y z : type-Set Y) →
  Id ( mul-initial-pset-with-automorphisms X Y pt-Y f up-Y
       ( mul-initial-pset-with-automorphisms X Y pt-Y f up-Y x y) z)
     ( mul-initial-pset-with-automorphisms X Y pt-Y f up-Y x
       ( mul-initial-pset-with-automorphisms X Y pt-Y f up-Y y z))
associative-mul-initial-pset-with-automorphisms X Y pt-Y f up-Y x y z = {!refl!}


{-# OPTIONS --safe #-}
module Cubical.Categories.Limits.Pullback where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.HLevels
open import Cubical.HITs.PropositionalTruncation.Base

open import Cubical.Data.Sigma

open import Cubical.Categories.Category
open import Cubical.Categories.Functor
open import Cubical.Categories.Instances.Cospan

private
  variable
    ℓ ℓ' : Level

module _ (C : Category ℓ ℓ') where

  open Category C
  open Functor

  record Cospan : Type (ℓ-max ℓ ℓ') where
    constructor cospan
    field
      l m r : ob
      s₁ : C [ l , m ]
      s₂ : C [ r , m ]

  open Cospan

  isPullback : (cspn : Cospan) →
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂) → Type (ℓ-max ℓ ℓ')
  isPullback cspn {c} p₁ p₂ H =
    ∀ {d} (h : C [ d , cspn .l ]) (k : C [ d , cspn .r ])
          (H' : h ⋆ cspn .s₁ ≡ k ⋆ cspn .s₂)
    → ∃![ hk ∈ C [ d , c ] ] (h ≡ hk ⋆ p₁) × (k ≡ hk ⋆ p₂)

  isPropIsPullback : (cspn : Cospan) →
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂) → isProp (isPullback cspn p₁ p₂ H)
  isPropIsPullback cspn p₁ p₂ H =
    isPropImplicitΠ (λ x → isPropΠ3 λ h k H' → isPropIsContr)

  record Pullback (cspn : Cospan) : Type (ℓ-max ℓ ℓ') where
    field
      pbOb  : ob
      pbPr₁ : C [ pbOb , cspn .l ]
      pbPr₂ : C [ pbOb , cspn .r ]
      pbCommutes : pbPr₁ ⋆ cspn .s₁ ≡ pbPr₂ ⋆ cspn .s₂
      univProp : isPullback cspn pbPr₁ pbPr₂ pbCommutes

  open Pullback

  pullbackArrow :
    {cspn : Cospan} (pb : Pullback cspn)
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂) → C [ c , pb . pbOb ]
  pullbackArrow pb p₁ p₂ H = pb .univProp p₁ p₂ H .fst .fst

  pullbackArrowPr₁ :
    {cspn : Cospan} (pb : Pullback cspn)
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂) →
    p₁ ≡ pullbackArrow pb p₁ p₂ H ⋆ pbPr₁ pb
  pullbackArrowPr₁ pb p₁ p₂ H = pb .univProp p₁ p₂ H .fst .snd .fst

  pullbackArrowPr₂ :
    {cspn : Cospan} (pb : Pullback cspn)
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂) →
    p₂ ≡ pullbackArrow pb p₁ p₂ H ⋆ pbPr₂ pb
  pullbackArrowPr₂ pb p₁ p₂ H = pb .univProp p₁ p₂ H .fst .snd .snd

  pullbackArrowUnique :
    {cspn : Cospan} (pb : Pullback cspn)
    {c : ob} (p₁ : C [ c , cspn .l ]) (p₂ : C [ c , cspn .r ])
    (H : p₁ ⋆ cspn .s₁ ≡ p₂ ⋆ cspn .s₂)
    (pbArrow' : C [ c , pb .pbOb ])
    (H₁ : p₁ ≡ pbArrow' ⋆ pbPr₁ pb) (H₂ : p₂ ≡ pbArrow' ⋆ pbPr₂ pb)
    → pullbackArrow pb p₁ p₂ H ≡ pbArrow'
  pullbackArrowUnique pb p₁ p₂ H pbArrow' H₁ H₂ =
    cong fst (pb .univProp p₁ p₂ H .snd (pbArrow' , (H₁ , H₂)))

  Pullbacks : Type (ℓ-max ℓ ℓ')
  Pullbacks = (cspn : Cospan) → Pullback cspn

  hasPullbacks : Type (ℓ-max ℓ ℓ')
  hasPullbacks = ∥ Pullbacks ∥

-- TODO: finish the following show that this definition of Pullback
-- is equivalent to the Cospan limit
module _ {C : Category ℓ ℓ'} where
  open Category C
  open Functor

  Cospan→Func : Cospan C → Functor CospanCat C
  Cospan→Func (cospan l m r f g) .F-ob ⓪ = l
  Cospan→Func (cospan l m r f g) .F-ob ① = m
  Cospan→Func (cospan l m r f g) .F-ob ② = r
  Cospan→Func (cospan l m r f g) .F-hom {⓪} {①} k = f
  Cospan→Func (cospan l m r f g) .F-hom {②} {①} k = g
  Cospan→Func (cospan l m r f g) .F-hom {⓪} {⓪} k = id
  Cospan→Func (cospan l m r f g) .F-hom {①} {①} k = id
  Cospan→Func (cospan l m r f g) .F-hom {②} {②} k = id
  Cospan→Func (cospan l m r f g) .F-id {⓪} = refl
  Cospan→Func (cospan l m r f g) .F-id {①} = refl
  Cospan→Func (cospan l m r f g) .F-id {②} = refl
  Cospan→Func (cospan l m r f g) .F-seq {⓪} {⓪} {⓪} φ ψ = sym (⋆IdL _)
  Cospan→Func (cospan l m r f g) .F-seq {⓪} {⓪} {①} φ ψ = sym (⋆IdL _)
  Cospan→Func (cospan l m r f g) .F-seq {⓪} {①} {①} φ ψ = sym (⋆IdR _)
  Cospan→Func (cospan l m r f g) .F-seq {①} {①} {①} φ ψ = sym (⋆IdL _)
  Cospan→Func (cospan l m r f g) .F-seq {②} {②} {②} φ ψ = sym (⋆IdL _)
  Cospan→Func (cospan l m r f g) .F-seq {②} {②} {①} φ ψ = sym (⋆IdL _)
  Cospan→Func (cospan l m r f g) .F-seq {②} {①} {①} φ ψ = sym (⋆IdR _)


open import Prelude
open import core

module lemmas-consistency where
  -- type consistency is symmetric
  ~sym : {t1 t2 : htyp} → t1 ~ t2 → t2 ~ t1
  ~sym TCRefl = TCRefl
  ~sym TCHole1 = TCHole2
  ~sym TCHole2 = TCHole1
  ~sym (TCArr p1 p2) = TCArr (~sym p1) (~sym p2)

  -- type consistency isn't transitive
  not-trans : ((t1 t2 t3 : htyp) → t1 ~ t2 → t2 ~ t3 → t1 ~ t3) → ⊥
  not-trans t with t (b ==> b) ⦇-⦈ b TCHole1 TCHole2
  ... | ()

  --  every pair of types is either consistent or not consistent
  ~dec : (t1 t2 : htyp) → ((t1 ~ t2) + (t1 ~̸ t2))
    -- this takes care of all hole cases, so we don't consider them below
  ~dec _ ⦇-⦈ = Inl TCHole1
  ~dec ⦇-⦈ _ = Inl TCHole2
    -- num cases
  ~dec b b = Inl TCRefl
  ~dec b (t2 ==> t3) = Inr ICBaseArr1
    -- arrow cases
  ~dec (t1 ==> t2) b = Inr ICBaseArr2
  ~dec (t1 ==> t2) (t3 ==> t4) with ~dec t1 t3 | ~dec t2 t4
  ... | Inl x | Inl y = Inl (TCArr x y)
  ... | Inl _ | Inr y = Inr (ICArr2 y)
  ... | Inr x | _     = Inr (ICArr1 x)

  -- no pair of types is both consistent and not consistent
  ~apart : {t1 t2 : htyp} → (t1 ~̸ t2) → (t1 ~ t2) → ⊥
  ~apart ICBaseArr1 ()
  ~apart ICBaseArr2 ()
  ~apart (ICArr1 x) TCRefl = ~apart x TCRefl
  ~apart (ICArr1 x) (TCArr y y₁) = ~apart x y
  ~apart (ICArr2 x) TCRefl = ~apart x TCRefl
  ~apart (ICArr2 x) (TCArr y y₁) = ~apart x y₁


{-# OPTIONS --safe #-}

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

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

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

Pr′ : A′ (p , i) → Set c
Pr′ {i} = unpred′ I _ Pr i

Below : (x : A′ (p , i)) → Acc x → Setω

BelowIndArg : (C : ConDesc P V I)
              (x : ⟦ C ⟧IndArg A′ (p , v))
            → AllIndArgω Acc C x
            → Setω
BelowIndArg (var _) x = Below x
BelowIndArg (π ai S C) x a = ∀ s → BelowIndArg C (app< ai > x s) (a s)
BelowIndArg (A ⊗ B) (x , y) (ax , ay)
  = BelowIndArg A x ax ×ω BelowIndArg B y ay

BelowCon : (C : ConDesc P V I)
           (x : ⟦ C ⟧Con A′ (p , v , i))
         → AllConω Acc C x
         → Setω
BelowCon (var _) x _ = ⊤ω
BelowCon (π ai S C) (s , x) = BelowCon C x
BelowCon (A ⊗ B) (f , x) (af , ax)
  =  BelowIndArg A f af
  ×ω BelowCon B x ax

Below x (acc a) with split x
... | (k , x) = BelowCon _ x a

module _ (f : ∀ {i} (x : A′ (p , i)) (a : Acc x) → Below x a → Pr′ x) where

  below : (x : A′ (p , i)) (a : Acc x) → Below x a
  below x (acc a) with split x
  ... | (k , x) = belowCon _ x a
    where
      belowIndArg : (C : ConDesc P V I)
                    (x : ⟦ C ⟧IndArg A′ (p , v))
                    (a : AllIndArgω Acc C x)
                  → BelowIndArg C x a
      belowIndArg (var _) x a = below x a
      belowIndArg (π ai _ C) x a s =
        belowIndArg C (app< ai > x s) (a s)
      belowIndArg (A ⊗ B) (x , y) (ax , ay)
        = belowIndArg A x ax
        , belowIndArg B y ay

      belowCon : (C : ConDesc P V I)
                 (x : ⟦ C ⟧Con A′ (p , v , i))
                 (a : AllConω Acc C x)
               → BelowCon C x a
      belowCon (var _) _ _ = tt
      belowCon (π _ _ C) (_ , x) a = belowCon C x a
      belowCon (A ⊗ B) (f , x) (af , ax)
        = belowIndArg A f af
        , belowCon B x ax

  rec : (x : A′ (p , i)) → Pr′ x
  rec x = f x (wf x) (below x (wf x))