--------------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.OpenGL.GLU.NURBS
-- Copyright   :  (c) Sven Panne 2002-2019
-- License     :  BSD3
--
-- Maintainer  :  Sven Panne <svenpanne@gmail.com>
-- Stability   :  stable
-- Portability :  portable
--
-- This module corresponds to chapter 7 (NURBS) of the GLU specs.
--
--------------------------------------------------------------------------------

module Graphics.Rendering.OpenGL.GLU.NURBS (
   NURBSObj, withNURBSObj,
   NURBSBeginCallback, withNURBSBeginCallback,
   NURBSVertexCallback, withNURBSVertexCallback,
   NURBSNormalCallback, withNURBSNormalCallback,
   NURBSColorCallback, withNURBSColorCallback,
   NURBSEndCallback, withNURBSEndCallback,
   checkForNURBSError,
   nurbsBeginEndCurve, nurbsCurve,
   nurbsBeginEndSurface, nurbsSurface,
   TrimmingPoint, nurbsBeginEndTrim, pwlCurve, trimmingCurve,
   NURBSMode(..), setNURBSMode,
   setNURBSCulling,
   SamplingMethod(..), setSamplingMethod,
   loadSamplingMatrices,
   DisplayMode'(..), setDisplayMode'
) where

import Control.Monad
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import Graphics.GLU hiding (
   NURBSBeginCallback, NURBSVertexCallback, NURBSNormalCallback,
   NURBSColorCallback, NURBSEndCallback )
import Graphics.Rendering.OpenGL.GL.Tensor
import Graphics.Rendering.OpenGL.GL.Capability
import Graphics.Rendering.OpenGL.GL.ControlPoint
import Graphics.Rendering.OpenGL.GL.CoordTrans
import Graphics.Rendering.OpenGL.GL.Exception
import Graphics.Rendering.OpenGL.GL.GLboolean
import Graphics.Rendering.OpenGL.GL.PrimitiveMode
import Graphics.Rendering.OpenGL.GL.PrimitiveModeInternal
import Graphics.Rendering.OpenGL.GL.VertexSpec
import Graphics.Rendering.OpenGL.GLU.ErrorsInternal
import Graphics.GL

--------------------------------------------------------------------------------
-- chapter 7.1: The NURBS Object

-- an opaque pointer to a NURBS object
type NURBSObj = Ptr GLUnurbs

isNullNURBSObj :: NURBSObj -> Bool
isNullNURBSObj = (nullPtr ==)

withNURBSObj :: a -> (NURBSObj -> IO a) -> IO a
withNURBSObj failureValue action =
   bracket gluNewNurbsRenderer safeDeleteNurbsRenderer
           (\nurbsObj -> if isNullNURBSObj nurbsObj
                            then do recordOutOfMemory
                                    return failureValue
                            else action nurbsObj)

safeDeleteNurbsRenderer :: NURBSObj -> IO ()
safeDeleteNurbsRenderer nurbsObj =
   unless (isNullNURBSObj nurbsObj) $ gluDeleteNurbsRenderer nurbsObj

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (begin)

type NURBSBeginCallback = PrimitiveMode -> IO ()

withNURBSBeginCallback :: NURBSObj -> NURBSBeginCallback -> IO a -> IO a
withNURBSBeginCallback nurbsObj beginCallback action =
   bracket (makeNURBSBeginCallback (beginCallback . unmarshalPrimitiveMode))
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_BEGIN callbackPtr
      action

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (vertex)

type NURBSVertexCallback = Vertex3 GLfloat -> IO ()

withNURBSVertexCallback :: NURBSObj -> NURBSVertexCallback -> IO a -> IO a
withNURBSVertexCallback nurbsObj vertexCallback action =
   bracket (makeNURBSVertexCallback (\p -> peek (castPtr p) >>= vertexCallback))
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_VERTEX callbackPtr
      action

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (normal)

type NURBSNormalCallback = Normal3 GLfloat -> IO ()

withNURBSNormalCallback :: NURBSObj -> NURBSNormalCallback -> IO a -> IO a
withNURBSNormalCallback nurbsObj normalCallback action =
   bracket (makeNURBSNormalCallback (\p -> peek (castPtr p) >>= normalCallback))
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_NORMAL callbackPtr
      action

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (color)

type NURBSColorCallback = Color4 GLfloat -> IO ()

withNURBSColorCallback :: NURBSObj -> NURBSColorCallback -> IO a -> IO a
withNURBSColorCallback nurbsObj colorCallback action =
   bracket (makeNURBSColorCallback (\p -> peek (castPtr p) >>= colorCallback))
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_COLOR callbackPtr
      action

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (end)

