#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
//#include "llvm/Type.h"
//#include "llvm/ADT/Triple.h"
//#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/CodeGen/LinkAllCodegenComponents.h"
#include "llvm/ExecutionEngine/GenericValue.h"
//#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/ExecutionEngine/JITEventListener.h"
#include "llvm/ExecutionEngine/JITMemoryManager.h"
//#include "llvm/ExecutionEngine/MCJIT.h"
//#include "llvm/Support/CommandLine.h"
#include "llvm/Support/IRReader.h"
//#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
//#include "llvm/Support/PluginLoader.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/raw_ostream.h"
//#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetSelect.h"
//#include <cerrno>

#include "llvm/DerivedTypes.h"
//#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Linker.h"

//using namespace llvm;


llvm::LLVMContext* LLVM_Context;
static llvm::ExecutionEngine* LLVM_ExecE = 0;
llvm::Module* LLVM_Module;
llvm::Type* LLVM_pointerType;

bool loadChunk(void* chunk)
{
	llvm::Module* m = (llvm::Module*)chunk;
	std::string msg;

	if (llvm::Linker::LinkModules(LLVM_Module, m, llvm::Function::ExternalLinkage, &msg))
	{
		printf("\nLINK ERROR - %s", &msg);
	}
	return true;
}
bool loadFile(const char* filename)
{
	// Load the bitcode...
	llvm::SMDiagnostic Err;
	llvm::Module* Mod = ParseIRFile(filename, Err, *LLVM_Context);
	if (!Mod) {
		Err.print("Error loading bitcode", llvm::errs());
		return 1;
	}
	printf("\nLink with main module.\n");
	loadChunk(Mod);

	printf("\nDone loading %s", filename);
	return true;
}
void callFunction(const char* name, unsigned int argc, ...)
{
	llvm::Function *f = LLVM_Module->getFunction(name);
	if (f)
	{
		std::vector<llvm::GenericValue> args;
		if (argc > 0) 
		{
			va_list argv;

			va_start(argv, argc);
			for (unsigned int x = 0; x < argc; x++) {
				llvm::GenericValue v;
				v.DoubleVal = (va_arg(argv, double)); // this might not work for anything other than doubles!
				args.push_back(v);
			}
			va_end(argv);
		}
		LLVM_ExecE->runFunction(f, args);
	}
	else 
	{
		printf("\nTried to execute non-existent function '%c'.\n", name);
	}
}

template<typename T>
const static void* void_cast(const T& object)
{
	union Retyper
	{
		const T object;
		void* pointer;
		Retyper(T obj) : object(obj) { }
	};

	return Retyper(object).pointer;
}
template<typename T, typename M>
const static void* getMethodPointer(const T* object, M method) // will work for virtual methods
{
	union MethodEntry
	{
		intptr_t offset;
		void* function;
	};

	const MethodEntry* entry = static_cast<const MethodEntry*>(void_cast(&method));

	if (entry->offset % sizeof(intptr_t) == 0) // looks like that's how the runtime guesses virtual from static
		return getMethodPointer(method);

	const void* const* const vtable = *reinterpret_cast<const void* const* const* const>(object);
	return vtable[(entry->offset - 1) / sizeof(void*)];
}
template<typename M>
const static void* getMethodPointer(M method) // will only work with non-virtual methods
{
	union MethodEntry
	{
		intptr_t offset;
		void* function;
	};

	return static_cast<const MethodEntry*>(void_cast(&method))->function;
}

static llvm::Type* voidptr_type = NULL;

void registerSymbolFunction(const char* mangled_name, void* func)
{
	// This is the JIT way for register a function
	// Commented "sig.push_back" before defined function because they don't seem to need args
	std::vector<llvm::Type*> sig;
	llvm::FunctionType* FT = NULL;
	llvm::Function* F = NULL;

	sig.clear();
	//sig.push_back(LLVM_pointerType);
	// Get the function type
	FT = llvm::FunctionType::get(voidptr_type, sig, false);
	// Create the function
	F = llvm::Function::Create(FT, llvm::Function::ExternalLinkage, mangled_name, LLVM_Module);
	// Register the function to the global module
	// This method register the function to the module and the ExecutionEngine
	LLVM_ExecE->addGlobalMapping(F, func);

	// This also work, i don't know what method is better, addGlobalMapping seem to be for the JIT
	// and AddSymbol for the interpreter
	// This method register the function directly to LLVM
	//llvm::sys::DynamicLibrary::AddSymbol(mangled_name, func);
}

