//===------ CLIJitMeta.cpp - CLI class/method/field operators -------------===//
//
//                              N3
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include <stdarg.h>

#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/GlobalVariable.h"
#include "llvm/LLVMContext.h"

#include "types.h"

#include "mvm/JIT.h"

#include "CLIAccess.h"
#include "CLIJit.h"
#include "CLIString.h"
#include "N3.h"
#include "VMClass.h"
#include "VMObject.h"
#include "VMThread.h"

using namespace n3;
using namespace llvm;

// TODO: MUST CHECK the type!
//		if (llvm::isa<check>(signature->naturalType)) {									 
//		if (signature->naturalType == Type::getFloatTy(getGlobalContext())) {

#define IMPLEMENTS_VMFIELD_ASSESSORS(name, type, do_root)								\
	void VMField::set##name(VMObject* obj, type val) {										\
		llvm_gcroot(obj, 0);																								\
		do_root(val, 0);																										\
																																				\
		if (classDef->status < ready)																				\
			classDef->resolveType(true, true, NULL);													\
																																				\
		*(type*)((char *)obj + ptrOffset) = val;														\
																																				\
		return;																															\
	}																																			\
																																				\
	type VMField::get##name(VMObject* obj) {															\
		llvm_gcroot(obj, 0);																								\
		if (classDef->status < ready)																				\
			classDef->resolveType(true, true, NULL);													\
																																				\
		type res;																														\
		do_root(res, 0);																										\
		res = *(type *)((char *)obj + ptrOffset);														\
		return res;																													\
	}

ON_TYPES(IMPLEMENTS_VMFIELD_ASSESSORS, _F_NTR)

#undef IMPLEMENTS_VMFIELD_ASSESSORS

GenericValue VMMethod::invokeGeneric(std::vector<llvm::GenericValue>& args) {																		
	// at this step, we must not launch the gc because objects arguments are somewhere in the stack a copying collector
	// can not know where they are
	assert(code);									// compiling a method can trigger a gc
	return mvm::MvmModule::executionEngine->runFunction(methPtr, args);
}

GenericValue VMMethod::invokeGeneric(va_list ap) {																		
	Function* func = methPtr;																						
	std::vector<GenericValue> args;																			
	for (Function::arg_iterator i = func->arg_begin(), e = func->arg_end();	
			 i != e; ++i) {																									
		const Type* cur = i->getType();																		
		if (cur == Type::getInt8Ty(getGlobalContext())) {									
			GenericValue gv;																								
			gv.IntVal = APInt(8, va_arg(ap, int));													
			args.push_back(gv);																							
		} else if (cur == Type::getInt16Ty(getGlobalContext())) {					
			GenericValue gv;																								
			gv.IntVal = APInt(16, va_arg(ap, int));													
			args.push_back(gv);																							
		} else if (cur == Type::getInt32Ty(getGlobalContext())) {					
			GenericValue gv;																								
			gv.IntVal = APInt(32, va_arg(ap, int));													
			args.push_back(gv);																							
		} else if (cur == Type::getInt64Ty(getGlobalContext())) {					
			GenericValue gv1;																								
			gv1.IntVal = APInt(64, va_arg(ap, uint64));											
			args.push_back(gv1);																						
		} else if (cur == Type::getDoubleTy(getGlobalContext())) {				
			GenericValue gv1;																								
			gv1.DoubleVal = va_arg(ap, double);															
			args.push_back(gv1);																						
		} else if (cur == Type::getFloatTy(getGlobalContext())) {					
			GenericValue gv;																								
			gv.FloatVal = (float)(va_arg(ap, double));											
			args.push_back(gv);																							
		} else {																													
			GenericValue gv(va_arg(ap, VMObject*));													
			args.push_back(gv);																							
		}																																	
	}																																		
	
	return invokeGeneric(args);
}

GenericValue VMMethod::invokeGeneric(...) {
	va_list ap;
	va_start(ap, this);
	GenericValue res = invokeGeneric(ap);
	va_end(ap);
	return res;
}

#define DEFINE_INVOKE(name, type, extractor)														\
	type VMMethod::invoke##name(...) {																		\
		va_list ap;																													\
		va_start(ap, this);																									\
		GenericValue res = invokeGeneric(ap);																\
		va_end(ap);																													\
		return (type)res.extractor;																					\
	}

ON_TYPES(DEFINE_INVOKE, _F_NTE)

#undef DEFINE_INVOKE

#define DEFINE_INVOKE_VOID(name, type, extractor)	\
	type VMMethod::invoke##name(...) {																		\
		va_list ap;																													\
		va_start(ap, this);																									\
		invokeGeneric(ap);																									\
		va_end(ap);																													\
	}

ON_VOID(DEFINE_INVOKE_VOID, _F_NTE)

#undef DEFINE_INVOKE_VOID


// // mettre en param un Function *
// // materializeFunction avant
// GenericValue VMMethod::operator()(va_list ap) {
  
//   if (classDef->status < ready) 
//     classDef->resolveType(true, true, NULL);
  
//   Function* func = compiledPtr(NULL);
  
