/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * File: transcendlayer.cpp
 */

/**
 * \file    transcendlayer.h
 * \brief   Transcend reasoning implementation
 */

#include "transcendlayer.h"
#include "analysis/utils.h"
#include "utils.h"

#include <gringo/scripts.hh>
#include <boost/format.hpp>
#include <boost/algorithm/string/join.hpp>
#include <iostream>
#include <memory>
#include <boost/variant/detail/apply_visitor_binary.hpp>

using namespace std;

//TODO escape identifiers started with upper case symbol

namespace xreate{

bool operator==(const SymbolAnonymous& s1, const SymbolAnonymous& s2) {
    return s1.id == s2.id && s1.flagIsUsed == s2.flagIsUsed;
}

struct VisitorSymbolNodeHash : public boost::static_visitor<size_t>{

    std::size_t operator()(const xreate::SymbolPacked& node) const noexcept {
        return 2 * (node.identifier + 3 * node.scope + 5 * std::abs(node.version));
    }

    std::size_t operator()(const xreate::SymbolAnonymous& node) const noexcept {
        return 7 * node.id;
    }
} ;
}

namespace std{

std::size_t
hash<xreate::SymbolNode>::operator()(xreate::SymbolNode const& s) const noexcept {
    return boost::apply_visitor(xreate::VisitorSymbolNodeHash(), s);
}

std::size_t
hash<xreate::SymbolGeneralized>::operator()(xreate::SymbolGeneralized const& s) const noexcept {
    return xreate::AttachmentsId<xreate::SymbolGeneralized>::getId(s);
}
}

namespace xreate{

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

    auto warningsModel = query(warningTag);

    if(warningsModel.size())
        for(auto warning : warningsModel) {
            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
TranscendLayer::processSolution(Gringo::Model const &model) {
    cout << "Model: " << endl;

    const string& atomBindVar = Config::get("transcend.bindings.variable");
    const string& atomBindFunc = Config::get("transcend.bindings.function");
    const string& atomBindScope = Config::get("transcend.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 atomAlias = std::get<1>(parse<Gringo::Symbol, Gringo::Symbol>(atom)).name().c_str();
            __model.emplace(atomAlias, atom);
            continue;
        }

        __model.emplace(atomName, atom);
    }

    return true;
}

void
TranscendLayer::registerReport(IAnalysisReport * report) {
    __reports.push_back(report);
}

void
TranscendLayer::printReports() {
    for(IAnalysisReport* report : __reports) {
        report->print(__partGeneral);
    }
}

void
TranscendLayer::deleteReports(){
  for(IAnalysisReport* report : __reports) {
    delete report;
  }
  __reports.clear();
}

void
TranscendLayer::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
TranscendLayer::registerWarning(std::string && message) {
    static int warningId = 0;
    __warnings.emplace(warningId, message);
    return warningId++;
}

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

    if(ast)
        for(string fn : ast->__rawImports) {
            std::ifstream file(fn);
            if(!file) {
                std::cout << "Can't process script file: " << fn << std::endl;
                assert(false);
            }

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

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

void
TranscendLayer::run() {
    involveImports();
    printReports();

    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->processSolution(model);
        return true;
    },
    {
    });

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

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

TranscendLayer::TranscendLayer() : ast(nullptr) { }

StaticModel
TranscendLayer::query(const std::string & atom) const {
    StaticModel result;

    if (! __model.count(atom)) {
        return result;
    }

    auto currentDataRange = __model.equal_range(atom);
    std::copy(currentDataRange.first, currentDataRange.second, std::inserter(result, result.end()));
    return result;
}

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

    return pos.first->second;
}

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

SymbolPacked
TranscendLayer::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
TranscendLayer::unpack(const SymbolPacked & symbol) const {
    return Symbol{ScopedSymbol
        {symbol.identifier, symbol.version}, __registryScopes[symbol.scope]};
};

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

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

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

class VisitorUnpackSymbol : public boost::static_visitor<SymbolGeneralized>{
public:

    VisitorUnpackSymbol(const TranscendLayer* transcend) : __transcend(transcend) { }

    SymbolGeneralized operator()(const SymbolPacked& symbol) const {
        return __transcend->unpack(symbol);
    }

    SymbolGeneralized operator()(const SymbolAnonymous& symbol) const {
        return symbol;
    }

private:
    const TranscendLayer* __transcend;
} ;

class VisitorPackSymbol : public boost::static_visitor<SymbolNode>{
public:

    VisitorPackSymbol(TranscendLayer* transcend, const std::string& hintSymbolName)
    : __transcend(transcend), __hint(hintSymbolName) { }

    SymbolNode operator()(const Symbol& symbol) const {
        return __transcend->pack(symbol, __hint);
    }

    SymbolNode operator()(const SymbolAnonymous& symbol) const {
        return symbol;
    }

private:
    TranscendLayer* __transcend;
    std::string __hint;
} ;

SymbolNode
TranscendLayer::pack(const SymbolGeneralized& symbol, const std::string & hintSymbolName) {
    return boost::apply_visitor(VisitorPackSymbol(this, hintSymbolName), symbol);
}

