{-# LANGUAGE Safe                     #-}
{-# LANGUAGE ForeignFunctionInterface #-}

-- This file is part of zhk
--
-- 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 the {organization} 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 HOLDER 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.

module Database.Zookeeper.CApi
       ( -- * C to Haskell
         toStat
       , toState
       , toAclList
       , toZKError
       , allocaStat
       , toClientId
       , toStringList
       , allocaAclVec
       , allocaStrVec
         -- * Haskell to C
       , withAclList
       , wrapWatcher
       , fromLogLevel
       , fromCreateFlag
       , fromCreateFlags
       , wrapVoidCompletion
         -- * Error handling
       , tryZ
       , isZOK
       , onZOK
       , whenZOK
         -- * C functions
       , c_zooSet2
       , c_zooWGet
       , c_zooState
       , c_zooDelete
       , c_zooSetAcl
       , c_zooCreate
       , c_zooGetAcl
       , c_zooAddAuth
       , c_zooWExists
       , c_zooClientId
       , c_zookeeperInit
       , c_zooSetWatcher
       , c_zookeeperClose
       , c_zooRecvTimeout
       , c_isUnrecoverable
       , c_zooWGetChildren
       , c_zooSetDebugLevel
       ) where

#include <zookeeper.h>

import Foreign.C
import Foreign.Safe
import Control.Applicative
import Database.Zookeeper.Types

tryZ :: IO CInt -> IO a -> IO (Either ZKError a)
tryZ zkIO nextIO = do
  rc <- zkIO
  rc `onZOK` nextIO

isZOK :: CInt -> Bool
isZOK rc = rc == (#const ZOK)

onZOK :: CInt -> IO a -> IO (Either ZKError a)
onZOK rc nextIO
  | isZOK rc  = fmap Right nextIO
  | otherwise = return (Left $ toZKError rc)

whenZOK :: CInt -> IO (Either ZKError a) -> IO (Either ZKError a)
whenZOK rc succIO
  | isZOK rc  = succIO
  | otherwise = return (Left $ toZKError rc)

toStat :: Ptr CStat -> IO Stat
toStat ptr = Stat <$> (#peek struct Stat, czxid) ptr
                  <*> (#peek struct Stat, mzxid) ptr
                  <*> (#peek struct Stat, pzxid) ptr
                  <*> (#peek struct Stat, ctime) ptr
                  <*> (#peek struct Stat, mtime) ptr
                  <*> (#peek struct Stat, version) ptr
                  <*> (#peek struct Stat, cversion) ptr
                  <*> (#peek struct Stat, aversion) ptr
                  <*> (#peek struct Stat, dataLength) ptr
                  <*> (#peek struct Stat, numChildren) ptr
                  <*> liftA toEphemeralOwner ((#peek struct Stat, ephemeralOwner) ptr)
    where
      toEphemeralOwner 0 = Nothing
      toEphemeralOwner c = Just c

fromCreateFlag :: CreateFlag -> CInt
fromCreateFlag Sequence  = (#const ZOO_SEQUENCE)
fromCreateFlag Ephemeral = (#const ZOO_EPHEMERAL)

fromCreateFlags :: [CreateFlag] -> CInt
fromCreateFlags = foldr (.|.) 0 . map fromCreateFlag

fromPerm :: Perm -> CInt
fromPerm CanRead   = (#const ZOO_PERM_READ)
fromPerm CanAdmin  = (#const ZOO_PERM_ADMIN)
fromPerm CanWrite  = (#const ZOO_PERM_WRITE)
fromPerm CanCreate = (#const ZOO_PERM_CREATE)
fromPerm CanDelete = (#const ZOO_PERM_DELETE)

fromLogLevel :: ZLogLevel -> CInt
fromLogLevel ZLogError = (#const ZOO_LOG_LEVEL_ERROR)
fromLogLevel ZLogWarn  = (#const ZOO_LOG_LEVEL_WARN)
fromLogLevel ZLogInfo  = (#const ZOO_LOG_LEVEL_INFO)
fromLogLevel ZLogDebug = (#const ZOO_LOG_LEVEL_DEBUG)

fromPerms :: [Perm] -> CInt
fromPerms = foldr (.|.) 0 . map fromPerm

toPerms :: CInt -> [Perm]
toPerms n = buildList [ ((#const ZOO_PERM_READ), CanRead)
                      , ((#const ZOO_PERM_ADMIN), CanAdmin)
                      , ((#const ZOO_PERM_WRITE), CanWrite)
                      , ((#const ZOO_PERM_CREATE), CanCreate)
                      , ((#const ZOO_PERM_DELETE), CanDelete)
                      ]
    where
      buildList [] = []
      buildList ((t, a):xs)
        | n .&. t == t = a : buildList xs
        | otherwise    = buildList xs

toStringList :: Ptr CStrVec -> IO [String]
toStringList strvPtr = do
  count   <- (#peek struct String_vector, count) strvPtr
  dataPtr <- (#peek struct String_vector, data) strvPtr
  buildList [] count dataPtr
    where
      buildList :: [String] -> Int32 -> Ptr CString -> IO [String]
      buildList acc 0 _   = return $ reverse acc
      buildList acc n ptr = do
        item <- peek ptr >>= peekCString
        buildList (item : acc) (n-1) (ptr `plusPtr` (sizeOf ptr))

toClientId :: Ptr CClientID -> IO Int64
toClientId clientPtr = (#peek clientid_t, client_id) clientPtr

allocaStat :: (Ptr CStat -> IO a) -> IO a
allocaStat fun = allocaBytes (#size struct Stat) fun

toAclList :: Ptr CAclVec -> IO AclList
toAclList aclvPtr = do
  count  <- (#peek struct ACL_vector, count) aclvPtr
  aclPtr <- (#peek struct ACL_vector, data) aclvPtr
  fmap List (buildList [] count aclPtr)
    where
      buildList :: [Acl] -> Int32 -> Ptr CAcl -> IO [Acl]
      buildList acc 0 _   = return acc
      buildList acc n ptr = do
        acl <- Acl <$> ((#peek struct ACL, id.scheme) ptr >>= peekCString)
                   <*> ((#peek struct ACL, id.id) ptr >>= peekCString)
                   <*> (fmap toPerms ((#peek struct ACL, perms) ptr))
        buildList (acl : acc) (n-1) (ptr `plusPtr` (#size struct ACL))

allocaStrVec :: (Ptr CStrVec -> IO a) -> IO a
allocaStrVec = allocaBytes (#size struct String_vector)

allocaAclVec :: (Ptr CAclVec -> IO a) -> IO a
allocaAclVec = allocaBytes (#size struct ACL_vector)

withAclList :: AclList -> (Ptr CAclVec -> IO a) -> IO a
withAclList CreatorAll cont    = cont c_zooCreatorAclAll
withAclList OpenAclUnsafe cont = cont c_zooOpenAclUnsafe
withAclList ReadAclUnsafe cont = cont c_zooReadAclUnsafe
withAclList (List acls) cont   =
  allocaAclVec $ \aclvPtr -> do
    (#poke struct ACL_vector, count) aclvPtr count
    allocaBytes (count * (#size struct ACL)) $ \aclPtr -> do
      (#poke struct ACL_vector, data) aclvPtr aclPtr
      pokeAcls acls aclvPtr aclPtr
    where
      count = length acls

      pokeAcls [] aclvPtr _              = cont aclvPtr
      pokeAcls (acl:rest) aclvPtr aclPtr = do
        withCString (aclScheme acl) $ \schemePtr -> do
          withCString (aclId acl) $ \idPtr -> do
            (#poke struct ACL, id.id) aclPtr idPtr
            (#poke struct ACL, perms) aclPtr (fromPerms (aclFlags acl))
            (#poke struct ACL, id.scheme) aclPtr schemePtr
            pokeAcls rest aclvPtr (aclPtr `plusPtr` (#size struct ACL))

toZKError :: CInt -> ZKError
toZKError (#const ZNONODE)                  = NoNodeError
toZKError (#const ZNOAUTH)                  = NoAuthError
toZKError (#const ZCLOSING)                 = ClosingError
toZKError (#const ZNOTHING)                 = NothingError
toZKError (#const ZAPIERROR)                = ApiError
toZKError (#const ZNOTEMPTY)                = NotEmptyError
toZKError (#const ZBADVERSION)              = BadVersionError
toZKError (#const ZINVALIDACL)              = InvalidACLError
toZKError (#const ZAUTHFAILED)              = AuthFailedError
toZKError (#const ZNODEEXISTS)              = NodeExistsError
toZKError (#const ZSYSTEMERROR)             = SystemError
toZKError (#const ZBADARGUMENTS)            = BadArgumentsError
toZKError (#const ZINVALIDSTATE)            = InvalidStateError
toZKError (#const ZSESSIONMOVED)            = SessionMovedError
toZKError (#const ZUNIMPLEMENTED)           = UnimplmenetedError
toZKError (#const ZCONNECTIONLOSS)          = ConnectionLossError
toZKError (#const ZSESSIONEXPIRED)          = SessionExpiredError
toZKError (#const ZINVALIDCALLBACK)         = InvalidCallbackError
toZKError (#const ZMARSHALLINGERROR)        = MarshallingError
toZKError (#const ZOPERATIONTIMEOUT)        = OperationTimeoutError
toZKError (#const ZDATAINCONSISTENCY)       = DataInconsistencyError
toZKError (#const ZRUNTIMEINCONSISTENCY)    = RuntimeInconsistencyError
toZKError (#const ZNOCHILDRENFOREPHEMERALS) = NoChildrenForEphemeralsError
toZKError code                              = (UnknownError $ fromIntegral code)

toState :: CInt -> State
toState (#const ZOO_CONNECTED_STATE)       = ConnectedState
toState (#const ZOO_CONNECTING_STATE)      = ConnectingState
toState (#const ZOO_ASSOCIATING_STATE)     = AssociatingState
toState (#const ZOO_AUTH_FAILED_STATE)     = AuthFailedState
toState (#const ZOO_EXPIRED_SESSION_STATE) = ExpiredSessionState
toState code                               = UnknownState $ fromIntegral code

toEvent :: CInt -> Event
toEvent (#const ZOO_CHILD_EVENT)       = ChildEvent
toEvent (#const ZOO_CREATED_EVENT)     = CreatedEvent
toEvent (#const ZOO_DELETED_EVENT)     = DeletedEvent
toEvent (#const ZOO_CHANGED_EVENT)     = ChangedEvent
toEvent (#const ZOO_SESSION_EVENT)     = SessionEvent
toEvent (#const ZOO_NOTWATCHING_EVENT) = NotWatchingEvent
toEvent code                           = UnknownEvent $ fromIntegral code

wrapWatcher :: Maybe Watcher -> IO (FunPtr CWatcherFn)
wrapWatcher Nothing   = return nullFunPtr
wrapWatcher (Just fn) = c_watcherFn $ \zh cevent cstate cpath _ -> do
  let event = toEvent cevent
      state = toState cstate
  path <- if (cpath == nullPtr)
            then return Nothing
            else fmap Just (peekCString cpath)
  fn (Zookeeper zh) event state path

wrapVoidCompletion :: VoidCompletion -> IO (FunPtr CVoidCompletionFn)
wrapVoidCompletion fn =
  c_voidCompletionFn $ \rc _ -> (fn =<< onZOK rc (return ()))

foreign import ccall safe "wrapper"
  c_watcherFn :: CWatcherFn
                 -> IO (FunPtr CWatcherFn)

foreign import ccall safe "wrapper"
  c_voidCompletionFn :: CVoidCompletionFn
                     -> IO (FunPtr CVoidCompletionFn)

foreign import ccall safe "zookeeper.h zookeeper_init"
  c_zookeeperInit :: CString
                  -> FunPtr CWatcherFn
                  -> CInt
                  -> Ptr CClientID
                  -> Ptr ()
                  -> CInt
                  -> IO (Ptr CZHandle)

foreign import ccall safe "zookeeper.h zookeeper_close"
  c_zookeeperClose :: Ptr CZHandle -> IO ()

foreign import ccall safe "zookeeper.h zoo_set_watcher"
  c_zooSetWatcher :: Ptr CZHandle -> FunPtr CWatcherFn -> IO ()

foreign import ccall safe "zookeeper.h zoo_create"
  c_zooCreate :: Ptr CZHandle -> CString -> CString -> CInt -> Ptr CAclVec -> CInt -> CString -> CInt -> IO CInt

foreign import ccall safe "zookeeper.h zoo_delete"
  c_zooDelete :: Ptr CZHandle -> CString -> CInt -> IO CInt

foreign import ccall safe "zookeeper.h zoo_wexists"
  c_zooWExists :: Ptr CZHandle -> CString -> FunPtr CWatcherFn -> Ptr () -> Ptr CStat -> IO CInt

foreign import ccall safe "zookeeper.h zoo_state"
  c_zooState :: Ptr CZHandle -> IO CInt

foreign import ccall safe "zookeeper.h zoo_client_id"
  c_zooClientId :: Ptr CZHandle -> IO (Ptr CClientID)

foreign import ccall safe "zookeeper.h zoo_recv_timeout"
  c_zooRecvTimeout :: Ptr CZHandle -> IO CInt

foreign import ccall safe "zookeeper.h zoo_add_auth"
  c_zooAddAuth :: Ptr CZHandle -> CString -> CString -> CInt -> FunPtr CVoidCompletionFn -> Ptr () -> IO CInt

foreign import ccall safe "zookeeper.h is_unrecoverable"
  c_isUnrecoverable :: Ptr CZHandle -> IO CInt

foreign import ccall safe "zookeeper.h zoo_set_debug_level"
  c_zooSetDebugLevel :: CInt -> IO ()

foreign import ccall safe "zookeeper.h zoo_get_acl"
  c_zooGetAcl :: Ptr CZHandle -> CString -> Ptr CAclVec -> Ptr CStat -> IO CInt

foreign import ccall safe "zookeeper.h zoo_set_acl"
  c_zooSetAcl :: Ptr CZHandle -> CString -> CInt -> Ptr CAclVec -> IO CInt

foreign import ccall safe "zookeeper.h zoo_wget"
  c_zooWGet :: Ptr CZHandle -> CString -> FunPtr CWatcherFn -> Ptr () -> CString -> Ptr CInt -> Ptr CStat -> IO CInt

foreign import ccall safe "zookeeper.h zoo_set2"
  c_zooSet2 :: Ptr CZHandle -> CString -> CString -> CInt -> CInt -> Ptr CStat -> IO CInt

foreign import ccall safe "zookeeper.h zoo_wget_children"
  c_zooWGetChildren :: Ptr CZHandle -> CString -> FunPtr CWatcherFn -> Ptr () -> Ptr CStrVec -> IO CInt

foreign import ccall safe "zookeeper.h &ZOO_CREATOR_ALL_ACL"
  c_zooCreatorAclAll :: Ptr CAclVec

foreign import ccall safe "zookeeper.h &ZOO_OPEN_ACL_UNSAFE"
  c_zooOpenAclUnsafe :: Ptr CAclVec

foreign import ccall safe "zookeeper.h &ZOO_READ_ACL_UNSAFE"
  c_zooReadAclUnsafe :: Ptr CAclVec
