#include "clasplayer.h"

#include <iostream>
#include "utils.h"
#include <boost/format.hpp>
#include <boost/algorithm/string/join.hpp>
#include <gringo/scripts.hh>

#include "analysis/aux.h"
#include "analysis/DominatorsTreeAnalysisProvider.h"
#include "analysis/cfagraph.h"
#include "analysis/dfagraph.h"

using namespace std;

//TODO escape identifiers started from upper case symbol
namespace xreate {

    void
    ClaspLayer::printWarnings(std::ostream& out)
    {
        const std::string warningTag = "warning";

        auto warningsRange = __model.equal_range(warningTag);

        for (auto warning=warningsRange.first; warning!= warningsRange.second; ++warning) {
            unsigned int warningId;

            Gringo::Symbol params;
            std::tie(warningId, params) = parse<unsigned int, Gringo::Symbol>(warning->second);

            cout << "Warning: " << __warnings.at(warningId) << " ";
            params.print(out);
            out<<params;
        }
    }

    bool
    ClaspLayer::handleSolution(Gringo::Model const &model) {
        std::list<std::string> warnings;
        cout << "Model: " << endl;

        const string& atomBindVar = Config::get("clasp.bindings.variable");
        const string& atomBindFunc = Config::get("clasp.bindings.function");
        const string& atomBindScope = Config::get("clasp.bindings.scope");

        for (Gringo::Symbol atom : model.atoms(clingo_show_type_atoms)) {
            atom.print(cout);
            cout <<" | "<< endl;

            string atomName(atom.name().c_str());
            if (atomName == atomBindVar || atomName == atomBindFunc || atomName == atomBindScope){
                string name = std::get<1>(parse<Gringo::Symbol, Gringo::Symbol>(atom)).name().c_str();
                __model.emplace(move(name), move(atom));
            }

            __model.emplace(atomName, move(atom));
        }

        return true;
    }

    void
    ClaspLayer::setCFAData(xreate::analysis::CFAGraph* graph) {
    	dataCFA.reset(graph);
    }

    void
    ClaspLayer::setDFAData(xreate::analysis::DFAGraph* graph){
        dataDFA.reset(graph);
    }

    void
    ClaspLayer::addRuleWarning(const RuleWarning &rule) {
        //__partGeneral << rule << endl;

        list<string> domains;
        boost::format formatDef("%1%(%2%)");
        std::transform(rule.__args.begin(), rule.__args.end(), std::inserter(domains, domains.begin()),
                [&formatDef](const std::pair<std::string, DomainAnnotation> &argument) {
                    string domain;
                    switch (argument.second) {
                        case DomainAnnotation::FUNCTION:
                            domain = "function";
                            break;
                        case DomainAnnotation::VARIABLE:
                            domain = "variable";
                            break;
                    }

                    return boost::str(formatDef % domain % argument.first);
                });

        list<string> vars;
        std::transform(rule.__args.begin(), rule.__args.end(), std::inserter(vars, vars.begin()),
                [](const std::pair<std::string, DomainAnnotation> &argument) {
                    return argument.first.c_str();
                });

        list<list<string>> guardsRaw;
        std::transform(rule.__guards.begin(), rule.__guards.end(), std::inserter(guardsRaw, guardsRaw.begin()),
                [this](const Expression &guard) {
                    return xreate::analysis::compile(guard);
                });

        const list<string>& guards = xreate::analysis::multiplyLists(std::move(guardsRaw));
        list<string> &&branches = xreate::analysis::compileNeg(rule.__condition);

        boost::format formatWarning("warning(%1%, (%2%)):- %3%, %4%, %5%.");
        for (const string &guardsJoined: guards)
            for (const string &branch: branches) {
                unsigned int hook = registerWarning(string(rule.__message));

                __partGeneral  <<  formatWarning
                        %(hook)
                        %(boost::algorithm::join(vars, ", "))
                        %(branch)
                        %(guardsJoined)
                        %(boost::algorithm::join(domains, ", "))
                    <<endl;
            }
    }



