module STLC.Soundness where

open import STLC.Prelude
open import STLC.Common
open import STLC.Decl
open import STLC.Decl.Properties
open import STLC.Algo
open import STLC.Algo.Properties

_▻_ : Term  List Term  Term
e  [] = e
e₁  (e₂  es) = (e₁ · e₂)  es

infix 4 _⊩_⇐_
data _⊩_⇐_ : Env  List Term  List Type  Set where
  ⊩none :  {Γ}
     Γ  []  []

  ⊩cons :  {Γ es As e A}
     Γ  es  As
     Γ   # e  A
     Γ  (e  es)  (A  As)

⊩-elim :  {Γ e Σ A es T As A'}
   Γ  Z # e  A
   Γ  es  As
    Σ , A ⟧→⟦ es , T , As , A' 
   Γ  Z # e  es  A'
⊩-elim ⊢d ⊩none none-□ = ⊢d
⊩-elim ⊢d ⊩none none-τ = ⊢d
⊩-elim ⊢d (⊩cons ⊩es x) (have spl) = ⊩-elim (⊢app₁ ⊢d x) ⊩es spl

Apps : Set
Apps = List Term

AppsType : Set
AppsType = List Type

infix 5 _↑s
_↑s : Apps  Apps
_↑s = map (_↑ 0)

data AppsDes (as : Apps) : Set where

  des-app :  x xs
     (eq : as  xs ++ (x  []))
     AppsDes as

apps-destruct :  as
   0 < len as
   AppsDes as
apps-destruct  x  (s≤s sz) = des-app x [] refl
apps-destruct (x  y  as) (s≤s sz) with apps-destruct (y  as) (s≤s z≤n)
... | des-app x' xs eq rewrite eq = des-app x' (x  xs) refl

len-++-distri :  (xs ys : Apps)
   len (xs ++ ys)  len xs + len ys
len-++-distri [] ys = refl
len-++-distri (x  xs) ys rewrite len-++-distri xs ys = refl

len-++-suc :  (x : Term) xs k
   suc (len (xs ++  x ))  suc k
   suc (len xs) < suc k
len-++-suc x xs k (s≤s sz) rewrite len-++-distri xs  x  | +-comm 1 (len xs) = s≤s sz

rw-apps-gen :  (es) {e es'}
   e  (es ++ es')  (e  es)  es'
rw-apps-gen [] = refl
rw-apps-gen (x  es) = rw-apps-gen es

rw-apps :  es e x
   e  (es ++  x )  (e  es) · x
rw-apps es e x = rw-apps-gen es {e = e} {es' =  x }

↑s-++-distri :  xs x
   (xs ++  x ) ↑s  (xs ↑s) ++ ( x  ↑s)
↑s-++-distri [] x = refl
↑s-++-distri (x₁  xs) x rewrite ↑s-++-distri xs x = refl

¬<0→nil :  {es : Apps}
   ¬ 1  len es
   es  []
¬<0→nil {[]} sz = refl
¬<0→nil {x  es} sz = ⊥-elim (sz (s≤s z≤n))

subst-case-0 :  {Γ A B es j e e₁}
   ¬ 1  len es
   Γ , A  j # e  map (_↑ 0) es  B
   Γ  Z # e₁  A
   Γ  j # ((ƛ e) · e₁)  es  B
subst-case-0 {es = es} sz ⊢1 ⊢2 rewrite ¬<0→nil {es = es} sz = ⊢app₂ (⊢lam-n ⊢1) ⊢2

size-counter : Counter  
size-counter  = 1
size-counter Z = 0
size-counter (S j) = 1 + size-counter j

¬Zsize-counter>0 :  {k j}
   ¬Z j
   size-counter j  k
   0 < k
¬Zsize-counter>0 ¬Z-∞ sz = sz
¬Zsize-counter>0 ¬Z-S (s≤s sz) = s≤s z≤n

subst-2 :  k₁ k₂ es {Γ A B e e₁ j}
   len es < k₁
   size-counter j < k₂
   Γ , A  j # e  (es ↑s)  B
   Γ  Z # e₁  A
   Γ  j # ((ƛ e) · e₁)  es  B

subst-2-app :  k₁ k₂ xs x {Γ A B e e₁ j}
   (1 + len xs) < k₁
   size-counter j < k₂
   Γ , A  j # (e  (xs ↑s)) · (x  0)  B
   Γ  Z # e₁  A
   Γ  j # (((ƛ e) · e₁)  xs) · x  B

subst-2 (suc k₁) (suc k₂) es (s≤s sz₁) (s≤s sz₂) ⊢1 ⊢2 with len es >? 0
subst-2 (suc k₁) (suc k₂) es {e = e} {e₁ = e₁} (s≤s sz₁) (s≤s sz₂) ⊢1 ⊢2 | yes p with apps-destruct es p
... | des-app x xs eq rewrite eq
                            | rw-apps xs ((ƛ e) · e₁) x
                            | ↑s-++-distri xs x
                            | rw-apps (xs ↑s) e (x  0)
                            = subst-2-app (suc k₁) (suc k₂) xs x (len-++-suc x xs k₁ (s≤s sz₁)) (s≤s sz₂) ⊢1 ⊢2
subst-2 (suc k₁) (suc k₂) es (s≤s sz₁) (s≤s sz₂) ⊢1 ⊢2 | no ¬p = subst-case-0 {es = es} ¬p ⊢1 ⊢2

subst-2-app (suc k₁) (suc k₂) xs x (s≤s sz₁) (s≤s sz₂) (⊢app₁ ⊢1 ⊢3) ⊢2 =
  let ind-e₁ = subst-2 k₁ (suc (suc k₂)) xs sz₁ (s≤s z≤n) ⊢1 ⊢2
  in (⊢app₁ ind-e₁ (⊢strengthen-0 ⊢3))
subst-2-app (suc k₁) (suc k₂) xs x (s≤s sz₁) (s≤s sz₂) (⊢app₂ ⊢1 ⊢3) ⊢2 =
  let ind-e₁ = subst-2 k₁ (suc (suc k₂)) xs sz₁ (s≤s (s≤s sz₂)) ⊢1 ⊢2
  in (⊢app₂ ind-e₁ (⊢strengthen-0 ⊢3))
subst-2-app (suc k₁) (suc k₂) xs x (s≤s sz₁) (s≤s sz₂) (⊢sub ⊢1 neq) ⊢2 =
  ⊢sub' (subst-2-app (suc k₁) k₂ xs x (s≤s sz₁) (¬Zsize-counter>0 neq sz₂) ⊢1 ⊢2)

subst :   {Γ A B e e₁ j} (es : List Term)
   Γ , A  j # e  map (_↑ 0) es  B
   Γ  Z # e₁  A
   Γ  j # ((ƛ e) · e₁)  es  B
subst {j = j} es ⊢1 ⊢2 = subst-2 (suc (len es)) (suc (size-counter j)) es (s≤s m≤m) (s≤s m≤m) ⊢1 ⊢2

⊢spl-eq :  {Γ Σ A e es T As A'}
   Γ  Σ  e  A
    Σ , A ⟧→⟦ es , τ T , As , A' 
   T  A'
⊢spl-eq ⊢e none-τ = ⊢context-full-type ⊢e
⊢spl-eq ⊢e (have spl) = ⊢spl-eq (⊢app ⊢e) spl

sound-≈ :  {Γ Σ A es T As A'}
   Γ  A  Σ
    Σ , A ⟧→⟦ es , T , As , A' 
   Γ  es  As

sound-inf :  {Γ e Σ A es As A'}
   Γ  Σ  e  A
    Σ , A ⟧→⟦ es ,  , As , A' 
   Γ  Z # e  es  A'

sound-chk :  {Γ e Σ A es T As A'}
   Γ  Σ  e  A
    Σ , A ⟧→⟦ es , τ T , As , A' 
   Γ   # e  es  T

sound-inf0 :  {Γ e A}
   Γ    e  A
   Γ  Z # e  A
sound-inf0 ⊢e = sound-inf ⊢e none-□

sound-chk0 :  {Γ e A B}
   Γ  τ B  e  A
   Γ   # e  B
sound-chk0 ⊢e = sound-chk ⊢e none-τ

sound-≈ ≈□ none-□ = ⊩none
sound-≈ ≈τ none-τ = ⊩none
sound-≈ (≈term ⊢e A≈Σ) (have spl) = ⊩cons (sound-≈ A≈Σ spl) (sound-chk ⊢e none-τ)

sound-inf ⊢lit none-□ = ⊢int
sound-inf (⊢var x) none-□ = ⊢var x
sound-inf (⊢ann ⊢e) none-□ = ⊢ann (sound-chk ⊢e none-τ)
sound-inf (⊢app ⊢e) spl = sound-inf ⊢e (have spl)
sound-inf {es = e  es} (⊢lam₂ ⊢e ⊢f) (have spl) = subst es (sound-inf ⊢f (spl-weaken spl)) (sound-inf ⊢e none-□)
sound-inf (⊢sub ⊢e A≈Σ p Σ≢□) spl = ⊢sub' (⊩-elim (sound-inf ⊢e none-□) (sound-≈ A≈Σ spl) spl)

sound-chk (⊢app ⊢e) spl = sound-chk ⊢e (have spl)
sound-chk (⊢lam₁ ⊢e) none-τ = ⊢lam-∞ (sound-chk ⊢e none-τ)
sound-chk {es = e  es} (⊢lam₂ ⊢e ⊢f) (have spl)  = subst es (sound-chk ⊢f (spl-weaken spl)) (sound-inf ⊢e none-□)
sound-chk ty@(⊢sub ⊢e A≈Σ p Σ≢□) spl rewrite ⊢spl-eq ty spl = ⊢sub' (⊩-elim (sound-inf ⊢e none-□) (sound-≈ A≈Σ spl) spl)