/* 
 * File:   targetabstract.h
 * Author: pgess
 *
 * Created on July 2, 2016, 1:25 PM
 */

#ifndef TARGETABSTRACT_H
#define TARGETABSTRACT_H
#include "ast.h"

#include <boost/optional.hpp>
#include <map>

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{
public:
    CodeScope* scope;
    
    typename TargetInfo<ConcreteTarget>::Result
    processSymbol(const Symbol& s){
        CodeScope* scope = s.scope;
        typename TargetInfo<ConcreteTarget>::Scope* self = function->getScope(scope);

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

        const Expression& declaration = CodeScope::findDeclaration(s);
        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() {
        if (raw) return *raw;

        raw = process(scope->__body);
        return *raw;
    }
    
    virtual typename TargetInfo<ConcreteTarget>::Result
    process(const Expression& expression)=0;    
    
    Scope(CodeScope* codeScope, Function<ConcreteTarget>* f)
            : scope(codeScope), function(f)  {}
    
    virtual ~Scope(){}

    void 
    bindArg(typename TargetInfo<ConcreteTarget>::Result arg, const std::string& name){
        assert(scope->__identifiers.count(name));
        
        VID id = scope->__identifiers.at(name);
        __bindings[id] = arg;
        
        //reset the result if any:
        raw.reset();
    }
    
protected:
    Function<ConcreteTarget>* function;
    std::map<VID, typename TargetInfo<ConcreteTarget>::Result> __bindings;
    typename boost::optional<typename TargetInfo<ConcreteTarget>::Result> raw;
    
    //ResultType findFunction(const std::string& callee);
};

template<typename ConcreteTarget>
class Function{
    typedef typename TargetInfo<ConcreteTarget>::Result Result;
    
public:
    Function(const ManagedFnPtr& function, Target<ConcreteTarget>* target)
        : man(target), __function(function)  {}
        
    typename TargetInfo<ConcreteTarget>::Scope*
    getScope(CodeScope* scope){
        if (!__scopes.count(scope)){
            typename TargetInfo<ConcreteTarget>::Scope* unit = new typename TargetInfo<ConcreteTarget>::Scope(scope, this);
            __scopes.emplace(scope, std::unique_ptr<typename TargetInfo<ConcreteTarget>::Scope>(unit));
        }

        return __scopes.at(scope).get();
    }
    
    virtual Result
    process(const std::vector<Result>& args)=0;
    
    Target<ConcreteTarget>* man;
    
protected:
    ManagedFnPtr __function;


    std::map<CodeScope*, std::unique_ptr<typename TargetInfo<ConcreteTarget>::Scope>> __scopes;
};

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, std::unique_ptr<ConcreteFunction>(unit));
                return unit;
            }

            return __functions.at(id).get();
        }
        
        AST* ast;
        
    private:
        std::map<unsigned int, std::unique_ptr<ConcreteFunction>> __functions;
};

}}

#endif /* TARGETABSTRACT_H */

