/* 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: Jan, 2020
*/

#include "transcendtarget.h"
#include "analysis/utils.h"
#include "analysis/resources.h"

#include <boost/format.hpp>
#include <boost/algorithm/string/join.hpp>
using namespace std;

namespace xreate{

void
TranscendPass::process(ManagedFnPtr function){
  boost::format formatFn(analysis::FUNCTION_PREDICATE_TPL);
  boost::format formatFnAnn(analysis::FUNCTION_ANN_PREDICATE_TPL);

  __output << endl << analysis::TRANSCEND_PASS_SECTION << endl;
  __output << formatFn
    % (function->getName())
    << "." << std::endl;

  for(const auto &tagRec: function->getTags()){
    const Expression &tag = tagRec.second;

    TranscendScope* scopeTr = getRootScope();
    list<string> definitions;
    const string& partTagRaw = scopeTr->compile(tag, definitions);
    TranscendScope::rule(__output,
      boost::str(formatFnAnn
        % (function->getName())
        % (partTagRaw)),
      definitions);

    __output << endl;
  }

  return Parent::process(function);
}

void
TranscendPass::process(CodeScope* scope, PassContext context, const std::string &hintBlockDecl) {
  TranscendLayer* transcend = man->transcend;
  boost::format formatScopeAnn(analysis::SCOPE_ANN_PREDICATE_TPL);
  ScopePacked scopeId = transcend->pack(scope);
  TranscendScope* scopeTr = getScope(scope);

  for (const auto& tag : scope->tags){
    list<string> definitions;
    const string& partTagRaw = scopeTr->compile(tag, definitions);
    TranscendScope::rule(__output,
      boost::str(formatScopeAnn
        % scopeId
        % (partTagRaw)),
      definitions);

    __output << endl;
  }

  return Parent::process(scope, context, hintBlockDecl);
}

void
TranscendPass::process(const Expression &expression, PassContext context, const std::string &varDecl){
  boost::format formatVarAnn(analysis::VAR_ANN_PREDICATE_TPL);
  TranscendLayer* transcend = man->transcend;
  TranscendScope* scopeTr = getScope(context.scope);

  for (const auto& tag: expression.tags){
    list<string> body;
    const string& head = scopeTr->compile(tag.second, body);

    TranscendScope::rule(__output,
      boost::str(formatVarAnn
        % Parent::man->transcend->pack(ASTSite{expression.id}).str()
        % head), body);
  }

  return Parent::process(expression, context, varDecl);
}

TranscendScope*
TranscendPass::getScope(const CodeScope * const scope){
  if(__scopes.count(scope)){
    return __scopes.at(scope);
  }

  TranscendScope* unit(new TranscendScope(scope, this));
  __scopes[scope] = unit;
  return unit;
}

void
TranscendPass::finish(){
  Parent::man->transcend->addRawScript(__output.str());
}

std::string
TranscendScope::compile(const Expression &e, std::list<std::string>& definitions){
  switch(e.__state){
    case Expression::COMPOUND: {
      switch (e.op){
        case Operator::VARIANT: {
          const string& predicate = escapePredicate(string(e.getValueString()));

          if(!e.operands.size()){
            return predicate;
          }

          list<string> operands;
          std::transform(e.operands.begin(), e.operands.end(), std::inserter(operands, operands.begin()),
            [this, &definitions](const Expression &e) {
              return this->compile(e, definitions);
            });

          return boost::str(
            boost::format("%1%(%2%)")
              % predicate
              % boost::algorithm::join(operands, ", "));
        }
        default: {
          return analysis::getSite(e, __pass->man->transcend).str();
        }
      }
    }

    case Expression::NUMBER: {
      return to_string((unsigned int)e.getValueDouble());
      break;
    }

    case Expression::STRING: {
      return boost::str(boost::format("\"%1%\"") % e.getValueString());
    }

    case Expression::IDENT: {
      Symbol s = Attachments::get<IdentifierSymbol>(e);
      const string& identSafe = escapeVar(string(e.getValueString()));
      const Expression& symbDefinition = CodeScope::getDefinition(s);
      definitions.push_back(boost::str(boost::format("%1% = %2%")
        % identSafe
        % compile(symbDefinition, definitions)
      ));

      return identSafe;
    }

    case Expression::INVALID: case Expression::BINDING: assert(false);
  }

  assert(false);
  return "";
}

std::string
TranscendScope::escapeVar(std::string&& var){
  assert(var.size());

  var[0] = std::toupper(var[0]);
  return var;
}

std::string
TranscendScope::escapePredicate(std::string&& pred){
  assert(pred.size());

  pred[0] = std::tolower(pred[0]);
  return pred;
}

void
TranscendScope::rule(std::ostringstream& output, const std::string& head, const std::list<std::string>& body){
  output << head;

  const string& partBody = boost::join(body, ";");
  if (!partBody.empty()){
    output << " :- " << partBody;
  }
  output << ".";
}
}