/* 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/.
 *
 * File:   CFAGraph.cpp
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created on June 27, 2016, 2:09 PM
 */

/**
 *  \file cfagraph.h
 *  \brief Control Flow Analysis(CFA) graph data
 *
 */

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

using namespace xreate::cfa;
using namespace std;

void
CFAGraph::print(std::ostringstream& output) const {
    const std::string& atomBinding = Config::get("transcend.bindings.function");
    const std::string& atomBindingScope = Config::get("transcend.bindings.scope");

    output << endl << "%\t\tStatic analysis: CFA" << endl;
    output << __outputPrecomputed.str();

    //show function tags
    int counterTags = 0;
    std::ostringstream bufFunctionNames;
    boost::format formatFunction("function(%1%).");
    boost::format formatBind(atomBinding + "(%1%, %2%).");
    for (auto function : this->__nodesFunction.left) {
        const  auto tags = this->__functionTags.equal_range(function.first);
        if (tags.first == tags.second) {
            //no tags
            bufFunctionNames << "; " << function.second ;
            continue;
        }

        output << formatFunction % (function.second) << std::endl;
        for (const auto& tag_ : boost::make_iterator_range(tags)) {
            const Expression& tag = tag_.second;

            list<string> tagRaw = xreate::analysis::compile(tag);
            assert(tagRaw.size() == 1);

            output << formatBind
                % (function.second)
                % (tagRaw.front())
                << endl;
            ++counterTags;
        }
    }

    if (bufFunctionNames.tellp()) {
        output << formatFunction % (bufFunctionNames.str().substr(2)) << std::endl;
    }

    if (counterTags == 0) {
        output << "%no function tags at all" << endl;
    }

    //declare scopes
    boost::format formatScope("scope(0..%1%).");
    output << formatScope % (__transcend->getScopesCount() - 1) << std::endl;

    //show context rules:
    for (auto rule : this->__contextRules) {
        output << ContextRule(rule.second).compile(rule.first) << std::endl;
    };

    //show scope tags:
    counterTags = 0;
    boost::format formatScopeBind(atomBindingScope + "(%1%, %2%, strong).");
    for (auto entry : this->__scopeTags) {
        ScopePacked scopeId = entry.first;
        const Expression& tag = entry.second;
        list<string> tagRaw = xreate::analysis::compile(tag);
        assert(tagRaw.size() == 1);

        output << formatScopeBind % scopeId % (tagRaw.front()) << endl;
        ++counterTags;
    }

    if (counterTags == 0) {
        output << "%scope tags: no tags at all" << endl;
    }

    //parent connections
    //TOTEST CFG parent function
    boost::format formatFunctionParent("cfa_parent(%1%, function(%2%)).");
    for (const auto &relation : this->__parentFunctionRelations) {
        const string& function = this->__nodesFunction.left.at(relation.right);

        output << formatFunctionParent % relation.left % function << endl;
    }

    //TOTEST CFG parent scope
    boost::format formatScopeParent("cfa_parent(%1%, scope(%2%)).");
    for (const auto &relation : this->__parentScopeRelations) {
        output << formatScopeParent % relation.first % relation.second << endl;
    }

    //call connections
    boost::format formatCall("cfa_call(%1%, %2%).");

    for (const auto &relation : this->__callRelations) {
        const ScopePacked scopeFrom = relation.left;
        const string& functionTo = this->__nodesFunction.left.at(relation.right);

        output << formatCall % (scopeFrom) % (functionTo) << endl;
    }

    //function specializations description
    boost::format formatSpecializations("cfa_function_specializations(%1%, %2%).");
    const list<ManagedFnPtr>& functions = __transcend->ast->getAllFunctions();
    for (auto f : functions) {
        if (f->guard.isValid()) {
            list<string> guardRaw = xreate::analysis::compile(f->guard);
            assert(guardRaw.size() == 1);
            output << formatSpecializations % (f->getName()) % (guardRaw.front()) << endl;
        }
    }

    //Dependencies
    boost::format formatDependencies("cfa_scope_depends(%1%, %2%).");
    for(const auto relation : __dependencyRelations) {
        output << formatDependencies % relation.first % relation.second << endl;
    }
    std::multimap<ScopePacked, ScopePacked> __dependencyRelations;
}

void
CFAGraph::addFunctionAnnotations(const std::string& function, const std::map<std::string, Expression>& tags) {
    unsigned int fid = registerNodeFunction(function);

    for (auto& tag : tags) {
        __functionTags.emplace(fid, tag.second);
    }
}

void
CFAGraph::addScopeAnnotations(const ScopePacked& scope, const std::vector<Expression>& tags) {
    for (Expression tag : tags) {
        __scopeTags.emplace(scope, tag);
    }
}

void
CFAGraph::addContextRules(const ScopePacked& scope, const std::vector<Expression>& rules) {
    for (Expression rule : rules) {
        __contextRules.emplace(scope, rule);
    }
}

void
CFAGraph::addCallConnection(const ScopePacked& scopeFrom, const std::string& functionTo) {
    unsigned int idFuncTo = registerNodeFunction(functionTo);

    __callRelations.insert(CALL_RELATIONS::value_type(scopeFrom, idFuncTo));
}

void
CFAGraph::addParentConnection(const ScopePacked& scope, const std::string& functionParent) {
    __parentFunctionRelations.insert(PARENT_FUNCTION_RELATIONS::value_type(scope, registerNodeFunction(functionParent)));
}

void
CFAGraph::addParentConnection(const ScopePacked& scope, const ScopePacked& scopeParent) {
    __parentScopeRelations.emplace(scope, scopeParent);
}

unsigned int
CFAGraph::registerNodeFunction(const std::string& fname) {
    auto pos = __nodesFunction.left.insert(make_pair(__nodesFunction.size(), fname));

    return pos.first->first;
}

void
CFAGraph::addDependency(const ScopePacked& scope, const ScopePacked& scopeDependency) {
    __dependencyRelations.emplace(scope, scopeDependency);
}

bool
CFAGraph::isDependent(const ScopePacked& scope) const {
    return __dependencyRelations.count(scope) > 0;
}

void
CFAGraph::transmitDependencies(const ScopePacked& scopeTo, const ScopePacked& scopeFrom) {
    auto range = __dependencyRelations.equal_range(scopeFrom);

    std::list<ScopePacked> dependencies;
    for (auto pairI = range.first; pairI != range.second; ++pairI) {
        dependencies.push_back(pairI->second);
    }

    for(auto dep : dependencies) {
        __dependencyRelations.emplace(scopeTo, dep);
    }
}

void
CFAGraph::addScope(CodeScope* scope) {
    boost::format formatScopeBinding("ast_scope_binding(%1%, %2%, \"%3%\").");

    ScopePacked scopeId = __transcend->pack(scope);
    for (int id=0, size = scope->__bindings.size(); id < size; ++id) {
        __outputPrecomputed <<  formatScopeBinding
            % scopeId
            % id
            % scope->__bindings.at(id)
            << endl;
    }
}
