module Graphics.Rendering.OpenGL.GL.DebugOutput (
  
  debugOutput, DebugMessage(..), DebugSource(..), DebugType(..),
  DebugMessageID(DebugMessageID), DebugSeverity(..), maxDebugMessageLength,
  
  debugMessageCallback,
  
  maxDebugLoggedMessages, debugLoggedMessages,
  
  MessageGroup(..), debugMessageControl,
  
  debugMessageInsert,
  
  DebugGroup(..), pushDebugGroup, popDebugGroup, withDebugGroup,
  maxDebugGroupStackDepth,
  
  CanBeLabeled(..), maxLabelLength,
  
  debugOutputSynchronous
) where
import Control.Monad ( unless, replicateM )
import Data.StateVar
import Foreign.C.String ( peekCStringLen, withCStringLen )
import Foreign.Marshal.Alloc ( alloca )
import Foreign.Marshal.Array ( allocaArray, withArrayLen )
import Foreign.Ptr (
  nullPtr, castPtrToFunPtr, FunPtr, nullFunPtr, freeHaskellFunPtr )
import Graphics.Rendering.OpenGL.GL.Capability
import Graphics.Rendering.OpenGL.GL.Exception
import Graphics.Rendering.OpenGL.GL.PeekPoke
import Graphics.Rendering.OpenGL.GL.QueryUtils
import Graphics.GL
debugOutput :: StateVar Capability
debugOutput = makeCapability CapDebugOutput
data DebugMessage =
  DebugMessage DebugSource DebugType DebugMessageID DebugSeverity String
  deriving ( Eq, Ord, Show )
data DebugSource =
    DebugSourceAPI
  | DebugSourceShaderCompiler
  | DebugSourceWindowSystem
  | DebugSourceThirdParty
  | DebugSourceApplication
  | DebugSourceOther
  deriving ( Eq, Ord, Show )
marshalDebugSource :: DebugSource -> GLenum
marshalDebugSource x = case x of
  DebugSourceAPI -> GL_DEBUG_SOURCE_API
  DebugSourceShaderCompiler -> GL_DEBUG_SOURCE_SHADER_COMPILER
  DebugSourceWindowSystem -> GL_DEBUG_SOURCE_WINDOW_SYSTEM
  DebugSourceThirdParty -> GL_DEBUG_SOURCE_THIRD_PARTY
  DebugSourceApplication -> GL_DEBUG_SOURCE_APPLICATION
  DebugSourceOther -> GL_DEBUG_SOURCE_OTHER
unmarshalDebugSource :: GLenum -> DebugSource
unmarshalDebugSource x
  | x == GL_DEBUG_SOURCE_API = DebugSourceAPI
  | x == GL_DEBUG_SOURCE_SHADER_COMPILER = DebugSourceShaderCompiler
  | x == GL_DEBUG_SOURCE_WINDOW_SYSTEM = DebugSourceWindowSystem
  | x == GL_DEBUG_SOURCE_THIRD_PARTY = DebugSourceThirdParty
  | x == GL_DEBUG_SOURCE_APPLICATION = DebugSourceApplication
  | x == GL_DEBUG_SOURCE_OTHER = DebugSourceOther
  | otherwise = error ("unmarshalDebugSource: illegal value " ++ show x)
data DebugType =
    DebugTypeError
  | DebugTypeDeprecatedBehavior
  | DebugTypeUndefinedBehavior
  | DebugTypePerformance
  | DebugTypePortability
  | DebugTypeMarker
  | DebugTypePushGroup
  | DebugTypePopGroup
  | DebugTypeOther
  deriving ( Eq, Ord, Show )
marshalDebugType :: DebugType -> GLenum
marshalDebugType x = case x of
  DebugTypeError -> GL_DEBUG_TYPE_ERROR
  DebugTypeDeprecatedBehavior -> GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR
  DebugTypeUndefinedBehavior -> GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR
  DebugTypePerformance -> GL_DEBUG_TYPE_PERFORMANCE
  DebugTypePortability -> GL_DEBUG_TYPE_PORTABILITY
  DebugTypeMarker -> GL_DEBUG_TYPE_MARKER
  DebugTypePushGroup -> GL_DEBUG_TYPE_PUSH_GROUP
  DebugTypePopGroup -> GL_DEBUG_TYPE_POP_GROUP
  DebugTypeOther -> GL_DEBUG_TYPE_OTHER
