module FRP.Elerea.SDL (sdlLoop, Ticks) where

import Data.Word (Word32)
import System.IO.Unsafe
import Control.Concurrent (threadDelay)

import FRP.Elerea.Param
import qualified Graphics.UI.SDL as SDL

-- | SDL Ticks
type Ticks = Word32

-- | Main SDL event loop (with framerate)
--
-- Produces an "infinite" list of network samples
sdlLoop ::
	Ticks
	-- ^ Frame duration / Frame time
	-> (SignalGen p (Signal [SDL.Event]) -> SignalGen Ticks (Signal a))
	-- ^ 'Signal' network, takes event 'SignalGen' as argument
	-> IO [a]
sdlLoop frameTime network = do
	(events, signalEvents) <- externalMulti
	sampleNetwork <- start (network events)
	now <- SDL.getTicks
	sdlLoop' signalEvents sampleNetwork frameTime now

sdlLoop' ::
	(SDL.Event -> IO ()) -- ^ IO action to signal an event
	-> (Ticks -> IO a)   -- ^ IO action to sample the network
	-> Ticks             -- ^ Frame duration / Frame time
	-> Ticks             -- ^ Current SDL time (getTicks)
	-> IO [a]
sdlLoop' signalEvents sampleNetwork frameTime = loop frameTime
	where
	loop timeLeft time = do
		(event, (left, nextTime)) <- waitEventTimeout (timeLeft, time)
		case event of
			(Just e) -> signalEvents e >> loop left nextTime
			Nothing -> do
				x <- sampleNetwork nextTime
				xs <- unsafeInterleaveIO (loop frameTime nextTime)
				return (x : xs)

-- | Turns out, SDL just does poll-and-wait internally anyway
--   This wait can time out, which is useful for drawing
waitEventTimeout :: (Ticks,Ticks) -> IO (Maybe SDL.Event, (Ticks,Ticks))
waitEventTimeout (initialLeft, lastTime) = do
	SDL.pumpEvents
	e <- SDL.pollEvent
	case e of
		SDL.NoEvent -> do
			now <- SDL.getTicks
			loop (initialLeft `sub` (now-lastTime)) now
		_ -> return (Just e, (initialLeft, lastTime))
	where
	loop 0 _ = do
		timeoutNow <- SDL.getTicks
		return (Nothing, (0, timeoutNow))
	loop _ now = do
		SDL.pumpEvents
		e <- SDL.pollEvent
		case e of
			SDL.NoEvent -> do
				threadDelay 10000
				eventNow <- SDL.getTicks
				loop (initialLeft `sub` (eventNow - now)) now
			_ -> do
				eventNow <- SDL.getTicks
				return (Just e, (initialLeft `sub` (eventNow - now), eventNow))
	sub t n
		| t > n = t - n
		| otherwise = 0