{-# LANGUAGE ForeignFunctionInterface #-}
{-# OPTIONS_GHC -funbox-strict-fields -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Graphics.Formats.Obj.ParserBits
-- Copyright   :  (c) Anygma BVBA & Thomas Davie 2008
-- License     :  BSD3
-- 
-- Maintainer  :  tom.davie@gmail.com
-- Stability   :  experimental
-- 
-- Common pieces of parsers for obj model parsing
----------------------------------------------------------------------
module Graphics.Formats.Obj.ParserBits
       (unsafeReadFloat,unsafeRFloat
       ,anyOf
       ,consumeWS,firstWord, bsWords,removeComments,parseName) where

import Foreign
import Foreign.C.String

import Data.ByteString.Internal
import qualified Data.ByteString.Char8  as CBS
import qualified Data.ByteString.Unsafe as BS

import Data.Maybe

import Control.Applicative hiding ((<|>))
import Control.Applicative.Infix

unsafeReadFloat :: CBS.ByteString -> Float
unsafeReadFloat =
  fst . maybe (error "unsafeReadFloat: No float to read.") id . unsafeRFloat

foreign import ccall unsafe "stdlib.h strtof" 
    c_strtof :: CString -> Ptr CString -> IO Float

-- | Bare bones, unsafe wrapper for strtof. This provides a non-copying
--   direct parsing of Float values from a ByteString. It uses strtof
--   directly on the bytestring buffer. strtof requires the string to be
--   null terminated, or for a guarantee that parsing will find a floating
--   point value before the end of the string.
--   Taken from Bytestring's ReadDouble
unsafeRFloat :: ByteString -> Maybe (Float, ByteString)
unsafeRFloat b | CBS.null b = Nothing
unsafeRFloat b = inlinePerformIO $
    alloca $ \resptr ->
    BS.unsafeUseAsCString b $ \ptr ->
      do -- copy just the bytes we want to parse
        d      <- c_strtof ptr resptr  -- 
        newPtr <- peek resptr
        return $! case d of
            0 | newPtr == ptr -> Nothing
            _ | otherwise  ->
                    let rest = BS.unsafeDrop (newPtr `minusPtr` ptr) b
                        z    = realToFrac d
                    in  z `seq` rest `seq` Just $! (z, rest)
{-# INLINE unsafeReadFloat #-}

anyOf :: [a -> Bool] -> a -> Bool
anyOf = foldr (liftA2 (||)) (const False)

bsWords :: CBS.ByteString -> [CBS.ByteString]
bsWords =
  filter ((>0) . CBS.length) . CBS.splitWith ((==' ') <^(||)^> (=='\t'))

removeComments :: CBS.ByteString -> CBS.ByteString
removeComments bs = case CBS.split '#' bs of
                      []    -> CBS.empty
                      (x:_) -> x

consumeWS :: CBS.ByteString -> CBS.ByteString
consumeWS = CBS.dropWhile ((==' ') <^(||)^> (=='\t'))

firstWord :: CBS.ByteString -> CBS.ByteString
firstWord = CBS.takeWhile (not . anyOf [(==' '), (=='\t'), (=='\n'), (=='\r')])

parseName :: CBS.ByteString -> CBS.ByteString
parseName = firstWord . consumeWS