std::string getPrintText()
{
	printf("\nRunning getPrintText()...\n");
	return std::string("Hello world from getPrintText()!");
}
class TestClass1
{
public:
	static void test1()
	{
		printf("\nHello World from TestClass1::test1()!\n");
	}
	void test2(std::string s)
	{
		printf("\nHello World from TestClass1::test2()! : %s\n",+s.data());
		//ConsoleM.newMsg(S+"Hello World from TestClass1::test2()! : "+s.data(), YELLOW);
	}
	void test3(const char* c)
	{
		printf("\nHello World from TestClass1::test3()! : %s\n",+c);
		//ConsoleM.newMsg(S+"Hello World from TestClass1::test2()! : "+s.data(), YELLOW);
	}
} testclass1;

int main(int argc, char** argv, char* const* envp)
{
	printf("argc: %i ", argc);
	printf("argv: %c ", argv);
	printf("envp: %c ", envp);

	llvm::sys::PrintStackTraceOnErrorSignal();
	llvm::PrettyStackTraceProgram X(argc, argv);

	LLVM_Context = new llvm::LLVMContext();

	// If we have a native target, initialize it to ensure it is linked in and
	// usable by the JIT.
	llvm::InitializeNativeTarget();
	llvm::InitializeNativeTargetAsmPrinter();

	llvm::SMDiagnostic Err;
	LLVM_Module = new llvm::Module("LLVM Test", *LLVM_Context);
	if (!LLVM_Module) {
		Err.print(argv[0], llvm::errs());
		return 1;
	}

	std::string ErrorMsg;
	llvm::EngineBuilder builder(LLVM_Module);
	builder.setErrorStr(&ErrorMsg);
	builder.setJITMemoryManager(llvm::JITMemoryManager::CreateDefaultMemManager());
	builder.setEngineKind(llvm::EngineKind::JIT);

	//CodeGenOpt::Level OLvl = CodeGenOpt::Default;

	//builder.setOptLevel(OLvl);

	llvm::TargetOptions Options;
	Options.JITExceptionHandling = false;
	Options.JITEmitDebugInfo = false;
	Options.JITEmitDebugInfoToDisk = false;
	builder.setTargetOptions(Options);

	LLVM_ExecE = builder.create();
	if (!LLVM_ExecE) {
		if (!ErrorMsg.empty())
			llvm::errs() << argv[0] << ": error creating ExecE: " << ErrorMsg << "\n";
		else
			llvm::errs() << argv[0] << ": unknown error creating ExecE!\n";
		exit(1);
	}

	// The following functions have no effect if their respective profiling
	// support wasn't enabled in the build configuration.
	LLVM_ExecE->RegisterJITEventListener(
		llvm::JITEventListener::createOProfileJITEventListener());
	LLVM_ExecE->RegisterJITEventListener(
		llvm::JITEventListener::createIntelJITEventListener());

	LLVM_ExecE->DisableLazyCompilation(false);


	voidptr_type = llvm::IntegerType::get(*LLVM_Context, 8)->getPointerTo();

	LLVM_pointerType = (llvm::Type*)llvm::Type::getInt32Ty(*LLVM_Context);


	registerSymbolFunction("_Z12getPrintTextv", (void*)&getPrintText);

	registerSymbolFunction("_ZdlPv", (void*)(void(*) (void*))operator delete);

	registerSymbolFunction("_ZN10TestClass15test2ESs", (void*)getMethodPointer(&TestClass1::test2));

	registerSymbolFunction("__mingw_vprintf", (void*)printf);

	//registerSymbolFunction("_ZNSs4_Rep20_S_empty_rep_storageE", (void*)std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_empty_rep_storage);
	
	registerSymbolFunction("_ZN10TestClass15test3EPKc", (void*)getMethodPointer(&TestClass1::test3));
	

	//registerSymbolFunction("_ZN10TestClass15test1Ev", (void *)TestClass1::test1);


	// Run static constructors.
	LLVM_ExecE->runStaticConstructorsDestructors(LLVM_Module, false);

	loadFile("engine_test.bc");

	// Run main.
	std::vector<llvm::GenericValue> args;
	LLVM_ExecE->runFunction(LLVM_Module->getFunction("_Z4Initv"), args);

	// Run static destructors.
	LLVM_ExecE->runStaticConstructorsDestructors(true);

	return 1;
}