module linear.Typing.Functional where

open import Data.Nat
open import Data.Product
open import Function
open import Relation.Binary.PropositionalEquality

open import linear.Type
open import linear.Context
open import linear.Language
open import linear.Usage
open import linear.Usage.Functional
open import linear.Typing
open import linear.Relation.Functional

RPattern : (i : Type × Σ[ m   ] Pattern m) (o : let (_ , m , _) = i in Context m)  Set
RPattern (A , _ , p) δ = A  p  δ

functionalPattern : Functional′ RPattern
functionalPattern _ `v         `v         = refl
functionalPattern _ `⟨⟩        `⟨⟩        = refl
functionalPattern _ (p₁ ,, q₁) (p₂ ,, q₂) = cong₂ _ (functionalPattern _ p₁ p₂) (functionalPattern _ q₁ q₂)

functionalInfer : Functional (InferTyping TInfer)
functionalInfer _ (`var k₁)    (`var k₂)    = functionalFin _ k₁ k₂
functionalInfer _ (`app t₁ u₁) (`app t₂ u₂) = cong  { (_ ─o τ)  τ; σ  σ })
                                            $ functionalInfer _ t₁ t₂
functionalInfer _ (`fst t₁)    (`fst t₂)    = cong  { (σ & _)  σ; σ  σ})
                                            $ functionalInfer _ t₁ t₂
functionalInfer _ (`snd t₁)    (`snd t₂)    = cong  { (_ & τ)  τ; σ  σ})
                                            $ functionalInfer _ t₁ t₂
functionalInfer _ (`case t₁ return σ₁ of l₁ %% r₁) (`case t₂ return .σ₁ of l₂ %% r₂) = refl
functionalInfer _ (`exfalso σ₁ t₁) (`exfalso σ₂ t₂) = refl
functionalInfer _ (`cut t₁) (`cut t₂) = refl


mutual

  functionalInferPost : Functional′ (InferTypingPost TInfer)
  functionalInferPost _ (`var x₁)    (`var x₂) = functionalFinPost _ x₁ x₂
  functionalInferPost _ (`app t₁ u₁) (`app t₂ u₂)
    with functionalInferPost _ t₁ t₂
  ... | refl = cong _ $ functionalCheckPost _ u₁ u₂
  functionalInferPost _ (`fst t₁) (`fst t₂)
    with functionalInferPost _ t₁ t₂
  ... | refl = refl
  functionalInferPost _ (`snd t₁) (`snd t₂)
    with functionalInferPost _ t₁ t₂
  ... | refl = refl
  functionalInferPost _ (`case t₁ return σ₁ of l₁ %% r₁) (`case t₂ return .σ₁ of l₂ %% r₂)
    with functionalInferPost _ t₁ t₂
  ... | refl with functionalCheckPost _ l₁ l₂
  ... | refl = refl
  functionalInferPost _ (`exfalso σ₁ t₁) (`exfalso σ₂ t₂)
    with functionalInferPost _ t₁ t₂
  ... | refl = refl
  functionalInferPost _ (`cut t₁) (`cut t₂) = cong _ $ functionalCheckPost _ t₁ t₂

  functionalCheckPost  : Functional′ (CheckTypingPost TCheck)
  functionalCheckPost _ (`lam t₁) (`lam t₂)
    with functionalCheckPost _ t₁ t₂
  ... | refl = refl
  functionalCheckPost (n , γ , Γ , σ , `let p ∷= t `in u) 
    (`let_∷=_`in_ {δ = δ} p₁ t₁ u₁) (`let p₂ ∷= t₂ `in u₂)
    with functionalInferPost _ t₁ t₂
  ... | refl with functionalPattern _ p₁ p₂
  ... | refl = functional++ ]] δ [[ refl (functionalCheckPost _ u₁ u₂)
  functionalCheckPost _ `unit `unit = refl
  functionalCheckPost _ (`prd⊗ a₁ b₁) (`prd⊗ a₂ b₂)
    with functionalCheckPost _ a₁ a₂
  ... | refl = functionalCheckPost _ b₁ b₂
  functionalCheckPost _ (`prd& a₁ b₁) (`prd& a₂ b₂) = functionalCheckPost _ a₁ a₂
  functionalCheckPost _ (`inl t₁) (`inl t₂) = functionalCheckPost _ t₁ t₂
  functionalCheckPost _ (`inr t₁) (`inr t₂) = functionalCheckPost _ t₁ t₂
  functionalCheckPost _ (`neu t₁) (`neu t₂) = cong proj₂ $ functionalInferPost _ t₁ t₂

mutual

  functionalInferPre : Functional′ (InferTypingPre TInfer)
  functionalInferPre _ (`var k₁)    (`var k₂)    = functionalFinPre _ k₁ k₂
  functionalInferPre _ (`app t₁ u₁) (`app t₂ u₂)
    with functionalInfer _ t₁ t₂
  ... | refl with functionalCheckPre _ u₁ u₂
  ... | refl with functionalInferPre _ t₁ t₂
  ... | refl = refl
  functionalInferPre _ (`fst t₁) (`fst t₂)
    with functionalInfer _ t₁ t₂
  ... | refl with functionalInferPre _ t₁ t₂
  ... | refl = refl
  functionalInferPre _ (`snd t₁) (`snd t₂)
    with functionalInfer _ t₁ t₂
  ... | refl with functionalInferPre _ t₁ t₂
  ... | refl = refl  
  functionalInferPre _ (`case t₁ return σ of l₁ %% r₁) (`case t₂ return .σ of l₂ %% r₂)
    with functionalInfer _ t₁ t₂
  ... | refl with functionalCheckPre _ r₁ r₂
  ... | refl with functionalInferPre _ t₁ t₂
  ... | refl = refl
  functionalInferPre _ (`exfalso σ₁ t₁) (`exfalso σ₂ t₂)
    with functionalInferPre _ t₁ t₂
  ... | refl = refl
  functionalInferPre _ (`cut t₁) (`cut t₂) = cong _ $ functionalCheckPre _ t₁ t₂

  functionalCheckPre : Functional′ (CheckTypingPre TCheck)
  functionalCheckPre _ (`lam t₁) (`lam t₂) with functionalCheckPre _ t₁ t₂
  ... | refl = refl
  functionalCheckPre _ (`let p₁ ∷= t₁ `in u₁) (`let p₂ ∷= t₂ `in u₂)
    with functionalInfer _ t₁ t₂
  ... | refl with functionalPattern _ p₁ p₂
  ... | refl with functional++ [[ patternContext p₁ ]] refl (functionalCheckPre _ u₁ u₂)
  ... | refl = cong proj₂ $ functionalInferPre _ t₁ t₂
  functionalCheckPre _ `unit `unit = refl
  functionalCheckPre _ (`prd⊗ a₁ b₁) (`prd⊗ a₂ b₂)
    rewrite functionalCheckPre _ b₁ b₂ = functionalCheckPre _ a₁ a₂
  functionalCheckPre _ (`prd& a₁ b₁) (`prd& a₂ b₂) = functionalCheckPre _ a₁ a₂
  functionalCheckPre _ (`inl t₁) (`inl t₂) = functionalCheckPre _ t₁ t₂
  functionalCheckPre _ (`inr t₁) (`inr t₂) = functionalCheckPre _ t₁ t₂
  functionalCheckPre _ (`neu t₁) (`neu t₂) = cong proj₂ $ functionalInferPre _ t₁ t₂