/* 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:   DFAGraph.h
 * Author: pgess <v.melnychenko@xreate.org>
 *
 */

/**
 *  \file dfagraph.h
 *  \brief Data Flow Analysis(DFA) graph data
 *
 */

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

#include <list>

using namespace std;

namespace xreate {namespace dfa {
    struct VisitorNodeHash : public boost::static_visitor<size_t>
    {
        std::size_t operator()(const xreate::SymbolPacked& node) const noexcept
        {
            return 2* (node.identifier +  2 * node.scope + 3 * std::abs(node.version)) + 1;
        }

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

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

namespace xreate {  namespace dfa {

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

class VisitorFormatSymbol: public boost::static_visitor<boost::format> {
public:

    boost::format operator()(const SymbolPacked& node) const {
        //boost::format formatSymbNamed("s(%1%, %2%, %3%)");
        boost::format formatSymbNamed("s%1%%2%");
        return formatSymbNamed % node.identifier /*% node.version*/ % node.scope ;
    }

    boost::format operator()(const SymbolAnonymous& node) const {
        //boost::format formatSymbAnonymous("anon(%1%)");
        boost::format formatSymbAnonymous("a%1%");
        return formatSymbAnonymous % node.id;
    }
};

void
DFACallInstance::print(std::ostringstream& output) const{
    boost::format formatInstance("dfa_callinstance(%1%, %2%).");
    boost::format formatArgs("dfa_callargs(%1%, %2%, %3%).");
    boost::format formatRet("dfa_callret(%1%, %2%).");

    output << formatInstance
        % id % fnName
        << endl;

    for(std::pair<SymbolPacked, SymbolNode> rec: args) {
        SymbolNode argFormal(rec.first);

        output << formatArgs
            % id
            % boost::apply_visitor(VisitorFormatSymbol(), argFormal)
            % boost::apply_visitor(VisitorFormatSymbol(), rec.second)
            << endl;
    }

    output << formatRet
        % id
        % boost::apply_visitor(VisitorFormatSymbol(), retActual)
        << endl;
}

void
DFAGraph::addDependency(const SymbolNode& node, const SymbolNode& subnode){
    __dependencies.emplace(node, subnode);
}

void
DFAGraph::printDependencies(std::ostringstream& output) const{
    for(const SymbolNode& root: __roots){
        printDependency(output, root, root);
    }
}

void
DFAGraph::printDependency(std::ostringstream& output, const SymbolNode& nodeCurrent, const SymbolNode& nodeDependent) const {
    auto range = __dependencies.equal_range(nodeCurrent);
    for (auto it = range.first; it != range.second; ++it){
        if (const SymbolAnonymous* symbAnon = boost::get<SymbolAnonymous>(&it->second)){
            if (!__usedAnonymousSymbols.count(symbAnon->id)){
                printDependency(output, it->second, nodeDependent);
                continue;
            }
        }

        boost::format formatDependency("dfa_depends(%1%, %2%).");

        output << formatDependency
            % boost::apply_visitor(VisitorFormatSymbol(), nodeDependent)
            % boost::apply_visitor(VisitorFormatSymbol(), it->second)
            << endl;

        printDependency(output, it->second, it->second);
    }
}

void
DFAGraph::printInplaceAnnotations(SymbolNode node, const Expression& expression) {
    // write down in-place expression tags:
    boost::format formatBind("bind(%1%, %2%).");

    if (expression.tags.size())
    if (boost::get<SymbolAnonymous>(&node)){
        markSymbolsAsUsed(vector<SymbolNode>({node}));
    }

    for (const pair<std::string, Expression>& tag : expression.tags) {
        for (const string& tagPart: xreate::analysis::compile(tag.second)) {
            __output << formatBind
                % boost::apply_visitor(VisitorFormatSymbol(), node)
                % tagPart
                << endl;
        }
    }
}

void
DFAGraph::printAlias(const SymbolNode& symbolFormal, const SymbolNode& symbActual){
    boost::format formatAlias("dfa_alias(%1%, %2%).");
    __output << formatAlias
        % boost::apply_visitor(VisitorFormatSymbol(), symbolFormal)
        % boost::apply_visitor(VisitorFormatSymbol(), symbActual)
        << endl;
}

void
DFAGraph::printFunctionRet(ManagedFnPtr function, const SymbolNode& symbolRet){
    boost::format formatRet("dfa_fnret(%1%, %2%).");

    __output << formatRet
        % function->getName()
        % boost::apply_visitor(VisitorFormatSymbol(), symbolRet)
        << endl;

    __roots.insert(symbolRet);
}

void
DFAGraph::addCallInstance(DFACallInstance&& instance){
    __callInstances.push_back(std::move(instance));
}

void
DFAGraph::print(std::ostringstream& output) const{
    output << endl << "%\t\tStatic analysis: DFA" << endl;

    //Dependencies
    printDependencies(output);

    //Add generated report
    output << __output.str() << endl;

    //Call instances
    for(const DFACallInstance& instance: __callInstances){
        instance.print(output);
    }

    output << endl;
}

void
DFAGraph::markSymbolsAsUsed(const std::vector<SymbolNode>& usedNodes){
    for (const SymbolNode& node: usedNodes){
        if (const SymbolAnonymous* symbol = boost::get<SymbolAnonymous>(&node)){
            __usedAnonymousSymbols.insert(symbol->id);
        }
    }
}

bool
DFAGraph::isSymbolUsed(const SymbolNode& node) const{
    if (const SymbolAnonymous* symbol = boost::get<SymbolAnonymous>(&node)){
        if (!__usedAnonymousSymbols.count(symbol->id)) {return false;}
    }

    return true;
}

void
DFAGraph::printSymbols(ClaspLayer* clasp){
    std::unordered_set<SymbolNode> symbols;

    for (auto entry: __dependencies){
        if (isSymbolUsed(entry.first)) {symbols.insert(entry.first);}
        if (isSymbolUsed(entry.second)) {symbols.insert(entry.second);}
    }

    for (const SymbolNode& node : symbols) {
        __output << "v(" << boost::apply_visitor(VisitorFormatSymbol(), node) << ").";

        if (const SymbolPacked* symbol = boost::get<SymbolPacked>(&node)){
            __output << " %" << clasp->getHintForPackedSymbol(*symbol);
        }
        __output << endl;
    }
}

}} //end of namespace xreate::dfa
