module Database.TokyoCabinet.Storable where

import Data.Int
import Data.Char
import Data.Word

import Foreign.Ptr
import Foreign.C.Types
import Foreign.C.String
import Foreign.Marshal (peekArray, free)
import Foreign.Marshal.Array (withArray)
import Data.ByteString.Unsafe

import qualified Foreign.Storable as F
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as LC
import qualified Foreign.Marshal.Utils as U

type PtrLen = (Ptr Word8, CInt)

liftPL :: (a -> (CStringLen -> IO b) -> IO b) -> a -> (PtrLen -> IO b) -> IO b
liftPL f val action =
    f val $ \(buf, siz) ->
        action (castPtr buf, fromIntegral siz)

class (Show a, Read a) => Storable a where
    withPtrLen :: a -> (PtrLen -> IO b) -> IO b
    withPtrLenL :: [a] -> (PtrLen -> IO b) -> IO b

    peekPtrLenL :: PtrLen -> a -> IO [a]
    peekPtrLen :: PtrLen -> IO a

    toInt64 :: a -> Int64
    toInt64L :: [a] -> Int64

    fromString :: String -> a
    fromStringL :: String -> [a]

    toInt64 = read . show
    toInt64L = read . concatMap show

    fromString = read
    fromStringL = (:[]) . fromString

    withPtrLenL = undefined
    peekPtrLenL = undefined

instance Storable C.ByteString where
    withPtrLen = liftPL unsafeUseAsCStringLen
    peekPtrLen (p, len) =
        unsafePackCStringFinalizer p (fromIntegral len) (free p)
    toInt64 = read . C.unpack
    fromString = C.pack

instance Storable L.ByteString where
    withPtrLen = liftPL unsafeUseAsCStringLen . C.concat . L.toChunks
    peekPtrLen (p, len) = do xs <- peekArray (fromIntegral len) p
                             free p
                             return $ L.pack xs
    toInt64 = read . LC.unpack
    fromString = LC.pack

withPtrLenForFStorable :: (F.Storable a) => a -> (PtrLen -> IO b) -> IO b
withPtrLenForFStorable n f =
    U.with n $ \p -> f (castPtr p, fromIntegral $ F.sizeOf n)

peekPtrLenForFStorable :: (F.Storable a) => PtrLen -> IO a
peekPtrLenForFStorable (p, _) = do val <- F.peek (castPtr p)
                                   free p
                                   return val

withPtrLenLForFStorable :: (F.Storable a) => [a] -> (PtrLen -> IO b) -> IO b
withPtrLenLForFStorable xs f =
    withArray xs $ \p ->
        f (castPtr p, fromIntegral $ (F.sizeOf $ head xs) * length xs)

peekPtrLenLForFStorable :: (F.Storable a) => PtrLen -> a -> IO [a]
peekPtrLenLForFStorable (p, size) x = do
  peekArray (fromIntegral size `div` (F.sizeOf x)) (castPtr p)

instance Storable Char where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = liftPL withCStringLen
    peekPtrLenL (buf, siz) _ =
        do val <- peekCStringLen (castPtr buf, fromIntegral siz)
           free buf
           return val

    toInt64 c | isDigit c = fromIntegral $ digitToInt c
    toInt64 c | otherwise = fromIntegral $ ord c

    toInt64L cs = read cs

    fromString str = read ('\'':str ++ "'")
    fromStringL = id

instance Storable CInt where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Int where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable CDouble where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Double where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable CFloat where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Float where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Int8 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Int16 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Int32 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Int64 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Word8 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Word16 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Word32 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance Storable Word64 where
    withPtrLen = withPtrLenForFStorable
    peekPtrLen = peekPtrLenForFStorable
    withPtrLenL = withPtrLenLForFStorable
    peekPtrLenL = peekPtrLenLForFStorable

instance (F.Storable a, Storable a) => Storable [a] where
    withPtrLen = withPtrLenL
    peekPtrLen xs = peekPtrLenL xs undefined
    toInt64 = toInt64L
    fromString = fromStringL