module Database.TokyoCabinet.Sequence where

import Foreign.Ptr
import Foreign.Storable (peek)
import Foreign.Marshal (alloca, mallocBytes, copyBytes)
import Foreign.ForeignPtr

import Database.TokyoCabinet.List.C
import Database.TokyoCabinet.Storable

class Sequence a where
    withList  :: (Storable s) => a s -> (Ptr LIST -> IO b) -> IO b
    peekList' :: (Storable s) => Ptr LIST -> IO (a s)
    empty :: (Storable s) => IO (a s)
    smap :: (Storable s1, Storable s2) => (s1 -> s2) -> a s1 -> IO (a s2)

instance Sequence List where
    withList xs action = withForeignPtr (unTCList xs) action
    peekList' tcls = List `fmap` newForeignPtr tclistFinalizer tcls
    empty = List `fmap` (c_tclistnew >>= newForeignPtr tclistFinalizer)
    smap f tcls =
        withForeignPtr (unTCList tcls) $ \tcls' ->
            alloca $ \sizbuf ->
                do num <- c_tclistnum tcls'
                   vals <- c_tclistnew
                   loop tcls' 0 num sizbuf vals
        where
          loop tcls' n num sizbuf acc
                | n < num = do vbuf <- c_tclistval tcls' n sizbuf
                               vsiz <- peek sizbuf
                               buf <- mallocBytes (fromIntegral vsiz)
                               copyBytes buf vbuf (fromIntegral vsiz)
                               val <- f `fmap` peekPtrLen (buf, vsiz)
                               withPtrLen val $ uncurry (c_tclistpush acc)
                               loop tcls' (n+1) num sizbuf acc
                | otherwise = List `fmap` newForeignPtr tclistFinalizer acc

instance Sequence [] where
    withList xs action =
        do list <- c_tclistnew
           mapM_ (push list) xs
           result <- action list
           c_tclistdel list
           return result
        where
          push list val = withPtrLen val $ uncurry (c_tclistpush list)

    peekList' tcls = do
        vals <- peekList'' tcls []
        c_tclistdel tcls
        return vals
      where
        peekList'' lis acc =
            alloca $ \sizbuf ->
                do val <- c_tclistpop lis sizbuf
                   siz <- peek sizbuf
                   if val == nullPtr
                     then return acc
                     else do elm <- peekPtrLen (val, siz)
                             peekList'' lis (elm:acc)

    empty = return []
    smap f = return . (map f)