module Reactive.Banana.ALSA.Time where

import qualified Sound.ALSA.Sequencer.Event as Event
import qualified Sound.ALSA.Sequencer.RealTime as RealTime
import qualified Sound.ALSA.Sequencer.Time as ATime

import qualified Numeric.NonNegative.Class as NonNeg

import qualified Data.Monoid as Mn
import Data.Ratio ((%), )

import Prelude hiding (div, )

{- |
The 'T' types are used instead of floating point types,
because the latter ones caused unpredictable 'negative number' errors.
The denominator must always be a power of 10,
this way we can prevent unlimited grow of denominators.
-}
type Abs = Rational
newtype T = Cons {decons :: Rational}
   deriving (Show, Eq, Ord)

cons :: String -> Rational -> T
cons msg x =
   if x>=0
     then Cons x
     else error $ msg ++ ": negative number"

inc :: T -> Abs -> Abs
inc dt t = t + decons dt

subSat :: Abs -> Abs -> T
subSat t1 t0 = cons "Time.sub" $ max 0 $ t1 - t0

scale :: Double -> T -> T
scale k (Cons t) =
   cons "Time.scale" $ round (toRational k * t * nano) % nano

scaleCeiling :: Double -> T -> T
scaleCeiling k (Cons t) =
   cons "Time.scaleCeiling" $ ceiling (toRational k * t * nano) % nano

scaleInt :: Integral i => i -> T -> T
scaleInt k (Cons t) =
   cons "Time.scaleInt" $ t * fromIntegral k

div :: T -> T -> Double
div dt1 dt0 =
   fromRational (decons dt1 / decons dt0)

nano :: Num a => a
nano = 1000^(3::Int)

instance Mn.Monoid T where
   mempty = Cons 0
   mappend (Cons x) (Cons y) = Cons (x+y)

instance NonNeg.C T where
   split = NonNeg.splitDefault decons Cons


fromStamp :: ATime.Stamp -> Abs
fromStamp t =
   case t of
      ATime.Real rt ->
         RealTime.toInteger rt % nano
--      _ -> 0,
      _ -> error "unsupported time stamp type"

toStamp :: Abs -> ATime.Stamp
toStamp t =
   ATime.Real (RealTime.fromInteger (round (t*nano)))


fromEvent :: Event.T -> Abs
fromEvent ev =
   case Event.time ev of
      ATime.Cons ATime.Absolute stamp -> fromStamp stamp
      _ -> error "timeFromEvent: we can only handle absolute time stamps"