packages feed

tigerbeetle-hs-0.1.0.0: src/Database/TigerBeetle/Raw/Account.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}

module Database.TigerBeetle.Raw.Account
  ( module Database.TigerBeetle.Raw.Account
  , TBAccount (..)
  )
where

import Control.Monad
import Control.Monad.IO.Class
import Data.Set qualified as S
import Data.Vector qualified as V
import Data.WideWord
import Database.TigerBeetle.Account
import Database.TigerBeetle.Internal.FFI.Account
  ( TBAccount (..)
  , TBAccountBalance (..)
  , TBAccountFilter (..)
  , TBAccountFilterFlags (..)
  )
import Database.TigerBeetle.Internal.FFI.Client
import Database.TigerBeetle.Internal.FFI.Query
  ( TBQueryFilter (..)
  , TBQueryFilterFlags
  )
import Database.TigerBeetle.Internal.FFI.Query qualified as Q
import Database.TigerBeetle.Ledger
import Database.TigerBeetle.Timestamp
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable

zeroTBAccount :: IO TBAccount
zeroTBAccount =
  pure $
    TBAccount
      { tbAccountId = 0
      , tbAccountDebitsPending = 0
      , tbAccountDebitsPosted = 0
      , tbAccountCreditsPending = 0
      , tbAccountCreditsPosted = 0
      , tbAccountUserData128 = 0
      , tbAccountUserData64 = 0
      , tbAccountUserData32 = 0
      , tbAccountReserved = 0
      , tbAccountLedger = 0
      , tbAccountCode = 0
      , tbAccountFlags = S.empty
      , tbAccountTimestamp = 0
      }

zeroTBAccountBalance :: IO TBAccountBalance
zeroTBAccountBalance =
  pure $
    TBAccountBalance
      { tbAccountBalanceDebitsPending = 0
      , tbAccountBalanceDebitsPosted = 0
      , tbAccountBalanceCreditsPending = 0
      , tbAccountBalanceCreditsPosted = 0
      , tbAccountBalanceTimestamp = 0
      , tbAccountBalanceReserved = mempty
      }

-- | Create a 'TBPacket' for the @TB_OPERATION_CREATE_ACCOUNTS@ operation.
createAccounts :: (MonadIO m) => [CreateAccount] -> m (ForeignPtr TBPacket)
createAccounts accts = do
  tbAccounts <- liftIO $ mapM createTBAccount accts
  tbPacketPtr <- liftIO $ createAccountsPacket tbAccounts
  liftIO $ newForeignPtr_ tbPacketPtr
 where
  createTBAccount :: CreateAccount -> IO TBAccount
  createTBAccount (CreateAccount{..}) = do
    tbAcct <- zeroTBAccount
    pure $
      tbAcct
        { tbAccountId = fromIntegral $ getAccountId createAccountId
        , tbAccountLedger = getLedgerId createAccountLedger
        , tbAccountCode = getAccountCode createAccountCode
        }

createAccountsPacket :: [TBAccount] -> IO (Ptr TBPacket)
createAccountsPacket accounts = do
  (accountData, accountDataSize) <- pack accounts
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBAccount @() accountData
      , tbPacketDataSize = fromIntegral accountDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = CreateAccounts
      , tbPacketStatus = Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [TBAccount] -> IO (Ptr TBAccount, Int)
  pack accts@(a : _) = do
    let dataSize = sizeOf a * length accts
    tbaccounts <- mallocBytes dataSize
    forM_ (zip [0 ..] accts) $ \(ix, acct) -> do
      pokeElemOff tbaccounts ix acct
    pure (tbaccounts, dataSize)
  pack [] = error "Cannot pack an empty list of accounts"

-- | Create a 'TBPacket' for the @TB_OPERATION_LOOKUP_ACCOUNTS@ operation.
lookupAccounts :: (MonadIO m) => [AccountId] -> m (ForeignPtr TBPacket)
lookupAccounts ids = do
  tbPacketPtr <- liftIO . createLookupAccountsPacket $ map getAccountId ids
  liftIO $ newForeignPtr_ tbPacketPtr

