/* 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){
    std::list < std::pair<typename TargetInfo<ConcreteTarget>::Result, ScopedSymbol>> bindingsSymbols;

    for(auto entry: bindings){
      assert(scope->__identifiers.count(entry.second));
      ScopedSymbol id{scope->__identifiers.at(entry.second), versions::VERSION_NONE};
      bindingsSymbols.push_back(std::make_pair(entry.first, id));
    }

    overrideBindings(bindingsSymbols);
  }

  void
  overrideBindings(std::list<std::pair<typename TargetInfo<ConcreteTarget>::Result, ScopedSymbol>> bindings){
    reset();

    for(auto entry: bindings){
      __bindings[entry.second] = entry.first;
    }
  }

  bool
  isBindingDefined(const ScopedSymbol& id){
    return __bindings.count(id);
  }

  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 */
