packages feed

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

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

module Database.TigerBeetle.Raw.Transfer
  ( module Database.TigerBeetle.Raw.Transfer
  , TBTransfer (..)
  , FFI.TBTransferFlag (..)
  )
where

import Control.Monad
import Control.Monad.IO.Class
import Data.Set qualified as Set
import Data.Vector qualified as V
import Database.TigerBeetle.Account hiding (AccountFlags (..))
import Database.TigerBeetle.Amount
import Database.TigerBeetle.Internal.FFI.Client
  ( TBOperation (..)
  , TBPacket (..)
  )
import Database.TigerBeetle.Internal.FFI.Client qualified as Client
import Database.TigerBeetle.Internal.FFI.Query
  ( TBQueryFilter (..)
  , TBQueryFilterFlags
  )
import Database.TigerBeetle.Internal.FFI.Query qualified as Raw
import Database.TigerBeetle.Internal.FFI.Transfer (TBTransfer (..))
import Database.TigerBeetle.Internal.FFI.Transfer qualified as FFI
import Database.TigerBeetle.Ledger
import Database.TigerBeetle.Timestamp
import Database.TigerBeetle.Transfer
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable

zeroTBTransfer :: IO TBTransfer
zeroTBTransfer =
  pure $
    TBTransfer
      { tbTransferId = 0
      , tbTransferDebitAccountId = 0
      , tbTransferCreditAccountId = 0
      , tbTransferAmount = 0
      , tbTransferPendingId = 0
      , tbTransferUserData128 = 0
      , tbTransferUserData64 = 0
      , tbTransferUserData32 = 0
      , tbTransferTimeout = 0
      , tbTransferLedger = 0
      , tbTransferCode = 0
      , tbTransferFlags = mempty
      , tbTransferTimestamp = 0
      }

-- | Create a 'TBPacket' for the @TB_OPERATION_CREATE_TRANSFERS@ operation.
createTransfers :: [CreateTransfer] -> IO (ForeignPtr TBPacket)
createTransfers transfers = do
  tbTransfers <- liftIO $ mapM createTBTransfer transfers
  tbPacketPtr <- liftIO $ createTransfersPacket tbTransfers
  liftIO $ newForeignPtr_ tbPacketPtr
 where
  createTBTransfer :: CreateTransfer -> IO TBTransfer
  createTBTransfer CreateTransfer{..} = do
    tbTransfer <- zeroTBTransfer
    pure $
      tbTransfer
        { tbTransferId = getTransferId createTransferId
        , tbTransferDebitAccountId = getAccountId createTransferDebitAccountId
        , tbTransferCreditAccountId = getAccountId createTransferCreditAccountId
        , tbTransferAmount = getAmount createTransferAmount
        , tbTransferPendingId = 0
        , tbTransferUserData128 = 0
        , tbTransferUserData64 = 0
        , tbTransferUserData32 = 0
        , tbTransferTimeout = 100
        , tbTransferLedger = getLedgerId createTransferLedger
        , tbTransferCode = getTransferCode createTransferCode
        , tbTransferFlags = toRawTransferFlags `Set.map` createTransferFlags
        , tbTransferTimestamp = 0
        }
   where
    toRawTransferFlags :: TransferFlag -> FFI.TBTransferFlag
    toRawTransferFlags = \case
      Linked -> FFI.Linked
      Pending -> FFI.Pending
      PostPending -> FFI.PostPendingTransfer
      VoidPending -> FFI.VoidPendingTransfer
      BalancingDebit -> FFI.BalancingDebit
      BalancingCredit -> FFI.BalancingCredit
      ClosingDebit -> FFI.ClosingDebit
      ClosingCredit -> FFI.ClosingCredit
      Imported -> FFI.Imported

createTransfersPacket :: [TBTransfer] -> IO (Ptr TBPacket)
createTransfersPacket transfers = do
  (transferData, transferDataSize) <- pack transfers
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBTransfer @() transferData
      , tbPacketDataSize = fromIntegral transferDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = CreateTransfers
      , tbPacketStatus = Client.Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [TBTransfer] -> IO (Ptr TBTransfer, Int)
  pack ts@(a : _) = do
    let dataSize = sizeOf a * length ts
    tbtransfers <- mallocBytes dataSize
    forM_ (zip [0 ..] ts) $ \(ix, transfer) -> do
      pokeElemOff tbtransfers ix transfer
    pure (tbtransfers, dataSize)
  pack [] = error "Cannot pack an empty list of transfers"

-- | Create a 'TBPacket' for the @TB_OPERATION_QUERY_TRANSFERS@ operation.
queryTransfers :: [TransferQuery] -> IO (ForeignPtr TBPacket)
queryTransfers transferQueries = do
  tbPacketPtr <- liftIO $ queryTransfersPacket transferQueries
  liftIO $ newForeignPtr_ tbPacketPtr

queryTransfersPacket :: [TransferQuery] -> IO (Ptr TBPacket)
queryTransfersPacket transferQueries = do
  (transferFilterData, transferFilterDataSize) <- pack transferQueries
  packetPtr <- malloc
  poke packetPtr $
    TBPacket
      { tbPacketUserData = nullPtr
      , tbPacketData = castPtr @TBQueryFilter @() transferFilterData
      , tbPacketDataSize = fromIntegral transferFilterDataSize
      , tbPacketUserTag = 0
      , tbPacketOperation = QueryTransfers
      , tbPacketStatus = Client.Ok
      , tbPacketOpaque = V.empty
      }
  pure packetPtr
 where
  pack :: [TransferQuery] -> 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 = fromIntegral query.queryTransferLedger -- TODO: refactor
              , tbQueryFilterCode = fromIntegral query.queryTransferCode -- TODO: refactor
              , tbQueryFilterReserved = mempty
              , tbQueryFilterTimestampMin = getTimestamp query.queryTransferTimestampMin
              , tbQueryFilterTimestampMax = getTimestamp query.queryTransferTimestampMax
              , tbQueryFilterLimit = fromIntegral query.queryTransferLimit
              , tbQueryFilterFlags = toTBQueryFilterFlag `Set.map` query.queryTransferFlags
              }
      pokeElemOff tbAccountFilters ix acctFilter
    pure (tbAccountFilters, dataSize)

  toTBQueryFilterFlag :: TransferQueryFlag -> TBQueryFilterFlags
  toTBQueryFilterFlag = \case
    Reversed -> Raw.Reversed