type NURBSEndCallback = IO ()

withNURBSEndCallback :: NURBSObj -> NURBSEndCallback -> IO a -> IO a
withNURBSEndCallback nurbsObj endCallback action =
   bracket (makeNURBSEndCallback endCallback)
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_END callbackPtr
      action

--------------------------------------------------------------------------------
-- chapter 7.2: Callbacks (error)

type ErrorCallback = GLenum -> IO ()

withErrorCallback :: NURBSObj -> ErrorCallback -> IO a -> IO a
withErrorCallback nurbsObj errorCallback action =
   bracket (makeNURBSErrorCallback errorCallback)
           freeHaskellFunPtr $ \callbackPtr -> do
      gluNurbsCallback nurbsObj GLU_NURBS_ERROR callbackPtr
      action

checkForNURBSError :: NURBSObj -> IO a -> IO a
checkForNURBSError nurbsObj = withErrorCallback nurbsObj recordErrorCode

--------------------------------------------------------------------------------
-- chapter 7.3: NURBS Curves

nurbsBeginEndCurve :: NURBSObj -> IO a -> IO a
nurbsBeginEndCurve nurbsObj =
   bracket_ (gluBeginCurve nurbsObj) (gluEndCurve nurbsObj)


nurbsCurve :: ControlPoint c => NURBSObj -> GLint -> Ptr GLfloat -> GLint -> Ptr (c GLfloat) -> GLint -> IO ()
nurbsCurve nurbsObj knotCount knots stride control order =
   gluNurbsCurve nurbsObj knotCount knots stride (castPtr control) order (map1Target (pseudoPeek control))

pseudoPeek :: Ptr (c GLfloat) -> c GLfloat
pseudoPeek _ = undefined

--------------------------------------------------------------------------------
-- chapter 7.4: NURBS Surfaces

nurbsBeginEndSurface :: NURBSObj -> IO a -> IO a
nurbsBeginEndSurface nurbsObj =
   bracket_ (gluBeginSurface nurbsObj) (gluEndSurface nurbsObj)


nurbsSurface :: ControlPoint c => NURBSObj -> GLint -> Ptr GLfloat -> GLint -> Ptr GLfloat -> GLint -> GLint -> Ptr (c GLfloat) -> GLint -> GLint -> IO ()
nurbsSurface nurbsObj sKnotCount sKnots tKnotCount tKnots sStride tStride control sOrder tOrder =
   gluNurbsSurface nurbsObj sKnotCount sKnots tKnotCount tKnots sStride tStride (castPtr control) sOrder tOrder (map2Target (pseudoPeek control))

--------------------------------------------------------------------------------
-- chapter 7.5: Trimming

class TrimmingPoint p where
   trimmingTarget :: p GLfloat -> GLenum

instance TrimmingPoint Vertex2 where
   trimmingTarget = const GLU_MAP1_TRIM_2

instance TrimmingPoint Vertex3 where
   trimmingTarget = const GLU_MAP1_TRIM_3

nurbsBeginEndTrim :: NURBSObj -> IO a -> IO a
nurbsBeginEndTrim nurbsObj =
   bracket_ (gluBeginTrim nurbsObj) (gluEndTrim nurbsObj)

pwlCurve :: TrimmingPoint p => NURBSObj -> GLint -> Ptr (p GLfloat) -> GLint -> IO ()
pwlCurve nurbsObj count points stride =
   gluPwlCurve nurbsObj count (castPtr points) stride (trimmingTarget (pseudoPeek points))

trimmingCurve :: TrimmingPoint c => NURBSObj -> GLint -> Ptr GLfloat -> GLint -> Ptr (c GLfloat) -> GLint -> IO ()
trimmingCurve nurbsObj knotCount knots stride control order =
   gluNurbsCurve nurbsObj knotCount knots stride (castPtr control) order (trimmingTarget (pseudoPeek control))

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

data NURBSMode =
     NURBSTessellator
   | NURBSRenderer
   deriving ( Eq, Ord, Show )

marshalNURBSMode :: NURBSMode -> GLfloat
marshalNURBSMode x = fromIntegral $ case x of
   NURBSTessellator -> GLU_NURBS_TESSELLATOR
   NURBSRenderer -> GLU_NURBS_RENDERER

setNURBSMode :: NURBSObj -> NURBSMode -> IO ()
setNURBSMode nurbsObj = gluNurbsProperty nurbsObj GLU_NURBS_MODE . marshalNURBSMode

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