unmarshalDebugType :: GLenum -> DebugType
unmarshalDebugType x
  | x == GL_DEBUG_TYPE_ERROR = DebugTypeError
  | x == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR = DebugTypeDeprecatedBehavior
  | x == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR = DebugTypeUndefinedBehavior
  | x == GL_DEBUG_TYPE_PERFORMANCE = DebugTypePerformance
  | x == GL_DEBUG_TYPE_PORTABILITY = DebugTypePortability
  | x == GL_DEBUG_TYPE_MARKER = DebugTypeMarker
  | x == GL_DEBUG_TYPE_PUSH_GROUP = DebugTypePushGroup
  | x == GL_DEBUG_TYPE_POP_GROUP = DebugTypePopGroup
  | x == GL_DEBUG_TYPE_OTHER = DebugTypeOther
  | otherwise = error ("unmarshalDebugType: illegal value " ++ show x)
newtype DebugMessageID = DebugMessageID { debugMessageID :: GLuint }
   deriving ( Eq, Ord, Show )
data DebugSeverity =
    DebugSeverityHigh
  | DebugSeverityMedium
  | DebugSeverityLow
  | DebugSeverityNotification
  deriving ( Eq, Ord, Show )
marshalDebugSeverity :: DebugSeverity -> GLenum
marshalDebugSeverity x = case x of
  DebugSeverityHigh -> GL_DEBUG_SEVERITY_HIGH
  DebugSeverityMedium -> GL_DEBUG_SEVERITY_MEDIUM
  DebugSeverityLow -> GL_DEBUG_SEVERITY_LOW
  DebugSeverityNotification -> GL_DEBUG_SEVERITY_NOTIFICATION
unmarshalDebugSeverity :: GLenum -> DebugSeverity
unmarshalDebugSeverity x
  | x == GL_DEBUG_SEVERITY_HIGH = DebugSeverityHigh
  | x == GL_DEBUG_SEVERITY_MEDIUM = DebugSeverityMedium
  | x == GL_DEBUG_SEVERITY_LOW = DebugSeverityLow
  | x == GL_DEBUG_SEVERITY_NOTIFICATION = DebugSeverityNotification
  | otherwise = error ("unmarshalDebugSeverity: illegal value " ++ show x)
maxDebugMessageLength :: GettableStateVar GLsizei
maxDebugMessageLength =
  makeGettableStateVar (getSizei1 id GetMaxDebugMessageLength)
debugMessageCallback :: StateVar (Maybe (DebugMessage -> IO ()))
debugMessageCallback =
  makeStateVar getDebugMessageCallback setDebugMessageCallback
getDebugMessageCallback :: IO (Maybe (DebugMessage -> IO ()))
getDebugMessageCallback = do
  cb <- getDebugCallbackFunction
  return $ if (cb == nullFunPtr)
             then Nothing
             else Just . toDebugProc . dyn_debugProc $ cb
foreign import CALLCONV "dynamic" dyn_debugProc
  :: FunPtr GLDEBUGPROCFunc -> GLDEBUGPROCFunc
toDebugProc:: GLDEBUGPROCFunc -> DebugMessage -> IO ()
toDebugProc debugFunc (DebugMessage source typ msgID severity message) =
  withCStringLen message $ \(msg, len) -> do
    debugFunc (marshalDebugSource source)
              (marshalDebugType typ)
              (marshalDebugSeverity severity)
              (debugMessageID msgID)
              (fromIntegral len)
              msg
              nullPtr
setDebugMessageCallback :: Maybe (DebugMessage -> IO ()) -> IO ()
setDebugMessageCallback maybeDebugProc = do
  oldCB <- getDebugCallbackFunction
  unless (oldCB == nullFunPtr) $
    freeHaskellFunPtr oldCB
  newCB <-
    maybe (return nullFunPtr) (makeGLDEBUGPROC . fromDebugProc) maybeDebugProc
  glDebugMessageCallbackARB newCB  nullPtr
fromDebugProc:: (DebugMessage -> IO ()) -> GLDEBUGPROCFunc
fromDebugProc debugProc source typ msgID severity len message _userParam = do
  msg <- peekCStringLen (message, fromIntegral len)
  debugProc (DebugMessage (unmarshalDebugSource source)
                          (unmarshalDebugType typ)
                          (DebugMessageID msgID)
                          (unmarshalDebugSeverity severity)
                          msg)
