/*
 *******************************************************************************
 *
 *  Copyright 2025 RIEGL Laser Measurement Systems
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  SPDX-License-Identifier: Apache-2.0
 *
 *******************************************************************************
 */
/*!
 *******************************************************************************
 *
 * \file    transactions.hpp
 * \author  RIEGL LMS GmbH, Austria
 * \brief   Manage point cloud transactions
 * \version 2015-10-14/AW: Initial version
 * \version 2017-11-24/AW: Constructors declared as "explicit" (#2825)
 * \version 2021-11-17/AW: New function to discard transaction data (#3843)
 * \version 2021-11-18/AW: New function to estimate transaction size (#3837)
 *
 *******************************************************************************
 */

#ifndef RIEGL_RDB_POINTCLOUD_TRANSACTIONS_HPP
#define RIEGL_RDB_POINTCLOUD_TRANSACTIONS_HPP

//---< INCLUDES >---------------------------------------------------------------

#include <memory>
#include <string>
#include <vector>
#include <cstdlib>
#include <utility>

#include "riegl/rdb/pointcloud/pointcloudData.hpp"
#include "riegl/rdb/pointcloud/transaction.hpp"
#include "riegl/rdb/progress.hpp"

//---< NAMESPACE >--------------------------------------------------------------

namespace riegl {
namespace rdb {
namespace pointcloud {

//---< CLASS Transactions >-----------------------------------------------------
/*!
 * \brief Manage point cloud transactions
 *
 * Modifying the point cloud database means to execute a transaction.
 *
 * This class allows to start new transactions (see begin()) and to
 * browse past (already executed) transactions.
 *
 * \see riegl::rdb::Pointcloud::transaction()
 */
class Transactions
{
public:
    /*!
     * \brief Constructor
     * \note  You cannot create new Transactions objects directly,
     *        use riegl::rdb::Pointcloud::transaction() instead.
     */
    explicit Transactions(riegl::rdb::PointcloudData* pointcloud);

    /*!
     * \brief Create new transaction
     *
     * Whenever you are going to modify the point cloud, you must create a
     * transaction by using this function. Without a transaction, any function
     * that modifies the point cloud will fail (i.e. it throws an exception).
     *
     * A transaction automatically locks the database so that other users
     * can query data but can __NOT__ add or modify data. Please note that
     * only __one transaction at a time__ is allowed. If there is already
     * a transaction pending, this function will raise an exception (see
     * Error::TransactionPending).
     *
     * To finish a transaction either call commit() or rollback().
     *
     * \note Parameters 'title' and 'agent' are required - i.e. creating
     *       a new transaction will fail if empty strings are provided.
     *       All other parameters are optional.
     *
     * \note Parameters 'comments' and 'settings' serve the user and client
     *       application for information, e.g. to repeat processing steps
     *       later on. They have no influence on the transaction itself. The
     *       client application is free to store information in any format
     *       that is string-compatible (e.g. plain text, INI, XML, JSON).
     *
     * \note The total size of the 'title', 'agent', 'comments' and 'settings'
     *       strings must not exceed 500 MB (bytes, not characters).
     */
    Transaction::ID begin(
        const std::string &title,         //!< [in] short description, e.g. "Import"
        const std::string &agent,         //!< [in] software name, e.g. "rdbimport v1.0"
        const std::string &comments = "", //!< [in] e.g. process details for humans
        const std::string &settings = ""  //!< [in] e.g. process settings for software
    );

    /*!
     * \brief Commit current transaction
     *
     * This function commits the current transaction. All changes made by the
     * transaction become visible to others.
     */
    void commit(
        Progress            progress  = nullptr, //!< [in] commit progress callback function
        void               *userdata  = nullptr, //!< [in] progress callback function user data
        const std::uint32_t signature = 0,       //!< [in] signature method (0: none, 1: default)
        const std::uint32_t key_size  = 0,       //!< [in] signature encryption key size (at least 32 byte)
        const void* const   key_data  = nullptr  //!< [in] signature encryption key buffer
    );

    /*!
     * \brief Commit current transaction
     *
     * Overloaded commit() that takes any callable type as progress callback.
     */
    template<typename Callable>
    void commit(
        Callable          &&progress,           //!< [in] progress callback function
        const std::uint32_t signature = 0,      //!< [in] signature method (0: none, 1: default)
        const std::uint32_t key_size  = 0,      //!< [in] signature encryption key size (at least 32 byte)
        const void* const   key_data  = nullptr //!< [in] signature encryption key buffer
    )
    {
        typedef typename std::decay<Callable>::type CallableType;
        this->commit(
            &progress_proxy_callable<CallableType>,
            const_cast<CallableType*>(&progress),
            signature, key_size, key_data
        );
    }

    /*!
     * \brief Commit current transaction
     *
     * Overloaded commit() that takes a progress callback method of an object.
     */
    template<typename Receiver>
    void commit(
        void (Receiver::*progress)(std::uint8_t), Receiver &receiver,
        const std::uint32_t signature = 0,      //!< [in] signature method (0: none, 1: default)
        const std::uint32_t key_size  = 0,      //!< [in] signature encryption key size (at least 32 byte)
        const void* const   key_data  = nullptr //!< [in] signature encryption key buffer
    )
    {
        auto userdata = std::make_pair(progress, &receiver);
        this->commit(
            &progress_proxy_receiver<Receiver>, &userdata,
            signature, key_size, key_data
        );
    }

