module DarkPlaces.Binary  where
import Control.Applicative
import qualified Data.ByteString.Lazy as BL
import Prelude hiding (getLine)
import Data.Binary.Get
import Data.Binary.IEEE754
import Data.Int
import DarkPlaces.Types


maxTrackLen :: Int64
maxTrackLen = 8


getQVector :: ProtocolVersion -> Get QVector
getQVector p = consQVector <$> cords <*> cords <*> cords
  where
    cords = getCoord p


getQVector32f :: Get QVector
getQVector32f = consQVector <$> getFloat32le <*> getFloat32le <*> getFloat32le


getCoord13i :: Get Float
getCoord13i = do
    d <- getInt16le
    return $ (fromIntegral d) * (1.0 / 8.0)


getCoord16i :: Get Float
getCoord16i = fromIntegral <$> getInt16le


getCoord32f :: Get Float
getCoord32f = getFloat32le


getCoord :: ProtocolVersion -> Get Float
getCoord p
    | p `elem` quakes ++ neharaFamily = getCoord13i
    | p `elem` [(ProtocolDarkplaces2)..(ProtocolDarkplaces4)] = getCoord16i
    | otherwise = getCoord32f -- for ProtocolDarkplaces1 and bigger then 4
  where
    quakes = [ProtocolQuake, ProtocolQuakeDP, ProtocolQuakeWorld]
    neharaFamily = [(ProtocolNehahraMovie)..(ProtocolNehahraBJP3)]


getLine :: Get BL.ByteString
getLine = do
    b <- getWord8
    if b == 10  -- 10 is '\n'
        then return $ BL.singleton b
        else BL.cons' b <$> getLine


getLineLimited :: Int64 -> Get BL.ByteString
getLineLimited limit | limit <= 0 = fail "Line to long"
                     | otherwise = do
    b <- getWord8
    if b == 10
        then return $ BL.singleton b
        else BL.cons' b <$> getLineLimited (limit - 1)


getStringList :: Get [BL.ByteString]
getStringList = do
    str <- getLazyByteStringNul
    if BL.null str
        then return []
        else (str :) <$> getStringList


-- signed char
getInt8 :: Get Int8
getInt8 = fromIntegral <$> getWord8

-- signed short
getInt16le :: Get Int16
getInt16le = fromIntegral <$> getWord16le

-- signed int
getInt32le :: Get Int32
getInt32le = fromIntegral <$> getWord32le

getAngle8i :: Get Float
getAngle8i = (360.0 / 256.0 *) . fromIntegral <$> getInt8

getAngle16i :: Get Float
getAngle16i = (360.0 / 65536.0 *) . fromIntegral <$> getInt16le

getCord16i :: Get Float
getCord16i = fromIntegral <$> getInt16le

-- unsigned char to int
getWord8asInt :: Get Int
getWord8asInt = fromIntegral <$> getWord8

-- signed char to int
getInt8asInt :: Get Int
getInt8asInt = fromIntegral <$> getInt8

getInt16asInt :: Get Int
getInt16asInt = fromIntegral <$> getInt16le

getWord16asInt :: Get Int
getWord16asInt = fromIntegral <$> getWord16le

getLineAndRemaining :: Get (BL.ByteString, Int64)
getLineAndRemaining = getLine >>= \l -> bytesRead >>= \b -> return (l, b)

getLineLAndRemaining :: Int64 -> Get (BL.ByteString, Int64)
getLineLAndRemaining n = getLineLimited n >>= \l -> bytesRead >>= \b -> return (l, b)

splitAtTrack :: BL.ByteString -> Either ErrorInfo (BL.ByteString, BL.ByteString)
splitAtTrack file_data = case either_track of
    Left (_, offset, msg) -> Left (offset, msg)
    Right (_, _, (line, drop_bytes)) -> Right (line, BL.drop drop_bytes file_data)
  where
    either_track = runGetOrFail (getLineLAndRemaining maxTrackLen) file_data

skipTrack :: BL.ByteString -> Either ErrorInfo BL.ByteString
skipTrack file_data = snd <$> splitAtTrack file_data