    unsigned int
    ClaspLayer::registerWarning(std::string &&message) {
        static int warningId = 0;
        __warnings.emplace(warningId, message);
        return warningId++;;
    }

    void
    ClaspLayer::involveImports()    {
        ostream &out = __partGeneral;

        for (string fn: ast->__rawImports)
        {
            std::ifstream file(fn);
            if (!file) continue;

            while(!file.eof()){
                string line;
                std::getline(file, line);
                out << line << endl;
            }
        }
    }

    void
    ClaspLayer::addRawScript(std::string&& script){
    	__partGeneral << script;
    }

    void
    ClaspLayer::run() {
        involveImports();

        if (this->dataDFA){
            this->dataDFA->print(__partGeneral);
        }

        if (this->dataCFA){
            this->dataCFA->print(__partGeneral);
        }

        DominatorsTreeAnalysisProvider providerDominators;
        providerDominators.run(this);
        providerDominators.print(__partGeneral);

        ostringstream program;
        program << __partTags.str() << __partGeneral.str();
        cout << FYEL(program.str()) << endl;

        std::vector<char const *> args{"clingo", nullptr};
        DefaultGringoModule moduleDefault;
        Gringo::Scripts scriptsDefault(moduleDefault);
        ClingoLib ctl(scriptsDefault, 0, args.data(), {}, 0);

        ctl.add("base", {}, program.str());
        ctl.ground({{"base", {}}}, nullptr);

//          solve
        Gringo::SolveResult result = ctl.solve([this](Gringo::Model const &model) {
            this->handleSolution(model);
            return true;
        }, {});

        if (result.satisfiable() == Gringo::SolveResult::Satisfiable) {
            cout << FGRN("SUCCESSFULLY") << endl;
        } else {
            cout << FRED("UNSUCCESSFULLY") << endl;
        }

//      invoke all query plugins to process clasp data
        for (auto q: __queries)
        {
            q.second->init(this);
        }
    }

    ClaspLayer::ClaspLayer() {
    }

    ClaspLayer::ModelFragment
    ClaspLayer::query(const std::string& atom)
    {
        if (! __model.count(atom)){
            return boost::none;
        }

        return ModelFragment(__model.equal_range(atom));
    }

    ScopePacked
    ClaspLayer::pack(CodeScope* const scope) {
    	auto pos = __indexScopes.emplace(scope, __indexScopes.size());
        if (pos.second)
        	__registryScopes.push_back(scope);

        return pos.first->second;
    }

    size_t
    ClaspLayer::getScopesCount() const{
        return __registryScopes.size();
    }

    SymbolPacked
    ClaspLayer::pack(const Symbol& symbol, std::string hintSymbolName)
    {
        SymbolPacked result(symbol.identifier.id, symbol.identifier.version, pack(symbol.scope));
        __indexSymbolNameHints.emplace(result, hintSymbolName);

        return result;
    }

    Symbol
    ClaspLayer::unpack(const SymbolPacked& symbol)
    {
        return Symbol{ScopedSymbol{symbol.identifier, symbol.version}, __registryScopes[symbol.scope]};
    };

    std::string
    ClaspLayer::getHintForPackedSymbol(const SymbolPacked& symbol){
        if (!symbol.categoryTransient) {
            auto result = __indexSymbolNameHints.find(symbol);
            return (result == __indexSymbolNameHints.end())? "" : result->second;

        } else {
            return "anonym(" + to_string(symbol.identifier) + ")";
        }
    }

    bool operator==(const SymbolPacked& s1, const SymbolPacked& s2)
    {
        return s1.identifier == s2.identifier && s1.scope == s2.scope;
    }

    bool operator<(const SymbolPacked& s1, const SymbolPacked& s2)
    {
        return  s1.scope < s2.scope  || (s1.scope ==  s2.scope && s1.identifier < s2.identifier);
    }

    IQuery*
    ClaspLayer::registerQuery(IQuery *query, const QueryId& id) {
        return __queries.emplace(id, query).first->second;
    }

    IQuery*
    ClaspLayer::getQuery(const QueryId& id){
    	assert(__queries.count(id) && "Undefined query");
    	return __queries.at(id);
    }
}