--------------------------------------------------------------------------------
-- |
-- Module      :  Graphics.UI.GLUT.Callbacks.Window
-- Copyright   :  (c) Sven Panne 2002-2018
-- License     :  BSD3
--
-- Maintainer  :  Sven Panne <svenpanne@gmail.com>
-- Stability   :  stable
-- Portability :  portable
--
--------------------------------------------------------------------------------

module Graphics.UI.GLUT.Callbacks.Window (
   -- * Redisplay callbacks
   DisplayCallback, displayCallback, overlayDisplayCallback,

   -- * Reshape callback
   ReshapeCallback, reshapeCallback,

   -- * Position callback
   PositionCallback, positionCallback,

   -- * Callbacks for visibility changes
   Visibility(..), VisibilityCallback, visibilityCallback,
   WindowState(..), WindowStateCallback, windowStateCallback,

   -- * Window close callback
   CloseCallback, closeCallback,

   -- * Life cycle callbacks for mobile platforms
   InitContextCallback, initContextCallback,
   AppStatus(..), AppStatusCallback, appStatusCallback,

   -- * Keyboard callback
   KeyboardCallback, keyboardCallback, keyboardUpCallback,

   -- * Special callback
   SpecialCallback, specialCallback, specialUpCallback,

   -- * Mouse callback
   MouseCallback, mouseCallback,

   -- * Keyboard and mouse input callback
   Key(..), SpecialKey(..), MouseButton(..), KeyState(..), Modifiers(..),
   KeyboardMouseCallback, keyboardMouseCallback,

   -- * Mouse wheel callback
   WheelNumber, WheelDirection, MouseWheelCallback, mouseWheelCallback,

   -- * Mouse movement callbacks
   MotionCallback, motionCallback, passiveMotionCallback,
   Crossing(..), CrossingCallback, crossingCallback,

   -- * Spaceball callback
   SpaceballMotion, SpaceballRotation, ButtonIndex, SpaceballInput(..),
   SpaceballCallback, spaceballCallback,

   -- * Dial & button box callback
   DialAndButtonBoxInput(..), DialIndex,
   DialAndButtonBoxCallback, dialAndButtonBoxCallback,

   -- * Tablet callback
   TabletPosition(..), TabletInput(..), TabletCallback, tabletCallback,

   -- * Joystick callback
   JoystickButtons(..), JoystickPosition(..),
   JoystickCallback, joystickCallback,

   -- * Multi-touch support
   TouchID,
   MultiMouseCallback, multiMouseCallback,
   MultiCrossingCallback, multiCrossingCallback,
   MultiMotionCallback, multiMotionCallback, multiPassiveMotionCallback

) where

import Data.Bits ( (.&.) )
import Data.Char ( chr )
import Data.Maybe ( fromJust )
import Data.StateVar ( SettableStateVar, makeSettableStateVar )
import Foreign.C.Types ( CInt, CUInt )
import Graphics.Rendering.OpenGL ( Position(..), Size(..) )

import Graphics.UI.GLUT.Callbacks.Registration
import Graphics.UI.GLUT.Raw
import Graphics.UI.GLUT.State
import Graphics.UI.GLUT.Types

--------------------------------------------------------------------------------

-- | A display callback

type DisplayCallback = IO ()

-- | Controls the display callback for the /current window./ When GLUT determines
-- that the normal plane for the window needs to be redisplayed, the display
-- callback for the window is called. Before the callback, the /current window/
-- is set to the window needing to be redisplayed and (if no overlay display
-- callback is registered) the /layer in use/ is set to the normal plane. The
-- entire normal plane region should be redisplayed in response to the callback
-- (this includes ancillary buffers if your program depends on their state).
--
-- GLUT determines when the display callback should be triggered based on the
-- window\'s redisplay state. The redisplay state for a window can be either set
-- explicitly by calling 'Graphics.UI.GLUT.Window.postRedisplay' or implicitly
-- as the result of window damage reported by the window system. Multiple posted
-- redisplays for a window are coalesced by GLUT to minimize the number of
-- display callbacks called.
--
-- When an overlay is established for a window, but there is no overlay display
-- callback registered, the display callback is used for redisplaying both the
-- overlay and normal plane (that is, it will be called if either the redisplay
-- state or overlay redisplay state is set). In this case, the /layer in use/ is
-- not implicitly changed on entry to the display callback.
--
-- See 'overlayDisplayCallback' to understand how distinct callbacks for the
-- overlay and normal plane of a window may be established.
--
-- When a window is created, no display callback exists for the window. It is
-- the responsibility of the programmer to install a display callback for the
-- window before the window is shown. A display callback must be registered for
-- any window that is shown. If a window becomes displayed without a display
-- callback being registered, a fatal error occurs. There is no way to
-- \"deregister\" a display callback (though another callback routine can always
-- be registered).
--
-- Upon return from the display callback, the normal damaged state of the window
-- (see 'Graphics.UI.GLUT.State.damaged') is cleared. If there is no overlay
-- display callback registered the overlay damaged state of the window (see
-- 'Graphics.UI.GLUT.State.damaged') is also cleared.

