{-# OPTIONS --safe --without-K #-}
open import Prelude

module Generics.Reflection.Uncurry where

open import Utils.Reflection
open import Utils.Error as Err

open import Generics.Telescope
open import Generics.Recursion
open import Generics.Description

open import Generics.Reflection.Telescope

------------------------------------------------------------------------
-- Meta-level uncurrying operations
-- [TODO] Unify all of the following

uncurryDataD : (D : DataD) → Name → TC Term
uncurryDataD D d = let open DataD D in do
  `A ← getType d
  
  let `ℓsΓΔ⋯ , _  = ⇑ `A ⦂ Telescope × Type
  let `ℓs   , ΓΔ⋯ = splitAt #levels `ℓsΓΔ⋯

  exCxtℓs #levels λ ℓs → let open PDataD (applyL ℓs) in do
    Γ , Δ⋯ ← Param ⊆ᵗ? ΓΔ⋯
    exCxtTel Param λ ps → let Index = Index ps in do
      Δ , _ ← Index ⊆ᵗ? Δ⋯

      let |Γ| = length Γ ; |Δ| = length Δ 
      
      let (_ , p₁) , (as₁ , _) = cxtToVars (|Γ| + |Δ|) (`tt , `tt) `ℓs
      (_ , p₂) , (as₂ , _) ← telToVars |Δ| (`tt , `tt) Param Γ
      (_ , p₃) , (as₃ , _) ← telToVars 0   (`tt , `tt) Index Δ

      return $ pat-lam₀ $
        (forgetTypes (`ℓs <> Γ <> Δ) ⊢ vArg p₁ ∷ vArg p₂ ∷ [ vArg p₃ ] `= def d (as₁ <> as₂ <> as₃))
        ∷ []

uncurryFoldP : (P : FoldP) → Name → TC Term
uncurryFoldP P d = let open FoldP P in do
  `A ← getType d
  let `ℓsΓΔ⋯ , _   = ⇑ `A ⦂ Telescope × Type
  let `ℓs    , ΓΔ⋯ = splitAt #levels `ℓsΓΔ⋯
  exCxtℓs #levels λ ℓs → do
    let PsTel = Param ℓs
    Γ , Δ⋯ ← PsTel ⊆ᵗ? ΓΔ⋯
    exCxtTel PsTel λ ps → do
      let Index = PDataD.Index (DataD.applyL Desc (level ℓs)) (param ps)
      Δ , _ ← Index ⊆ᵗ? Δ⋯ 

      let |Γ| = length Γ ; |Δ| = length Δ
      let ((_ , p₁) , as₁ , _) = cxtToVars (|Γ| + |Δ|) (`tt , `tt) `ℓs
      (_ , p₂) , (as₂ , _) ← telToVars |Δ| (`tt , `tt) PsTel Γ
      (_ , p₃) , (as₃ , _) ← telToVars 0 (`tt , `tt) Index Δ
      return $ pat-lam₀ $
        ((`ℓs <> Γ <> Δ) ⊢ vArg p₁ ∷ vArg p₂ ∷ hArg p₃ ∷ [] `= def d (as₁ <> as₂ <> as₃) )
        ∷ []

uncurryIndP : (P : IndP) → Name → TC Term
uncurryIndP P d = let open IndP P in do
  `A ← getType d
  let `ℓsΓΔ⋯ , _   = ⇑ `A ⦂ Telescope × Type
  let `ℓs    , ΓΔ⋯ = splitAt #levels `ℓsΓΔ⋯
  exCxtℓs #levels λ ℓs → do
    let PsTel = Param ℓs
    Γ , Δ⋯ ← PsTel ⊆ᵗ? ΓΔ⋯
    exCxtTel PsTel λ ps → do
      let Index = PDataD.Index (DataD.applyL Desc (level ℓs)) (param ps)
      Δ , _ ← Index ⊆ᵗ? Δ⋯ 

      let |Γ| = length Γ ; |Δ| = length Δ
      let ((_ , p₁) , as₁ , _) = cxtToVars (|Γ| + |Δ|) (`tt , `tt) `ℓs
      (_ , p₂) , (as₂ , _) ← telToVars |Δ| (`tt , `tt) PsTel Γ
      (_ , p₃) , (as₃ , _) ← telToVars 0 (`tt , `tt) Index Δ
      return $ pat-lam₀ $
        ((`ℓs <> Γ <> Δ) ⊢ vArg p₁ ∷ vArg p₂ ∷ hArg p₃ ∷ [] `= def d (as₁ <> as₂ <> as₃) )
        ∷ []

genDataTT : (D : DataD) → Name → Tactic
genDataTT D d hole = do
  `D ← quoteωTC D
--    t ← uncurryDataD D d
--    defineUnify "_" (def₁ (quote DataT) `D) t hole
  checkType hole (def₁ (quote DataT) `D)
  uncurryDataD D d >>= unify hole

genFoldTT : (P : FoldP) → Name → Tactic
genFoldTT P d hole = do
  `P ← quoteωTC P
--    t ← uncurryFoldP P d
--    defineUnify "_" (def₁ (quote FoldT) `P) t hole
  checkType hole (def₁ (quote FoldT) `P)
  uncurryFoldP P d >>= unify hole

genIndTT : (P : IndP) → Name → Tactic
genIndTT P d hole = do
  `P ← quoteωTC P
--  t ← uncurryIndP P d
--  defineUnify (vArg "_") (def₁ (quote IndT) `P) t hole
  checkType hole (def₁ (quote IndT) `P)
  uncurryIndP P d >>= unify hole

macro
  genDataT = genDataTT
  genFoldT = genFoldTT
  genIndT  = genIndTT