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

/**
 * \file    llvmlayer.h
 * \brief   Bytecode generation
 */

#include "ast.h"
#include "llvmlayer.h"
#include "analysis/typehints.h"

#ifdef XREATE_ENABLE_EXTERN
#include "ExternLayer.h"
#endif

#include <compilation/containers.h>
#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IR/IRPrintingPasses.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/Support/TargetSelect.h"

#include <iostream>
#include <cmath>


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

LLVMLayer::LLVMLayer(AST *root)
: llvmContext(),
  irBuilder(llvmContext),
  ast(root),
  module(new llvm::Module(root->getModuleName(), llvmContext)){
  llvm::InitializeNativeTarget();
  llvm::InitializeNativeTargetAsmPrinter();
  llvm::EngineBuilder builder;
  TargetMachine *target = builder.selectTarget();
  module->setDataLayout(target->createDataLayout());

#ifdef XREATE_ENABLE_EXTERN
  layerExtern = new ExternLayer(this);
  layerExtern->init(root);
#endif
}

void *
LLVMLayer::getFunctionPointer(llvm::Function *function){
  uint64_t entryAddr = jit->getFunctionAddress(function->getName().str());
  return (void *) entryAddr;
}

void
LLVMLayer::initJit(){
  std::string ErrStr;
  llvm::EngineBuilder builder(std::unique_ptr<llvm::Module>(module.release()));
  jit.reset(builder
    .setEngineKind(llvm::EngineKind::JIT)
    .setTargetOptions(optsTarget)
    .setOptLevel(optsLevel)
    .setErrorStr(&ErrStr)
    .setVerifyModules(true)
    .create()
  );
}

void
LLVMLayer::print(){
  llvm::PassManager<llvm::Module> PM;
  PM.addPass(llvm::PrintModulePass(llvm::outs(), "banner"));
  llvm::AnalysisManager<llvm::Module> aman;
  PM.run(*module.get(), aman);
}

void
LLVMLayer::moveToGarbage(void *o){
  __garbage.push_back(o);
}

llvm::Type*
LLVMLayer::toLLVMType(const ExpandedType &ty, const Expression& expr){
  TypeAnnotation t = ty.get();
  switch(t.__operator){
    case TypeOperator::ARRAY:{
      if (expr.tags.size() == 0) return nullptr; //TODO shouldn't be invalid

      return containers::IContainersIR::getRawType(expr, ty, this);
    }

    case TypeOperator::RECORD:{
      std::vector<llvm::Type *> packVec;
      packVec.reserve(t.__operands.size());

      std::transform(t.__operands.begin(), t.__operands.end(), std::inserter(packVec, packVec.end()),
                     [this](const TypeAnnotation &t){
                       return toLLVMType(ExpandedType(TypeAnnotation(t)));
                     });

      llvm::ArrayRef<llvm::Type *> packArr(packVec);
      return llvm::StructType::get(llvmContext, packArr, false);
    };

    case TypeOperator::REF:{
      TypeAnnotation tyRef = t.__operands.at(0);
      assert(tyRef.__operator == TypeOperator::ALIAS);
      llvm::StructType *tyOpaqRaw = llvm::StructType::create(llvmContext, tyRef.__valueCustom);
      llvm::PointerType *tyRefRaw = llvm::PointerType::get(tyOpaqRaw, 0);

      return tyRefRaw;
    };

    case TypeOperator::ALIAS:{
#ifdef XREATE_ENABLE_EXTERN
      //Look in extern types
      clang::QualType qt = layerExtern->lookupType(t.__valueCustom);
      return layerExtern->toLLVMType(qt);
#else
      assert(false);
#endif
    };

      //DEBT omit ID field in case of single variant.
    case TypeOperator::VARIANT:{
      /*  Variant Type Layout:
       *   {
       *       id     :: i8,  Holds stored variant id
       *       storage::      type of biggest variant
       *   }
       */
      uint64_t sizeStorage = 0;
      llvm::Type *typStorageRaw = llvm::Type::getVoidTy(llvmContext);
      for(const TypeAnnotation &subtype : t.__operands){
        llvm::Type *subtypeRaw = toLLVMType(ExpandedType(subtype));
        if(subtypeRaw->isVoidTy()) continue;

        uint64_t sizeSubtype = module->getDataLayout().getTypeStoreSize(subtypeRaw);
        if(sizeSubtype > sizeStorage){
          sizeStorage = sizeSubtype;
          typStorageRaw = subtypeRaw;
        }
      }

      std::vector<llvm::Type *> layout;
      layout.push_back(llvm::Type::getInt8Ty(llvmContext)); //id

      const bool flagHoldsData = sizeStorage > 0;
      if(flagHoldsData){
        layout.push_back(typStorageRaw); //storage
      }

      return llvm::StructType::get(llvmContext, llvm::ArrayRef<llvm::Type *>(layout));
    }

    case TypeOperator::NONE:{
      switch(t.__value){
        case TypePrimitive::Bool:
          return llvm::Type::getInt1Ty(llvmContext);

        case TypePrimitive::I8:
          return llvm::Type::getInt8Ty(llvmContext);

        case TypePrimitive::I32:
          return llvm::Type::getInt32Ty(llvmContext);

        case TypePrimitive::I64:
          return llvm::Type::getInt64Ty(llvmContext);

        case TypePrimitive::Int: {
//          IntBits hintSize;
//          if (existsSize(hintSize)){
//            return llvm::IntegerType::getIntNTy(llvmContext, hintSize.n);
//          }

          TypesHelper helper(this);
          return helper.getPreferredIntTy();
        }

        case TypePrimitive::Float:
          return llvm::Type::getDoubleTy(llvmContext);

        case TypePrimitive::String:
          return llvm::Type::getInt8PtrTy(llvmContext);

        case TypePrimitive::Invalid:
          return llvm::Type::getVoidTy(llvmContext);

        default:
          assert(false);
      }
    }

    default:
      assert(false);
  }

  assert(false);
  return nullptr;
}

