open import Prelude
open import Nat
open import dynamics-core
open import contexts
open import disjointness

-- this module contains lemmas and properties about the holes-disjoint
-- judgement that double check that it acts as we would expect

module holes-disjoint-checks where
  -- these lemmas are all structurally recursive and quite
  -- mechanical. morally, they establish the properties about reduction
  -- that would be obvious / baked into Agda if holes-disjoint was defined
  -- as a function rather than a judgement (datatype), or if we had defined
  -- all the O(n^2) cases rather than relying on a little indirection to
  -- only have O(n) cases. that work has to go somewhwere, and we prefer
  -- that it goes here.
  ds-lem-num  : ∀{e n} → holes-disjoint e (N n)
  ds-lem-num {N x} = HDNum
  ds-lem-num {e ·+ e₁} = HDPlus ds-lem-num ds-lem-num
  ds-lem-num {e ·: x} = HDAsc ds-lem-num
  ds-lem-num {X x} = HDVar
  ds-lem-num {·λ x e} = HDLam1 ds-lem-num
  ds-lem-num {·λ x ·[ x₁ ] e} = HDLam2 ds-lem-num
  ds-lem-num {e ∘ e₁} = HDAp ds-lem-num ds-lem-num
  ds-lem-num {inl e} = HDInl ds-lem-num
  ds-lem-num {inr e} = HDInr ds-lem-num
  ds-lem-num {case e x e₁ x₁ e₂} = HDCase ds-lem-num ds-lem-num ds-lem-num
  ds-lem-num {⟨ e , e₁ ⟩} = HDPair ds-lem-num ds-lem-num
  ds-lem-num {fst e} = HDFst ds-lem-num
  ds-lem-num {snd e} = HDSnd ds-lem-num
  ds-lem-num {⦇-⦈[ x ]} = HDHole HNNum
  ds-lem-num {⦇⌜ e ⌟⦈[ x ]} = HDNEHole HNNum ds-lem-num

  ds-lem-plus : ∀{e1 e2 e3} → holes-disjoint e3 e1 → holes-disjoint e3 e2 → holes-disjoint e3 (e1 ·+ e2)
  ds-lem-plus HDNum ν = HDNum
  ds-lem-plus (HDPlus hd hd₁) (HDPlus ν ν₁) = HDPlus (ds-lem-plus hd ν) (ds-lem-plus hd₁ ν₁)
  ds-lem-plus (HDAsc hd) (HDAsc ν) = HDAsc (ds-lem-plus hd ν)
  ds-lem-plus HDVar HDVar = HDVar
  ds-lem-plus (HDLam1 hd) (HDLam1 ν) = HDLam1 (ds-lem-plus hd ν)
  ds-lem-plus (HDLam2 hd) (HDLam2 ν) = HDLam2 (ds-lem-plus hd ν)
  ds-lem-plus (HDAp hd hd₁) (HDAp ν ν₁) = HDAp (ds-lem-plus hd ν) (ds-lem-plus hd₁ ν₁)
  ds-lem-plus (HDInl hd) (HDInl ν) = HDInl (ds-lem-plus hd ν)
  ds-lem-plus (HDInr hd) (HDInr ν) = HDInr (ds-lem-plus hd ν)
  ds-lem-plus (HDCase hd hd₁ hd₂) (HDCase ν ν₁ ν₂) = HDCase (ds-lem-plus hd ν) (ds-lem-plus hd₁ ν₁) (ds-lem-plus hd₂ ν₂)
  ds-lem-plus (HDPair hd hd₁) (HDPair ν ν₁) = HDPair (ds-lem-plus hd ν) (ds-lem-plus hd₁ ν₁)
  ds-lem-plus (HDFst hd) (HDFst ν) = HDFst (ds-lem-plus hd ν)
  ds-lem-plus (HDSnd hd) (HDSnd ν) = HDSnd (ds-lem-plus hd ν)
  ds-lem-plus (HDHole x) (HDHole x₁) = HDHole (HNPlus x x₁)
  ds-lem-plus (HDNEHole x hd) (HDNEHole x₁ ν) = HDNEHole (HNPlus x x₁) (ds-lem-plus hd ν)

  ds-lem-var : ∀{e x} → holes-disjoint e (X x)
  ds-lem-var {N x} = HDNum
  ds-lem-var {e ·+ e₁} = HDPlus ds-lem-var ds-lem-var
  ds-lem-var {e ·: x} = HDAsc ds-lem-var
  ds-lem-var {X x} = HDVar
  ds-lem-var {·λ x e} = HDLam1 ds-lem-var
  ds-lem-var {·λ x ·[ x₁ ] e} = HDLam2 ds-lem-var
  ds-lem-var {e ∘ e₁} = HDAp ds-lem-var ds-lem-var
  ds-lem-var {inl e} = HDInl ds-lem-var
  ds-lem-var {inr e} = HDInr ds-lem-var
  ds-lem-var {case e x e₁ x₁ e₂} = HDCase ds-lem-var ds-lem-var ds-lem-var
  ds-lem-var {⟨ e , e₁ ⟩} = HDPair ds-lem-var ds-lem-var
  ds-lem-var {fst e} = HDFst ds-lem-var
  ds-lem-var {snd e} = HDSnd ds-lem-var
  ds-lem-var {⦇-⦈[ x ]} = HDHole HNVar
  ds-lem-var {⦇⌜ e ⌟⦈[ x ]} = HDNEHole HNVar ds-lem-var

  ds-lem-asc : ∀{e1 e2 τ} → holes-disjoint e2 e1 → holes-disjoint e2 (e1 ·: τ)
  ds-lem-asc HDNum = HDNum
  ds-lem-asc (HDPlus hd hd₁) = HDPlus (ds-lem-asc hd) (ds-lem-asc hd₁)
  ds-lem-asc (HDAsc hd) = HDAsc (ds-lem-asc hd)
  ds-lem-asc HDVar = HDVar
  ds-lem-asc (HDLam1 hd) = HDLam1 (ds-lem-asc hd)
  ds-lem-asc (HDLam2 hd) = HDLam2 (ds-lem-asc hd)
  ds-lem-asc (HDAp hd hd₁) = HDAp (ds-lem-asc hd) (ds-lem-asc hd₁)
  ds-lem-asc (HDInl hd) = HDInl (ds-lem-asc hd)
  ds-lem-asc (HDInr hd) = HDInr (ds-lem-asc hd)
  ds-lem-asc (HDCase hd hd₁ hd₂) = HDCase (ds-lem-asc hd) (ds-lem-asc hd₁) (ds-lem-asc hd₂)
  ds-lem-asc (HDPair hd hd₁) = HDPair (ds-lem-asc hd) (ds-lem-asc hd₁)
  ds-lem-asc (HDFst hd) = HDFst (ds-lem-asc hd)
  ds-lem-asc (HDSnd hd) = HDSnd (ds-lem-asc hd)
  ds-lem-asc (HDHole x) = HDHole (HNAsc x)
  ds-lem-asc (HDNEHole x hd) = HDNEHole (HNAsc x) (ds-lem-asc hd)

  ds-lem-lam1 : ∀{e1 e2 x} → holes-disjoint e2 e1 → holes-disjoint e2 (·λ x e1)
  ds-lem-lam1 HDNum = HDNum
  ds-lem-lam1 (HDPlus hd hd₁) = HDPlus (ds-lem-lam1 hd) (ds-lem-lam1 hd₁)
  ds-lem-lam1 (HDAsc hd) = HDAsc (ds-lem-lam1 hd)
  ds-lem-lam1 HDVar = HDVar
  ds-lem-lam1 (HDLam1 hd) = HDLam1 (ds-lem-lam1 hd)
  ds-lem-lam1 (HDLam2 hd) = HDLam2 (ds-lem-lam1 hd)
  ds-lem-lam1 (HDAp hd hd₁) = HDAp (ds-lem-lam1 hd) (ds-lem-lam1 hd₁)
  ds-lem-lam1 (HDInl hd) = HDInl (ds-lem-lam1 hd)
  ds-lem-lam1 (HDInr hd) = HDInr (ds-lem-lam1 hd)
  ds-lem-lam1 (HDCase hd hd₁ hd₂) = HDCase (ds-lem-lam1 hd) (ds-lem-lam1 hd₁) (ds-lem-lam1 hd₂)
  ds-lem-lam1 (HDPair hd hd₁) = HDPair (ds-lem-lam1 hd) (ds-lem-lam1 hd₁)
  ds-lem-lam1 (HDFst hd) = HDFst (ds-lem-lam1 hd)
  ds-lem-lam1 (HDSnd hd) = HDSnd (ds-lem-lam1 hd)
  ds-lem-lam1 (HDHole x₁) = HDHole (HNLam1 x₁)
  ds-lem-lam1 (HDNEHole x₁ hd) = HDNEHole (HNLam1 x₁) (ds-lem-lam1 hd)

  ds-lem-lam2 : ∀{e1 e2 x τ} → holes-disjoint e2 e1 → holes-disjoint e2 (·λ x ·[ τ ] e1)
  ds-lem-lam2 HDNum = HDNum
  ds-lem-lam2 (HDPlus hd hd₁) = HDPlus (ds-lem-lam2 hd) (ds-lem-lam2 hd₁)
  ds-lem-lam2 (HDAsc hd) = HDAsc (ds-lem-lam2 hd)
  ds-lem-lam2 HDVar = HDVar
  ds-lem-lam2 (HDLam1 hd) = HDLam1 (ds-lem-lam2 hd)
  ds-lem-lam2 (HDLam2 hd) = HDLam2 (ds-lem-lam2 hd)
  ds-lem-lam2 (HDAp hd hd₁) = HDAp (ds-lem-lam2 hd) (ds-lem-lam2 hd₁)
  ds-lem-lam2 (HDInl hd) = HDInl (ds-lem-lam2 hd)
  ds-lem-lam2 (HDInr hd) = HDInr (ds-lem-lam2 hd)
  ds-lem-lam2 (HDCase hd hd₁ hd₂) = HDCase (ds-lem-lam2 hd) (ds-lem-lam2 hd₁) (ds-lem-lam2 hd₂)
  ds-lem-lam2 (HDPair hd hd₁) = HDPair (ds-lem-lam2 hd) (ds-lem-lam2 hd₁)
  ds-lem-lam2 (HDFst hd) = HDFst (ds-lem-lam2 hd)
  ds-lem-lam2 (HDSnd hd) = HDSnd (ds-lem-lam2 hd)
  ds-lem-lam2 (HDHole x₁) = HDHole (HNLam2 x₁)
  ds-lem-lam2 (HDNEHole x₁ hd) = HDNEHole (HNLam2 x₁) (ds-lem-lam2 hd)

  ds-lem-ap : ∀{e1 e2 e3} → holes-disjoint e3 e1 → holes-disjoint e3 e2 → holes-disjoint e3 (e1 ∘ e2)
  ds-lem-ap HDNum hd2 = HDNum
  ds-lem-ap (HDPlus hd1 hd2) (HDPlus hd3 hd4) = HDPlus (ds-lem-ap hd1 hd3) (ds-lem-ap hd2 hd4)
  ds-lem-ap (HDAsc hd1) (HDAsc hd2) = HDAsc (ds-lem-ap hd1 hd2)
  ds-lem-ap HDVar hd2 = HDVar
  ds-lem-ap (HDLam1 hd1) (HDLam1 hd2) = HDLam1 (ds-lem-ap hd1 hd2)
  ds-lem-ap (HDLam2 hd1) (HDLam2 hd2) = HDLam2 (ds-lem-ap hd1 hd2)
  ds-lem-ap (HDAp hd1 hd2) (HDAp hd3 hd4) = HDAp (ds-lem-ap hd1 hd3) (ds-lem-ap hd2 hd4)
  ds-lem-ap (HDInl hd) (HDInl ν) = HDInl (ds-lem-ap hd ν)
  ds-lem-ap (HDInr hd) (HDInr ν) = HDInr (ds-lem-ap hd ν)
  ds-lem-ap (HDCase hd hd₁ hd₂) (HDCase ν ν₁ ν₂) = HDCase (ds-lem-ap hd ν) (ds-lem-ap hd₁ ν₁) (ds-lem-ap hd₂ ν₂)
  ds-lem-ap (HDPair hd hd₁) (HDPair ν ν₁) = HDPair (ds-lem-ap hd ν) (ds-lem-ap hd₁ ν₁)
  ds-lem-ap (HDFst hd) (HDFst ν) = HDFst (ds-lem-ap hd ν)
  ds-lem-ap (HDSnd hd) (HDSnd ν) = HDSnd (ds-lem-ap hd ν)
  ds-lem-ap (HDHole x) (HDHole x₁) = HDHole (HNAp x x₁)
  ds-lem-ap (HDNEHole x hd1) (HDNEHole x₁ hd2) = HDNEHole (HNAp x x₁) (ds-lem-ap hd1 hd2)

  ds-lem-inl : ∀{e1 e2} → holes-disjoint e2 e1 → holes-disjoint e2 (inl e1)
  ds-lem-inl HDNum = HDNum
  ds-lem-inl (HDPlus hd hd₁) = HDPlus (ds-lem-inl hd) (ds-lem-inl hd₁)
  ds-lem-inl (HDAsc hd) = HDAsc (ds-lem-inl hd)
  ds-lem-inl HDVar = HDVar
  ds-lem-inl (HDLam1 hd) = HDLam1 (ds-lem-inl hd)
  ds-lem-inl (HDLam2 hd) = HDLam2 (ds-lem-inl hd)
  ds-lem-inl (HDAp hd hd₁) = HDAp (ds-lem-inl hd) (ds-lem-inl hd₁)
  ds-lem-inl (HDInl hd) = HDInl (ds-lem-inl hd)
  ds-lem-inl (HDInr hd) = HDInr (ds-lem-inl hd)
  ds-lem-inl (HDCase hd hd₁ hd₂) = HDCase (ds-lem-inl hd) (ds-lem-inl hd₁) (ds-lem-inl hd₂)
  ds-lem-inl (HDPair hd hd₁) = HDPair (ds-lem-inl hd) (ds-lem-inl hd₁)
  ds-lem-inl (HDFst hd) = HDFst (ds-lem-inl hd)
  ds-lem-inl (HDSnd hd) = HDSnd (ds-lem-inl hd)
  ds-lem-inl (HDHole x) = HDHole (HNInl x)
  ds-lem-inl (HDNEHole x hd) = HDNEHole (HNInl x) (ds-lem-inl hd)

  ds-lem-inr : ∀{e1 e2} → holes-disjoint e2 e1 → holes-disjoint e2 (inr e1)
  ds-lem-inr HDNum = HDNum
  ds-lem-inr (HDPlus hd hd₁) = HDPlus (ds-lem-inr hd) (ds-lem-inr hd₁)
  ds-lem-inr (HDAsc hd) = HDAsc (ds-lem-inr hd)
  ds-lem-inr HDVar = HDVar
  ds-lem-inr (HDLam1 hd) = HDLam1 (ds-lem-inr hd)
  ds-lem-inr (HDLam2 hd) = HDLam2 (ds-lem-inr hd)
  ds-lem-inr (HDAp hd hd₁) = HDAp (ds-lem-inr hd) (ds-lem-inr hd₁)
  ds-lem-inr (HDInl hd) = HDInl (ds-lem-inr hd)
  ds-lem-inr (HDInr hd) = HDInr (ds-lem-inr hd)
  ds-lem-inr (HDCase hd hd₁ hd₂) = HDCase (ds-lem-inr hd) (ds-lem-inr hd₁) (ds-lem-inr hd₂)
  ds-lem-inr (HDPair hd hd₁) = HDPair (ds-lem-inr hd) (ds-lem-inr hd₁)
  ds-lem-inr (HDFst hd) = HDFst (ds-lem-inr hd)
  ds-lem-inr (HDSnd hd) = HDSnd (ds-lem-inr hd)
  ds-lem-inr (HDHole x) = HDHole (HNInr x)
  ds-lem-inr (HDNEHole x hd) = HDNEHole (HNInr x) (ds-lem-inr hd)

  ds-lem-case : ∀{e3 e x e1 y e2} → holes-disjoint e3 e → holes-disjoint e3 e1 → holes-disjoint e3 e2 → holes-disjoint e3 (case e x e1 y e2)
  ds-lem-case HDNum HDNum HDNum = HDNum
  ds-lem-case (HDPlus hd hd₁) (HDPlus ν ν₂) (HDPlus ν₁ ν₃) = HDPlus (ds-lem-case hd ν ν₁) (ds-lem-case hd₁ ν₂ ν₃)
  ds-lem-case (HDAsc hd) (HDAsc ν) (HDAsc ν₁) = HDAsc (ds-lem-case hd ν ν₁)
  ds-lem-case HDVar HDVar HDVar = HDVar
  ds-lem-case (HDLam1 hd) (HDLam1 ν) (HDLam1 ν₁) = HDLam1 (ds-lem-case hd ν ν₁)
  ds-lem-case (HDLam2 hd) (HDLam2 ν) (HDLam2 ν₁) = HDLam2 (ds-lem-case hd ν ν₁)
  ds-lem-case (HDAp hd hd₁) (HDAp ν ν₁) (HDAp ν₂ ν₃) = HDAp (ds-lem-case hd ν ν₂) (ds-lem-case hd₁ ν₁ ν₃)
  ds-lem-case (HDInl hd) (HDInl ν) (HDInl ν₁) = HDInl (ds-lem-case hd ν ν₁)
  ds-lem-case (HDInr hd) (HDInr ν) (HDInr ν₁) = HDInr (ds-lem-case hd ν ν₁)
  ds-lem-case (HDCase hd hd₁ hd₂) (HDCase ν ν₁ ν₂) (HDCase ν₃ ν₄ ν₅) = HDCase (ds-lem-case hd ν ν₃) (ds-lem-case hd₁ ν₁ ν₄) (ds-lem-case hd₂ ν₂ ν₅)
  ds-lem-case (HDPair hd hd₁) (HDPair ν ν₁) (HDPair ν₂ ν₃) = HDPair (ds-lem-case hd ν ν₂) (ds-lem-case hd₁ ν₁ ν₃)
  ds-lem-case (HDFst hd) (HDFst ν) (HDFst ν₁) = HDFst (ds-lem-case hd ν ν₁)
  ds-lem-case (HDSnd hd) (HDSnd ν) (HDSnd ν₁) = HDSnd (ds-lem-case hd ν ν₁)
  ds-lem-case (HDHole x) (HDHole x₁) (HDHole x₂) = HDHole (HNCase x x₁ x₂)
  ds-lem-case (HDNEHole x hd) (HDNEHole x₁ ν) (HDNEHole x₂ ν₁) = HDNEHole (HNCase x x₁ x₂) (ds-lem-case hd ν ν₁)

  ds-lem-pair : ∀{e1 e2 e3} → holes-disjoint e3 e1 → holes-disjoint e3 e2 → holes-disjoint e3 ⟨ e1 , e2 ⟩
  ds-lem-pair HDNum HDNum = HDNum
  ds-lem-pair (HDPlus hd hd₁) (HDPlus ν ν₁) = HDPlus (ds-lem-pair hd ν) (ds-lem-pair hd₁ ν₁)
  ds-lem-pair (HDAsc hd) (HDAsc ν) = HDAsc (ds-lem-pair hd ν)
  ds-lem-pair HDVar HDVar = HDVar
  ds-lem-pair (HDLam1 hd) (HDLam1 ν) = HDLam1 (ds-lem-pair hd ν)
  ds-lem-pair (HDLam2 hd) (HDLam2 ν) = HDLam2 (ds-lem-pair hd ν)
  ds-lem-pair (HDAp hd hd₁) (HDAp ν ν₁) = HDAp (ds-lem-pair hd ν) (ds-lem-pair hd₁ ν₁)
  ds-lem-pair (HDInl hd) (HDInl ν) = HDInl (ds-lem-pair hd ν)
  ds-lem-pair (HDInr hd) (HDInr ν) = HDInr (ds-lem-pair hd ν)
  ds-lem-pair (HDCase hd hd₁ hd₂) (HDCase ν ν₁ ν₂) = HDCase (ds-lem-pair hd ν) (ds-lem-pair hd₁ ν₁) (ds-lem-pair hd₂ ν₂)
  ds-lem-pair (HDPair hd hd₁) (HDPair ν ν₁) = HDPair (ds-lem-pair hd ν) (ds-lem-pair hd₁ ν₁)
  ds-lem-pair (HDFst hd) (HDFst ν) = HDFst (ds-lem-pair hd ν)
  ds-lem-pair (HDSnd hd) (HDSnd ν) = HDSnd (ds-lem-pair hd ν)
  ds-lem-pair (HDHole x) (HDHole x₁) = HDHole (HNPair x x₁)
  ds-lem-pair (HDNEHole x hd) (HDNEHole x₁ ν) = HDNEHole (HNPair x x₁) (ds-lem-pair hd ν)

  ds-lem-fst : ∀{e1 e2} → holes-disjoint e2 e1 → holes-disjoint e2 (fst e1)
  ds-lem-fst HDNum = HDNum
  ds-lem-fst (HDPlus hd hd₁) = HDPlus (ds-lem-fst hd) (ds-lem-fst hd₁)
  ds-lem-fst (HDAsc hd) = HDAsc (ds-lem-fst hd)
  ds-lem-fst HDVar = HDVar
  ds-lem-fst (HDLam1 hd) = HDLam1 (ds-lem-fst hd)
  ds-lem-fst (HDLam2 hd) = HDLam2 (ds-lem-fst hd)
  ds-lem-fst (HDAp hd hd₁) = HDAp (ds-lem-fst hd) (ds-lem-fst hd₁)
  ds-lem-fst (HDInl hd) = HDInl (ds-lem-fst hd)
  ds-lem-fst (HDInr hd) = HDInr (ds-lem-fst hd)
  ds-lem-fst (HDCase hd hd₁ hd₂) = HDCase (ds-lem-fst hd) (ds-lem-fst hd₁) (ds-lem-fst hd₂)
  ds-lem-fst (HDPair hd hd₁) = HDPair (ds-lem-fst hd) (ds-lem-fst hd₁)
  ds-lem-fst (HDFst hd) = HDFst (ds-lem-fst hd)
  ds-lem-fst (HDSnd hd) = HDSnd (ds-lem-fst hd)
  ds-lem-fst (HDHole x) = HDHole (HNFst x)
  ds-lem-fst (HDNEHole x hd) = HDNEHole (HNFst x) (ds-lem-fst hd)

  ds-lem-snd : ∀{e1 e2} → holes-disjoint e2 e1 → holes-disjoint e2 (snd e1)
  ds-lem-snd HDNum = HDNum
  ds-lem-snd (HDPlus hd hd₁) = HDPlus (ds-lem-snd hd) (ds-lem-snd hd₁)
  ds-lem-snd (HDAsc hd) = HDAsc (ds-lem-snd hd)
  ds-lem-snd HDVar = HDVar
  ds-lem-snd (HDLam1 hd) = HDLam1 (ds-lem-snd hd)
  ds-lem-snd (HDLam2 hd) = HDLam2 (ds-lem-snd hd)
  ds-lem-snd (HDAp hd hd₁) = HDAp (ds-lem-snd hd) (ds-lem-snd hd₁)
  ds-lem-snd (HDInl hd) = HDInl (ds-lem-snd hd)
  ds-lem-snd (HDInr hd) = HDInr (ds-lem-snd hd)
  ds-lem-snd (HDCase hd hd₁ hd₂) = HDCase (ds-lem-snd hd) (ds-lem-snd hd₁) (ds-lem-snd hd₂)
  ds-lem-snd (HDPair hd hd₁) = HDPair (ds-lem-snd hd) (ds-lem-snd hd₁)
  ds-lem-snd (HDFst hd) = HDFst (ds-lem-snd hd)
  ds-lem-snd (HDSnd hd) = HDSnd (ds-lem-snd hd)
  ds-lem-snd (HDHole x) = HDHole (HNSnd x)
  ds-lem-snd (HDNEHole x hd) = HDNEHole (HNSnd x) (ds-lem-snd hd)

  ds-lem-hole : ∀{e u} → hole-name-new e u → holes-disjoint e ⦇-⦈[ u ]
  ds-lem-hole HNNum = HDNum
  ds-lem-hole (HNPlus hnn hnn₁) = HDPlus (ds-lem-hole hnn) (ds-lem-hole hnn₁)
  ds-lem-hole (HNAsc hnn) = HDAsc (ds-lem-hole hnn)
  ds-lem-hole HNVar = HDVar
  ds-lem-hole (HNLam1 hnn) = HDLam1 (ds-lem-hole hnn)
  ds-lem-hole (HNLam2 hnn) = HDLam2 (ds-lem-hole hnn)
  ds-lem-hole (HNAp hnn hnn₁) = HDAp (ds-lem-hole hnn) (ds-lem-hole hnn₁)
  ds-lem-hole (HNInl hnn) = HDInl (ds-lem-hole hnn)
  ds-lem-hole (HNInr hnn) = HDInr (ds-lem-hole hnn)
  ds-lem-hole (HNCase hnn hnn₁ hnn₂) = HDCase (ds-lem-hole hnn) (ds-lem-hole hnn₁) (ds-lem-hole hnn₂)
  ds-lem-hole (HNPair hn hn₁) = HDPair (ds-lem-hole hn) (ds-lem-hole hn₁)
  ds-lem-hole (HNFst hn) = HDFst (ds-lem-hole hn)
  ds-lem-hole (HNSnd hn) = HDSnd (ds-lem-hole hn)
  ds-lem-hole (HNHole x) = HDHole (HNHole (flip x))
  ds-lem-hole (HNNEHole x hnn) = HDNEHole (HNHole (flip x)) (ds-lem-hole hnn)

  ds-lem-nehole : ∀{e e1 u} →
                  holes-disjoint e e1 →
                  hole-name-new e u →
                  holes-disjoint e ⦇⌜ e1 ⌟⦈[ u ]
  ds-lem-nehole HDNum ν = HDNum
  ds-lem-nehole (HDPlus hd hd₁) (HNPlus ν ν₁) = HDPlus (ds-lem-nehole hd ν) (ds-lem-nehole hd₁ ν₁)
  ds-lem-nehole (HDAsc hd) (HNAsc ν) = HDAsc (ds-lem-nehole hd ν)
  ds-lem-nehole HDVar ν = HDVar
  ds-lem-nehole (HDLam1 hd) (HNLam1 ν) = HDLam1 (ds-lem-nehole hd ν)
  ds-lem-nehole (HDLam2 hd) (HNLam2 ν) = HDLam2 (ds-lem-nehole hd ν)
  ds-lem-nehole (HDAp hd hd₁) (HNAp ν ν₁) = HDAp (ds-lem-nehole hd ν) (ds-lem-nehole hd₁ ν₁)
  ds-lem-nehole (HDInl hd) (HNInl ν) = HDInl (ds-lem-nehole hd ν)
  ds-lem-nehole (HDInr hd) (HNInr ν) = HDInr (ds-lem-nehole hd ν)
  ds-lem-nehole (HDCase hd hd₁ hd₂) (HNCase ν ν₁ ν₂) = HDCase (ds-lem-nehole hd ν) (ds-lem-nehole hd₁ ν₁) (ds-lem-nehole hd₂ ν₂)
  ds-lem-nehole (HDPair hd hd₁) (HNPair ν ν₁) = HDPair (ds-lem-nehole hd ν) (ds-lem-nehole hd₁ ν₁)
  ds-lem-nehole (HDFst hd) (HNFst ν) = HDFst (ds-lem-nehole hd ν)
  ds-lem-nehole (HDSnd hd) (HNSnd ν) = HDSnd (ds-lem-nehole hd ν)
  ds-lem-nehole (HDHole x) (HNHole x₁) = HDHole (HNNEHole (flip x₁) x)
  ds-lem-nehole (HDNEHole x hd) (HNNEHole x₁ ν) = HDNEHole (HNNEHole (flip x₁) x) (ds-lem-nehole hd ν)

  -- holes-disjoint is symmetric
  disjoint-sym : ∀{e1 e2} → holes-disjoint e1 e2 → holes-disjoint e2 e1
  disjoint-sym HDNum = ds-lem-num
  disjoint-sym (HDPlus hd hd₁) = ds-lem-plus (disjoint-sym hd) (disjoint-sym hd₁)
  disjoint-sym (HDAsc hd) = ds-lem-asc (disjoint-sym hd)
  disjoint-sym HDVar = ds-lem-var
  disjoint-sym (HDLam1 hd) = ds-lem-lam1 (disjoint-sym hd)
  disjoint-sym (HDLam2 hd) = ds-lem-lam2 (disjoint-sym hd)
  disjoint-sym (HDAp hd hd₁) = ds-lem-ap (disjoint-sym hd) (disjoint-sym hd₁)
  disjoint-sym (HDInl hd) = ds-lem-inl (disjoint-sym hd)
  disjoint-sym (HDInr hd) = ds-lem-inr (disjoint-sym hd)
  disjoint-sym (HDCase hd hd₁ hd₂) = ds-lem-case (disjoint-sym hd) (disjoint-sym hd₁) (disjoint-sym hd₂)
  disjoint-sym (HDPair hd hd₁) = ds-lem-pair (disjoint-sym hd) (disjoint-sym hd₁)
  disjoint-sym (HDFst hd) = ds-lem-fst (disjoint-sym hd)
  disjoint-sym (HDSnd hd) = ds-lem-snd (disjoint-sym hd)
  disjoint-sym (HDHole x) = ds-lem-hole x
  disjoint-sym (HDNEHole x hd) = ds-lem-nehole (disjoint-sym hd) x

  -- note that this is false, so holes-disjoint isn't transitive
  -- disjoint-new : ∀{e1 e2 u} → holes-disjoint e1 e2 → hole-name-new e1 u → hole-name-new e2 u

  -- it's also not reflexive, because ⦇-⦈[ u ] isn't hole-disjoint with
  -- itself since refl : u == u; it's also not anti-reflexive, because the
  -- expression c *is* hole-disjoint with itself (albeit vacuously)


