#include "binding-util.h"
#include "binding-types.h"
#include "sharedstate.h"
#include "debugwriter.h"
#include "eventthread.h"
#include "define.h"

#if defined _WIN32
#define OS_W32
#include <shlwapi.h>
#elif unix_like
	#define LINUX
	#ifdef __APPLE__
		#define OS_OSX
	#else
		#define OS_LINUX
	#endif

	#include <fcntl.h>
	#include <sys/stat.h>
	#include <sys/types.h>
	#ifdef __linux__
		#include <sys/inotify.h>
	#endif
	#include <unistd.h>
	#include <cstdio>
	#include <pwd.h>
	#include <string>
	#include <errno.h>
#endif

#include <SDL3/SDL.h>
namespace syswm {}

#define NIKO_X (320 - 16)
#define NIKO_Y ((13 * 16) * 2)

#define BUFFER_SIZE 256

static SDL_Thread *thread = NULL;
static SDL_Mutex *mutex = NULL;
static volatile char message_buffer[BUFFER_SIZE];
static volatile bool active = false;
static volatile int message_len = 0;

#ifdef unix_like
	static std::string NIKO_PIPE_PATH = std::string(getpwuid(getuid())->pw_dir) + "/.oneshot-niko-pipe";
	static volatile int out_pipe = -1;
	void niko_cleanup_pipe(){
		unlink(NIKO_PIPE_PATH.c_str());
		remove(NIKO_PIPE_PATH.c_str());
	}
#endif

int niko_server_thread(void *data){
	(void)data;
#if defined OS_W32
	HANDLE pipe = CreateNamedPipeW(L"\\\\.\\pipe\\oneshot-journal-to-game",
	                               PIPE_ACCESS_OUTBOUND,
	                               PIPE_TYPE_BYTE | PIPE_WAIT,
	                               PIPE_UNLIMITED_INSTANCES,
	                               BUFFER_SIZE,
	                               BUFFER_SIZE,
	                               0,
	                               NULL);
	for (;;) {
		ConnectNamedPipe(pipe, NULL);
		SDL_LockMutex(mutex);
		DWORD written;
		WriteFile(pipe, (const void*)message_buffer, BUFFER_SIZE, &written, NULL);
		active = true;
		SDL_UnlockMutex(mutex);
		FlushFileBuffers(pipe);
		DisconnectNamedPipe(pipe);
	}
	CloseHandle(pipe);
#else
	if (access(NIKO_PIPE_PATH.c_str(), F_OK) != -1) {
		out_pipe = open(NIKO_PIPE_PATH.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
		SDL_LockMutex(mutex);
		active = true;
		if (message_len > 0){
			if (write(out_pipe, (char*)message_buffer, message_len) == -1){
				Debug() << "[niko_server_thread] Failed to write to pipe!";
			}
		}
		SDL_UnlockMutex(mutex);
	}
	return 0;
#endif
}

RB_METHOD(nikoPrepare){
	RB_UNUSED_PARAM;

	// Prime native window info
	//syswm::SDL_SysWMinfo syswindow;
	//SDL_VERSION(&syswindow.version);
	//SDL_GetWindowWMInfo(shState->rtData().window, &syswindow);

#ifdef unix_like
	char path[PATH_MAX];
	std::string journal;

	// Get current path
	if (getcwd(path, sizeof(path)) == NULL) {
		return Qnil;
	}

	#ifdef OS_OSX
		journal = std::string(path) + "/_______.app/Contents/MacOS/_______";
	#else
		journal = std::string(path) + "/_______";
	#endif

	// Run the binary.
	pid_t pid = fork();
	if (pid == 0) {
		execl(journal.c_str(), journal.c_str(), (char*)"niko", (char*)0);
		exit(1);
	}
#endif
	return Qnil;
}

RB_METHOD(nikoStart){
	RB_UNUSED_PARAM;

	// Prime native window info
	//syswm::SDL_SysWMinfo syswindow;
	//SDL_VERSION(&syswindow.version);
	//SDL_GetWindowWMInfo(shState->rtData().window, &syswindow);

#ifdef _WIN32
	// Calculate where to stick the window
	POINT pos;
	pos.x = NIKO_X;
	pos.y = NIKO_Y;

	// idk if shState->rtData().window will work, i inserted it here so that project would at least be build
	SDL_PropertiesID props = SDL_GetWindowProperties(shState->rtData().window);

	HWND hwnd = (HWND)SDL_GetPointerProperty(
	    props,
	    SDL_PROP_WINDOW_WIN32_HWND_POINTER,
	    NULL
	);

	ClientToScreen(hwnd, &pos);
	// Start process
	WCHAR path[MAX_PATH];
	WCHAR args[MAX_PATH];
	GetModuleFileNameW(NULL, path, MAX_PATH);
	PathRemoveFileSpecW(path);
	wcscat(path, L"\\_______.exe");
	wsprintfW(args, L"_______.exe %d %d", pos.x, pos.y);
	STARTUPINFOW si;
	SDL_memset(&si, 0, sizeof(si));
	si.cb = sizeof(si);
	PROCESS_INFORMATION pi;
	CreateProcessW(path, args, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
#else
	// Calculate where to stick the window
	int x, y; // Top-left area of client (hopefully)
	SDL_GetWindowPosition(shState->rtData().window, &x, &y);
	x += NIKO_X;
	y += NIKO_Y;
	char message[32];
	sprintf(message, "%d,%d\n", x, y);

	SDL_LockMutex(mutex);
	message_len = SDL_strlen(message);
	strcpy((char*)message_buffer, message);
	SDL_UnlockMutex(mutex);

	// Clean up connection thread
	if (thread != NULL && out_pipe != -1) {
		SDL_WaitThread(thread, NULL);
		thread = NULL;
	}
	if (out_pipe == -1) {
		// We don't have a pipe open, so spawn the connection thread
		thread = SDL_CreateThread(niko_server_thread, "journal", NULL);
	}
	// Attempt to send it over the tubes
	if (out_pipe != -1) {
		// We have a connection, so send it over
		if (write(out_pipe, (char*)message_buffer, message_len) <= 0) {
			// In the case of an error, close
			close(out_pipe);
			out_pipe = -1;
		}
	}
#endif
	return Qnil;
}

void nikoBindingInit(){
	mutex = SDL_CreateMutex();
#if unix_like
	mkfifo(NIKO_PIPE_PATH.c_str(), 0666);
	atexit(niko_cleanup_pipe);
#endif

	VALUE module = rb_define_module("Niko");

	//Functions
	_rb_define_module_function(module, "get_ready", nikoPrepare);
	_rb_define_module_function(module, "do_your_thing", nikoStart);
}