bool
TypesHelper::isRecordT(const ExpandedType &ty){
  const TypeAnnotation &t = ty.get();

  if(t.__operator == TypeOperator::RECORD){
    return true;
  }

  if(t.__operator != TypeOperator::ALIAS){
    return false;
  }

#ifdef XREATE_ENABLE_EXTERN
    clang::QualType tqual = llvm->layerExtern->lookupType(t.__valueCustom);
    const clang::Type * raw = tqual.getTypePtr();

    // TODO skip ALL the pointers until non-pointer type found
    if (raw->isStructureType()) return true;
    if (!raw->isAnyPointerType()) return false;

    clang::QualType pointee = raw->getPointeeType();
    return pointee->isStructureType();
#else
  assert(false);
  return false;
#endif
}

bool
TypesHelper::isArrayT(const Expanded<TypeAnnotation>& ty){
  const TypeAnnotation &t = ty.get();

  if(t.__operator == TypeOperator::ARRAY){
    return true;
  }

  return false;
}

bool
TypesHelper::isPointerT(const ExpandedType &ty){
  if(ty.get().__operator != TypeOperator::ALIAS) return false;

#ifdef XREATE_ENABLE_EXTERN
    clang::QualType qt = llvm->layerExtern->lookupType(ty.get().__valueCustom);
    return llvm->layerExtern->isPointer(qt);
#else
  assert(false);
  return false;
#endif
}

bool
TypesHelper::isIntegerT(const Expanded<TypeAnnotation>& ty){
return
  (ty->__operator == TypeOperator::NONE) &&
  ((ty->__value == TypePrimitive::Bool) ||
  (ty->__value == TypePrimitive::I8) ||
  (ty->__value == TypePrimitive::I32) ||
  (ty->__value == TypePrimitive::I64) ||
  (ty->__value == TypePrimitive::Int));
}


std::vector<std::string>
TypesHelper::getRecordFields(const ExpandedType &t){
#ifdef XREATE_ENABLE_EXTERN
  return (t.get().__operator == TypeOperator::RECORD)
      ? t.get().fields
      : llvm->layerExtern->getStructFields(
      llvm->layerExtern->lookupType(t.get().__valueCustom));
#else
  assert(t.get().__operator == TypeOperator::RECORD);
  return t.get().fields;
#endif
}

llvm::IntegerType *
TypesHelper::getPreferredIntTy() const{
  unsigned sizePreferred = llvm->module->getDataLayout().getLargestLegalIntTypeSizeInBits();
  return llvm::IntegerType::getIntNTy(llvm->llvmContext, sizePreferred);
}