module Eventloop.Module.Timer.Timer
    ( setupTimerModuleConfiguration
    , timerModuleIdentifier
    , timerInitializer
    , timerEventRetriever
    , timerEventSender
    , timerTeardown
    ) where

import Control.Concurrent.Datastructures.BlockingConcurrentQueue
import Control.Concurrent.STM
import Control.Concurrent.Timer
import Control.Concurrent.Suspend.Lifted
import Data.Maybe
import Data.List

import Eventloop.Module.Timer.Types
import Eventloop.Types.Common
import Eventloop.Types.Events
import Eventloop.Types.System


setupTimerModuleConfiguration :: EventloopSetupModuleConfiguration
setupTimerModuleConfiguration = ( EventloopSetupModuleConfiguration
                                        timerModuleIdentifier
                                        (Just timerInitializer)
                                        (Just timerEventRetriever)
                                        Nothing
                                        Nothing
                                        (Just timerEventSender)
                                        (Just timerTeardown)
                                 )

timerModuleIdentifier :: EventloopModuleIdentifier
timerModuleIdentifier = "timer"


timerInitializer :: Initializer
timerInitializer sharedConst sharedIO
    = do
        inQueue <- createBlockingConcurrentQueue
        return (sharedConst, sharedIO, TimerConstants inQueue, TimerState [] [])


timerEventRetriever :: EventRetriever
timerEventRetriever sharedConst sharedIOT ioConst ioStateT
    = do
        inTicks <- takeAllFromBlockingConcurrentQueue inQueue
        ioState <- readTVarIO ioStateT -- This first read is just a snapshot
        let
            toStop = map (\(Tick id) -> id) inTicks
            startedTimers_  = startedTimers ioState
        sequence $ map (haltTimer startedTimers_) toStop
        atomically $ do
            ioState' <- readTVar ioStateT
            let
                startedTimers_'  = startedTimers ioState'
                startedTimers_'' = foldl unregisterTimer startedTimers_' toStop
            writeTVar ioStateT (ioState'{startedTimers = startedTimers_'})

        return (map InTimer inTicks)
    where
        inQueue = tickInQueue ioConst


timerEventSender :: EventSender
timerEventSender _ _ _ _ Stop = return ()
timerEventSender sharedConst sharedIOT ioConst ioStateT (OutTimer a)
    = timerEventSender' ioStateT tickBuffer a
    where
        tickBuffer = tickInQueue ioConst


timerEventSender' :: TVar IOState -> TickBuffer -> TimerOut -> IO ()
timerEventSender' ioStateT tickBuffer (SetTimer id delay)
    = do
        startedTimer <- startTimer tickBuffer id delay (oneShotStart)
        atomically $ do
            ioState <- readTVar ioStateT
            let
                startedTimers_' = registerTimer (startedTimers ioState) startedTimer
            writeTVar ioStateT ioState{startedTimers=startedTimers_'}

timerEventSender' ioStateT tickBuffer (SetIntervalTimer id delay)
    = do
        startedTimer <- startTimer tickBuffer id delay (repeatedStart)
        atomically $ do
            ioState <- readTVar ioStateT
            let
                startedITimers_' = registerTimer (startedIntervalTimers ioState) startedTimer
            writeTVar ioStateT ioState{startedIntervalTimers=startedITimers_'}

timerEventSender' ioStateT tickBuffer (UnsetTimer id)
    = do
        ioState <- readTVarIO ioStateT -- This first read is just a snapshot
        let
            startedTimers_  = startedTimers ioState
            startedITimers_ = startedIntervalTimers ioState
        haltTimer startedTimers_ id
        haltTimer startedITimers_ id
        atomically $ do
            ioState' <- readTVar ioStateT
            let
                startedTimers_'  = startedTimers ioState'
                startedITimers_' = startedIntervalTimers ioState'
            writeTVar ioStateT ioState'{ startedTimers = startedTimers_'
                                       , startedIntervalTimers = startedITimers_'
                                       }


timerTeardown :: Teardown
timerTeardown sharedConst sharedIO ioConst ioState
    = do
        let
            allStartedTimers = (startedTimers ioState) ++ (startedIntervalTimers ioState)
            allStartedIds = map fst allStartedTimers
        sequence_ $ map (haltTimer allStartedTimers) allStartedIds
        return sharedIO


registerTimer :: [StartedTimer] -> StartedTimer -> [StartedTimer]
registerTimer startedTimers startedTimer
    = startedTimers ++ [startedTimer]


startTimer :: TickBuffer -> TimerId -> MicroSecondDelay -> TimerStartFunction -> IO StartedTimer
startTimer incTickBuff id delay startFunc
    = do
        timer <- newTimer
        startFunc timer (tick id incTickBuff) ((usDelay.fromIntegral) delay)
        return (id, timer)


unregisterTimer :: [StartedTimer] -> TimerId -> [StartedTimer]
unregisterTimer startedTimers id
    = filter (\(id', _) -> id /= id') startedTimers


haltTimer :: [StartedTimer] -> TimerId -> IO ()
haltTimer startedTimers id
    = do
        let
            startedTimerM = findStartedTimer startedTimers id
            stopAction (Just (_, timer)) = stopTimer timer
            stopAction Nothing           = return ()
        stopAction startedTimerM


findStartedTimer :: [StartedTimer] -> TimerId -> Maybe StartedTimer
findStartedTimer startedTimers id = find (\(id', timer) -> id == id') startedTimers


tick :: TimerId -> TickBuffer -> IO ()
tick id tickBuffer = putInBlockingConcurrentQueue tickBuffer (Tick id)