/* 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:   containers.cpp
 * Author: pgess <v.melnychenko@xreate.org>
 */

/**
 * \file    compilation/containers.h
 * \brief   Containers compilation support. See [Containers](/d/concepts/containers/) in the Xreate's documentation.
 */

#include "compilation/containers.h"
#include "compilation/targetinterpretation.h"
#include "aux/expressions.h"
#include "compilation/containers/arrays.h"
#include "compilation/lambdas.h"
#include "analysis/predefinedanns.h"
#include "analysis/utils.h"

using namespace std;

namespace xreate { namespace containers{

ImplementationType
IContainersIR::getImplementation(const Expression& aggrE, AST* ast){
  auto manPredefined = analysis::PredefinedAnns::instance();
  const Expression& hintE = analysis::findAnnByType(aggrE, ExpandedType(manPredefined.hintsContT), ast);
  assert(hintE.isValid());

  return (ImplementationType ) hintE.getValueDouble();
}

IContainersIR *
IContainersIR::create(const Expression &aggrE, const TypeAnnotation &expectedT, const compilation::Context &context){
  ExpandedType aggrT = context.pass->man->root->getType(aggrE, expectedT);
  Expression aggr2E;

  if (aggrE.__state == Expression::IDENT && !aggrE.tags.size()){
    Symbol aggrS = Attachments::get<IdentifierSymbol>(aggrE);
    aggr2E = CodeScope::getDefinition(aggrS);
  } else {
    aggr2E = aggrE;
  }

  switch(aggr2E.op){
    case Operator::LIST:{
      typehints::ArrayHint aggrHint = typehints::find(
        aggr2E, typehints::ArrayHint{aggr2E.operands.size()}
      );

      return new ArrayIR(aggrT, aggrHint, context);
    }

    default:
      typehints::ArrayHint aggrHint = typehints::find(
        aggr2E, typehints::ArrayHint{0}
      );
      assert(aggrHint.size != 0);
      return new ArrayIR(aggrT, aggrHint, context);
  }

  assert(false);
  return nullptr;
}

llvm::Type*
IContainersIR::getRawType(const Expression& aggrE, const ExpandedType& aggrT, LLVMLayer* llvm){
  auto manPredefined = analysis::PredefinedAnns::instance();
  const Expression& hintE = analysis::findAnnByType(aggrE, ExpandedType(manPredefined.hintsContT), llvm->ast);
  assert(hintE.isValid());

  return getRawTypeByHint(hintE, aggrT, llvm);
}

llvm::Type*
IContainersIR::getRawTypeByHint(const Expression& hintE, const ExpandedType& aggrT, LLVMLayer* llvm) {
  ImplementationType hintImpl = (ImplementationType ) hintE.getValueDouble();
  switch (hintImpl){
    case SOLID: {
      typehints::ArrayHint hint = typehints::parse<typehints::ArrayHint>(hintE);
      return ArrayIR::getRawType(aggrT, hint, llvm);
    }

    case ON_THE_FLY:{
      typehints::FlyHint hint = typehints::parse<typehints::FlyHint>(hintE);
      return FlyIR::getRawType(aggrT, hint, llvm);
    }

    default: break;
  }

  assert(false);
  return nullptr;
}

llvm::Value *
RecordIR::init(llvm::StructType *tyAggr){
  return llvm::UndefValue::get(tyAggr);
}

llvm::Value*
RecordIR::init(std::forward_list<llvm::Type*> fields){
  std::vector<llvm::Type*> fieldsVec(fields.begin(), fields.end());
  llvm::ArrayRef<llvm::Type *> fieldsArr(fieldsVec);
  llvm::StructType* resultTR = llvm::StructType::get(__context.pass->man->llvm->llvmContext, fieldsArr, false);

  return init(resultTR);
}

llvm::Value *
RecordIR::update(llvm::Value *aggrRaw, const ExpandedType &aggrT, const Expression &updE){
  interpretation::InterpretationScope *scopeI12n =
    __context.pass->targetInterpretation->transformContext(__context);
  TypesHelper helper(__context.pass->man->llvm);
  const auto &fields = helper.getRecordFields(aggrT);

  std::map<std::string, size_t> indexFields;
  for(size_t i = 0, size = fields.size(); i < size; ++i){
    indexFields.emplace(fields[i], i);
  }

  for(const auto &entry: reprListAsDict(updE)){
    unsigned keyId;
    std::string keyHint;
    const Expression keyE = scopeI12n->process(entry.first);

    switch(keyE.__state){
      case Expression::STRING:
        keyId = indexFields.at(keyE.getValueString());
        keyHint = keyE.getValueString();
        break;

      case Expression::NUMBER:
        keyId = keyE.getValueDouble();
        keyHint = aggrT->fields.at(keyId);
        break;
      default:
        assert(false);
        break;
    }

    const TypeAnnotation &valueT = aggrT->__operands.at(keyId);

    llvm::Value *valueRaw = __context.scope->process(entry.second, keyHint, valueT);
    aggrRaw = __context.pass->man->llvm->irBuilder.CreateInsertValue(
    aggrRaw,
    valueRaw,
    keyId);
  }

  return aggrRaw;
}

llvm::Value*
FlyIR::create(llvm::Value* sourceRaw, CodeScope* body, const std::string& hintAlias){
  RecordIR recordIR(__context);
  compilation::LambdaIR lambdaIR(__context.pass);
  llvm::Function* fnTransform = lambdaIR.compile(body, hintAlias);

  llvm::Value* resultRaw = recordIR.init({
    sourceRaw->getType(),
    fnTransform->getFunctionType()->getPointerTo()
  });

  resultRaw = __context.pass->man->llvm->irBuilder.CreateInsertValue(
    resultRaw, sourceRaw, 0);
  resultRaw = __context.pass->man->llvm->irBuilder.CreateInsertValue(
    resultRaw, fnTransform, 1);

  return resultRaw;
}

llvm::Type*
FlyIR::getRawType(const ExpandedType& aggrT, const typehints::FlyHint& hint, LLVMLayer* llvm){
  assert(aggrT->__operator == TypeOperator::ARRAY);
  TypesHelper types(llvm);

  llvm::Type* sourceTRaw = IContainersIR::getRawTypeByHint(hint.hintSrc, aggrT, llvm);
  llvm::Type* elTRaw = llvm->toLLVMType(ExpandedType(aggrT->__operands.at(0)));
  llvm::Type* resultTRaw = types.getPreferredIntTy();

  llvm::Type* fnTnsfTRaw = llvm::FunctionType::get(resultTRaw, llvm::ArrayRef<llvm::Type*>(elTRaw), false);
  std::vector<llvm::Type*> fieldsVec = {
    sourceTRaw,
    fnTnsfTRaw->getPointerTo()
  };
  llvm::ArrayRef<llvm::Type *> fieldsArr(fieldsVec);
  llvm::StructType* resultTR = llvm::StructType::get(llvm->llvmContext, fieldsArr, false);

  return resultTR;
}

llvm::Value*
FlyIR::getTransformFn(llvm::Value* aggrRaw){
  LLVMLayer* llvm = __context.pass->man->llvm;
  llvm::Value* fnRaw = llvm->irBuilder.CreateExtractValue(aggrRaw, llvm::ArrayRef<unsigned>{1});

  return fnRaw;
}

llvm::Value*
FlyIR::getSourceAggr(llvm::Value* aggrRaw){
  LLVMLayer* llvm = __context.pass->man->llvm;
  return llvm->irBuilder.CreateExtractValue(aggrRaw, llvm::ArrayRef<unsigned>{0});
}

llvm::Value*
FlyIR::operatorMap(const Expression& expr, const std::string& hintAlias){
  const Expression& sourceE = expr.getOperands().at(0);
  llvm::Value* sourceRaw = __context.scope->process(sourceE);
  CodeScope* loopS = expr.blocks.front();

  return create(sourceRaw, loopS, hintAlias);
}

FlyIR::FlyIR(typehints::FlyHint hint, compilation::Context context)
  : __hint(hint), __context(context){}

IFwdIteratorIR*
IFwdIteratorIR::createByHint(const Expression& hintE, const ExpandedType& aggrT, const compilation::Context& context){
  ImplementationType hintType = (ImplementationType) hintE.getValueDouble();

  switch(hintType){
    case SOLID:{
      ArrayIR compiler(aggrT, typehints::parse<typehints::ArrayHint>(hintE), context);
      return new FwdIteratorIR<SOLID>(compiler);
    }

    case ON_THE_FLY:{
      return new FwdIteratorIR<ON_THE_FLY>(typehints::parse<typehints::FlyHint>(hintE), aggrT, context);
    }

    default: break;
  }

  assert(false);
  return nullptr;
}

IFwdIteratorIR*
IFwdIteratorIR::create(const Expression& aggrE, const ExpandedType& aggrT, const compilation::Context& context){
  auto manPredefined = analysis::PredefinedAnns::instance();
  const Expression& hintE = analysis::findAnnByType(
    aggrE,
    ExpandedType(manPredefined.hintsContT),
    context.pass->man->root
  );
  assert(hintE.isValid());

  return createByHint(hintE, aggrT, context);
}

llvm::Value*
FwdIteratorIR<ON_THE_FLY>::begin() {
 std::unique_ptr<IFwdIteratorIR> itSrcIR(IFwdIteratorIR::createByHint(__hint.hintSrc, __aggrT, __context));
 return itSrcIR->begin();
}

llvm::Value*
FwdIteratorIR<ON_THE_FLY>::end() {
  std::unique_ptr<IFwdIteratorIR> itSrcIR(IFwdIteratorIR::createByHint(__hint.hintSrc, __aggrT, __context));
  return itSrcIR->end();
}

llvm::Value*
FwdIteratorIR<ON_THE_FLY>::advance(llvm::Value* idxRaw, const std::string& hintAlias){
  std::unique_ptr<IFwdIteratorIR> itSrcIR(IFwdIteratorIR::createByHint(__hint.hintSrc, __aggrT, __context));
  return itSrcIR->advance(idxRaw, hintAlias);
}

llvm::Value*
FwdIteratorIR<ON_THE_FLY>::get(llvm::Value* aggrRaw, llvm::Value *idxRaw, const std::string &hintAlias){
  std::unique_ptr<IFwdIteratorIR> srcIterIR(IFwdIteratorIR::createByHint(__hint.hintSrc, __aggrT, __context));
  FlyIR compilerFly(__hint, __context);
  llvm::Value* aggrSrcRaw = compilerFly.getSourceAggr(aggrRaw);
  llvm::Value* valueSrcRaw = srcIterIR->get(aggrSrcRaw, idxRaw);
  FlyIR flyIR(__hint, __context);
  llvm::Value* fnTnsfRaw = flyIR.getTransformFn(aggrRaw);
  llvm::Type* fnTnsfTRawRaw = llvm::cast<llvm::PointerType>(fnTnsfRaw->getType())->getElementType();
  llvm::FunctionType* fnTnsfTRaw = llvm::cast<llvm::FunctionType>(fnTnsfTRawRaw);
  compilation::BruteFnInvocation fnTnsfInvoc(fnTnsfRaw, fnTnsfTRaw, __context.pass->man->llvm);
  return fnTnsfInvoc({valueSrcRaw}, hintAlias);
}

}} //end of xreate::containers