//   std::vector<GenericValue> args;
//   for (Function::arg_iterator i = func->arg_begin(), e = func->arg_end();
//        i != e; ++i) {
//     const Type* type = i->getType();
//     if (type == Type::getInt8Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(8, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt16Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(16, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt32Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(32, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt64Ty(getGlobalContext())) {
//       GenericValue gv1;
//       gv1.IntVal = APInt(64, va_arg(ap, uint64));
//       args.push_back(gv1);
//     } else if (type == Type::getDoubleTy(getGlobalContext())) { 
//       GenericValue gv1;
//       gv1.DoubleVal = va_arg(ap, double);
//       args.push_back(gv1);
//     } else if (type == Type::getFloatTy(getGlobalContext())) {
//       GenericValue gv;
//       gv.FloatVal = (float)(va_arg(ap, double));
//       args.push_back(gv);
//     } else {
//       GenericValue gv(va_arg(ap, VMObject*));
//       args.push_back(gv);
//     }
//   }
  
//   return mvm::MvmModule::executionEngine->runFunction(func, args);
// }

// GenericValue VMMethod::operator()(VMObject* obj, va_list ap) {
  
//   if (classDef->status < ready) 
//     classDef->resolveType(true, true, NULL);
  
//   Function* func = compiledPtr(NULL);
  
//   std::vector<GenericValue> args;
//   GenericValue object(obj);
//   args.push_back(object);

//   for (Function::arg_iterator i = ++(func->arg_begin()), e = func->arg_end();
//        i != e; ++i) {
//     const Type* type = i->getType();
//     if (type == Type::getInt8Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(8, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt16Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(16, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt32Ty(getGlobalContext())) {
//       GenericValue gv;
//       gv.IntVal = APInt(32, va_arg(ap, int));
//       args.push_back(gv);
//     } else if (type == Type::getInt64Ty(getGlobalContext())) {
//       GenericValue gv1;
//       gv1.IntVal = APInt(64, va_arg(ap, uint64));
//       args.push_back(gv1);
//     } else if (type == Type::getDoubleTy(getGlobalContext())) { 
//       GenericValue gv1;
//       gv1.DoubleVal = va_arg(ap, double);
//       args.push_back(gv1);
//     } else if (type == Type::getFloatTy(getGlobalContext())) {
//       GenericValue gv;
//       gv.FloatVal = (float)(va_arg(ap, double));
//       args.push_back(gv);
//     } else {
//       GenericValue gv(va_arg(ap, VMObject*));
//       args.push_back(gv);
//     }
//   }
      
//   return mvm::MvmModule::executionEngine->runFunction(func, args);
// }


// GenericValue VMMethod::operator()(...) {
//   va_list ap;
//   va_start(ap, this);
//   GenericValue ret = (*this)(ap);
//   va_end(ap);
//   return  ret;
// }

// GenericValue VMMethod::run(...) {
//   va_list ap;
//   va_start(ap, this);
//   GenericValue ret = (*this)(ap);
//   va_end(ap);
//   return  ret;
// }

// GenericValue VMMethod::operator()(std::vector<GenericValue>& args) {
  
//   if (classDef->status < ready) 
//     classDef->resolveType(true, true, NULL);
  
//   Function* func = compiledPtr(NULL);
//   return mvm::MvmModule::executionEngine->runFunction(func, args);
// }

GlobalVariable* VMCommonClass::llvmVar() {
  if (!_llvmVar) {
    aquire();
    if (!_llvmVar) {
      Module* Mod = vm->getLLVMModule();
      const Type* pty = PointerType::getUnqual(Type::getInt8Ty(getGlobalContext()));
      Constant* cons = 
        ConstantExpr::getIntToPtr(ConstantInt::get(Type::getInt64Ty(getGlobalContext()), uint64_t (this)),
                                    pty);

      _llvmVar = new GlobalVariable(*Mod, pty, true,
                                    GlobalValue::ExternalLinkage,
                                    cons, "");
    
    }
    release();
  }
  return _llvmVar;
}

GlobalVariable* VMField::llvmVar() {
  if (!_llvmVar) {
    classDef->aquire();
    if (!_llvmVar) {
      const Type* pty = PointerType::getUnqual(Type::getInt8Ty(getGlobalContext()));
      Module* Mod = classDef->vm->getLLVMModule();
      Constant* cons = 
        ConstantExpr::getIntToPtr(ConstantInt::get(Type::getInt64Ty(getGlobalContext()), uint64_t (this)),
                                  pty);

      _llvmVar = new GlobalVariable(*Mod, pty, true,
                                    GlobalValue::ExternalLinkage,
                                    cons, "");
    }
    classDef->release();
  }
  return _llvmVar;
}

GlobalVariable* VMMethod::llvmVar() {
  if (!_llvmVar) {
    classDef->aquire();
    if (!_llvmVar) {
      Module* Mod = classDef->vm->getLLVMModule();
      const Type* pty = PointerType::getUnqual(Type::getInt8Ty(getGlobalContext()));
      Constant* cons = 
        ConstantExpr::getIntToPtr(ConstantInt::get(Type::getInt64Ty(getGlobalContext()), uint64_t (this)),
                                  pty);

      _llvmVar = new GlobalVariable(*Mod, pty, true,
                                    GlobalValue::ExternalLinkage,
                                    cons, "");
    
    }
    classDef->release();
  }
  return _llvmVar;
}

Constant* VMObject::classOffset() {
  return VMThread::get()->getVM()->module->constantOne;
}
