-- | Interface to Hash based DBM. See also,
-- <http://tokyocabinet.sourceforge.net/spex-en.html#tchdbapi> for details
module Database.TokyoCabinet.HDB
    (
    -- $doc
    -- * Constructors
      HDB
    , ECODE(..)
    , OpenMode(..)
    , TuningOption(..)
    -- * Basic API (tokyocabinet.idl compliant)
    , new
    , delete
    , ecode
    , errmsg
    , tune
    , setcache
    , setxmsiz
    , open
    , close
    , put
    , putkeep
    , putcat
    , putasync
    , out
    , get
    , vsiz
    , iterinit
    , iternext
    , fwmkeys
    , addint
    , adddouble
    , sync
    , optimize
    , vanish
    , copy
    , tranbegin
    , trancommit
    , tranabort
    , path
    , rnum
    , fsiz
    )
    where


import Foreign.ForeignPtr

import Data.Int
import Data.Word

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

-- $doc
-- Example
--
-- @
--    import Control.Monad
--    import Database.TokyoCabinet.HDB
-- @
--
-- @
--    main = do hdb <- new
--              -- open the database
--              open hdb \"casket.tch\" [OWRITER, OCREAT] >>= err hdb
--              -- store records
--              puts hdb [(\"foo\", \"hop\"), (\"bar\", \"step\"), (\"baz\", \"jump\")] >>=
--                       err hdb . (all id)
--              -- retrieve records
--              get_print hdb \"foo\"
--              -- traverse records
--              iterinit hdb
--              iter hdb >>= mapM_ (\k -> putStr (k++\":\") >> get_print hdb k)
--              -- close the database
--              close hdb >>= err hdb
--        where
--          puts :: HDB -> [(String, String)] -> IO [Bool]
--          puts hdb = mapM (uncurry $ put hdb)
-- @
--
-- @
--          get_print :: HDB -> String -> IO ()
--          get_print hdb key = get hdb key >>=
--                              maybe (error \"something goes wrong\") putStrLn
-- @
--
-- @  
--          err :: HDB -> Bool -> IO ()
--          err hdb = flip unless $ ecode hdb >>= error . show
-- @
--
-- @    
--          iter :: HDB -> IO [String]
--          iter hdb = iternext hdb >>=
--                     maybe (return []) (\x -> return . (x:) =<< iter hdb)
-- @
--

data HDB = HDB { unTCHDB :: !(ForeignPtr HDB') }

-- | Create a Hash database object. 
new :: IO HDB
new = HDB `fmap` (c_tchdbnew >>= newForeignPtr tchdbFinalizer)

-- | Free HDB resource forcibly. 
-- HDB 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 HDB object. Its behavior is undefined.
delete :: HDB -> IO ()
delete hdb = finalizeForeignPtr (unTCHDB hdb)

-- | Return the last happened error code.
ecode :: HDB -> IO ECODE
ecode hdb = cintToError `fmap` withForeignPtr (unTCHDB hdb) c_tchdbecode

-- | Set the tuning parameters.
tune :: HDB   -- ^ HDB object
     -> Int64 -- ^ the number of elements of the bucket array.
     -> Int8  -- ^ the size of record alignment by power of 2. 
     -> Int8  -- ^ the maximum number of elements of the free block
              -- pool by power of 2.
     -> [TuningOption] -- ^ tuning options.
     -> IO Bool -- ^ if successful, the return value is True.
tune hdb bnum apow fpow options =
    withForeignPtr (unTCHDB hdb) $ \p ->
        c_tchdbtune p bnum apow fpow (combineTuningOption options)

-- | Set the caching parameters.
setcache :: HDB   -- ^ HDB object.
         -> Int32   -- ^ the maximum number of records to be cached.
         -> IO Bool -- ^ if successful, the return value is True.
setcache hdb rcnum = withForeignPtr (unTCHDB hdb) (flip c_tchdbsetcache rcnum)

-- | Set the size of extra mapped memory.
setxmsiz :: HDB -> Int64 -> IO Bool
setxmsiz hdb xmsiz = withForeignPtr (unTCHDB hdb) (flip c_tchdbsetxmsiz xmsiz)

-- | Open a database file.
open :: HDB -> String -> [OpenMode] -> IO Bool
open = openHelper c_tchdbopen unTCHDB combineOpenMode

-- | Close the database file.
close :: HDB -> IO Bool
close hdb = withForeignPtr (unTCHDB hdb) c_tchdbclose

-- | Stora a record (key-value pair) on HDB.  Key and value type must
-- be instance of Storable class.  Usually, we can use `String',
-- `ByteString' for key, `String', `ByteString', `Int', `Double' for
-- value.
put :: (Storable k, Storable v) => HDB -> k -> v -> IO Bool
put = putHelper c_tchdbput unTCHDB

-- | Store a new record. If a record with the same key exists in the
-- database, this function has no effect.
putkeep :: (Storable k, Storable v) => HDB -> k -> v -> IO Bool
putkeep = putHelper c_tchdbputkeep unTCHDB

-- | Concatenate a value at the end of the existing record.
putcat :: (Storable k, Storable v) => HDB -> k -> v -> IO Bool
putcat = putHelper c_tchdbputcat unTCHDB

-- | Store a record into a hash database object in asynchronous fashion.
putasync :: (Storable k, Storable v) => HDB -> k -> v -> IO Bool
putasync = putHelper c_tchdbputasync unTCHDB

-- | Delete a record.
out :: (Storable k) => HDB -> k -> IO Bool
out = outHelper c_tchdbout unTCHDB

-- | Return the value of record. 
get :: (Storable k, Storable v) => HDB -> k -> IO (Maybe v)
get = getHelper c_tchdbget unTCHDB

-- | Return the byte size of value in a record.
vsiz :: (Storable k) => HDB -> k -> IO (Maybe Int)
vsiz = vsizHelper c_tchdbvsiz unTCHDB

-- | Initialize the iterator of a HDB object.
iterinit :: HDB -> IO Bool
iterinit hdb = withForeignPtr (unTCHDB hdb) c_tchdbiterinit

-- | Return the next key of the iterator of a HDB object.
iternext :: (Storable k) => HDB -> IO (Maybe k)
iternext = iternextHelper c_tchdbiternext unTCHDB

-- | Return list of forward matched keys.
fwmkeys :: (Storable k1, Storable k2, Sequence q) =>
           HDB -> k1 -> Int -> IO (q k2)
fwmkeys = fwmHelper c_tchdbfwmkeys unTCHDB

-- | Increment the corresponding value. (The value specified by a key
-- is treated as integer.)
addint :: (Storable k) => HDB -> k -> Int -> IO (Maybe Int)
addint = addHelper c_tchdbaddint unTCHDB fromIntegral fromIntegral (== cINT_MIN)

-- | Increment the corresponding value. (The value specified by a key
-- is treated as double.)
adddouble :: (Storable k) => HDB -> k -> Double -> IO (Maybe Double)
adddouble = addHelper c_tchdbadddouble unTCHDB realToFrac realToFrac isNaN

-- | Synchronize updated contents of a database object with the file
-- and the device.
sync :: HDB -> IO Bool
sync hdb = withForeignPtr (unTCHDB hdb) c_tchdbsync

-- |  Optimize the file of a Hash database object.
optimize :: HDB   -- ^ HDB object
         -> Int64 -- ^ the number of elements of the bucket array.
         -> Int8  -- ^ the size of record alignment by power of 2. 
         -> Int8  -- ^ the maximum number of elements of the free block
                  -- pool by power of 2.
         -> [TuningOption] -- ^ tuning options.
         -> IO Bool -- ^ if successful, the return value is True.
optimize hdb bnum apow fpow options = 
    withForeignPtr (unTCHDB hdb) $ \p ->
        c_tchdboptimize p bnum apow fpow (combineTuningOption options)

-- | Delete all records.
vanish :: HDB -> IO Bool
vanish hdb = withForeignPtr (unTCHDB hdb) c_tchdbvanish

-- | Copy the database file.
copy :: HDB -> String -> IO Bool
copy = copyHelper c_tchdbcopy unTCHDB

-- | Begin the transaction.
tranbegin :: HDB -> IO Bool
tranbegin hdb = withForeignPtr (unTCHDB hdb) c_tchdbtranbegin

-- | Commit the transaction.
trancommit :: HDB -> IO Bool
trancommit hdb = withForeignPtr (unTCHDB hdb) c_tchdbtrancommit

-- | Abort the transaction.
tranabort :: HDB -> IO Bool
tranabort hdb = withForeignPtr (unTCHDB hdb) c_tchdbtranabort

-- | Return the file path of currentry opened database.
path :: HDB -> IO (Maybe String)
path = pathHelper c_tchdbpath unTCHDB

-- | Return the number of records in the database.
rnum :: HDB -> IO Word64
rnum hdb = withForeignPtr (unTCHDB hdb) c_tchdbrnum

-- | Return the size of the database file.
fsiz :: HDB -> IO Word64
fsiz hdb = withForeignPtr (unTCHDB hdb) c_tchdbfsiz