-- | Interface to TC's Abstract DataBase. See also,
-- <http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi> for details
module Database.TokyoCabinet.ADB
    (
    -- $doc
      ADB
    , ECODE(..)
    , new
    , delete
    , open
    , close
    , put
    , putkeep
    , putcat
    , out
    , get
    , vsiz
    , iterinit
    , iternext
    , fwmkeys
    , addint
    , adddouble
    , sync
    , optimize
    , vanish
    , copy
    , tranbegin
    , trancommit
    , tranabort
    , path
    , rnum
    , size
    , misc
    ) where

import Data.Word

import Foreign.C.String
import Foreign.ForeignPtr

import Database.TokyoCabinet.ADB.C
import Database.TokyoCabinet.Error
import Database.TokyoCabinet.Internal
import Database.TokyoCabinet.Storable
import Database.TokyoCabinet.Sequence

-- $doc
-- Example
--
-- @
--   import Control.Monad
--   import Database.TokyoCabinet.ADB
-- @
--
-- @
--   main = do adb <- new
--             -- open the abstract database object
--             -- \"+\" means that the database will be an on-memory tree database
--             open adb \"+\" >>= err adb \"open failed\"
--             -- store records
--             puts adb [(\"foo\", \"hop\"), (\"bar\", \"step\"), (\"baz\", \"jump\")] >>=
--                      err adb \"put failed\" . (all id)
--             -- retrieve records
--             get_print adb \"foo\"
--             -- traverse records
--             iterinit adb
--             iter adb >>= mapM_ (\k -> putStr (k++\":\") >> get_print adb k)
--             -- close the database
--             close adb >>= err adb \"close failed\"
--       where
--         puts :: ADB -> [(String, String)] -> IO [Bool]
--         puts adb = mapM (uncurry $ put adb)
-- @
--
-- @
--         get_print :: ADB -> String -> IO ()
--         get_print adb key = get adb key >>=
--                             maybe (error \"something goes wrong\") putStrLn
-- @
--
-- @
--         err :: ADB -> String -> Bool -> IO ()
--         err adb msg = flip unless $ error msg
-- @
--
-- @
--         iter :: ADB -> IO [String]
--         iter adb = iternext adb >>=
--                    maybe (return []) (\x -> return . (x:) =<< iter adb)
-- @
--

data ADB = ADB { unTCADB :: !(ForeignPtr ADB') }

-- | Create a Abstract database object. 
new :: IO ADB
new = ADB `fmap` (c_tcadbnew >>= newForeignPtr tcadbFinalizer)

-- | Free ADB resource forcibly. 
-- ADB is kept by ForeignPtr, so Haskell runtime GC cleans up memory for
-- almost situation. Most always, you don't need to call this. 
-- After call this, you must not touch ADB object. Its behavior is undefined.
delete :: ADB -> IO ()
delete adb = finalizeForeignPtr (unTCADB adb)

-- | Open an abstract dataabse.
open :: ADB -> String -> IO Bool
open adb name = withForeignPtr (unTCADB adb) $ (withCString name) . c_tcadbopen

-- | Close an abstract database object.
close :: ADB -> IO Bool
close adb = withForeignPtr (unTCADB adb) c_tcadbclose

-- | Stora a record into an abstract database object. 
put :: (Storable k, Storable v) => ADB -> k -> v -> IO Bool
put = putHelper c_tcadbput unTCADB

-- | Store a new record into an abstract database object.
putkeep :: (Storable k, Storable v) => ADB -> k -> v -> IO Bool
putkeep = putHelper c_tcadbputkeep unTCADB

-- | Concatenate a value at the end of the existing record in an
-- abstract database object.
putcat :: (Storable k, Storable v) => ADB -> k -> v -> IO Bool
putcat = putHelper c_tcadbputcat unTCADB

-- | Remove a record of an abstract database object.
out :: (Storable k) => ADB -> k -> IO Bool
out = outHelper c_tcadbout unTCADB

-- | Retrieve a record in an abstract database object.
get :: (Storable k, Storable v) => ADB -> k -> IO (Maybe v)
get = getHelper c_tcadbget unTCADB

-- | Get the size of the value of a record in an abstract database object.
vsiz :: (Storable k) => ADB -> k -> IO (Maybe Int)
vsiz = vsizHelper c_tcadbvsiz unTCADB

-- | Initialize the iterator of an abstract database object.
iterinit :: ADB -> IO Bool
iterinit adb = withForeignPtr (unTCADB adb) c_tcadbiterinit

-- | Get the next key of the iterator of an abstract database object.
iternext :: (Storable k) => ADB -> IO (Maybe k)
iternext = iternextHelper c_tcadbiternext unTCADB

-- | Get forward matching keys in an abstract database object.
fwmkeys :: (Storable k1, Storable k2, Sequence q) =>
           ADB -> k1 -> Int -> IO (q k2)
fwmkeys = fwmHelper c_tcadbfwmkeys unTCADB

-- | Add an integer to a record in an abstract database object.
addint :: (Storable k) => ADB -> k -> Int -> IO (Maybe Int)
addint = addHelper c_tcadbaddint unTCADB fromIntegral fromIntegral (== cINT_MIN)

-- | Add a real number to a record in an abstract database object.
adddouble :: (Storable k) => ADB -> k -> Double -> IO (Maybe Double)
adddouble = addHelper c_tcadbadddouble unTCADB realToFrac realToFrac isNaN

-- | Synchronize updated contents of an abstract database object with
-- the file and the device.
sync :: ADB -> IO Bool
sync adb = withForeignPtr (unTCADB adb) c_tcadbsync

-- | Optimize the storage of an abstract database object.
optimize :: ADB -> String -> IO Bool
optimize adb params =
    withForeignPtr (unTCADB adb) $ (withCString params) . c_tcadboptimize

-- | Remove all records of an abstract database object.
vanish :: ADB -> IO Bool
vanish adb = withForeignPtr (unTCADB adb) c_tcadbvanish

-- | Copy the database file of an abstract database object.
copy :: ADB -> String -> IO Bool
copy = copyHelper c_tcadbcopy unTCADB

-- | Begin the transaction of an abstract database object.
tranbegin :: ADB -> IO Bool
tranbegin adb = withForeignPtr (unTCADB adb) c_tcadbtranbegin

-- | Commit the transaction of an abstract database object.
trancommit :: ADB -> IO Bool
trancommit adb = withForeignPtr (unTCADB adb) c_tcadbtrancommit

-- | Abort the transaction of an abstract database object.
tranabort :: ADB -> IO Bool
tranabort adb = withForeignPtr (unTCADB adb) c_tcadbtranabort

-- | Get the file path of an abstract database object.
path :: ADB -> IO (Maybe String)
path = pathHelper c_tcadbpath unTCADB

-- | Get the number of records of an abstract database object.
rnum :: ADB -> IO Word64
rnum adb = withForeignPtr (unTCADB adb) c_tcadbrnum

-- | Get the size of the database of an abstract database object.
size :: ADB -> IO Word64
size adb = withForeignPtr (unTCADB adb) c_tcadbsize

-- | Call a versatile function for miscellaneous operations of an
-- abstract database object.
misc :: (Storable a, Storable b, Sequence q1, Sequence q2) =>
        ADB -> String -> q1 a -> IO (q2 b)
misc adb name args =
    withForeignPtr (unTCADB adb) $ \adb' ->
        withCString name $ \name' ->
            withList args $ \args' -> do
              ret <- c_tcadbmisc adb' name' args'
              peekList' ret