/* 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 "pass/ipass.h"
#include "xreatemanager.h"
#include "ast.h"

namespace xreate {

/** \brief Holds current position in the %AST during traverse*/
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() {}
};

template<class Output>
Output
defaultValue();

template<>
void
defaultValue<void>();

/** \brief Stores processing results for the 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 the 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 \ref xreate::IPass implementation useful for many passes as the base class*/
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->getFnSpecializations(calleeName);

    //Determined specialization
    if(callees.size() == 1 && callees.front()) {
      return processFnCall(callees.front(), context);
    }
    
    //Several specializations or External Fn
    return processFnCallUncertain(calleeName, callees, context);
  }

  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 which exactly function is invoked.
   */
  virtual Output processFnCallUncertain(const std::string& calleeName, const std::list<ManagedFnPtr>& candidates, PassContext context) {
      return defaultValue<Output>();
  }

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