#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 {

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

        const Expression expression;
        xreate::analysis::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), 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 xreate::analysis::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));
    }

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