// 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_MODULE_MODULE_SACK_IMPL_HPP
#define LIBDNF5_MODULE_MODULE_SACK_IMPL_HPP

#include "base/base_impl.hpp"
#include "module/module_db.hpp"
#include "module/module_metadata.hpp"
#include "solv/id_queue.hpp"
#include "solv/solv_map.hpp"

#include "libdnf5/base/base.hpp"
#include "libdnf5/module/module_sack.hpp"
#include "libdnf5/rpm/reldep_list.hpp"

extern "C" {
#include <solv/pool.h>
#include <solv/poolarch.h>
}

#include <optional>


namespace libdnf5::base {

class Transaction;

}

namespace libdnf5::module {


class ModuleGoalPrivate;


class ModuleSack::Impl {
public:
    /// Create libsolv pool and set the appropriate pool flags
    Impl(ModuleSack & module_sack, const BaseWeakPtr & base)
        : module_sack(&module_sack),
          base(base),
          module_metadata(base),
          pool(pool_create()),
          module_db(new ModuleDB(base)) {
        // Ensure excluded packages are not taken as candidates for solver
        pool_set_flag(pool, POOL_FLAG_WHATPROVIDESWITHDISABLED, 1);
        // Allow packages of the same name with different architectures to be installed in parallel
        pool_set_flag(pool, POOL_FLAG_IMPLICITOBSOLETEUSESCOLORS, 1);
    }
    ~Impl() {
        //TODO(amatej): Perhaps it would be good to create and use ModulePool which would take care of considered.
        //              It would also unify the approach with CompsPool and RpmPool.
        if (pool->considered) {
            map_free(pool->considered);
            g_free(pool->considered);
        }
        pool_free(pool);
    }

    const std::vector<std::unique_ptr<ModuleItem>> & get_modules();

    /// Apply modular filtering to package set. For proper functionality, all repositories must be loaded and active
    /// modules must be resolved (modular solver).
    void module_filtering();

    /// Supporting method that iterates over all modules and creates filtering sets for modular filtering
    /// include_NEVRAs contains artifacts from active modules
    /// exclude_NEVRAs contains artifacts from not active modules
    /// names and reldep_name_list contain names from include_NEVRAs that are not source
    /// src_names contains names from include_NEVRAs that are source
    ///
    /// @return <include_NEVRAs>, <exclude_NEVRAs>, <names>, <src_names>, <reldep_name_list>
    std::tuple<
        std::vector<std::string>,
        std::vector<std::string>,
        std::vector<std::string>,
        std::vector<std::string>,
        rpm::ReldepList>
    collect_data_for_modular_filtering();

    // Compute static context for older modules and move these modules to `ModuleSack.modules`.
    void add_modules_without_static_context();

    void make_provides_ready();
    void recompute_considered_in_pool();

    // Requires resolved goal. Takes list_installs() from goal and adds all modules with the same SOLVABLE_NAME
    // (<name>:<stream>:<context>) into active_modules.
    void set_active_modules(ModuleGoalPrivate & goal);

    // Requires resolved goal. Enable all modules that are required by any newly enabled modules.
    // @replaces libdnf:ModulePackageContainer.hpp:method:ModulePackageContainer.enableDependencyTree()
    void enable_dependent_modules();

    /// Resolve given module items.
    ///
    /// @param module_items Module Items to resolve.
    /// @return `std::pair` of problems in resolving and a GoalProblem.
    /// @since 5.0
    std::pair<std::vector<std::vector<std::tuple<ProblemRules, Id, Id, Id, std::string>>>, GoalProblem> module_solve(
        std::vector<ModuleItem *> & module_items);

    /// Enable module stream.
    /// @param name module name to be enabled.
    /// @param stream module stream to be enabled.
    /// @param count if `true`, count the change towards the limit of module status modifications.
    /// @return `true` if requested change really triggers a change in the ModuleDB, `false` otherwise.
    /// @throw EnableMultipleStreamsError in case of conflicting enable requests.
    /// @throw NoModuleError if the module doesn't exist.
    /// @since 5.0.14
    bool enable(const std::string & name, const std::string & stream, bool count = true);
    /// Enable module stream.
    /// @param module_spec module to be enabled.
    /// @param count if `true`, count the change towards the limit of module status modifications.
    /// @return `true` if requested change really triggers a change in the ModuleDB, `false` otherwise,
    ///         and a set of module:stream strings for modules with multiple streams and no enabled or default one.
    /// @throw NoModuleError if the module doesn't exist.
    /// @since 5.0.14
    std::pair<bool, std::set<std::string>> enable(const std::string & module_spec, bool count = true);
    /// Disable module.
    /// @param module_spec module to be disabled.
    /// @param count if `true`, count the change towards the limit of module status modifications.
    /// @throw NoModuleError if the module doesn't exist.
    /// @since 5.0.14
    bool disable(const std::string & module_spec, bool count = true);
    /// Reset module, so it's no longer enabled nor disabled.
    /// @param module_spec module to be reset.
    /// @param count if `true`, count the change towards the limit of module status modifications.
    /// @throw NoModuleError if the module doesn't exist.
    /// @since 5.0.14
    bool reset(const std::string & module_spec, bool count = true);
    /// Set architecture for module pool
    /// @param arch architecture.
    /// @since 5.1.8
    void set_arch(const char * arch) { pool_setarch(pool, arch); };


private:
    friend class libdnf5::base::Transaction;
    friend class libdnf5::Goal;
    friend ModuleSack;
    friend ModuleItem;
    friend ModuleGoalPrivate;
    friend class ModuleQuery;

    ModuleSack * module_sack;
    BaseWeakPtr base;
    ModuleMetadata module_metadata;
    std::vector<std::unique_ptr<ModuleItem>> modules;
    Pool * pool;
    // Repositories containing any modules. Key is repoid, value is Id of the repo in libsolv.
    // This is needed in `ModuleItem::create_solvable` for creating solvable in the correct repository.
    std::map<std::string, Id> repositories;

    // Older ModuleItems that don't have static context. After all metadata are loaded, static contexts are assigned
    // also to these ModuleItems and they are removed from this vector and added to `ModuleSack.modules`.
    // This is done in `ModuleSack::add_modules_without_static_context`.
    std::vector<std::unique_ptr<ModuleItem>> modules_without_static_context;

    bool provides_ready = false;
    bool considered_uptodate = false;
    bool platform_detected = false;

    std::map<std::string, std::string> module_defaults;
    std::unique_ptr<libdnf5::solv::SolvMap> excludes;
    std::map<Id, ModuleItem *> active_modules;
    std::vector<libdnf5::solv::IdQueue> modules_to_enable;

    std::unique_ptr<ModuleDB> module_db;

    /// @brief Method for autodetection of the platform id.
    /// @return If platform id was detected, it returns a pair where the first item is the platform
    ///         module name and second is the platform stream. Otherwise std::nullopt is returned.
    std::optional<std::pair<std::string, std::string>> detect_platform_name_and_stream() const;

    /// @brief Keep only one stream for each module. If more than one stream is originally there, keep only
    // the enabled or default one if possible.
    /// @return Set of module:stream strings for modules with multiple streams and no enabled or default one.
    std::set<std::string> prune_module_dict(
        std::unordered_map<std::string, std::unordered_map<std::string, libdnf5::solv::IdQueue>> & module_dict);
};

inline const std::vector<std::unique_ptr<ModuleItem>> & ModuleSack::Impl::get_modules() {
    add_modules_without_static_context();
    return modules;
}

}  // namespace libdnf5::module

#endif  // LIBDNF5_MODULE_MODULE_SACK_IMPL_HPP