SymbolGeneralized
TranscendLayer::unpack(const SymbolNode & symbol) const {
    return boost::apply_visitor(VisitorUnpackSymbol(this), symbol);
}

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);
}

Expression
ParseImplAtom<Expression>
::get(const Gringo::Symbol & atom) {
    switch(atom.type()) {
    case Gringo::SymbolType::Num: return Expression(atom.num());
    case Gringo::SymbolType::Str: return Expression(Atom<String_t>(std::string(atom.string().c_str())));

    case Gringo::SymbolType::Fun:
    {
        //FUNC
        Expression result(Operator::CALL,{Expression(Atom<Identifier_t>(std::string(atom.name().c_str())))});
        for(const Gringo::Symbol& arg : atom.args()) {
            result.addArg(ParseImplAtom<Expression>::get(arg));
        }

        return result;
    }

    default:
    {
        assert(false);
    }
    }
}

int
ParseImplAtom<int>
::get(const Gringo::Symbol & atom) {
    switch(atom.type()) {
    case Gringo::SymbolType::Num: return atom.num();
    default: break;
    }

    assert(false && "Inappropriate symbol type");
}

std::string
ParseImplAtom<std::string>
::get(const Gringo::Symbol & atom) {
    switch(atom.type()) {
    case Gringo::SymbolType::Str: return atom.string().c_str();
    case Gringo::SymbolType::Fun: return atom.name().c_str();

    default: break;
    }

    assert(false && "Inappropriate symbol type");
}

SymbolPacked
ParseImplAtom<SymbolPacked>
::get(const Gringo::Symbol & atom) {
    auto result = TranscendLayer::parse<int, int, int>(atom);
    return SymbolPacked(std::get<0>(result), std::get<1>(result), std::get<2>(result));
};

Gringo::Symbol
ParseImplAtom<Gringo::Symbol>
::get(const Gringo::Symbol & atom) {
    return atom;
}

SymbolNode
ParseImplAtom<SymbolNode>
::get(const Gringo::Symbol & atom) {
    assert(atom.type() == Gringo::SymbolType::Fun
        && "Inappropriate symbol type");

    if(atom.name() == "a") {
        return SymbolAnonymous{(unsigned int) std::get<0>(TranscendLayer::parse<int>(atom))};

    } else if(atom.name() == "s") {
        return ParseImplAtom<SymbolPacked>::get(atom);
    }

    assert(false && "Wrong symbol format");
}

class VisitorSymbolId : public boost::static_visitor<unsigned int>{
public:

    unsigned int operator()(const Symbol& symbol) const {
        return AttachmentsId<Symbol>::getId(symbol);
    }

    unsigned int operator()(const SymbolAnonymous& symbol) const {
        return symbol.id;
    }
} ;

unsigned int
AttachmentsId<SymbolGeneralized>
::getId(const SymbolGeneralized & symbol) {
    return boost::apply_visitor(VisitorSymbolId(), symbol);
}

} //end of xreate namespace

/**
 * \class xreate::TranscendLayer
 * \brief Logic reasoning implementation. Internally, it's a proxy to the external ASP solver [Clasp](https://potassco.org/clasp/)
 *
 * Performs reasoning over source codes in order to facilitate efficient compilation using results from a number of internal analyzers.
 * Clients implement \ref IAnalysisReport to supply Transcend with data and implement \ref IQuery to find out resolutions.
 * 
 * Transcend uses the following sources to build a logic program before actual reasoning is performed:
 *  - Raw content. Clients are free to include arbitrary ASP format data in the logic program. See \ref addRawScript().
 *  - External scripts. External files with ASP scripts can be appended to the logic program. See `involveImports()` (private member).
 *  - Diagnostic rules to produce diagnostic messages during
 *    compilation(warnings) or even to signal to halt compilation with errors. See \ref addRuleWarning(), \ref registerWarning().
 *  - Internal analyzers. The analyzer can publish logic facts and rules by implementing \ref IAnalysisReport interface. 
 *
 * Generally, Transcend input could be loosely broke down into three categories:
 *  - Internally derived data. CFA, DFA, and other analyzers automatically supply the reasoner with
 *    useful  insights about source codes, the structure and algorithms of a program.
 *  - User provided custom data. Analyzers extract manual developer-provided annotations from the source codes.
 *  - External data. External files supply reasoner with third-party data
 *    which relates to different aspects of a program possibly produced by external analyzers.
 *
 * After Transcend has gathered data from all providers and the logic program is fully constructed, 
 * it runs the external logic reasoner to receive back the solution.
 * 
 * The solution from the external logic reasoner is accessible via *queries*.
 * Classes which want to request data from Transcend should implement the \ref IQuery interface. See \ref IQuery descendants to find out currently available queries. 
 *
 * \section tl_adapt Adaptability 
 * Decorators are used to extend %TranscendLayer functionality. The default bundle defined by \ref DefaultTranscendLayerImpl.
 * 
 * \sa See xreate::dfa::DFAPass, xreate::cfa::CFAPass, xreate::IQuery, xreate::IAnalysisReport
 */
