/* 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/.
 * 
 * File:   targetabstract.h
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created on July 2, 2016, 1:25 PM
 */

/**
 * \file    
 * \brief   Compilation targets infrastructure 
 */


#ifndef TARGETABSTRACT_H
#define TARGETABSTRACT_H

#include "ast.h"
#include <boost/optional.hpp>
#include <map>
#include <list>

namespace xreate{ namespace compilation {
    
template <typename ConcreteTarget> 
struct TargetInfo{
    //typedef Result 
    //typedef Function
    //typedef Scope 
};
    
template<typename ConcreteTarget>
class Function;

template<typename ConcreteTarget>
class Target;

template<typename ConcreteTarget>
class Scope{
    typedef typename TargetInfo<ConcreteTarget>::Scope Self;
    
public:
    const CodeScope* scope;
    Function<ConcreteTarget>* function=0;
    
    typename TargetInfo<ConcreteTarget>::Result
    processSymbol(const Symbol& s){
        const CodeScope* scope = s.scope;
        Self* self = function->getScope(scope);

        if (self->__bindings.count(s.identifier))     {
            return self->__bindings[s.identifier];
        }

        const Expression& declaration = CodeScope::getDefinition(s, true);
        if (!declaration.isDefined()){
            assert(false); //for bindings there should be result already
        }
        
        return self->__bindings[s.identifier] = self->process(declaration);
    }
    
    typename TargetInfo<ConcreteTarget>::Result
    processScope() {
        return process(scope->getBody());
    }
    
//    typename TargetInfo<ConcreteTarget>::Result
//    processFunction(typename TargetInfo<ConcreteTarget>::Function* fnRemote, const std::vector<typename TargetInfo<ConcreteTarget>::Result>& args){
//        Scope<ConcreteTarget> scopeRemote = fnRemote->getScope(fnRemote->__function->__entry);
//        
//        if (scopeRemote->raw){
//            return scopeRemote->raw;
//        }
//        
//        return fnRemote->process(args);
//    }
    
    virtual typename TargetInfo<ConcreteTarget>::Result
    process(const Expression& expression)=0;    
    
    Scope(const CodeScope* codeScope, Function<ConcreteTarget>* f)
            : scope(codeScope), function(f)  {}
    
    virtual ~Scope(){}

    void 
    overrideBindings(std::list<std::pair<typename TargetInfo<ConcreteTarget>::Result, std::string>> bindings){
        reset();
        
        for (auto entry: bindings){
            assert(scope->__identifiers.count(entry.second));
            ScopedSymbol id{scope->__identifiers.at(entry.second), versions::VERSION_NONE};
            
            __bindings[id] = entry.first;
        }
    }
    
    void registerChildScope(std::shared_ptr<Self> scope){
        __childScopes.push_back(scope);
    }

    void reset(){
        __bindings.clear();
        __childScopes.clear();
    }
    
protected:
    std::map<ScopedSymbol, typename TargetInfo<ConcreteTarget>::Result> __bindings;
    std::list<std::shared_ptr<Self>> __childScopes;
};

template<typename ConcreteTarget>
class Function{
    typedef typename TargetInfo<ConcreteTarget>::Result Result;
    typedef typename TargetInfo<ConcreteTarget>::Scope ConcreteScope;
    
public:
    Function(const ManagedFnPtr& function, Target<ConcreteTarget>* target)
        : man(target), __function(function)  {}
        
	virtual ~Function(){};
        
    ConcreteScope*
    getScope(const CodeScope* const scope){
        if (__scopes.count(scope)) {
            auto result = __scopes.at(scope).lock();
            
            if (result){
                return result.get();
            }
        }

        std::shared_ptr<ConcreteScope> unit(new ConcreteScope(scope, this));
            
        if (scope->__parent != nullptr){
            getScope(scope->__parent)->registerChildScope(unit);

        } else {
            assert(!__entryScope);
            __entryScope = unit;
        }

        if (!__scopes.emplace(scope, unit).second){
            __scopes[scope] = unit;
        }
        
        return unit.get();
    }
    
    virtual Result
    process(const std::vector<Result>& args)=0;
    
    Target<ConcreteTarget>* man=0;
    ManagedFnPtr __function;

protected:
    std::map<const CodeScope*, std::weak_ptr<ConcreteScope>> __scopes;
    std::shared_ptr<ConcreteScope> __entryScope;
};

/** \brief Similar to xreate::IPass */
template<typename ConcreteTarget>
class Target {
    typedef typename TargetInfo<ConcreteTarget>::Function ConcreteFunction;
    
    public:
        Target(AST* root): ast(root){}
        
        ConcreteFunction*
        getFunction(const ManagedFnPtr& function){
            unsigned int id = function.id();

            if (!__functions.count(id)){
                ConcreteFunction* unit = new ConcreteFunction(function, this);
                __functions.emplace(id, unit);
                return unit;
            }

            return __functions.at(id);
        }
        
        AST* ast;
        virtual ~Target(){
            for (const auto& entry: __functions){
                delete entry.second;
            }
        }
        
    protected:
        std::map<unsigned int, ConcreteFunction*> __functions;
};

}}

#endif /* TARGETABSTRACT_H */
