{-# LANGUAGE 
        TypeFamilies,
        MultiParamTypeClasses, 
        TypeSynonymInstances, 
        FlexibleContexts #-}

-- | Functions to add your own opcodes or operations
--
-- Constructor takes in 'String' and list of CsTree's. 'String' represnts 
-- name of opcode\/operation for rendering to csd file. 'CsTree' is an inner 
-- type of csound expression. Any type @a@ that belongs to 'IM' 'CsTree' @a@ 
-- can be converted to 'CsTree' with 'to' method. 

module CsoundExpr.Base.UserDefined
    (IM(..), Opr2(..),  
     opcode, outOpcode, operation,
     infixOperation, prefixOperation, unaryInfixOperation)
where

import CsoundExpr.Translator.Cs.IM
import CsoundExpr.Translator.Cs.CsTree
import CsoundExpr.Translator.ExprTree.ExprTree
import CsoundExpr.Translator.Types


-- | Opcode constructor
--
-- Example :
-- 
-- > ares oscil xamp, xcps, ift, [iphs]
-- > kres oscil kamp, kcps, ift, [iphs]
--
-- > oscilA :: (X a0, X a1) => [Irate] -> a0 -> a1 -> Irate -> Arate
-- > oscilA inits xamp xcps ift = opcode "oscil" args 
-- >     where args = [to xamp, to xcps, to ift] ++ map to inits
--
-- > oscilK :: (K a0, K a1) => [Irate] -> a0 -> a1 -> Irate -> Krate
-- > oscilK inits kamp kcps ift = opcode "oscil" args 
-- >     where args = [to kamp, to kcps, to ift] ++ map to inits
--
--
-- > ares noise xamp, kbeta
--
-- > noise :: (X a, K b) => a -> b -> SideEffect Ares
-- > noise xamp kbeta = opcode "noise" args
-- >    where args = [to xamp, to kbeta]
--
opcode :: IM CsTree a => String -> [CsTree] -> a			
opcode name = from . pure (opc name)

-- | Constructor for opcode that doesn't produce any value 
--
-- Example :
--
-- > outs asig1, asig2
-- 
-- > outs :: Arate -> Arate -> SignalOut
-- > outs asig1 asig2 = outOpcode "outs" args
-- >     where args = [to asig1, to asig2]
--
outOpcode :: String -> [CsTree] -> SignalOut
outOpcode name = SignalOut . return . opcode name

-- | operation constructor
--
-- names can be anywhere between arguments
--
-- > operation names args 
--
-- in csound code becomes
--
-- > names !! 0 ++ show (args !! 0) ++ names !! 1 ++ show (args !! 1) ++ ... 
operation :: IM CsTree a => [String] -> [CsTree] -> a
operation names = from . pure (opr names) 


-- | Infix operation constructor
--
-- Example : 
--
-- > xres = xsig1 + xsig2
-- 
-- > add :: (X a, X b, X (Opr2 a b)) => a -> b -> Opr2 a b
-- > add a b = infixOperation "+" [to a, to b]
--

infixOperation :: IM CsTree a => String -> [CsTree] -> a
infixOperation name = from . pure (opr ["(", name, ")"])
                      
-- | Prefix operation constructor
--
-- Example :
--
-- > xres = sin(xsig)
--
-- > csSin :: X a => a -> a
-- > csSin a = prefixOperation "sin" [to a]
--
prefixOperation :: IM CsTree a => String -> [CsTree] -> a
prefixOperation name = from . pure (opr [name ++ "(", ")"])


unaryInfixOperation :: IM CsTree a => String -> CsTree -> a
unaryInfixOperation name = from . pure (opr ["(" ++ name, ")"]) . return

-- | 'Opr2' @a@ @b@  - defines output type of binary arithmetic operator
--
-- > type instance Opr2 Irate Irate = Irate
-- > type instance Opr2 Irate Krate = Krate
-- > type instance Opr2 Irate Arate = Arate
--
-- > type instance Opr2 Krate Irate = Krate
-- > type instance Opr2 Krate Krate = Krate
-- > type instance Opr2 Krate Arate = Arate
--
-- > type instance Opr2 Arate Irate = Arate
-- > type instance Opr2 Arate Krate = Arate
-- > type instance Opr2 Arate Arate = Arate

type family   Opr2     a     b 
-- Is
type instance Opr2 Irate Irate = Irate
type instance Opr2 Irate Krate = Krate
type instance Opr2 Irate Arate = Arate
 
-- Ks
type instance Opr2 Krate Irate = Krate
type instance Opr2 Krate Krate = Krate
type instance Opr2 Krate Arate = Arate
 
-- As
type instance Opr2 Arate Irate = Arate
type instance Opr2 Arate Krate = Arate
type instance Opr2 Arate Arate = Arate