{-# LANGUAGE PackageImports #-}
{-# LANGUAGE CPP #-}
-- | Bracketed GLFW resource initializers.
module Graphics.GPipe.Context.GLFW.Resource
( newContext
, newSharedContext
, WindowConf(..)
, GLFWWindow(..)
, defaultWindowConf
, Window
, ErrorCallback
) where

import qualified "GLFW-b" Graphics.UI.GLFW as GLFW
import qualified Control.Exception as Exc
import qualified Data.Maybe as M
import qualified Text.Printf as P

#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif

------------------------------------------------------------------------------
-- Types & Constants

-- | A value representing a GLFW OpenGL context window.
newtype GLFWWindow = GLFWWindow
  {
    getGLFWWindow :: GLFW.Window
  }

-- reexports
type Window = GLFW.Window
type ErrorCallback = GLFW.ErrorCallback

-- a default error callback which ragequits
defaultOnError :: ErrorCallback
defaultOnError err msg = fail $ P.printf "%s: %s" (show err) msg

-- | Initial window size and title suggestions for GLFW. The window will usually
-- be set to the given size with the given title, unless the window manager
-- overrides this.
data WindowConf = WindowConf
    { width :: Int
    , height :: Int
    , title :: String
    }

-- | A set of sensible defaults for the 'WindowConf'. Used by
-- 'Graphics.GPipe.Context.GLFW.newContext'.
defaultWindowConf :: WindowConf
defaultWindowConf = WindowConf 1024 768 "GLFW Window"

------------------------------------------------------------------------------
-- Code

-- set and unset the GLFW error callback, using a default if none is provided
withErrorCallback :: Maybe ErrorCallback -> IO a -> IO a
withErrorCallback customOnError =
    Exc.bracket_
        (GLFW.setErrorCallback $ Just onError)
        (GLFW.setErrorCallback Nothing)
    where
        onError :: ErrorCallback
        onError = M.fromMaybe defaultOnError customOnError

-- init and terminate GLFW
withGLFW :: IO a -> IO a
withGLFW =
    Exc.bracket_
        GLFW.init
        $ return () -- GLFW.terminate
        -- to clean up we should call GLFW.terminate, but it currently breaks
        -- see issue https://github.com/bsl/GLFW-b/issues/54

-- reset window hints and apply the given list, afterward reset window hints
withHints :: [GLFW.WindowHint] -> IO a -> IO a
withHints hints =
    Exc.bracket_
        (GLFW.defaultWindowHints >> mapM_ GLFW.windowHint hints)
        GLFW.defaultWindowHints

-- create a window, as the current context, using any monitor
-- if given a `Window`, create the new window's context from that
newWindow :: Maybe Window -> Maybe WindowConf -> IO Window
newWindow share customWindowConf =
    M.fromMaybe noWindow <$> createWindowHuh
    where
        WindowConf {width=w, height=h, title=t} = M.fromMaybe defaultWindowConf customWindowConf
        createWindowHuh :: IO (Maybe Window)
        createWindowHuh = do
            GLFW.makeContextCurrent Nothing
            win <- GLFW.createWindow w h t Nothing share
            GLFW.makeContextCurrent win
            return win
        noWindow :: Window
        noWindow = error "Couldn't create a window"

------------------------------------------------------------------------------
-- Top-level

-- establish a *new* opengl context
newContext :: Maybe ErrorCallback -> [GLFW.WindowHint] -> Maybe WindowConf -> IO Window
newContext ec hints wc
    = withErrorCallback ec
    . withGLFW
    . withHints hints
    $ newWindow Nothing wc

-- establish a *shared* opengl context
newSharedContext :: Window -> [GLFW.WindowHint] -> Maybe WindowConf -> IO Window
newSharedContext ctx hints wc
    = withHints hints
    $ newWindow (Just ctx) wc

-- eof