#include "ffi.hpp"

#include <cstring> // memcpy

#if SOUP_WINDOWS
#include <windows.h>
#endif

#include "memGuard.hpp"

NAMESPACE_SOUP
{
	bool ffi::isSafeToCall(void* func) noexcept
	{
		bool ret = true;
#if SOUP_WINDOWS
		auto ntdll = LoadLibraryA("ntdll.dll");
		if (GetProcAddress(ntdll, "NtRaiseHardError") == func)
		{
			ret = false;
		}
		FreeLibrary(ntdll);
#endif
		return ret;
	}

	uintptr_t ffi::call(void* func, const uintptr_t* args, size_t nargs)
	{
		// Generated by codegen/ffi.php
		switch (nargs)
		{
		case 0: return reinterpret_cast<uintptr_t(*)()>(func)();
		case 1: return reinterpret_cast<uintptr_t(*)(uintptr_t)>(func)(args[0]);
		case 2: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t)>(func)(args[0], args[1]);
		case 3: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2]);
		case 4: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3]);
		case 5: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4]);
		case 6: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5]);
		case 7: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
		case 8: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
		case 9: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
		case 10: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
		case 11: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]);
		case 12: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]);
		case 13: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]);
		case 14: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]);
		case 15: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]);
		case 16: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]);
		case 17: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]);
		case 18: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]);
		case 19: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]);
		case 20: return reinterpret_cast<uintptr_t(*)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t)>(func)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]);
		}
		SOUP_THROW(BadCall());
	}

