module linear.Typing.Mix where

open import Data.Fin as F
open import Data.Sum as Sum
open import Data.Product as Prod
open import Function
open import Relation.Binary.PropositionalEquality as Eq hiding ([_])

open import linear.Context as C
open import linear.Context.Pointwise as CP
open import linear.Usage as U hiding ([_])
open import linear.Usage.Pointwise as UP
open import linear.Usage.Mix
open import linear.Language
open import linear.Typing
open import linear.Typing.Extensional

open import linear.Mix
open import linear.Context.Mix hiding (_++ˡ_)
open import linear.Usage.Mix

splitUsages :
  ∀ {m n p} {γ : Context m} {δ : Context n} {θ : Context p}
  (p : γ ++ δ ≅ θ) (Γ : Usages θ) →
  ∃ λ Γ₁ → ∃ λ Γ₂ → [ p ] Γ₁ ++ Γ₂ ≅ Γ
splitUsages []       []      = [] , [] , []
splitUsages (a ∷ˡ p) (A ∷ Γ) =
  let (Γ₁ , Γ₂ , eq) = splitUsages p Γ
  in A ∷ Γ₁ , Γ₂ , A ∷ˡ eq
splitUsages (a ∷ʳ p) (A ∷ Γ) =
  let (Γ₁ , Γ₂ , eq) = splitUsages p Γ
  in Γ₁ , A ∷ Γ₂ , A ∷ʳ eq

unsplitUsages : ∀ {m n p} {γ : Context m} {δ : Context n} {θ : Context p} (p : γ ++ δ ≅ θ)
  (Γ : Usages γ) (Δ : Usages δ) →
  ∃ λ Θ → [ p ] Γ ++ Δ ≅ Θ
unsplitUsages []       []      []      = , []
unsplitUsages (a ∷ˡ p) (A ∷ Γ) Δ       = Prod.map (A ∷_) (A ∷ˡ_) $ unsplitUsages p Γ Δ
unsplitUsages (a ∷ʳ p) Γ       (A ∷ Δ) = Prod.map (A ∷_) (A ∷ʳ_) $ unsplitUsages p Γ Δ

mutual

  mixCheck : Mix TCheck
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ (`lam b) =
    Prod.map `lam_ `lam_ $ mixCheck (_ ∷ˡ eqΓ) (_ ∷ˡ eqΔ) (_ ∷ˡ eqΓ′) (_ ∷ˡ eqΔ′) b
  mixCheck {p = p} {q} eqΓ eqΔ eqΓ′ eqΔ′ (`let pat ∷= t `in u) =
    let (Δ₁  , Δ₂  , eqΔ₁₂) = splitUsages p _
        (Δ′₁₂ , eqΔ′₁₂)     = unsplitUsages q Δ₁ Δ₂
        (t , T)             = mixInfer eqΓ eqΔ₁₂ eqΓ′ eqΔ′₁₂ t
        φ                   = patternContext pat
        (u , U)             = mixCheck ([[ φ ]] ++ˡ eqΔ₁₂)  (]] φ [[ ++ˡ eqΔ)
                                       ([[ φ ]] ++ˡ eqΔ′₁₂) (]] φ [[ ++ˡ eqΔ′) u
    in , `let pat ∷= T `in U
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ `unit =
    let (eqΓΔ₁ , eqΓΔ₂) = ≅-inj _ eqΓ eqΔ
        eqΔ′′           = subst₂ ([ _ ]_++_≅ _) (Eq.sym eqΓΔ₁) (Eq.sym eqΓΔ₂) eqΔ′
        eq              = functionalMix _ eqΓ′ eqΔ′′ 
    in , subst (TCheck _ _ _) eq `unit
  mixCheck {p = p} {q} eqΓ eqΔ eqΓ′ eqΔ′ (`prd⊗ t u) =
    let (Δ₁  , Δ₂  , eqΔ₁₂) = splitUsages p _
        (Δ′₁₂ , eqΔ′₁₂)     = unsplitUsages q Δ₁ Δ₂
        (t , T)             = mixCheck eqΓ eqΔ₁₂ eqΓ′ eqΔ′₁₂ t
        (u , U)             = mixCheck eqΔ₁₂ eqΔ eqΔ′₁₂ eqΔ′ u
    in , `prd⊗ T U
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ (`prd& t u) =
    let (t , T) = mixCheck eqΓ eqΔ eqΓ′ eqΔ′ t
        (u , U) = mixCheck eqΓ eqΔ eqΓ′ eqΔ′ u
    in , `prd& T U
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ (`inl t) = Prod.map `inl_ `inl_ $ mixCheck eqΓ eqΔ eqΓ′ eqΔ′ t
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ (`inr t) = Prod.map `inr_ `inr_ $ mixCheck eqΓ eqΔ eqΓ′ eqΔ′ t
  mixCheck eqΓ eqΔ eqΓ′ eqΔ′ (`neu t) = Prod.map `neu_ `neu_ $ mixInfer eqΓ eqΔ eqΓ′ eqΔ′ t

  mixInfer : Mix TInfer
  mixInfer eqΓ eqΔ eqΓ′ eqΔ′ (`var k) = Prod.map `var_ `var_ $ mixFin eqΓ eqΔ eqΓ′ eqΔ′ k
  mixInfer {p = p} {q} eqΓ eqΔ eqΓ′ eqΔ′ (`app f t) =
    let (Δ₁  , Δ₂  , eqΔ₁₂) = splitUsages p _
        (Δ′₁₂ , eqΔ′₁₂)     = unsplitUsages q Δ₁ Δ₂
        (f , F)             = mixInfer eqΓ eqΔ₁₂ eqΓ′ eqΔ′₁₂ f
        (t , T)             = mixCheck eqΔ₁₂ eqΔ eqΔ′₁₂ eqΔ′ t
    in , `app F T
  mixInfer eqΓ eqΔ eqΓ′ eqΔ′ (`fst p) = Prod.map `fst_ `fst_ $ mixInfer eqΓ eqΔ eqΓ′ eqΔ′ p
  mixInfer eqΓ eqΔ eqΓ′ eqΔ′ (`snd p) = Prod.map `snd_ `snd_ $ mixInfer eqΓ eqΔ eqΓ′ eqΔ′ p
  mixInfer {p = p} {q} eqΓ eqΔ eqΓ′ eqΔ′ (`case t return σ of l %% r) =
    let (Δ₁  , Δ₂  , eqΔ₁₂) = splitUsages p _
        (Δ′₁₂ , eqΔ′₁₂)     = unsplitUsages q Δ₁ Δ₂
        (t , T)             = mixInfer eqΓ eqΔ₁₂ eqΓ′ eqΔ′₁₂ t
        (l , L)             = mixCheck (_ ∷ˡ eqΔ₁₂) (_ ∷ˡ eqΔ) (_ ∷ˡ eqΔ′₁₂) (_ ∷ˡ eqΔ′) l
        (r , R)             = mixCheck (_ ∷ˡ eqΔ₁₂) (_ ∷ˡ eqΔ) (_ ∷ˡ eqΔ′₁₂) (_ ∷ˡ eqΔ′) r
    in , `case T return σ of L %% R
  mixInfer eqΓ eqΔ eqΓ′ eqΔ′ (`exfalso σ t) =
   Prod.map (`exfalso σ) (`exfalso σ) $ mixInfer eqΓ eqΔ eqΓ′ eqΔ′ t
  mixInfer eqΓ eqΔ eqΓ′ eqΔ′ (`cut t) = Prod.map _ `cut $ mixCheck eqΓ eqΔ eqΓ′ eqΔ′ t
