/*
 * 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>
 * Created on June 7, 2018, 7:20 PM
 *
 * \file    latereasoningpass.h
 * \brief   latereasoningpass
 */

#ifndef LATEREASONINGPASS_H
#define LATEREASONINGPASS_H

#include "pass/dfapass.h"

namespace xreate { namespace latereasoning {

class LateReasoningScope{
public:
    LateReasoningScope(LateReasoningScope* parent): __parent(parent){ }

    boost::optional<LateSymbolRecognized>
    recognizeIdentifier(const std::string& identifier){

        //Search identifier in the current scope
        if(__identifiers.count(identifier)){
            return make_pair(identifier, __identifiers.at(identifier));
        }

        //Search in the parent scope
        if(__parent){
            return __parent->recognizeIdentifier(identifier);
        }

        return boost::none;
    }

    void
    addIdentifier(std::string idenName, const SymbolPacked& identSymbol){
        __identifiers.emplace(idenName, identSymbol);
    }

private:
    std::map<std::string, SymbolPacked> __identifiers;
    LateReasoningScope *__parent;
};

/**
 * \note Limitation: Produces late annotation with target as a symbol to which annotation is attached
 */
template<class Parent>
class LateReasoningDFAPassDecorator: public Parent{
public:
    LateReasoningDFAPassDecorator(PassManager* manager): Parent(manager){ }

    void
    registerLateScope(CodeScope* scope, LateReasoningScope* scopeLate){
        __dictScopes.emplace(scope, scopeLate);
    }

private:
    LateReasoningScope*
    liftScope(const CodeScope* scope){
        while(scope){
            if(__dictScopes.count(scope)) return __dictScopes.at(scope);
            scope = scope->__parent;
        }

        return nullptr;
    }

    std::list<LateSymbolRecognized>
    recognizeLateIdentifiers(const Expression& expression, LateReasoningScope* scope){
        std::list<LateSymbolRecognized> result;

        switch(expression.op){
            case Operator::CALL:
            {
                for(const auto& op: expression.operands){
                    std::list<LateSymbolRecognized> opResult = recognizeLateIdentifiers(op, scope);
                    result.insert(result.end(), opResult.begin(), opResult.end());
                }

                if(!expression.operands.size()){
                    if(auto symbolRecognized = scope->recognizeIdentifier(expression.getValueString())){
                        result.push_back(*symbolRecognized);
                    }
                }
                break;
            }

            case Operator::NEG:
            {
                assert(expression.operands.size() == 1);

                const Expression &op = expression.operands.at(0);
                std::list<LateSymbolRecognized> opResult = recognizeLateIdentifiers(op, scope);
                result.insert(result.end(), opResult.begin(), opResult.end());
            };

            case Operator::INVALID:
            {
                switch(expression.__state){
                    case Expression::NUMBER:
                        break;

                    default:
                        assert(true);
                }
                break;
            }

            default: break;
        }

        return result;
    }

protected:
    virtual SymbolNode process(const Expression& expression, PassContext context, const std::string& varDecl="") override{
        if(expression.__state == Expression::COMPOUND && expression.op == Operator::SWITCH_LATE){
            //Reserve late scope:
            LateReasoningScope* scopeLate = new LateReasoningScope(liftScope(context.scope));
            CodeScope* scopeBody = expression.blocks.front();
            registerLateScope(scopeBody, scopeLate);
            
            //Assign late identifiers
            for(const std::string& identLate: expression.bindings){
                ScopedSymbol identLateS = scopeBody->getSymbol(identLate);
                SymbolPacked identLateSP = Parent::man->transcend->pack(Symbol{identLateS, scopeBody});
                
                scopeLate->addIdentifier(identLate, identLateSP);
            }
        }
        
        return Parent::process(expression, context, varDecl);
    }
    
    virtual void
    processAnnotations(const Expression& expression,
                       PassContext context,
                       const SymbolNode& ident){

        LateReasoningScope* scopeLate = liftScope(context.scope);

        if(!expression.tags.size() || !scopeLate)
            return Parent::processAnnotations(expression, context, ident);

        for(const std::pair<std::string, Expression>& tag: expression.tags){
            std::list<LateSymbolRecognized> symbols = recognizeLateIdentifiers(tag.second, scopeLate);
            if(!symbols.size()){
                //Standard compilation
                Parent::graph->printInplaceAnnotation(ident, tag.second);

            } else{
                //Late compilation
                std::list<std::string> domains;
                for(const auto& symbol: symbols){
                    Symbol symbolUnpacked = Parent::man->transcend->unpack(symbol.second);
                    ExpandedType typSymbol = Parent::man->root->getType(CodeScope::getDefinition(symbolUnpacked));
                    assert(typSymbol->__operator == TypeOperator::SLAVE);

                    domains.push_back(typSymbol->__valueCustom);
                }

                Parent::graph->printLateAnnotation(ident, tag.second, symbols, domains);
            }
        }
    }

private:
    std::unordered_map<const CodeScope*, LateReasoningScope*> __dictScopes;
};

}}

#endif /* LATEREASONINGPASS_H */

