module Csound.Exp.Cons (
    Spec1, Specs,
    toE, fromE,
    withInits,
    bi,
    opcs, opc0, opc1, opc2, opc3, opc4, opc5, opc6, opc7, opc8, opc9, opc10, opc11,
    mopcs, mopc0, mopc1, mopc2, mopc3, mopc4, mopc5, mopc6, mopc7
) where

import Data.String
import Control.Applicative
import Data.Default
import qualified Data.Map as M
import Control.Monad.Trans.State

import Data.Fix

import Csound.Exp
import Csound.Exp.Wrapper

-- | Converts a value to the private representation.
toE :: Val a => a -> E
toE = Fix . unwrap

-- | Constructs a value from the private representation.
fromE :: Val a => E -> a
fromE = wrap . unFix

-- | Appends initialisation arguments. It's up to you to supply arguments with the right types. For example:
--
-- > oscil 0.5 440 sinWave `withInits` (0.5 :: D)
withInits :: (Val a, CsdTuple inits) => a -> inits -> Sig
withInits a b = wrap $ onExp phi $ unwrap a
    where phi x = case x of
            Tfm t xs -> Tfm t (xs ++ fromCsdTuple b)
            x        -> x

------------------------------------------------
-- helper constructors

instance IsString Str where
    fromString = str

------------------------------------------------
-- constructor for simple arithmetic operators

bi :: (Val a1, Val a2, Val b) => Name -> a1 -> a2 -> b
bi name = opc2 name biSignature

biSignature :: Spec1
biSignature = [
    (Ar, [Ar, Ar]),
    (Kr, [Kr, Kr]),
    (Ir, [Ir, Ir])]

idSignature :: Spec1
idSignature = [
    (Ar, repeat Ar),
    (Kr, repeat Kr),
    (Ir, repeat Ir)]

----------------------------
-- spec tfms

tfms :: (Val a, Val b) => Info -> [a] -> b
tfms t as = tfm t $ map unwrap as

tfm0 :: (Val a) => Info -> a
tfm0 t = tfm t []

tfm1 :: (Val a, Val b) => Info -> a -> b
tfm1 t a = tfm t [unwrap a]

tfm2 :: (Val a1, Val a2, Val b) => Info -> a1 -> a2 -> b
tfm2 t a1 a2 = tfm t [unwrap a1, unwrap a2]

tfm3 :: (Val a1, Val a2, Val a3, Val b) => Info -> a1 -> a2 -> a3 -> b
tfm3 t a1 a2 a3 = tfm t [unwrap a1, unwrap a2, unwrap a3]

tfm4 :: (Val a1, Val a2, Val a3, Val a4, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> b
tfm4 t a1 a2 a3 a4 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4]

tfm5 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> b
tfm5 t a1 a2 a3 a4 a5 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5]

tfm6 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> b
tfm6 t a1 a2 a3 a4 a5 a6 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6]

tfm7 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> b
tfm7 t a1 a2 a3 a4 a5 a6 a7 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6, unwrap a7]

tfm8 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> b
tfm8 t a1 a2 a3 a4 a5 a6 a7 a8 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6, unwrap a7, unwrap a8]

tfm9 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> b
tfm9 t a1 a2 a3 a4 a5 a6 a7 a8 a9 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6, unwrap a7, unwrap a8, unwrap a9]

tfm10 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val a10, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10 -> b
tfm10 t a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6, unwrap a7, unwrap a8, unwrap a9, unwrap a10]

tfm11 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val a10, Val a11, Val b) => Info -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10 -> a11 -> b
tfm11 t a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 = tfm t [unwrap a1, unwrap a2, unwrap a3, unwrap a4, unwrap a5, unwrap a6, unwrap a7, unwrap a8, unwrap a9, unwrap a10, unwrap a11]

-------------------------------
-- single out

type Spec1 = [(Rate, [Rate])]

spec1 :: Spec1 -> Signature
spec1 = SingleRate . M.fromList