displayCallback :: SettableStateVar DisplayCallback
displayCallback = makeSettableStateVar $
   setCallback DisplayCB glutDisplayFunc makeDisplayFunc . Just

--------------------------------------------------------------------------------

-- | Controls the overlay display callback for the /current window./ The overlay
-- display callback is functionally the same as the window\'s display callback
-- except that the overlay display callback is used to redisplay the window\'s
-- overlay.
--
-- When GLUT determines that the overlay plane for the window needs to be
-- redisplayed, the overlay display callback for the window is called. Before
-- the callback, the /current window/ is set to the window needing to be
-- redisplayed and the /layer in use/ is set to the overlay. The entire overlay
-- region should be redisplayed in response to the callback (this includes
-- ancillary buffers if your program depends on their state).
--
-- GLUT determines when the overlay display callback should be triggered based
-- on the window\'s overlay redisplay state. The overlay redisplay state for a
-- window can be either set explicitly by calling
-- 'Graphics.UI.GLUT.Overlay.postOverlayRedisplay' or implicitly as the result
-- of window damage reported by the window system. Multiple posted overlay
-- redisplays for a window are coalesced by GLUT to minimize the number of
-- overlay display callbacks called.
--
-- Upon return from the overlay display callback, the overlay damaged state of
-- the window (see 'Graphics.UI.GLUT.State.damaged') is cleared.
--
-- Initially there is no overlay display callback registered when an overlay is
-- established. See 'displayCallback' to understand how the display callback
-- alone is used if an overlay display callback is not registered.

overlayDisplayCallback :: SettableStateVar (Maybe DisplayCallback)
overlayDisplayCallback = makeSettableStateVar $
   setCallback OverlayDisplayCB glutOverlayDisplayFunc makeOverlayDisplayFunc

--------------------------------------------------------------------------------

-- | A reshape callback

type ReshapeCallback = Size -> IO ()

-- | Controls the reshape callback for the /current window./ The reshape callback
-- is triggered when a window is reshaped. A reshape callback is also triggered
-- immediately before a window\'s first display callback after a window is
-- created or whenever an overlay for the window is established. The parameter
-- of the callback specifies the new window size in pixels. Before the callback,
-- the /current window/ is set to the window that has been reshaped.
--
-- If a reshape callback is not registered for a window or 'reshapeCallback' is
-- set to 'Nothing' (to deregister a previously registered callback), the
-- default reshape callback is used. This default callback will simply call
--
-- @
-- 'Graphics.Rendering.OpenGL.GL.CoordTrans.viewport' ('Graphics.Rendering.OpenGL.GL.CoordTrans.Position' 0 0) ('Graphics.Rendering.OpenGL.GL.CoordTrans.Size' /width/ /height/)
-- @
--
-- on the normal plane (and on the overlay if one exists).
--
-- If an overlay is established for the window, a single reshape callback is
-- generated. It is the callback\'s responsibility to update both the normal
-- plane and overlay for the window (changing the layer in use as necessary).
--
-- When a top-level window is reshaped, subwindows are not reshaped. It is up to
-- the GLUT program to manage the size and positions of subwindows within a
-- top-level window. Still, reshape callbacks will be triggered for subwindows
-- when their size is changed using 'Graphics.UI.GLUT.Window.windowSize'.

reshapeCallback :: SettableStateVar (Maybe ReshapeCallback)
reshapeCallback = makeSettableStateVar $
   setCallback ReshapeCB glutReshapeFunc (makeReshapeFunc . unmarshal)
   where unmarshal cb w h = cb (Size (fromIntegral w) (fromIntegral h))

--------------------------------------------------------------------------------

-- | A position callback

type PositionCallback = Position -> IO ()

-- | (/freeglut only/) Controls the position callback for the /current window./
-- The position callback for a window is called when the position of a window
-- changes.

