module Data.MovingAverage.Types
    ( SmoothedResults(..)
    , SmoothedResult(..)
    , MovingAverageError(..)
    , buildSimpleMovingAverage
    , buildSingleExponentialMovingAverage
    , buildDoubleExponentialMovingAverage
    ) where

newtype Window = Window Int deriving Eq
newtype Alpha a = Alpha a deriving Eq
newtype Beta b = Beta b deriving Eq

data GraphType a
    = SimpleMovingAverage Window
    | SingleExponentialMovingAverage (Alpha a)
    | DoubleExponentialMovingAverage (Alpha a) (Beta a)
    deriving Eq

instance Show a => Show (GraphType a) where
    show (SimpleMovingAverage (Window w)) = "SMA(" ++ show w ++ ")"
    show (SingleExponentialMovingAverage (Alpha a)) = "SEMA(" ++ show a ++ ")"
    show (DoubleExponentialMovingAverage (Alpha a) (Beta b)) = "DEMA(" ++ show a ++ ", " ++ show b ++ ")"

data SmoothedResults a = SmoothedResults
    { srsGraphType :: GraphType a
    , srsResults :: [SmoothedResult a]
    , srsSumSquaredErrors :: a
    , srsMeanSquaredErrors :: a
    } deriving (Eq, Show)

data SmoothedResult a = SmoothedResult
    { srValue :: a
    , srSmoothedValue :: a
    , srError :: a
    , srErrorSquared :: a
    } deriving (Eq, Show)

data MovingAverageError
    = InvalidAlphaValue String
    | InvalidBetaValue String
    | NoValuesProvided
    | InvalidWindow String
    deriving (Eq, Show)

buildSimpleMovingAverage :: Floating a => Int -> [(a, a)] -> SmoothedResults a
buildSimpleMovingAverage w = buildResults (SimpleMovingAverage (Window w))

buildSingleExponentialMovingAverage :: Floating a => a -> [(a, a)] -> SmoothedResults a
buildSingleExponentialMovingAverage a = buildResults (SingleExponentialMovingAverage (Alpha a))

buildDoubleExponentialMovingAverage :: Floating a => a -> a -> [(a, a)] -> SmoothedResults a
buildDoubleExponentialMovingAverage a b = buildResults (DoubleExponentialMovingAverage (Alpha a) (Beta b))

buildResults :: Floating a => GraphType a -> [(a, a)] -> SmoothedResults a
buildResults g = buildSmoothedResults g . map (uncurry buildSmoothedResult)

buildSmoothedResults :: Floating a => GraphType a -> [SmoothedResult a] -> SmoothedResults a
buildSmoothedResults g xs = SmoothedResults g xs sumSquaredErrors meanSquaredErrors
  where
    sumSquaredErrors = sum squaredErrors
    meanSquaredErrors = sumSquaredErrors / fromIntegral (length squaredErrors)
    squaredErrors = map srErrorSquared xs

buildSmoothedResult :: Floating a => a -> a -> SmoothedResult a
buildSmoothedResult v smoothed =
    SmoothedResult v smoothed smoothedError smoothedErrorSquared
  where
    smoothedError = v - smoothed
    smoothedErrorSquared = smoothedError ** 2