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

-- | Arithmetic operators

module CsoundExpr.Base.Arithmetic 
	(Opr2, (<+>), neg, (<->), (<*>), (</>), (<^>), (<%>), (^*), (^+))
where

import Prelude hiding (div)

import Control.Applicative(liftA2, (<$>))
import Data.Function(on)

import CsoundExpr.Translator.Types 
import CsoundExpr.Translator.Cs.CsTree
import CsoundExpr.Translator.ExprTree.ExprTree
import CsoundExpr.Base.UserDefined

import CsoundExpr.Base.Types (Irate(..), Krate(..), Arate(..))

import CsoundExpr.Base.Boolean

infixr 9  ^* 
infixr 8  ^+, <^>
infixr 7  <*>, </>
infixr 6  <+>, <->, <%>


subst :: (IM d a, IM d b, IM d c) => (d -> d -> c) -> (a -> b -> c)
subst f x y = f (to x) (to y)

opr1 :: IM CsTree a => Name -> (Double -> Double) -> CsTree -> a
opr1 name fun a = maybe (unaryInfixOperation name a) 
			from (optim1 fun a)

opr1p :: IM CsTree a => Name -> (Double -> Double) -> CsTree -> a
opr1p name fun a = maybe (prefixOperation name $ return a)
			 from (optim1 fun a)	

opr2 :: IM CsTree a => Name -> (Double -> Double -> Double) -> CsTree -> CsTree -> a
opr2 name fun a b = maybe (infixOperation name [a, b])
			  from (optim2 fun a b)

optim1 :: (Double -> Double) -> CsTree -> Maybe CsTree
optim1 fun a
	| isVal a'  = fmap (double . fun) $ toDouble $ value a'
 	| otherwise = Nothing
	where a' = exprOp $ exprTreeTag a

optim2 :: (Double -> Double -> Double) -> CsTree -> CsTree -> Maybe CsTree
optim2 fun a b  
	| isVal a' && isVal b' = fmap double $
				 (liftA2 fun `on` toDouble . value) a' b' 
	| otherwise            = Nothing
	where a' = exprOp $ exprTreeTag a 
	      b' = exprOp $ exprTreeTag b	


----------------------------------------------------------
-- Type inference

(<+>), (<->), (<*>), (</>) :: (X a, X b, X (Opr2 a b)) => a -> b -> Opr2 a b

(<+>) = subst $ opr2 "+" (+)
(<->) = subst $ opr2 "-" (-)
(<*>) = subst $ opr2 "*" (*)
(</>) = subst $ opr2 "/" (/)

-- | \"power of\" operator
(<^>) :: (X a, X b, X (Opr2 a b)) => a -> b -> Opr2 a b
(<^>) = subst $ opr2 "^" (**)

-- | modulus operator
(<%>) :: (X a, X b, X (Opr2 a b)) => a -> b -> Opr2 a b
(<%>) = subst $ opr2 "%" modDouble

-- | negation
neg :: (X a) => a -> a
neg = opr1 "-" negate . to

-- | scaling
(^*) :: X a => Irate -> a -> a
(^*) = subst $ opr2 "*" (*)

-- | shifting
(^+) :: X a => Irate -> a -> a
(^+) = subst $ opr2 "+" (+)

modDouble :: Double -> Double -> Double
modDouble a b = signum a * until ( < b') (+ (-b')) a'
    where a' = abs a
          b' = abs b

----------------------------------------------------------
-- Num instances
--

instance Eq Arate where
    (==) = error "(==) is undefined"
    (/=) = error "(/=) is undefined"

instance Eq Krate where
    (==) = error "(==) is undefined"
    (/=) = error "(/=) is undefined"

instance Eq Irate where
    (==) = error "(==) is undefined"
    (/=) = error "(/=) is undefined"



instance Num Arate where
    (+) = (<+>)
    (*) = (<*>)
    abs = opr1p "abs" abs . to
    signum x = ifB (krate x >* num 0) 1 $ ifB (krate x <* num 0) (-1) 0
    (-) = (<->)
    fromInteger = double . fromInteger


instance Fractional Arate where
    (/) = (</>)
    fromRational = double . fromRational


instance Num Krate where
    (+) = (<+>)
    (*) = (<*>)
    abs = opr1p "abs" abs . to
    signum x = ifB (x >* num 0) 1 $ ifB (x <* num 0) (-1) 0 
    (-) = (<->)
    fromInteger = double . fromInteger


instance Fractional Krate where
    (/) = (</>)
    fromRational = double . fromRational


instance Num Irate where
    (+) = (<+>)
    (*) = (<*>)
    abs = opr1p "abs" abs . to
    signum x = ifB (x >* num 0) 1 $ ifB (x <* num 0) (-1) 0 
    (-) = (<->)
    fromInteger = double . fromInteger

num :: Irate -> Irate
num = id

instance Fractional Irate where
    (/) = (</>)
    fromRational = double . fromRational

--------------------------------------------------
--