module Examples.DivergingContext where

open import Prelude

data TC : Set where
  tc-int : TC
  tc-bool : TC

_tc≟_ : (a b : TC) → Dec (a ≡ b)
tc-int tc≟ tc-int = yes refl
tc-int tc≟ tc-bool = no (λ ())
tc-bool tc≟ tc-int = no (λ ())
tc-bool tc≟ tc-bool = yes refl

open import Implicits.Oliveira.Types TC _tc≟_
open import Implicits.Oliveira.Terms TC _tc≟_
open import Implicits.Oliveira.Contexts TC _tc≟_
open import Implicits.Oliveira.WellTyped TC _tc≟_
open import Implicits.Oliveira.Substitutions TC _tc≟_
open import Implicits.Oliveira.Types.Unification TC _tc≟_
open import Implicits.Improved.Infinite.Resolution TC _tc≟_
open import Data.Maybe
open import Data.List
open import Coinduction
open import Data.List.Any.Membership using (map-mono)
open import Data.List.Any
open Membership-≡

Int : ∀ {ν} → Type ν
Int = simpl (tc tc-int)

module Ex₁ where

  r : Type zero
  r = (∀' (((simpl (x →' x)) ⇒ (simpl (x →' (simpl (x →' x))))) ⇒ x))
    where
      x = (simpl (tvar zero))

  Δ : ICtx zero
  Δ = r ∷ []

  -- Exploiting coinductive definition here to show that there is an infinite derivation
  -- of any type under the context Δ with the special characteristic that the context grows to
  -- infinity as well!
  --
  -- This may not be immediately obvious, but it can be seen by exploring the first argument of 'p'
  -- in the recursive calls, denoting the growing context.
  -- Also note that the element added to the context won't ever match the goal.
  -- This is not proven here, but an argument for it is made in the form of ¬r↓goal
  p : ∀ Δ' a → Δ ⊆ Δ' → Δ' ⊢ᵣ simpl a
  p Δ' a f = r-simp (f (here refl))
    (i-tabs
      (simpl a)
      (i-iabs (♯ r-iabs (p (new-r ∷ Δ') new-goal (λ x → there (f x)))) (i-simp a)))
    where
      new-r = simpl (simpl a →' simpl a)
      new-goal = (simpl a →' simpl (simpl a →' simpl a))

      ¬r↓goal : ¬ (new-r ∷ Δ') ⊢ new-r ↓ new-goal
      ¬r↓goal ()


module Tactic.Reflection.Meta where

open import Prelude
open import Builtin.Reflection
open import Tactic.Reflection.DeBruijn

ensureNoMetas : Term → TC ⊤

noMetaArg : Arg Term → TC ⊤
noMetaArg (arg _ v) = ensureNoMetas v

noMetaArgs : List (Arg Term) → TC ⊤
noMetaArgs [] = pure _
noMetaArgs (v ∷ vs) = noMetaArg v *> noMetaArgs vs

noMetaClause : Clause → TC ⊤
noMetaClause (clause ps t) = ensureNoMetas t
noMetaClause (absurd-clause ps) = pure _

noMetaClauses : List Clause → TC ⊤
noMetaClauses [] = pure _
noMetaClauses (c ∷ cs) = noMetaClause c *> noMetaClauses cs

noMetaAbs : Abs Term → TC ⊤
noMetaAbs (abs _ v) = ensureNoMetas v

noMetaSort : Sort → TC ⊤
noMetaSort (set t) = ensureNoMetas t
noMetaSort (lit n) = pure _
noMetaSort unknown = pure _

ensureNoMetas (var x args) = noMetaArgs args
ensureNoMetas (con c args) = noMetaArgs args
ensureNoMetas (def f args) = noMetaArgs args
ensureNoMetas (lam v (abs _ t)) = ensureNoMetas t
ensureNoMetas (pat-lam cs args) = noMetaClauses cs *> noMetaArgs args
ensureNoMetas (pi a b) = noMetaArg a *> noMetaAbs b
ensureNoMetas (agda-sort s) = noMetaSort s
ensureNoMetas (lit l) = pure _
ensureNoMetas (meta x x₁) = blockOnMeta x
ensureNoMetas unknown = pure _

normaliseNoMetas : Term → TC Term
normaliseNoMetas a = do a ← normalise a
                        stripBoundNames a <$ ensureNoMetas a


open import Data.List hiding ([_]) renaming (_∷_ to _∷ₗ_)
open import Data.Maybe
open import Data.Product

open import AEff
open import EffectAnnotations
open import Renamings
open import Substitutions
open import Types

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

module Preservation where

-- BINDING CONTEXTS

BCtx = List VType

-- WELL-TYPED EVALUATION CONTEXTS

data _⊢E[_]⦂_ (Γ : Ctx) : (Δ : BCtx) → CType → Set where

  [-]              : {C : CType} →
                     -------------
                     Γ ⊢E[ [] ]⦂ C

  let=_`in_        : {Δ : BCtx}
                     {X Y : VType}
                     {o : O}
                     {i : I} →
                     Γ ⊢E[ Δ ]⦂ X ! (o , i) →
                     Γ ∷ X ⊢M⦂ Y ! (o , i) →
                     ------------------------
                     Γ ⊢E[ Δ ]⦂ Y ! (o , i)

  ↑                : {Δ : BCtx}
                     {X : VType}
                     {o : O}
                     {i : I} →
                     (op : Σₛ) →
                     op ∈ₒ o →
                     Γ ⊢V⦂ ``(payload op) →
                     Γ ⊢E[ Δ ]⦂ X ! (o , i) →
                     ------------------------
                     Γ ⊢E[ Δ ]⦂ X ! (o , i)

  ↓                : {Δ : BCtx}
                     {X : VType}
                     {o : O}
                     {i : I}
                     (op : Σₛ) →
                     Γ ⊢V⦂ ``(payload op) →
                     Γ ⊢E[ Δ ]⦂ X ! (o , i) →
                     ---------------------------
                     Γ ⊢E[ Δ ]⦂ X ! op ↓ₑ (o , i)

  promise_∣_↦_`in_ : {Δ : BCtx}
                     {X Y : VType}
                     {o o' : O}
                     {i i' : I} →
                     (op : Σₛ) →
                     lkpᵢ op i ≡ just (o' , i') →
                     Γ ∷ ``(payload op) ⊢M⦂ ⟨ X ⟩ ! (o' , i') →
                     Γ ∷ ⟨ X ⟩ ⊢E[ Δ ]⦂ Y ! (o , i) →
                     ------------------------------------------
                     Γ ⊢E[ X ∷ₗ Δ ]⦂ Y ! (o , i)

  coerce           : {Δ : BCtx}
                     {X : VType}
                     {o o' : O}
                     {i i' : I} →
                     o ⊑ₒ o' →
                     i ⊑ᵢ i' →
                     Γ ⊢E[ Δ ]⦂ X ! (o , i) →
                     ------------------------
                     Γ ⊢E[ Δ ]⦂ X ! (o' , i')

-- MERGING AN ORDINARY CONTEXT AND A BINDING CONTEXT

infix 30 _⋈_

_⋈_ : Ctx → BCtx → Ctx
Γ ⋈ [] = Γ
Γ ⋈ (X ∷ₗ Δ) = (Γ ∷ ⟨ X ⟩) ⋈ Δ

-- FINDING THE TYPE OF THE HOLE OF A WELL-TYPED EVALUATION CONTEXT

hole-ty-e : {Γ : Ctx} {Δ : BCtx} {C : CType} → Γ ⊢E[ Δ ]⦂ C → CType
hole-ty-e {_} {_} {C} [-] =
  C
hole-ty-e (let= E `in M) =
  hole-ty-e E
hole-ty-e (↑ op p V E) =
  hole-ty-e E
hole-ty-e (↓ op V E) =
  hole-ty-e E
hole-ty-e (promise op ∣ p ↦ M `in E) =
  hole-ty-e E
hole-ty-e (coerce p q E) =
  hole-ty-e E

-- FILLING A WELL-TYPED EVALUATION CONTEXT

{- LEMMA 3.5 -}

infix 30 _[_]

_[_] : {Γ : Ctx} {Δ : BCtx} {C : CType} → (E : Γ ⊢E[ Δ ]⦂ C) → Γ ⋈ Δ ⊢M⦂ (hole-ty-e E) → Γ ⊢M⦂ C
[-] [ M ] =
  M
(let= E `in N) [ M ] =
  let= (E [ M ]) `in N
↑ op p V E [ M ] =
  ↑ op p V (E [ M ])
↓ op V E [ M ] =
  ↓ op V (E [ M ])
(promise op ∣ p ↦ N `in E) [ M ] =
  promise op ∣ p ↦ N `in (E [ M ])
coerce p q E [ M ] =
  coerce p q (E [ M ])

-- STRENGTHENING OF GROUND VALUES WRT BOUND PROMISES

strengthen-var : {Γ : Ctx} → (Δ : BCtx) → {A : BType} → `` A ∈ Γ ⋈ Δ → `` A ∈ Γ
strengthen-var [] x = x
strengthen-var (y ∷ₗ Δ) x with strengthen-var Δ x
... | Tl p = p

strengthen-val : {Γ : Ctx} {Δ : BCtx} {A : BType} → Γ ⋈ Δ ⊢V⦂ `` A → Γ ⊢V⦂ `` A
strengthen-val {_} {Δ} (` x) =
  ` strengthen-var Δ x
strengthen-val (``_ c) =
  ``_ c

strengthen-val-[] : {Γ : Ctx}
                    {A : BType} →
                    (V : Γ ⋈ [] ⊢V⦂ `` A) →
                    --------------------
                    strengthen-val {Δ = []} V ≡ V

strengthen-val-[] (` x) =
  refl
strengthen-val-[] (``_ c) =
  refl

-- SMALL-STEP OPERATIONAL SEMANTICS FOR WELL-TYPED COMPUTATIONS
-- (ADDITIONALLY SERVES AS THE PRESERVATION THEOREM)

{- THEOREM 3.6 -}

infix 10 _↝_

data _↝_ {Γ : Ctx} : {C : CType} → Γ ⊢M⦂ C → Γ ⊢M⦂ C → Set where

  -- COMPUTATIONAL RULES

  apply           : {X : VType}
                    {C : CType} →
                    (M : Γ ∷ X ⊢M⦂ C) →
                    (V : Γ ⊢V⦂ X) →
                    ----------------------
                    (ƛ M) · V
                    ↝
                    M [ id-subst [ V ]s ]m

  let-return      : {X Y : VType}
                    {o : O}
                    {i : I} →
                    (V : Γ ⊢V⦂ X) →
                    (N : Γ ∷ X ⊢M⦂ Y ! (o , i)) →
                    -----------------------------
                    let= (return V) `in N
                    ↝
                    N [ id-subst [ V ]s ]m

  let-↑           : {X Y : VType}
                    {o : O}
                    {i : I}
                    {op : Σₛ} →
                    (p : op ∈ₒ o) →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (M : Γ ⊢M⦂ X ! (o , i)) →
                    (N : Γ ∷ X ⊢M⦂ Y ! (o , i)) →
                    -----------------------------
                    let= (↑ op p V M) `in N
                    ↝
                    ↑ op p V (let= M `in N)

  let-promise     : {X Y Z : VType}
                    {o o' : O}
                    {i i' : I}
                    {op : Σₛ} →
                    (p : lkpᵢ op i ≡ just (o' , i')) →
                    (M₁ : Γ ∷ ``(payload op) ⊢M⦂ ⟨ X ⟩ ! (o' , i')) →
                    (M₂ : Γ ∷ ⟨ X ⟩ ⊢M⦂ Y ! (o , i)) →
                    (N : Γ ∷ Y ⊢M⦂ Z ! (o , i)) →
                    ---------------------------------------------------------------------------
                    let= (promise op ∣ p ↦ M₁ `in M₂) `in N
                    ↝
                    (promise op ∣ p ↦ M₁ `in (let= M₂ `in (M-rename (comp-ren exchange wk₁) N)))

  letrec-unfold   : {X : VType}
                    {C D : CType}
                    (M : Γ ∷ (X ⇒ C) ∷ X ⊢M⦂ C) →
                    (N : Γ ∷ (X ⇒ C) ⊢M⦂ D) →
                    ----------------------------------------
                    (letrec M `in N)
                    ↝
                    N [ id-subst [ ƛ (letrec M-rename wk₃ M `in M-rename exchange M) ]s ]m

  promise-↑       : {X Y : VType}
                    {o o' : O}
                    {i i' : I}
                    {op op' : Σₛ} →
                    (p : lkpᵢ op i ≡ just (o' , i')) →
                    (q : op' ∈ₒ o) →
                    (V : Γ ∷ ⟨ X ⟩ ⊢V⦂ ``(payload op')) →
                    (M : Γ ∷ ``(payload op) ⊢M⦂ ⟨ X ⟩ ! (o' , i')) →
                    (N : Γ ∷ ⟨ X ⟩ ⊢M⦂ Y ! (o , i)) →
                    --------------------------------------------
                    (promise op ∣ p ↦ M `in (↑ op' q V N))
                    ↝
                    ↑ op' q (strengthen-val {Δ = X ∷ₗ []} V) (promise op ∣ p ↦ M `in N)

  ↓-return        : {X : VType}
                    {o : O}
                    {i : I}
                    {op : Σₛ} →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (W : Γ ⊢V⦂ X) →
                    ----------------------------------------------------------------
                    ↓ {o = o} {i = i} op V (return W)
                    ↝
                    return {o = proj₁ (op ↓ₑ (o , i))} {i = proj₂ (op ↓ₑ (o , i))} W

  ↓-↑             : {X : VType}
                    {o : O}
                    {i : I}
                    {op : Σₛ}
                    {op' : Σₛ} →
                    (p : op' ∈ₒ o) →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (W : Γ ⊢V⦂ ``(payload op')) →
                    (M : Γ ⊢M⦂ X ! (o , i)) →
                    -------------------------------
                    ↓ op V (↑ op' p W M)
                    ↝
                    ↑ op' (↓ₑ-⊑ₒ op' p) W (↓ op V M)

  ↓-promise-op    : {X Y : VType}
                    {o o' : O}
                    {i i' : I}
                    {op : Σₛ} →
                    (p : lkpᵢ op i ≡ just (o' , i')) →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (M : Γ ∷ ``(payload op) ⊢M⦂ ⟨ X ⟩ ! (o' , i')) →
                    (N : Γ ∷ ⟨ X ⟩ ⊢M⦂ Y ! (o , i)) →
                    ---------------------------------------------------------------------------------------
                    ↓ op V (promise op ∣ p ↦ M `in N )
                    ↝
                    (let= (coerce (↓ₑ-⊑ₒ-o' {o} p) (↓ₑ-⊑ₒ-i' {o} p) (M [ id-subst [ V ]s ]m)) `in
                      ↓ op (V-rename wk₁ V) ((M-rename (comp-ren exchange wk₁) N) [ id-subst [ ` Hd ]s ]m))

  ↓-promise-op'   : {X Y : VType}
                    {o o' : O}
                    {i i' : I}
                    {op op' : Σₛ} →
                    (p : ¬ op ≡ op') →
                    (q : lkpᵢ op' i ≡ just (o' , i')) →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (M : Γ ∷ ``(payload op') ⊢M⦂ ⟨ X ⟩ ! (o' , i')) →
                    (N : Γ ∷ ⟨ X ⟩ ⊢M⦂ Y ! (o , i)) →
                    ------------------------------------------------------------------------------------------
                    ↓ op V (promise op' ∣ q ↦ M `in N )
                    ↝
                    promise_∣_↦_`in_ {o' = proj₁ (lkpᵢ-↓ₑ-neq {o = o} {i = i} p q)}
                                     {i' = proj₁ (proj₂ (lkpᵢ-↓ₑ-neq {o = o} {i = i} p q))}
                                     op'
                                     (proj₁ (proj₂ (proj₂ (lkpᵢ-↓ₑ-neq {o = o} {i = i} p q))))
                                     (coerce (proj₁ (proj₂ (proj₂ (proj₂ (lkpᵢ-↓ₑ-neq {o = o} {i = i} p q)))))
                                             (proj₂ (proj₂ (proj₂ (proj₂ (lkpᵢ-↓ₑ-neq {o = o} {i = i} p q)))))
                                             M)
                                     (↓ op (V-rename wk₁ V) N)

  await-promise   : {X : VType}
                    {C : CType} →
                    (V : Γ ⊢V⦂ X) →
                    (M : Γ ∷ X ⊢M⦂ C) →
                    --------------------
                    await ⟨ V ⟩ until M
                    ↝
                    M [ id-subst [ V ]s ]m

  -- EVALUATION CONTEXT RULE

  context         : {Δ : BCtx}
                    {C : CType} →
                    (E : Γ ⊢E[ Δ ]⦂ C) →
                    {M N : Γ ⋈ Δ ⊢M⦂ (hole-ty-e E)} →
                    M ↝ N →
                    -------------------------------
                    E [ M ] ↝ E [ N ]

  -- COERCION RULES
  -- (THE RESULT OF WORKING WITH WELL-TYPED SYNTAX AND MAKING SUBSUMPTION INTO AN EXPLICIT COERCION)

  coerce-return   : {X : VType}
                    {o o' : O}
                    {i i' : I}
                    {p : o ⊑ₒ o'}
                    {q : i ⊑ᵢ i'} →
                    (V : Γ ⊢V⦂ X) →
                    --------------------------------
                    coerce p q (return V) ↝ return V

  coerce-↑        : {X : VType}
                    {o o' : O}
                    {i i' : I}
                    {p : o ⊑ₒ o'}
                    {q : i ⊑ᵢ i'}
                    {op : Σₛ} →
                    (r : op ∈ₒ o) →
                    (V : Γ ⊢V⦂ ``(payload op)) →
                    (M : Γ ⊢M⦂ X ! (o , i)) →
                    -------------------------------
                    coerce p q (↑ op r V M)
                    ↝
                    ↑ op (p op r) V (coerce p q M)

  coerce-promise  : {X Y : VType}
                    {o o' o'' : O}
                    {i i' i'' : I}
                    {p : o ⊑ₒ o'}
                    {q : i ⊑ᵢ i'}
                    {op : Σₛ} →
                    (r : lkpᵢ op i ≡ just (o'' , i''))
                    (M : Γ ∷ ``(payload op) ⊢M⦂ ⟨ X ⟩ ! (o'' , i'')) →
                    (N : Γ ∷ ⟨ X ⟩ ⊢M⦂ Y ! (o , i)) →
                    ------------------------------------------------------------------
                    coerce p q (promise op ∣ r ↦ M `in N)
                    ↝
                    promise_∣_↦_`in_ {o' = lkpᵢ-nextₒ q r}
                                     {i' = lkpᵢ-nextᵢ q r}
                                     op
                                     (lkpᵢ-next-eq q r)
                                     (coerce (lkpᵢ-next-⊑ₒ q r) (lkpᵢ-next-⊑ᵢ q r) M)
                                     (coerce p q N)


module DeclSN where

open import Data.Sum
open import Library
open import Terms
open import Substitution
open import TermShape
open import SN
open import Reduction

-- SN as accessibility

data sn {Γ} {a} (t : Tm Γ a) : Set where
  acc : (∀ {t'} → t ⇒β t' → sn t') → sn t

sn⇒β :  ∀ {Γ} {a} {t t' : Tm Γ a} → sn t → t ⇒β t' → sn t'
sn⇒β (acc h) r = h r

varsn : ∀ {Γ} {a} (x : Var Γ a) → sn (var x)
varsn x = acc λ { (cong () _ _) }

abssn : ∀ {Γ} {a b} {t : Tm (a ∷ Γ) b} → sn t → sn (abs t)
abssn (acc f) = acc (λ { {._} (cong abs abs x)  → abssn (f x) })

subsn : ∀ {Γ Δ} {a b} {f : Tm Γ a -> Tm Δ b} →
        (g : ∀ {t t' : Tm Γ a} → t ⇒β t' → f t ⇒β f t') →
        ∀ {t} → sn (f t) → sn t
subsn g (acc ft) = acc λ t⇒ → subsn g (ft (g t⇒))

-- Goal here: prove that sne is closed under application.

appsn : ∀ {Γ a b} {t : Tm Γ (a →̂ b)} {u} → sn t → sn u → SNe t →
        ∀ {r : Tm Γ b} → app t u ⇒β r → sn r
appsn (acc 𝒕) 𝒖       𝒏           (cong (appl u) (appl .u) t⇒) = acc (appsn (𝒕 t⇒) 𝒖      (mapβSNe t⇒ 𝒏 ))
appsn 𝒕       (acc u) 𝒏           (cong (appr t) (appr .t) t⇒) = acc (appsn 𝒕      (u t⇒) 𝒏)
appsn 𝒕       u       (elim 𝒏 ()) β

elimsn : ∀ {Γ a b}{E : ECxt Γ a b}{t : Tm Γ a}{Et : Tm Γ b} → sn t → PCxt sn Et E t → SNe t →
         ∀ {Et' : Tm Γ b} → Et ⇒β Et' → sn Et'
elimsn 𝒕 (appl 𝒖) 𝒏 t⇒ = appsn 𝒕 𝒖 𝒏 t⇒

substβsn : ∀ {i m vt a Γ} {Δ} {σ ρ : RenSub {m} vt Γ Δ} → (∀ {b} (x : Var Γ b) → vt2tm _ (σ x) ⇒β* vt2tm _ (ρ x))
             → (t : Tm Γ a) → SN {i} (subst σ t) → SN {i} (subst ρ t)
substβsn f t =  mapβ*SN (subst⇒β* f t)

antiSubst : ∀ {Γ a b} {t : Tm (a ∷ Γ) b}{u : Tm Γ a} → sn (subst (sgs u) t) → sn t
antiSubst {t = t} = subsn (λ x → subst⇒β (sgs _) x)

_[_]⇒β : ∀ {Γ} {a b} (E : ECxt Γ a b) {t₁ t₂ : Tm Γ a} →  t₁ ⇒β t₂ → E [ t₁ ] ⇒β E [ t₂ ]
appl u [ t⇒ ]⇒β = cong (appl u) (appl u) t⇒

_[_]⇒β* : ∀ {Γ} {a b} (E : ECxt* Γ a b) {t₁ t₂ : Tm Γ a} → t₁ ⇒β t₂ → E [ t₁ ]* ⇒β E [ t₂ ]*
[]       [ t⇒ ]⇒β* = t⇒
(E ∷ Es) [ t⇒ ]⇒β* = Es [ E [ t⇒ ]⇒β ]⇒β*

cong*2 : ∀ {Γ a b t t'}(E : ECxt* Γ a b)
          → (t⇒ : t ⇒β t')
          → E [ t ]* ⇒β E [ t' ]*
cong*2 E t⇒ =  E [ t⇒ ]⇒β*

subexpsn : ∀ {Γ a b} (E : ECxt* Γ a b) {t : Tm Γ a} → sn (E [ t ]*) -> sn t
subexpsn E = subsn (cong*3 E)

data _Redex {Γ} : ∀ {a} → Tm Γ a → Set where

  β     : ∀ {a b}{t : Tm (a ∷ Γ) b}{u}
          → (app (abs t) u) Redex

mkHole2 : ∀ {Γ} {a b} (E : ECxt Γ a b) {t : Tm Γ a} → βEhole (E [ t ]) (EC→βEC E) t
mkHole2 (appl u) = appl u

mkHole3 : ∀ {Γ} {a b c} (E : ECxt Γ a b) {Es : ECxt* Γ _ _} {t : Tm Γ c} → βEhole ((Es ∷r E) [ t ]*) (EC→βEC E) (Es [ t ]*)
mkHole3 E {Es} {t} rewrite ≡.sym (lemma {t = t} Es {E = E}) = mkHole2 E {Es [ t ]*}

≡subst⇒β : ∀ {a Γ} {t t1 t' t'1 : Tm Γ a} → t ≡ t1 → t' ≡ t'1 → t ⇒β t' → t1 ⇒β t'1
≡subst⇒β ≡.refl ≡.refl x = x

split : ∀ {Γ} {a b} (E : ECxt* Γ a b) {t₁ : Tm Γ a}{t₂ Et₁ : Tm Γ b} →
         Ehole* Et₁ E t₁ → t₁ Redex →
         Et₁ ⇒β t₂ → (∃ λ t₃ → Ehole* t₂ E t₃ × t₁ ⇒β t₃)
         ⊎ (∃ λ E₁ → Ehole* t₂ E₁ t₁ × (∀ t → E [ t ]* ⇒β E₁ [ t ]*))
split ._ [] r t⇒ = inj₁ (_ , [] , t⇒)
split .(appl u ∷ []) (appl u ∷ []) () β
split ._ (appl u ∷ (() ∷ eq)) r β
split ._ (appl u ∷ eq) r (cong (appl .u) (appl .u) t⇒) with split _ eq r t⇒
split ._ (appl u ∷ eq) r (cong (appl .u) (appl .u) t⇒) | inj₁ (x , eq0 , t⇒') = inj₁ (_ , ((appl u) ∷ eq0) , t⇒')
split ._ (_∷_ {Es = Es} (appl u) eq) r (cong (appl .u) (appl .u) t⇒) | inj₂ (Es' , eq0 , f) = inj₂ (_ , ((appl u ∷ eq0) ,
                                                        (λ t → cong (mkHole3 (appl u) {Es}) (mkHole3 (appl u) {Es'}) (f t))))
split ._ (_∷_ {Es = Es} (appl t) eq) r (cong (appr Est) (appr .Est) t⇒) = inj₂ (_ , ((appl _ ∷ eq) ,
      (λ t₁ → ≡subst⇒β (lemma Es {E = appl t}) (lemma Es {E = appl _}) (_⇒β_.cong {E = (appr (Es [ t₁ ]*))} (βEhole.appr (Es [ t₁ ]*)) (appr (Es [ t₁ ]*)) t⇒))))

mutual

-- it seems possible to use sn (Es [ subst (sgs u) t ]*) instead of SN {i} (Es [ subst (sgs u) t ]*) ? - bp
  appsn₃ : ∀ {i a b c Γ} {u : Tm Γ a} {t : Tm (a ∷ Γ) b}{Es : ECxt* Γ b c}{x}
    → sn (Es [ x ]*)
    → sn t
    → SN {i} (Es [ subst (sgs u) t ]*)  -- TODO: use sn here?
    → sn u
    → sn (Es [ app (abs t) u ]*)
  appsn₃ {Es = Es} x t t[u] u = acc (λ t⇒ → help {Es = Es} x t t[u] u (mkEhole* Es) t⇒)
    where
    help : ∀ {i a b c Γ} {u : Tm Γ a} {t : Tm (a ∷ Γ) b} {t' : Tm Γ c} {x}  {z}{Es : ECxt* Γ b c} → sn (Es [ x ]*) → sn t →
         SN {i} (Es [ subst (u ∷s var) t ]*) →
         sn u → Ehole* z Es (app (abs t) u) → z ⇒β t' → sn t'

    help {Es = Es} x t t[u]∈sn u∈sn eq t⇒ with split Es eq β t⇒
    help x t₂ t[u]∈sn u∈sn eq t⇒ | inj₁ (._ , a₁ , β) rewrite hole*→≡ a₁ = fromSN t[u]∈sn
    help {Es = Es} x (acc t₃) t[u]∈sn u∈sn eq t⇒ | inj₁ (._ , a₁ , cong (appl u₁) (appl .u₁) (cong abs abs b₁)) rewrite hole*→≡ a₁
      = appsn₃ {Es = Es} x (t₃ b₁) (mapβSN (cong*3 Es (subst⇒β (sgs u₁) b₁)) t[u]∈sn) u∈sn
    help {t = t} {Es = Es} x t₃ t[u]∈sn (acc u∈sn) eq t⇒ | inj₁ (._ , a₁ , cong (appr ._) (appr ._) b₁) rewrite hole*→≡ a₁
      = appsn₃ {Es = Es} x t₃ (mapβ*SN (cong*4 Es
                                          (subst⇒β* (λ { {._} zero → b₁ ∷ [] ; (suc n) → [] }) t)) t[u]∈sn) (u∈sn b₁)
    help {x = x} (acc f) t₂ t[u]∈sn u∈sn eq t⇒ | inj₂ (Es' , a , g) rewrite hole*→≡ a
         = appsn₃ {Es = Es'} (f (g x)) t₂ (mapβSN (g _) t[u]∈sn) u∈sn

  helperCxt : ∀ {i j Γ a b} {t th to : Tm Γ a}  → (Es : ECxt* Γ a b)
              →       t ⟨ i ⟩⇒ th → SN {j} (Es [ th ]*) → sn (Es [ th ]*) -> t ⇒β to → sn (Es [ to ]*)

  helperCxt E (β 𝒖)    𝒕h 𝑡h β    = 𝑡h

  helperCxt E (β         𝒖) 𝒕h 𝑡h (cong (appl  u) (appl .u) (cong abs abs t⇒))
    = appsn₃ {Es = E} 𝑡h (sn⇒β (antiSubst (subexpsn E 𝑡h)) t⇒)
              (mapβSN (cong*3 E (subst⇒β (sgs u) t⇒)) 𝒕h)
              (fromSN 𝒖)
  helperCxt E (β {t = t} 𝒖) 𝒕h 𝑡h (cong (appr ._) (appr ._) t⇒)
    = appsn₃ {Es = E} 𝑡h (antiSubst (subexpsn E 𝑡h))
              (mapβ*SN (cong*4 E (subst⇒β* (λ { zero → t⇒ ∷ [] ; (suc _) → [] }) t)) 𝒕h)
              (sn⇒β (fromSN 𝒖) t⇒)

  helperCxt E (cong (appl u) (appl .u) (cong () 𝑬𝒕' th⇒)) 𝒕h 𝑡h β

  helperCxt E (cong (appl u) (appl .u) th⇒) 𝒕h 𝑡h       (cong (appl .u) (appl .u) t⇒) = helperCxt (appl u ∷ E) th⇒ 𝒕h 𝑡h t⇒

  helperCxt E (cong (appl u) (appl .u) th⇒) 𝒕h (acc 𝑡h) (cong (appr t)  (appr .t) t⇒)
            = acc (helperCxt [] (E [ cong (appl _) (appl _) th⇒ ]⇒*) (mapβSN t⇒' 𝒕h) (𝑡h t⇒'))
               where
               t⇒' =  E [ cong (appr _) (appr _) t⇒  ]⇒β*

  fromSN : ∀ {i} {Γ} {a} {t : Tm Γ a} → SN {i} t → sn t
  fromSN (ne 𝒏)       = fromSNe 𝒏
  fromSN (abs t₁)     = abssn (fromSN t₁)
  fromSN (exp t⇒ t₁)  = acc (helperCxt [] t⇒ t₁ (fromSN t₁))

  fromSNe : ∀ {i Γ a} {t : Tm Γ a} → SNe {i} t → sn t
  fromSNe (elim 𝒏 E) = acc (elimsn (fromSNe 𝒏) (mapPCxt fromSN E) 𝒏)
  fromSNe (var x)    = varsn x


------------------------------------------------------------------------
-- Polymorphic and iso-recursive types
------------------------------------------------------------------------

module SystemF.Type where

open import Data.Fin using (Fin; zero; suc)
open import Data.Fin.Substitution
open import Data.Fin.Substitution.Lemmas
open import Data.Nat using (ℕ; _+_)
open import Data.Star using (Star; ε; _◅_)
open import Data.Vec using (Vec; []; _∷_; lookup; map)
open import Relation.Binary.PropositionalEquality as PropEq
  using (refl; _≡_; cong; cong₂; sym)
open PropEq.≡-Reasoning

------------------------------------------------------------------------
-- Polymorphic and iso-recursive types

infixr 7 _→'_

-- Types with up to n free type variables
data Type (n : ℕ) : Set where
  var  : Fin n           → Type n   -- type variable
  _→'_ : Type n → Type n → Type n   -- arrow/function type
  ∀'   : Type (1 + n)    → Type n   -- universal type
  μ    : Type (1 + n)    → Type n   -- recursive type

------------------------------------------------------------------------
-- Substitutions in types

module TypeSubst where
  module TypeApp {T : ℕ → Set} (l : Lift T Type) where
    open Lift l hiding (var)

    infixl 8 _/_

    -- Apply a substitution to a type
    _/_ : ∀ {m n} → Type m → Sub T m n → Type n
    var x    / σ = lift (lookup σ x)
    (a →' b) / σ = (a / σ) →' (b / σ)
    ∀' a     / σ = ∀' (a / σ ↑)
    μ a      / σ = μ (a / σ ↑)

    open Application (record { _/_ = _/_ }) using (_/✶_)

    -- Helper lemmas relating application of simple substitutions (_/_)
    -- to application of sequences of substititions (_/✶_).  These are
    -- used to derive other (more general) lemmas below.

    →'-/✶-↑✶ : ∀ k {m n a b} (ρs : Subs T m n) →
               (a →' b) /✶ ρs ↑✶ k ≡ (a /✶ ρs ↑✶ k) →' (b /✶ ρs ↑✶ k)
    →'-/✶-↑✶ k ε        = refl
    →'-/✶-↑✶ k (ρ ◅ ρs) = cong₂ _/_ (→'-/✶-↑✶ k ρs) refl

    ∀'-/✶-↑✶ : ∀ k {m n a} (ρs : Subs T m n) →
               (∀' a) /✶ ρs ↑✶ k ≡ ∀' (a /✶ ρs ↑✶ (1 + k))
    ∀'-/✶-↑✶ k ε        = refl
    ∀'-/✶-↑✶ k (ρ ◅ ρs) = cong₂ _/_ (∀'-/✶-↑✶ k ρs) refl

    μT-/✶-↑✶ : ∀ k {m n a} (ρs : Subs T m n) →
               (μ a) /✶ ρs ↑✶ k ≡ μ (a /✶ ρs ↑✶ (1 + k))
    μT-/✶-↑✶ k ε        = refl
    μT-/✶-↑✶ k (ρ ◅ ρs) = cong₂ _/_ (μT-/✶-↑✶ k ρs) refl

  -- Defining the abstract members var and _/_ in
  -- Data.Fin.Substitution.TermSubst for T = Type gives us access to a
  -- number of (generic) substitution functions out-of-the-box.
  typeSubst : TermSubst Type
  typeSubst = record { var = var; app = TypeApp._/_ }

  open TermSubst typeSubst public hiding (var)

  weaken↑ : ∀ {n} → Type (1 + n) → Type (2 + n)
  weaken↑ a = a / wk ↑

  infix 8 _[/_]

  -- Shorthand for single-variable type substitutions
  _[/_] : ∀ {n} → Type (1 + n) → Type n → Type n
  a [/ b ] = a / sub b

-- Substitution lemmas.
module TypeLemmas where

  -- FIXME: The following lemmas are generic and should go somewhere
  -- else.
  module AdditionalLemmas {T} (lemmas : TermLemmas T) where
    open TermLemmas lemmas

    -- Weakening commutes with single-variable substitution
    weaken-sub : ∀ {n} (a : T (1 + n)) (b : T n) →
                 weaken (a / sub b) ≡ a / wk ↑ / sub (weaken b)
    weaken-sub a b = begin
      weaken (a / sub b)        ≡⟨ sym (/-wk′ (a / sub b)) ⟩
      a / sub b / wk            ≡⟨ sub-commutes a ⟩
      a / wk ↑ / sub (b / wk)   ≡⟨ cong (λ c → a / wk ↑ / sub c) (/-wk′ b) ⟩
      a / wk ↑ / sub (weaken b) ∎
      where /-wk′ : ∀ {n} (a : T n) → a / wk ≡ weaken a
            /-wk′ a = /-wk {t = a}

    -- Weakening commutes with reverse composition of substitutions.
    map-weaken-⊙ : ∀ {m n k} (σ₁ : Sub T m n) (σ₂ : Sub T n k) →
                   map weaken (σ₁ ⊙ σ₂) ≡ (map weaken σ₁) ⊙ (σ₂ ↑)
    map-weaken-⊙ σ₁ σ₂ = begin
      map weaken (σ₁ ⊙ σ₂)     ≡⟨ map-weaken ⟩
      (σ₁ ⊙ σ₂) ⊙ wk           ≡⟨ sym ⊙-assoc ⟩
      σ₁ ⊙ (σ₂ ⊙ wk)           ≡⟨ cong (λ σ₂ → σ₁ ⊙ σ₂) ⊙-wk ⟩
      σ₁ ⊙ (wk ⊙ (σ₂ ↑))       ≡⟨ ⊙-assoc ⟩
      (σ₁ ⊙ wk) ⊙ (σ₂ ↑)       ≡⟨ cong (λ σ₁ → σ₁ ⊙ (σ₂ ↑)) (sym map-weaken) ⟩
      (map weaken σ₁) ⊙ (σ₂ ↑) ∎

  -- Giving concrete definitions (i.e. proofs) for the abstract members
  -- (i.e. lemmas) in Data.Fin.Substitution.Lemmas.TermLemmas for T =
  -- Type gives us access to a number of (generic) substitutions lemmas
  -- out-of-the-box.
  typeLemmas : TermLemmas Type
  typeLemmas = record
    { termSubst = TypeSubst.typeSubst
    ; app-var   = refl
    ; /✶-↑✶     = Lemma./✶-↑✶
    }
    where
    module Lemma {T₁ T₂} {lift₁ : Lift T₁ Type} {lift₂ : Lift T₂ Type} where

      open TypeSubst
      open Lifted lift₁ using () renaming (_↑✶_ to _↑✶₁_; _/✶_ to _/✶₁_)
      open Lifted lift₂ using () renaming (_↑✶_ to _↑✶₂_; _/✶_ to _/✶₂_)

      /✶-↑✶ : ∀ {m n} (ρs₁ : Subs T₁ m n) (ρs₂ : Subs T₂ m n) →
              (∀ k x → var x /✶₁ ρs₁ ↑✶₁ k ≡ var x /✶₂ ρs₂ ↑✶₂ k) →
               ∀ k t → t     /✶₁ ρs₁ ↑✶₁ k ≡ t     /✶₂ ρs₂ ↑✶₂ k
      /✶-↑✶ ρs₁ ρs₂ hyp k (var x) = hyp k x
      /✶-↑✶ ρs₁ ρs₂ hyp k (a →' b) = begin
          (a →' b) /✶₁ ρs₁ ↑✶₁ k
        ≡⟨ TypeApp.→'-/✶-↑✶ _ k ρs₁ ⟩
          (a /✶₁ ρs₁ ↑✶₁ k) →' (b /✶₁ ρs₁ ↑✶₁ k)
        ≡⟨ cong₂ _→'_ (/✶-↑✶ ρs₁ ρs₂ hyp k a) (/✶-↑✶ ρs₁ ρs₂ hyp k b) ⟩
          (a /✶₂ ρs₂ ↑✶₂ k) →' (b /✶₂ ρs₂ ↑✶₂ k)
        ≡⟨ sym (TypeApp.→'-/✶-↑✶ _ k ρs₂) ⟩
          (a →' b) /✶₂ ρs₂ ↑✶₂ k
        ∎
      /✶-↑✶ ρs₁ ρs₂ hyp k (∀' a) = begin
        (∀' a) /✶₁ ρs₁ ↑✶₁ k        ≡⟨ TypeApp.∀'-/✶-↑✶ _ k ρs₁ ⟩
        ∀' (a /✶₁ ρs₁ ↑✶₁ (1 + k))  ≡⟨ cong ∀' (/✶-↑✶ ρs₁ ρs₂ hyp (1 + k) a) ⟩
        ∀' (a /✶₂ ρs₂ ↑✶₂ (1 + k))  ≡⟨ sym (TypeApp.∀'-/✶-↑✶ _ k ρs₂) ⟩
        (∀' a) /✶₂ ρs₂ ↑✶₂ k        ∎
      /✶-↑✶ ρs₁ ρs₂ hyp k (μ a) = begin
        (μ a) /✶₁ ρs₁ ↑✶₁ k         ≡⟨ TypeApp.μT-/✶-↑✶ _ k ρs₁ ⟩
        μ (a /✶₁ ρs₁ ↑✶₁ (1 + k))   ≡⟨ cong μ (/✶-↑✶ ρs₁ ρs₂ hyp (1 + k) a) ⟩
        μ (a /✶₂ ρs₂ ↑✶₂ (1 + k))   ≡⟨ sym (TypeApp.μT-/✶-↑✶ _ k ρs₂) ⟩
        (μ a) /✶₂ ρs₂ ↑✶₂ k         ∎

  open TermLemmas typeLemmas public hiding (var)
  open TypeSubst public using (_[/_]; _/Var_; weaken↑; module Lifted)

  -- The above lemma /✶-↑✶ specialized to single substitutions
  /-↑⋆ : ∀ {T₁ T₂} {lift₁ : Lift T₁ Type} {lift₂ : Lift T₂ Type} →
         let open Lifted lift₁ using () renaming (_↑⋆_ to _↑⋆₁_; _/_ to _/₁_)
             open Lifted lift₂ using () renaming (_↑⋆_ to _↑⋆₂_; _/_ to _/₂_)
         in
         ∀ {n k} (ρ₁ : Sub T₁ n k) (ρ₂ : Sub T₂ n k) →
         (∀ i x → var x /₁ ρ₁ ↑⋆₁ i ≡ var x /₂ ρ₂ ↑⋆₂ i) →
          ∀ i a → a /₁ ρ₁ ↑⋆₁ i ≡ a /₂ ρ₂ ↑⋆₂ i
  /-↑⋆ ρ₁ ρ₂ hyp i a = /✶-↑✶ (ρ₁ ◅ ε) (ρ₂ ◅ ε) hyp i a

  open AdditionalLemmas typeLemmas public

------------------------------------------------------------------------
-- Encoding/translation of additional type operators

module TypeOperators where
  open TypeLemmas hiding (id)

  -- Type of the polymorphic identity
  id : ∀ {n} → Type n
  id = ∀' ((var zero) →' (var zero))

  -- Bottom/initial/zero type
  ⊥ : ∀ {n} → Type n
  ⊥ = ∀' (var zero)

  -- Top/terminal/unit type
  ⊤ : ∀ {n} → Type n
  ⊤ = id

  -- Existential type
  ∃ : ∀ {n} → Type (1 + n) → Type n
  ∃ a = ∀' (∀' (weaken↑ a →' var (suc zero)) →' var zero)

  infixr 7 _→ⁿ_

  -- n-ary function type
  _→ⁿ_ : ∀ {n k} → Vec (Type n) k → Type n → Type n
  []       →ⁿ z = z
  (a ∷ as) →ⁿ z = as →ⁿ a →' z

  -- Record/finite tuple
  rec : ∀ {n k} → Vec (Type n) k → Type n
  rec []       = ⊤
  rec (a ∷ as) = ∀' ((map weaken (a ∷ as) →ⁿ var zero) →' var zero)

------------------------------------------------------------------------
-- Lemmas about encoded type operators

module TypeOperatorLemmas where
  open TypeOperators
  open TypeLemmas hiding (_/_; id)

  module TypeOperatorAppLemmas {T} (l : Lift T Type) where
    open TypeSubst.TypeApp l

    -- Type substitution commutes with the translation of of n-ary
    -- function types
    /-→ⁿ : ∀ {m n k} (as : Vec (Type m) k) (b : Type m) (σ : Sub T m n) →
           (as →ⁿ b) / σ ≡ map (λ a → a / σ) as →ⁿ b / σ
    /-→ⁿ []       _ _ = refl
    /-→ⁿ (a ∷ as) b σ = /-→ⁿ as (a →' b) σ

    -- FIXME: Write similar lemmas for the remaining type operators

  private
    module WeakeningLemmas where
      open TypeOperatorAppLemmas TypeSubst.varLift

      -- Weakening commutes with the translation of of n-ary function
      -- types
      weaken-→ⁿ : ∀ {n k} (as : Vec (Type n) k) (b : Type n) →
                  weaken (as →ⁿ b) ≡ map weaken as →ⁿ weaken b
      weaken-→ⁿ as b = /-→ⁿ as b VarSubst.wk

      -- FIXME: Write similar lemmas for the remaining type operators

  open TypeOperatorAppLemmas TypeSubst.termLift public
  open WeakeningLemmas public


open import Relation.Binary
  using (Decidable)
open import Relation.Binary.PropositionalEquality
  using (_≡_ ; _≢_ ; refl ; subst ; subst₂)
open import Data.Nat as Nat
  using (ℕ ; _≤′_ ; _+_)
open import Relation.Binary.PropositionalEquality using (sym)

import Relation.Unary as U

open import Function
  using (_∘_)

open import Relation.Nullary

module Data.FiniteMap
  {Key : Set}
  (inject : Key → ℕ)
  (surject : ℕ → Key)
  (inject-injective : ∀ {k1 k2} → inject k1 ≡ inject k2 → k1 ≡ k2)
  (surject-inject : ∀ {k} → inject (surject k) ≡ k)
  where

open import utility

open import Data.Maybe using (just)
open import Data.Empty
  using (⊥-elim)
open import Data.List
  using (List ; _++_ ; [_] ; [] ; _∷_)
open import Data.List.Any
  using (any ; here ; there ; Any)
open import Data.Nat
  using (ℕ ; zero ; suc)
open import Data.Sum
  using (inj₁ ; inj₂ ; _⊎_)
open import Data.Product
  using (Σ ; Σ-syntax ; ∃ ; proj₁ ; proj₂ ; _×_ ; _,_ ; ∃-syntax ; uncurry ; map)
open import Function
  using (id ; _$_)
open import Relation.Nullary
  using (yes ; no)
open import Data.OrderedListMap(Key)(inject) as OMap hiding (insert-mono ; U-mono)
open import Data.Bool using (Bool ; true ; false)

private
  nbiject : ∀ {k1 k2} → k1 ≢ k2 → inject k1 ≢ inject k2
  nbiject = (_∘ inject-injective)

Map : Set → Set
Map = LMap

keys : ∀{Value} → Map Value → List ℕ
keys{Value} l = (Dom' Value l)

keys+∈ : ∀{Value} → (l : Map Value) → List (∃[ n ] (n ∈ (Dom' Value l)))
keys+∈{Value} l = (Dom'+∈ Value l)

inj= : Key → ℕ → Set
inj= = _≡_ ∘ inject

∈Dom : ∀{Value} → Key → Map Value → Set
∈Dom{Value} k m = (inject k) ∈ (Dom' Value m)

in-inject : ∀ {Value}{x : ℕ} → (l : Map Value) → x ∈ Dom' Value l → ∈Dom (surject x) l
in-inject{x = x} l ∈ rewrite surject-inject{x} = ∈

key-map : ∀{Value}{a}{L : Set a} → (l : Map Value) → (f : (k : Key) → (∈Dom k l) → L) → List L
key-map{L = L} l f
  = Data.List.map ((uncurry f) ∘ (map surject (in-inject l)))
                   (keys+∈ l)

∈Check : ∀{Value}
         → (k : Key)
         → (m : Map Value)
         → Relation.Nullary.Dec (∈Dom k m)
∈Check{Value} k m = any (Nat._≟_ (inject k)) (Dom' Value m)

lookup : ∀{Value k} → (l : Map Value) → (∈Dom k l) → Value
lookup{Value}{k = k} m k∈ = deref Value (inject k) m k∈

update : ∀{Value} → (l : Map Value) → Key → Value → Map Value
update{Value} l k v = m-insert Value (just v) (inject k) l

-- Union map. For repeated keys, the values from the second map is preferred.
union : ∀{Value} → Map Value → Map Value → Map Value
union{Value} m1 m2 = (_U_ Value m1 m2)

empty : ∀{Value} → Map Value
empty = []

[_↦_] : ∀{Value} → (k : Key) → (v : Value) → Map  Value
[ k ↦ v ] = update empty k v

[_↦_,_↦_] : ∀{Value} →
              (k₁ : Key) → (v₁ : Value) →
              (k₂ : Key) → (v₂ : Value) →
              Map Value
[ k₁ ↦ v₁ , k₂ ↦ v₂ ] = update [ k₁ ↦ v₁ ] k₂ v₂

keys-1map : ∀{Value} → (k : Key) → (v : Value) → keys [ k ↦ v ] ≡ inject k ∷ []
keys-1map {Value} k v = Dom'-1map Value (inject k) v

keys-2map : ∀{Value} → (k₁ : Key) → (v₁ : Value) → (k₂ : Key) → (v₂ : Value) →
  (keys [ k₁ ↦ v₁ , k₂ ↦ v₂ ] ≡ inject k₁ ∷ [] × k₁ ≡ k₂) ⊎
  (keys [ k₁ ↦ v₁ , k₂ ↦ v₂ ] ≡ inject k₁ ∷ inject k₂ ∷ []) ⊎
  (keys [ k₁ ↦ v₁ , k₂ ↦ v₂ ] ≡ inject k₂ ∷ inject k₁ ∷ [])
keys-2map {Value} k₁ v₁ k₂ v₂ =
  Data.Sum.map (Data.Product.map id inject-injective) id
    (Dom'-2map Value (inject k₁) v₁ (inject k₂) v₂)

keys-1map-distinct : ∀{Value} → (k1 k2 : Key) → (v1 v2 : Value) → ¬ (k1 ≡ k2) → utility.distinct' (keys [ k1 ↦ v1 ]) (keys [ k2 ↦ v2 ])
keys-1map-distinct k1 k2 v1 v2 neg z k1∈ k2∈ rewrite (keys-1map k1 v1) | (keys-1map k2 v2) with k1∈ | k2∈
... | here refl | here px = (nbiject neg) px
... | there () | _
... | here px | there ()

keys-1map∈ : ∀{Value} → (k1 : Key) → (v1 : Value) → (inject k1) ∈ keys [ k1 ↦ v1 ]
keys-1map∈ k v rewrite keys-1map k v = here refl

2map-eq-one : ∀{V} k1 k2 v k1∈ →  (lookup{V}{k1} ([ k1 ↦ v , k2 ↦ v ]) k1∈) ≡ v
2map-eq-one {V} k1 k2 = insert-two-eq-one V (inject k1) (inject k2)

2map-eq-two : ∀{V} k1 k2 v k2∈ →  (lookup{V}{k2} ([ k1 ↦ v , k2 ↦ v ]) k2∈) ≡ v
2map-eq-two {V} k1 k2 = insert-two-eq-two V (inject k1) (inject k2)

-- theorems

update-in-keys : ∀{V} m k v →  ∈Dom k (update{V} m k v)
update-in-keys {V} m k = insert-in-domain V m (inject k)

lookup-∈-irr : ∀{Value k m} → (∈1 : ∈Dom k m) → (∈2 : ∈Dom k m) → lookup{Value}{k} m ∈1 ≡ lookup{Value}{k} m ∈2
lookup-∈-irr{Value} ∈1 ∈2 = deref-∈-irr Value ∈1 ∈2

insert-mono : ∀{Value k m k' v} → ∈Dom k m → ∈Dom k (update{Value} m k' v)
insert-mono{Value}{k}{m}{k'}{v} i = (OMap.insert-mono Value {m}{inject k}{inject k'}{v} i)

U-mono : ∀{Value m m' k} → ∈Dom k m → ∈Dom k (union{Value} m m')
U-mono{V}{m}{m'}{k} i =  OMap.U-mono V {m}{m'}{inject k} i

union-comm : ∀ {Value} → (m1 : Map Value) → (m2 : Map Value) → distinct' (keys m1) (keys m2) →  union m1 m2 ≡ union m2 m1
union-comm {Value} m1 m2 keys-m1≠keys-m2 = U-comm Value {m1} {m2} keys-m1≠keys-m2

union-assoc : ∀ {Value} → (m1 m2 m3 : Map Value) → union m1 (union m2 m3) ≡ union (union m1 m2) m3
union-assoc {Value} = U-assoc Value

keys-assoc-comm : ∀ {Value} m1 m2 m3 →
  keys (union {Value} (union {Value} m1 m2) m3) ≡
  keys (union {Value} (union {Value} m1 m3) m2)
keys-assoc-comm {Value} = Dom'-assoc-comm Value

U⁻-m : ∀ {Value m m' n} → ∈Dom n (union {Value} m m') → ∈Dom n m ⊎ ∈Dom n m'
U⁻-m {Value} {m} {m'} {n} = U⁻ Value {m} {m'} {inject n}

U-∉-irr-get-help-m : ∀{Value m m' k} → (k∈m : ∈Dom k m) → ¬ (∈Dom k m') → (k∈' : ∈Dom k (union {Value} m m'))→ lookup {Value} {k} m k∈m ≡ lookup {Value} {k} (union {Value} m m') k∈'
U-∉-irr-get-help-m {Value} = U-∉-irr-get-help Value

U-∉-irr-get-m : ∀{Value m m' k} → (k∈m : ∈Dom k m) → ¬ (∈Dom k m') → ∃ λ k∈' → lookup {Value} {k} m k∈m ≡ lookup {Value} {k} (union {Value} m m') k∈'
U-∉-irr-get-m {Value} = U-∉-irr-get Value

get-U-right-irr-m : ∀ {Value} k m m' →
  (k∈mUm' : ∈Dom k (union {Value} m m')) →
  (k∈m'   : ∈Dom k m') →
  lookup {Value} {k} (union {Value} m m') k∈mUm' ≡ lookup {Value} {k} m' k∈m'
get-U-right-irr-m {Value} k = deref-U-right-irr Value (inject k)

∈-get-U-irr-m : ∀ {Value} k m m' m'' k∈mUm'' k∈m'Um'' →
  (k∈m'' : ∈Dom k m'') →
    lookup {Value} {k} (union {Value} m  m'') k∈mUm'' ≡
    lookup {Value} {k} (union {Value} m' m'') k∈m'Um''
∈-get-U-irr-m {Value} k = ∈-deref-U-irr Value (inject k)

get-U-both-irr-m : ∀ {Value} k m m' m'' k∈m k∈m' k∈mUm'' k∈m'Um'' →
  lookup {Value} {k} m k∈m ≡ lookup {Value} {k} m' k∈m' →
    lookup {Value} {k} (union {Value} m  m'') k∈mUm'' ≡
    lookup {Value} {k} (union {Value} m' m'') k∈m'Um''
get-U-both-irr-m {Value} k = deref-U-both-irr Value (inject k)

putputget : ∀{Value m k k' v v'}
            → ¬ k ≡ k'
            → (∈1 : ∈Dom k m)
            → (∈2 : ∈Dom k (update{Value} m k' v'))
            → lookup{Value}{k} m ∈1 ≡ v
            → lookup{Value}{k} (update{Value} m k' v') ∈2 ≡ v
putputget{V}{m}{k}{k'}{v}{v'} neq kin1 kin2 eq = putputget-neq V {inject k}{inject k'}{m} (nbiject neq) kin1 kin2 eq

putput-overwrite : ∀{Value} m k v v'
                   → (update{Value} (update{Value} m k v) k v') ≡ (update m k v')
putput-overwrite {V} m k v v' = putputget-eq V (inject k) m v v'

getput-m : ∀ {Value} k m →
  (∈1 : ∈Dom k m) →
  m ≡ update {Value} m k (lookup {Value} {k} m ∈1)
getput-m {Value} k = getput Value (inject k)

putget-m : ∀{Value m k v} → (kin : ∈Dom k (update{Value} m k v)) → (lookup{Value}{k} (update{Value} m k v) kin) ≡ v
putget-m {V}{m}{k}{v} kin = putget V kin

update-union-union : ∀ {Value} k v m m' →
  (∈1 : ∈Dom k m) →
  (∈2 : ∈Dom k m') →
    union {Value} m m' ≡
    union {Value} (update {Value} m k v) m'
update-union-union {Value} k = put-U-U Value (inject k)

ineq-m : ∀{Value k m} → (k1 : ∈Dom{Value} k m) → (k2 : ∈Dom k m) → k1 ≡ k2
ineq-m {V}{k}{m} k1 k2 = ineq V {inject k}{m} k1 k2

put-comm : ∀{Value m k1 k2 v1 v2}
          → ¬ k1 ≡ k2 → (update (update{Value} m k1 v1) k2 v2) ≡ (update (update m k2 v2) k1 v1)
put-comm{V}{m}{k1}{k2}{v1}{v2} ¬k1≡k2 = insert-comm V {m}{inject k1}{inject k2}{v1}{v2} (nbiject ¬k1≡k2)

put-union-comm : ∀ {Value} k v m m' →
  ¬ (∈Dom k m') →
    union {Value} (update {Value} m k v) m' ≡
    update {Value} (union {Value} m m') k v
put-union-comm {Value} k = insert-U-comm Value (inject k)

union-=-irr : ∀ {Value} m m' → (keys{Value} m) ≡ (keys{Value} m') → ∀ k k∈ k∈2 → (lookup{Value}{k} (union m m') k∈) ≡ (lookup{Value}{k} m' k∈2)
union-=-irr {V} m m' eql k = U-=-irr V m m' eql (inject k)

union-single-overwrite : ∀ {V} k v1 v2 → union{V} [ k ↦ v1 ] [ k ↦ v2 ] ≡ [ k ↦ v2 ]
union-single-overwrite {V} k v1 v2 = U-single-overwrite V (inject k) v1 v2

update-single-val : ∀ {V} k v k∈ → lookup{V}{k} [ k ↦ v ] k∈ ≡ v
update-single-val {V} k v k∈ = set-single-val V (inject k) v k∈

single-val-overwrite : ∀ {V} k v m → (∈Dom{V} k m) → union{V}  ([ k ↦ v ]) m ≡ m
single-val-overwrite {V} k = overwrite-single-val V (inject k)

union-insert-eq : ∀{V} k v m → (update{V} m k v) ≡ (union{V} m [ k ↦ v ])
union-insert-eq {V} k = U-insert-eq V (inject k)

update-domain-eq : ∀{V} k v m → (inject k) ∈ keys{V} m → (keys m) ≡ (keys (update{V} m k v))
update-domain-eq {V} k = insert-domain-eq V (inject k)

--(OMap.insert-mono Value {m}{inject k}{inject k'}{v} i)
keys-2map∈ : ∀{Value} → (k1 : Key) → (v1 : Value) → (k2 : Key) → (v2 : Value) → (∈Dom k1 [ k1 ↦ v1 , k2 ↦ v2 ]) × (∈Dom k2 [ k1 ↦ v1 , k2 ↦ v2 ])
keys-2map∈ {V} k1 v1 k2 v2 = insert-mono (keys-1map∈ k1 v1) , update-in-keys ([ k1 ↦ v1 ]) k2 v2

count : ∀ {V} -> (V -> Bool) -> Map V -> ℕ
count {V} p m = ocount V p m

change-one-count-goes-down :
  ∀ {Value} (m : Map Value) ->
    (p : Value -> Bool) ->
    (k : Key) ->
    (k∈ : ∈Dom k m) ->
    p (lookup m k∈) ≡ true ->
    (v : Value) ->
    p v ≡ false ->
    count p m ≡ suc (count p (update m k v))
change-one-count-goes-down {V} m p k =
  change-one-ocount-goes-down V m p (inject k)

change-nothing-count-stays-same :
  ∀ {Value} (m : Map Value) ->
    (p : Value -> Bool) ->
    (k : Key) ->
    (k∈ : ∈Dom k m) ->
    (v : Value) ->
    p (lookup m k∈) ≡ p v ->
    count p m ≡ count p (update m k v)
change-nothing-count-stays-same {V} m p k =
  change-nothing-ocount-stays-same V m p (inject k)

count-merge≤′sum-count :
  ∀ {Value}
    (m1 m2 : Map Value) ->
    (p : Value -> Bool) ->
    count p (union m1 m2) ≤′ count p m1 + count p m2
count-merge≤′sum-count {V} m1 m2 p =
  ocount-merge≤′sum-ocount V m1 m2 p


{-# OPTIONS --cubical --no-import-sorts --safe #-}

module Cubical.Relation.Binary.Raw.Construct.Intersection where

open import Cubical.Core.Everything
open import Cubical.Foundations.Function using (_∘_)

open import Cubical.Data.Prod
open import Cubical.Data.Sum.Base using (_⊎_; inl; inr; rec)
open import Cubical.Relation.Binary.Raw
open import Cubical.Relation.Nullary using (yes; no)

------------------------------------------------------------------------
-- Definition

_∩_ : ∀ {a b ℓ₁ ℓ₂} {A : Type a} {B : Type b} →
      RawREL A B ℓ₁ → RawREL A B ℓ₂ → RawREL A B (ℓ-max ℓ₁ ℓ₂)
L ∩ R = λ i j → L i j × R i j

------------------------------------------------------------------------
-- Properties

module _ {a ℓ₁ ℓ₂} {A : Type a} (L : RawRel A ℓ₁) (R : RawRel A ℓ₂) where

  reflexive : Reflexive L → Reflexive R → Reflexive (L ∩ R)
  reflexive L-refl R-refl = L-refl , R-refl

  symmetric : Symmetric L → Symmetric R → Symmetric (L ∩ R)
  symmetric L-sym R-sym = map L-sym R-sym

  transitive : Transitive L → Transitive R → Transitive (L ∩ R)
  transitive L-trans R-trans (xLy , xRy) (yLz , yRz) = L-trans xLy yLz , R-trans xRy yRz

  respects : ∀ {p} (P : A → Type p) →
             P Respects L ⊎ P Respects R → P Respects (L ∩ R)
  respects P resp (Lxy , Rxy) = rec (λ x → x Lxy) (λ x → x Rxy) resp

  min : ∀ {⊤} → Minimum L ⊤ → Minimum R ⊤ → Minimum (L ∩ R) ⊤
  min L-min R-min = intro L-min R-min

  max : ∀ {⊥} → Maximum L ⊥ → Maximum R ⊥ → Maximum (L ∩ R) ⊥
  max L-max R-max = intro L-max R-max

  toNotEq : ToNotEq L ⊎ ToNotEq R → ToNotEq (L ∩ R)
  toNotEq tonoteq (Lxy , Rxy) x≡y = rec (λ x → x Lxy x≡y) (λ y → y Rxy x≡y) tonoteq

  irreflexive : Irreflexive L ⊎ Irreflexive R → Irreflexive (L ∩ R)
  irreflexive irrefl (xLx , xRx) = rec (λ x → x xLx) (λ y → y xRx) irrefl

  antisymmetric : Antisymmetric L ⊎ Antisymmetric R → Antisymmetric (L ∩ R)
  antisymmetric (inl L-antisym) (Lxy , _) (Lyx , _) = L-antisym Lxy Lyx
  antisymmetric (inr R-antisym) (_ , Rxy) (_ , Ryx) = R-antisym Rxy Ryx

module _ {a b ℓ₁ ℓ₂ ℓ₃} {A : Type a} {B : Type b}
         (≈ : RawREL A B ℓ₁) {L : RawREL A B ℓ₂} {R : RawREL A B ℓ₃} where

  implies : (≈ ⇒ L) → (≈ ⇒ R) → ≈ ⇒ (L ∩ R)
  implies ≈⇒L ≈⇒R = intro ≈⇒L ≈⇒R

module _ {a ℓ₁ ℓ₂ ℓ₃} {A : Type a}
         (≈ : RawRel A ℓ₁) (L : RawRel A ℓ₂) (R : RawRel A ℓ₃) where

  respectsˡ : L Respectsˡ ≈ → R Respectsˡ ≈ → (L ∩ R) Respectsˡ ≈
  respectsˡ L-resp R-resp x≈y = map (L-resp x≈y) (R-resp x≈y)

  respectsʳ : L Respectsʳ ≈ → R Respectsʳ ≈ → (L ∩ R) Respectsʳ ≈
  respectsʳ L-resp R-resp x≈y = map (L-resp x≈y) (R-resp x≈y)

  respects₂ : L Respects₂ ≈ → R Respects₂ ≈ → (L ∩ R) Respects₂ ≈
  respects₂ (Lʳ , Lˡ) (Rʳ , Rˡ) = respectsʳ Lʳ Rʳ , respectsˡ Lˡ Rˡ

module _ {a b ℓ₁ ℓ₂} {A : Type a} {B : Type b} {L : RawREL A B ℓ₁} {R : RawREL A B ℓ₂} where

  decidable : Decidable L → Decidable R → Decidable (L ∩ R)
  decidable L? R? x y = ×-dec (L? x y) (R? x y)

------------------------------------------------------------------------
-- Structures

module _ {a ℓ₁ ℓ₂} {A : Type a} {L : RawRel A ℓ₁} {R : RawRel A ℓ₂} where

  isPartialEquivalence : IsPartialEquivalence L → IsPartialEquivalence R → IsPartialEquivalence (L ∩ R)
  isPartialEquivalence eqₗ eqᵣ = record
    { symmetric  = symmetric  L R Eqₗ.symmetric  Eqᵣ.symmetric
    ; transitive = transitive L R Eqₗ.transitive Eqᵣ.transitive
    }
    where module Eqₗ = IsPartialEquivalence eqₗ; module Eqᵣ = IsPartialEquivalence eqᵣ

  isEquivalence : IsEquivalence L → IsEquivalence R → IsEquivalence (L ∩ R)
  isEquivalence eqₗ eqᵣ = record
    { isPartialEquivalence = isPartialEquivalence Eqₗ.isPartialEquivalence Eqᵣ.isPartialEquivalence
    ; reflexive            = reflexive L R Eqₗ.reflexive Eqᵣ.reflexive
    }
    where module Eqₗ = IsEquivalence eqₗ; module Eqᵣ = IsEquivalence eqᵣ

  isDecEquivalence : IsDecEquivalence L → IsDecEquivalence R → IsDecEquivalence (L ∩ R)
  isDecEquivalence eqₗ eqᵣ = record
    { isEquivalence = isEquivalence Eqₗ.isEquivalence Eqᵣ.isEquivalence
    ; _≟_           = decidable Eqₗ._≟_ Eqᵣ._≟_
    }
    where module Eqₗ = IsDecEquivalence eqₗ; module Eqᵣ = IsDecEquivalence eqᵣ

  isPreorder : IsPreorder L → IsPreorder R → IsPreorder (L ∩ R)
  isPreorder Oₗ Oᵣ = record
    { reflexive  = reflexive L R Oₗ.reflexive Oᵣ.reflexive
    ; transitive = transitive L R Oₗ.transitive Oᵣ.transitive
    }
    where module Oₗ = IsPreorder Oₗ; module Oᵣ = IsPreorder Oᵣ

  isPartialOrderˡ : IsPartialOrder L → IsPreorder R → IsPartialOrder (L ∩ R)
  isPartialOrderˡ Oₗ Oᵣ = record
    { isPreorder = isPreorder Oₗ.isPreorder Oᵣ
    ; antisym    = antisymmetric L R (inl Oₗ.antisym)
    }
    where module Oₗ = IsPartialOrder Oₗ; module Oᵣ = IsPreorder Oᵣ

  isPartialOrderʳ : IsPreorder L → IsPartialOrder R → IsPartialOrder (L ∩ R)
  isPartialOrderʳ Oₗ Oᵣ = record
    { isPreorder = isPreorder Oₗ Oᵣ.isPreorder
    ; antisym    = antisymmetric L R (inr Oᵣ.antisym)
    }
    where module Oₗ = IsPreorder Oₗ; module Oᵣ = IsPartialOrder Oᵣ

  isStrictPartialOrderˡ : IsStrictPartialOrder L → Transitive R → IsStrictPartialOrder (L ∩ R)
  isStrictPartialOrderˡ Oₗ transitiveᵣ = record
    { irrefl     = irreflexive L R (inl Oₗ.irrefl)
    ; transitive = transitive L R Oₗ.transitive transitiveᵣ
    }
    where module Oₗ = IsStrictPartialOrder Oₗ

  isStrictPartialOrderʳ : Transitive L → IsStrictPartialOrder R → IsStrictPartialOrder (L ∩ R)
  isStrictPartialOrderʳ transitiveₗ Oᵣ = record
    { irrefl     = irreflexive L R (inr Oᵣ.irrefl)
    ; transitive = transitive L R transitiveₗ Oᵣ.transitive
    }
    where module Oᵣ = IsStrictPartialOrder Oᵣ


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

open import HoTT
open import cw.CW
open import cw.FinCW
open import cw.FinBoundary
open import cohomology.Theory
open import cohomology.ChainComplex

module cw.cohomology.ReconstructedCochainsEquivCellularCochains
  (OT : OrdinaryTheory lzero) where

  open OrdinaryTheory OT
  open import cw.cohomology.cellular.ChainComplex as CCC
  open import cw.cohomology.reconstructed.cochain.Complex OT as RCC
  open import cw.cohomology.ReconstructedCochainsIsoCellularCochains OT
  open import cw.cohomology.cochainequiv.AugmentCommSquare OT
  open import cw.cohomology.cochainequiv.FirstCoboundaryCommSquare OT
  open import cw.cohomology.cochainequiv.HigherCoboundaryCommSquare OT

  private
    frcc-comm-fccc-Type : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
      {m} (m≤n? : Dec (m ≤ n)) (Sm≤n? : Dec (S m ≤ n))
      (coboundary : AbGroup.grp (RCC.cochain-template ⊙⦉ ⊙fin-skel ⦊ m≤n?)
                 →ᴳ AbGroup.grp (RCC.cochain-template ⊙⦉ ⊙fin-skel ⦊ Sm≤n?))
      (boundary : AbGroup.grp (CCC.chain-template ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊ Sm≤n?)
               →ᴳ AbGroup.grp (CCC.chain-template ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊ m≤n?))
      → Type₀
    frcc-comm-fccc-Type {n} ⊙fin-skel {m} m≤n? Sm≤n? coboundary boundary =
      CommSquareᴳ
        coboundary
        (pre∘ᴳ-hom (C2-abgroup 0) boundary)
        (GroupIso.f-hom (rcc-iso-ccc-template ⊙⦉ ⊙fin-skel ⦊ m≤n?
          (⊙FinSkeleton-has-cells-with-choice 0 ⊙fin-skel lzero)))
        (GroupIso.f-hom (rcc-iso-ccc-template ⊙⦉ ⊙fin-skel ⦊ Sm≤n?
          (⊙FinSkeleton-has-cells-with-choice 0 ⊙fin-skel lzero)))

    CCC-boundary-template' : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
      → {m : ℕ} (m≤n? : Dec (m ≤ n)) (Sm≤n? : Dec (S m ≤ n))
      →  AbGroup.grp (CCC.chain-template ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊ Sm≤n?)
      →ᴳ AbGroup.grp (CCC.chain-template ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊ m≤n?)
    CCC-boundary-template' ⊙fin-skel =
      CCC.boundary-template ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
        (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
        (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))

    RCC-coboundary-template' : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
      → {m : ℕ} (m≤n? : Dec (m ≤ n)) (Sm≤n? : Dec (S m ≤ n))
      →  AbGroup.grp (RCC.cochain-template ⊙⦉ ⊙fin-skel ⦊ m≤n?)
      →ᴳ AbGroup.grp (RCC.cochain-template ⊙⦉ ⊙fin-skel ⦊ Sm≤n?)
    RCC-coboundary-template' ⊙fin-skel = RCC.coboundary-template ⊙⦉ ⊙fin-skel ⦊

    abstract
      {- This can be directly generalized to the non-finite cases. -}
      frcc-comm-fccc-above : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
          {m} (m≤n? : Dec (m ≤ n)) (Sm≰n : ¬ (S m ≤ n))
        → frcc-comm-fccc-Type ⊙fin-skel m≤n? (inr Sm≰n)
            (RCC-coboundary-template' ⊙fin-skel m≤n? (inr Sm≰n))
            (CCC-boundary-template' ⊙fin-skel m≤n? (inr Sm≰n))
      frcc-comm-fccc-above ⊙fin-skel m≤n? Sm≰n =
        (comm-sqrᴳ λ g → group-hom= $ λ= λ _ →
          ! $ GroupHom.pres-ident
            (GroupIso.f
              (rcc-iso-ccc-template ⊙⦉ ⊙fin-skel ⦊ m≤n?
              (⊙FinSkeleton-has-cells-with-choice 0 ⊙fin-skel lzero)) g))

      frcc-comm-fccc-nth-zero : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n) (0≤n : 0 ≤ n) (1≤n : 1 ≤ n)
        → frcc-comm-fccc-Type ⊙fin-skel (inl 0≤n) (inl 1≤n)
            (RCC-coboundary-template' ⊙fin-skel (inl 0≤n) (inl 1≤n))
            (CCC-boundary-template' ⊙fin-skel (inl 0≤n) (inl 1≤n))
      frcc-comm-fccc-nth-zero ⊙fin-skel (inl idp) (inl 1=0) = ⊥-rec (ℕ-S≠O O 1=0)
      frcc-comm-fccc-nth-zero ⊙fin-skel (inl idp) (inr ())
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr ltS) (inl 1=1) =
        transport!
          (λ 1=1 → frcc-comm-fccc-Type ⊙fin-skel (inl lteS) (inl (inl 1=1))
              (RCC-coboundary-template' ⊙fin-skel (inl lteS) (inl (inl 1=1)))
              (CCC-boundary-template' ⊙fin-skel (inl lteS) (inl (inl 1=1))))
          (prop-has-all-paths 1=1 idp)
          (coe!
            (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel {m = O} (inl (inr ltS)) (inl (inl idp)) p₁ p₂)
              (RCC.coboundary-first-template-β ⊙⦉ ⊙fin-skel ⦊)
              (CCC.boundary-template-β ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
                (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
                (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))))
            (fst (first-coboundary-comm-sqrᴳ ⊙fin-skel)))
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr ltS) (inr (ltSR ()))
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr (ltSR 0<n)) (inl 1=Sn) = ⊥-rec (<-to-≠ (<-ap-S 0<n) 1=Sn)
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr (ltSR ltS)) (inr 1<2) =
        transport!
          (λ 1<2 → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr 1<2))
            (RCC-coboundary-template' ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr 1<2)))
            (CCC-boundary-template' ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr 1<2))))
          (prop-has-all-paths 1<2 ltS)
          (coe!
            (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr ltS)) p₁ p₂)
              (RCC.coboundary-first-template-descend-from-two ⊙⦉ ⊙fin-skel ⦊)
              (CCC.boundary-template-descend-from-two-above ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
                (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
                (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))))
            (frcc-comm-fccc-nth-zero (⊙fcw-init ⊙fin-skel) (inr ltS) (inl idp)))
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr (ltSR (ltSR ()))) (inr ltS)
      frcc-comm-fccc-nth-zero ⊙fin-skel (inr (ltSR (ltSR 0<n))) (inr (ltSR 1<Sn)) =
        (coe!
          (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR (ltSR 0<n)))) (inl (inr (ltSR 1<Sn))) p₁ p₂)
            (RCC.coboundary-first-template-descend-from-far ⊙⦉ ⊙fin-skel ⦊ (ltSR 0<n) 1<Sn)
            (CCC.boundary-template-descend-from-far ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
              (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
              (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))
              (ltSR 0<n) 1<Sn))
          (frcc-comm-fccc-nth-zero (⊙fcw-init ⊙fin-skel) (inr (ltSR 0<n)) (inr 1<Sn)))

      frcc-comm-fccc-nth-higher : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n) {m} (Sm≤n : S m ≤ n) (SSm≤n : S (S m) ≤ n)
        → frcc-comm-fccc-Type ⊙fin-skel (inl Sm≤n) (inl SSm≤n)
            (RCC-coboundary-template' ⊙fin-skel (inl Sm≤n) (inl SSm≤n))
            (CCC-boundary-template' ⊙fin-skel (inl Sm≤n) (inl SSm≤n))
      frcc-comm-fccc-nth-higher ⊙fin-skel (inl idp) (inl SSm=Sm) = ⊥-rec (<-to-≠ ltS (! SSm=Sm))
      frcc-comm-fccc-nth-higher ⊙fin-skel (inl idp) (inr SSm<Sm) = ⊥-rec (<-to-≠ (<-trans SSm<Sm ltS) idp)
      frcc-comm-fccc-nth-higher ⊙fin-skel {m} (inr ltS) (inl SSm=SSm) =
        transport!
          (λ SSm=SSm → frcc-comm-fccc-Type ⊙fin-skel (inl lteS) (inl (inl SSm=SSm))
            (RCC-coboundary-template' ⊙fin-skel (inl lteS) (inl (inl SSm=SSm)))
            (CCC-boundary-template' ⊙fin-skel (inl lteS) (inl (inl SSm=SSm))))
          (prop-has-all-paths SSm=SSm idp)
          (coe!
            (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel {m = S m} (inl (inr ltS)) (inl (inl idp)) p₁ p₂)
              (RCC.coboundary-higher-template-β ⊙⦉ ⊙fin-skel ⦊)
              (CCC.boundary-template-β ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
                (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
                (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))))
            (fst (higher-coboundary-comm-sqrᴳ ⊙fin-skel)))
      frcc-comm-fccc-nth-higher ⊙fin-skel (inr ltS) (inr SSm<SSm) = ⊥-rec (<-to-≠ SSm<SSm idp)
      frcc-comm-fccc-nth-higher ⊙fin-skel (inr (ltSR Sm<n)) (inl SSm=Sn) = ⊥-rec (<-to-≠ (<-ap-S Sm<n) SSm=Sn)
      frcc-comm-fccc-nth-higher ⊙fin-skel (inr (ltSR ltS)) (inr SSm<SSSm) =
        transport!
          (λ SSm<SSSm → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr SSm<SSSm))
            (RCC-coboundary-template' ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr SSm<SSSm)))
            (CCC-boundary-template' ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr SSm<SSSm))))
          (prop-has-all-paths SSm<SSSm ltS)
          (coe!
            (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR ltS))) (inl (inr ltS)) p₁ p₂)
              (RCC.coboundary-higher-template-descend-from-one-above ⊙⦉ ⊙fin-skel ⦊)
              (CCC.boundary-template-descend-from-two-above ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
                (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
                (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))))
            (frcc-comm-fccc-nth-higher (⊙fcw-init ⊙fin-skel) (inr ltS) (inl idp)))
      frcc-comm-fccc-nth-higher ⊙fin-skel (inr (ltSR (ltSR Sm<Sm))) (inr ltS) = ⊥-rec (<-to-≠ Sm<Sm idp)
      frcc-comm-fccc-nth-higher ⊙fin-skel (inr (ltSR (ltSR Sm<n))) (inr (ltSR SSm<Sn)) =
        (coe!
          (ap2 (λ p₁ p₂ → frcc-comm-fccc-Type ⊙fin-skel (inl (inr (ltSR (ltSR Sm<n)))) (inl (inr (ltSR SSm<Sn))) p₁ p₂)
            (RCC.coboundary-higher-template-descend-from-far ⊙⦉ ⊙fin-skel ⦊ (ltSR Sm<n) SSm<Sn)
            (CCC.boundary-template-descend-from-far ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
              (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
              (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))
              (ltSR Sm<n) SSm<Sn))
          (frcc-comm-fccc-nth-higher (⊙fcw-init ⊙fin-skel) (inr (ltSR Sm<n)) (inr SSm<Sn)))

      frcc-comm-fccc-template : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
          {m} (m≤n? : Dec (m ≤ n)) (Sm≤n? : Dec (S m ≤ n))
        → frcc-comm-fccc-Type ⊙fin-skel m≤n? Sm≤n?
            (RCC-coboundary-template' ⊙fin-skel m≤n? Sm≤n?)
            (CCC-boundary-template' ⊙fin-skel m≤n? Sm≤n?)
      frcc-comm-fccc-template ⊙fin-skel m≤n? (inr Sm≰n) = frcc-comm-fccc-above ⊙fin-skel m≤n? Sm≰n
      frcc-comm-fccc-template ⊙fin-skel (inr m≰n) (inl Sm≤n) = ⊥-rec $ m≰n (≤-trans lteS Sm≤n)
      frcc-comm-fccc-template ⊙fin-skel {m = O} (inl m≤n) (inl Sm≤n) = frcc-comm-fccc-nth-zero ⊙fin-skel m≤n Sm≤n
      frcc-comm-fccc-template ⊙fin-skel {m = S m} (inl Sm≤n) (inl SSm≤n) = frcc-comm-fccc-nth-higher ⊙fin-skel Sm≤n SSm≤n

      frcc-comm-fccc : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n) m
        → frcc-comm-fccc-Type ⊙fin-skel (≤-dec m n) (≤-dec (S m) n)
            (RCC-coboundary-template' ⊙fin-skel (≤-dec m n) (≤-dec (S m) n))
            (CCC-boundary-template' ⊙fin-skel (≤-dec m n) (≤-dec (S m) n))
      frcc-comm-fccc {n} ⊙fin-skel m =
        frcc-comm-fccc-template ⊙fin-skel {m} (≤-dec m n) (≤-dec (S m) n)

      frcc-comm-fccc-augment : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n)
        → CommSquareᴳ
            (CochainComplex.augment (RCC.cochain-complex ⊙⦉ ⊙fin-skel ⦊))
            (pre∘ᴳ-hom (C2-abgroup 0) (FreeAbGroup-extend (Lift-abgroup {j = lzero} ℤ-abgroup) λ _ → lift 1))
            (GroupIso.f-hom rhead-iso-chead)
            (GroupIso.f-hom (rcc-iso-ccc-template ⊙⦉ ⊙fin-skel ⦊ (inl (O≤ n))
              (⊙FinSkeleton-has-cells-with-choice 0 ⊙fin-skel lzero)))
      frcc-comm-fccc-augment {n = O} ⊙fin-skel =
        fst (augment-comm-sqrᴳ ⊙fin-skel)
      frcc-comm-fccc-augment {n = S O} ⊙fin-skel =
        frcc-comm-fccc-augment (⊙fcw-init ⊙fin-skel)
      frcc-comm-fccc-augment {n = S (S n)} ⊙fin-skel =
        frcc-comm-fccc-augment (⊙fcw-init ⊙fin-skel)

  frcc-equiv-fccc : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n) →
    CochainComplexEquiv
      (RCC.cochain-complex ⊙⦉ ⊙fin-skel ⦊)
      (CCC.cochain-complex ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
         (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
         (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))
         (C2-abgroup 0))
  frcc-equiv-fccc {n} ⊙fin-skel = record
    { head = rhead-iso-chead
    ; cochain = λ m → rcc-iso-ccc ⊙⦉ ⊙fin-skel ⦊ m (⊙FinSkeleton-has-cells-with-choice 0 ⊙fin-skel lzero)
    ; augment = frcc-comm-fccc-augment ⊙fin-skel
    ; coboundary = frcc-comm-fccc ⊙fin-skel}

  frc-iso-fcc : ∀ {n} (⊙fin-skel : ⊙FinSkeleton n) (m : ℤ)
    →  cohomology-group (RCC.cochain-complex ⊙⦉ ⊙fin-skel ⦊) m
    ≃ᴳ cohomology-group
        (CCC.cochain-complex ⦉ ⊙FinSkeleton.skel ⊙fin-skel ⦊
          (FinSkeleton-has-cells-with-dec-eq (⊙FinSkeleton.skel ⊙fin-skel))
          (FinSkeleton-has-degrees-with-finite-support (⊙FinSkeleton.skel ⊙fin-skel))
          (C2-abgroup 0))
        m
  frc-iso-fcc ⊙fin-skel = cohomology-group-emap (frcc-equiv-fccc ⊙fin-skel)


------------------------------------------------------------------------
-- Well-typed binary relations lifted to substitutions
------------------------------------------------------------------------

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

module Data.Fin.Substitution.TypedRelation where

open import Data.Context hiding (map)
open import Data.Context.WellFormed
open import Data.Fin using (Fin; zero)
open import Data.Fin.Substitution
open import Data.Fin.Substitution.Lemmas
open import Data.Fin.Substitution.ExtraLemmas
open import Data.Fin.Substitution.Typed
open import Data.Nat using (ℕ; zero; suc)
open import Data.Product as Prod using (_×_; _,_)
open import Data.Vec as Vec using (_∷_; map; zip; unzip)
open import Data.Vec.Properties
open import Function using (flip)
open import Level using (_⊔_) renaming (zero to lzero; suc to lsuc)
open import Relation.Binary.PropositionalEquality
open ≡-Reasoning
open import Relation.Unary using (Pred)

------------------------------------------------------------------------
-- Abstract typed binary relations lifted point-wise to substitutions

-- A shorthand.

infixr 2 _⊗_

_⊗_ : ∀ {t₁ t₂ } → Pred ℕ t₁ → Pred ℕ t₂ → Pred ℕ (t₁ ⊔ t₂)
(T₁ ⊗ T₂) n = T₁ n × T₂ n

-- Abstract typed binary relations on terms.
--
-- An abtract typed term relation _⊢_∼_∈_ : TypedRel Bnd Term₁ Term₂
-- Type is a four-place relation which, in a given Bnd-context,
-- relates Term₁ to Term₂-terms and their Type-types.

-- An abtract typing judgment _⊢_∈_ : Typing Bnd Term Type is a
-- ternary relation which, in a given Bnd-context, relates Term-terms
-- to their Type-types.

TypedRel : ∀ {t₁ t₂ t₃ t₄} →
         Pred ℕ t₁ → Pred ℕ t₂ → Pred ℕ t₃ → Pred ℕ t₄ → ∀ ℓ →
         Set (t₁ ⊔ t₂ ⊔ t₃ ⊔ t₄ ⊔ lsuc ℓ)
TypedRel Bnd Term₁ Term₂ Type ℓ =
  ∀ {n} → Ctx Bnd n → Term₁ n → Term₂ n → Type n → Set ℓ

module _ {t₁ t₂ t₃ t₄ ℓ} {Bnd : Pred ℕ t₁}
         {Term₁ : Pred ℕ t₂} {Term₂ : Pred ℕ t₃} {Type : Pred ℕ t₄} where

  -- The following maps witness the obvious correspondence between typed
  -- relations on Term₁ and Term₂ terms and typings of Term₁-Term₂ pairs.

  toTyping : TypedRel Bnd Term₁ Term₂ Type ℓ →
             Typing Bnd (Term₁ ⊗ Term₂) Type ℓ
  toTyping _⊢_∼_∈_ Γ = Prod.uncurry (_⊢_∼_∈_ Γ)

  fromTyping : Typing Bnd (Term₁ ⊗ Term₂) Type ℓ →
               TypedRel Bnd Term₁ Term₂ Type ℓ
  fromTyping _⊢_∈_ Γ = Prod.curry (_⊢_∈_ Γ)

record TypedRelation {t}
                     (Type : Pred ℕ t)      -- Type syntax
                     (Term : Pred ℕ lzero)  -- Term syntax
                     ℓ
                     : Set (lsuc (t ⊔ ℓ)) where

  infix 4 _⊢_∼_∈_ _⊢_wf

  field
    _⊢_wf   : Wf Type Type ℓ                   -- Type formation judgment
    _⊢_∼_∈_ : TypedRel Type Term Term Type ℓ   -- Typed relation judgment

------------------------------------------------------------------------
-- Abstract typed term relations lifted point-wise to substitutions

record TypedSubRel {t}
                   (Type : Pred ℕ t)      -- Type syntax
                   (Term : Pred ℕ lzero)  -- Term syntax
                   ℓ
                   : Set (lsuc (t ⊔ ℓ)) where

  -- The underlying typed relation

  field typedRelation : TypedRelation Type Term ℓ

  open TypedRelation typedRelation public

  -- Weakening and term substitutions in types.

  field
    typeExtension       : Extension Type                  -- type weakening
    typeTermApplication : Application Type (Term ⊗ Term)  -- term subst. app.
    termSimple          : Simple Term                     -- simple term subst.

  open ContextFormation _⊢_wf          using (_wf)
  open Application typeTermApplication using (_/_)

  -- We associate with every typed relation _⊢_∼_∈_ lifted to a pair
  -- of Term-substitutions a single typed substitution of pairs of
  -- Term-terms.

  termPairSimple : Simple (Term ⊗ Term)
  termPairSimple = record
    { var    = Prod.< var , var >
    ; weaken = Prod.map weaken weaken
    }
    where open Simple termSimple

  typedSub : TypedSub Type (Term ⊗ Term) ℓ
  typedSub = record
    { _⊢_wf               = _⊢_wf
    ; _⊢_∈_               = toTyping _⊢_∼_∈_
    ; typeExtension       = typeExtension
    ; typeTermApplication = typeTermApplication
    ; termSimple          = termPairSimple
    }

  open TypedSub typedSub using (_⊢_∈_; _⊢/_∈_; /∈-wf) public

  infix  4 _⊢/_∼_∈_

  -- Typed relations lifted to substitutions.
  --
  -- A typed substitution relation Δ ⊢/ σ ∼ ρ ∈ Γ is a pair σ, ρ of
  -- substitutions which, when applied to related terms Γ ⊢ s ∼ t ∈ a
  -- in a source context Γ, yield related well-typed terms Δ ⊢ s / σ ∼
  -- t / ρ ∈ a / zip σ ρ in a well-formed target context Δ.

  _⊢/_∼_∈_ : ∀ {m n} →
             Ctx Type n → Sub Term m n → Sub Term m n → Ctx Type m →
             Set (t ⊔ ℓ)
  Δ ⊢/ σ ∼ ρ ∈ Γ = Δ ⊢/ zip σ ρ ∈ Γ

------------------------------------------------------------------------
-- Operations on abstract well-typed substitutions

-- Helpers functions for relating extensions of (untyped) zipped
-- substitutions to their projections.

module ZipUnzipExtension {Tm₁ Tm₂ : ℕ → Set}
                         (ext₁ : Extension Tm₁)
                         (ext₂ : Extension Tm₂)
                         where

  private
    module E₁ = Extension ext₁
    module E₂ = Extension ext₂

  -- Extensions of (untyped) Tm⊗Tm substitutions.
  zippedExtension : Extension (Tm₁ ⊗ Tm₂)
  zippedExtension = record { weaken = Prod.map E₁.weaken E₂.weaken }
  open Extension zippedExtension

  -- Some useful shorthands

  π₁ : ∀ {n m} → Sub (Tm₁ ⊗ Tm₂) m n → Sub Tm₁ m n
  π₁ σ = Prod.proj₁ (unzip σ)

  π₂ : ∀ {n m} → Sub (Tm₁ ⊗ Tm₂) m n → Sub Tm₂ m n
  π₂ σ = Prod.proj₂ (unzip σ)

  -- π₁ and π₂ are projections (w.r.t. zipping)

  π₁-zip : ∀ {n m} (σ : Sub Tm₁ m n) (τ : Sub Tm₂ m n) → π₁ (zip σ τ) ≡ σ
  π₁-zip σ τ = cong Prod.proj₁ (unzip∘zip σ τ)

  π₂-zip : ∀ {n m} (σ : Sub Tm₁ m n) (τ : Sub Tm₂ m n) → π₂ (zip σ τ) ≡ τ
  π₂-zip σ τ = cong Prod.proj₂ (unzip∘zip σ τ)

  -- Weakening commutes with zipping and unzipping.

  map-weaken-zip : ∀ {m n} (σ : Sub Tm₁ m n) τ →
                   Vec.map weaken (zip σ τ) ≡
                     zip (Vec.map E₁.weaken σ) (Vec.map E₂.weaken τ)
  map-weaken-zip σ τ = map-zip E₁.weaken E₂.weaken σ τ

  map-weaken-unzip : ∀ {m n} (στ : Sub (Tm₁ ⊗ Tm₂) m n) →
                     (Vec.map E₁.weaken (π₁ στ) , Vec.map E₂.weaken (π₂ στ)) ≡
                       unzip (Vec.map weaken στ)
  map-weaken-unzip στ = map-unzip E₁.weaken E₂.weaken στ

  -- Extension commutes with zipping and unzipping.

  /∷-zip : ∀ {m n s t} (σ : Sub Tm₁ m n) τ →
           (s , t) /∷ zip σ τ ≡ zip (s E₁./∷ σ) (t E₂./∷ τ)
  /∷-zip σ τ = cong (_∷_ _) (map-weaken-zip σ τ)

  /∷-unzip : ∀ {m n s t} (στ : Sub (Tm₁ ⊗ Tm₂) m n) →
             (s E₁./∷ π₁ στ , t E₂./∷ π₂ στ) ≡ unzip ((s , t) /∷ στ)
  /∷-unzip στ = cong (Prod.map (_∷_ _) (_∷_ _)) (map-weaken-unzip στ)

-- Operations on related abstract typed substitutions that require
-- weakening of derivations of the typed relation

record TypedRelWeakenOps {t ℓ} {Type : Pred ℕ t} {Term : Pred ℕ lzero}
                         (typedSubRel : TypedSubRel Type Term ℓ)
                         : Set (lsuc (t ⊔ ℓ)) where

  open TypedSubRel      typedSubRel
  open ContextFormation _⊢_wf               using (_wf)
  open Application      typeTermApplication using (_/_)

  -- Operations on contexts and raw terms that require weakening

  private
    termExtension : Extension Term
    termExtension = record { weaken = Simple.weaken termSimple }

    module E = Extension termExtension
    module C = WeakenOps typeExtension
    module P = Simple    termPairSimple

  field

    -- Weakening preserves well-typed terms.

    ∼∈-weaken : ∀ {n} {Δ : Ctx Type n} {t u a b} →
                Δ ⊢ a wf → Δ ⊢ t ∼ u ∈ b →
                (a ∷ Δ) ⊢ E.weaken t ∼ E.weaken u ∈ C.weaken b

    -- Well-typedness implies well-formedness of contexts.

    ∼∈-wf : ∀ {n} {Δ : Ctx Type n} {t u a} → Δ ⊢ t ∼ u ∈ a → Δ wf

    -- Lemmas relating type weakening to term substitutions

    /-wk : ∀ {n} {a : Type n} → a / P.wk ≡ C.weaken a

    /-weaken : ∀ {m n} {στ : Sub (Term ⊗ Term) m n} a →
               a / Vec.map P.weaken στ ≡ a / στ / P.wk

    weaken-/-∷ : ∀ {n m} {tu} {στ : Sub (Term ⊗ Term) m n} (a : Type m) →
                 C.weaken a / (tu ∷ στ) ≡ a / στ

  -- Operations on the underlying abstract typed substitutions that
  -- require weakening.

  typedWeakenOps : TypedWeakenOps typedSub
  typedWeakenOps = record
    { ∈-weaken   = ∼∈-weaken
    ; ∈-wf       = ∼∈-wf
    ; /-wk       = /-wk
    ; /-weaken   = /-weaken
    ; weaken-/-∷ = weaken-/-∷
    }

  open TypedWeakenOps typedWeakenOps using (∈-/∷)
  open ZipUnzipExtension termExtension termExtension using (/∷-zip)

  -- Look up a pair of related entries in a pair of related
  -- substitutions.

  lookup : ∀ {m n Δ} {σ τ : Sub Term m n} {Γ} →
           Δ ⊢/ σ ∼ τ ∈ Γ → (x : Fin m) →
           Δ ⊢ Vec.lookup σ x ∼ Vec.lookup τ x ∈ C.lookup Γ x / zip σ τ
  lookup {σ = σ} {τ} σ∼τ∈Γ x =
    subst₂ (toTyping _⊢_∼_∈_ _) (lookup-zip x σ τ) refl
           (TypedWeakenOps.lookup typedWeakenOps σ∼τ∈Γ x)

  -- Extension by a pair of related typed terms.

  ∼∈-/∷ : ∀ {m n Γ} {σ τ : Sub Term m n} {Δ t u a b} →
          b ∷ Δ ⊢ t ∼ u ∈ C.weaken (a / zip σ τ) → Δ ⊢/ σ ∼ τ ∈ Γ →
          b ∷ Δ ⊢/ (t E./∷ σ) ∼ (u E./∷ τ) ∈ a ∷ Γ
  ∼∈-/∷ t∼u∈a/στ σ∼τ∈Γ =
    subst (flip (_⊢/_∈_ _) _) (/∷-zip _ _) (∈-/∷ t∼u∈a/στ σ∼τ∈Γ)

-- Helpers functions for relating simple (untyped) zipped
-- substitutions to their projections.

module ZipUnzipSimple {Tm₁ Tm₂ : ℕ → Set}
                      (simple₁ : Simple Tm₁)
                      (simple₂ : Simple Tm₂)
                      where

  private
    module S₁ = SimpleExt simple₁
    module S₂ = SimpleExt simple₂

  open ZipUnzipExtension S₁.extension S₂.extension public

  -- Simple (untyped) Tm⊗Tm substitutions.
  zippedSimple : Simple (Tm₁ ⊗ Tm₂)
  zippedSimple = record
    { weaken = Prod.map S₁.weaken S₂.weaken
    ; var    = λ x → S₁.var x , S₂.var x
    }
  open SimpleExt zippedSimple

  -- Lifting commutes with zipping.

  ↑-zip : ∀ {m n} (σ : Sub Tm₁ m n) τ →
          zip σ τ ↑ ≡ zip (σ S₁.↑) (τ S₂.↑)
  ↑-zip σ τ = /∷-zip σ τ

  ↑⋆-zip : ∀ {m n} (σ : Sub Tm₁ m n) τ k →
           zip σ τ ↑⋆ k ≡ zip (σ S₁.↑⋆ k) (τ S₂.↑⋆ k)
  ↑⋆-zip σ τ zero    = refl
  ↑⋆-zip σ τ (suc k) = cong (var zero ∷_) (begin
      map weaken (zip σ τ ↑⋆ k)
    ≡⟨ cong (map weaken) (↑⋆-zip σ τ k) ⟩  --
      map weaken (zip (σ S₁.↑⋆ k) (τ S₂.↑⋆ k))
    ≡⟨ map-weaken-zip (σ S₁.↑⋆ k) (τ S₂.↑⋆ k) ⟩
      zip (map S₁.weaken (σ S₁.↑⋆ k)) (map S₂.weaken (τ S₂.↑⋆ k))
    ∎)

  -- Lifting commutes with unzipping.
  ↑-unzip : ∀ {m n} (σ : Sub (Tm₁ ⊗ Tm₂) m n) →
            (π₁ σ S₁.↑ , π₂ σ S₂.↑) ≡ unzip (σ ↑)
  ↑-unzip σ = /∷-unzip σ

  -- Zipping preserves identity substitutions.
  id-zip : ∀ {n} → id {n} ≡ zip S₁.id S₂.id
  id-zip {zero}  = refl
  id-zip {suc n} = begin
    id {n} ↑                         ≡⟨ cong _↑ id-zip ⟩
    (zip S₁.id S₂.id) ↑              ≡⟨ ↑-zip S₁.id S₂.id ⟩
    zip (S₁.id S₁.↑) (S₂.id S₂.↑)    ∎

  -- Unzipping preserves identity substitutions.
  id-unzip : ∀ {n} → (S₁.id , S₂.id) ≡ unzip (id {n})
  id-unzip {zero}  = refl
  id-unzip {suc n} = begin
    (S₁.id S₁.↑ , S₂.id S₂.↑)   ≡⟨ cong (Prod.map S₁._↑ S₂._↑) id-unzip ⟩
    (π₁ id S₁.↑ , π₂ id S₂.↑)   ≡⟨ ↑-unzip id ⟩
    unzip (id {n} ↑)            ∎

  -- Zipping preserves weakening substitutions.
  wk-zip : ∀ {n} → wk {n} ≡ zip S₁.wk S₂.wk
  wk-zip {n} = begin
      map weaken id
    ≡⟨ cong (map weaken) id-zip ⟩
      map weaken (zip S₁.id S₂.id)
    ≡⟨ map-zip S₁.weaken S₂.weaken _ _ ⟩
      zip (map S₁.weaken S₁.id) (map S₂.weaken S₂.id)
    ∎

  -- Unzipping preserves weakening substitutions.
  wk-unzip : ∀ {n} → (S₁.wk , S₂.wk) ≡ unzip (wk {n})
  wk-unzip {n} = begin
      (map S₁.weaken S₁.id , map S₂.weaken S₂.id)
    ≡⟨ cong (Prod.map (map S₁.weaken) (map S₂.weaken)) id-unzip ⟩
      (map S₁.weaken (π₁ id) , map S₂.weaken (π₂ id))
    ≡⟨ map-unzip S₁.weaken S₂.weaken id ⟩
      unzip (map weaken id)
    ∎

  -- Zipping preserves sigle-variable substitutions.
  sub-zip : ∀ {n} (s : Tm₁ n) t → sub (s , t) ≡ zip (S₁.sub s) (S₂.sub t)
  sub-zip s t = cong (_∷_ (s , t)) id-zip

  -- Unzipping preserves sigle-variable substitutions.
  sub-unzip : ∀ {n} (s : Tm₁ n) t → (S₁.sub s , S₂.sub t) ≡ unzip (sub (s , t))
  sub-unzip s t = cong (Prod.map (_∷_ s) (_∷_ t)) id-unzip

-- Operations on abstract typed substitutions that require simple term
-- substitutions

record TypedRelSimple {t ℓ} {Type : Pred ℕ t} {Term : Pred ℕ lzero}
                      (typedSubRel : TypedSubRel Type Term ℓ)
                      : Set (lsuc (t ⊔ ℓ)) where

  -- Operations on related abstract typed substitutions that require
  -- weakening.

  field typedRelWeakenOps : TypedRelWeakenOps typedSubRel

  open TypedRelWeakenOps typedRelWeakenOps public
  open TypedSubRel       typedSubRel
  open ContextFormation  _⊢_wf               using (_wf; _⊢_wfExt)
  open Application       typeTermApplication using (_/_)

  private
    module S = Simple    termSimple
    module C = WeakenOps typeExtension
    module P = Simple    termPairSimple
  open S using (_↑; _↑⋆_; id; wk; sub)

  -- Context operations that require term substitutions in types.

  open SubstOps typeTermApplication termPairSimple using (_E/_)

  field

    -- Takes equal variables to well-typed related terms.

    ∼∈-var : ∀ {n} {Γ : Ctx Type n} →
             ∀ x → Γ wf → Γ ⊢ S.var x ∼ S.var x ∈ C.lookup Γ x

    -- Well-formedness of related types implies well-formedness of
    -- contexts.

    wf-wf : ∀ {n} {Γ : Ctx Type n} {a} → Γ ⊢ a wf → Γ wf

    -- The identity substitution in types is an identity

    id-vanishes : ∀ {n} (a : Type n) → a / P.id ≡ a

  -- Operations on the underlying abstract typed substitutions that
  -- require simple typed term substitutions.

  typedSimple : TypedSimple typedSub
  typedSimple = record
    { typedWeakenOps = typedWeakenOps
    ; ∈-var          = ∼∈-var
    ; wf-wf          = wf-wf
    ; id-vanishes    = id-vanishes
    }

  open TypedSimple typedSimple using (∈-↑; ∈-↑⋆; ∈-id; ∈-wk; ∈-sub; ∈-tsub)
  open ZipUnzipSimple termSimple termSimple

  -- Lifting.

  ∼∈-↑ : ∀ {m n Δ} {σ τ : Sub Term m n} {a Γ} →
         Δ ⊢ a / zip σ τ wf → Δ ⊢/ σ ∼ τ ∈ Γ →
         a / zip σ τ ∷ Δ ⊢/ σ ↑ ∼ τ ↑ ∈ a ∷ Γ
  ∼∈-↑ a/στ-wf σ∼τΓ =
    subst (flip (_⊢/_∈_ _) _) (↑-zip _ _) (∈-↑ a/στ-wf σ∼τΓ)

  ∼∈-↑⋆ : ∀ {k m n Δ} {E : CtxExt Type m k} {σ τ : Sub Term m n} {Γ} →
          Δ ⊢ (E E/ zip σ τ) wfExt → Δ ⊢/ σ ∼ τ ∈ Γ →
          (E E/ zip σ τ) ++ Δ ⊢/ σ ↑⋆ k ∼ τ ↑⋆ k ∈ (E ++ Γ)
  ∼∈-↑⋆ {k} E/στ-wfExt σ∼τ∈Γ =
    subst (flip (_⊢/_∈_ _) _) (↑⋆-zip _ _ k) (∈-↑⋆ E/στ-wfExt σ∼τ∈Γ)

  -- The identity substitution.

  ∼∈-id : ∀ {n} {Γ : Ctx Type n} → Γ wf → Γ ⊢/ id ∼ id ∈ Γ
  ∼∈-id Γ-wf = subst (flip (_⊢/_∈_ _) _) id-zip (∈-id Γ-wf)

  -- Weakening.

  ∼∈-wk : ∀ {n} {Γ : Ctx Type n} {a} → Γ ⊢ a wf → a ∷ Γ ⊢/ wk ∼ wk ∈ Γ
  ∼∈-wk a-wf = subst (flip (_⊢/_∈_ _) _) wk-zip (∈-wk a-wf)

  -- A substitution which only replaces the first variable.

  ∼∈-sub : ∀ {n} {Γ : Ctx Type n} {t u a} → Γ ⊢ t ∼ u ∈ a →
           Γ ⊢/ sub t ∼ sub u ∈ a ∷ Γ
  ∼∈-sub s∼t∈a =
    subst (flip (_⊢/_∈_ _) _) (sub-zip _ _) (∈-sub s∼t∈a)

  -- A substitution which only changes the type of the first variable.

  ∼∈-tsub : ∀ {n} {Γ : Ctx Type n} {a b} →
            b ∷ Γ ⊢ S.var zero ∼ S.var zero ∈ C.weaken a →
            b ∷ Γ ⊢/ id ∼ id ∈ a ∷ Γ
  ∼∈-tsub z∈a = subst (flip (_⊢/_∈_ _) _) id-zip (∈-tsub z∈a)


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

open import Algebra
open import Relation.Unary
open import Relation.Binary hiding (Decidable)

module Data.FingerTree.Split.Structures
  {r m}
  (ℳ : Monoid r m)
  {s}
  {ℙ : Pred (Monoid.Carrier ℳ) s}
  (ℙ-resp : ℙ Respects (Monoid._≈_ ℳ))
  (ℙ? : Decidable ℙ)
  where

open import Relation.Nullary using (¬_; yes; no; Dec)
open import Level using (_⊔_)
open import Data.Product
open import Function
open import Data.List as List using (List; _∷_; [])

open import Data.FingerTree.Measures ℳ
open import Data.FingerTree.Structures ℳ
open import Data.FingerTree.Reasoning ℳ
open import Data.FingerTree.View ℳ using (deepₗ; deepᵣ)
open import Data.FingerTree.Cons ℳ using (listToTree)

open import Relation.Nullary using (Dec; yes; no)
open import Relation.Nullary.Decidable using (True; toWitness; False; toWitnessFalse)

open σ ⦃ ... ⦄

open Monoid ℳ renaming (Carrier to 𝓡)

open import Data.FingerTree.Relation.Binary.Reasoning.FasterInference.Setoid setoid

open import Data.FingerTree.Split.Point ℳ ℙ-resp ℙ?
open import Data.FingerTree.Split.StoredPredicate ℳ ℙ-resp ℙ?
open import Data.FingerTree.Split.Intermediate ℳ ℙ-resp ℙ?

open import Data.Empty.Irrelevant using (⊥-elim)

module _ {a} {Σ : Set a} ⦃ _ : σ Σ ⦄ where
  splitList : (i : 𝓡) → (xs : List Σ) → .(i ∣ μ xs) → μ⟨ Split′ i (List Σ) Σ ⟩≈ (i ∙ μ xs)
  splitList i [] s = ⊥-elim (¬∄ℙ s)
  splitList i (x ∷ xs) s with ⟪ℙ?⟫ (i ∙ μ x)
  ... | yes p ≈ℙ i∙x [ i∙x≈ ] = [] ∷⟨ x ⟩∷ xs [ s ▻ p ≈◄⟅ ℳ ↯ ⟆  ] ⇑[ ℳ ↯ ]
  ... | no ¬p ≈ℙ i∙x [ i∙x≈ ] with splitList i∙x xs (s ◄ ¬p ≈◄⟅ sym i∙x≈ ⟆) i≈[ i∙x≈ ]
  ... | ys ∷⟨ y ⟩∷ zs [ s′ ] ⇑[ ys≈ ] = (x ∷ ys) ∷⟨ y ⟩∷ zs [ s′ ≈◄⟅ ℳ ↯ ⟆ ] ⇑[ ℳ ↯ ] ≈[ ys≈ ]′ ≈[ ℳ ↯ ]

  splitNode : ∀ i → (xs : Node Σ) → .(i ∣ μ xs) → μ⟨ Split′ i (List Σ) Σ ⟩≈ (i ∙ μ xs)
  splitNode i xs s = do
    ys ← nodeToList xs [ _ ∙> sz ⟿ sz ]
    splitList i ys (s ≈▻⟅ sym (_ ≈? _) ⟆)

  splitDigit : ∀ i → (xs : Digit Σ) → .(i ∣ μ xs) → μ⟨ Split′ i (List Σ) Σ ⟩≈ (i ∙ μ xs)
  splitDigit i xs s = digitToList xs [ _ ∙> sz ⟿ sz ] >>= λ ys → splitList i ys (s ≈▻⟅ sym (_ ≈? _) ⟆)

  splitTree-l : ∀ i → (ls : Digit Σ) → (m : Tree ⟪ Node Σ ⟫) → (rs : Digit Σ) → .(i ∣ μ ls) → μ⟨ Split′ i (Tree Σ) Σ ⟩≈ (i ∙ (μ ls ∙ (μ m ∙ μ rs)))
  splitTree-l i ls m rs s with splitDigit i ls s
  splitTree-l i ls m rs s | lsₗ ∷⟨ mₗ ⟩∷ rsₗ [ p ] ⇑[ l≈ ] = [ ( ℳ ↯ ⍮′ ≪∙ l≈ ⍮ assoc _ _ _) ]≈ do
    ls′ ← listToTree lsₗ [ i ∙> (sz <∙ _) ⟿ sz ]
    rs′ ← deepₗ rsₗ m rs [ i ∙> (_ ∙> (_ ∙> sz)) ⟿ sz ]
    ls′ ∷⟨ mₗ ⟩∷ rs′ [ p ≈◄⟅ ∙≫ sym (μ ls′ ≈? μ lsₗ)  ⟆ ] ⇑

  splitTree-r : ∀ i → (ls : Digit Σ) → (m : Tree ⟪ Node Σ ⟫) → (rs : Digit Σ) → ∀ i∙ls∙m → .(i∙ls∙m ≈  (i ∙ μ ls ∙ μ m)) → .((i ∙ μ ls ∙ μ m) ∣ μ rs) → μ⟨ Split′ i (Tree Σ) Σ ⟩≈ (i ∙ (μ ls ∙ (μ m ∙ μ rs)))
  splitTree-r i ls m rs i′ i′≈ s with splitDigit i′ rs (s ≈◄⟅ sym i′≈ ⟆)
  splitTree-r i ls m rs i′ i′≈ s | lsᵣ ∷⟨ mᵣ ⟩∷ rsᵣ [ p ] ⇑[ r≈ ] = [ lemma r≈ i′≈ ]≈ do
      ls′ ← deepᵣ ls m lsᵣ [ i ∙> (sz <∙ _) ⟿ sz ]
      rs′ ← listToTree rsᵣ [ i ∙> (_ ∙> (_ ∙> sz)) ⟿ sz ]
      ls′ ∷⟨ mᵣ ⟩∷ rs′ [ p ≈◄⟅ ≪∙ i′≈ ⍮ ℳ ↯ ⍮′ ∙≫ sym (μ ls′ ≈? (μ ls ∙ (μ m ∙ μ lsᵣ))) ⟆ ] ⇑
    where
    lemma = λ r≈ i′≈ → begin
      i ∙ (μ ls ∙ (μ m ∙ μ lsᵣ) ∙ (μ mᵣ ∙ μ rsᵣ))
        ≈⟨ ℳ ↯ ⟩
      i ∙ μ ls ∙ μ m ∙ (μ lsᵣ ∙ (μ mᵣ ∙ μ rsᵣ))
        ≈⟨ ≪∙ sym i′≈ ⟩
      i′ ∙ (μ lsᵣ ∙ (μ mᵣ ∙ μ rsᵣ))
        ≈⟨ r≈ ⟩
      i′ ∙ μ rs
        ≈⟨ ≪∙ i′≈ ⟩
      i ∙ μ ls ∙ μ m ∙ μ rs
        ≈⟨ ℳ ↯ ⟩
      i ∙ (μ ls ∙ (μ m ∙ μ rs)) ∎

splitTree : ∀ {a} {Σ : Set a} ⦃ _ : σ Σ ⦄
          → ∀ i
          → (xs : Tree Σ)
          → .(i ∣ μ xs)
          → μ⟨ Split′ i (Tree Σ) Σ ⟩≈ (i ∙ μ xs)
splitTree i empty s = ⊥-elim (¬∄ℙ s)
splitTree i (single x) s = empty ∷⟨ x ⟩∷ empty [ s ≈◄⟅ ℳ ↯ ⟆ ] ⇑[ ℳ ↯ ]
splitTree i (deep (𝓂 ↤ ls & m & rs ⇑[ 𝓂≈ ])) s with ⟪ℙ?⟫ (i ∙ μ ls)
... | yes p₁ ≈ℙ i∙ls [ i∙ls≈ ] = splitTree-l i ls m rs ¬[ ¬ℙ s ]ℙ[ p₁ ]  ≈[ ∙≫ 𝓂≈ ]
... | no ¬p₁ ≈ℙ i∙ls [ i∙ls≈ ] with ⟪ℙ?⟫ (i∙ls ∙ μ m)
... | no ¬p₂ ≈ℙ i∙ls∙m [ i∙ls∙m≈ ] = splitTree-r i ls m rs i∙ls∙m (i∙ls∙m≈ ⍮ ≪∙ i∙ls≈) (s ≈▻⟅ sym 𝓂≈ ⟆ ◄ ¬p₁ ≈◄⟅ sym i∙ls≈ ⟆ ◄ ¬p₂ ≈◄⟅ ≪∙ i∙ls≈ ⟆) ≈[ ∙≫ 𝓂≈ ]
... | yes p₂ ≈ℙ i∙ls∙m [ i∙ls∙m≈ ] with splitTree i∙ls m (s ≈▻⟅ sym 𝓂≈ ⟆ ◄ ¬p₁ ≈◄⟅ sym i∙ls≈ ⟆ ▻ p₂)
... | lsₘ ∷⟨ μmₘ ↤ mₘ ⇑[ mₘ≈ ] ⟩∷ rsₘ [ sₘ ] ⇑[ m≈ ] with splitNode (i∙ls ∙ μ lsₘ) mₘ (sₘ ≈▻⟅ sym mₘ≈ ⟆)
... | lsₗ ∷⟨ mₗ ⟩∷ rsₗ [ sₗ ] ⇑[ l≈ ] = [ lemma 𝓂≈ m≈ mₘ≈ l≈ ]≈ do
      ll ← deepᵣ ls lsₘ lsₗ [ i ∙> (sz <∙ _) ⟿ sz ]
      rr ← deepₗ rsₗ rsₘ rs [ i ∙> (_ ∙> (μ mₗ ∙> sz)) ⟿ sz ]
      ll ∷⟨ mₗ ⟩∷ rr [ sₗ ≈◄⟅ ≪∙ ≪∙ i∙ls≈ ⍮ ℳ ↯ ⍮′ ∙≫ sym (μ ll ≈? (μ ls ∙ (μ lsₘ ∙ μ lsₗ))) ⟆ ] ⇑
  where
  lemma = λ 𝓂≈ m≈ mₘ≈ l≈ → begin
    i ∙ (μ ls ∙ (μ lsₘ ∙ μ lsₗ) ∙ (μ mₗ ∙ (μ rsₗ ∙ (μ rsₘ ∙ μ rs))))
      ≈⟨ ℳ ↯ ⟩
    i ∙ μ ls ∙ μ lsₘ ∙ (μ lsₗ ∙ (μ mₗ ∙ μ rsₗ)) ∙ (μ rsₘ ∙ μ rs)
      ≈⟨ ≪∙ ≪∙ ≪∙ sym i∙ls≈ ⍮ l≈ <∙ (μ rsₘ ∙ μ rs) ⟩
    i∙ls ∙ μ lsₘ ∙ μ mₘ ∙ (μ rsₘ ∙ μ rs)
      ≈⟨ ≪∙ ∙≫ mₘ≈ ⟩
    i∙ls ∙ μ lsₘ ∙ μmₘ ∙ (μ rsₘ ∙ μ rs)
      ≈⟨ ℳ ↯ ⟩
    i∙ls ∙ (μ lsₘ ∙ (μmₘ ∙ μ rsₘ)) ∙ μ rs
      ≈⟨ m≈ <∙ μ rs ⟩
    i∙ls ∙ μ m ∙ μ rs
      ≈⟨ ≪∙ ≪∙ i∙ls≈ ⟩
    i ∙ μ ls ∙ μ m ∙ μ rs
      ≈⟨ ℳ ↯ ⟩
    i ∙ (μ ls ∙ (μ m ∙ μ rs))
      ≈⟨ ∙≫ 𝓂≈ ⟩
    i ∙ 𝓂 ∎

init-ℙ : ∀ {𝓂} → (¬ℙ⟨ε⟩ : False (ℙ? ε)) → (ℙ⟨xs⟩ : True (ℙ? 𝓂)) → ε ∣ 𝓂
¬ℙ (init-ℙ fl tr) = toWitnessFalse fl
!ℙ (init-ℙ fl tr) = ℙ-resp (sym (identityˡ _)) (toWitness tr)