/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * Author: pgess <v.melnychenko@xreate.org>
 */

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

#include<iostream>
namespace xreate
{
    /** \brief Holds current position in %AST while traversing*/
    struct PassContext
    {
        const CodeScope* scope = 0;
        ManagedFnPtr function;
        ManagedRulePtr rule;
        std::string varDecl;

        PassContext()
        {}

        PassContext updateScope(const CodeScope* scopeNew) {
            PassContext context2{*this};
            context2.scope = scopeNew;
            return context2;
        }

        ~PassContext(){}
    };

    /** \brief Base class for all passes to inherit */
    class IPass {
    public:
        IPass(PassManager* manager);
        virtual ~IPass(){}
        
        /** \brief Executes pass */
        virtual void run()=0;
        
        /** \brief Finalizes pass. Empty by default*/
        virtual void finish();

        PassManager* man;
    };

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

    /** \brief Stores processing results for already visited nodes */
    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)[symbol] = value;
                return value;
            }

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

    /** \brief Set of already visited nodes */
    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){
        }
    };

/** \brief Minimal useful IPass implementation*/    
template<class Output>
class AbstractPass: public IPass    {
    SymbolCache<Output> __visitedSymbols;

protected:        
    virtual Output processSymbol(const Symbol& symbol, PassContext context, const std::string& hintSymbol=""){
        if (__visitedSymbols.isCached(symbol))
            return __visitedSymbols.getCachedValue(symbol);

        const Expression& declaration = CodeScope::getDefinition(symbol, true);
        if (declaration.isDefined()){
            PassContext context2 = context.updateScope(symbol.scope);

            Output&& result = process(declaration, context2, hintSymbol);
            return __visitedSymbols.setCachedValue(symbol, std::move(result));
        }

        return defaultValue<Output>();
    }

    Output processExpressionCall(const Expression& expression, PassContext context){
        const std::string &calleeName = expression.getValueString();
        std::list<ManagedFnPtr> callees = man->root->getFunctionSpecializations(calleeName);
        if (callees.size() == 1 && callees.front()){
            return processFnCall(callees.front(), context);

        } else {
            for (const ManagedFnPtr& callee: callees){
                processFnCallUncertain(callee, context);
            }

            return defaultValue<Output>();
        }
    }

    SymbolCache<Output>& getSymbolCache(){
        return __visitedSymbols;
    }

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

        /** \brief Processes function invocation instruction */
    virtual Output processFnCall(ManagedFnPtr functionCallee, PassContext context){
        return defaultValue<Output>();
    }

        /** \brief Processes function invocation instruction in uncertain cases
         * \details Executed when it's impossible statically determine exact function invocation. 
         *  In this case get executed for all possible candidates 
         */
    virtual void processFnCallUncertain(ManagedFnPtr functionCallee, PassContext context)
    {}

    /** \brief Processes Logic Rule */
    virtual void process(ManagedRulePtr rule)
    {}

    /** \brief Processes Function */
    virtual Output process(ManagedFnPtr function)
    {
        PassContext context;
        context.function = function;

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

    /** \brief Processes single CodeScope */
    virtual Output process(CodeScope* scope, PassContext context, const std::string& hintBlockDecl=""){
        context.scope = scope;

        return processSymbol(Symbol{ScopedSymbol::RetSymbol, scope}, context);
    }
    
    //TODO expose Symbol instead of varDecl. Required by DFAPass. 
    /** \brief Processes single Expression */
    virtual Output process(const Expression& expression, PassContext context, const std::string& varDecl=""){
        if (expression.__state == Expression::IDENT){
            assert(context.scope);
            return processSymbol(Attachments::get<IdentifierSymbol>(expression), context, expression.getValueString());
        }

        assert(false);
        return defaultValue<Output>();
    }

    /** \brief Executes AST traverse */
    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 Symbol& symbol, PassContext context, const std::string& hintSymbol);

template<>
void
AbstractPass<void>::process(const Expression& expression, PassContext context, const std::string& hintSymbol);

}    
#endif
