/* 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/resources.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;
using namespace xreate::analysis;

//namespace xreate{

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

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 = VAR_ANN_PREDICATE;
    const string& atomBindFunc = FUNCTION_ANN_PREDICATE;
    const string& atomBindScope = SCOPE_ANN_PREDICATE;

    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) {
  //FIX
}

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

void
TranscendLayer::appendImport() {
    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() {
  appendImport();
    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]};
};

ASTSitePacked
TranscendLayer::pack(const ASTSite& s){
  ASTSitePacked result;

  if(Attachments::exists<ExprAlias_A>(s.id)){
    Symbol resultS = Attachments::get<ExprAlias_A>(s.id);
    result = ASTSitePacked(pack(resultS));

  } else{
    result = ASTSitePacked(s);
  }

  return result;
}

ASTSite
TranscendLayer::unpack(const ASTSitePacked& s){
  switch(s.tag){
    case ASTSitePacked::SYMBOL:
      return {CodeScope::getDefinition(unpack(s.symbol), true).id};

    case ASTSitePacked::ANON:
      return s.site;
  }
  assert(false);
  return  ASTSite();
}

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

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

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

  if(atom.name() == SITE_ANON_PREDICATE) {
    return ASTSitePacked(ASTSite{std::get<0>(TranscendLayer::parse<int>(atom))});

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

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

std::string
ASTSitePacked::str() const {
  boost::format formatSymbAnonymous(string(SITE_ANON_PREDICATE) + "(%1%)");
  boost::format formatSymbNamed(string(SITE_SYMBOL_PREDICATE) + "(%1%,%2%,%3%)");

  switch(tag){
    case ASTSitePacked::SYMBOL:{
      return boost::str(formatSymbNamed % symbol.identifier % symbol.version % symbol.scope);
    }

    case ASTSitePacked::ANON:{
      return boost::str(formatSymbAnonymous % site.id);
    }
  }
}

//unsigned int
//AttachmentsId<ASTSite>::getId(const ASTSite& 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 `appendImport()` (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
 */