#if SOUP_FFI_CALLBACK_AVAILABLE
	struct FfiCallbackTls
	{
		uintptr_t a, b, c, d;
#if SOUP_ARM || SOUP_BITS == 32 || !SOUP_WINDOWS
		uintptr_t e, f;
#endif
#if SOUP_ARM || SOUP_BITS == 32
		uintptr_t g, h;
#endif
#if SOUP_X86 && SOUP_BITS == 32
		uintptr_t i, j, k, l, m, n, o, p, q, r, s, t;
#endif
	};

	static thread_local FfiCallbackTls ffi_callback_tls;

	static void callback_save_args(
#if SOUP_X86 && SOUP_BITS == 32
		void* _retaddr,
#endif
		uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d
#if SOUP_ARM || SOUP_BITS == 32 || !SOUP_WINDOWS
		, uintptr_t e, uintptr_t f
#endif
#if SOUP_ARM || SOUP_BITS == 32
		, uintptr_t g, uintptr_t h
#endif
#if SOUP_X86 && SOUP_BITS == 32
		, uintptr_t i, uintptr_t j, uintptr_t k, uintptr_t l, uintptr_t m, uintptr_t n, uintptr_t o, uintptr_t p, uintptr_t q, uintptr_t r, uintptr_t s, uintptr_t t
#endif
	)
	{
		ffi_callback_tls.a = a;
		ffi_callback_tls.b = b;
		ffi_callback_tls.c = c;
		ffi_callback_tls.d = d;
#if SOUP_ARM || SOUP_BITS == 32 || !SOUP_WINDOWS
		ffi_callback_tls.e = e;
		ffi_callback_tls.f = f;
#endif
#if SOUP_ARM || SOUP_BITS == 32
		ffi_callback_tls.g = g;
		ffi_callback_tls.h = h;
#endif
#if SOUP_X86 && SOUP_BITS == 32
		ffi_callback_tls.i = i;
		ffi_callback_tls.j = j;
		ffi_callback_tls.k = k;
		ffi_callback_tls.l = l;
		ffi_callback_tls.m = m;
		ffi_callback_tls.n = n;
		ffi_callback_tls.o = o;
		ffi_callback_tls.p = p;
		ffi_callback_tls.q = q;
		ffi_callback_tls.r = r;
		ffi_callback_tls.s = s;
		ffi_callback_tls.t = t;
#endif
	}

	static uintptr_t callback_finish(
		uintptr_t(*func)(uintptr_t user_data, const uintptr_t* args), uintptr_t user_data
		, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f, uintptr_t g, uintptr_t h, uintptr_t i, uintptr_t j, uintptr_t k, uintptr_t l, uintptr_t m, uintptr_t n, uintptr_t o, uintptr_t p, uintptr_t q, uintptr_t r, uintptr_t s, uintptr_t t
		)
	{
		const uintptr_t args[20] = {
			ffi_callback_tls.a, ffi_callback_tls.b, ffi_callback_tls.c, ffi_callback_tls.d,
#if SOUP_ARM || SOUP_BITS == 32 || !SOUP_WINDOWS
			ffi_callback_tls.e, ffi_callback_tls.f,
#else
			e, f,
#endif
#if SOUP_ARM || SOUP_BITS == 32
			ffi_callback_tls.g, ffi_callback_tls.h,
#else
			g, h,
#endif
#if SOUP_X86 && SOUP_BITS == 32
			ffi_callback_tls.i, ffi_callback_tls.j, ffi_callback_tls.k, ffi_callback_tls.l, ffi_callback_tls.m, ffi_callback_tls.n, ffi_callback_tls.o, ffi_callback_tls.p, ffi_callback_tls.q, ffi_callback_tls.r, ffi_callback_tls.s, ffi_callback_tls.t,
#else
			i, j, k, l, m, n, o, p, q, r, s, t,
#endif
		};
		return func(user_data, args);
	}

	static const uint8_t callback_bytes[] = {
#if SOUP_X86
  #if SOUP_BITS == 32
		/*  0 */ 0xB8, 0, 0, 0, 0,									// mov     eax, (4 bytes) ; callback_save_args
		/*  5 */ 0xFF, 0xD0,										// call    eax
		/*  7 */ 0xC7, 0x44, 0x24, 0x04, 0, 0, 0, 0,				// mov     DWORD PTR [esp+0x4], (4 bytes) ; func
		/* 15 */ 0xC7, 0x44, 0x24, 0x08, 0, 0, 0, 0,				// mov     DWORD PTR [esp+0x8], (4 bytes) ; user_data
		/* 23 */ 0xB8, 0, 0, 0, 0,									// mov     eax, (4 bytes) ; callback_finish
		/* 28 */ 0xFF, 0xE0,										// jmp     eax
  #else
		/*  0 */ 0x48, 0x81, 0xEC, 0xA8, 0x00, 0x00, 0x00,			// sub     rsp, 0xa8
		/*  7 */ 0xFF, 0x15, (46 - 13), 0, 0, 0,					// call    QWORD PTR [rip+...] ; callback_save_args
		/* 13 */ 0x48, 0x81, 0xC4, 0xA8, 0x00, 0x00, 0x00,			// add     rsp, 0xa8
	#if SOUP_WINDOWS
		/* 20 */ 0x48, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,				// mov     rcx, (8 bytes) ; func
		/* 30 */ 0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,				// mov     rdx, (8 bytes) ; user_data
	#else
		/* 20 */ 0x48, 0xBF, 0, 0, 0, 0, 0, 0, 0, 0,				// mov     rdi, (8 bytes) ; func
		/* 30 */ 0x48, 0xBE, 0, 0, 0, 0, 0, 0, 0, 0,				// mov     rsi, (8 bytes) ; user_data
	#endif
		/* 40 */ 0xFF, 0x25, 0x08, 0x00, 0x00, 0x00,				// jmp     QWORD PTR [rip+0x8] ; callback_finish
		/* 46 */ 0, 0, 0, 0, 0, 0, 0, 0,							// callback_save_args
		/* 54 */ 0, 0, 0, 0, 0, 0, 0, 0,							// callback_finish
  #endif
#else
		/*  0 */ 0xff, 0x43, 0x00, 0xd1, // sub sp, sp, #16
		/*  4 */ 0xfe, 0x27, 0x00, 0xa9, // stp x30, x9, [sp]
		/*  8 */ 0x89, 0x01, 0x00, 0x10, // adr x9, #48 ; callback_save_args (56 - 8)
		/* 12 */ 0x29, 0x01, 0x40, 0xf9, // ldr x9, [x9]
		/* 16 */ 0x20, 0x01, 0x3f, 0xd6, // blr x9
		/* 20 */ 0xfe, 0x27, 0x40, 0xa9, // ldp x30, x9, [sp]
		/* 24 */ 0xff, 0x43, 0x00, 0x91, // add sp, sp, #16
		/* 28 */ 0x20, 0x01, 0x00, 0x10, // adr x0, #36 ; func (64 - 28)
		/* 32 */ 0x00, 0x00, 0x40, 0xf9, // ldr x0, [x0]
		/* 36 */ 0x21, 0x01, 0x00, 0x10, // adr x1, #36 ; user_data (72 - 36)
		/* 40 */ 0x21, 0x00, 0x40, 0xf9, // ldr x1, [x1]
		/* 44 */ 0x22, 0x01, 0x00, 0x10, // adr x2, #36 ; callback_finish (80 - 44)
		/* 48 */ 0x42, 0x00, 0x40, 0xf9, // ldr x2, [x2]
		/* 52 */ 0x40, 0x00, 0x1f, 0xd6, // br x2
		/* 56 */ 0, 0, 0, 0, 0, 0, 0, 0, // callback_save_args
		/* 64 */ 0, 0, 0, 0, 0, 0, 0, 0, // func
		/* 72 */ 0, 0, 0, 0, 0, 0, 0, 0, // user_data
		/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, // callback_finish
#endif
	};
