#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) {
        processElementaryOp(expression, context, cache, decl);
        
    } else {
        processCompoundOp(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);
            }
            
            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: {
            const string& identName = expression.getValueString();
            
            SymbolNode identSymbol = clasp->pack(context.scope->findSymbol(identName), context.function->getName() + ":" + identName);
            __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();};

}





/*
if (SymbolTransient* symbT = boost::get<SymbolTransient>(&symbolRet)){
                std::vector<Tag> tags;
                tags.reserve(symbT->tags.size());

                const std::string stmntRetTag = Config::get("clasp.ret.tag");
                std::transform(symbT->tags.begin(), symbT->tags.end(), std::inserter(tags, tags.begin()),
                                [&stmntRetTag](const Expression& e) {
                                        Expression tag(Operator::CALL, {Atom<Identifier_t>(string(stmntRetTag)), e});
                                        return Tag{e, TagModifier::NONE};
                });

                clasp->cfagraph->addFunctionAnnotations(function->getName(), tags);
}
*/