#include <QDebug>
#include <QLocale>

#include "aptpackagebuilder.h"
#include "aptcacheparser.h"
#include "dpkgparser.h"
#include "helpers.h"
#include "runcommandforparsing.h"
#include "packagename.h"
#include "packagenotfoundexception.h"



namespace NApt {

AptCacheParser::AptCacheParser() {}

// splits into section and content
pair<string, string> splitSectionLine(const string& line) {
    auto pos = line.find(": ");
    return make_pair(line.substr(0, pos), line.substr(pos + 2));
}

void assignValueToSection(AptPackageBuilder& builder, const string& section, const string& content) {
    static auto languageCode = QLocale::system().name().first(2);
    static string localDescriptionSection = ("Description-" + languageCode).toStdString();

    if (section == "Package") {
        builder.setName(toQString(content));
    } else if (section == "Name") {
        builder.setName(toQString(content));
    } else if (section == "Architecture") {
        builder.setArchitecture(toQString(content));
    } else if (
        section == "Description" ||
        section == "Description-en" ||
        section == localDescriptionSection
    ) {
        builder.setDescription(toQString(content));
    } else if (section == "Version") {
        builder.setVersion(toQString(content));
    } else if (section == "Status") {
        if (content.find("installed") != std::string::npos)
            builder.setInstalledState(Package::INSTALLED);
        else
            builder.setInstalledState(Package::NOT_INSTALLED);
    } else if (section == "Source") {
        builder.setSource(toQString(content));
    } else if (section == "Installed-Size") {
        builder.setInstalledSize(toQString(content).toUInt());
    } else if (section == "Maintainer") {
        builder.setMaintainer(toQString(content));
    } else if (section == "Depends") {
        builder.setDepends(toQString(content));
    } else if (section == "Replaces") {
        builder.setReplaces(toQString(content));
    } else if (section == "Provides") {
        builder.setProvides(toQString(content));
    } else if (section == "Pre-Depends") {
        builder.setPreDepends(toQString(content));
    } else if (section == "Recommends") {
        builder.setRecommends(toQString(content));
    } else if (section == "Suggests") {
        builder.setSuggests(toQString(content));
    } else if (section == "Conflicts") {
        builder.setConflicts(toQString(content));
    } else if (section == "Breaks") {
        builder.setBreaks(toQString(content));
    } else if (section == "Homepage") {
        builder.setHomepage(toQString(content));
    } else if (section == "Section") {
        builder.setSection(toQString(content));
    } else if (section == "Priority") {
        builder.setPriority(toQString(content));
    } else if (section == "Filename") {
        builder.setFilename(toQString(content));
    } else if (section == "Size") {
        builder.setSize(toQString(content).toUInt());
    } else if (section == "MD5sum") {
        builder.setMd5sum(toQString(content));
    } else if (section == "SHA256") {
        builder.setSha256(toQString(content));
    } else if (section == "Conffiles") {
        builder.setConffiles(toQString(content));
    } else if (section == "Description-md5") {
    } else if (section == "Multi-Arch") {
    } else if (section == "Tag") {
    } else if (section == "Built-Using") {
    } else if (section == "Enhances") {
    } else if (section == "Sha1") {
    } else if (section == "Sha512") {
    } else if (section == "Enhances") {
    } else if (section == "Bugs") {
    } else if (true) {
        // Those and some more like Static-Built-Using, X-Cargo-Built-Using, Gstreamer-Elements, ...
        // Intentionally ignored:
        // qDebug() << "Ignored section " << toQString(section);
    }
}

void AptCacheParser::mergeIntoPackageMap(
    const AptPackageBuilder& builder,
    map<QString, unique_ptr<const IPackage>>& target
) {
    static QString defaultArchitecture = toQString(PackageName::defaultArchitecture());
    if (target.find(builder.name()) == target.end()){
        target.insert({builder.name(), builder.createPackage()});
    } else {
        // prefer default architecture
        if (builder.architecture() == defaultArchitecture) {
            target.insert_or_assign(builder.name(), builder.createPackage());
        }
    }
}

map<QString, unique_ptr<const IPackage>> AptCacheParser::parseDumpAvail(
    int packageCount,
    const map<string, const DpkgParser::PackageInformation>& dpkgPackage,
    NUtil::IProgressObserver& progressObserver
) {
    auto packages = map<QString, unique_ptr<const IPackage>>();

    auto lineProcessor = [&packages, &dpkgPackage, packageCount, &progressObserver](const string& line) {
        static auto builder = AptPackageBuilder(dpkgPackage);
        static string currentSection = "";
        static string currentContent = "";
        static int parsedPackageCount = 0;
        if (line.empty()) {
            assignValueToSection(builder, currentSection, currentContent);
            currentSection = "";
            currentContent = "";
            mergeIntoPackageMap(builder, packages);
            builder.clear();
            ++parsedPackageCount;
            if (parsedPackageCount%500 == 0) {
                progressObserver.setProgress(100 * parsedPackageCount / packageCount);
            }
        } else if (line[0] == ' ') {
            // ignore multi-line content for dump-avail, there should be no
            // such content expect for debtags or long description
            // usually from third-party repos
        } else {
            auto sectionAndContent = splitSectionLine(line);
            if (!currentSection.empty() && sectionAndContent.first != currentSection) {
                assignValueToSection(builder, currentSection, currentContent);
                currentSection = "";
                currentContent = "";
            }
            currentSection = sectionAndContent.first;
            currentContent = sectionAndContent.second;
        }

    };

    NApplication::runCommandForParsing(
        "apt-cache dumpavail |grep -E \"(^(Package:|Version:|Architecture:|Status:|Description:))|^$\"",
        lineProcessor
    );
    return packages;

}

PackageDetails mergePackageDetails(const list<AptPackageBuilder>& input) {
    assert(input.size() > 0);
    if (input.size() == 1) return input.front().createPackageDetails();
    auto installedPackage = std::find_if(input.begin(), input.end(), [](const AptPackageBuilder& builder) {
        return builder.isInstalled();
    });
    // if we did find an installed package
    if (installedPackage != input.end()) return installedPackage->createPackageDetails();
    // else any guess is good, so take the first package in the list
    else return input.front().createPackageDetails();
}

PackageDetails AptCacheParser::parseCacheShow(const::string& packageName) {
    auto packageBuilders = list<AptPackageBuilder>();

    auto lineProcessor = [&packageBuilders](const string& line) {
        static auto builder = AptPackageBuilder();
        static string currentSection = "";
        static string currentContent = "";
        if (line.empty()) {
            assignValueToSection(builder, currentSection, currentContent);
            currentSection = "";
            currentContent = "";
            packageBuilders.push_back(builder);
            builder.clear();
        } else  // this line is a newline of a multiline entry
        if (line[0] == ' ') {
            currentContent += "\n" + line;
        } else {
            auto sectionAndContent = splitSectionLine(line);
            if (!currentSection.empty() && sectionAndContent.first != currentSection) {
                assignValueToSection(builder, currentSection, currentContent);
                currentSection = "";
                currentContent = "";
            }
            currentSection = sectionAndContent.first;
            currentContent = sectionAndContent.second;
        }

    };

    NApplication::runCommandForParsing(string("apt-cache show ") + packageName, lineProcessor);

    if (packageBuilders.empty()) throw NPlugin::PackageNotFoundException(packageName);

    return mergePackageDetails(packageBuilders);
}


}
