// Copyright Contributors to the DNF5 project.
// Copyright Contributors to the libdnf project.
// SPDX-License-Identifier: LGPL-2.1-or-later
//
// This file is part of libdnf: https://github.com/rpm-software-management/libdnf/
//
// Libdnf is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Libdnf is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with libdnf.  If not, see <https://www.gnu.org/licenses/>.

#ifndef LIBDNF5_PLUGIN_IPLUGIN_HPP
#define LIBDNF5_PLUGIN_IPLUGIN_HPP

#include "plugin_version.hpp"

#include "libdnf5/common/impl_ptr.hpp"
#include "libdnf5/defs.h"
#include "libdnf5/version.hpp"

#include <exception>
#include <string>
#include <vector>

namespace libdnf5 {

class Base;
struct ConfigParser;

namespace base {
class Transaction;
}

}  // namespace libdnf5

namespace libdnf5::plugin {

class IPluginData;


/// The `StopRequest` class is used to mark a plugin-generated exception as a stop request.
/// The stop request is not caused by an error in the plugin runtime, but is a plugin request.
/// The exception is marked by inheriting from the `StopRequest` class (using multiple inheritance).
class LIBDNF_PLUGIN_API StopRequest {
public:
    StopRequest() noexcept;
    StopRequest(const StopRequest & other) noexcept;
    StopRequest(StopRequest && other) noexcept;
    ~StopRequest();

    StopRequest & operator=(const StopRequest & other) noexcept;
    StopRequest & operator=(StopRequest && other) noexcept;
};


/// @brief A base class for implementing LIBDNF5 plugins that introduce additional logic into the library using hooks.
class LIBDNF_PLUGIN_API IPlugin {
public:
    explicit IPlugin(IPluginData & data);
    virtual ~IPlugin();

    IPlugin() = delete;
    IPlugin(const IPlugin &) = delete;
    IPlugin(IPlugin &&) = delete;
    IPlugin & operator=(const IPlugin &) = delete;
    IPlugin & operator=(IPlugin &&) = delete;

    /// Returns the version of the API supported by the plugin. It can be called at any time.
    virtual PluginAPIVersion get_api_version() const noexcept = 0;

    /// Returns the name of the plugin. It can be called at any time.
    virtual const char * get_name() const noexcept = 0;

    /// Gets the plugin version. It can be called at any time.
    virtual Version get_version() const noexcept = 0;

    /// @return A nullptr terminated array of attributes supported by the plugin.
    virtual const char * const * get_attributes() const noexcept = 0;

    /// Gets the value of the attribute from the plugin. Returns nullptr if the attribute does not exist.
    /// It can be called at any time.
    virtual const char * get_attribute(const char * name) const noexcept = 0;

    /// The plugin can load additional plugins. E.g. C++ plugin for loading Python plugins.
    /// Called before init.
    virtual void load_plugins();

    /// Plugin initialization.
    /// Called before hooks.
    virtual void init();

    /// The pre_base_setup hook.
    /// It is called at the beginning of the `Base::setup` method (after the `init` hook).
    virtual void pre_base_setup();

    /// The post_base_setup hook.
    /// It is called at the end of the `Base::setup` method.
    virtual void post_base_setup();

    /// The repos_configured hook.
    /// It is called in `Base::notify_repos_configured` method.
    virtual void repos_configured();

    /// The repos_loaded hook.
    /// It is called at the end of the `RepoSack::load_repos` method (in Impl).
    virtual void repos_loaded();

    /// The pre_add_cmdline_packages hook.
    /// It is called at the beginning of the `RepoSack::add_cmdline_packages` method.
    ///
    /// @param paths Vector of paths (local files or URLs) to package files to be inserted into cmdline repo.
    virtual void pre_add_cmdline_packages(const std::vector<std::string> & paths);

    /// The post_add_cmdline_packages hook.
    /// It is called at the end of the `RepoSack::add_cmdline_packages` method.
    virtual void post_add_cmdline_packages();

    /// The pre_transaction hook.
    /// It is called just before the actual transaction starts.
    ///
    /// @param transaction Contains the transaction that will be started.
    virtual void pre_transaction(const libdnf5::base::Transaction & transaction);

