/* 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: arrays.cpp
 * Author: pgess <v.melnychenko@xreate.org>
 *
 * Created in March 2020.
 */

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

using namespace std;
using namespace llvm;

namespace xreate { namespace containers{

llvm::PointerType*
ArrayIR::getRawType(const ExpandedType& aggrT, const typehints::ArrayHint& hint, LLVMLayer* llvm){
  assert(aggrT->__operator == TypeOperator::ARRAY);
  assert(aggrT->__operands.size() == 1);
  llvm::Type* elRawT = llvm->toLLVMType(ExpandedType(aggrT->__operands.at(0)));

  return llvm::ArrayType::get(elRawT, hint.size)->getPointerTo();
}

llvm::Value*
ArrayIR::init(const string& hintAlias){
  LLVMLayer* llvm = __context.pass->man->llvm;
  TypesHelper helper(llvm);
  llvm::PointerType* aggrRawT = getRawType(__aggrT, __hint, __context.pass->man->llvm);

  //llvm::Value* aggrLengthRaw = ConstantInt::get(helper.getPreferredIntTy(), aggrInfo.size);
  llvm::Value* aggrRaw = llvm->irBuilder.CreateAlloca(aggrRawT->getElementType(), nullptr, hintAlias);

  return aggrRaw;
}

llvm::Value*
ArrayIR::update(llvm::Value* aggrRaw, const Expression& updE, const std::string& hintAlias) {
  LLVMLayer* llvm = __context.pass->man->llvm;
  TypesHelper helper(llvm);
  llvm::IntegerType* intT = helper.getPreferredIntTy();
  llvm::Value* idxZeroRaw = ConstantInt::get(intT, 0);
  llvm::PointerType* aggrRawT = getRawType(__aggrT, __hint, __context.pass->man->llvm);
  const TypeAnnotation& elT = __aggrT->__operands.at(0);
  //llvm::Type* elTRaw = llvm->toLLVMType(ExpandedType(aggrT->__operands.at(0)));

  for (const auto& entry: reprListAsDict(updE)){
    llvm::Value* keyRaw = __context.scope->process(entry.first);
    llvm::Value* elRaw = __context.scope->process(entry.second, "", elT);

    llvm::Value* elLoc = llvm->irBuilder.CreateGEP(
      aggrRawT->getElementType(), aggrRaw, ArrayRef<llvm::Value*>(std::vector<Value*>{idxZeroRaw, keyRaw}));
    llvm->irBuilder.CreateStore(elRaw, elLoc) ;
  }

  return aggrRaw;
}

llvm::Value*
ArrayIR::get(llvm::Value* aggrRaw, std::vector<llvm::Value *> idxL, const std::string& hintAlias) {
  LLVMLayer* llvm = __context.pass->man->llvm;
  TypesHelper helper(llvm);
  llvm::IntegerType* intT = helper.getPreferredIntTy();
  llvm::Value* zeroRaw = ConstantInt::get(intT, 0);

  idxL.insert(idxL.begin(), zeroRaw);
  llvm::Value *pEl = llvm->irBuilder.CreateGEP(aggrRaw, llvm::ArrayRef<llvm::Value *>(idxL));
  return llvm->irBuilder.CreateLoad(pEl, hintAlias);
}

llvm::Value*
ArrayIR::operatorMap(const Expression& expr, const std::string& hintAlias) {
  assert(false); return nullptr;
  //EXPAND_CONTEXT UNUSED(scope);

//    //initializationcompileListAsSolidArray
//    Symbol symbolIn = Attachments::get<IdentifierSymbol>(expr.getOperands()[0]);
//
//    ImplementationRec<SOLID> implIn = containers::Query::queryImplementation(symbolIn).extract<SOLID>(); // impl of input list
//    size_t size = implIn.size;
//    CodeScope* scopeLoop = expr.blocks.front();
//    std::string varEl = scopeLoop->__bindings[0];
//
//    IFwdIteratorIR* it = IFwdIteratorIR::create(context, symbolIn);
//    llvm::Value *rangeFrom = it->begin();
//    llvm::Value *rangeTo = it->end();
//
//    //definitions
//    ArrayType* tyNumArray = nullptr; //(ArrayType*) (llvm->toLLVMType(ExpandedType(TypeAnnotation(tag_array, TypePrimitive::Int, size))));
//    llvm::IRBuilder<> &builder = llvm->irBuilder;
//
//    llvm::BasicBlock *blockLoop = llvm::BasicBlock::Create(llvm->llvmContext, "loop", function->raw);
//    llvm::BasicBlock *blockBeforeLoop = builder.GetInsertBlock();
//    llvm::BasicBlock *blockAfterLoop = llvm::BasicBlock::Create(llvm->llvmContext, "postloop", function->raw);
//    Value* dataOut = llvm->irBuilder.CreateAlloca(tyNumArray, ConstantInt::get(tyNum, size), NAME("map"));
//
//    // * initial check
//    Value* condBefore = builder.CreateICmpSLE(rangeFrom, rangeTo);
//    builder.CreateCondBr(condBefore, blockLoop, blockAfterLoop);
//
//    // create PHI:
//    builder.SetInsertPoint(blockLoop);
//    llvm::PHINode *stateLoop = builder.CreatePHI(tyNum, 2, "mapIt");
//    stateLoop->addIncoming(rangeFrom, blockBeforeLoop);
//
//    // loop body:
//    Value* elIn = it->get(stateLoop, varEl);
//    compilation::IBruteScope* scopeLoopUnit = function->getBruteScope(scopeLoop);
//    scopeLoopUnit->bindArg(elIn, move(varEl));
//    Value* elOut = scopeLoopUnit->compile();
//    Value *pElOut = builder.CreateGEP(dataOut, ArrayRef<Value *>(std::vector<Value*>{ConstantInt::get(tyNum, 0), stateLoop}));
//    builder.CreateStore(elOut, pElOut);
//
//    //next iteration preparing
//    Value *stateLoopNext = builder.CreateAdd(stateLoop, llvm::ConstantInt::get(tyNum, 1));
//    stateLoop->addIncoming(stateLoopNext, builder.GetInsertBlock());
//
//    //next iteration checks:
//    Value* condAfter = builder.CreateICmpSLE(stateLoopNext, rangeTo);
//    builder.CreateCondBr(condAfter, blockLoop, blockAfterLoop);
//
//    //finalization:
//    builder.SetInsertPoint(blockAfterLoop);
//
//    return dataOut;
}

llvm::Value*
ArrayIR::keys(llvm::Value* aggrRaw, const std::string &hintAlias){
  TypesHelper helper(__context.pass->man->llvm);
  RangeIR compiler(__context);

  llvm::Type* intRawT = helper.getPreferredIntTy();
  llvm::Value* keyBeginRaw = llvm::ConstantInt::get(intRawT, 0);
  llvm::Value* keyEndRaw = llvm::ConstantInt::get(intRawT, __hint.size);

  return compiler.create(__aggrT, keyBeginRaw, keyEndRaw);
}

IFwdIteratorIR* ArrayIR::getFwdIterator(){
  return new FwdIteratorIR<SOLID>(*this);
}

llvm::Value *
FwdIteratorIR<SOLID>::begin(llvm::Value* aggrRaw){
  TypesHelper helper(__compiler.__context.pass->man->llvm);
  llvm::IntegerType* intT = helper.getPreferredIntTy();

  return llvm::ConstantInt::get(intT, 0);
}

llvm::Value *
FwdIteratorIR<SOLID>::end(llvm::Value* aggrRaw){
  TypesHelper helper(__compiler.__context.pass->man->llvm);
  llvm::IntegerType* intT = helper.getPreferredIntTy();
  size_t size = __compiler.__hint.size;

  return llvm::ConstantInt::get(intT, size);
}

llvm::Value *
FwdIteratorIR<SOLID>::get(llvm::Value* aggrRaw, llvm::Value *idxRaw, const std::string &hintAlias){
  return __compiler.get(aggrRaw, {idxRaw}, hintAlias);
}

llvm::Value *
FwdIteratorIR<SOLID>::advance(llvm::Value *idxRaw, const std::string &hintAlias){
  LLVMLayer* llvm = __compiler.__context.pass->man->llvm;
  TypesHelper helper(llvm);
  llvm::IntegerType* intT = helper.getPreferredIntTy();
  llvm::Value* cnstOneRaw = llvm::ConstantInt::get(intT, 1);

  return llvm->irBuilder.CreateAdd(idxRaw, cnstOneRaw, hintAlias);
}

}} //xreate::containers