opcs :: (Val a, Val b) => Name -> Spec1 -> [a] -> b
opcs name signature = tfms (pref name $ spec1 signature)

opc0 :: (Val a) => Name -> Spec1 -> a
opc0 name signature = tfm0 (pref name $ spec1 signature)

opc1 :: (Val a, Val b) => Name -> Spec1 -> a -> b
opc1 name signature = tfm1 (pref name $ spec1 signature)

opc2 :: (Val a1, Val a2, Val b) => Name -> Spec1 -> a1 -> a2 -> b
opc2 name signature = tfm2 (pref name $ spec1 signature)

opc3 :: (Val a1, Val a2, Val a3, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> b
opc3 name signature = tfm3 (pref name $ spec1 signature)

opc4 :: (Val a1, Val a2, Val a3, Val a4, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> b
opc4 name signature = tfm4 (pref name $ spec1 signature)

opc5 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> b
opc5 name signature = tfm5 (pref name $ spec1 signature)

opc6 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> b
opc6 name signature = tfm6 (pref name $ spec1 signature)

opc7 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> b
opc7 name signature = tfm7 (pref name $ spec1 signature)

opc8 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> b
opc8 name signature = tfm8 (pref name $ spec1 signature)

opc9 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> b
opc9 name signature = tfm9 (pref name $ spec1 signature)

opc10 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val a10, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10 -> b
opc10 name signature = tfm10 (pref name $ spec1 signature)

opc11 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, Val a8, Val a9, Val a10, Val a11, Val b) => Name -> Spec1 -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> a8 -> a9 -> a10 -> a11 -> b
opc11 name signature = tfm11 (pref name $ spec1 signature)

-------------------------------
-- multiple outs

type Specs = ([Rate], [Rate])

specs :: Specs -> Signature
specs = uncurry MultiRate 

mo :: (CsdTuple a) => E -> a
mo = multiOuts

mopcs :: (Val a, CsdTuple b) => Name -> Specs -> [a] -> b
mopcs name signature as = mo $ tfms (pref name $ specs signature) as

mopc0 :: (CsdTuple a) => Name -> Specs -> a
mopc0 name signature = mo $ tfm0 (pref name $ specs signature)

mopc1 :: (Val a, CsdTuple b) => Name -> Specs -> a -> b
mopc1 name signature a1 = mo $ tfm1 (pref name $ specs signature) a1

mopc2 :: (Val a1, Val a2, CsdTuple b) => Name -> Specs -> a1 -> a2 -> b
mopc2 name signature a1 a2 = mo $ tfm2 (pref name $ specs signature) a1 a2

mopc3 :: (Val a1, Val a2, Val a3, CsdTuple b) => Name -> Specs -> a1 -> a2 -> a3 -> b
mopc3 name signature a1 a2 a3 = mo $ tfm3 (pref name $ specs signature) a1 a2 a3

mopc4 :: (Val a1, Val a2, Val a3, Val a4, CsdTuple b) => Name -> Specs -> a1 -> a2 -> a3 -> a4 -> b
mopc4 name signature a1 a2 a3 a4 = mo $ tfm4 (pref name $ specs signature) a1 a2 a3 a4

mopc5 :: (Val a1, Val a2, Val a3, Val a4, Val a5, CsdTuple b) => Name -> Specs -> a1 -> a2 -> a3 -> a4 -> a5 -> b
mopc5 name signature a1 a2 a3 a4 a5 = mo $ tfm5 (pref name $ specs signature) a1 a2 a3 a4 a5

mopc6 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, CsdTuple b) => Name -> Specs -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> b
mopc6 name signature a1 a2 a3 a4 a5 a6 = mo $ tfm6 (pref name $ specs signature) a1 a2 a3 a4 a5 a6

mopc7 :: (Val a1, Val a2, Val a3, Val a4, Val a5, Val a6, Val a7, CsdTuple b) => Name -> Specs -> a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> b
mopc7 name signature a1 a2 a3 a4 a5 a6 a7 = mo $ tfm7 (pref name $ specs signature) a1 a2 a3 a4 a5 a6 a7