module Data.Yarr.Utils.FixedVector.VecTuple (
    VecTuple(..), makeVecTupleInstance
) where

import Language.Haskell.TH

import Data.Vector.Fixed (Dim(..), Arity(..), Fun(..), Vector(..))
import Data.Vector.Fixed.Internal (arity)


data family VecTuple n e

funD' name cs =
    let fd = funD name cs
        inline = pragInlD name Inline ConLike AllPhases
    in [fd, inline]

makeVecTupleInstance arityType a = do

    let n = arity a
        ns = show n
        mkN name = mkName $ name ++ "_" ++ ns
        
        vtConName = mkN "VT"
        vtCon = conE vtConName

        e = varT $ mkName "e"
        tupleType = foldl appT (tupleT n) $ replicate n e

    familyInst <-
        newtypeInstD
            (cxt [])
            ''VecTuple
            [arityType, e]
            (recC vtConName
                  [varStrictType
                    (mkN "toTuple")
                    (strictType notStrict tupleType)])
            []

    let vn = (conT ''VecTuple) `appT` arityType
        vt = vn `appT` e

    dimInst <- tySynInstD ''Dim [vn] arityType
        
    let as = [mkName $ "a" ++ (show i) | i <- [1..n]]
        pas = fmap varP as
        eas = fmap varE as

        constructF = funD'
            'construct
            [clause
                []
                (normalB $ appE (conE 'Fun) $ parensE $
                    lamE pas (appE vtCon (tupE eas)))
                []]

        instP = conP vtConName [tupP pas]
        fn = mkName "f"
        inspectF = funD'
            'inspect
            [clause
                [instP, conP 'Fun [varP fn]]
                (normalB $ foldl appE (varE fn) eas)
                []]

    vectorInst <-
        instanceD
            (cxt [])
            ((conT ''Vector) `appT` vn `appT` e)
            (constructF ++ inspectF)


    let selectNames =
            [mkName $ "sel_" ++ ns ++ "_" ++ (show i) | i <- [1..n]]
        makeSelect i = funD'
            (selectNames !! (i - 1))
            [clause [instP] (normalB $ eas !! (i - 1)) []]

    selectDs <- sequence $ concat $ map makeSelect [1..n]


    return $ [familyInst, dimInst, vectorInst] ++ selectDs