    /*!
     * \brief Commit current transaction
     *
     * Overloaded commit() that takes a constant progress callback method
     * of a constant object.
     */
    template<typename Receiver>
    void commit(
        void (Receiver::*progress)(std::uint8_t) const, const Receiver &receiver,
        const std::uint32_t signature = 0,      //!< [in] signature method (0: none, 1: default)
        const std::uint32_t key_size  = 0,      //!< [in] signature encryption key size (at least 32 byte)
        const void* const   key_data  = nullptr //!< [in] signature encryption key buffer
    )
    {
        auto userdata = std::make_pair(progress, &receiver);
        this->commit(
            &progress_proxy_receiver<Receiver>, &userdata,
            signature, key_size, key_data
        );
    }

    /*!
     * \brief Abort current transaction
     *
     * This function rolls back the current transaction and causes all changes
     * made by the transaction to be discarded.
     */
    void rollback();

    /*!
     * \brief Get list of transactions
     *
     * The database keeps a log (journal) of all transactions. This function
     * returns a list of identifiers (IDs) for all recorded transactions.
     */
    std::vector<Transaction::ID> list() const;

    /*!
     * \brief ID of current transaction
     *
     * This function returns the ID of the current transaction. Usually this
     * is the transaction that was committed at last. However, if restore() is
     * used to return to a previous database state, the current transaction is
     * the restored transaction.
     *
     * \note This function does not return the ID of a pending transaction.
     */
    Transaction::ID current() const;

    /*!
     * \brief Check if transaction is pending
     *
     * This function checks whether there is a pending transaction that was
     * started by __this__ database instance. This function does __not__ check
     * whether a transaction was started by a different database instance of
     * the current or different thread or process.
     */
    bool pending() const;

    /*!
     * \brief Query transaction details
     */
    Transaction details(
        const Transaction::ID transaction //!< [in] transaction identifier
    ) const;

    /*!
     * \brief Restore database state
     *
     * This function restores the database state that was current at the end
     * of the transaction. Those transactions that were created after the
     * restored transaction remain valid until a new transaction is created.
     * This offers some simple undo/redo functionality.
     */
    void restore(
        const Transaction::ID transaction //!< [in] transaction identifier
    );

    /*!
     * \brief Discard transaction data
     *
     * This function deletes the given transaction(s). Please note that this
     * operation only removes the transaction(s) from the database history and
     * releases the related data blocks in the database file so that they can
     * be re-used by subsequent transactions. However the database file size
     * will not decrease unless you call vacuum().
     *
     * \note The first (database creation) and the current transaction
     *       (last committed or restored) can not be deleted.
     *
     * \see riegl::rdb::pointcloud::Management::finalize()
     * \see riegl::rdb::pointcloud::Management::vacuum()
     */
    void discard(
        const Transaction::ID transaction //!< [in] transaction identifier
    );

    //! \copydoc discard()
    void discard(
        const std::vector<Transaction::ID> &transactions //!< [in] transaction identifiers
    );

    /*!
     * \brief Estimate database size
     *
     * This function returns the size (in bytes) of all data of the specified
     * transactions, which is approximately the file size of a database that
     * contains only these transactions (and no gaps).
     *
     * \note Data blocks may be shared among multiple transactions and size()
     *       returns the total size of the union of all used data blocks. That
     *       is why size(1) + size(2) is not necessarily equal to size(1, 2).
     *
     * \see riegl::rdb::pointcloud::Transactions::discard()
     * \see riegl::rdb::pointcloud::Management::finalize()
     * \see riegl::rdb::pointcloud::Management::vacuum()
     */
    std::uint64_t size(
        const Transaction::ID transaction //!< [in] transaction identifier
    ) const;

    //! \copydoc size()
    std::uint64_t size(
        const std::vector<Transaction::ID> &transactions //!< [in] transaction identifiers
    ) const;

private:
    riegl::rdb::PointcloudData *data;

    template<typename Callable>
    static void progress_proxy_callable(std::uint8_t progress, void *userdata)
    {
        try
        {
            Callable &callback = *reinterpret_cast<Callable*>(userdata);
            callback(progress); // = invoke original callback
        }
        catch(...)
        {
            // ignore all errors
        }
    }

    template<typename Receiver>
    static void progress_proxy_receiver(std::uint8_t progress, void *userdata)
    {
        try
        {
            typedef void (Receiver::*Function)(std::uint8_t); // just a shortcut...
            auto data(reinterpret_cast<std::pair<Function, Receiver*>*>(userdata));
            (*data->second.*data->first)(progress); // = invoke original callback
        }
        catch(...)
        {
            // ignore all errors
        }
    }
};

}}} // namespace riegl::rdb::pointcloud

#endif // RIEGL_RDB_POINTCLOUD_TRANSACTIONS_HPP
