{-# LINE 1 "Database/KyotoCabinet/Db.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls, DeriveDataTypeable,
{-# LINE 2 "Database/KyotoCabinet/Db.hsc" #-}
             ScopedTypeVariables #-}
{-
Copyright 2011, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
    * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-}


{-# LINE 35 "Database/KyotoCabinet/Db.hsc" #-}

-- | Kyoto Cabinet DB bindings. All IO monad functions can throw KcException.
--
-- This documentation is not a complete description of the Kyoto
-- Cabinet DB interface.  You will need to refer to Kyoto Cabinet DB's
-- C API documentation for the details: <http://fallabs.com/kyotocabinet/>

module Database.KyotoCabinet.Db (
  -- * Types
  KcDb,
  KcCur,
  KcError(..),
  KcTune(..),
  KcTuneType(..),
  KcLogger(..),
  KcLogLevel(..),
  KcOption(..),
  KcCompressor(..),
  KcComparator(..),
  KcOpenMode(..),
  KcMergeMode(..),
  KcException(..),
  KcVisitAction(..),
  KcVisitFull,
  KcVisitEmpty,
  KcFileProc,

  -- * Utilities
  -- | Most of these routines are probably not needed by haskell users, but are
  -- included for completeness.
  kcversion,
  kcmalloc,
  kcfree,
  kctime,
  kcatoi,
  kcatoix,
  kcatof,
  kchashmurmur,
  kchashfnv,
  kcnan,
  kcinf,
  kcchknan,
  kcchkinf,
  kcecodename,

  -- * DB Operations
  kcdbnew,
  kcdbdel,
  kcdbopen,
  kcdbclose,
  kcdbecode,
  kcdbemsg,
  kcdbaccept,
  kcdbacceptbulk,
  kcdbiterate,
  kcdbset,
  kcdbadd,
  kcdbreplace,
  kcdbappend,
  kcdbincrint,
  kcdbincrdouble,
  kcdbcas,
  kcdbremove,
  kcdbget,
  kcdbgetbuf,
  kcdbsetbulk,
  kcdbremovebulk,
  kcdbgetbulk,
  kcdbclear,
  kcdbsync,
  kcdbcopy,
  kcdbbegintran,
  kcdbbegintrantry,
  kcdbendtran,
  kcdbdumpsnap,
  kcdbloadsnap,
  kcdbcount,
  kcdbsize,
  kcdbpath,
  kcdbstatus,
  kcdbmatchprefix,
  kcdbmatchregex,
  kcdbmerge,

  -- * Cursor Operations
  kcdbcursor,
  kccurdel,
  kccuraccept,
  kccurremove,
  kccurgetkey,
  kccurgetvalue,
  kccurget,
  kccurjump,
  kccurjumpkey,
  kccurjumpback,
  kccurjumpbackkey,
  kccurstep,
  kccurstepback,
  kccurdb,
  kccurecode,
  kccuremsg,

  -- * Scoped Operations
  kcwithdbopen,
  kcwithdbcursor,
  kcwithdbtran
  ) where

import Control.Applicative
import Control.Exception
import Data.Bits
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe as BS
import Data.Typeable
import Data.Int
import Foreign.C
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import Foreign.Storable
import Foreign.Ptr
import Foreign.ForeignPtr
import Prelude hiding (catch)
--import Debug.Trace

--------------------------------------------------------------------------------
-- Structs

-- | Binary string of byte array.
newtype KcStr = KcStr BS.ByteString

instance Storable KcStr where
  sizeOf    _ = ((16))
{-# LINE 168 "Database/KyotoCabinet/Db.hsc" #-}
  alignment _ = alignment (undefined :: CSize)
  peek ptr = do
    b <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) ptr
{-# LINE 171 "Database/KyotoCabinet/Db.hsc" #-}
    s <- ((\hsc_ptr -> peekByteOff hsc_ptr 8)) ptr
{-# LINE 172 "Database/KyotoCabinet/Db.hsc" #-}
    KcStr <$> BS.unsafePackCStringFinalizer b s (kcfree b)
  poke ptr (KcStr bs) = do
    BS.unsafeUseAsCStringLen bs $ \(b, s) -> do
      ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) ptr b
{-# LINE 176 "Database/KyotoCabinet/Db.hsc" #-}
      ((\hsc_ptr -> pokeByteOff hsc_ptr 8)) ptr s
{-# LINE 177 "Database/KyotoCabinet/Db.hsc" #-}

withKcStrArray :: [BS.ByteString] -> (Ptr KcStr -> IO a) -> IO a
withKcStrArray strs f = do
  allocaArray (length strs) $ \arr -> do
    pokeArray arr (map KcStr strs)
    f arr

-- | Key-Value record.
newtype KcRec = KcRec (BS.ByteString, BS.ByteString)

instance Storable KcRec where
  sizeOf    _ = ((32))
{-# LINE 189 "Database/KyotoCabinet/Db.hsc" #-}
  alignment _ = alignment (undefined :: KcStr)
  peek ptr = do
    KcStr k <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) ptr
{-# LINE 192 "Database/KyotoCabinet/Db.hsc" #-}
    KcStr v <- ((\hsc_ptr -> peekByteOff hsc_ptr 16)) ptr
{-# LINE 193 "Database/KyotoCabinet/Db.hsc" #-}
    return $ KcRec (k, v)
  poke ptr (KcRec (k, v)) = do
    ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) ptr (KcStr k)
{-# LINE 196 "Database/KyotoCabinet/Db.hsc" #-}
    ((\hsc_ptr -> pokeByteOff hsc_ptr 16)) ptr (KcStr v)
{-# LINE 197 "Database/KyotoCabinet/Db.hsc" #-}

withKcRecArray :: [(BS.ByteString, BS.ByteString)] -> (Ptr KcRec -> IO a) -> IO a
withKcRecArray recs f = do
  allocaArray (length recs) $ \arr -> do
    pokeArray arr (map KcRec recs)
    f arr

bsListOfCStringArr :: Ptr CString -> CLLong -> IO [BS.ByteString]
bsListOfCStringArr strarr n = do
  strlist <- peekArray (fromIntegral n) strarr
  bssOfCstrs strlist

bssOfCstrs :: [CString] -> IO [BS.ByteString]
bssOfCstrs [] = return []
bssOfCstrs (h:t) = do
  hLen <- BS.c_strlen h
  bs' <- BS.unsafePackCStringFinalizer (castPtr h) (fromIntegral hLen) (kcfree h)
  rest <- bssOfCstrs t
  return $ bs' `seq` (bs':rest)

--------------------------------------------------------------------------------
-- Constants

-- | Error codes.
data KcError = KCESUCCESS   -- ^ success
             | KCENOIMPL    -- ^ not implemented
             | KCEINVALID   -- ^ invalid operation
             | KCENOREPOS   -- ^ no repository
             | KCENOPERM    -- ^ no permission
             | KCEBROKEN    -- ^ broken file
             | KCEDUPREC    -- ^ record duplication
             | KCENOREC     -- ^ no record
             | KCELOGIC     -- ^ logical inconsistency
             | KCESYSTEM    -- ^ system error
             | KCEMISC      -- ^ miscellaneous error
             | KCUNKNOWNERROR Int
             deriving (Eq, Show)

kcErrorOfNum :: CInt -> KcError
kcErrorOfNum (0) = KCESUCCESS
{-# LINE 237 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (1) = KCENOIMPL
{-# LINE 238 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (2) = KCEINVALID
{-# LINE 239 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (3) = KCENOREPOS
{-# LINE 240 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (4) = KCENOPERM
{-# LINE 241 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (5) = KCEBROKEN
{-# LINE 242 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (6) = KCEDUPREC
{-# LINE 243 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (7) = KCENOREC
{-# LINE 244 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (8) = KCELOGIC
{-# LINE 245 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (9) = KCESYSTEM
{-# LINE 246 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum (15) = KCEMISC
{-# LINE 247 "Database/KyotoCabinet/Db.hsc" #-}
kcErrorOfNum n = KCUNKNOWNERROR (fromIntegral n)

-- | Open modes.
data KcOpenMode = KCOREADER         -- ^ open as a reader
                | KCOWRITER         -- ^ open as a writer
                | KCOCREATE         -- ^ writer creating
                | KCOTRUNCATE       -- ^ writer truncating
                | KCOAUTOTRAN       -- ^ auto transaction
                | KCOAUTOSYNC       -- ^ auto synchronization
                | KCONOLOCK         -- ^ open without locking
                | KCOTRYLOCK        -- ^ lock without blocking
                | KCONOREPAIR       -- ^ open without auto repair
                deriving (Eq, Show)

numOfKcOpenMode :: KcOpenMode -> Int
numOfKcOpenMode KCOREADER = (1)
{-# LINE 263 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOWRITER = (2)
{-# LINE 264 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOCREATE = (4)
{-# LINE 265 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOTRUNCATE = (8)
{-# LINE 266 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOAUTOTRAN = (16)
{-# LINE 267 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOAUTOSYNC = (32)
{-# LINE 268 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCONOLOCK = (64)
{-# LINE 269 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCOTRYLOCK = (128)
{-# LINE 270 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcOpenMode KCONOREPAIR = (256)
{-# LINE 271 "Database/KyotoCabinet/Db.hsc" #-}

numOfOpenModes :: [KcOpenMode] -> CUInt
numOfOpenModes mode = fromIntegral $ foldr (\m acc -> numOfKcOpenMode m .|. acc) 0 mode

-- | Merge modes.
data KcMergeMode = KCMSET       -- ^ overwrite the existing value
                 | KCMADD       -- ^ keep the existing value
                 | KCMREPLACE   -- ^ modify the existing record only
                 | KCMAPPEND    -- ^ append the new value
                 deriving (Eq, Show)

numOfKcMergeMode :: KcMergeMode -> Int
numOfKcMergeMode KCMSET = (0)
{-# LINE 284 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcMergeMode KCMADD = (1)
{-# LINE 285 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcMergeMode KCMREPLACE = (2)
{-# LINE 286 "Database/KyotoCabinet/Db.hsc" #-}
numOfKcMergeMode KCMAPPEND = (3)
{-# LINE 287 "Database/KyotoCabinet/Db.hsc" #-}

{- link errors:
-- | The package version.
kcVERSION = #const KCVERSION

-- | Special pointer for no operation by the visiting function.
kcVISNOP = #const KCVISNOP

-- | Special pointer to remove the record by the visiting function.
kcVISREMOVE = #const KCVISREMOVE
-}

-- | Current KC version.
kcversion :: IO String
kcversion = kcVERSION >>= peekCAString

foreign import ccall safe "kclangc.h getKCVERSION" kcVERSION
  :: IO CString

foreign import ccall safe "kclangc.h getKCVISNOP" kcVISNOP
  :: IO CString

foreign import ccall safe "kclangc.h getKCVISREMOVE" kcVISREMOVE
  :: IO CString

-- | Return one of these from 'KcVisitFull', 'KcVisitEmpty' or
-- 'KcFileProc' to update the database after a visitor access.
data KcVisitAction = KCVISNOP | KCVISSET BS.ByteString | KCVISREMOVE

-- | Type of a visitor function when key-value is present.
type KcVisitFull = BS.ByteString -> BS.ByteString -> IO KcVisitAction

callVisitFull :: KcVisitFull -> KCVISITFULL
callVisitFull visitFull kbuf ksiz vbuf vsiz sp _ = do
  key <- BS.unsafePackCStringLen (kbuf, fromIntegral ksiz)
  val <- BS.unsafePackCStringLen (vbuf, fromIntegral vsiz)
  rv <- visitFull key val
  case rv of
    KCVISNOP -> kcVISNOP
    KCVISSET newVal ->
      BS.unsafeUseAsCStringLen newVal $ \(cstr, clen) -> do
        poke sp (fromIntegral clen)
        return cstr -- XXX what if newVal is gc'd?
    KCVISREMOVE -> kcVISREMOVE

type KCVISITFULL =
  CString -> CSize -> CString -> CSize -> Ptr CSize -> Ptr () -> IO CString

foreign import ccall "wrapper" wrapKCVISITFULL
  :: KCVISITFULL -> IO (FunPtr KCVISITFULL)

-- | Type of a visitor function when key-value is absent.
type KcVisitEmpty = BS.ByteString -> IO KcVisitAction

callVisitEmpty :: KcVisitEmpty -> KCVISITEMPTY
callVisitEmpty visitEmpty kbuf ksiz sp _ = do
  key <- BS.unsafePackCStringLen (kbuf, fromIntegral ksiz)
  rv <- visitEmpty key
  case rv of
    KCVISNOP -> kcVISNOP
    KCVISSET newVal ->
      BS.unsafeUseAsCStringLen newVal $ \(cstr, clen) -> do
        poke sp (fromIntegral clen)
        return cstr -- XXX what if newVal is gc'd?
    KCVISREMOVE -> kcVISREMOVE

type KCVISITEMPTY = CString -> CSize -> Ptr CSize -> Ptr () -> IO CString

foreign import ccall "wrapper" wrapKCVISITEMPTY
  :: KCVISITEMPTY -> IO (FunPtr KCVISITEMPTY)

-- | Type of a database synchronization callback.
type KcFileProc = FilePath -> Int64 -> Int64 -> IO Bool

callFileProc :: KcFileProc -> KCFILEPROC
callFileProc fileProc path count size _ = do
  filePath <- peekCAString path
  rv <- fileProc filePath (fromIntegral count) (fromIntegral size)
  if rv then return 1 else return 0

type KCFILEPROC = CString -> CLLong -> CLLong -> Ptr () -> IO CInt

foreign import ccall "wrapper" wrapKCFILEPROC
  :: KCFILEPROC -> IO (FunPtr KCFILEPROC)

int64_min :: CLLong
int64_min = -9223372036854775808
{-# LINE 374 "Database/KyotoCabinet/Db.hsc" #-}

--------------------------------------------------------------------------------
-- Utilities

-- | Allocate a region on memory.
kcmalloc :: Int -> IO (Ptr a)
kcmalloc sz = _kcmalloc (fromIntegral sz)

foreign import ccall safe "kclangc.h kcmalloc" _kcmalloc
  :: CSize -> IO (Ptr a)

-- | Release a region allocated in the library.
foreign import ccall safe "kclangc.h kcfree" kcfree
  :: Ptr a -> IO ()

-- | Get the time of day in seconds.
kctime :: IO Double
kctime = _kctime >>= return . realToFrac

foreign import ccall safe "kclangc.h kctime" _kctime
  :: IO CDouble

-- | Convert a string to an integer.
kcatoi :: String -> IO Int64
kcatoi str =
  withCAString str $ \cstr ->
  _kcatoi cstr >>= return . fromIntegral

foreign import ccall unsafe "kclangc.h kcatoi" _kcatoi
  :: CString -> IO CLLong

-- | Convert a string with a metric prefix to an integer.
kcatoix :: String -> IO Int64
kcatoix str =
  withCAString str $ \cstr ->
  _kcatoix cstr >>= return . fromIntegral

foreign import ccall unsafe "kclangc.h kcatoix" _kcatoix
  :: CString -> IO CLLong

-- | Convert a string to a real number.
kcatof :: String -> IO Double
kcatof str =
  withCAString str $ \cstr ->
  _kcatof cstr >>= return . realToFrac

foreign import ccall unsafe "kclangc.h kcatof" _kcatof
  :: CString -> IO CDouble

-- | Get the hash value by MurMur hashing.
kchashmurmur :: BS.ByteString -> IO Int64
kchashmurmur buff =
  BS.unsafeUseAsCStringLen buff $ \(b, n) ->
    _kchashmurmur b (fromIntegral n) >>= return . fromIntegral

foreign import ccall unsafe "kclangc.h kchashmurmur" _kchashmurmur
  :: CString -> CSize -> IO CLLong

-- | Get the hash value by FNV hashing.
kchashfnv :: BS.ByteString -> IO Int64
kchashfnv buff =
  BS.unsafeUseAsCStringLen buff $ \(b, n) ->
    _kchashfnv b (fromIntegral n) >>= return . fromIntegral

foreign import ccall unsafe "kclangc.h kchashfnv" _kchashfnv
  :: CString -> CSize -> IO CLLong

-- | Get the quiet Not-a-Number value.
kcnan :: IO Double
kcnan = _kcnan >>= return . realToFrac

foreign import ccall unsafe "kclangc.h kcnan" _kcnan
  :: IO CDouble

-- | Get the positive infinity value.
kcinf :: IO Double
kcinf = _kcinf >>= return . realToFrac

foreign import ccall unsafe "kclangc.h kcinf" _kcinf
  :: IO CDouble

-- | Check a number is a Not-a-Number value.
kcchknan :: Double -> IO Bool
kcchknan num = do rv <- _kcchknan (realToFrac num)
                  if rv == 0 then return False else return True

foreign import ccall unsafe "kclangc.h kcchknan" _kcchknan
  :: CDouble -> IO CInt

-- | Check a number is an infinity value.
kcchkinf :: Double -> IO Bool
kcchkinf num = do rv <- _kcchkinf (realToFrac num)
                  if rv == 0 then return False else return True

foreign import ccall unsafe "kclangc.h kcchkinf" _kcchkinf
  :: CDouble -> IO CInt

-- | Get the readable string of an error code.
kcecodename :: Int -> IO String
kcecodename code = do rv <- _kcecodename (fromIntegral code); peekCAString rv

foreign import ccall unsafe "kclangc.h kcecodename" _kcecodename
  :: CInt -> IO CString

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

-- | An exception indicating an error in a KC operation.
data KcException = KcException String KcError String
    deriving (Eq, Show, Typeable)

instance Exception KcException

kcThrow :: KcDb -> String -> IO a
kcThrow db fname = do
  err <- kcdbecode db
  msg <- kcdbemsg db
  throwIO $ KcException fname err msg

handleBoolResult :: KcDb -> String -> CInt -> IO ()
handleBoolResult db fname status =
  if status == 0 then kcThrow db fname else return ()

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

-- | Polymorphic database.
newtype KcDb = KcDb { unKcDb :: ForeignPtr KCDB }
data KCDB      -- native type

-- | Create a polymorphic database object.
kcdbnew :: IO KcDb
kcdbnew = _kcdbnew >>= wrapdb

wrapdb :: Ptr KCDB -> IO KcDb
wrapdb db = do
  fp <- newForeignPtr_ db
  --addForeignPtrFinalizer _kcdbdelFunPtr fp
  return $ KcDb fp

foreign import ccall safe "kclangc.h kcdbnew" _kcdbnew
  :: IO (Ptr KCDB)

foreign import ccall safe "kclangc.h &kcdbdel" _kcdbdelFunPtr
  :: FunPtr (Ptr KCDB -> IO ())

-- | Destroy a database object.
kcdbdel :: KcDb -> IO ()
kcdbdel db = do withForeignPtr (unKcDb db) _kcdbdel; return ()

foreign import ccall safe "kclangc.h kcdbdel" _kcdbdel
  :: Ptr KCDB -> IO ()

-- | Tuning parameters for database creation.
data KcTune = KcTuneType KcTuneType
            | KcTuneLogger KcLogger
            | KcTuneLogKinds KcLogLevel
            | KcTuneLogPx String
            | KcTuneOptions [KcOption] -- ^ supported by: cache hash, cache tree, file hash, file tree, directory hash, directory tree
            | KcTuneBuckets Int -- ^ supported by: cache hash, cache tree, file hash, file tree
            | KcTuneCompressor KcCompressor -- ^ supported by: cache hash, cache tree, file hash, file tree, directory hash, directory tree
            | KcTuneZkey String -- ^ supported by: cache hash, cache tree, file hash, file tree, directory hash, directory tree
            | KcTuneCapCount Int -- ^ supported by: cache hash
            | KcTuneCapSize Int -- ^ supported by: cache hash
            | KcTunePage Int -- ^ supported by: cache tree, file tree, directory tree
            | KcTuneComparator KcComparator -- ^ supported by: cache tree, file tree, directory tree
            | KcTunePageCache Int -- ^ supported by: cache tree, file tree, directory tree
            | KcTuneAlignment Int -- ^ supported by: file hash, file tree
            | KcTuneFbp Int -- ^ supported by: file hash, file tree
            | KcTuneMap Int -- ^ supported by: file hash, file tree
            | KcTuneDefrag Int -- ^ supported by: file hash, file tree
data KcTuneType = KcTypePrototypeHashDb | KcTypePrototypeTreeDb
                | KcTypeCacheHashDb | KcTypeCacheTreeDb
                | KcTypeFileHashDb | KcTypeFileTreeDb
                | KcTypeDirectoryHashDb | KcTypeDirectoryTreeDb
data KcLogger = KcLoggerStdout | KcLoggerStderr
data KcLogLevel = KcLogDebug | KcLogInfo | KcLogWarn | KcLogError
data KcOption = KcOptionSmall | KcOptionLinear | KcOptionCompress
data KcCompressor = KcCompressorZlib | KcCompressorDeflate
                      | KcCompressorGzip | KcCompressorLzo
                      | KcCompressorLzma | KcCompressorArc
data KcComparator = KcComparatorLexical | KcComparatorDecimal

instance Show KcTune where
  show (KcTuneType t) = "type=" ++ show t
  show (KcTuneLogger l) = "log=" ++ show l
  show (KcTuneLogKinds l) = "logkinds=" ++ show l
  show (KcTuneLogPx s) = "logpx=" ++ s
  show (KcTuneOptions opts) = "opts=" ++ foldr (\o r -> show o ++ r) "" opts
  show (KcTuneBuckets n) = "bnum=" ++ show n
  show (KcTuneCompressor c) = "zcomp=" ++ show c
  show (KcTuneZkey s) = "zkey=" ++ show s
  show (KcTuneCapCount c) = "capcount=" ++ show c
  show (KcTuneCapSize n) = "capsize=" ++ show n
  show (KcTunePage n) = "psiz=" ++ show n
  show (KcTuneComparator c) = "rcomp=" ++ show c
  show (KcTunePageCache n) = "pccap=" ++ show n
  show (KcTuneAlignment n) = "apow=" ++ show n
  show (KcTuneFbp n) = "fpow=" ++ show n
  show (KcTuneMap n) = "msiz=" ++ show n
  show (KcTuneDefrag n) = "dfunit=" ++ show n

instance Show KcTuneType where
  show KcTypePrototypeHashDb = "-"
  show KcTypePrototypeTreeDb = "+"
  show KcTypeCacheHashDb = "*"
  show KcTypeCacheTreeDb = "%"
  show KcTypeFileHashDb = "kch"
  show KcTypeFileTreeDb = "kct"
  show KcTypeDirectoryHashDb = "kcd"
  show KcTypeDirectoryTreeDb = "kcf"

instance Show KcLogger where
  show KcLoggerStdout = "-"
  show KcLoggerStderr = "+"

instance Show KcLogLevel where
  show KcLogDebug = "debug"
  show KcLogInfo = "info"
  show KcLogWarn = "warn"
  show KcLogError = "error"

instance Show KcOption where
  show KcOptionSmall = "s"
  show KcOptionLinear = "l"
  show KcOptionCompress = "c"

instance Show KcCompressor where
  show KcCompressorZlib = "zlib"
  show KcCompressorDeflate = "def"
  show KcCompressorGzip = "gz"
  show KcCompressorLzo = "lzo"
  show KcCompressorLzma = "lzma"
  show KcCompressorArc = "arc"

instance Show KcComparator where
  show KcComparatorLexical = "lex"
  show KcComparatorDecimal = "dec"

-- | Open a database file.
kcdbopen ::
  KcDb
  -- ^ @db@ - a database object.
  -> String
  -- ^ @path@ - the path of a database file.
  --
  -- * If it is @-@, the database will be a prototype hash database.
  --
  -- * If it is @+@, the database will be a prototype tree database.
  --
  -- * If it is @*@, the database will be a cache hash database.
  --
  -- * If it is @%@, the database will be a cache tree database.
  --
  -- * If its suffix is @.kch@, the database will be a file hash database.
  --
  -- * If its suffix is @.kct@, the database will be a file tree database.
  --
  -- * If its suffix is @.kcd@, the database will be a directory hash database.
  --
  -- * If its suffix is @.kcf@, the database will be a directory tree database.
  --
  -- * Otherwise, this function fails.
  -> [KcTune]
  -- ^ @tune@ - tuning parameters.
  -> [KcOpenMode]
  -- ^ @mode@ - the connection mode flags.
  -- 'KCOWRITER' as a writer, 'KCOREADER' as a reader.
  -- The following flags may be added to the writer mode:
  --
  -- * 'KCOCREATE', which means it creates a new database if the file
  -- does not exist,
  --
  -- * 'KCOTRUNCATE', which means it creates a new database regardless
  -- if the file exists,
  --
  -- * 'KCOAUTOTRAN', which means each updating operation is performed
  -- in implicit transaction,
  --
  -- * 'KCOAUTOSYNC', which means each updating operation is followed
  -- by implicit synchronization with the file system.
  --
  -- The following flags may be added to both of the reader mode and the writer mode:
  --
  -- * 'KCONOLOCK', which means it opens the database file without file locking,
  --
  -- * 'KCOTRYLOCK', which means locking is performed without blocking,
  --
  -- * 'KCONOREPAIR', which means the database file is not repaired
  -- implicitly even if file destruction is detected.
  -> IO ()
  -- ^ Returns @()@ on success, throws 'KcException' on failure.
  --
  -- Every opened database must be closed by the 'kcdbclose' method
  -- when it is no longer in use.  It is not allowed for two or more
  -- database objects in the same process to keep their connections to
  -- the same database file at the same time.
kcdbopen db path tune mode =
  let tunePath = foldl (\prev t -> prev ++ "#" ++ show t) path tune in
  withForeignPtr (unKcDb db) $ \c_db ->
    withCAString tunePath $ \c_path ->
      _kcdbopen c_db c_path (numOfOpenModes mode) >>= handleBoolResult db "kcdbopen"

foreign import ccall safe "kclangc.h kcdbopen" _kcdbopen
  :: Ptr KCDB -> CString -> CUInt -> IO CInt

-- | Close the database file.
kcdbclose :: KcDb -> IO ()
kcdbclose db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    _kcdbclose c_db >>= handleBoolResult db "kcdbclose"

foreign import ccall safe "kclangc.h kcdbclose" _kcdbclose
  :: Ptr KCDB -> IO CInt

-- | Get the code of the last happened error.
kcdbecode :: KcDb -> IO KcError
kcdbecode db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    code <- _kcdbecode c_db
    return $ kcErrorOfNum code

foreign import ccall unsafe "kclangc.h kcdbecode" _kcdbecode
  :: Ptr KCDB -> IO CInt

-- | Get the supplement message of the last happened error.
kcdbemsg :: KcDb -> IO String
kcdbemsg db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    msg <- _kcdbemsg c_db
    peekCAString msg

foreign import ccall unsafe "kclangc.h kcdbemsg" _kcdbemsg
  :: Ptr KCDB -> IO CString

-- | Accept a visitor to a record.
kcdbaccept :: KcDb
           -> BS.ByteString -- ^ key
           -> KcVisitFull -> KcVisitEmpty
           -> Bool -- ^ writable
           -> IO ()
kcdbaccept db key visitFull visitEmpty writable =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      vf <- wrapKCVISITFULL $ callVisitFull visitFull
      ve <- wrapKCVISITEMPTY $ callVisitEmpty visitEmpty
      rv <- _kcdbaccept c_db kbuf (fromIntegral ksiz) vf ve
              nullPtr (if writable then 1 else 0)
      freeHaskellFunPtr ve
      freeHaskellFunPtr vf
      handleBoolResult db "kcdbaccept" rv

foreign import ccall safe "kclangc.h kcdbaccept" _kcdbaccept
  :: Ptr KCDB -> CString -> CSize -> FunPtr KCVISITFULL -> FunPtr KCVISITEMPTY ->
     Ptr () -> CInt -> IO CInt

-- | Accept a visitor to multiple records at once.
kcdbacceptbulk :: KcDb
               -> [BS.ByteString] -- ^ keys
               -> KcVisitFull -> KcVisitEmpty
               -> Bool -- ^ writable
               -> IO ()
kcdbacceptbulk db keys visitFull visitEmpty writable = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    let nkeys = length keys
    withKcStrArray keys $ \keyArr -> do
      vf <- wrapKCVISITFULL $ callVisitFull visitFull
      ve <- wrapKCVISITEMPTY $ callVisitEmpty visitEmpty
      rv <- _kcdbacceptbulk c_db keyArr (fromIntegral nkeys) vf ve
              nullPtr (if writable then 1 else 0)
      freeHaskellFunPtr ve
      freeHaskellFunPtr vf
      handleBoolResult db "kcdbacceptbulk" rv

foreign import ccall safe "kclangc.h kcdbacceptbulk" _kcdbacceptbulk
  :: Ptr KCDB -> Ptr KcStr -> CSize -> FunPtr KCVISITFULL -> FunPtr KCVISITEMPTY ->
     Ptr () -> CInt -> IO CInt

-- | Iterate to accept a visitor for each record.
kcdbiterate :: KcDb -> KcVisitFull
               -> Bool -- ^ writable
               -> IO ()
kcdbiterate db visitFull writable = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    vf <- wrapKCVISITFULL $ callVisitFull visitFull
    rv <- _kcdbiterate c_db vf nullPtr (if writable then 1 else 0)
    freeHaskellFunPtr vf
    handleBoolResult db "kcdbiterate" rv

foreign import ccall safe "kclangc.h kcdbiterate" _kcdbiterate
  :: Ptr KCDB -> FunPtr KCVISITFULL -> Ptr () -> CInt -> IO CInt

-- | Set the value of a record.
kcdbset :: KcDb
        -> BS.ByteString -- ^ key
        -> BS.ByteString -- ^ value
        -> IO ()
kcdbset db key val =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) ->
      BS.unsafeUseAsCStringLen val $ \(vbuf, vsiz) -> do
        _kcdbset c_db kbuf (fromIntegral ksiz) vbuf (fromIntegral vsiz)
        >>= handleBoolResult db "kcdbset"

foreign import ccall safe "kclangc.h kcdbset" _kcdbset
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize -> IO CInt

-- | Add a record.
kcdbadd :: KcDb
        -> BS.ByteString -- ^ key
        -> BS.ByteString -- ^ value
        -> IO ()
kcdbadd db key val =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) ->
      BS.unsafeUseAsCStringLen val $ \(vbuf, vsiz) -> do
        _kcdbadd c_db kbuf (fromIntegral ksiz) vbuf (fromIntegral vsiz)
        >>= handleBoolResult db "kcdbadd"

foreign import ccall safe "kclangc.h kcdbadd" _kcdbadd
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize -> IO CInt

-- | Replace the value of a record.
kcdbreplace :: KcDb
            -> BS.ByteString -- ^ key
            -> BS.ByteString -- ^ value
            -> IO ()
kcdbreplace db key val =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) ->
      BS.unsafeUseAsCStringLen val $ \(vbuf, vsiz) ->
        _kcdbreplace c_db kbuf (fromIntegral ksiz) vbuf (fromIntegral vsiz)
        >>= handleBoolResult db "kcdbreplace"

foreign import ccall safe "kclangc.h kcdbreplace" _kcdbreplace
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize -> IO CInt

-- | Append the value of a record.
kcdbappend :: KcDb
           -> BS.ByteString -- ^ key
           -> BS.ByteString -- ^ value
           -> IO ()
kcdbappend db key val =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) ->
      BS.unsafeUseAsCStringLen val $ \(vbuf, vsiz) ->
        _kcdbappend c_db kbuf (fromIntegral ksiz) vbuf (fromIntegral vsiz)
        >>= handleBoolResult db "kcdbappend"

foreign import ccall safe "kclangc.h kcdbappend" _kcdbappend
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize -> IO CInt

-- | Add a number to the numeric value of a record.
kcdbincrint :: KcDb
            -> BS.ByteString -- ^ key
            -> Int64 -- ^ increment amount
            -> IO Int64
kcdbincrint db key num =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      rv <- _kcdbincrint c_db kbuf (fromIntegral ksiz) (fromIntegral num)
      if rv == int64_min then kcThrow db "kcdbincrint"
        else return (fromIntegral rv)

foreign import ccall safe "kclangc.h kcdbincrint" _kcdbincrint
  :: Ptr KCDB -> CString -> CSize -> CLLong -> IO CLLong

-- | Add a number to the numeric value of a record.
kcdbincrdouble :: KcDb
               -> BS.ByteString -- ^ key
               -> Double -- ^ increment amount
               -> IO Double
kcdbincrdouble db key num =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      rv <- _kcdbincrdouble c_db kbuf (fromIntegral ksiz) (realToFrac num)
      nan <- _kcnan
      if rv == nan then kcThrow db "kcdbincrdouble"
        else return (realToFrac rv)

foreign import ccall safe "kclangc.h kcdbincrdouble" _kcdbincrdouble
  :: Ptr KCDB -> CString -> CSize -> CDouble -> IO CDouble

-- | Perform compare-and-swap.
kcdbcas :: KcDb
        -> BS.ByteString -- ^ key
        -> BS.ByteString -- ^ old value
        -> BS.ByteString -- ^ new value
        -> IO ()
kcdbcas db key nv ov =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      BS.unsafeUseAsCStringLen nv $ \(nvbuf, nvsiz) -> do
        BS.unsafeUseAsCStringLen ov $ \(ovbuf, ovsiz) -> do
          _kcdbcas c_db kbuf (fromIntegral ksiz) nvbuf (fromIntegral nvsiz)
            ovbuf (fromIntegral ovsiz)
          >>= handleBoolResult db "kcdbcas"

foreign import ccall safe "kclangc.h kcdbcas" _kcdbcas
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize ->
     CString -> CSize -> IO CInt

-- | Remove a record.
kcdbremove :: KcDb
           -> BS.ByteString -- ^ key
           -> IO ()
kcdbremove db key =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      _kcdbremove c_db kbuf (fromIntegral ksiz) >>= handleBoolResult db "kcdbremove"

foreign import ccall safe "kclangc.h kcdbremove" _kcdbremove
  :: Ptr KCDB -> CString -> CSize -> IO CInt

-- | Retrieve the value of a record.
kcdbget :: KcDb
        -> BS.ByteString -- ^ key
        -> IO (Maybe BS.ByteString)
kcdbget db key =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      alloca $ \ptr -> do
        cstr <- _kcdbget c_db kbuf (fromIntegral ksiz) ptr
        if cstr == nullPtr then return Nothing
          else do
          csiz <- peek ptr
          bs <- BS.unsafePackCStringFinalizer (castPtr cstr) (fromIntegral csiz)
                  (kcfree cstr)
          return $ Just bs

foreign import ccall safe "kclangc.h kcdbget" _kcdbget
  :: Ptr KCDB -> CString -> CSize -> Ptr CSize -> IO CString

-- | Retrieve the value of a record.
kcdbgetbuf :: KcDb
           -> BS.ByteString -- ^ key
           -> Int -- ^ max chars to retrieve
           -> IO (Maybe BS.ByteString)
kcdbgetbuf db key maxElts =
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      vbuf <- mallocArray maxElts
      n <- _kcdbgetbuf c_db kbuf (fromIntegral ksiz) vbuf (fromIntegral maxElts)
      if n == -1 then return Nothing
        else do
          bs <- BS.unsafePackCStringFinalizer (castPtr vbuf) (fromIntegral n)
                  (kcfree vbuf)
          return $ Just bs

foreign import ccall safe "kclangc.h kcdbgetbuf" _kcdbgetbuf
  :: Ptr KCDB -> CString -> CSize -> CString -> CSize -> IO CInt

-- | Store records at once.
kcdbsetbulk :: KcDb
            -> [(BS.ByteString, BS.ByteString)] -- ^ records to store
            -> Bool  -- ^ atomic
            -> IO Int64 -- ^ returns number of records stored
kcdbsetbulk db recs atomic =
  withForeignPtr (unKcDb db) $ \c_db -> do
    let nrecs = length recs
    withKcRecArray recs $ \keyArr -> do
      n <- _kcdbsetbulk c_db keyArr (fromIntegral nrecs) (if atomic then 1 else 0)
      if n == -1 then kcThrow db "kcdbsetbulk" else return (fromIntegral n)

foreign import ccall safe "kclangc.h kcdbsetbulk" _kcdbsetbulk
  :: Ptr KCDB -> Ptr KcRec -> CSize -> CInt -> IO CLLong

-- | Remove records at once.
kcdbremovebulk :: KcDb
               -> [BS.ByteString] -- ^ keys of records to remove
               -> Bool -- ^ atomic
               -> IO Int64 -- ^ returns number of records removed
kcdbremovebulk db keys atomic =
  withForeignPtr (unKcDb db) $ \c_db -> do
    let nrecs = length keys
    withKcStrArray keys $ \keyArr -> do
      n <- _kcdbremovebulk c_db keyArr (fromIntegral nrecs) (if atomic then 1 else 0)
      if n == -1 then kcThrow db "kcdbremovebulk" else return (fromIntegral n)

foreign import ccall safe "kclangc.h kcdbremovebulk" _kcdbremovebulk
  :: Ptr KCDB -> Ptr KcStr -> CSize -> CInt -> IO CLLong

-- | Retrieve records at once.
kcdbgetbulk :: KcDb
            -> [BS.ByteString] -- ^ keys of the records to retrieve
            -> Bool -- ^ atomic
            -> IO [(BS.ByteString, BS.ByteString)]
kcdbgetbulk db keys atomic =
  withForeignPtr (unKcDb db) $ \c_db -> do
    let nkeys = length keys
    withKcStrArray keys $ \keyArr -> do
      allocaArray nkeys $ \recArr -> do
        n <- _kcdbgetbulk c_db keyArr (fromIntegral nkeys) recArr
             (if atomic then 1 else 0)
        if n == -1 then kcThrow db "kcdbgetbulk"
          else do
          recList <- peekArray (fromIntegral n) recArr
          return $ map (\(KcRec p) -> p) recList

foreign import ccall safe "kclangc.h kcdbgetbulk" _kcdbgetbulk
  :: Ptr KCDB -> Ptr KcStr -> CSize -> Ptr KcRec -> CInt -> IO CLLong

-- | Remove all records.
kcdbclear :: KcDb -> IO ()
kcdbclear db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    _kcdbclear c_db >>= handleBoolResult db "kcdbclear"

foreign import ccall safe "kclangc.h kcdbclear" _kcdbclear
  :: Ptr KCDB -> IO CInt

-- | Synchronize updated contents with the file and the device.
kcdbsync :: KcDb
         -> Bool -- ^ @True@ for physical synchronization, @False@ for
                 -- logical synchronization
         -> KcFileProc -- ^ postprocessor callback
         -> IO ()
kcdbsync db hard fileProc = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    fp <- wrapKCFILEPROC $ callFileProc fileProc
    rv <- _kcdbsync c_db (if hard then 1 else 0) fp nullPtr
    freeHaskellFunPtr fp
    handleBoolResult db "kcdbsync" rv

foreign import ccall safe "kclangc.h kcdbsync" _kcdbsync
  :: Ptr KCDB -> CInt -> FunPtr KCFILEPROC -> Ptr () -> IO CInt

-- | Create a copy of the database file.
kcdbcopy :: KcDb
         -> FilePath -- ^ path to the destination file
         -> IO ()
kcdbcopy db dest =
  withForeignPtr (unKcDb db) $ \c_db -> do
    withCAString dest $ \c_dest -> do
      _kcdbcopy c_db c_dest >>= handleBoolResult db "kcdbcopy"

foreign import ccall safe "kclangc.h kcdbcopy" _kcdbcopy
  :: Ptr KCDB -> CString -> IO CInt

-- | Begin transaction.
kcdbbegintran :: KcDb
              -> Bool -- ^ @True@ for physical synchronization, @False@
                      -- for logical synchronization
              -> IO ()
kcdbbegintran db hard =
  withForeignPtr (unKcDb db) $ \c_db -> do
    _kcdbbegintran c_db (if hard then 1 else 0)
    >>= handleBoolResult db "kcdbbegintran"

foreign import ccall safe "kclangc.h kcdbbegintran" _kcdbbegintran
  :: Ptr KCDB -> CInt -> IO CInt

-- | Try to begin transaction.
kcdbbegintrantry :: KcDb
                 -> Bool  -- ^ @True@ for physical synchronization,
                          -- @False@ for logical synchronization
                 -> IO ()
kcdbbegintrantry db hard =
  withForeignPtr (unKcDb db) $ \c_db -> do
    _kcdbbegintrantry c_db (if hard then 1 else 0)
    >>= handleBoolResult db "kcdbbegintrantry"

foreign import ccall safe "kclangc.h kcdbbegintrantry" _kcdbbegintrantry
  :: Ptr KCDB -> CInt -> IO CInt

-- | End transaction.
kcdbendtran :: KcDb
            -> Bool  -- ^ @True@ to commit, @False@ to abort
            -> IO ()
kcdbendtran db commit =
  withForeignPtr (unKcDb db) $ \c_db -> do
    _kcdbendtran c_db (if commit then 1 else 0)
    >>= handleBoolResult db "kcdbendtran"

foreign import ccall safe "kclangc.h kcdbendtran" _kcdbendtran
  :: Ptr KCDB -> CInt -> IO CInt

-- | Dump records into a file.
kcdbdumpsnap :: KcDb
             -> FilePath -- ^ destination file
             -> IO ()
kcdbdumpsnap db dest =
  withForeignPtr (unKcDb db) $ \c_db -> do
    withCAString dest $ \c_dest -> do
      _kcdbdumpsnap c_db c_dest >>= handleBoolResult db "kcdbdumpsnap"

foreign import ccall safe "kclangc.h kcdbdumpsnap" _kcdbdumpsnap
  :: Ptr KCDB -> CString -> IO CInt

-- | Load records from a file.
kcdbloadsnap :: KcDb
             -> FilePath -- ^ source file
             -> IO ()
kcdbloadsnap db src =
  withForeignPtr (unKcDb db) $ \c_db -> do
    withCAString src $ \c_src -> do
      _kcdbloadsnap c_db c_src >>= handleBoolResult db "kcdbloadsnap"

foreign import ccall safe "kclangc.h kcdbloadsnap" _kcdbloadsnap
  :: Ptr KCDB -> CString -> IO CInt

-- | Get the number of records.
kcdbcount :: KcDb -> IO Int64
kcdbcount db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    n <- _kcdbcount c_db
    if n == -1 then kcThrow db "kcdbcount" else return (fromIntegral n)

foreign import ccall safe "kclangc.h kcdbcount" _kcdbcount
  :: Ptr KCDB -> IO CLLong

-- | Get the size of the database file.
kcdbsize :: KcDb -> IO Int64
kcdbsize db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    n <- _kcdbsize c_db
    if n == -1 then kcThrow db "kcdbsize" else return (fromIntegral n)

foreign import ccall unsafe "kclangc.h kcdbsize" _kcdbsize
  :: Ptr KCDB -> IO CLLong

-- | Get the path of the database file.
kcdbpath :: KcDb -> IO String
kcdbpath db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    cstr <- _kcdbpath c_db
    if cstr == nullPtr then kcThrow db "kcdbpath"
      else do rv <- peekCAString cstr
              kcfree cstr
              return rv

foreign import ccall safe "kclangc.h kcdbpath" _kcdbpath
  :: Ptr KCDB -> IO CString

-- | Get the miscellaneous status information.
kcdbstatus :: KcDb -> IO String
kcdbstatus db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    cstr <- _kcdbstatus c_db
    if cstr == nullPtr then kcThrow db "kcdbstatus"
      else do rv <- peekCAString cstr
              kcfree cstr
              return rv

foreign import ccall safe "kclangc.h kcdbstatus" _kcdbstatus
  :: Ptr KCDB -> IO CString

-- | Get keys matching a prefix string.
kcdbmatchprefix :: KcDb
                -> BS.ByteString -- ^ prefix
                -> Int -- ^ max elements to return
                -> IO [BS.ByteString]
kcdbmatchprefix db prefix maxElts = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCString prefix $ \pre -> do
      allocaArray maxElts $ \strarr -> do
        n <- _kcdbmatchprefix c_db pre strarr (fromIntegral maxElts)
        if n == -1 then kcThrow db "kcdbmatchprefix"
          else bsListOfCStringArr strarr n

foreign import ccall safe "kclangc.h kcdbmatchprefix" _kcdbmatchprefix
  :: Ptr KCDB -> CString -> Ptr CString -> CSize -> IO CLLong

-- | Get keys matching a regular expression string.
kcdbmatchregex :: KcDb
               -> BS.ByteString -- ^ regexp
               -> Int -- ^ max elements to return
               -> IO [BS.ByteString]
kcdbmatchregex db regexp maxElts = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    BS.unsafeUseAsCString regexp $ \pre -> do
      allocaArray maxElts $ \strarr -> do
        n <- _kcdbmatchregex c_db pre strarr (fromIntegral maxElts)
        if n == -1 then kcThrow db "kcdbmatchregex"
          else bsListOfCStringArr strarr n

foreign import ccall safe "kclangc.h kcdbmatchregex" _kcdbmatchregex
  :: Ptr KCDB -> CString -> Ptr CString -> CSize -> IO CLLong

-- | Merge records from other databases.
kcdbmerge :: KcDb
          -> [KcDb] -- ^ database sources
          -> KcMergeMode -- ^ merge mode:
                         --
                         -- * 'KCMSET' to overwrite the existing value,
                         --
                         -- * 'KCMADD' to keep the existing value,
                         --
                         -- * 'KCMREPLACE' to modify the existing record only,
                         --
                         -- * 'KCMAPPEND' to append the new value.
          -> IO ()
kcdbmerge db srcs mode = do
  withForeignPtr (unKcDb db) $ \c_db -> do
    let elts = length srcs
    allocaArray elts $ \srcarr -> do
      pokeArray srcarr (map (unsafeForeignPtrToPtr . unKcDb) srcs)
      let m = fromIntegral $ numOfKcMergeMode mode
      _kcdbmerge c_db srcarr (fromIntegral elts) m
      >>= handleBoolResult db "kcdbmerge"

foreign import ccall safe "kclangc.h kcdbmerge" _kcdbmerge
  :: Ptr KCDB -> Ptr (Ptr KCDB) -> CSize -> CUInt -> IO CInt

--------------------------------------------------------------------------------
-- Cursors

-- | Polymorphic cursor.
newtype KcCur = KcCur { unKcCur :: ForeignPtr KCCUR } deriving (Eq)
data KCCUR      -- native type

-- | Create a cursor object.
kcdbcursor :: KcDb -> IO KcCur
kcdbcursor db =
  withForeignPtr (unKcDb db) $ \c_db -> do
    cur <- _kcdbcursor c_db
    fp <- newForeignPtr_ cur
    --addForeignPtrFinalizer _kccurdelFunPtr fp
    return $ KcCur fp

foreign import ccall safe "kclangc.h kcdbcursor" _kcdbcursor
  :: Ptr KCDB -> IO (Ptr KCCUR)

foreign import ccall safe "kclangc.h &kccurdel" _kccurdelFunPtr
  :: FunPtr (Ptr KCCUR -> IO ())

-- | Destroy a cursor object.
kccurdel :: KcCur -> IO ()
kccurdel cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    _kccurdel c_cur

foreign import ccall safe "kclangc.h kccurdel" _kccurdel
  :: Ptr KCCUR -> IO ()

-- | Accept a visitor to the current record.
kccuraccept :: KcCur
            -> KcVisitFull
            -> Bool -- ^ writable
            -> Bool -- ^ step
            -> IO ()
kccuraccept cur visitFull writable step = do
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    vf <- wrapKCVISITFULL $ callVisitFull visitFull
    rv <- _kccuraccept c_cur vf nullPtr (if writable then 1 else 0)
            (if step then 1 else 0)
    freeHaskellFunPtr vf
    db <- kccurdb cur
    handleBoolResult db "kccuraccept" rv

foreign import ccall safe "kclangc.h kccuraccept" _kccuraccept
  :: Ptr KCCUR -> FunPtr KCVISITFULL -> Ptr () -> CInt -> CInt -> IO CInt

-- | Remove the current record.
kccurremove :: KcCur -> IO ()
kccurremove cur = do
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    rv <- _kccurremove c_cur
    db <- kccurdb cur
    handleBoolResult db "kccurremove" rv

foreign import ccall safe "kclangc.h kccurremove" _kccurremove
  :: Ptr KCCUR -> IO CInt

-- | Get the key of the current record.
kccurgetkey :: KcCur
            -> Bool -- ^ step
            -> IO BS.ByteString
kccurgetkey cur step = do
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    alloca $ \ptr -> do
      cstr <- _kccurgetkey c_cur ptr (if step then 1 else 0)
      if cstr == nullPtr then kccurdb cur >>= flip kcThrow "kccurgetkey"
        else do
        len <- peek ptr
        BS.unsafePackCStringFinalizer (castPtr cstr) (fromIntegral len) (kcfree cstr)

foreign import ccall safe "kclangc.h kccurgetkey" _kccurgetkey
  :: Ptr KCCUR -> Ptr CSize -> CInt -> IO CString

-- | Get the value of the current record.
kccurgetvalue :: KcCur
              -> Bool -- ^ step
              -> IO BS.ByteString
kccurgetvalue cur step = do
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    alloca $ \ptr -> do
      cstr <- _kccurgetvalue c_cur ptr (if step then 1 else 0)
      if cstr == nullPtr then kccurdb cur >>= flip kcThrow "kccurgetkey"
        else do
        len <- peek ptr
        BS.unsafePackCStringFinalizer (castPtr cstr) (fromIntegral len) (kcfree cstr)

foreign import ccall safe "kclangc.h kccurgetvalue" _kccurgetvalue
  :: Ptr KCCUR -> Ptr CSize -> CInt -> IO CString

-- | Get a pair of the key and the value of the current record.
kccurget :: KcCur
         -> Bool -- ^ step
         -> IO (BS.ByteString, BS.ByteString)
kccurget cur step = do
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    alloca $ \ksp -> do
      alloca $ \vbp -> do
        alloca $ \vsp -> do
          cstr <- _kccurget c_cur ksp vbp vsp (if step then 1 else 0)
          if cstr == nullPtr then kccurdb cur >>= flip kcThrow "kccurget"
            else do
            ks <- peek ksp
            key <- BS.unsafePackCStringFinalizer (castPtr cstr) (fromIntegral ks)
                     (kcfree cstr) -- also frees vp
            vb <- peek vbp
            vs <- peek vsp
            val <- BS.packCStringLen (vb, fromIntegral vs)
            return (key, val)

foreign import ccall safe "kclangc.h kccurget" _kccurget
  :: Ptr KCCUR -> Ptr CSize -> Ptr CString -> Ptr CSize -> CInt ->
     IO CString

-- | Jump the cursor to the first record for forward scan.
kccurjump :: KcCur -> IO ()
kccurjump cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    rv <- _kccurjump c_cur
    db <- kccurdb cur
    handleBoolResult db "kccurjump" rv

foreign import ccall safe "kclangc.h kccurjump" _kccurjump
  :: Ptr KCCUR -> IO CInt

-- | Jump the cursor to a record for forward scan.
kccurjumpkey :: KcCur -> BS.ByteString -> IO ()
kccurjumpkey cur key =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      rv <- _kccurjumpkey c_cur kbuf (fromIntegral ksiz)
      db <- kccurdb cur
      handleBoolResult db "kccurjumpkey" rv

foreign import ccall safe "kclangc.h kccurjumpkey" _kccurjumpkey
  :: Ptr KCCUR -> CString -> CSize -> IO CInt

-- | Jump the cursor to the last record for backward scan.
kccurjumpback :: KcCur -> IO ()
kccurjumpback cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    rv <- _kccurjumpback c_cur
    db <- kccurdb cur
    handleBoolResult db "kccurjumpback" rv

foreign import ccall safe "kclangc.h kccurjumpback" _kccurjumpback
  :: Ptr KCCUR -> IO CInt

-- | Jump the cursor to a record for backward scan.
kccurjumpbackkey :: KcCur -> BS.ByteString -> IO ()
kccurjumpbackkey cur key =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    BS.unsafeUseAsCStringLen key $ \(kbuf, ksiz) -> do
      rv <- _kccurjumpbackkey c_cur kbuf (fromIntegral ksiz)
      db <- kccurdb cur
      handleBoolResult db "kccurjumpbackkey" rv

foreign import ccall safe "kclangc.h kccurjumpbackkey" _kccurjumpbackkey
  :: Ptr KCCUR -> CString -> CSize -> IO CInt

-- | Step the cursor to the next record.
kccurstep :: KcCur -> IO ()
kccurstep cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    rv <- _kccurstep c_cur
    db <- kccurdb cur
    handleBoolResult db "kccurstep" rv

foreign import ccall safe "kclangc.h kccurstep" _kccurstep
  :: Ptr KCCUR -> IO CInt

-- | Step the cursor to the previous record.
kccurstepback :: KcCur -> IO ()
kccurstepback cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    rv <- _kccurstepback c_cur
    db <- kccurdb cur
    handleBoolResult db "kccurstepback" rv

foreign import ccall safe "kclangc.h kccurstepback" _kccurstepback
  :: Ptr KCCUR -> IO CInt

-- | Get the database object.
kccurdb :: KcCur -> IO KcDb
kccurdb cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    _kccurdb c_cur >>= wrapdb

foreign import ccall unsafe "kclangc.h kccurdb" _kccurdb
  :: Ptr KCCUR -> IO (Ptr KCDB)

-- | Get the code of the last happened error.
kccurecode :: KcCur -> IO KcError
kccurecode cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    code <- _kccurecode c_cur
    return $ kcErrorOfNum code

foreign import ccall unsafe "kclangc.h kccurecode" _kccurecode
  :: Ptr KCCUR -> IO CInt

-- | Get the supplement message of the last happened error.
kccuremsg :: KcCur -> IO String
kccuremsg cur =
  withForeignPtr (unKcCur cur) $ \c_cur -> do
    msg <- _kccuremsg c_cur
    peekCAString msg

foreign import ccall unsafe "kclangc.h kccuremsg" _kccuremsg
  :: Ptr KCCUR -> IO CString

--------------------------------------------------------------------------------
-- Helper Routines

-- | Brackets a db command between 'kcdbnew', 'kcdbopen', 'kcdbclose',
-- and 'kcdbdel' calls.
kcwithdbopen :: FilePath -> [KcTune] -> [KcOpenMode] -> (KcDb -> IO a) -> IO a
kcwithdbopen path tune mode action =
  bracket
    (do db <- kcdbnew
        kcdbopen db path tune mode
        return db)
    (\db -> do kcdbclose db
               kcdbdel db)
    action

-- | Brackets a cursor command between 'kcdbcursor' and 'kccurdel' calls.
kcwithdbcursor :: KcDb -> (KcCur -> IO a) -> IO a
kcwithdbcursor db action = bracket (kcdbcursor db) kccurdel action

-- | Brackets a db command between 'kcdbbegintran' and 'kcdbendtran' calls,
-- committing on successful completion of the command, and aborting on
-- exception.
kcwithdbtran :: KcDb
             -> Bool  -- ^ @True@ for physical synchronization,
                      -- @False@ for logical synchronization
             -> IO a  -- ^ db command
             -> IO a
kcwithdbtran db hard action =
  bracketOnError (kcdbbegintran db hard)
                 (const $ kcdbendtran db False)
                 (const $ do rv <- action; kcdbendtran db True; return rv)

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