{-# OPTIONS --safe #-}

-- Based on:
-- A Type and Scope Safe Universe of Syntaxes with Binding: Their Semantics and Proofs
-- (https://github.com/gallais/generic-syntax)

-- When scopes are represented by lists of kinds, a variable of
-- a given kind is a position in such a list. This is a strongly
-- typed version of de Bruijn indices hence the name we picked
-- for Var's constructor:
-- * z for zero
-- * s for successor

module Data.Var where

open import Data.Sum hiding (map)
open import Data.List.Base hiding ([_]; _─_)
open import Data.List.Relation.Unary.All using (All; _∷_)
open import Relation.Unary
open import Function.Base using (const; _∋_)
open import Agda.Builtin.Equality


-- TODO: extract this to somewhere else?
_─Scoped : Set → Set₁
I ─Scoped = I → List I → Set


private
  variable
    I J : Set
    σ τ : I


data Var : I ─Scoped where
  z : ∀[ (σ ∷_) ⊢ Var σ ]
  s : ∀[ Var σ ⇒ (τ ∷_) ⊢ Var σ ]

infixl 3 _─_
_─_ : {σ : I} (Γ : List I) → Var σ Γ → List I
_ ∷ Γ ─ z   = Γ
σ ∷ Γ ─ s v = σ ∷ (Γ ─ v)

get : {B : I → Set} → ∀[ Var σ ⇒ All B ⇒ const (B σ) ]
get z     (b  ∷ _)  = b
get (s v) (_  ∷ bs) = get v bs

_<$>_ : (f : I → J) → ∀[ Var σ ⇒ map f ⊢ Var (f σ) ]
f <$> z    = z
f <$> s v  = s (f <$> v)

record Injective (f : I → J) : Set where
  constructor mkInjective
  field inj : ∀ {i₁ i₂} → f i₁ ≡ f i₂ → i₁ ≡ i₂
open Injective public

Injective-inj₁ : ∀ {A B : Set} → Injective ((A → A ⊎ B) ∋ inj₁)
inj Injective-inj₁ refl = refl

Injective-inj₂ : ∀ {A B : Set} → Injective ((B → A ⊎ B) ∋ inj₂)
inj Injective-inj₂ refl = refl

_<$>⁻¹_ : {f : I → J} → Injective f →
          ∀[ map f ⊢ Var (f σ) ⇒ Var σ ]
_<$>⁻¹_ {f = f} F = go _ refl refl where

  go : ∀ {j} Γ {js} → f σ ≡ j → map f Γ ≡ js → Var j js → Var σ Γ
  go []        eq ()   z
  go []        eq ()   (s v)
  go (i ∷ is)  eq refl z rewrite inj F eq = z
  go (i ∷ is)  eq refl (s v) = s (go is eq refl v)

injectˡ : ∀ Δ → ∀[ Var σ ⇒ (_++ Δ) ⊢ Var σ ]
injectˡ k z      = z
injectˡ k (s v)  = s (injectˡ k v)

injectʳ : ∀ Δ → ∀[ Var σ ⇒ (Δ ++_) ⊢ Var σ ]
injectʳ []        v = v
injectʳ (y ∷ ys)  v = s (injectʳ ys v)