    /// The post_transaction hook.
    /// It is called after transactions.
    ///
    /// @param transaction Contains the completed transaction.
    virtual void post_transaction(const libdnf5::base::Transaction & transaction);

    /// Finish the plugin and release all resources obtained by the init method and in hooks.
    virtual void finish() noexcept;

    Base & get_base() const noexcept;

private:
    class LIBDNF_LOCAL Impl;
    ImplPtr<Impl> p_impl;
};

/// @brief Extended plugin interface with additional hooks introduced in version 2.1 of the plugin API.
class LIBDNF_PLUGIN_API IPlugin2_1 : public IPlugin {
public:
    explicit IPlugin2_1(IPluginData & data);
    ~IPlugin2_1();

    /// The goal resolved hook.
    /// It is called right after the goal is resolved.
    ///
    /// @param transaction Contains the transaction that was resolved.
    virtual void goal_resolved(const libdnf5::base::Transaction & transaction);
};

/// @brief Extended plugin interface with cleanup hooks introduced in version 2.2 of the plugin API.
///
/// Cleanup hooks are guaranteed to run regardless of whether the operation
/// succeeded or failed.
/// They are intended for releasing resources acquired in the pre hook.
/// Each cleanup hook must only touch the plugin's own local state.
class LIBDNF_PLUGIN_API IPlugin2_2 : public IPlugin2_1 {
public:
    explicit IPlugin2_2(IPluginData & data);
    ~IPlugin2_2();

    /// Cleanup hook guaranteed to run after Base::setup() completes (successfully or not).
    virtual void post_base_setup_cleanup() noexcept;

    /// Cleanup hook guaranteed to run after RepoSack::add_cmdline_packages() completes
    /// (successfully or not).
    virtual void post_add_cmdline_packages_cleanup() noexcept;

    /// Cleanup hook guaranteed to run after the transaction run finishes (successfully or not).
    ///
    /// @param transaction The same transaction object passed to pre_transaction()/post_transaction().
    virtual void post_transaction_cleanup(const libdnf5::base::Transaction & transaction) noexcept;
};

}  // namespace libdnf5::plugin


extern "C" {

/// Returns the version of the API implemented by the plugin.
/// Same result as IPlugin::get_api_version(), but can be called without creating an IPlugin instance.
///
/// @return API version implemented by the plugin.
LIBDNF_PLUGIN_API libdnf5::PluginAPIVersion libdnf_plugin_get_api_version(void);

/// Returns the name of the plugin.
/// Same result as IPlugin::get_name(), but can be called without creating an IPlugin instance.
///
/// @return Plugin name
LIBDNF_PLUGIN_API const char * libdnf_plugin_get_name(void);

/// Returns the version of the plugin.
/// Same result as IPlugin::get_version(), but can be called without creating an IPlugin instance.
///
/// @return Plugin version
LIBDNF_PLUGIN_API libdnf5::plugin::Version libdnf_plugin_get_version(void);

/// Creates a new plugin instance.
/// On failure, returns `nullptr` and saves the exception.
///
/// @param library_version Version of libdnf library.
/// @param data            Private libdnf data passed to the plugin.
/// @param parser          Parser with loaded plugin configuration file.
/// @return Pointer to the new plugin instance or `nullptr`.
LIBDNF_PLUGIN_API libdnf5::plugin::IPlugin * libdnf_plugin_new_instance(
    libdnf5::LibraryVersion library_version, libdnf5::plugin::IPluginData & data, libdnf5::ConfigParser & parser);

/// Deletes plugin instance.
///
/// @param plugin_instance Plugin instance to delete.
LIBDNF_PLUGIN_API void libdnf_plugin_delete_instance(libdnf5::plugin::IPlugin * plugin_instance);

/// Returns a pointer to `std::exception_ptr` containing the last caught exception.
/// If no exception has occurred yet, returns a pointer to an empty `std::exception_ptr`.
///
/// @return Pointer to the `std::exception_ptr` containing the last caught exception.
LIBDNF_PLUGIN_API std::exception_ptr * libdnf_plugin_get_last_exception(void);
}

#endif
