// 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_RPM_PACKAGE_SACK_IMPL_HPP
#define LIBDNF5_RPM_PACKAGE_SACK_IMPL_HPP

#include "solv/id_queue.hpp"
#include "solv/pool.hpp"
#include "solv/solv_map.hpp"

#include "libdnf5/base/base.hpp"
#include "libdnf5/common/sack/exclude_flags.hpp"
#include "libdnf5/rpm/package.hpp"

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

#include <optional>
#include <vector>


static inline bool nevra_solvable_cmp_key(const Solvable * first, const Solvable * second) {
    if (first->name != second->name) {
        return first->name < second->name;
    }
    if (first->arch != second->arch) {
        return first->arch < second->arch;
    }
    return first->evr < second->evr;
}

static inline bool nevra_solvable_cmp_icase_key(
    const std::pair<Id, Solvable *> & first, const std::pair<Id, Solvable *> & second) {
    if (first.first != second.first) {
        return first.first < second.first;
    }

    if (first.second->arch != second.second->arch) {
        return first.second->arch < second.second->arch;
    }
    return first.second->evr < second.second->evr;
}

namespace libdnf5::rpm {

class PackageSack::Impl {
public:
    explicit Impl(const BaseWeakPtr & base) : base(base) {}

    /// Return number of solvables in pool
    int get_nsolvables() const noexcept { return get_rpm_pool(base)->nsolvables; };

    /// Return SolvMap with all package solvables
    libdnf5::solv::SolvMap & get_solvables();

    /// Return sorted list of all package solvables
    std::vector<Solvable *> & get_sorted_solvables();

    /// Return sorted list of all package solvables in format pair<id_of_lowercase_name, Solvable *>
    std::vector<std::pair<Id, Solvable *>> & get_sorted_icase_solvables();

    void make_provides_ready();

    void invalidate_provides() { provides_ready = false; }

    PackageId get_running_kernel_id();

    /// Sets excluded and included packages according to the configuration.
    ///
    /// Uses the `disable_excludes`, `excludepkgs`, and `includepkgs` configuration options to calculate the `config_includes` and `config_excludes` sets.
    ///
    /// Invalidates the Pool's considered map, sets `considered_uptodate` to `false` to mark it needs to be recomputed.
    /// @param only_main If `true`, only `excludepkgs` and `includepkgs` from the main config are recomputed.
    // TODO(jrohel): Is param `only_main` needed? Used in DNF4 with commandline repo.
    void load_config_excludes_includes(bool only_main = false);

    /// Load versionlock excludes from the config file.
    void load_versionlock_excludes();

    const PackageSet get_user_excludes();
    void add_user_excludes(const PackageSet & excludes);
    void remove_user_excludes(const PackageSet & excludes);
    void set_user_excludes(const PackageSet & excludes);
    void clear_user_excludes();

    const PackageSet get_user_includes();
    void add_user_includes(const PackageSet & includes);
    void remove_user_includes(const PackageSet & includes);
    void set_user_includes(const PackageSet & includes);
    void clear_user_includes();

    const PackageSet get_module_excludes();
    void add_module_excludes(const PackageSet & excludes);
    void remove_module_excludes(const PackageSet & excludes);
    void set_module_excludes(const PackageSet & excludes);
    void clear_module_excludes();

    VersionlockConfig get_versionlock_config() const;
    const PackageSet get_versionlock_excludes();
    void add_versionlock_excludes(const PackageSet & excludes);
    void remove_versionlock_excludes(const PackageSet & excludes);
    void set_versionlock_excludes(const PackageSet & excludes);
    void clear_versionlock_excludes();

    /// Computes considered map.
    /// If there are no excluded packages, the considered map may not be present in the return value.
    std::optional<libdnf5::solv::SolvMap> compute_considered_map(libdnf5::sack::ExcludeFlags flags) const;

    /// If the considered map in the pool is out of date - `considered_uptodate == false` - it will recompute it.
    /// And sets `considered_uptodate` to` true`.
    void recompute_considered_in_pool();

private:
    bool provides_ready{false};

    BaseWeakPtr base;

    WeakPtrGuard<PackageSack, false> sack_guard;