setNURBSCulling :: NURBSObj -> Capability -> IO ()
setNURBSCulling nurbsObj = gluNurbsProperty nurbsObj GLU_CULLING . fromIntegral . marshalCapability

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

data SamplingMethod' =
     PathLength'
   | ParametricError'
   | DomainDistance'
   | ObjectPathLength'
   | ObjectParametricError'

marshalSamplingMethod' :: SamplingMethod' -> GLfloat
marshalSamplingMethod' x = fromIntegral $ case x of
   PathLength' -> GLU_PATH_LENGTH
   ParametricError' -> GLU_PARAMETRIC_TOLERANCE
   DomainDistance' -> GLU_DOMAIN_DISTANCE
   ObjectPathLength' -> GLU_OBJECT_PATH_LENGTH
   ObjectParametricError' -> GLU_OBJECT_PARAMETRIC_ERROR

setSamplingMethod' :: NURBSObj -> SamplingMethod' -> IO ()
setSamplingMethod' nurbsObj = gluNurbsProperty nurbsObj GLU_SAMPLING_METHOD . marshalSamplingMethod'

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

data SamplingMethod =
     PathLength GLfloat
   | ParametricError GLfloat
   | DomainDistance GLfloat GLfloat
   | ObjectPathLength GLfloat
   | ObjectParametricError GLfloat
   deriving ( Eq, Ord, Show )

setSamplingMethod :: NURBSObj -> SamplingMethod -> IO ()
setSamplingMethod nurbsObj x = case x of
   PathLength s -> do
      gluNurbsProperty nurbsObj GLU_SAMPLING_TOLERANCE s
      setSamplingMethod' nurbsObj PathLength'
   ParametricError p -> do
      gluNurbsProperty nurbsObj GLU_PARAMETRIC_TOLERANCE p
      setSamplingMethod' nurbsObj ParametricError'
   DomainDistance u v -> do
      gluNurbsProperty nurbsObj GLU_U_STEP u
      gluNurbsProperty nurbsObj GLU_V_STEP v
      setSamplingMethod' nurbsObj DomainDistance'
   ObjectPathLength s -> do
      gluNurbsProperty nurbsObj GLU_SAMPLING_TOLERANCE s
      setSamplingMethod' nurbsObj ObjectPathLength'
   ObjectParametricError p -> do
      gluNurbsProperty nurbsObj GLU_PARAMETRIC_TOLERANCE p
      setSamplingMethod' nurbsObj ObjectParametricError'

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

setAutoLoadMatrix :: NURBSObj -> Bool -> IO ()
setAutoLoadMatrix nurbsObj = gluNurbsProperty nurbsObj GLU_AUTO_LOAD_MATRIX . marshalGLboolean

loadSamplingMatrices :: (Matrix m1, Matrix m2) => NURBSObj -> Maybe (m1 GLfloat, m2 GLfloat, (Position, Size)) -> IO ()
loadSamplingMatrices nurbsObj =
   maybe
      (setAutoLoadMatrix nurbsObj True)
      (\(mv, proj, (Position x y, Size w h)) -> do
          withMatrixColumnMajor mv $ \mvBuf ->
             withMatrixColumnMajor proj $ \projBuf ->
                withArray [x, y, fromIntegral w, fromIntegral h] $ \viewportBuf ->
                  gluLoadSamplingMatrices nurbsObj mvBuf projBuf viewportBuf
          setAutoLoadMatrix nurbsObj False)

withMatrixColumnMajor :: (Matrix m, MatrixComponent c) => m c -> (Ptr c -> IO a) -> IO a
withMatrixColumnMajor mat act =
   withMatrix mat $ \order p ->
      if order == ColumnMajor
         then act p
         else do
            elems <- mapM (peekElemOff p) [ 0, 4,  8, 12,
                                            1, 5,  9, 13,
                                            2, 6, 10, 14,
                                            3, 7, 11, 15 ]
            withArray elems act

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

data DisplayMode' =
     Fill'
   | OutlinePolygon
   | OutlinePatch
   deriving ( Eq, Ord, Show )

marshalDisplayMode' :: DisplayMode' -> GLfloat
marshalDisplayMode' x = fromIntegral $ case x of
   Fill' -> GLU_FILL
   OutlinePolygon -> GLU_OUTLINE_POLYGON
   OutlinePatch -> GLU_OUTLINE_PATCH

setDisplayMode' :: NURBSObj -> DisplayMode' -> IO ()
setDisplayMode' nurbsObj = gluNurbsProperty nurbsObj GLU_DISPLAY_MODE . marshalDisplayMode'