// 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/>.


#include "libdnf5/rpm/nevra.hpp"

#include "libdnf5/utils/bgettext/bgettext-mark-domain.h"

#include <rpm/rpmver.h>


namespace libdnf5::rpm {

class Nevra::Impl {
public:
    explicit Impl() = default;

private:
    friend Nevra;

    std::string name;
    std::string epoch;
    std::string version;
    std::string release;
    std::string arch;
};

static const std::vector<Nevra::Form> PKG_SPEC_FORMS{
    Nevra::Form::NEVRA, Nevra::Form::NA, Nevra::Form::NAME, Nevra::Form::NEVR, Nevra::Form::NEV};

const std::vector<Nevra::Form> & Nevra::get_default_pkg_spec_forms() {
    return PKG_SPEC_FORMS;
}

std::vector<Nevra> Nevra::parse(const std::string & nevra_str, const std::vector<Form> & forms) {
    std::vector<Nevra> result;
    const char * before_last_delim = nullptr;
    const char * epoch_delim = nullptr;
    const char * last_delim = nullptr;
    const char * arch_delim = nullptr;
    const char * end;

    // parse nevra
    const char * nevra_pattern = nevra_str.c_str();
    // detect whether string contains a glob range [a-z]
    bool start_range = false;
    for (end = nevra_pattern; *end != '\0'; ++end) {
        // skip all characters before glob range is closed
        if (start_range) {
            if (*end == ']') {
                start_range = false;
            } else {
                continue;
            }
        }
        if (*end == '[') {
            start_range = true;
        } else if (*end == '-') {
            before_last_delim = last_delim;
            last_delim = end;
        } else if (*end == '.') {
            arch_delim = end;
        } else if (*end == ':') {
            // ':' can be only once in nevra
            if (epoch_delim != nullptr) {
                throw NevraIncorrectInputError(M_("NEVRA string \"{}\" contains ':' multiple times"), nevra_str);
            }
            epoch_delim = end;
        } else if (*end == '(' || *end == '/' || *end == '=' || *end == '<' || *end == '>' || *end == ' ') {
            throw NevraIncorrectInputError(M_("Invalid character '{}' in NEVRA string \"{}\""), *end, nevra_str);
        }
    }
    for (auto form : forms) {
        Nevra nevra;
        switch (form) {
            case Form::NEVRA: {
                if (before_last_delim == nullptr || last_delim == nullptr || arch_delim == nullptr) {
                    continue;
                }

                // test name presence
                if (before_last_delim == nevra_pattern) {
                    continue;
                }

                const char * evr_delim_for_nevra = before_last_delim;
                const char * release_delim_for_nevra = last_delim;
                const char * arch_delim_for_nevra = arch_delim;

                nevra.p_impl->name.assign(nevra_pattern, evr_delim_for_nevra);
                ++evr_delim_for_nevra;

                // test presence of epoch (optional)
                if (epoch_delim != nullptr) {
                    // test that ':' was in range of evr. ':' sign is only allowed in evr as an epoch deliminator
                    if (epoch_delim - evr_delim_for_nevra < 1 || release_delim_for_nevra - epoch_delim < 2) {
                        continue;
                    }
                    nevra.p_impl->epoch.assign(evr_delim_for_nevra, epoch_delim);
                    evr_delim_for_nevra = epoch_delim + 1;
                }

                // test presence of version
                if (release_delim_for_nevra - evr_delim_for_nevra < 1) {
                    continue;
                }
                nevra.p_impl->version.assign(evr_delim_for_nevra, release_delim_for_nevra);
                ++release_delim_for_nevra;

                // test presence of release
                if (arch_delim_for_nevra - release_delim_for_nevra < 1) {
                    continue;
                }
                nevra.p_impl->release.assign(release_delim_for_nevra, arch_delim_for_nevra);
                ++arch_delim_for_nevra;

                // test presence of arch
                if (end - arch_delim_for_nevra < 1) {
                    continue;
                }
                nevra.p_impl->arch.assign(arch_delim_for_nevra);

                result.push_back(std::move(nevra));
            } break;
            case Form::NEVR: {
                if (before_last_delim == nullptr || last_delim == nullptr) {
                    continue;
                }

                // test name presence
                if (before_last_delim == nevra_pattern) {
                    continue;
                }

                const char * evr_delim_for_nevr = before_last_delim;
                const char * release_delim_for_nevr = last_delim;

                nevra.p_impl->name.assign(nevra_pattern, evr_delim_for_nevr);
                ++evr_delim_for_nevr;

                // test presence of epoch (optional)
                if (epoch_delim != nullptr) {
                    // test that ':' was in range of evr. ':' sign is only allowed in evr as an epoch deliminator
                    if (epoch_delim - evr_delim_for_nevr < 1 || release_delim_for_nevr - epoch_delim < 2) {
                        continue;
                    }
                    nevra.p_impl->epoch.assign(evr_delim_for_nevr, epoch_delim);
                    evr_delim_for_nevr = epoch_delim + 1;
                }

                // test presence of version
                if (release_delim_for_nevr - evr_delim_for_nevr < 1) {
                    continue;
                }
                nevra.p_impl->version.assign(evr_delim_for_nevr, release_delim_for_nevr);
                ++release_delim_for_nevr;

                // test presence of release
                if (end - release_delim_for_nevr < 1) {
                    continue;
                }
                nevra.p_impl->release.assign(release_delim_for_nevr);

                result.push_back(std::move(nevra));
            } break;
            case Form::NEV: {
                if (last_delim == nullptr) {
                    continue;
                }

                // test name presence
                if (before_last_delim == nevra_pattern) {
                    continue;
                }

                const char * evr_delim_for_nev = last_delim;

                nevra.p_impl->name.assign(nevra_pattern, evr_delim_for_nev);
                ++evr_delim_for_nev;

                // test presence of epoch (optional)
                if (epoch_delim != nullptr) {
                    // test that ':' was in range of evr. ':' sign is only allowed in evr as an epoch deliminator
                    if (epoch_delim - evr_delim_for_nev < 1 || end - epoch_delim < 2) {
                        continue;
                    }
                    nevra.p_impl->epoch.assign(evr_delim_for_nev, epoch_delim);
                    evr_delim_for_nev = epoch_delim + 1;
                }

                // test presence of version
                if (end - evr_delim_for_nev < 1) {
                    continue;
                }
                nevra.p_impl->version.assign(evr_delim_for_nev);

                result.push_back(std::move(nevra));
            } break;
            case Form::NA: {
                if (arch_delim == nullptr) {
                    continue;
                }
                // test: NA cannot contain ':'
                if (epoch_delim != nullptr) {
                    continue;
                }

                // test name presence
                if (arch_delim == nevra_pattern) {
                    continue;
                }

                // test arch for absence of '-'
                if (last_delim != nullptr && last_delim > arch_delim) {
                    continue;
                }

                // test arch presence
                if (end - arch_delim == 1) {
                    continue;
                }

                nevra.p_impl->name.assign(nevra_pattern, arch_delim);
                nevra.p_impl->arch.assign(arch_delim + 1);

                result.push_back(std::move(nevra));
            } break;
            case Form::NAME:
                // test: Name cannot contain ':'
                if (epoch_delim != nullptr) {
                    continue;
                }

                // test name presence
                if (end == nevra_pattern) {
                    continue;
                }

                nevra.p_impl->name.assign(nevra_pattern);

                result.push_back(std::move(nevra));
                break;
        }
    }
    return result;
}


void Nevra::clear() noexcept {
    p_impl->name.clear();
    p_impl->epoch.clear();
    p_impl->version.clear();
    p_impl->release.clear();
    p_impl->arch.clear();
}

bool Nevra::has_just_name() const {
    return !p_impl->name.empty() && p_impl->epoch.empty() && p_impl->version.empty() && p_impl->release.empty() &&
           p_impl->arch.empty();
}


bool Nevra::operator==(const Nevra & other) const {
    return p_impl->name == other.p_impl->name && p_impl->epoch == other.p_impl->epoch &&
           p_impl->version == other.p_impl->version && p_impl->release == other.p_impl->release &&
           p_impl->arch == other.p_impl->arch;
}


std::ostringstream & operator<<(std::ostringstream & out, const Nevra & nevra) {
    out << to_full_nevra_string(nevra);
    return out;
}


int rpmvercmp(const char * lhs, const char * rhs) {
    return ::rpmvercmp(lhs, rhs);
}

Nevra::Nevra() : p_impl(new Impl()) {};
Nevra::~Nevra() = default;

Nevra::Nevra(const Nevra & src) = default;
Nevra::Nevra(Nevra && src) noexcept = default;

Nevra & Nevra::operator=(const Nevra & src) = default;
Nevra & Nevra::operator=(Nevra && src) noexcept = default;

const std::string & Nevra::get_name() const noexcept {
    return p_impl->name;
}

// @replaces hawkey:hawkey/__init__.py:attribute:Nevra.epoch
const std::string & Nevra::get_epoch() const noexcept {
    return p_impl->epoch;
}

// @replaces hawkey:hawkey/__init__.py:attribute:Nevra.version
const std::string & Nevra::get_version() const noexcept {
    return p_impl->version;
}

// @replaces hawkey:hawkey/__init__.py:attribute:Nevra.release
const std::string & Nevra::get_release() const noexcept {
    return p_impl->release;
}

// @replaces hawkey:hawkey/__init__.py:attribute:Nevra.arch
const std::string & Nevra::get_arch() const noexcept {
    return p_impl->arch;
}

void Nevra::set_name(const std::string & value) {
    p_impl->name = value;
}
void Nevra::set_epoch(const std::string & value) {
    p_impl->epoch = value;
}
void Nevra::set_version(const std::string & value) {
    p_impl->version = value;
}
void Nevra::set_release(const std::string & value) {
    p_impl->release = value;
}
void Nevra::set_arch(const std::string & value) {
    p_impl->arch = value;
}

void Nevra::set_name(std::string && value) {
    p_impl->name = std::move(value);
}
void Nevra::set_epoch(std::string && value) {
    p_impl->epoch = std::move(value);
}
void Nevra::set_version(std::string && value) {
    p_impl->version = std::move(value);
}
void Nevra::set_release(std::string && value) {
    p_impl->release = std::move(value);
}
void Nevra::set_arch(std::string && value) {
    p_impl->arch = std::move(value);
}

}  // namespace libdnf5::rpm
