--------------------------------------------------------------------------------
-- |
-- Module      :  Sound.OpenAL.ALC.Errors
-- Copyright   :  (c) Sven Panne 2003-2009
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  sven.panne@aedion.de
-- Stability   :  stable
-- Portability :  portable
--
-- This module corresponds to section 6.3.6 (Query for Error Conditions) of the
-- OpenAL Specification and Reference (version 1.1).
--
--------------------------------------------------------------------------------

module Sound.OpenAL.ALC.Errors (
   ALCError(..), ALCErrorCategory(..), alcErrors
) where

import Data.StateVar
import Sound.OpenAL.ALC.BasicTypes ( ALCenum )
import Sound.OpenAL.ALC.Device ( Device )
import Sound.OpenAL.ALC.QueryUtils ( StringQuery(..), getString )
import Sound.OpenAL.Constants (
   alc_NO_ERROR, alc_INVALID_DEVICE, alc_INVALID_CONTEXT, alc_INVALID_ENUM,
   alc_INVALID_VALUE, alc_OUT_OF_MEMORY, alc_INVALID_OPERATION )
import Sound.OpenAL.Config ( ALCdevice(..), marshalDevice )

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

-- | ALC errors consist of a general error category and a description of what
-- went wrong.

data ALCError = ALCError ALCErrorCategory String
   deriving ( Eq, Ord, Show )

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

-- | General ALC error categories.

data ALCErrorCategory =
     ALCInvalidEnum
   | ALCInvalidValue
   | ALCInvalidOperation
   | ALCInvalidDevice
   | ALCInvalidContext
   | ALCOutOfMemory
   deriving ( Eq, Ord, Show )

unmarshalALCErrorCategory :: ALCenum -> ALCErrorCategory
unmarshalALCErrorCategory x
   | x == alc_INVALID_ENUM = ALCInvalidEnum
   | x == alc_INVALID_VALUE = ALCInvalidValue
   | x == alc_INVALID_OPERATION = ALCInvalidOperation
   | x == alc_INVALID_DEVICE = ALCInvalidDevice
   | x == alc_INVALID_CONTEXT = ALCInvalidContext
   | x == alc_OUT_OF_MEMORY = ALCOutOfMemory
   | otherwise = error ("unmarshalALCErrorCategory: illegal value " ++ show x)

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

-- | OpenAL detects only a subset of those conditions that could be considered
-- errors. This is because in many cases error checking would adversely impact
-- the performance of an error-free program. The state variable 'alcErrors' is
-- used to obtain error information. When an error is detected by ALC, a flag is
-- set and the error code is recorded. Further errors, if they occur, do not
-- affect this recorded code. When 'alcErrors' is read, the error for the given
-- device is returned and the flag is cleared, so that a further error will
-- again record its code. If reading 'alcErrors' returns @\[\]@ then there has
-- been no detectable error since the last time 'alcErrors' (or since the ALC
-- was initialized).
--
-- When an error flag is set, results of ALC operations are undefined only if
-- 'ALCOutOfMemory' has occurred. In other cases, the command generating the
-- error is ignored so that it has no effect on ALC state or output buffer
-- contents. If the error generating command returns a value, it returns zero.
-- If the generating command modifies values through a pointer argument, no
-- change is made to these values. These error semantics apply only to ALC
-- errors, not to system errors such as memory access errors.

alcErrors :: Device -> GettableStateVar [ALCError]
alcErrors device =
   makeGettableStateVar $ do
      c <- alcGetError (marshalDevice device)
      if c == alc_NO_ERROR
         then return []
         else do
            s <- getString (Just device) (ALCErrorCategory c)
            return [ ALCError (unmarshalALCErrorCategory c) s ]

foreign import CALLCONV unsafe "alcGetError"
   alcGetError :: ALCdevice -> IO ALCenum
