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

#include<iostream>
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 AbstractPassBase {
    public:
        AbstractPassBase(PassManager* manager);
        virtual void run()=0;
        virtual void finish();

        PassManager* man;
    };

    template<class Output>
    Output defaultValue();
    
    template<>
    void defaultValue<void>();

    template<class Output>
    class SymbolCache: private std::map<Symbol, Output>{
        public:
            bool isCached(const Symbol& symbol){
                return this->count(symbol);
            }

            Output setCachedValue(const Symbol& symbol, Output&& value){
                this->emplace(symbol, value);
                return value;
            }

            Output getCachedValue(const  Symbol& symbol){
                assert(this->count(symbol));
                return this->at(symbol);
            }
    };

    template<>
    class SymbolCache<void>: private std::set<Symbol>{
        public:
        bool isCached(const Symbol& symbol){
            bool result = this->count(symbol) > 0;
            return result;
        }
        void setCachedValue(const Symbol& symbol){
            this->insert(symbol);
        }

        void getCachedValue(const  Symbol& symbol){
        }
    };

    template<class Output>
    class AbstractPass: public AbstractPassBase    {
        
        SymbolCache<Output> __visitedSymbols;

    protected:        
        Output processSymbol(const std::string& ident, PassContext context){
            const Symbol& symbol = context.scope->findSymbol(ident);

            if (__visitedSymbols.isCached(symbol))
                return __visitedSymbols.getCachedValue(symbol);

            
            const Expression& declaration = CodeScope::findDeclaration(symbol);
            if (declaration.isDefined()){
                PassContext context2 = context.updateScope(symbol.scope);
                
                Output&& result = process(declaration, context2, ident);
                return __visitedSymbols.setCachedValue(symbol, std::move(result));
            }
            
            return defaultValue<Output>();
        }
        
        SymbolCache<Output>& getSymbolCache(){
            return __visitedSymbols;
        }
        
        
    public:
        AbstractPass(PassManager* manager)
            : AbstractPassBase(manager){}
            
            //NOTE implement processFnCall
        virtual void processFnCall(ManagedFnPtr function, PassContext context)
            {}

        virtual void processFnCallUncertain(ManagedFnPtr function, PassContext context)
        	{}

        virtual void process(ManagedRulePtr rule)
            {}

        virtual Output process(ManagedFnPtr function){
            const Symbol& symbolFunction{0, function->getEntryScope()};
            
            if (__visitedSymbols.isCached(symbolFunction))
                return __visitedSymbols.getCachedValue(symbolFunction);
                        
            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:
                assert(expression.op != Operator::MAP || (expression.op == Operator::MAP && expression.blocks.size()));

                //TODO there are discrepancies for SWITCH CASE scopes.case body parent scope differs from context.scope.
                for (const Expression &op: expression.getOperands()) {
                    process(op, context);
                }

                //
                for (CodeScope* scope: expression.blocks) {
                    process(scope, context);
                }

                if (expression.op == Operator::CALL) {
                    const std::string &calleeName = expression.getValueString();
                    std::list<ManagedFnPtr> callees = man->root->getFunctionVariants(calleeName);
                    if (callees.size() == 1 && callees.front()){
                    	processFnCall(callees.front(), context);
                    } else {
                    	for (const ManagedFnPtr& callee: callees){
                    		processFnCallUncertain(callee, context);
                    	}
                    }
                }
                break;

                case Expression::IDENT:
                    assert(context.scope);
                    return processSymbol(expression.getValueString(), context);

                default:
                break;
            }

            return defaultValue<Output>();
        }

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

    template<>
    void
    AbstractPass<void>::processSymbol(const std::string& ident, PassContext context);
}
#endif
