{-# LANGUAGE RecordWildCards #-}
module Affection
    ( withAffection
    , getAffection
    , putAffection
    -- , withWindow
    -- , withDefaultWindow
    , delaySec
    , get
    , put
    , module A
    ) where

import qualified SDL
import qualified SDL.Internal.Numbered as SDL (toNumber)
import qualified SDL.Raw as Raw
import qualified GEGL as G

import Data.Maybe
import Data.IORef

import System.Clock

import Control.Monad.Loops
import Control.Monad.State

import Foreign.C.Types (CInt(..))
import Foreign.Storable (peek)

import Debug.Trace

import Affection.Types as A
import Affection.Draw as A
import Affection.Particle as A

import qualified BABL as B

-- | Main function which bootstraps everything else.
withAffection
  :: AffectionConfig us -- ^ Configuration of the Game and its engine.
  -> IO ()
withAffection AffectionConfig{..} = do
  case initComponents of
    All ->
      SDL.initializeAll
    Only is ->
      SDL.initialize is
  G.gegl_init
  execTime <- newIORef =<< getTime Monotonic
  window <- SDL.createWindow windowTitle windowConfig
  -- let surface = (flip SDL.Surface Nothing) rawSurfacePtr
  (oldSurf, surface) <- getSurfaces window
  let (SDL.Surface ptr _) = surface
  rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0
  pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr
  SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface
  let (w, h) = (fromIntegral rw, fromIntegral rh)
      stride = (fromIntegral $ Raw.pixelFormatBytesPerPixel pixelFormat) * w
  pixels <- SDL.surfacePixels $ surface
  let bablFormat = B.PixelFormat B.RGBA B.CFu8
      cpp        = B.babl_components_per_pixel bablFormat
  format <- B.babl_format bablFormat
  initContainer <- (\x -> AffectionData
    { quitEvent       = False
    , userState       = x
    , drawWindow      = window
    , windowSurface   = oldSurf
    , drawSurface     = surface
    , drawFormat      = format
    , drawPixels      = pixels
    , drawDimensions  = (w, h)
    , drawStride      = stride
    , drawCPP         = cpp
    , drawStack       = []
    , elapsedTime     = 0
    }) <$> loadState surface
  (_, nState) <- runStateT ( A.runState $ do
    preLoop
    pad <- get
    liftIO $ SDL.surfaceBlit (drawSurface pad) Nothing (windowSurface pad) Nothing
    whileM_ (do
        current <- get
        return $ not $ A.quitEvent current
      )
      (do
        -- Measure time difference form last run
        now      <- liftIO $ getTime Monotonic
        lastTime <- liftIO $ readIORef execTime
        -- get state
        ad <- get
        -- clean draw requests from last run
        mapM_ (invalidateDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad)
        -- compute dt and update elapsedTime
        let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9)
        put $ ad
          { drawStack = []
          , elapsedTime = elapsedTime ad + dt
          }
        -- poll events
        evs <- preHandleEvents =<< liftIO SDL.pollEvents
        -- execute user defined update loop
        updateLoop dt evs
        -- execute user defined draw loop
        drawLoop
        -- handle all new draw requests
        ad2 <- get
        clear <- catMaybes <$>
          mapM (handleDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad2)
        -- save all draw requests to clear in next run
        put $ ad2
          { drawStack = clear }
        -- blit surface and update window
        liftIO $ SDL.surfaceBlit
          (drawSurface ad2)
          Nothing
          (windowSurface ad2)
          Nothing
        liftIO $ SDL.updateWindowSurface $ drawWindow ad2
        -- save new time
        liftIO $ writeIORef execTime $ now
      )
    ) initContainer
  G.gegl_exit
  cleanUp $ userState nState
  SDL.quit

getSurfaces :: SDL.Window -> IO (SDL.Surface, SDL.Surface)
getSurfaces window = do
  oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window
  rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0
  let surface = (flip SDL.Surface Nothing) rawSurfacePtr
  return (oldSurf, surface)

preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event]
preHandleEvents evs =
  mapM handle evs
  where
    handle e =
      case SDL.eventPayload e of
        SDL.WindowMovedEvent _ -> do
          liftIO $ traceIO "I was moved"
          putNewSurface
          return e
        _ ->
          return e
    putNewSurface = do
      ad <- get
      (oldSurf, surface) <- liftIO $ getSurfaces $ drawWindow ad
      pixels <- SDL.surfacePixels $ surface
      SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface
      let (SDL.Surface ptr _) = surface
      rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0
      pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr
      let (w, h) = (fromIntegral rw, fromIntegral rh)
          stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w
      put ad
        { windowSurface   = oldSurf
        , drawSurface     = surface
        , drawPixels      = pixels
        , drawDimensions  = (w, h)
        , drawStride      = stride
        }

-- | Return the userstate to the user
getAffection :: Affection us us
getAffection = do
  ad <- get
  return $ userState ad

-- | Put altered user state back
putAffection
  :: us              -- User state
  -> Affection us ()
putAffection us = do
  ad <- get
  put $ ad
    { userState = us }

-- | block a thread for a specified amount of time
delaySec
  :: Int -- ^ Number of seconds
  -> IO ()
delaySec dur = SDL.delay (fromIntegral $ dur * 1000)