createLookupAccountsPacket :: [Word128] -> IO (Ptr TBPacket)
createLookupAccountsPacket ids = do
  (accountIdData, accountIdDataSize) <- pack ids
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @Word128 @() accountIdData
      , tbPacketDataSize = fromIntegral accountIdDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = LookupAccounts
      , tbPacketStatus = Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [Word128] -> IO (Ptr Word128, Int)
  pack acctIds@(a : _) = do
    let dataSize = sizeOf a * length acctIds
    tbAccountIds <- mallocBytes dataSize
    forM_ (zip [0 ..] acctIds) $ \(ix, acctId) -> do
      pokeElemOff tbAccountIds ix acctId
    pure (tbAccountIds, dataSize)
  pack [] = error "Cannot pack an empty list of account ids"

toTBAccountFilterFlag :: AccountFlag -> TBAccountFilterFlags
toTBAccountFilterFlag = \case
  AccountDebits -> Debits
  AccountCredits -> Credits
  AccountReversed -> Reversed

-- | Create a 'TBPacket' for the @TB_OPERATION_GET_ACCOUNT_BALANCES@ operation.
getAccountBalances :: (MonadIO m) => [AccountBalances] -> m (ForeignPtr TBPacket)
getAccountBalances balances = do
  tbPacketPtr <- liftIO $ createGetAccountBalancesPacket balances
  liftIO $ newForeignPtr_ tbPacketPtr

createGetAccountBalancesPacket :: [AccountBalances] -> IO (Ptr TBPacket)
createGetAccountBalancesPacket accountBalances = do
  (accountFilterData, accountFilterDataSize) <- pack accountBalances
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBAccountFilter @() accountFilterData
      , tbPacketDataSize = fromIntegral accountFilterDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = GetAccountBalances
      , tbPacketStatus = Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [AccountBalances] -> IO (Ptr TBAccountFilter, Int)
  pack balanceFilters = do
    let zeroAcctFilter =
          TBAccountFilter
            { tbAccountFilterAccountId = 0
            , tbAccountFilterUserData128 = 0
            , tbAccountFilterUserData64 = 0
            , tbAccountFilterUserData32 = 0
            , tbAccountFilterCode = 0
            , tbAccountFilterReserved = mempty
            , tbAccountFilterTimestampMin = 0
            , tbAccountFilterTimestampMax = 0
            , tbAccountFilterLimit = 0
            , tbAccountFilterFlags = mempty
            }
        dataSize = sizeOf zeroAcctFilter * length balanceFilters
    tbAccountFilters <- mallocBytes dataSize
    forM_ (zip [0 ..] balanceFilters) $ \(ix, balanceFilter) -> do
      let acctFilter =
            TBAccountFilter
              { tbAccountFilterAccountId = getAccountId balanceFilter.balancesAccountId
              , tbAccountFilterUserData128 = 0
              , tbAccountFilterUserData64 = 0
              , tbAccountFilterUserData32 = 0
              , tbAccountFilterCode = 0
              , tbAccountFilterReserved = mempty
              , tbAccountFilterTimestampMin = 0
              , tbAccountFilterTimestampMax = 0
              , tbAccountFilterLimit = fromIntegral balanceFilter.balancesLimit
              , tbAccountFilterFlags = toTBAccountFilterFlag `S.map` balanceFilter.balancesFlags
              }
      pokeElemOff tbAccountFilters ix acctFilter
    pure (tbAccountFilters, dataSize)

-- | Create a 'TBPacket' for the @TB_OPERATION_GET_ACCOUNT_TRANSFERS@ operation.
getAccountTransfers :: (MonadIO m) => [AccountTransfers] -> m (ForeignPtr TBPacket)
getAccountTransfers transfers = do
  tbPacketPtr <- liftIO $ createGetAccountTransfersPacket transfers
  liftIO $ newForeignPtr_ tbPacketPtr

