// 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_CONF_VARS_HPP
#define LIBDNF5_CONF_VARS_HPP

#include "vars_errors.hpp"

#include "libdnf5/base/base_weak.hpp"
#include "libdnf5/defs.h"

#include <map>
#include <string>
#include <vector>


namespace libdnf5 {

/// @class Vars
///
/// @brief Class for reading and substituting DNF vars (arch, releasever, etc.).
///
/// The class loads the variables from the environment as well as from a list
/// of directories.
struct LIBDNF_API Vars {
public:
    enum class Priority {
        DEFAULT = 10,
        AUTO = 20,
        VARSDIR = 30,
        PLUGIN = 40,
        ENVIRONMENT = 50,
        COMMANDLINE = 60,
        RUNTIME = 70
    };

    struct Variable {
        std::string value;
        Priority priority;
    };

    Vars(const libdnf5::BaseWeakPtr & base);
    Vars(libdnf5::Base & base);

    ~Vars();

    /// @brief Substitute DNF vars in the input text.
    ///
    /// @param text The text for substitution
    /// @return The substituted text
    std::string substitute(const std::string & text) const;

    const std::map<std::string, Variable> & get_variables() const;

    /// @brief Set particular variable to a value
    ///
    /// @param name Name of the variable
    /// @param value Value to be stored in variable
    /// @param prio Source/Priority of the value
    /// @throw ReadOnlyVariableError if the variable is read-only
    void set(const std::string & name, const std::string & value, Priority prio = Priority::RUNTIME);

    /// @brief Unset particular variable
    ///
    /// @param name Name of the variable
    /// @param prio Source/Priority of the request
    /// @throw ReadOnlyVariableError if the variable is read-only
    /// @return false if the variable exists after the function returns (insufficient request priority)
    bool unset(const std::string & name, Priority prio = Priority::RUNTIME);

    /// @brief Checks whether a variable is read-only
    ///
    /// @param name Name of the variable
    /// @return true if the variable is read-only, false if it is writable
    bool is_read_only(const std::string & name) const;

    /// @brief Checks if there is an variable with name equivalent to name in the container.
    ///
    /// @param name Name of the variable
    /// @return true if there is such an element, otherwise false
    bool contains(const std::string & name) const;

    /// @brief Get value of particular variable.
    ///
    /// @param name Name of the variable
    const std::string & get_value(const std::string & name) const;

    /// @brief Get particular variable.
    ///
    /// @param name Name of the variable
    const Variable & get(const std::string & name) const;

    /// @brief Detects the releasever of the system
    ///
    /// Returns the release name of the distribution of the tree rooted at `installroot`.
    /// This function uses information from RPMDB found under the tree.
    /// It's preferred to use ``detect_releasevers`` over ``detect_release``; if you use the latter, you will not be aware of distribution overrides for the major and minor release versions.
    ///
    /// @return the detected releasever, or nullptr if the releasever could not be determined (perhaps because the tree has no RPMDB).
    static std::unique_ptr<std::string> detect_release(const BaseWeakPtr & base, const std::string & install_root_path);

    /// @brief Detect the releasever, overridden major release, and overridden minor release for the system
    ///
    /// Returns a tuple of the release name, overridden major release, and overridden minor release of the distribution of the tree rooted at `installroot`.
    /// This function uses information from RPMDB found under the tree.
    /// The major and minor release versions are usually derived from the release version by splitting it on the first ``.``, but distributions can override the derived major and minor versions.
    ///
    /// @return a tuple of the releasever, overridden releasever_major, and overridden releasever_minor. Each member of the tuple can be nullptr if not provided by the system.
    static std::tuple<std::unique_ptr<std::string>, std::unique_ptr<std::string>, std::unique_ptr<std::string>>
    detect_releasevers(const BaseWeakPtr & base, const std::string & install_root_path);

private:
    friend class Base;

    /// @brief Loads DNF vars from the environment and the passed directories.
    ///
    /// Environment variables are loaded first, then the directories are loaded
    /// in the order they are stored in the vector. Variables will overwrite
    /// any previous occurrence of a variable with the same name.
    ///
    /// @param installroot The path to the installroot
    /// @param directories The directories to load vars from
    LIBDNF_LOCAL void load(const std::string & installroot, const std::vector<std::string> & directories);

    /// @brief Detects the system's arch, basearch and relesever.
    ///
    /// @param installroot The installroot directory
    LIBDNF_LOCAL void detect_vars(const std::string & installroot);

    /// @brief Loads DNF vars from a directory.
    ///
    /// Each file in the directory represents one variable. The filename stands
    /// for the name of the variable and the value is the first line of the
    /// file's contents.
    ///
    /// @param directory Path to a directory with DNF vars
    LIBDNF_LOCAL void load_from_dir(const std::string & directory);

    /// @brief Loads DNF vars from the environment.
    ///
    /// Reads environment variables that match "DNF[0-9]" and
    /// "DNF_VAR_[A-Za-z0-9_]+" patterns. The "DNF_VAR_" prefix is cut off.
    LIBDNF_LOCAL void load_from_env();

    /// @brief Set a variable to a value, only obtaining the value if needed using `get_value`
    ///
    /// @param name Name of the variable
    /// @param get_value Function that returns the (optional) value for the variable
    /// @param prio Source/Priority of the value
    /// @throw ReadOnlyVariableError if the variable is read-only
    LIBDNF_LOCAL void set_lazy(
        const std::string & name,
        const std::function<const std::unique_ptr<const std::string>()> & get_value,
        Priority prio);

    /// @brief Expand variables in a subexpression
    ///
    /// @param text String with variable expressions
    /// @param depth The recursive depth
    /// @return Pair of the resulting string and the number of characters scanned in `text`
    LIBDNF_LOCAL std::pair<std::string, size_t> substitute_expression(std::string_view text, unsigned int depth) const;

    /// @brief Split releasever on the first "." into its "major" and "minor" components
    ///
    /// @param releasever A releasever string, possibly containing a "."
    /// @return releasever_major, releasever_minor
    LIBDNF_LOCAL static std::tuple<std::string, std::string> split_releasever(const std::string & releasever);

    class LIBDNF_LOCAL Impl;
    std::unique_ptr<Impl> p_impl;
};

}  // namespace libdnf5

#endif
