{-# LANGUAGE FlexibleInstances #-}
module BCM.Binary where

import Control.Applicative ((<$>))
import Foreign
import System.IO
import System.IO.Unsafe (unsafePerformIO)

import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Storable as S

import qualified Data.Matrix.Dense.Generic as DM
import qualified Data.Matrix.Symmetric as DS
import qualified Data.Matrix.Sparse.Generic as SM

class Swappable a where
    byteSwap :: a -> a

instance Swappable Double where
    byteSwap x = unsafePerformIO $ with x $ \ptr -> do
        word64 <- byteSwap64 <$> peek (castPtr ptr)
        poke (castPtr ptr) word64
        peek ptr 
    {-# INLINE byteSwap #-}

instance Swappable Int64 where
    byteSwap = fromIntegral . byteSwap64 . fromIntegral
    {-# INLINE byteSwap #-}

class BinaryData a where
    hGetData :: Handle -> Bool -> IO a
    hPutData :: Handle -> a -> IO ()
    size :: a -> Int

instance BinaryData Int64 where
    hGetData hdl = fmap fromIntegral . (hGetData hdl :: Bool -> IO Word64)
    hPutData hdl = (hPutData hdl :: Word64 -> IO ()). fromIntegral
    size _ = 8
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

instance BinaryData Double where
    hGetData hdl byteSwapped = alloca $ \ptr -> do
        hGetBuf hdl ptr 8
        word64 <- if byteSwapped
                     then byteSwap64 <$> peek ptr
                     else peek ptr
        poke ptr word64
        peek $ castPtr ptr
    hPutData hdl x = with x $ \ptr -> hPutBuf hdl ptr 8
    size _ = 8
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

instance BinaryData Word64 where
    hGetData hdl byteSwapped = alloca $ \ptr -> do
        hGetBuf hdl ptr 8
        if byteSwapped
           then byteSwap64 <$> peek ptr
           else peek ptr
    hPutData hdl x = with x $ \ptr -> hPutBuf hdl ptr 8
    size _ = 8
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

instance BinaryData Word32 where
    hGetData hdl byteSwapped = alloca $ \ptr -> do
        hGetBuf hdl ptr 4
        if byteSwapped
           then byteSwap32 <$> peek ptr
           else peek ptr
    hPutData hdl x = with x $ \ptr -> hPutBuf hdl ptr 4
    size _ = 4
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

d_matrix_magic :: Word32
d_matrix_magic = 0x22D20B77

instance BinaryData (DM.Matrix U.Vector Double) where
    hPutData hdl (DM.Matrix r c _ _ vec) = do
        hPutData hdl d_matrix_magic
        hPutData hdl (fromIntegral r :: Int64)
        hPutData hdl (fromIntegral c :: Int64)
        G.mapM_ (hPutData hdl) vec

    hGetData hdl _ = do
        magic <- hGetData hdl False
        let byteSwapped | magic == d_matrix_magic = False
                        | byteSwap32 magic == d_matrix_magic = True
                        | otherwise = error "Read matrix fail: wrong signature"
        r <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        c <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        vec <- hGetDoubleVector hdl (r*c)
        if byteSwapped
           then return $ DM.Matrix r c c 0 $ G.map byteSwap $ G.convert vec
           else return $ DM.Matrix r c c 0 $ G.convert vec
    size = undefined
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

ds_matrix_magic :: Word32
ds_matrix_magic = 0x33D31A66

instance BinaryData (DS.SymMatrix U.Vector Double) where
    hPutData hdl (DS.SymMatrix n vec) = do
        hPutData hdl ds_matrix_magic
        hPutData hdl (fromIntegral n :: Int64)
        G.mapM_ (hPutData hdl) vec

    hGetData hdl _ = do
        magic <- hGetData hdl False
        let byteSwapped | magic == ds_matrix_magic = False
                        | byteSwap32 magic == ds_matrix_magic = True
                        | otherwise = error "Read matrix fail: wrong signature"
        n <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        let len = ((n+1)*n) `shiftR` 1
        vec <- hGetDoubleVector hdl len
        if byteSwapped
           then return $ DS.SymMatrix n $ G.map byteSwap $ G.convert vec
           else return $ DS.SymMatrix n $ G.convert vec
    size = undefined
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

sp_matrix_magic :: Word32
sp_matrix_magic = 0x177BFFA0

instance BinaryData (SM.CSR U.Vector Double) where
    hPutData hdl (SM.CSR r c vec ci rp) = do
        hPutData hdl sp_matrix_magic
        hPutData hdl (fromIntegral r :: Int64)
        hPutData hdl (fromIntegral c :: Int64)
        hPutData hdl (fromIntegral n :: Int64)
        G.mapM_ (hPutData hdl) vec
        G.mapM_ (hPutData hdl . (fromIntegral :: Int -> Int64)) ci
        G.mapM_ (hPutData hdl . (fromIntegral :: Int -> Int64)) rp
      where
        n = G.length vec

    hGetData hdl _ = do
        magic <- hGetData hdl False
        let byteSwapped | magic == sp_matrix_magic = False
                        | byteSwap32 magic == sp_matrix_magic = True
                        | otherwise = error "Read matrix fail: wrong signature"
        r <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        c <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        n <- fromIntegral <$> (hGetData hdl byteSwapped :: IO Int64)
        vec <- G.convert <$> hGetDoubleVector hdl n
        ci <- G.convert <$> hGetIntVector hdl n
        rp <- G.convert <$> hGetIntVector hdl c
        if byteSwapped
           then return $ SM.CSR r c (G.map byteSwap vec)
                                    (G.map (fromIntegral . (byteSwap :: Int64 -> Int64) . fromIntegral) ci)
                                    (G.map (fromIntegral . (byteSwap :: Int64 -> Int64) . fromIntegral) rp)
           else return $ SM.CSR r c vec ci rp
    {-# INLINE hGetData #-}
    {-# INLINE hPutData #-}

    size (SM.CSR _ _ vec ci rp) = 28 + 8 * (G.length vec + G.length ci + G.length rp)


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

hGetIntVector :: Handle -> Int -> IO (S.Vector Int)
hGetIntVector hdl n = do
    ptr <- mallocBytes (n*8)
    hGetBuf hdl ptr (n*8)
    ptr' <- newForeignPtr_ ptr
    return $ S.unsafeFromForeignPtr0 ptr' n
{-# INLINE hGetIntVector #-}

hGetDoubleVector :: Handle -> Int -> IO (S.Vector Double)
hGetDoubleVector hdl n = do
    ptr <- mallocBytes (n*8)
    hGetBuf hdl ptr (n*8)
    ptr' <- newForeignPtr_ ptr
    return $ S.unsafeFromForeignPtr0 ptr' n
{-# INLINE hGetDoubleVector #-}
