--
-- MODBUS Transaction Controller
--

local util = require("scada-common.util")

local txnctrl = {}

-- 2000ms max wait
local TIMEOUT = 2000

-- create a new transaction controller
---@nodiscard
function txnctrl.new()
    local self = {
        list = {},
        next_id = 0
    }

    ---@class transaction_controller
    local public = {}

    local insert = table.insert
    local remove = table.remove

    -- get the length of the transaction list
    ---@nodiscard
    function public.length()
        return #self.list
    end

    -- check if there are no active transactions
    ---@nodiscard
    function public.empty()
        return #self.list == 0
    end

    -- create a new transaction of the given type
    ---@nodiscard
    ---@param txn_type integer
    ---@return integer txn_id
    function public.create(txn_type)
        local txn_id = self.next_id

        insert(self.list, {
            txn_id = txn_id,
            txn_type = txn_type,
            expiry = util.time() + TIMEOUT
        })

        self.next_id = self.next_id + 1

        return txn_id
    end

    -- mark a transaction as resolved to get its transaction type
    ---@nodiscard
    ---@param txn_id integer
    ---@return integer|nil txn_type
    function public.resolve(txn_id)
        local txn_type = nil

        for i = 1, public.length() do
            if self.list[i].txn_id == txn_id then
                local entry = remove(self.list, i)
                txn_type = entry.txn_type
                break
            end
        end

        return txn_type
    end

    -- renew a transaction by re-inserting it with its ID and type
    ---@param txn_id integer
    ---@param txn_type integer
    function public.renew(txn_id, txn_type)
        insert(self.list, {
            txn_id = txn_id,
            txn_type = txn_type,
            expiry = util.time() + TIMEOUT
        })
    end

    -- close timed-out transactions
    function public.cleanup()
        local now = util.time()
        util.filter_table(self.list, function (txn) return txn.expiry > now end)
    end

    -- clear the transaction list
    function public.clear()
        self.list = {}
    end

    return public
end

return txnctrl
