#include "pass/dfapass.h"
#include "analysis/dfagraph.h"

#include "passmanager.h"
#include "clasplayer.h"

#include <boost/format.hpp>

using namespace std;
using namespace xreate::analysis;

namespace xreate{

DFAPass::DFAPass(PassManager* manager)
        : AbstractPass(manager)
            , __context{new xreate::analysis::DFAGraph(manager->clasp)}
            , clasp(manager->clasp)
{}

SymbolNode
DFAPass::process(CodeScope* scope, PassContext context, const std::string& hintBlockDecl){
	const SymbolNode& retActual = AbstractPass::process(scope, context, hintBlockDecl);
	const SymbolPacked& retFormal{0, clasp->pack(scope)};
	__context.graph->addConnection(retFormal, retActual, DFGConnection::STRONG);

	return retFormal;
}

SymbolNode
DFAPass::process(const Expression& expression, PassContext context, const std::string& decl)
{
    ExpressionCache cache;

    if (!decl.empty()){
        cache.result = clasp->pack(context.scope->findSymbol(decl), context.function->getName() + ":" + decl);

    } else if (!expression.tags.empty()) {
        cache.result = __context.graph->createAnonymousSymbol(clasp->pack(context.scope));

    } else {
        cache.result = SymbolTransient{{}, clasp->pack(context.scope)};
    }

    cache.operands.reserve(expression.getOperands().size());
    for (const Expression &op: expression.getOperands()) {
        cache.operands.push_back(process(op, context));
    }

    cache.blocks.reserve(expression.blocks.size());
    for (CodeScope* scope: expression.blocks) {
        cache.blocks.push_back(process(scope, context));
    }

    if (expression.__state == Expression::COMPOUND) {
        processCompoundOp(expression, context, cache, decl);

    } else {
        processElementaryOp(expression, context, cache, decl);
    }

    applyDependencies(expression, context, cache, decl);
    applyStaticAnnotations(expression, context, cache, decl);
    applySignatureAnnotations(expression, context, cache, decl);
    applyInPlaceAnnotations(expression, context, cache, decl);

//TODO Null ad hoc DFG implementation
//    if (expression.isNone()){
//        return SymbolTransient{{Atom<Identifier_t>(Config::get("clasp.nonevalue"))}};
//    }

    //non initialized(SymbolInvalid) value

    return cache.result;
}


void
DFAPass::processElementaryOp(const Expression& expression, PassContext context, DFAPass::ExpressionCache& cache, const std::string& varDecl){
    switch(expression.__state) {
        case Expression::IDENT: {
            std::string identifier = expression.getValueString();

            SymbolNode nodeFrom = AbstractPass::process(expression, context, identifier);
            if (SymbolPacked* nodeTo = boost::get<SymbolPacked>(&cache.result)){
                __context.graph->addConnection(*nodeTo, nodeFrom, DFGConnection::STRONG);

            } else {
     //           cache.result = nodeFrom;
            }

            break;
        }

        default: break;
    }
}

void
DFAPass::processCompoundOp(const Expression& expression, PassContext context, ExpressionCache& cache, const std::string& varDecl){

    switch(expression.op) {
            //apply calling relation
        case Operator::CALL: {
            const string &nameCalleeFunction = expression.getValueString();

            	//TODO implement processFnCall/Uncertain
            list<ManagedFnPtr> variantsCalleeFunction = man->root->getFunctionVariants(nameCalleeFunction);
            if (variantsCalleeFunction.size()!=1) return;
            ManagedFnPtr function= variantsCalleeFunction.front();

            	// set calling relations:
            CodeScope *scopeRemote = function->getEntryScope();
            std::vector<SymbolNode>::iterator nodeActual = cache.operands.begin();
            for (const std::string &identFormal: scopeRemote->__bindings) {
                const Symbol &symbolFormal = scopeRemote->findSymbol(identFormal);
                __context.graph->addConnection(clasp->pack(symbolFormal, nameCalleeFunction + ":" + identFormal), *nodeActual, DFGConnection::WEAK);
                ++nodeActual;
            }

            	//TODO represent RET connection
            break;
        }

            //apply PROTOTYPE relation
        case Operator::MAP: {
            SymbolNode nodeFrom= cache.operands.front();

            SymbolPacked* nodeFromPacked = boost::get<SymbolPacked>(&nodeFrom);
            assert(nodeFromPacked);

            SymbolPacked* nodeTo = boost::get<SymbolPacked>(&cache.result);
            assert(nodeTo);

            __context.graph->addConnection(*nodeTo, *nodeFromPacked, DFGConnection::PROTOTYPE);
            break;
        }

        default: break;
    }
}

void
DFAPass::applyDependencies(const Expression& expression, PassContext context, ExpressionCache& cache, const std::string& decl){
    for (SymbolNode &op: cache.operands) {
        __context.graph->addDependencyConnection(cache.result, op);
    }

    for (SymbolNode &block: cache.blocks) {
        __context.graph->addDependencyConnection(cache.result, block);
    }

    switch(expression.__state) {
        case Expression::IDENT: {
            SymbolNode identSymbol = clasp->pack(Attachments::get<Symbol>(expression), context.function->getName() + ":" + expression.getValueString());
            __context.graph->addDependencyConnection(cache.result, identSymbol);
        }

        default: break;
    }
}

void
DFAPass::applyStaticAnnotations(const Expression& expression, PassContext context, ExpressionCache& cache, const std::string& decl){

    switch(expression.__state) {
        case Expression::NUMBER:
        case Expression::STRING:
            __context.graph->addAnnotation(cache.result, Expression(Atom<Identifier_t>("static")));
            break;

        default: break;
    }
}

void
DFAPass::applySignatureAnnotations(const Expression& expression, PassContext context, ExpressionCache& cache, const std::string& decl){
    if (__signatures.count(expression.op)) {
        const Expression &scheme = __signatures.at(expression.op);

        //TODO add possibility to specifi signature for a particular function
//        if (expression.op == Operator::CALL || expression.op == Operator::INDEX){
//            string caption = expression.getValueString();
//            operands.push_back(process(Expression(move(caption)), context, ""));
//        }

        std::vector<SymbolNode>::iterator arg = cache.operands.begin();
        std::vector<Expression>::const_iterator tag = ++scheme.getOperands().begin();

        while (tag != scheme.getOperands().end()) {
            if (tag->__state != Expression::INVALID) {
                    __context.graph->addAnnotation(*arg, Expression(*tag));
            }

            ++arg; ++tag;
        }

        //TODO represent RET connection
//        Expression retTag = *scheme.getOperands().begin();
//        if (retTag.__state != Expression::INVALID) {
//            __context.graph->addAnnotation(node, move(retTag));
//        }
    }
}

void
DFAPass::applyInPlaceAnnotations(const Expression& expression, PassContext context, ExpressionCache& cache, const std::string& decl){
        // write down in-place expression tags:
    for (pair<std::string, Expression> tag: expression.tags) {
        __context.graph->addAnnotation(cache.result, Expression(tag.second));
    }
}

void
DFAPass::run()
{
    init();
    return AbstractPass::run();
}

void
DFAPass::init()
{
    for (const Expression& scheme: man->root->__dfadata)
    {
        __signatures.emplace(scheme.op, scheme);
    }
}

void DFAPass::finish()
{
    man->clasp->setDFAData(move(__context.graph));
}

template<>
SymbolNode defaultValue(){return SymbolInvalid();};

}

