module Data.Repa.Array.Auto.XSV
        ( getArrayFromXSV,      hGetArrayFromXSV
        , putArrayAsXSV,        hPutArrayAsXSV)
where
import Data.Repa.Array.Auto.IO
import Data.Repa.Array.Auto.Base
import Data.Repa.Array.Generic.Convert
import Data.Char
import qualified Data.Repa.Array.Material.Auto          as A
import qualified Data.Repa.Array.Material.Nested        as A
import qualified Data.Repa.Array.Meta                   as A
import qualified Data.Repa.Array.Generic                as A
import qualified System.IO                              as S
import qualified System.FileLock                        as S


---------------------------------------------------------------------------------------------------
-- | Read a XSV file as a nested array.
--   We get an array of rows:fields:characters.
getArrayFromXSV
        :: Char                 -- ^ Field separator character, eg '|', ',' or '\t'.
        -> FilePath             -- ^ Source file handle.
        -> IO (Array (Array (Array Char)))

getArrayFromXSV !cSep !path
 = S.withFileLock path S.Shared
 $ \_lock -> do
        h       <- S.openFile path S.ReadMode
        arr     <- hGetArrayFromXSV cSep h
        S.hClose h
        return arr


-- | Read an XSV file as a nested array.
--   We get an array of rows:fields:characters.
hGetArrayFromXSV 
        :: Char                 -- ^ Field separator character, eg '|', ',' or '\t'.
        -> S.Handle             -- ^ Source file handle.
        -> IO (Array (Array (Array Char)))

hGetArrayFromXSV !cSep !hIn
 = do   
        -- Find out how much data there is remaining in the file.
        start     <- S.hTell hIn
        S.hSeek hIn S.SeekFromEnd 0
        end       <- S.hTell hIn
        let !len  =  end - start
        S.hSeek hIn S.AbsoluteSeek start

        -- Read array as Word8s.
        !arr8   <- hGetArray hIn (fromIntegral len)

        -- Rows are separated by new lines,
        -- fields are separated by the given separator character.
        let !nl = fromIntegral $ ord '\n'
        let !nc = fromIntegral $ ord cSep

        -- Split XSV file into rows and fields.
        -- Convert element data from Word8 to Char.
        -- Chars take 4 bytes each, but are standard Haskell and pretty
        -- print properly. We've done the dicing on the smaller Word8
        -- version, and now map across the elements vector in the array
        -- to do the conversion.
        let !arrChar 
                = A.mapElems 
                        (A.mapElems (A.computeS A.F . A.map (chr . fromIntegral))) 
                        (A.diceSep nc nl arr8)

        return $ convert arrChar


--------------------------------------------------------------------------------------------------
-- | Write a nested array as an XSV file.
--
--   The array contains rows:fields:characters.
putArrayAsXSV
        :: Char                         -- ^ Separator character, eg '|', ',' or '\t'
        -> FilePath                     -- ^ Source file handle.
        -> Array (Array (Array Char))   -- ^ Array of row, field, character.
        -> IO ()

putArrayAsXSV !cSep !path !arrChar
 = S.withFileLock path S.Exclusive
 $ \_lock -> do
        h       <- S.openFile path S.WriteMode
        hPutArrayAsXSV cSep h arrChar
        S.hClose h


-- | Write a nested array as an XSV file.
--
--   The array contains rows:fields:characters.
hPutArrayAsXSV
        :: Char                         -- ^ Separator character, eg '|', ',' or '\t'
        -> S.Handle                     -- ^ Source file handle.
        -> Array (Array (Array Char))   -- ^ Array of row, field, character.
        -> IO ()

hPutArrayAsXSV !cSep !hOut !arrChar
 = do
        -- Concat result back into Word8s
        let !arrC       = A.fromList A.U [cSep]
        let !arrNL      = A.fromList A.U ['\n']

        let !arrOut     
                = A.mapS A.A (fromIntegral . ord) 
                $ A.concat A.U 
                $ A.mapS A.B (\  arrFields
                              -> A.concat A.U $ A.fromList A.B
                                        [ A.intercalate A.U arrC arrFields, arrNL])
                $ arrChar

        hPutArray hOut arrOut
{-# INLINE hPutArrayAsXSV #-}