#ifndef ABSTRACTPASS_H
#define ABSTRACTPASS_H
#include "ast.h"
#include "passmanager.h"

#include<iostream>
using namespace std;
namespace xreate
{
    struct PassContext
    {
        CodeScope* scope = 0;
        ManagedFnPtr function;
        ManagedRulePtr rule;
        std::string varDecl;

        PassContext()
        {}

        PassContext&& updateScope(CodeScope* scopeNew) {
            PassContext context2{*this};
            context2.scope = scopeNew;
            return std::move(context2);
        }

        ~PassContext(){}
    };

    class PassManager;

    class AbstractPassBase {
    public:
        AbstractPassBase(PassManager* manager);
        virtual void run()=0;
        virtual void finish();

        PassManager* man;
    };

    template<class Output>
    class AbstractPass: public AbstractPassBase    {
    private:
        std::set<Symbol> __visitedSymbols;

    public:
        AbstractPass(PassManager* manager): AbstractPassBase(manager) {}

            //NOTE implement processFnCall
        virtual void processFnCall(ManagedFnPtr function, PassContext context)
            {}

        virtual void process(ManagedRulePtr rule)
            {}

        virtual Output process(ManagedFnPtr function){
            PassContext context;
            context.function = function;

            return process(function->getEntryScope(), context);
        }

        virtual Output process(CodeScope* scope, PassContext context, const std::string& hintBlockDecl=""){
            context.scope = scope;
            return process(scope->__body, context);
        }

        virtual Output process(const Expression& expression, PassContext context, const std::string& varDecl=""){
            switch (expression.__state) {
                case Expression::COMPOUND:
                for (const Expression &op: expression.getOperands()) {
                    process(op, context);
                }

                assert(expression.op != Operator::MAP || expression.op == Operator::MAP && expression.blocks.size());
                for (CodeScope* scope: expression.blocks) {
                    process(scope, context);
                }

                    /*
                if (expression.op == Operator::CALL) {
                    const std::string &calleeName = expression.getValueString();
                    ManagedFnPtr callee = root->findFunction(calleeName);
                }
                    */
                break;

                case Expression::IDENT:
                    assert(context.scope);

                    std::string ident = expression.getValueString();
                    const Symbol& symbol = context.scope->findSymbol(ident);

                    if (__visitedSymbols.count(symbol)) {break;}
                    __visitedSymbols.insert(symbol);

                    PassContext context2 = context;
                    context2.scope = symbol.scope;
                    if (CodeScope::hasDeclaration(symbol)) {
                        process(CodeScope::findDeclaration(symbol), context2, ident);
                    }

                    break;
            }
        }

        void run() {
            ManagedRulePtr rule = man->root->begin<MetaRuleAbstract>();
            while (rule.isValid()) {
                process(rule);
                ++rule;
            }

            ManagedFnPtr f = man->root->begin<Function>();
            while (f.isValid()) {
                process(f);
                ++f;
            }
        }
    };
}
#endif
