/* 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>
 *
 * dfapass.cpp
 */

/**
 * \file    dfapass.h
 * \brief   Data Flow Analysis(DFA)
 */

//DEBT DFA represent VersionaPass in declarative form using applyDependencies
//    applyDependencies(expression, context, cache, decl);

//DEBT DFA prepare static annotations and represent InterpretationPass in declarative form
//    applyStaticAnnotations(expression, context, cache, decl);

//DEBT DFA Eliminate dfa schemes

#include "pass/dfapass.h"
#include "xreatemanager.h"
#include "transcendlayer.h"

#include <boost/format.hpp>
#include <boost/variant/variant.hpp>

using namespace std;
namespace xreate {namespace dfa {

DFAPass::DFAPass(PassManager* manager)
: AbstractPass(manager)
, graph{new DFAGraph()}

, transcend(manager->transcend) { }

void
DFAPass::processCallInstance(const Expression& expr, PassContext context, const SymbolNode& result) {
    const string &nameCalleeFunction=expr.getValueString();

    //TODO implement processFnCall/Uncertain
    list<ManagedFnPtr> variantsCalleeFunction=man->root->getFunctionSpecializations(nameCalleeFunction);

    vector<SymbolNode> operands;
    operands.reserve(expr.getOperands().size());

    for(const Expression& arg : expr.getOperands()) {
        operands.push_back(process(arg, context));
    }

    //Set calling relations:
    DFACallInstanceType type=variantsCalleeFunction.size()>1?WEAK:STRONG;
    for(ManagedFnPtr function : variantsCalleeFunction) {
        CodeScope *scopeRemote=function->getEntryScope();
        DFACallInstance callInstance;
        callInstance.fnName=function->getName();
        callInstance.type=type;

        std::vector<SymbolNode>::const_iterator nodeActual=operands.begin();
        for(const std::string &identFormal : scopeRemote->__bindings) {
            const ScopedSymbol symbolFormal{scopeRemote->__identifiers.at(identFormal), versions::VERSION_NONE};
            SymbolPacked symbolFormalPacked=transcend->pack(Symbol{symbolFormal, scopeRemote}, nameCalleeFunction+":"+identFormal);
            callInstance.args.push_back(std::make_pair(symbolFormalPacked, *nodeActual));

            ++nodeActual;
        }

        callInstance.retActual=result;
        SymbolNode retFormal=SymbolNode(transcend->pack(Symbol{ScopedSymbol::RetSymbol, scopeRemote}, nameCalleeFunction+":[ret]"));

        graph->addCallInstance(std::move(callInstance));
    }
}

void
DFAPass::processDependencies(const SymbolNode& node, const Expression& expression, PassContext context, ProcessingCache& cache) {
    cache.operands.reserve(expression.getOperands().size());
    for(const Expression &op : expression.getOperands()) {
        const SymbolNode& subnodeOperand=process(op, context);

        cache.operands.push_back(subnodeOperand);
        graph->addDependency(node, subnodeOperand);
    }

    cache.blocks.reserve(expression.blocks.size());
    for(CodeScope* block : expression.blocks) {
        const SymbolNode& subnodeBlock=process(block, context);

        cache.blocks.push_back(subnodeBlock);
        graph->addDependency(node, subnodeBlock);
    }
}

void
DFAPass::processAnnotations(const Expression& expression, PassContext context, const SymbolNode& ident){
    for (const pair<std::string, Expression>& tag : expression.tags){
        graph->printInplaceAnnotation(ident, tag.second);
    }
}

SymbolNode
DFAPass::process(const Expression& expression, PassContext context, const std::string& varDecl) {
    SymbolNode result;

    if(Attachments::exists<SymbolAlias>(expression)){
        Symbol varSymbol=Attachments::get<SymbolAlias>(expression);
        result=transcend->pack(varSymbol, varDecl);

    } else if(expression.__state==Expression::IDENT&&expression.tags.size()==0){
        Symbol varSymbol=Attachments::get<IdentifierSymbol>(expression);
        result=transcend->pack(varSymbol, expression.getValueString());

    } else {
        result=SymbolAnonymous{expression.id};
    }

    processAnnotations(expression, context, result);

    switch(expression.__state) {
    case Expression::COMPOUND:    {
        switch(expression.op) {
        case Operator::CALL:        {
            processCallInstance(expression, context, result);
            break;
        }

        case Operator::IF:        {
            const SymbolNode& scopeA=process(expression.blocks.front(), context, "ifTrue" + std::to_string(expression.id));
            const SymbolNode& scopeB=process(expression.blocks.back(), context, "ifFalse" + std::to_string(expression.id));
            const SymbolNode& condition=process(expression.operands.at(0), context);

            graph->addDependency(result, scopeA);
            graph->addDependency(result, scopeB);
            graph->addDependency(result, condition);
            graph->printWeakAlias(result, scopeA);
            graph->printWeakAlias(result, scopeB);
            break;
        }

        case Operator::SWITCH:
        case Operator::SWITCH_VARIANT:        {
            for(CodeScope* block : expression.blocks) {
                const SymbolNode& subnodeBlock=process(block, context, "case"+to_string(block->getBody().id));

                graph->addDependency(result, subnodeBlock);
                graph->printWeakAlias(result, subnodeBlock);
            }

            const SymbolNode& condition=process(expression.operands.at(0), context);
            graph->addDependency(result, condition);
            break;
        }

        default:        {
            ProcessingCache cache;
            processDependencies(result, expression, context, cache);
            break;
        }
        }
        break;
    }

    case Expression::IDENT:    {
        SymbolNode symbIdent=AbstractPass<SymbolNode>::process(expression, context, varDecl);

        if(!(result==symbIdent)){
            graph->addDependency(result, symbIdent);
            graph->printAlias(result, symbIdent);
        }

        break;
    }

    case Expression::NUMBER:
    case Expression::STRING:    {
        break;
    }

    case Expression::INVALID:
    case Expression::BINDING:    {
        assert(false);
        break;
    }
    }

    return result;
}

SymbolNode
DFAPass::process(CodeScope* scope, PassContext context, const std::string& hintBlockDecl) {
    if (!hintBlockDecl.empty()) {
        Symbol symbRet{ScopedSymbol::RetSymbol, scope};
        transcend->pack(symbRet, hintBlockDecl + ":[ret]");
    }

    for(const std::string& binding : scope->__bindings) {
        Symbol bindingSymb{scope->getSymbol(binding), scope};
        SymbolPacked bindingSymbP=transcend->pack(bindingSymb, binding);

        getSymbolCache().setCachedValue(bindingSymb, SymbolNode(bindingSymbP));
        processAnnotations(scope->getDefinition(bindingSymb), context, SymbolNode(bindingSymbP));
    }

    return AbstractPass<SymbolNode>::process(scope, context, hintBlockDecl);
}

SymbolNode
DFAPass::process(ManagedFnPtr function) {
    transcend->pack(Symbol{ScopedSymbol::RetSymbol, function->getEntryScope()}, function->getName()+to_string(function.id())+":[ret]");
    SymbolNode result=AbstractPass<SymbolNode>::process(function);

    graph->printFunctionRet(function, result);
    return result;
}

void
DFAPass::finish() {
    transcend->registerReport(graph);

    //Declare symbols:
    graph->printSymbols(transcend);

    AbstractPass::finish();
}

} //end of namespace dfa

template<>
SymbolNode
defaultValue() {
    assert(false);
}

} //end of xreate namespace

/**
 * \class xreate::dfa::DFAPass
 * \details Provides DFA, important analysis for reasoning. Iterates over AST and stores collected data in DFAGraph
 */