    std::unique_ptr<libdnf5::solv::SolvMap> config_excludes;  // packages explicitly excluded by configuration
    std::unique_ptr<libdnf5::solv::SolvMap> config_includes;  // packages explicitly included by configuration

    std::unique_ptr<libdnf5::solv::SolvMap> user_excludes;  // packages explicitly excluded by API user
    std::unique_ptr<libdnf5::solv::SolvMap> user_includes;  // packages explicitly included by API user

    // packages excluded by disabling repositories
    // Optimization, the PackageQuery does not have to test whether the package comes from an allowed repository.
    // TODO(jrohel): Recompute when state of repository is changed enabled/disabled or added solvable to disabled repo
    std::unique_ptr<libdnf5::solv::SolvMap> repo_excludes;

    std::unique_ptr<libdnf5::solv::SolvMap> module_excludes;  // packages excluded by modularity

    // packages excluded by versionlock feature
    std::unique_ptr<libdnf5::solv::SolvMap> versionlock_excludes;  // packages excluded by versionlock

    bool considered_uptodate = true;

    std::vector<Solvable *> cached_sorted_solvables;
    int cached_sorted_solvables_size{0};
    /// pair<id_of_lowercase_name, Solvable *>
    std::vector<std::pair<Id, Solvable *>> cached_sorted_icase_solvables;
    int cached_sorted_icase_solvables_size{0};
    libdnf5::solv::SolvMap cached_solvables{0};
    int cached_solvables_size{0};
    PackageId running_kernel;

    friend PackageSack;
    friend Package;
    friend PackageSet;
    friend Reldep;
    friend ReldepList;

    friend class Transaction;
};

inline std::vector<Solvable *> & PackageSack::Impl::get_sorted_solvables() {
    auto nsolvables = get_nsolvables();
    if (nsolvables == cached_sorted_solvables_size) {
        return cached_sorted_solvables;
    }
    auto & solvables_map = get_solvables();
    cached_sorted_solvables.clear();
    cached_sorted_solvables.reserve(static_cast<size_t>(nsolvables));
    auto & pool = get_rpm_pool(base);
    for (Id id : solvables_map) {
        cached_sorted_solvables.push_back(pool.id2solvable(id));
    }
    std::sort(cached_sorted_solvables.begin(), cached_sorted_solvables.end(), nevra_solvable_cmp_key);
    cached_sorted_solvables_size = nsolvables;
    return cached_sorted_solvables;
}

inline std::vector<std::pair<Id, Solvable *>> & PackageSack::Impl::get_sorted_icase_solvables() {
    auto & pool = get_rpm_pool(base);
    auto nsolvables = get_nsolvables();
    if (nsolvables == cached_sorted_icase_solvables_size) {
        return cached_sorted_icase_solvables;
    }
    Id name = 0;
    Id icase_name = 0;
    for (auto * solvable : get_sorted_solvables()) {
        if (solvable->name != name) {
            icase_name = pool.id_to_lowercase_id(solvable->name, 1);
        }
        cached_sorted_icase_solvables.emplace_back(std::make_pair(icase_name, solvable));
    }
    std::sort(cached_sorted_icase_solvables.begin(), cached_sorted_icase_solvables.end(), nevra_solvable_cmp_icase_key);
    cached_sorted_icase_solvables_size = nsolvables;
    return cached_sorted_icase_solvables;
}

inline libdnf5::solv::SolvMap & PackageSack::Impl::get_solvables() {
    auto & spool = get_rpm_pool(base);
    ::Pool * pool = *spool;

    auto nsolvables = get_nsolvables();
    if (nsolvables == cached_solvables_size) {
        return cached_solvables;
    }
    if (nsolvables > cached_solvables.allocated_size()) {
        cached_solvables = libdnf5::solv::SolvMap(nsolvables);
    } else {
        cached_solvables.clear();
    }
    Id solvable_id;

    // loop over all package solvables
    FOR_POOL_SOLVABLES(solvable_id) {
        if (spool.is_package(solvable_id)) {
            cached_solvables.add_unsafe(solvable_id);
        }
    }
    cached_solvables_size = nsolvables;
    return cached_solvables;
}

}  // namespace libdnf5::rpm


#endif  // LIBDNF5_RPM_PACKAGE_SACK_IMPL_HPP
