#include "Parser.h"

#include <algorithm>

std::list<Section_ptr>
Parser::parseAvailableConfiguration(const Glib::ustring& xml) {
    this->logger->debug(
        Glib::ustring::compose(this->translator->trns("Parsing XML %1"), xml));
    std::list<Section_ptr> availableSections;
    pugi::xml_document parser;

    auto result = parser.load_string(xml.c_str());

    if (!parser) {
        this->logger->error(
            Glib::ustring::compose(this->translator->trns("pugiXml error: %1"),
                                   result.description()));
        this->logger->error(
            Glib::ustring::compose(this->translator->trns("XML Parsed: %1"),
                                   xml));
        return availableSections;
    }
    auto rootNode = parser.child("driinfo");

    for (const auto& section : rootNode.children("section")) {
        Section_ptr confSection = std::make_shared<Section>();

        for (const auto& description : section.children("description")) {
            Glib::ustring lang(description.attribute("lang").value());

            if (lang == "en") {
                Glib::ustring value(description.attribute("text").value());
                confSection->setDescription(value);
            }
        }

        auto existingSection = std::find_if(
            availableSections.begin(),
            availableSections.end(),
            [&confSection](const Section_ptr& s) {
                return s->getDescription() == confSection->getDescription();
            });

        if (existingSection != availableSections.end()) {
            confSection = *existingSection;
        } else {
            availableSections.push_back(confSection);
        }

        for (const auto& option : section.children("option")) {
            auto parsedOption = parseSectionOptions(option);

            confSection->addOption(parsedOption);
        }
    }

    return availableSections;
}

DriverOption_ptr Parser::parseSectionOptions(const pugi::xml_node& option) {
    DriverOption_ptr parsedOption = std::make_shared<DriverOption>();

    parsedOption->setName(option.attribute("name").value());
    Glib::ustring type = option.attribute("type").value();
    parsedOption->setType(parsedOption->stringToEnum(type));

    auto defaultValue = option.attribute("default");
    if (!defaultValue.empty()) {
        parsedOption->setDefaultValue(defaultValue.value());
    }

    auto validValues = option.attribute("valid");
    if (!validValues.empty()) {
        parsedOption->setValidValues(validValues.value());
    }

    const pugi::xml_node* descriptionHolder = nullptr;

    for (const auto& description : option.children("description")) {
        Glib::ustring lang(description.attribute("lang").value());
        Glib::ustring value(description.attribute("text").value());

        if (lang == "en") {
            parsedOption->setDescription(value);
            descriptionHolder = &description;
            break;
        }
    }

    if (parsedOption->getType() == DriverOptionType::ENUM
        && descriptionHolder != nullptr) {
        for (const auto& enumOption : descriptionHolder->children("enum")) {
            Glib::ustring value(enumOption.attribute("value").value());
            Glib::ustring text(enumOption.attribute("text").value());

            parsedOption->addEnumValue(text, value);
        }
    }

    parsedOption->recomputeOptionTypes();

    return parsedOption;
}

std::list<Device_ptr> Parser::parseDevices(Glib::ustring& xml) {
    std::list<Device_ptr> deviceList;

    this->logger->debug(Glib::ustring::compose(
        this->translator->trns("Parsing device for XML: %1"),
        xml));

    pugi::xml_document parser;
    auto result = parser.load_string(xml.c_str());

    if (!result) {
        this->logger->error(Glib::ustring::compose(
            this->translator->trns("Error during device XML parsing: %1"),
            result.description()));
        return deviceList;
    }

    auto rootNode = parser.child("driconf");
    for (const auto& device : rootNode.children("device")) {
        auto deviceConf = std::make_shared<Device>();

        auto deviceScreen = device.attribute("screen");
        if (!deviceScreen.empty()) {
            deviceConf->setScreen(std::stoi(deviceScreen.value()));
        }

        auto deviceDriver = device.attribute("driver");
        if (!deviceDriver.empty()) {
            deviceConf->setDriver(deviceDriver.value());
        }

        for (const auto& application : device.children("application")) {
            auto parsedApp = parseApplication(application);
            deviceConf->addApplication(parsedApp);
        }

        deviceList.emplace_back(deviceConf);
    }

    return deviceList;
}

Profile_ptr Parser::parseApplication(const pugi::xml_node& application) {
    auto app = std::make_shared<Profile>();

    auto applicationName = application.attribute("name");
    if (!applicationName.empty()) {
        app->setName(applicationName.value());
    }

    auto applicationExecutable = application.attribute("executable");
    if (!applicationExecutable.empty()) {
        app->setExecutable(applicationExecutable.value());
    }

    auto applicationExecutableRegex
        = application.attribute("executable_regexp");
    if (!applicationExecutableRegex.empty()) {
        app->setExecutableRegex(applicationExecutableRegex.value());
    }

    auto applicationSha1 = application.attribute("sha1");
    if (!applicationSha1.empty()) {
        app->setSha1(applicationSha1.value());
    }

    for (const auto& option : application.children("option")) {
        auto optionName  = option.attribute("name");
        auto optionValue = option.attribute("value");
        if (!optionName.empty() && !optionValue.empty()) {
            auto newOption = std::make_shared<ProfileOption>();
            newOption->setName(optionName.value());
            newOption->setValue(optionValue.value());

            app->addOption(newOption);
        }
    }

    return app;
}