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

#include "xreatemanager.h"
#include "clasplayer.h"

#include <boost/format.hpp>

using namespace std;
namespace xreate { namespace dfa {

class DfaExpressionProcessor {
    std::vector<SymbolNode> operands;
    std::vector<SymbolPacked> blocks;

    const Expression expression;
    SymbolNode result;
    DFAPass * const pass;
    const PassContext context;

public:
    DfaExpressionProcessor(const Expression& expr, SymbolNode resInitial, DFAPass * const p, const PassContext c)
    : expression(expr), result(resInitial), pass(p), context(c) {

        operands.reserve(expression.getOperands().size());
        for (const Expression &op : expression.getOperands()) {
            SymbolAnonymous symbOp(op.id);

            operands.push_back(DfaExpressionProcessor(op, symbOp, pass, context).process());
        }

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

    SymbolNode
    process() {
        if (expression.__state == Expression::COMPOUND) {
            processCompoundOp();

        } else {
            processElementaryOp();
        }

        applySignatureAnnotations();
        applyInPlaceAnnotations();

        return result;
    }

private:
    void
    processElementaryOp() {
        switch (expression.__state) {
            case Expression::IDENT:
            {
                SymbolPacked symbFrom = pass->processSymbol(Attachments::get<Symbol>(expression), context, expression.getValueString());

                SymbolPacked* symbTo = boost::get<SymbolPacked>(&result);
                if (symbTo) {
                    pass->__context.graph->addConnection(*symbTo, SymbolNode(symbFrom), DFGConnection::STRONG);

                } else {
                    result = SymbolNode(symbFrom);
                }

                break;
            }

            default: break;
        }
    }

    void
    processCompoundOp() {
        switch (expression.op) {

                //DEBT provide CALL processing
                //            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 ScopedSymbol symbolFormal{scopeRemote->__identifiers.at(identFormal), versions::VERSION_NONE};
                //
                //                    __context.graph->addConnection(clasp->pack(Symbol{symbolFormal, scopeRemote}, nameCalleeFunction + ":" + identFormal), *nodeActual, DFGConnection::WEAK);
                //                    ++nodeActual;
                //                }
                //
                //                //TODO add RET connection
                //                break;
                //            }

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

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

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

            default: break;
        }
    }

    void
    applySignatureAnnotations() {
        if (pass->__signatures.count(expression.op)) {
            const Expression &scheme = pass->__signatures.at(expression.op);



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

            //Assign scheme RET annotation
            Expression retTag = *scheme.getOperands().begin();
            if (retTag.__state != Expression::INVALID) {
                pass->__context.graph->addAnnotation(result, move(retTag));
            }

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

                ++arg;
                ++tag;
            }

            //  TODO add possibility to have specific 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, ""));
            //        }


        }
    }

    void
    applyInPlaceAnnotations() {
        // write down in-place expression tags:
        for (pair<std::string, Expression> tag : expression.tags) {
            pass->__context.graph->addAnnotation(result, Expression(tag.second));
        }
    }
};


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

SymbolPacked
DFAPass::process(CodeScope* scope, PassContext context, const std::string& hintBlockDecl) {
    const SymbolPacked& symbRet = AbstractPass::process(scope, context, hintBlockDecl);
    return symbRet;
}

SymbolPacked
DFAPass::processSymbol(const Symbol& symbol, PassContext context, const std::string& hintSymbol) {
    const Expression& declaration = CodeScope::getDeclaration(symbol);
    const SymbolPacked& symbPacked = clasp->pack(symbol, hintSymbol);
    DfaExpressionProcessor(declaration, symbPacked, this, context).process();

    return symbPacked;
}

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() {
    clasp->setDFAData(move(__context.graph));
}

} //end of namespace dfa

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

} //xreate namespace



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

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



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

    //   non initialized(SymbolInvalid) value

    //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;
    //    }
    //}