positionCallback :: SettableStateVar (Maybe PositionCallback)
positionCallback = makeSettableStateVar $
   setCallback PositionCB glutPositionFunc (makePositionFunc . unmarshal)
   where unmarshal cb x y = cb (Position (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

-- | The visibility state of the /current window/

data Visibility
   = NotVisible -- ^ No part of the /current window/ is visible, i.e., until the
                --   window\'s visibility changes, all further rendering to the
                --   window is discarded.
   | Visible    -- ^ The /current window/ is totally or partially visible. GLUT
                --   considers a window visible if any pixel of the window is
                --   visible or any pixel of any descendant window is visible on
                --   the screen.
   deriving ( Eq, Ord, Show )

unmarshalVisibility :: CInt -> Visibility
unmarshalVisibility x
   | x == glut_NOT_VISIBLE = NotVisible
   | x == glut_VISIBLE = Visible
   | otherwise = error ("unmarshalVisibility: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | A visibility callback

type VisibilityCallback = Visibility -> IO ()

-- | Controls the visibility callback for the /current window./ The visibility
-- callback for a window is called when the visibility of a window changes.
--
-- If the visibility callback for a window is disabled and later re-enabled, the
-- visibility status of the window is undefined; any change in window visibility
-- will be reported, that is if you disable a visibility callback and re-enable
-- the callback, you are guaranteed the next visibility change will be reported.
--
-- Note that you can either use 'visibilityCallback' or 'windowStateCallback',
-- but not both, because the former is implemented via the latter.

visibilityCallback :: SettableStateVar (Maybe VisibilityCallback)
visibilityCallback = makeSettableStateVar $
   setCallback VisibilityCB glutVisibilityFunc
               (makeVisibilityFunc . unmarshal)
   where unmarshal cb  = cb . unmarshalVisibility

--------------------------------------------------------------------------------

-- | The window state of the /current window/

data WindowState
   = Unmapped          -- ^ The /current window/ is unmapped.
   | FullyRetained     -- ^ The /current window/ is unobscured.
   | PartiallyRetained -- ^ The /current window/ is partially obscured.
   | FullyCovered      -- ^ The /current window/ is fully obscured.
   deriving ( Eq, Ord, Show )

unmarshalWindowState :: CInt -> WindowState
unmarshalWindowState x
   | x == glut_HIDDEN = Unmapped
   | x == glut_FULLY_RETAINED = FullyRetained
   | x == glut_PARTIALLY_RETAINED = PartiallyRetained
   | x == glut_FULLY_COVERED = FullyCovered
   | otherwise = error ("unmarshalWindowState: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | A window state callback

type WindowStateCallback = WindowState -> IO ()

-- | Controls the window state callback for the
-- /current window./ The window state callback for a window is called when the
-- window state of a window changes.
--
-- If the window state callback for a window is disabled and later re-enabled,
-- the window state state of the window is undefined; any change in the window
-- state will be reported, that is if you disable a window state callback and
-- re-enable the callback, you are guaranteed the next window state change will
-- be reported.
--
-- Note that you can either use 'visibilityCallback' or 'windowStateCallback',
-- but not both, because the former is implemented via the latter.

windowStateCallback :: SettableStateVar (Maybe WindowStateCallback)
windowStateCallback = makeSettableStateVar $
   setCallback WindowStatusCB glutWindowStatusFunc
               (makeWindowStatusFunc . unmarshal)
   where unmarshal cb  = cb . unmarshalWindowState

--------------------------------------------------------------------------------

-- | A window close callback

type CloseCallback = IO ()

-- | Controls the window close callback for the /current window/.

closeCallback :: SettableStateVar (Maybe CloseCallback)
closeCallback = makeSettableStateVar $
   setCallback CloseCB glutCloseFunc makeCloseFunc

--------------------------------------------------------------------------------

-- | An initialize context callback

type InitContextCallback = IO ()

-- | (/freeglut only/) Controls the initialize context callback for the /current
-- window/.

initContextCallback :: SettableStateVar (Maybe InitContextCallback)
initContextCallback = makeSettableStateVar $
   setCallback InitContextCB glutInitContextFunc makeInitContextFunc

--------------------------------------------------------------------------------

-- | The application status of the /current window/

data AppStatus
   = AppStatusPause
   | AppStatusResume
   deriving ( Eq, Ord, Show )

unmarshalAppStatus :: CInt -> AppStatus
unmarshalAppStatus x
   | x == glut_APPSTATUS_PAUSE = AppStatusPause
   | x == glut_APPSTATUS_RESUME = AppStatusResume
   | otherwise = error ("unmarshalAppStatus: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | An application status callback

type AppStatusCallback = AppStatus -> IO ()

-- | Controls the application status callback for the /current window./

appStatusCallback :: SettableStateVar (Maybe AppStatusCallback)
appStatusCallback = makeSettableStateVar $
   setCallback AppStatusCB glutAppStatusFunc
               (makeAppStatusFunc . unmarshal)
   where unmarshal cb  = cb . unmarshalAppStatus

--------------------------------------------------------------------------------

-- | A keyboard callback

type KeyboardCallback = Char -> Position -> IO ()

setKeyboardCallback :: Maybe KeyboardCallback -> IO ()
setKeyboardCallback =
   setCallback KeyboardCB glutKeyboardFunc (makeKeyboardFunc . unmarshal)
   where unmarshal cb c x y = cb (chr (fromIntegral c))
                                 (Position (fromIntegral x) (fromIntegral y))

-- | Controls the keyboard callback for the /current window/. This is
-- activated only when a key is pressed.

keyboardCallback :: SettableStateVar (Maybe KeyboardCallback)
keyboardCallback = makeSettableStateVar setKeyboardCallback

--------------------------------------------------------------------------------

setKeyboardUpCallback :: Maybe KeyboardCallback -> IO ()
setKeyboardUpCallback =
   setCallback KeyboardUpCB glutKeyboardUpFunc
               (makeKeyboardUpFunc . unmarshal)
   where unmarshal cb c x y = cb (chr (fromIntegral c))
                                 (Position (fromIntegral x) (fromIntegral y))

-- | Controls the keyboard callback for the /current window/. This is
-- activated only when a key is released.

keyboardUpCallback :: SettableStateVar (Maybe KeyboardCallback)
keyboardUpCallback = makeSettableStateVar setKeyboardUpCallback

--------------------------------------------------------------------------------

-- | Special keys

data SpecialKey
   = KeyF1
   | KeyF2
   | KeyF3
   | KeyF4
   | KeyF5
   | KeyF6
   | KeyF7
   | KeyF8
   | KeyF9
   | KeyF10
   | KeyF11
   | KeyF12
   | KeyLeft
   | KeyUp
   | KeyRight
   | KeyDown
   | KeyPageUp
   | KeyPageDown
   | KeyHome
   | KeyEnd
   | KeyInsert
   | KeyNumLock
   | KeyBegin
   | KeyDelete
   | KeyShiftL
   | KeyShiftR
   | KeyCtrlL
   | KeyCtrlR
   | KeyAltL
   | KeyAltR
   | KeyUnknown Int -- ^ You should actually never encounter this value, it is
                    -- just here as a safeguard against future changes in the
                    -- native GLUT library.
   deriving ( Eq, Ord, Show )

unmarshalSpecialKey :: CInt -> SpecialKey
unmarshalSpecialKey x
   | x == glut_KEY_F1 = KeyF1
   | x == glut_KEY_F2 = KeyF2
   | x == glut_KEY_F3 = KeyF3
   | x == glut_KEY_F4 = KeyF4
   | x == glut_KEY_F5 = KeyF5
   | x == glut_KEY_F6 = KeyF6
   | x == glut_KEY_F7 = KeyF7
   | x == glut_KEY_F8 = KeyF8
   | x == glut_KEY_F9 = KeyF9
   | x == glut_KEY_F10 = KeyF10
   | x == glut_KEY_F11 = KeyF11
   | x == glut_KEY_F12 = KeyF12
   | x == glut_KEY_LEFT = KeyLeft
   | x == glut_KEY_UP = KeyUp
   | x == glut_KEY_RIGHT = KeyRight
   | x == glut_KEY_DOWN = KeyDown
   | x == glut_KEY_PAGE_UP = KeyPageUp
   | x == glut_KEY_PAGE_DOWN = KeyPageDown
   | x == glut_KEY_HOME = KeyHome
   | x == glut_KEY_END = KeyEnd
   | x == glut_KEY_INSERT = KeyInsert
   | x == glut_KEY_NUM_LOCK = KeyNumLock
   | x == glut_KEY_BEGIN = KeyBegin
   | x == glut_KEY_DELETE = KeyDelete
   | x == glut_KEY_SHIFT_L = KeyShiftL
   | x == glut_KEY_SHIFT_R = KeyShiftR
   | x == glut_KEY_CTRL_L = KeyCtrlL
   | x == glut_KEY_CTRL_R = KeyCtrlR
   | x == glut_KEY_ALT_L = KeyAltL
   | x == glut_KEY_ALT_R = KeyAltR
   | otherwise = KeyUnknown (fromIntegral x)

--------------------------------------------------------------------------------

-- | A special key callback

type SpecialCallback = SpecialKey -> Position -> IO ()

setSpecialCallback :: Maybe SpecialCallback -> IO ()
setSpecialCallback =
   setCallback SpecialCB glutSpecialFunc (makeSpecialFunc . unmarshal)
   where unmarshal cb k x y = cb (unmarshalSpecialKey k)
                                 (Position (fromIntegral x) (fromIntegral y))

-- | Controls the special key callback for the /current window/. This is
-- activated only when a special key is pressed.

specialCallback :: SettableStateVar (Maybe SpecialCallback)
specialCallback = makeSettableStateVar setSpecialCallback

--------------------------------------------------------------------------------

setSpecialUpCallback :: Maybe SpecialCallback -> IO ()
setSpecialUpCallback =
   setCallback SpecialUpCB glutSpecialUpFunc (makeSpecialUpFunc . unmarshal)
   where unmarshal cb k x y = cb (unmarshalSpecialKey k)
                                 (Position (fromIntegral x) (fromIntegral y))

-- | Controls the special key callback for the /current window/. This is
-- activated only when a special key is released.

specialUpCallback :: SettableStateVar (Maybe SpecialCallback)
specialUpCallback = makeSettableStateVar setSpecialUpCallback

--------------------------------------------------------------------------------

-- | The current state of a key or button

data KeyState
   = Down
   | Up
   deriving ( Eq, Ord, Show )

unmarshalKeyState :: CInt -> KeyState
unmarshalKeyState x
   | x == glut_DOWN = Down
   | x == glut_UP = Up
   | otherwise = error ("unmarshalKeyState: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | A mouse callback

type MouseCallback = MouseButton -> KeyState -> Position -> IO ()

setMouseCallback :: Maybe MouseCallback -> IO ()
setMouseCallback =
   setCallback MouseCB glutMouseFunc (makeMouseFunc . unmarshal)
   where unmarshal cb b s x y = cb (unmarshalMouseButton b)
                                   (unmarshalKeyState s)
                                   (Position (fromIntegral x) (fromIntegral y))

-- | Controls the mouse callback for the /current window/.

mouseCallback :: SettableStateVar (Maybe MouseCallback)
mouseCallback = makeSettableStateVar setMouseCallback

--------------------------------------------------------------------------------

-- | The state of the keyboard modifiers

data Modifiers = Modifiers { shift, ctrl, alt :: KeyState }
   deriving ( Eq, Ord, Show )

-- Could use fromBitfield + Enum/Bounded instances + marshalModifier instead...
unmarshalModifiers :: CInt -> Modifiers
unmarshalModifiers m = Modifiers {
   shift = if (m .&. glut_ACTIVE_SHIFT) /= 0 then Down else Up,
   ctrl  = if (m .&. glut_ACTIVE_CTRL ) /= 0 then Down else Up,
   alt   = if (m .&. glut_ACTIVE_ALT  ) /= 0 then Down else Up }

getModifiers :: IO Modifiers
getModifiers = fmap unmarshalModifiers glutGetModifiers

--------------------------------------------------------------------------------

-- | A generalized view of keys

data Key
   = Char Char
   | SpecialKey SpecialKey
   | MouseButton MouseButton
   deriving ( Eq, Ord, Show )

-- | A keyboard\/mouse callback

type KeyboardMouseCallback =
   Key -> KeyState -> Modifiers -> Position -> IO ()

-- | Controls the keyboard\/mouse callback for the /current window./ The
-- keyboard\/mouse callback for a window is called when the state of a key or
-- mouse button changes. The callback parameters indicate the new state of the
-- key\/button, the state of the keyboard modifiers, and the mouse location in
-- window relative coordinates.
--
-- Note that this is a convenience function that should not ordinarily be used
-- in conjunction with `keyboardCallback`, `keyboardUpCallback`,
-- `specialCallback`, `specialUpCallback`, or `mouseCallback`.

keyboardMouseCallback :: SettableStateVar (Maybe KeyboardMouseCallback)
keyboardMouseCallback = makeSettableStateVar setKeyboardMouseCallback

setKeyboardMouseCallback :: Maybe KeyboardMouseCallback -> IO ()
setKeyboardMouseCallback Nothing = do
   setKeyboardCallback   Nothing
   setKeyboardUpCallback Nothing
   setSpecialCallback    Nothing
   setSpecialUpCallback  Nothing
   setMouseCallback      Nothing
setKeyboardMouseCallback (Just cb) = do
   setKeyboardCallback   (Just (\c   p -> do m <- getModifiers
                                             cb (Char        c) Down m p))
   setKeyboardUpCallback (Just (\c   p -> do m <- getModifiers
                                             cb (Char        c) Up   m p))
   setSpecialCallback    (Just (\s   p -> do m <- getModifiers
                                             cb (SpecialKey  s) Down m p))
   setSpecialUpCallback  (Just (\s   p -> do m <- getModifiers
                                             cb (SpecialKey  s) Up   m p))
   setMouseCallback      (Just (\b s p -> do m <- getModifiers
                                             cb (MouseButton b) s    m p))

--------------------------------------------------------------------------------

type WheelNumber = Int

type WheelDirection = Int

type MouseWheelCallback = WheelNumber -> WheelDirection -> Position -> IO ()

-- | (/freeglut only/) Controls the mouse wheel callback for the
-- /current window./ The mouse wheel callback for a window is called when a
-- mouse wheel is used and the wheel number is greater than or equal to
-- 'Graphics.UI.GLUT.State.numMouseButtons'.

mouseWheelCallback :: SettableStateVar (Maybe MouseWheelCallback)
mouseWheelCallback = makeSettableStateVar $
   setCallback MouseWheelCB glutMouseWheelFunc (makeMouseWheelFunc . unmarshal)
   where unmarshal cb n d x y = cb (fromIntegral n) (fromIntegral d)
                                   (Position (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

-- | A motion callback

type MotionCallback = Position -> IO ()

-- | Controls the motion callback for the /current window./ The motion callback
-- for a window is called when the mouse moves within the window while one or
-- more mouse buttons are pressed. The callback parameter indicates the mouse
-- location in window relative coordinates.

motionCallback :: SettableStateVar (Maybe MotionCallback)
motionCallback = makeSettableStateVar $
   setCallback MotionCB glutMotionFunc (makeMotionFunc . unmarshal)
   where unmarshal cb x y = cb (Position (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

-- | Controls the passive motion callback for the /current window./ The passive
-- motion callback for a window is called when the mouse moves within the window
-- while /no/ mouse buttons are pressed. The callback parameter indicates the
-- mouse location in window relative coordinates.

passiveMotionCallback :: SettableStateVar (Maybe MotionCallback)
passiveMotionCallback = makeSettableStateVar $
   setCallback PassiveMotionCB glutPassiveMotionFunc
               (makePassiveMotionFunc . unmarshal)
   where unmarshal cb x y = cb (Position (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

-- | The relation between the mouse pointer and the /current window/ has
-- changed.

data Crossing
   = WindowLeft    -- ^ The mouse pointer has left the /current window./
   | WindowEntered -- ^ The mouse pointer has entered the /current window./
   deriving ( Eq, Ord, Show )

unmarshalCrossing :: CInt -> Crossing
unmarshalCrossing x
   | x == glut_LEFT = WindowLeft
   | x == glut_ENTERED = WindowEntered
   | otherwise = error ("unmarshalCrossing: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | An enter\/leave callback

type CrossingCallback = Crossing -> IO ()

-- | Controls the mouse enter\/leave callback for the /current window./ Note
-- that some window systems may not generate accurate enter\/leave callbacks.
--
-- /X Implementation Notes:/ An X implementation of GLUT should generate
-- accurate enter\/leave callbacks.

crossingCallback :: SettableStateVar (Maybe CrossingCallback)
crossingCallback = makeSettableStateVar $
   setCallback CrossingCB glutEntryFunc (makeEntryFunc . unmarshal)
   where unmarshal cb = cb . unmarshalCrossing

--------------------------------------------------------------------------------

-- | Translation of the Spaceball along one axis, normalized to be in the range
-- of -1000 to +1000 inclusive

type SpaceballMotion = Int

-- | Rotation of the Spaceball along one axis, normalized to be in the range
-- of -1800 .. +1800 inclusive

type SpaceballRotation = Int

-- | The index of a specific buttons of an input device.

type ButtonIndex = Int

-- | The state of the Spaceball has changed.

data SpaceballInput
   = SpaceballMotion   SpaceballMotion SpaceballMotion SpaceballMotion
   | SpaceballRotation SpaceballRotation SpaceballRotation SpaceballRotation
   | SpaceballButton   ButtonIndex KeyState
   deriving ( Eq, Ord, Show )

-- | A SpaceballButton callback

type SpaceballCallback = SpaceballInput -> IO ()

-- | Controls the Spaceball callback for the /current window./ The Spaceball
-- callback for a window is called when the window has Spaceball input focus
-- (normally, when the mouse is in the window) and the user generates Spaceball
-- translations, rotations, or button presses. The number of available Spaceball
-- buttons can be determined with 'Graphics.UI.GLUT.State.numSpaceballButtons'.
--
-- Registering a Spaceball callback when a Spaceball device is not available has
-- no effect and is not an error. In this case, no Spaceball callbacks will be
-- generated.

spaceballCallback :: SettableStateVar (Maybe SpaceballCallback)
spaceballCallback = makeSettableStateVar setSpaceballCallback

setSpaceballCallback :: Maybe SpaceballCallback -> IO ()
setSpaceballCallback Nothing = do
   setSpaceballMotionCallback   Nothing
   setSpaceballRotationCallback Nothing
   setSpaceballButtonCallback   Nothing
setSpaceballCallback (Just cb) = do
   setSpaceballMotionCallback   (Just (\x y z -> cb (SpaceballMotion   x y z)))
   setSpaceballRotationCallback (Just (\x y z -> cb (SpaceballRotation x y z)))
   setSpaceballButtonCallback   (Just (\b s   -> cb (SpaceballButton   b s)))

--------------------------------------------------------------------------------

type SpaceballMotionCallback =
   SpaceballMotion -> SpaceballMotion -> SpaceballMotion -> IO ()

setSpaceballMotionCallback :: Maybe SpaceballMotionCallback -> IO ()
setSpaceballMotionCallback =
   setCallback SpaceballMotionCB glutSpaceballMotionFunc
               (makeSpaceballMotionFunc . unmarshal)
   where unmarshal cb x y z =
            cb (fromIntegral x) (fromIntegral y) (fromIntegral z)

--------------------------------------------------------------------------------

type SpaceballRotationCallback =
   SpaceballRotation -> SpaceballRotation -> SpaceballRotation -> IO ()

setSpaceballRotationCallback :: Maybe SpaceballRotationCallback -> IO ()
setSpaceballRotationCallback =
   setCallback SpaceballRotateCB glutSpaceballRotateFunc
               (makeSpaceballRotateFunc . unmarshal)
   where unmarshal cb x y z =
            cb (fromIntegral x) (fromIntegral y) (fromIntegral z)

--------------------------------------------------------------------------------

type SpaceballButtonCallback = ButtonIndex -> KeyState -> IO ()

setSpaceballButtonCallback :: Maybe SpaceballButtonCallback -> IO ()
setSpaceballButtonCallback =
   setCallback SpaceballButtonCB glutSpaceballButtonFunc
               (makeSpaceballButtonFunc . unmarshal)
   where unmarshal cb b s = cb (fromIntegral b) (unmarshalKeyState s)

--------------------------------------------------------------------------------

-- | The index of a specific dial of a dial and button box.

type DialIndex = Int

-- | The dial & button box state has changed.

data DialAndButtonBoxInput
   = DialAndButtonBoxButton ButtonIndex KeyState
   | DialAndButtonBoxDial   DialIndex Int
   deriving ( Eq, Ord, Show )

-- | A dial & button box callback

type DialAndButtonBoxCallback = DialAndButtonBoxInput -> IO ()

-- | Controls the dial & button box callback for the /current window./ The dial
-- & button box button callback for a window is called when the window has dial
-- & button box input focus (normally, when the mouse is in the window) and the
-- user generates dial & button box button presses or dial changes. The number
-- of available dial & button box buttons and dials can be determined with
-- 'Graphics.UI.GLUT.State.numDialsAndButtons'.
--
-- Registering a dial & button box callback when a dial & button box device is
-- not available is ineffectual and not an error. In this case, no dial & button
-- box button will be generated.

dialAndButtonBoxCallback :: SettableStateVar (Maybe DialAndButtonBoxCallback)
dialAndButtonBoxCallback = makeSettableStateVar setDialAndButtonBoxCallback

setDialAndButtonBoxCallback :: Maybe DialAndButtonBoxCallback -> IO ()
setDialAndButtonBoxCallback Nothing = do
   setButtonBoxCallback Nothing
   setDialsCallback     Nothing
setDialAndButtonBoxCallback (Just cb) = do
   setButtonBoxCallback (Just (\b s -> cb (DialAndButtonBoxButton b s)))
   setDialsCallback     (Just (\d x -> cb (DialAndButtonBoxDial   d x)))

--------------------------------------------------------------------------------

type ButtonBoxCallback = ButtonIndex -> KeyState -> IO ()

setButtonBoxCallback :: Maybe ButtonBoxCallback -> IO ()
setButtonBoxCallback =
   setCallback ButtonBoxCB glutButtonBoxFunc (makeButtonBoxFunc . unmarshal)
   where unmarshal cb b s = cb (fromIntegral b) (unmarshalKeyState s)

--------------------------------------------------------------------------------

type DialsCallback = DialIndex -> Int -> IO ()

setDialsCallback :: Maybe DialsCallback -> IO ()
setDialsCallback =
    setCallback DialsCB glutDialsFunc (makeDialsFunc . unmarshal)
    where unmarshal cb d x = cb (fromIntegral d) (fromIntegral x)

--------------------------------------------------------------------------------

-- | Absolute tablet position, with coordinates normalized to be in the range of
-- 0 to 2000 inclusive

data TabletPosition = TabletPosition Int Int
   deriving ( Eq, Ord, Show )

-- | The table state has changed.

data TabletInput
   = TabletMotion
   | TabletButton ButtonIndex KeyState
   deriving ( Eq, Ord, Show )

-- | A tablet callback

type TabletCallback = TabletInput -> TabletPosition -> IO ()

-- | Controls the tablet callback for the /current window./ The tablet callback
-- for a window is called when the window has tablet input focus (normally, when
-- the mouse is in the window) and the user generates tablet motion or button
-- presses. The number of available tablet buttons can be determined with
-- 'Graphics.UI.GLUT.State.numTabletButtons'.
--
-- Registering a tablet callback when a tablet device is not available is
-- ineffectual and not an error. In this case, no tablet callbacks will be
-- generated.

tabletCallback :: SettableStateVar (Maybe TabletCallback)
tabletCallback = makeSettableStateVar setTabletCallback

setTabletCallback :: Maybe TabletCallback -> IO ()
setTabletCallback Nothing = do
   setTabletMotionCallback Nothing
   setTabletButtonCallback Nothing
setTabletCallback (Just cb) = do
   setTabletMotionCallback (Just (\p     -> cb TabletMotion       p))
   setTabletButtonCallback (Just (\b s p -> cb (TabletButton b s) p))

--------------------------------------------------------------------------------

type TabletMotionCallback = TabletPosition -> IO ()

setTabletMotionCallback :: Maybe TabletMotionCallback -> IO ()
setTabletMotionCallback =
    setCallback TabletMotionCB glutTabletMotionFunc
                (makeTabletMotionFunc . unmarshal)
    where unmarshal cb x y =
             cb (TabletPosition (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

type TabletButtonCallback = ButtonIndex -> KeyState -> TabletPosition -> IO ()

setTabletButtonCallback :: Maybe TabletButtonCallback -> IO ()
setTabletButtonCallback =
    setCallback TabletButtonCB glutTabletButtonFunc
                (makeTabletButtonFunc . unmarshal)
    where unmarshal cb b s x y =
             cb (fromIntegral b) (unmarshalKeyState s)
                (TabletPosition (fromIntegral x) (fromIntegral y))

--------------------------------------------------------------------------------

-- | The state of the joystick buttons

data JoystickButtons = JoystickButtons {
   joystickButtonA, joystickButtonB,
   joystickButtonC, joystickButtonD :: KeyState }
   deriving ( Eq, Ord, Show )

-- Could use fromBitfield + Enum/Bounded instances + unmarshalJoystickButton
-- instead...
unmarshalJoystickButtons :: CUInt -> JoystickButtons
unmarshalJoystickButtons m = JoystickButtons {
   joystickButtonA = if (m .&. glut_JOYSTICK_BUTTON_A) /= 0 then Down else Up,
   joystickButtonB = if (m .&. glut_JOYSTICK_BUTTON_B) /= 0 then Down else Up,
   joystickButtonC = if (m .&. glut_JOYSTICK_BUTTON_C) /= 0 then Down else Up,
   joystickButtonD = if (m .&. glut_JOYSTICK_BUTTON_D) /= 0 then Down else Up }

--------------------------------------------------------------------------------

-- | Absolute joystick position, with coordinates normalized to be in the range
-- of -1000 to 1000 inclusive. The signs of the three axes mean the following:
--
-- * negative = left, positive = right
--
-- * negative = towards player, positive = away
--
-- * if available (e.g. rudder): negative = down, positive = up

data JoystickPosition = JoystickPosition Int Int Int
   deriving ( Eq, Ord, Show )

--------------------------------------------------------------------------------

-- | A joystick callback

type JoystickCallback = JoystickButtons -> JoystickPosition -> IO ()

-- | Controls the joystick callback for the /current window./ The joystick
-- callback is called either due to polling of the joystick at the uniform timer
-- interval specified (if > 0) or in response to an explicit call of
-- 'Graphics.UI.GLUT.DeviceControl.forceJoystickCallback'.
--
-- /X Implementation Notes:/ Currently GLUT has no joystick support for X11.

-- joystickCallback :: SettableStateVar (Maybe JoystickCallback, PollRate)
joystickCallback :: SettableStateVar (Maybe (JoystickCallback, PollRate))
joystickCallback =
   makeSettableStateVar $ \maybeCBAndRate ->
      setCallback JoystickCB
                  (\f -> glutJoystickFunc f (fromIntegral (snd (fromJust maybeCBAndRate))))
                  (makeJoystickFunc . unmarshal)
                  (fmap fst maybeCBAndRate)
    where unmarshal cb b x y z = cb (unmarshalJoystickButtons b)
                                    (JoystickPosition (fromIntegral x)
                                                      (fromIntegral y)
                                                      (fromIntegral z))

--------------------------------------------------------------------------------

-- | A description where the multi-touch event is coming from, the freeglut
-- specs are very vague about the actual semantics. It contains the device ID
-- and\/or the cursor\/finger ID.

type TouchID = Int

-- | A multi-touch variant of 'MouseCallback'.

type MultiMouseCallback = TouchID -> MouseCallback

-- | (/freeglut only/) A multi-touch variant of 'mouseCallback'.

multiMouseCallback :: SettableStateVar (Maybe MultiMouseCallback)
multiMouseCallback = makeSettableStateVar $
   setCallback MultiButtonCB glutMultiButtonFunc (makeMultiButtonFunc . unmarshal)
   where unmarshal cb d x y b s = cb (fromIntegral d)
                                     (unmarshalMouseButton b)
                                     (unmarshalKeyState s)
                                     (Position (fromIntegral x) (fromIntegral y))

-- | A multi-touch variant of 'CrossingCallback'.

type MultiCrossingCallback = TouchID -> CrossingCallback

-- | (/freeglut only/) A multi-touch variant of 'crossingCallback'.

multiCrossingCallback :: SettableStateVar (Maybe MultiCrossingCallback)
multiCrossingCallback = makeSettableStateVar $
   setCallback MultiEntryCB glutMultiEntryFunc (makeMultiEntryFunc . unmarshal)
   where unmarshal cb d c = cb (fromIntegral d) (unmarshalCrossing c)

-- | A multi-touch variant of 'MotionCallback'.

type MultiMotionCallback = TouchID -> MotionCallback

-- | (/freeglut only/) A multi-touch variant of 'motionCallback'.

multiMotionCallback :: SettableStateVar (Maybe MultiMotionCallback)
multiMotionCallback = makeSettableStateVar $
   setCallback MultiMotionCB glutMultiMotionFunc (makeMultiMotionFunc . unmarshal)
   where unmarshal cb d x y =
            cb (fromIntegral d) (Position (fromIntegral x) (fromIntegral y))

-- | (/freeglut only/) A multi-touch variant of 'passiveMotionCallback'.

multiPassiveMotionCallback :: SettableStateVar (Maybe MultiMotionCallback)
multiPassiveMotionCallback = makeSettableStateVar $
   setCallback MultiPassiveCB glutMultiPassiveFunc (makeMultiPassiveFunc . unmarshal)
   where unmarshal cb d x y =
            cb (fromIntegral d) (Position (fromIntegral x) (fromIntegral y))
