#include <Converter.hpp>

#include <string>

#include <alib/exception>

#include <QDomComment>

#include <common/EvalHelper.h>
#include <registry/StringWriterRegistry.hpp>
#include <registry/XmlRegistry.h>
#include <sax/SaxComposeInterface.h>
#include <sax/SaxParseInterface.h>

#include <GraphvizIntegrator.hpp>

namespace Converter {

    std::optional<QString> toString(const std::shared_ptr<abstraction::Value>& data) {
        try {
            auto res = abstraction::StringWriterRegistry::getAbstraction(data->getType());
            res->attachInput(data, 0);

            auto value = std::dynamic_pointer_cast < abstraction::ValueHolderInterface < std::string > > ( res->eval ( ) );
            return QString::fromStdString(value->getValue ( ) );
        }
        catch ( ... ) {
			alib::ExceptionHandler::handle ( std::cerr );
            return std::nullopt;
        }
    }

    std::optional<QString> toDOT(const std::shared_ptr<abstraction::Value>& data) {
        try {
            ext::vector<std::shared_ptr<abstraction::Value>> params;
            params.push_back(data);

			abstraction::TemporariesHolder environment;

            auto res = abstraction::EvalHelper::evalAlgorithm(environment, "convert::DotConverter",
                                                          {},
                                                          params,
                                                          abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT);

            auto value = std::dynamic_pointer_cast < abstraction::ValueHolderInterface < std::string > > ( res );

            return QString::fromStdString(value->getValue());
        }
        catch ( ... ) {
			alib::ExceptionHandler::handle ( std::cerr );
            return std::nullopt;
        }
    }


    std::optional<QString> toXML(const std::shared_ptr<abstraction::Value>& data, bool indent) {
        try {
            auto res = abstraction::XmlRegistry::getXmlComposerAbstraction(data->getType());
            res->attachInput(data, 0);

            auto value = std::dynamic_pointer_cast < abstraction::ValueHolderInterface < ext::deque<sax::Token> > > ( res->eval ( ) );

            std::string result;
            sax::SaxComposeInterface::composeMemory(result, value->getValue());

            auto text = QString::fromStdString(result);

            if (indent) {
                QDomDocument doc;
                if (doc.setContent(text, false))
                    return doc.toString(4);
            }
            return text;

        }
        catch ( ... ) {
			alib::ExceptionHandler::handle ( std::cerr );
            return std::nullopt;
        }
    }

    std::optional<QImage> toPNG(const std::shared_ptr<abstraction::Value>& data) {
        if (auto dot = Converter::toDOT(data)) {
            return GraphvizIntegrator::createImage(*dot, GraphvizIntegrator::PNG);
        }
        else {
            return std::nullopt;
        }
    }

    std::shared_ptr<abstraction::Value> parseXML(const QString& xml) {
        try {
            ext::deque<sax::Token> tokens;
            sax::SaxParseInterface::parseMemory(xml.toStdString(), tokens);
            std::string type = tokens[0].getData();
            std::shared_ptr<abstraction::OperationAbstraction> automaton = abstraction::XmlRegistry::getXmlParserAbstraction(
                    type);

            auto tokensAbstraction = std::make_shared<abstraction::ValueHolder<ext::deque<sax::Token>>>(std::move ( tokens ), true);
            automaton->attachInput(tokensAbstraction, 0);
            return automaton->eval ( );
        }
        catch ( ... ) {
			alib::ExceptionHandler::handle ( std::cerr );
            return nullptr;
        }
    }

    std::shared_ptr<abstraction::Value> parseText(const QString& txt) {
        ext::vector<std::shared_ptr<abstraction::Value>> params;
        params.push_back(std::make_shared<abstraction::ValueHolder<std::string>>( txt.toStdString(), true));

		abstraction::TemporariesHolder environment;

        for (const auto& type: { "automaton::Automaton", "grammar::Grammar", "regexp::RegExp"}) {
            try {
                return abstraction::EvalHelper::evalAlgorithm(environment, "string::Parse",
                                                          {type},
                                                          params,
                                                          abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT);
            }
            catch ( ... ) {
				alib::ExceptionHandler::handle ( std::cerr );
			}
        }
        return nullptr;
    }

    std::shared_ptr<abstraction::Value> tryParse(const QString& input) {
        if (auto automaton = Converter::parseXML(input)) {
            return automaton;
        }
        if (auto automaton = Converter::parseText(input)) {
            return automaton;
        }
        return nullptr;
    }

    void saveToImage(const std::shared_ptr<abstraction::Value>& data, const QString& filename) {
        if (auto dot = Converter::toDOT(data)) {
            if (!GraphvizIntegrator::createImageFile(*dot, filename, GraphvizIntegrator::formatFromFilename(filename))) {
                throw std::runtime_error { "Failed to write to file." };
            }
        }
        else {
            throw std::runtime_error { "Failed to convert data to DOT." };
        }
    }
}
