{-# LANGUAGE BangPatterns #-}
module PCD.Internal.AsciiParsers where
import Control.Applicative
import Control.Lens ((^.))
import Data.Attoparsec.Text (double, count, skipSpace)
import qualified Data.Attoparsec.Text.Lazy as ATL
import qualified Data.Text.Lazy.IO as TL
import qualified Data.Vector as B
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Generic.Mutable as GM
import System.IO (Handle)

import PCD.Header (Header, FieldType, pointParser, points)
import PCD.Internal.Types (V3(..), V4(..))

-- |Read point data using a user-supplied ASCII point parser.
readPoints :: (G.Vector v a) => 
                   Header -> Handle -> ATL.Parser a -> IO (v a)
readPoints pcd h p = aux <$> TL.hGetContents h
  where n = fromIntegral $ pcd^.points
        aux t0 = G.create $
                 do v <- GM.new n
                    let write = GM.write v
                        go !i !t
                          | i == n = return v
                          | otherwise = case ATL.parse p t of
                                          ATL.Done !t' !pt -> write i pt >> 
                                                              go (i+1) t'
                                          ATL.Fail _ _ msg -> error msg
                    go 0 t0

-- |Load points of arbitrary dimension into a boxed vector with a
-- 'B.Vector' of 'FieldType' as the point representation.
readPointsDefault :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))
readPointsDefault pcd h = readPoints pcd h $ B.fromList <$> pointParser pcd

-- |Parse 3D points serialized in ASCII.
readXYZ :: Fractional a => ATL.Parser (V3 a)
readXYZ = (\[x,y,z] -> V3 x y z) <$> 
          count 3 ((realToFrac <$> double) <* skipSpace)

-- |Parse 4D points serialized to ASCII. This is useful for points
-- with X,Y,Z, and RGB fields each represented by a single float.
readXYZW :: Fractional a => ATL.Parser (V4 a)
readXYZW = (\[x,y,z,w] -> V4 x y z w) <$>
           count 4 ((realToFrac <$> double) <* skipSpace)