getDebugCallbackFunction :: IO (FunPtr GLDEBUGPROCFunc)
getDebugCallbackFunction =
  castPtrToFunPtr `fmap` getPointer DebugCallbackFunction
maxDebugLoggedMessages :: GettableStateVar GLsizei
maxDebugLoggedMessages =
  makeGettableStateVar (getSizei1 id GetMaxDebugLoggedMessages)
debugLoggedMessages :: IO [DebugMessage]
debugLoggedMessages = do
  count <- getSizei1 fromIntegral GetDebugLoggedMessages
  replicateM count debugNextLoggedMessage
debugNextLoggedMessage :: IO DebugMessage
debugNextLoggedMessage = do
  len <- getSizei1 id GetDebugNextLoggedMessageLength
  alloca $ \sourceBuf ->
    alloca $ \typeBuf ->
      alloca $ \idBuf ->
        alloca $ \severityBuf ->
          allocaArray (fromIntegral len) $ \messageBuf -> do
            _ <- glGetDebugMessageLog 1 len sourceBuf typeBuf idBuf
                                      severityBuf nullPtr messageBuf
            source <- peek1 unmarshalDebugSource sourceBuf
            typ <- peek1 unmarshalDebugType typeBuf
            msgID <- peek1 DebugMessageID idBuf
            severity <- peek1 unmarshalDebugSeverity severityBuf
            message <- peekCStringLen (messageBuf, fromIntegral len)
            return $ DebugMessage source typ msgID severity message
data MessageGroup =
    MessageGroup (Maybe DebugSource) (Maybe DebugType) (Maybe DebugSeverity)
  | MessageGroupWithIDs DebugSource DebugType [DebugMessageID]
  deriving ( Eq, Ord, Show )
debugMessageControl :: MessageGroup -> SettableStateVar Capability
debugMessageControl x = case x of
  MessageGroup maybeSource maybeType maybeSeverity ->
    doDebugMessageControl maybeSource maybeType maybeSeverity []
  MessageGroupWithIDs source typ messageIDs ->
    doDebugMessageControl (Just source) (Just typ) Nothing messageIDs
doDebugMessageControl :: Maybe DebugSource
                      -> Maybe DebugType
                      -> Maybe DebugSeverity
                      -> [DebugMessageID]
                      -> SettableStateVar Capability
doDebugMessageControl maybeSource maybeType maybeSeverity messageIDs =
  makeSettableStateVar $ \cap ->
    withArrayLen (map debugMessageID messageIDs) $ \len idsBuf ->
      glDebugMessageControl (maybe GL_DONT_CARE marshalDebugSource maybeSource)
                            (maybe GL_DONT_CARE marshalDebugType maybeType)
                            (maybe GL_DONT_CARE marshalDebugSeverity maybeSeverity)
                            (fromIntegral len)
                            idsBuf
                            (marshalCapability cap)
debugMessageInsert :: DebugMessage -> IO ()
debugMessageInsert (DebugMessage source typ msgID severity message) =
  withCStringLen message $ \(msg, len) ->
    glDebugMessageInsert (marshalDebugSource source)
                         (marshalDebugType typ)
                         (debugMessageID msgID)
                         (marshalDebugSeverity severity)
                         (fromIntegral len)
                         msg
data DebugGroup = DebugGroup DebugSource DebugMessageID String
pushDebugGroup :: DebugSource -> DebugMessageID -> String -> IO ()
pushDebugGroup source msgID message =
  withCStringLen message $ \(msg, len) ->
    glPushDebugGroup (marshalDebugSource source)
                     (debugMessageID msgID)
                     (fromIntegral len)
                     msg
popDebugGroup :: IO ()
popDebugGroup = glPopDebugGroup
withDebugGroup :: DebugSource -> DebugMessageID -> String -> IO a -> IO a
withDebugGroup source msgID message =
  bracket_ (pushDebugGroup source msgID message) popDebugGroup
maxDebugGroupStackDepth :: GettableStateVar GLsizei
maxDebugGroupStackDepth =
  makeGettableStateVar (getSizei1 id GetMaxDebugGroupStackDepth)
class CanBeLabeled a where
  objectLabel :: a -> StateVar (Maybe String)
debugOutputSynchronous :: StateVar Capability
debugOutputSynchronous = makeCapability CapDebugOutputSynchronous