#include "ravi_llvm.h"

#include <iostream>

// This file contains a basic test case that covers following:
// Creating a function that takes pointer to struct as argument
// The function gets value from one of the fields in the struct
// And returns it
// The equivalent C program is:
//
// extern int printf(const char *, ...);
//
// struct GCObject {
//   struct GCObject *next;
//   unsigned char a;
//   unsigned char b;
// };
//
// int testfunc(struct GCObject *obj) {
//   printf("value = %d\n", obj->a);
//   return obj->a;
// }

// This mirrors the Lua GCObject structure in lobject.h
typedef struct RaviGCObject {
  struct RaviGCObject *next;
  unsigned char b1;
  unsigned char b2;
} RaviGCObject;

// Our prototype for the JITted function
typedef int (*myfunc_t)(RaviGCObject *);

int test1() {
  // FIXME context should be deleted at the end but here we
  // don't bother
  llvm::LLVMContext *context = new llvm::LLVMContext();

  // Module is the translation unit
  std::unique_ptr<llvm::Module> theModule =
      std::unique_ptr<llvm::Module>(new llvm::Module("ravi", *context));
  llvm::Module *module = theModule.get();
  llvm::IRBuilder<> builder(*context);

#if defined(_WIN32) && (!defined(_WIN64) || LLVM_VERSION_MINOR < 7)
  // On Windows we get error saying incompatible object format
  // Reading posts on mailining lists I found that the issue is that COEFF
  // format is not supported and therefore we need to set -elf as the object
  // format
  auto triple = llvm::sys::getProcessTriple();
  // module->setTargetTriple("x86_64-pc-win32-elf");
  module->setTargetTriple(triple + "-elf");
#endif

  // create a GCObject structure as defined in lobject.h
  llvm::StructType *structType =
      llvm::StructType::create(*context, "RaviGCObject");
  llvm::PointerType *pstructType =
      llvm::PointerType::get(structType, 0); // pointer to RaviGCObject
  std::vector<llvm::Type *> elements;
  elements.push_back(pstructType);
  elements.push_back(llvm::Type::getInt8Ty(*context));
  elements.push_back(llvm::Type::getInt8Ty(*context));
  structType->setBody(elements);
#if defined(LLVM_ENABLE_DUMP)
  structType->dump();
#endif
  // Create printf declaration
  std::vector<llvm::Type *> args;
  args.push_back(llvm::Type::getInt8PtrTy(*context));
  // accepts a char*, is vararg, and returns int
  llvm::FunctionType *printfType =
      llvm::FunctionType::get(builder.getInt32Ty(), args, true);
  llvm::Constant *printfFunc =
      module->getOrInsertFunction("printf", printfType);

  // Create the testfunc()
  args.clear();
  args.push_back(pstructType);
  llvm::FunctionType *funcType =
      llvm::FunctionType::get(builder.getInt32Ty(), args, false);
  llvm::Function *mainFunc = llvm::Function::Create(
      funcType, llvm::Function::ExternalLinkage, "testfunc", module);
  llvm::BasicBlock *entry =
      llvm::BasicBlock::Create(*context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  // printf format string
  llvm::Value *formatStr = builder.CreateGlobalStringPtr("value = %d\n");

  // Get the first argument which is RaviGCObject *
  auto argiter = mainFunc->arg_begin();
  llvm::Value *arg1 = &(*argiter);
  arg1->setName("obj");

  // Now we need a GEP for the second field in RaviGCObject
  std::vector<llvm::Value *> values;
  llvm::APInt zero(32, 0);
  llvm::APInt one(32, 1);
  // This is the array offset into RaviGCObject*
  values.push_back(
      llvm::Constant::getIntegerValue(llvm::Type::getInt32Ty(*context), zero));
  // This is the field offset
  values.push_back(
      llvm::Constant::getIntegerValue(llvm::Type::getInt32Ty(*context), one));

  // Create the GEP value
  llvm::Value *arg1_a = builder.CreateGEP(arg1, values, "ptr");

  // Now retrieve the data from the pointer address
  llvm::Value *tmp1 = builder.CreateLoad(arg1_a, "a");
  // As the retrieved value is a byte - convert to int i
  llvm::Value *tmp2 =
      builder.CreateZExt(tmp1, llvm::Type::getInt32Ty(*context), "i");

  // Call the printf function
  values.clear();
  values.push_back(formatStr);
  values.push_back(tmp2);
  builder.CreateCall(printfFunc, values);

  // return i
  builder.CreateRet(tmp2);
#if defined(LLVM_ENABLE_DUMP)
  module->dump();
#endif

  // Lets create the MCJIT engine
  std::string errStr;
#if LLVM_VERSION_MAJOR > 3 || LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR > 5
  std::unique_ptr<llvm::Module> module_(module);
  auto engine = llvm::EngineBuilder(std::move(module_))
                    .setErrorStr(&errStr)
                    .setEngineKind(llvm::EngineKind::JIT)
                    .create();
#else
  auto engine = llvm::EngineBuilder(module)
    .setErrorStr(&errStr)
    .setEngineKind(llvm::EngineKind::JIT)
    .setUseMCJIT(true)
    .create();
#endif
  if (!engine) {
    std::cerr << "Failed to construct MCJIT ExecutionEngine: " << errStr
                 << "\n";
    return 1;
  }

  // Now lets compile our function into machine code
  std::string funcname = "testfunc";
  myfunc_t funcptr = (myfunc_t)engine->getFunctionAddress(funcname);
  if (funcptr == nullptr) {
    std::cerr << "Failed to obtain compiled function\n";
    return 1;
  }

  // Run the function and test results.
  RaviGCObject obj = {NULL, 42, 65};
  int ans = funcptr(&obj);
  printf("The answer is %d\n", ans);
  return ans == 42 ? 0 : 1;
}

extern "C" int mytest(RaviGCObject *obj) {
  printf("value = %d\n", obj->b1);
  return obj->b1;
}

#if 0
// This version of the test calls mytest() rather than
// printf() as in test1(). Also we use RaviJITState and related
// infrastructure
int test2() {

  auto jitStatePtr = ravi::RaviJITStateFactory::newJITState();
  ravi::RaviJITState &jitState = *jitStatePtr.get();

  jitState.dump();

  llvm::LLVMContext &context = jitState.context();
  llvm::IRBuilder<> builder(context);

  // create a GCObject structure as defined in lobject.h
  llvm::StructType *structType =
      llvm::StructType::create(context, "RaviGCObject");
  llvm::PointerType *pstructType =
      llvm::PointerType::get(structType, 0); // pointer to RaviGCObject
  std::vector<llvm::Type *> elements;
  elements.push_back(pstructType);
  elements.push_back(llvm::Type::getInt8Ty(context));
  elements.push_back(llvm::Type::getInt8Ty(context));
  structType->setBody(elements);
#if defined(LLVM_ENABLE_DUMP)
  structType->dump();
#endif

  // Create declaration for mytest
  // int mytest(RaviGCObject *obj)
  std::vector<llvm::Type *> args;
  args.push_back(pstructType);
  llvm::FunctionType *mytestFuncType =
      llvm::FunctionType::get(builder.getInt32Ty(), args, false);

  // Create the testfunc()
  args.clear();
  args.push_back(pstructType);
  llvm::FunctionType *funcType =
      llvm::FunctionType::get(builder.getInt32Ty(), args, false);
  ravi::RaviJITFunction *func = jitState.createFunction(
      funcType, llvm::Function::ExternalLinkage, "testfunc");

  llvm::Function *mainFunc = func->function();
  llvm::BasicBlock *entry =
      llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  // Get the first argument which is RaviGCObject *
  auto argiter = mainFunc->arg_begin();
  llvm::Value *arg1 = argiter++;
  arg1->setName("obj");

  // Add an extern int mytest(RaviGCObject *obj) and link this
  // to mytest()
  llvm::Constant *mytestFunc =
      func->addExternFunction(mytestFuncType, reinterpret_cast<void *>(&mytest), "mytest");

  // Call the mytest() function
  std::vector<llvm::Value *> values;
  values.push_back(arg1);
  llvm::Value *tmp2 = builder.CreateCall(mytestFunc, values, "i");

  // return i
  builder.CreateRet(tmp2);
  func->dump();

  // Now lets compile our function into machine code
  myfunc_t funcptr = (myfunc_t)func->compile();
  if (funcptr == nullptr) {
    std::cerr << "Failed to obtain compiled function\n";
    return 1;
  }

  // Run the function and test results.
  RaviGCObject obj = {NULL, 42, 65};
  int ans = funcptr(&obj);
  printf("The answer is %d\n", ans);
  return ans == 42 ? 0 : 1;
}
#endif

int main() {

  // Looks like unless following three lines are not executed then
  // ExecutionEngine cannot be created
  llvm::InitializeNativeTarget();
  llvm::InitializeNativeTargetAsmPrinter();
  llvm::InitializeNativeTargetAsmParser();

  int failure_count = 0;
  failure_count += test1();
//  failure_count += test2();

  return failure_count != 0 ? 1 : 0;
}
