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

/**
 * \file    compilepass.h
 * \brief   Main compilation routine. See \ref xreate::CompilePass
 */

#include "compilepass.h"
#include "transcendlayer.h"
#include "ast.h"
#include "llvmlayer.h"

#include "compilation/decorators.h"
#include "compilation/pointers.h"
#include "analysis/typeinference.h"
#include "compilation/control.h"
#include "compilation/demand.h"
#include "analysis/resources.h"
#ifdef XREATE_ENABLE_EXTERN
  #include "ExternLayer.h"
#endif

#include "compilation/containers.h"
#include "compilation/containers/arrays.h"

#ifndef XREATE_CONFIG_MIN
  #include "query/containers.h"

  #include "pass/versionspass.h"
  #include "compilation/targetinterpretation.h"
#endif

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

using namespace std;
using namespace llvm;
using namespace xreate::typehints;
using namespace xreate::containers;

namespace xreate{
namespace compilation{
#define DEFAULT(x) (hintAlias.empty()? x: hintAlias)

std::string
BasicBruteFunction::prepareName() {
  AST* ast = IBruteFunction::pass->man->root;

  string name = ast->getFnSpecializations(__function->__name).size() > 1 ?
    __function->__name + std::to_string(__function.id()) :
    __function->__name;

  return name;
}

std::vector<llvm::Type*>
BasicBruteFunction::prepareSignature() {
  CodeScope* entry = __function->__entry;

  return getScopeSignature(entry);
}

llvm::Type*
BasicBruteFunction::prepareResult() {
  LLVMLayer* llvm = IBruteFunction::pass->man->llvm;
  AST* ast = IBruteFunction::pass->man->root;
  CodeScope* entry = __function->__entry;
  const Expression retE = entry->__declarations.at(ScopedSymbol::RetSymbol);
  const ExpandedType& retT = ast->getType(retE);
  return llvm->toLLVMType(retT, retE);
}

llvm::Function::arg_iterator
BasicBruteFunction::prepareBindings() {
    CodeScope* entry = __function->__entry;
    IBruteScope* entryCompilation = IBruteFunction::getBruteScope(entry);
    llvm::Function::arg_iterator fargsI = IBruteFunction::raw->arg_begin();

    for (std::string &arg : entry->__bindings) {
        ScopedSymbol argid{entry->__identifiers[arg], versions::VERSION_NONE};

        entryCompilation->bindArg(&*fargsI, argid);
        fargsI->setName(arg);
        ++fargsI;
    }

    return fargsI;
}

void
BasicBruteFunction::applyAttributes(){}

IBruteScope::IBruteScope(const CodeScope * const codeScope, IBruteFunction* f, CompilePass* compilePass)
: pass(compilePass), function(f), scope(codeScope), lastBlockRaw(nullptr) { }

llvm::Value*
BruteFnInvocation::operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl) {
  if (__calleeTy) {
    auto argsFormalT = __calleeTy->params();
    size_t sizeArgsF = __calleeTy->getNumParams();
    assert(args.size() >= sizeArgsF);
    assert(__calleeTy->isVarArg() || args.size() == sizeArgsF);

    auto argFormalT = argsFormalT.begin();
    for(size_t argId = 0; argId < args.size(); ++argId){
      if(argFormalT != argsFormalT.end()){
        args[argId] = typeinference::doAutomaticTypeConversion(
        args.at(argId), *argFormalT, llvm->irBuilder);
        ++argFormalT;
      }
    }
  }

  //Do not name function call that returns Void.
  std::string hintName = (!__calleeTy->getReturnType()->isVoidTy()) ? hintDecl : "";
  return llvm->irBuilder.CreateCall(__calleeTy, __callee, args, hintName);
}

llvm::Value*
HiddenArgsFnInvocation::operator() (std::vector<llvm::Value *>&& args, const std::string& hintDecl) {
  args.insert(args.end(), __args.begin(), __args.end());
  return __parent->operator ()(std::move(args), hintDecl);
}

class CallStatementInline : public IFnInvocation{
public:

    CallStatementInline(IBruteFunction* caller, IBruteFunction* callee, LLVMLayer* l)
    : __caller(caller), __callee(callee), llvm(l) { }

    llvm::Value* operator()(std::vector<llvm::Value *>&& args, const std::string& hintDecl) {
        return nullptr;
    }

private:
    IBruteFunction* __caller;
    IBruteFunction* __callee;
    LLVMLayer* llvm;

    bool
    isInline() {
        // Symbol ret = Symbol{0, function->__entry};
        // bool flagOnTheFly = SymbolAttachments::get<IsImplementationOnTheFly>(ret, false);
        //TODO consider inlining
        return false;
    }
} ;

BasicBruteScope::BasicBruteScope(const CodeScope * const codeScope, IBruteFunction* f, CompilePass* compilePass)
: IBruteScope(codeScope, f, compilePass) { }

llvm::Value*
BasicBruteScope::processSymbol(const Symbol& s, std::string hintRetVar) {
  Expression declaration = CodeScope::getDefinition(s);
  const CodeScope* scopeExternal = s.scope;
  IBruteScope* scopeBruteExternal = IBruteScope::function->getBruteScope(scopeExternal);
  assert(scopeBruteExternal->lastBlockRaw);

  llvm::Value* resultRaw;
  llvm::BasicBlock* blockOwn = pass->man->llvm->irBuilder.GetInsertBlock();

  if (scopeBruteExternal->lastBlockRaw == blockOwn) {
    resultRaw = scopeBruteExternal->process(declaration, hintRetVar);
    scopeBruteExternal->lastBlockRaw = lastBlockRaw =
      pass->man->llvm->irBuilder.GetInsertBlock();

  } else {
    pass->man->llvm->irBuilder.SetInsertPoint(scopeBruteExternal->lastBlockRaw);
    resultRaw = scopeBruteExternal->processSymbol(s, hintRetVar);
    pass->man->llvm->irBuilder.SetInsertPoint(blockOwn);
  }

  return resultRaw;
}

IFnInvocation*
BasicBruteScope::findFunction(const Expression& opCall) {
    const std::string& calleeName = opCall.getValueString();
    LLVMLayer* llvm = pass->man->llvm;
    const std::list<ManagedFnPtr>& specializations = pass->man->root->getFnSpecializations(calleeName);

#ifdef XREATE_ENABLE_EXTERN
  //if no specializations registered - check external function
  if (specializations.size() == 0) {
      llvm::Function* external = llvm->layerExtern->lookupFunction(calleeName);

      llvm::outs() << "Debug/External function: " << calleeName;
      external->getType()->print(llvm::outs(), true);
      llvm::outs() << "\n";

      return new BruteFnInvocation(external, llvm);
  }
#endif

    //There should be only one specialization without any valid guards at this point
    return new BruteFnInvocation(pass->getBruteFn(
    pass->man->root->findFunction(calleeName))->compile(),
        llvm);
}

//DISABLEDFEATURE transformations
//    if (pass->transformations->isAcceptable(expr)){
//        return pass->transformations->transform(expr, result, ctx);
//    }

llvm::Value*
BasicBruteScope::process(const Expression& expr, const std::string& hintAlias, const TypeAnnotation& expectedT) {
  llvm::Value *leftRaw;
  llvm::Value *rightRaw;
  LLVMLayer& l = *pass->man->llvm;
  Context ctx{this, function, pass};
  xreate::compilation::ControlIR controlIR = xreate::compilation::ControlIR({this, function, pass});

  switch (expr.op) {
  case Operator::ADD:
  case Operator::SUB: case Operator::MUL: case Operator::MOD:
  case Operator::DIV: case Operator::EQU: case Operator::LSS:
  case Operator::GTR: case Operator::NE: case Operator::LSE:
  case Operator::GTE:
    assert(expr.__state == Expression::COMPOUND);
    assert(expr.operands.size() == 2);

    leftRaw = process(expr.operands.at(0));
    rightRaw = process(expr.operands.at(1));

    break;

  default:;
  }

  switch (expr.op) {
  case Operator::AND:
  {
    assert(expr.operands.size());
    llvm::Value* resultRaw = process(expr.operands.at(0));
    if (expr.operands.size() == 1) return resultRaw;

    for(size_t i=1; i< expr.operands.size()-1; ++i){
      resultRaw = l.irBuilder.CreateAnd(resultRaw, process(expr.operands.at(i)));
    }
    return l.irBuilder.CreateAnd(resultRaw, process(expr.operands.at(expr.operands.size()-1)), hintAlias);
  }

  case Operator::OR:
  {
    assert(expr.operands.size());
    llvm::Value* resultRaw = process(expr.operands.at(0));
    if (expr.operands.size() == 1) return resultRaw;

    for(size_t i=1; i< expr.operands.size()-1; ++i){
      resultRaw = l.irBuilder.CreateOr(resultRaw, process(expr.operands.at(i)));
    }
    return l.irBuilder.CreateOr(resultRaw, process(expr.operands.at(expr.operands.size()-1)), hintAlias);
  }

  case Operator::ADD:
  {
    return l.irBuilder.CreateAdd(leftRaw, rightRaw, hintAlias);
  }

  case Operator::SUB:
    return l.irBuilder.CreateSub(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::MUL:
    return l.irBuilder.CreateMul(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::DIV:
    if (leftRaw->getType()->isIntegerTy()) return l.irBuilder.CreateSDiv(leftRaw, rightRaw, hintAlias);
    if (leftRaw->getType()->isFloatingPointTy()) return l.irBuilder.CreateFDiv(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::MOD:{
    return  l.irBuilder.CreateSRem(leftRaw, rightRaw, hintAlias);
  }

  case Operator::EQU: {
    if (leftRaw->getType()->isIntegerTy()) return l.irBuilder.CreateICmpEQ(leftRaw, rightRaw, hintAlias);
    if (leftRaw->getType()->isFloatingPointTy()) return l.irBuilder.CreateFCmpOEQ(leftRaw, rightRaw, hintAlias);

    const ExpandedType& leftT = pass->man->root->getType(expr.operands[0]);
    const ExpandedType& rightT = pass->man->root->getType(expr.operands[1]);

    if(leftT->__operator == TypeOperator::VARIANT && rightT->__operator == TypeOperator::VARIANT){
      llvm::Type* selectorT = llvm::cast<llvm::StructType>(leftRaw->getType())->getElementType(0);
      llvm::Value* leftUnwapped = typeinference::doAutomaticTypeConversion(leftRaw, selectorT, l.irBuilder);
      llvm::Value* rightUnwapped = typeinference::doAutomaticTypeConversion(rightRaw, selectorT, l.irBuilder);
      return l.irBuilder.CreateICmpEQ(leftUnwapped, rightUnwapped, hintAlias);
    }
    break;
  }

  case Operator::NE:
    return l.irBuilder.CreateICmpNE(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::LSS:
    return l.irBuilder.CreateICmpSLT(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::LSE:
    return l.irBuilder.CreateICmpSLE(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::GTR:
    return l.irBuilder.CreateICmpSGT(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::GTE:
    return l.irBuilder.CreateICmpSGE(leftRaw, rightRaw, hintAlias);
    break;

  case Operator::NEG:
  {
    leftRaw = process(expr.operands[0]);
      ExpandedType leftTy = pass->man->root->getType(expr.operands[0]);

      if (leftTy->__value == TypePrimitive::Bool){
        return l.irBuilder.CreateNot(leftRaw, hintAlias);
      } else {
        return l.irBuilder.CreateNeg(leftRaw, hintAlias);
      }
      break;
  }

  case Operator::CALL:
  {
    assert(expr.__state == Expression::COMPOUND);
    shared_ptr<IFnInvocation> callee(findFunction(expr));
    const std::string& nameCallee = expr.getValueString();

    //prepare arguments
    std::vector<llvm::Value *> args;
    args.reserve(expr.operands.size());

    std::transform(expr.operands.begin(), expr.operands.end(), std::inserter(args, args.end()),
        [this](const Expression & operand) {
            return process(operand);
        }
    );

    return (*callee)(move(args), hintAlias);
  }

  case Operator::IF:
  {
    return controlIR.compileIf(expr, hintAlias);
  }

  case Operator::SWITCH:
  {
    return controlIR.compileSwitch(expr, hintAlias);
  }

  case Operator::LOGIC_AND:
  {
    assert(expr.operands.size() == 1);
    return process(expr.operands[0]);
  }

  case Operator::LIST: //init record or array
  {
    ExpandedType exprT = l.ast->getType(expr, expectedT);
    TypesHelper helper(pass->man->llvm);

    enum {RECORD, ARRAY} kind;
    if (helper.isArrayT(exprT)){
      kind = ARRAY;

    } else if (helper.isRecordT(exprT)){
      kind = RECORD;
    } else {
      assert(false && "Inapproriate type");
    }

    #ifdef XREATE_ENABLE_EXTERN
    if (exprT->__operator  == TypeOperator::ALIAS){
        if (l.layerExtern->isArrayType(exprT->__valueCustom)){
            flagIsArray = true;
            break;
        }

        if (l.layerExtern->isRecordType(exprT->__valueCustom)){
            flagIsArray = false;
            break;
        }

        assert(false && "Inapproriate external type");
    }
    #endif

    switch(kind){
      case RECORD:{
        const std::vector<string> fieldsFormal = helper.getRecordFields(exprT);
        containers::RecordIR irRecords(ctx);
        llvm::StructType *recordTRaw = llvm::cast<llvm::StructType>(l.toLLVMType(exprT));
        llvm::Value *resultRaw = irRecords.init(recordTRaw);
        return irRecords.update(resultRaw, exprT, expr);
      }

      case ARRAY: {
        std::unique_ptr<containers::IContainersIR> containerIR(
          containers::IContainersIR::create(expr, expectedT, ctx));
        llvm::Value* aggrRaw = containerIR->init(hintAlias);
        return containerIR->update(aggrRaw, expr, hintAlias);
      }
    }
    break;
  };

  case Operator::LIST_RANGE:
  {
    containers::RangeIR compiler(ctx);
    const ExpandedType& aggrT = pass->man->root->getType(expr);

    return compiler.init(expr, aggrT, hintAlias);
  };

  case Operator::MAP:
  {
    assert(expr.blocks.size());

    containers::ImplementationType implType = containers::IContainersIR::getImplementation(expr, pass->man->root);

    switch(implType){
      case containers::ImplementationType::SOLID: {
        ExpandedType exprT = pass->man->root->getType(expr, expectedT);
        ArrayHint hint = find(expr, ArrayHint{});

        containers::ArrayIR compiler(exprT, hint, ctx);
        return compiler.operatorMap(expr, hintAlias);
      }

      case containers::ImplementationType::ON_THE_FLY:{
        FlyHint hint = find<FlyHint>(expr, {});
        containers::FlyIR compiler(hint, ctx);

        return compiler.operatorMap(expr, hintAlias);
      }

      default:
        break;
    }
    assert(false && "Operator MAP does not support this container impl");
    return nullptr;
  };

  case Operator::FOLD:
  {
    return controlIR.compileFold(expr, hintAlias);
  };

  case Operator::FOLD_INF:
  {
    return controlIR.compileFoldInf(expr, hintAlias);
  };

  case Operator::INDEX:
  {
    assert(expr.operands.size() > 1);

    const Expression& aggrE = expr.operands[0];
    const ExpandedType& aggrT = pass->man->root->getType(aggrE);
    llvm::Value* aggrRaw = process(aggrE);
    switch (aggrT->__operator) {
    case TypeOperator::RECORD:
    {
      list<string> fieldsList;
      for(auto opIt = ++expr.operands.begin(); opIt!=expr.operands.end(); ++opIt){
        fieldsList.push_back(getIndexStr(*opIt));
      }

      return controlIR.compileStructIndex(aggrRaw, aggrT, fieldsList);
    };

    case TypeOperator::ARRAY:
    {
      std::vector<llvm::Value*> indexes;
      std::transform(++expr.operands.begin(), expr.operands.end(), std::inserter(indexes, indexes.end()),
          [this] (const Expression & op) {
              return process(op);
          }
      );

      std::unique_ptr<containers::IContainersIR> containersIR(
        containers::IContainersIR::create(aggrE, expectedT, ctx)
      );

      containers::ArrayIR* arraysIR = static_cast<containers::ArrayIR*>(containersIR.get());
      return arraysIR->get(aggrRaw, indexes, hintAlias);
    };

    default:
        assert(false);
    }
  };

  case Operator::CALL_INTRINSIC:
  {
//        const std::string op = expr.getValueString();
//
//        if (op == "copy") {
//            llvm::Value* result = process(expr.getOperands().at(0));
//
//            auto decoratorVersions = Decorators<VersionsScopeDecoratorTag>::getInterface(this);
//            llvm::Value* storage = decoratorVersions->processIntrinsicInit(result->getType());
//            decoratorVersions->processIntrinsicCopy(result, storage);
//
//            return l.irBuilder.CreateLoad(storage, hintAlias);
//        }

      assert(false && "undefined intrinsic");
  }

  case Operator::QUERY:
  case Operator::QUERY_LATE:
  {
    assert(false && "Should be processed by interpretation");
  }

  case Operator::VARIANT:
  {
    const ExpandedType& typResult = pass->man->root->getType(expr);
    llvm::Type* typResultRaw = l.toLLVMType(typResult);
    llvm::Type* typIdRaw = llvm::cast<llvm::StructType>(typResultRaw)->getElementType(0);

    uint64_t id = expr.getValueDouble();
    llvm::Value* resultRaw = llvm::UndefValue::get(typResultRaw);
    resultRaw = l.irBuilder.CreateInsertValue(resultRaw, llvm::ConstantInt::get(typIdRaw, id), llvm::ArrayRef<unsigned>({0}));

    const ExpandedType& typVariant = ExpandedType(typResult->__operands.at(id));
    llvm::Type* typVariantRaw = l.toLLVMType(typVariant);
    llvm::Value* variantRaw = llvm::UndefValue::get(typVariantRaw);
    assert(expr.operands.size() == typVariant->__operands.size() && "Wrong variant arguments count");
    if (!typVariant->__operands.size()) return resultRaw;

    for (unsigned int fieldId = 0; fieldId < expr.operands.size(); ++fieldId) {
      const ExpandedType& typField = ExpandedType(typVariant->__operands.at(fieldId));
      Attachments::put<TypeInferred>(expr.operands.at(fieldId), typField);
      llvm::Value* fieldRaw = process(expr.operands.at(fieldId));
      assert(fieldRaw);

      variantRaw = l.irBuilder.CreateInsertValue(variantRaw, fieldRaw, llvm::ArrayRef<unsigned>({fieldId}));
    }

    llvm::Type* typStorageRaw = llvm::cast<llvm::StructType>(typResultRaw)->getElementType(1);
    llvm::Value* addrAsStorage = l.irBuilder.CreateAlloca(typStorageRaw);
    llvm::Value* addrAsVariant =  l.irBuilder.CreateBitOrPointerCast(addrAsStorage, typVariantRaw->getPointerTo());

    l.irBuilder.CreateStore(variantRaw, addrAsVariant);
    llvm::Value* storageRaw = l.irBuilder.CreateLoad(typStorageRaw, addrAsStorage);
    resultRaw = l.irBuilder.CreateInsertValue(resultRaw, storageRaw, llvm::ArrayRef<unsigned>({1}));

    return resultRaw;
  }

  case Operator::SWITCH_VARIANT:
  {
    return controlIR.compileSwitchVariant(expr, hintAlias);
  }

  case Operator::SWITCH_LATE:
  {
    assert(false && "Instruction's compilation should've been redirected to interpretation");
    return nullptr;
  }

  case Operator::SEQUENCE:
  {
    return controlIR.compileSequence(expr);
  }

  case Operator::UNDEF:
  {
    llvm::Type* typExprUndef = l.toLLVMType(pass->man->root->getType(expr, expectedT));
    return llvm::UndefValue::get(typExprUndef);
  }

  case Operator::UPDATE:
  {
    TypesHelper helper(pass->man->llvm);
    containers::RecordIR irRecords(ctx);

    const Expression& aggrE = expr.operands.at(0);
    const Expression& updE = expr.operands.at(1);
    const ExpandedType& aggrT = pass->man->root->getType(aggrE);
    llvm::Value* aggrRaw = process(aggrE);

    if (helper.isRecordT(aggrT)){
      return irRecords.update(aggrRaw, aggrT, updE);
    }

    if (helper.isArrayT(aggrT)){
      if (updE.op == Operator::LIST_INDEX){

        std::unique_ptr<containers::IContainersIR> containersIR(
          containers::IContainersIR::create(aggrE, TypeAnnotation(), ctx
        ));

        return containersIR->update(aggrRaw, updE, hintAlias);
      }
    }

    assert(false);
    return nullptr;
  }

  case Operator::INVALID:
    assert(expr.__state != Expression::COMPOUND);

    switch (expr.__state) {
    case Expression::IDENT:
    {
      Symbol s = Attachments::get<IdentifierSymbol>(expr);
      return processSymbol(s, expr.getValueString());
    }

    case Expression::NUMBER:
    {
      llvm::Type* typConst = l.toLLVMType(pass->man->root->getType(expr, expectedT));
      int literal = expr.getValueDouble();

      if (typConst->isFloatingPointTy()) return llvm::ConstantFP::get(typConst, literal);
      if (typConst->isIntegerTy()) return llvm::ConstantInt::get(typConst, literal);

      assert(false && "Can't compile literal");
    }

    case Expression::STRING:
    {
      return controlIR.compileConstantStringAsPChar(expr.getValueString(), hintAlias);
    };

    default:
    {
      break;
    }
    };

    break;

  default: break;

  }

  assert(false && "Can't compile expression");
  return 0;
}

llvm::Value*
BasicBruteScope::compile(const std::string& hintBlockDecl) {
  LLVMLayer* llvm = pass->man->llvm;

  if (!hintBlockDecl.empty()) {
    llvm::BasicBlock *block = llvm::BasicBlock::Create(llvm->llvmContext, hintBlockDecl, function->raw);
    pass->man->llvm->irBuilder.SetInsertPoint(block);
  }

  lastBlockRaw = pass->man->llvm->irBuilder.GetInsertBlock();
  Symbol symbScope = Symbol{ScopedSymbol::RetSymbol, scope};

  //set hint for an entry scope
  string retAlias = (scope->__parent)? "" : function->prepareName();
  return processSymbol(symbScope, retAlias);
}

IBruteScope::~IBruteScope() { }

IBruteFunction::~IBruteFunction() { }

llvm::Function*
IBruteFunction::compile() {
    if (raw != nullptr) return raw;

    LLVMLayer* llvm = pass->man->llvm;
    llvm::IRBuilder<>& builder = llvm->irBuilder;

    string&& functionName = prepareName();
    std::vector<llvm::Type*>&& types = prepareSignature();
    llvm::Type* expectedResultType = prepareResult();

    llvm::FunctionType *ft = llvm::FunctionType::get(expectedResultType, types, false);
    raw = llvm::cast<llvm::Function>(llvm->module->getOrInsertFunction(functionName, ft));
    prepareBindings();
    applyAttributes();

    const std::string& blockName = "entry";
    llvm::BasicBlock* blockCurrent = builder.GetInsertBlock();

    llvm::Value* result = getBruteScope(__entry)->compile(blockName);
    assert(result);

    //SECTIONTAG types/convert function ret value
    builder.CreateRet(typeinference::doAutomaticTypeConversion(result, expectedResultType, llvm->irBuilder));

    if (blockCurrent) {
        builder.SetInsertPoint(blockCurrent);
    }

    llvm->moveToGarbage(ft);
    return raw;
}

IBruteScope*
IBruteFunction::getBruteScope(const CodeScope * const scope) {
    if (__scopes.count(scope)) {
        auto result = __scopes.at(scope).lock();

        if (result) {
            return result.get();
        }
    }

    std::shared_ptr<IBruteScope> unit(pass->buildCodeScopeUnit(scope, this));

    if (scope->__parent != nullptr) {
        auto parentUnit = Decorators<CachedScopeDecoratorTag>::getInterface(getBruteScope(scope->__parent));
        parentUnit->registerChildScope(unit);

    } else {
        __orphanedScopes.push_back(unit);
    }

    if (!__scopes.emplace(scope, unit).second) {
        __scopes[scope] = unit;
    }

    return unit.get();
}

IBruteScope*
IBruteFunction::getScopeUnit(ManagedScpPtr scope) {
    return getBruteScope(&*scope);
}

IBruteScope*
IBruteFunction::getEntry() {
    return getBruteScope(__entry);
}

std::vector<llvm::Type*>
IBruteFunction::getScopeSignature(CodeScope* scope){
  LLVMLayer* llvm = IBruteFunction::pass->man->llvm;
  AST* ast = IBruteFunction::pass->man->root;
  std::vector<llvm::Type*> result;

  std::transform(scope->__bindings.begin(), scope->__bindings.end(), std::inserter(result, result.end()),
    [llvm, ast, scope](const std::string & argAlias)->llvm::Type* {
      assert(scope->__identifiers.count(argAlias));

      ScopedSymbol argS{scope->__identifiers.at(argAlias), versions::VERSION_NONE};
      const Expression& argE = scope->__declarations.at(argS);
      const ExpandedType& argT = ast->expandType(argE.type);

      return llvm->toLLVMType(argT, argE);
  });

  if(scope->trackExternalSymbs){
    std::transform(scope->boundExternalSymbs.begin(), scope->boundExternalSymbs.end(), std::inserter(result, result.end()),
                   [llvm, ast](const Symbol& argS){
      const Expression& argE = CodeScope::getDefinition(argS);
      const ExpandedType& argT = ast->expandType(argE.type);

      return llvm->toLLVMType(argT, argE);
    });
  }

  return result;
}

template<>
compilation::IBruteFunction*
CompilePassCustomDecorators<void, void>
::buildFunctionUnit(const ManagedFnPtr& function) {
    return new BruteFunctionDefault(function, this);

}

template<>
compilation::IBruteScope*
CompilePassCustomDecorators<void, void>
::buildCodeScopeUnit(const CodeScope * const scope, IBruteFunction* function) {
    return new DefaultCodeScopeUnit(scope, function, this);
}

std::string
BasicBruteScope::getIndexStr(const Expression& index){
  switch(index.__state){

//named struct field
    case Expression::STRING:
      return index.getValueString();
      break;

//anonymous struct field
    case Expression::NUMBER:
      return to_string((int) index.getValueDouble());
      break;

    default:
      assert(false && "Wrong index for a struct");
  }
  return "";
}

} // end of compilation

compilation::IBruteFunction*
CompilePass::getBruteFn(const ManagedFnPtr& function) {
    unsigned int id = function.id();

    if (!functions.count(id)) {
        compilation::IBruteFunction* unit = buildFunctionUnit(function);
        functions.emplace(id, unit);
        return unit;
    }

    return functions.at(id);
}

void
CompilePass::prepare(){
  //Initialization:
#ifndef XREATE_CONFIG_MIN
#endif
  managerTransformations = new xreate::compilation::TransformationsManager();
  targetInterpretation = new interpretation::TargetInterpretation(man, this);
}

void
CompilePass::run() {
  prepare();

  //Determine entry function:
  StaticModel modelEntry = man->transcend->query(analysis::FN_ENTRY_PREDICATE);

  if (man->options.requireEntryFn){
    assert(modelEntry.size() && "Error: No entry function found");
    assert(modelEntry.size() == 1 && "Error: Ambiguous entry function");
  }

  if(modelEntry.size()){
    string fnEntryName = std::get<0>(TranscendLayer::parse<std::string>(modelEntry.begin()->second));
    compilation::IBruteFunction* fnEntry = getBruteFn(man->root->findFunction(fnEntryName));
    __fnEntryRaw = fnEntry->compile();
  }

  //Compile exterior functions:
  StaticModel modelExterior = man->transcend->query(analysis::FN_EXTERIOR_PREDICATE);
  for(const auto entry: modelExterior){
    const string& fnName = std::get<0>(TranscendLayer::parse<std::string>(entry.second));
    getBruteFn(man->root->findFunction(fnName))->compile();
  }
}

llvm::Function*
CompilePass::getEntryFunction() {
    return __fnEntryRaw;
}

void
CompilePass::prepareQueries(TranscendLayer* transcend) {
#ifndef XREATE_CONFIG_MIN
  transcend->registerQuery(new latex::LatexQuery(), QueryId::LatexQuery);
#endif

  transcend->registerQuery(new containers::Query(), QueryId::ContainersQuery);
  transcend->registerQuery(new demand::DemandQuery(), QueryId::DemandQuery);
  transcend->registerQuery(new polymorph::PolymorphQuery(), QueryId::PolymorphQuery);
}

} //end of namespace xreate

/**
 * \class xreate::CompilePass
 * \brief The owner of the compilation process. Performs fundamental compilation activities along with the xreate::compilation's routines
 *
 * xreate::CompilePass traverses over xreate::AST tree and produces executable code.
 * The pass performs compilation using the following data sources:
 *   - %Attachments: the data gathered by the previous passes. See \ref xreate::Attachments.
 *   - Transcend solutions accessible via queries. See \ref xreate::IQuery, \ref xreate::TranscendLayer.
 * 
 * The pass generates a bytecode by employing \ref xreate::LLVMLayer(wrapper over LLVM toolchain). 
 * Many compilation activities are delegated to more specific routines. Most notable delegated compilation aspects are:
 *   - Containers support. See \ref xreate::containers.
 *   - Latex compilation. See \ref xreate::latex.
 *   - Interpretation support. See \ref xreate::interpretation.
 *   - Loop saturation support. See \ref xreate::compilation::TransformationsScopeDecorator.
 *   - External code interaction support. See \ref xreate::ExternLayer (wrapper over Clang library).
 *
 * \section adaptability_sect Adaptability
 * xreate::CompilePass's behaviour can be adapted in several ways:
 *   - %Function Decorators to alter function-level compilation. See \ref xreate::compilation::IBruteFunction
 *   - Code Block Decorators to alter code block level compilation. See \ref xreate::compilation::ICodeScopeUnit. 
 *      Default functionality defined by \ref xreate::compilation::DefaultCodeScopeUnit
 *   - Targets to allow more versitile extensions.
 *     Currently only xreate::interpretation::TargetInterpretation  use Targets infrastructure. See \ref xreate::compilation::Target.
 *   - Altering %function invocation. See \ref xreate::compilation::IFnInvocation.
 *
 * Clients are free to construct a compiler instantiation with the desired decorators by using \ref xreate::compilation::CompilePassCustomDecorators.
 * As a handy alias, `CompilePassCustomDecorators<void, void>` constructs the default compiler.
 *
 */