createGetAccountTransfersPacket :: [AccountTransfers] -> IO (Ptr TBPacket)
createGetAccountTransfersPacket accountTransfers = do
  (accountFilterData, accountFilterDataSize) <- pack accountTransfers
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBAccountFilter @() accountFilterData
      , tbPacketDataSize = fromIntegral accountFilterDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = GetAccountTransfers
      , tbPacketStatus = Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [AccountTransfers] -> IO (Ptr TBAccountFilter, Int)
  pack transfers = do
    let zeroAcctFilter =
          TBAccountFilter
            { tbAccountFilterAccountId = 0
            , tbAccountFilterUserData128 = 0
            , tbAccountFilterUserData64 = 0
            , tbAccountFilterUserData32 = 0
            , tbAccountFilterCode = 0
            , tbAccountFilterReserved = mempty
            , tbAccountFilterTimestampMin = 0
            , tbAccountFilterTimestampMax = 0
            , tbAccountFilterLimit = 0
            , tbAccountFilterFlags = mempty
            }
        dataSize = sizeOf zeroAcctFilter * length transfers
    tbAccountFilters <- mallocBytes dataSize
    forM_ (zip [0 ..] transfers) $ \(ix, transfer) -> do
      let acctFilter =
            TBAccountFilter
              { tbAccountFilterAccountId = getAccountId transfer.transfersAccountId
              , tbAccountFilterUserData128 = 0
              , tbAccountFilterUserData64 = 0
              , tbAccountFilterUserData32 = 0
              , tbAccountFilterCode = 0
              , tbAccountFilterReserved = mempty
              , tbAccountFilterTimestampMin = 0
              , tbAccountFilterTimestampMax = 0
              , tbAccountFilterLimit = fromIntegral transfer.transfersLimit
              , tbAccountFilterFlags = toTBAccountFilterFlag `S.map` transfer.transfersFlags
              }
      pokeElemOff tbAccountFilters ix acctFilter
    pure (tbAccountFilters, dataSize)

-- | Create a 'TBPacket' for the @TB_OPERATION_QUERY_ACCOUNTS@ operation.
queryAccounts :: (MonadIO m) => [AccountQuery] -> m (ForeignPtr TBPacket)
queryAccounts queries = do
  tbPacketPtr <- liftIO $ queryAccountsPacket queries
  liftIO $ newForeignPtr_ tbPacketPtr

queryAccountsPacket :: [AccountQuery] -> IO (Ptr TBPacket)
queryAccountsPacket accountQueries = do
  (accountFilterData, accountFilterDataSize) <- pack accountQueries
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBQueryFilter @() accountFilterData
      , tbPacketDataSize = fromIntegral accountFilterDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = QueryAccounts
      , tbPacketStatus = Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [AccountQuery] -> IO (Ptr TBQueryFilter, Int)
  pack queries = do
    let zeroQueryFilter =
          TBQueryFilter
            { tbQueryFilterUserData128 = 0
            , tbQueryFilterUserData64 = 0
            , tbQueryFilterUserData32 = 0
            , tbQueryFilterLedger = 0
            , tbQueryFilterCode = 0
            , tbQueryFilterReserved = mempty
            , tbQueryFilterTimestampMin = 0
            , tbQueryFilterTimestampMax = 0
            , tbQueryFilterLimit = 0
            , tbQueryFilterFlags = mempty
            }
        dataSize = sizeOf zeroQueryFilter * length queries
    tbAccountFilters <- mallocBytes dataSize
    forM_ (zip [0 ..] queries) $ \(ix, query) -> do
      let acctFilter =
            TBQueryFilter
              { tbQueryFilterUserData128 = 0
              , tbQueryFilterUserData64 = 0
              , tbQueryFilterUserData32 = 0
              , tbQueryFilterLedger = getLedgerId query.accountQueryLedger
              , tbQueryFilterCode = getAccountCode query.accountQueryCode
              , tbQueryFilterReserved = mempty
              , tbQueryFilterTimestampMin = getTimestamp query.accountQueryTimestampMin
              , tbQueryFilterTimestampMax = getTimestamp query.accountQueryTimestampMax
              , tbQueryFilterLimit = fromIntegral query.accountQueryLimit
              , tbQueryFilterFlags = toTBQueryFilterFlag `S.map` query.accountQueryFlags
              }
      pokeElemOff tbAccountFilters ix acctFilter
    pure (tbAccountFilters, dataSize)

  toTBQueryFilterFlag :: AccountQueryFlag -> TBQueryFilterFlags
  toTBQueryFilterFlag = \case
    AccountQueryReversed -> Q.Reversed