#endif

#if SOUP_FFI_CALLBACK_AVAILABLE
	void* ffi::callbackAlloc(uintptr_t(*func)(uintptr_t user_data, const uintptr_t* args), uintptr_t user_data) noexcept
	{
#if SOUP_X86
		void* block = memGuard::alloc(sizeof(callback_bytes), memGuard::ACC_RWX);
		SOUP_IF_LIKELY (block)
		{
			memcpy(block, callback_bytes, sizeof(callback_bytes));
	#if SOUP_BITS == 32
			*(void**)((uint8_t*)block + 0 + 1) = (void*)&callback_save_args;
			*(void**)((uint8_t*)block + 7 + 4) = (void*)func;
			*(uintptr_t*)((uint8_t*)block + 15 + 4) = user_data;
			*(void**)((uint8_t*)block + 23 + 1) = (void*)&callback_finish;
	#else
			*(void**)((uint8_t*)block + 20 + 2) = (void*)func;
			*(uintptr_t*)((uint8_t*)block + 30 + 2) = user_data;
			*(void**)((uint8_t*)block + sizeof(callback_bytes) - sizeof(void*) * 2) = (void*)&callback_save_args;
			*(void**)((uint8_t*)block + sizeof(callback_bytes) - sizeof(void*) * 1) = (void*)&callback_finish;
	#endif
		}
		memGuard::setAllowedAccess(block, sizeof(callback_bytes), memGuard::ACC_READ | memGuard::ACC_EXEC);
		return block;
#elif SOUP_ARM && SOUP_BITS == 64
		void* block = memGuard::alloc(sizeof(callback_bytes), memGuard::ACC_RWX);
		SOUP_IF_LIKELY (block)
		{
			memcpy(block, callback_bytes, sizeof(callback_bytes));
			*(void**)((uint8_t*)block + sizeof(callback_bytes) - 8 * 4) = (void*)&callback_save_args;
			*(void**)((uint8_t*)block + sizeof(callback_bytes) - 8 * 3) = (void*)func;
			*(uintptr_t*)((uint8_t*)block + sizeof(callback_bytes) - 8 * 2) = user_data;
			*(void**)((uint8_t*)block + sizeof(callback_bytes) - 8 * 1) = (void*)&callback_finish;
			memGuard::setAllowedAccess(block, sizeof(callback_bytes), memGuard::ACC_READ | memGuard::ACC_EXEC);
		}
		return block;
#else
		return nullptr;
#endif
	}

	void ffi::callbackFree(void* cb) noexcept
	{
		return memGuard::free(cb, sizeof(callback_bytes));
	}
#endif
}
