/*
 * Copyright (c) 2024 EfficiOS, Inc.
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef BABELTRACE_CPP_COMMON_BT2_GRAPH_HPP
#define BABELTRACE_CPP_COMMON_BT2_GRAPH_HPP

#include <cstdint>

#include <babeltrace2/babeltrace.h>

#include "borrowed-object.hpp"
#include "component-class.hpp"
#include "exc.hpp"
#include "shared-object.hpp"

namespace bt2 {
namespace internal {

struct GraphRefFuncs final
{
    static void get(const bt_graph * const libObjPtr) noexcept
    {
        bt_graph_get_ref(libObjPtr);
    }

    static void put(const bt_graph * const libObjPtr) noexcept
    {
        bt_graph_put_ref(libObjPtr);
    }
};

} /* namespace internal */

class Graph final : public BorrowedObject<bt_graph>
{
public:
    using Shared = SharedObject<Graph, bt_graph, internal::GraphRefFuncs>;

    explicit Graph(const LibObjPtr libObjPtr) noexcept : _ThisBorrowedObject {libObjPtr}
    {
    }

    static Shared create(const std::uint64_t mipVersion)
    {
        const auto libObjPtr = bt_graph_create(mipVersion);

        if (!libObjPtr) {
            throw MemoryError {};
        }

        return Shared::createWithoutRef(libObjPtr);
    }

    ConstSourceComponent addComponent(const ConstSourceComponentClass componentClass,
                                      const bt2c::CStringView name,
                                      const OptionalBorrowedObject<ConstMapValue> params = {},
                                      const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstSourceComponent>(
            componentClass, name, params, static_cast<void *>(nullptr), loggingLevel,
            bt_graph_add_source_component_with_initialize_method_data);
    }

    template <typename InitDataT>
    ConstSourceComponent addComponent(const ConstSourceComponentClass componentClass,
                                      const bt2c::CStringView name, InitDataT&& initData,
                                      const OptionalBorrowedObject<ConstMapValue> params = {},
                                      const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstSourceComponent>(
            componentClass, name, params, &initData, loggingLevel,
            bt_graph_add_source_component_with_initialize_method_data);
    }

    ConstFilterComponent addComponent(const ConstFilterComponentClass componentClass,
                                      const bt2c::CStringView name,
                                      const OptionalBorrowedObject<ConstMapValue> params = {},
                                      const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstFilterComponent>(
            componentClass, name, params, static_cast<void *>(nullptr), loggingLevel,
            bt_graph_add_filter_component_with_initialize_method_data);
    }

    template <typename InitDataT>
    ConstFilterComponent addComponent(const ConstFilterComponentClass componentClass,
                                      const bt2c::CStringView name, InitDataT&& initData,
                                      const OptionalBorrowedObject<ConstMapValue> params = {},
                                      const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstFilterComponent>(
            componentClass, name, params, &initData, loggingLevel,
            bt_graph_add_filter_component_with_initialize_method_data);
    }

    ConstSinkComponent addComponent(const ConstSinkComponentClass componentClass,
                                    const bt2c::CStringView name,
                                    const OptionalBorrowedObject<ConstMapValue> params = {},
                                    const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstSinkComponent>(
            componentClass, name, params, static_cast<void *>(nullptr), loggingLevel,
            bt_graph_add_sink_component_with_initialize_method_data);
    }

    template <typename InitDataT>
    ConstSinkComponent addComponent(const ConstSinkComponentClass componentClass,
                                    const bt2c::CStringView name, InitDataT&& initData,
                                    const OptionalBorrowedObject<ConstMapValue> params = {},
                                    const LoggingLevel loggingLevel = LoggingLevel::None) const
    {
        return this->_addComponent<ConstSinkComponent>(
            componentClass, name, params, &initData, loggingLevel,
            bt_graph_add_sink_component_with_initialize_method_data);
    }

    Graph connectPorts(const ConstOutputPort outputPort, const ConstInputPort inputPort) const
    {
        const auto status = bt_graph_connect_ports(this->libObjPtr(), outputPort.libObjPtr(),
                                                   inputPort.libObjPtr(), nullptr);

        if (status == BT_GRAPH_CONNECT_PORTS_STATUS_ERROR) {
            throw Error {};
        } else if (status == BT_GRAPH_CONNECT_PORTS_STATUS_MEMORY_ERROR) {
            throw MemoryError {};
        }

        return *this;
    }

    Graph runOnce() const
    {
        const auto status = bt_graph_run_once(this->libObjPtr());

        if (status == BT_GRAPH_RUN_ONCE_STATUS_ERROR) {
            throw Error {};
        } else if (status == BT_GRAPH_RUN_ONCE_STATUS_MEMORY_ERROR) {
            throw MemoryError {};
        } else if (status == BT_GRAPH_RUN_ONCE_STATUS_AGAIN) {
            throw TryAgain {};
        }

        return *this;
    }

    Graph run() const
    {
        const auto status = bt_graph_run(this->libObjPtr());

        if (status == BT_GRAPH_RUN_STATUS_ERROR) {
            throw Error {};
        } else if (status == BT_GRAPH_RUN_STATUS_MEMORY_ERROR) {
            throw MemoryError {};
        } else if (status == BT_GRAPH_RUN_STATUS_AGAIN) {
            throw TryAgain {};
        }

        return *this;
    }

private:
    template <typename ConstComponentT, typename ConstComponentClassT, typename InitDataT,
              typename AddFuncT>
    ConstComponentT
    _addComponent(const ConstComponentClassT componentClass, const bt2c::CStringView name,
                  const OptionalBorrowedObject<ConstMapValue> params, InitDataT * const initData,
                  const LoggingLevel loggingLevel, AddFuncT&& addFunc) const
    {
        typename ConstComponentT::LibObjPtr libObjPtr = nullptr;

        const auto status = addFunc(this->libObjPtr(), componentClass.libObjPtr(), name,
                                    params ? params->libObjPtr() : nullptr,
                                    const_cast<void *>(static_cast<const void *>(initData)),
                                    static_cast<bt_logging_level>(loggingLevel), &libObjPtr);

        if (status == BT_GRAPH_ADD_COMPONENT_STATUS_ERROR) {
            throw Error {};
        } else if (status == BT_GRAPH_ADD_COMPONENT_STATUS_MEMORY_ERROR) {
            throw MemoryError {};
        }

        return ConstComponentT {libObjPtr};
    }
};

} /* namespace bt2 */

#endif /* BABELTRACE_CPP_COMMON_BT2_GRAPH_HPP */
