/*
 * mii_emu_gl.c
 *
 * Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
 *
 * SPDX-License-Identifier: MIT
 */
/*
 * This is the main file for the X11/GLX version of the MII emulator
 */
#define _GNU_SOURCE // for asprintf
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <locale.h>
#include <pthread.h>
#include <sys/timerfd.h>
#include <sys/stat.h>
#include <unistd.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <X11/Xatom.h>
#include "mii_mui.h"
#include "mish.h"
#include "mii_thread.h"
#include "mii_ssc.h"
#include "mii_mui_gl.h"
#include "miigl_counter.h"
#define MII_ICON64_DEFINE
#include "mii_icon64.h"

/*
 * Note: This *assumes* that the GL implementation has support for
 * non-power-of-2 * textures, which is not a given for older
 * implementations. However, I think (by 2024) that's a safe assumption.
 */
#define WINDOW_WIDTH 		1280
#define WINDOW_HEIGHT		720

#define MII_MUI_GL_POW2 	0

typedef struct mii_x11_t {
	mii_mui_t			video;
	pthread_t 			cpu_thread;

	Cursor 				cursor;
	Display *			dpy;
	Window 				win;

	Atom 				wm_delete_window;
	int 				width, height;
	GLXContext 			glContext;

//	miigl_counter_t 	videoc, redrawc, sleepc;
} mii_x11_t;


static int gl_err = 0;
static int gl_error_handler(
		Display *dpy,
		XErrorEvent *ev)
{
	gl_err = true;
	return 0;
}

#define die(_w) { fprintf(stderr,"%s\n",_w); exit(1); }

static int
has_gl_extension(
		const char *string,
		const char *ext)
{
	if (!string || !ext)
		return false;
	int l = strlen(ext);
	while (*string) {
		char * gotit = strstr((const char *)string, ext);
		if (!gotit)
			return false;
		if (gotit && (gotit[l] == ' ' || gotit[l] == '\0'))
			return true;
		string += l;
	}
	return false;
}

/*
 * xmodmap -pke or -pk will print the list of keycodes
 */
static bool
_mii_x11_convert_keycode(
		mii_x11_t *ui,
		KeySym sym,
		mui_event_t *out )
{
	switch (sym) {
		case XK_F1 ... XK_F12:
			out->key.key = MUI_KEY_F1 + (sym - XK_F1);
			break;
		case XK_Escape: out->key.key = MUI_KEY_ESCAPE; break;
		case XK_Left: out->key.key = MUI_KEY_LEFT; break;
		case XK_Up: out->key.key = MUI_KEY_UP; break;
		case XK_Right: out->key.key = MUI_KEY_RIGHT; break;
		case XK_Down: out->key.key = MUI_KEY_DOWN; break;
		// XK_Begin
		case XK_Insert: out->key.key = MUI_KEY_INSERT; break;
		case XK_Home: out->key.key = MUI_KEY_HOME; break;
		case XK_End: out->key.key = MUI_KEY_END; break;
		case XK_Page_Up: out->key.key = MUI_KEY_PAGEUP; break;
		case XK_Page_Down: out->key.key = MUI_KEY_PAGEDOWN; break;

		case XK_Shift_R: out->key.key = MUI_KEY_RSHIFT; break;
		case XK_Shift_L: out->key.key = MUI_KEY_LSHIFT; break;
		case XK_Control_R: out->key.key = MUI_KEY_RCTRL; break;
		case XK_Control_L: out->key.key = MUI_KEY_LCTRL; break;
		case XK_Alt_L: out->key.key = MUI_KEY_LALT; break;
		case XK_Alt_R: out->key.key = MUI_KEY_RALT; break;
		case XK_Super_L: out->key.key = MUI_KEY_LSUPER; break;
		case XK_Super_R: out->key.key = MUI_KEY_RSUPER; break;
		case XK_Caps_Lock: out->key.key = MUI_KEY_CAPSLOCK; break;
		default:
			out->key.key = sym & 0xff;
			break;
	}
//	printf("%s %08x to %04x\n", __func__, sym, out->key.key);
	return true;
}

static int
mii_x11_init(
		struct mii_x11_t *ui )
{
//	mui_t * mui = &ui->video.mui;

	if (!setlocale(LC_ALL,"") ||
			!XSupportsLocale() ||
			!XSetLocaleModifiers("@im=none"))
		return -1;

	ui->dpy = XOpenDisplay(NULL);
	if (!ui->dpy)
		die("Failed to open X display\n");
	{
		/* check glx version */
		int glx_major, glx_minor;
		if (!glXQueryVersion(ui->dpy, &glx_major, &glx_minor))
			die("[X11]: Error: Failed to query OpenGL version\n");
		if ((glx_major == 1 && glx_minor < 3) || (glx_major < 1))
			die("[X11]: Error: Invalid GLX version!\n");
	}
	GLXFBConfig 		fbc;
	XVisualInfo *		vis = NULL;
	{
		/* find and pick matching framebuffer visual */
		int fb_count;
		static const GLint attr[] = {
			GLX_X_RENDERABLE, True,
			GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
			GLX_RENDER_TYPE, GLX_RGBA_BIT,
			GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
			GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8,
			GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8,
			None
		};
		GLXFBConfig *fbc_list = glXChooseFBConfig(ui->dpy,
								DefaultScreen(ui->dpy), attr, &fb_count);
		if (!fbc_list)
			die("[X11]: Error: failed to retrieve framebuffer configuration\n");
		{
			/* pick framebuffer with most samples per pixel */
			int fb_best = -1, best_num_samples = -1;
			for (int i = 0; i < fb_count; ++i) {
				XVisualInfo *vi = glXGetVisualFromFBConfig(ui->dpy, fbc_list[i]);
				if (vi) {
					int sample_buffer, samples;
					glXGetFBConfigAttrib(ui->dpy, fbc_list[i],
								GLX_SAMPLE_BUFFERS, &sample_buffer);
					glXGetFBConfigAttrib(ui->dpy, fbc_list[i],
								GLX_SAMPLES, &samples);
					if ((fb_best < 0) ||
							(sample_buffer && samples > best_num_samples))
						fb_best = i, best_num_samples = samples;
					XFree(vi);
				}
			}
			fbc = fbc_list[fb_best];
			XFree(fbc_list);
			vis = glXGetVisualFromFBConfig(ui->dpy, fbc);
		}
	}
	{
		/* create window */
		XSetWindowAttributes swa = {
			.colormap = XCreateColormap(ui->dpy,
						RootWindow(ui->dpy, vis->screen),
						vis->visual, AllocNone),
			.background_pixmap = None,
			.border_pixel = 0,
			.event_mask =
				ExposureMask | KeyPressMask | KeyReleaseMask |
				ButtonPress | ButtonReleaseMask | ButtonMotionMask |
				Button1MotionMask | Button3MotionMask |
				Button4MotionMask | Button5MotionMask |
				PointerMotionMask | StructureNotifyMask | FocusChangeMask,
		};
		ui->win = XCreateWindow(ui->dpy,
					RootWindow(ui->dpy, vis->screen), 0, 0,
					WINDOW_WIDTH, WINDOW_HEIGHT, 0, vis->depth, InputOutput,
					vis->visual, CWBorderPixel | CWColormap | CWEventMask,
					&swa);
		if (!ui->win)
			die("[X11]: Failed to create window\n");
		XFree(vis);
		XFreeColormap(ui->dpy, swa.colormap);
		{
			char title[128];
			sprintf(title, "MII //e Emulator");
			char *telnet = getenv("MISH_TELNET_PORT");
			if (telnet)
				sprintf(title + strlen(title), " (telnet port %s)", telnet);
			else
				sprintf(title + strlen(title), " (telnet disabled)");
			XStoreName(ui->dpy, ui->win, title);
		}
		{
			Atom net_wm_icon_atom = XInternAtom(ui->dpy, "_NET_WM_ICON", False);
			XChangeProperty(ui->dpy, ui->win, net_wm_icon_atom, XA_CARDINAL,
						32, PropModeReplace,
						(unsigned char *)mii_icon64,
						sizeof(mii_icon64) / sizeof(mii_icon64[0]));
			XFlush(ui->dpy);
		}
		{
			XSizeHints *hints = XAllocSizeHints();
			hints->flags = PMinSize | PAspect;// | PMaxSize;
			hints->min_width = WINDOW_WIDTH / 2;
			hints->min_height = WINDOW_HEIGHT / 2;
			hints->max_aspect.x = WINDOW_WIDTH;
			hints->max_aspect.y = WINDOW_HEIGHT;
			hints->min_aspect.x = WINDOW_WIDTH;
			hints->min_aspect.y = WINDOW_HEIGHT;
			XSetWMNormalHints(ui->dpy, ui->win, hints);
			XFree(hints);
		}
		XMapWindow(ui->dpy, ui->win);
		ui->wm_delete_window = XInternAtom(ui->dpy, "WM_DELETE_WINDOW", False);
		XSetWMProtocols(ui->dpy, ui->win, &ui->wm_delete_window, 1);
	}
	/* create invisible cursor */
	{
		static XColor dummy; char data[1] = {0};
		Pixmap blank = XCreateBitmapFromData(ui->dpy, ui->win, data, 1, 1);
		if (blank == None) return 0;
		ui->cursor = XCreatePixmapCursor(ui->dpy, blank, blank, &dummy, &dummy, 0, 0);
		XFreePixmap(ui->dpy, blank);
	}
	{
		/* create opengl context */
		typedef GLXContext (*glxCreateContext)(
						Display *, GLXFBConfig, GLXContext, Bool, const int *);
		int (*old_handler)(Display *, XErrorEvent *) =
						XSetErrorHandler(gl_error_handler);
		const char *extensions_str = glXQueryExtensionsString(
						ui->dpy, DefaultScreen(ui->dpy));
		glxCreateContext create_context = (glxCreateContext)
			glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");

		gl_err = false;
		if (!has_gl_extension(extensions_str, "GLX_ARB_create_context") ||
				!create_context) {
			fprintf(stdout, "[X11]: glXCreateContextAttribARB() not found...\n");
			fprintf(stdout, "[X11]: ... using old-style GLX context\n");
			ui->glContext = glXCreateNewContext(
						ui->dpy, fbc, GLX_RGBA_TYPE, 0, True);
		} else {
			GLint attr[] = {
				GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
				GLX_CONTEXT_MINOR_VERSION_ARB, 0,
				None
			};
			ui->glContext = create_context(ui->dpy, fbc, 0, True, attr);
			XSync(ui->dpy, False);
			if (gl_err || !ui->glContext) {
				attr[1] = 1;
				attr[3] = 0;
				gl_err = false;
				fprintf(stdout, "[X11] Failed to create OpenGL 3.0 context\n");
				fprintf(stdout, "[X11] ... using old-style GLX context!\n");
				ui->glContext = create_context(ui->dpy, fbc, 0, True, attr);
			}
		}
		XSync(ui->dpy, False);
		XSetErrorHandler(old_handler);
		if (gl_err || !ui->glContext)
			die("[X11]: Failed to create an OpenGL context\n");
		glXMakeCurrent(ui->dpy, ui->win, ui->glContext);
	}
	return 0;
}


static void
mui_read_clipboard(
		struct mui_t *mui)
{
	FILE *f = popen("xclip -selection clipboard -o", "r");
	if (!f) {
		perror("popen xclip");
		return;
	}
	mui_utf8_t clip = {};
	char buf[1024];
	size_t r = 0;
	do {
		r = fread(buf, 1, sizeof(buf), f);
		if (r > 0)
			mui_utf8_append(&clip, (uint8_t*)buf, r);
	} while (r > 0);
	pclose(f);
	mui_utf8_free(&mui->clipboard);
	mui->clipboard = clip;
}

static int
mii_x11_handle_event(
		mii_x11_t * ui,
		XEvent *evt)
{
	int quit = 0;

	/* We don't actually 'grab' as in warp the pointer, we just show/hide it
	 * dynamically when we enter/leave the video rectangle */
	if (ui->video.mouse.grab) {
		XDefineCursor(ui->dpy, ui->win, ui->cursor);
		ui->video.mouse.grab = 0;
	} else if (ui->video.mouse.ungrab) {
		XUndefineCursor(ui->dpy, ui->win);
		ui->video.mouse.ungrab = 0;
	}
	mui_t * mui = &ui->video.mui;
	mii_t * mii = &ui->video.mii;

	switch (evt->type) {
		case FocusIn:
		case FocusOut:
		//	printf("%s focus %d\n", __func__, evt->type == FocusIn);
			/* This prevents 'stale' modifiers keys when the emulator is
			 * in front and we switches window/desktop with a modifier on..
			 * this used to trigger 'phantom' modifiers still being 'on' */
			mui->modifier_keys = 0;
			break;
		case KeyRelease:
		case KeyPress: {
			int ret, down = (evt->type == KeyPress);
			KeySym *code = XGetKeyboardMapping(ui->dpy,
						(KeyCode)evt->xkey.keycode, 1, &ret);
			ui->video.key.type = down ? MUI_EVENT_KEYDOWN : MUI_EVENT_KEYUP;
			ui->video.key.key.up = 0;
			bool handled = false;
			bool converted = _mii_x11_convert_keycode(ui, *code,
											&ui->video.key);
			bool is_modifier = ui->video.key.key.key >= MUI_KEY_MODIFIERS &&
						ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST;
			if (converted) {
				// convert keycodes into a bitfields of current modifiers
				if (ui->video.key.key.key >= MUI_KEY_MODIFIERS &&
						ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST) {
					if (down)
						mui->modifier_keys |= (1 <<
								(ui->video.key.key.key - MUI_KEY_MODIFIERS));
					else
						mui->modifier_keys &= ~(1 <<
								(ui->video.key.key.key - MUI_KEY_MODIFIERS));
				}
				ui->video.key.modifiers = mui->modifier_keys;
				switch (ui->video.key.key.key) {
#if 0
					case MUI_KEY_RALT:
					case MUI_KEY_LALT: {
						int apple = ui->video.key.key.key - MUI_KEY_RALT;
#else
					case MUI_KEY_RSUPER:
					case MUI_KEY_LSUPER: {
						int apple = ui->video.key.key.key - MUI_KEY_LSUPER;
#endif
						mii_bank_t *bank = &mii->bank[MII_BANK_SW];
						uint8_t old = mii_bank_peek(bank, 0xc061 + apple);
						mii_bank_poke(bank, 0xc061 + apple, down ? 0x80 : 0);
						if (!!down != !!old) {
						//	printf("Apple %s %s\n", apple ? "Open" : "Close",
						//		down ? "down" : "up");
						}
					}	break;
				}
				handled = mui_handle_event(mui, &ui->video.key);
				// if not handled and theres a window visible, assume
				// it's a dialog and it OUGHT to eat the key
				if (!handled)
					handled = mui_window_front(mui) != NULL;
			//	printf("%s key handled %d\n", __func__, handled);
			}
			if (!handled && down && !is_modifier) {
				uint16_t mii_key = ui->video.key.key.key;
                char buf[32] = "";
                KeySym keysym = 0;
                if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) {
		#if 0
					printf("   lookup sym %04x %d:", (int)keysym, (int)strlen(buf));
					for (int i = 0; i < (int)strlen(buf); i++)
						printf("  %02x", buf[i]);
					printf("\n");
		#endif
					mii_key = buf[0];
                }
				switch (mii_key) {
					case MUI_KEY_DOWN: 	mii_key = 'J' - 'A' + 1; break;
					case MUI_KEY_UP: 	mii_key = 'K' - 'A' + 1; break;
					case MUI_KEY_RIGHT: mii_key = 'U' - 'A' + 1; break;
					case MUI_KEY_LEFT: 	mii_key = 'H' - 'A' + 1; break;
				}
//				printf("key %04x %4x\n", mii_key, mui->modifier_keys);
				/* control shift V is hard coded! */
				if (mii_key == 0x016 &&
						(mui->modifier_keys & MUI_MODIFIER_SHIFT) &&
						(mui->modifier_keys & MUI_MODIFIER_CTRL)) {
					printf("Paste\n");
					mui_read_clipboard(mui);
					if (mui->clipboard.count) {
						mui_utf8_add(&mui->clipboard, 0);
						// convert newlines
						for (uint i = 0; i < mui->clipboard.count; i++)
							if (mui->clipboard.e[i] == '\n')
								mui->clipboard.e[i] = '\r';
						mii_th_signal_t sig = {
							.cmd = SIGNAL_PASTE,
							.ptr = mui->clipboard.e,
						};
						mui->clipboard.e = NULL;
						mui->clipboard.count = mui->clipboard.size = 0;
						mii_th_fifo_write(mii_thread_get_fifo(mii), sig);
					}
				} else
					mii_keypress(mii, mii_key);
			}
			XFree(code);
		}	break;
		case ButtonPress:
		case ButtonRelease: {
		//	printf("%s %s button %d grabbed:%d\n", __func__,
		//			evt->type == ButtonPress ? "Down":"Up  ",
		//			evt->xbutton.button, ui->video.mouse.grabbed);
			switch (evt->xbutton.button) {
				case Button1: {
					ui->video.mouse.down = evt->type == ButtonPress;
					ui->video.mouse.pos.x = evt->xbutton.x;
					ui->video.mouse.pos.y = evt->xbutton.y;
					if (ui->video.mui_visible) {
						mui_event_t ev = {
							.type = ui->video.mouse.down ?
										MUI_EVENT_BUTTONDOWN :
										MUI_EVENT_BUTTONUP,
							.mouse.where = ui->video.mouse.pos,
							.modifiers = mui->modifier_keys, // | MUI_MODIFIER_EVENT_TRACE,
						};
						mui_handle_event(mui, &ev);
					}
					mii_mui_update_mouse_card(&ui->video);
				}	break;
				case Button4:
				case Button5: {
				//	printf("%s wheel %d %d\n", __func__,
				//			evt->xbutton.button, ui->video.mui_visible);
					if (ui->video.mui_visible) {
						mui_event_t ev = {
							.type = MUI_EVENT_WHEEL,
							.modifiers = mui->modifier_keys,// | MUI_MODIFIER_EVENT_TRACE,
							.wheel.where = ui->video.mouse.pos,
							.wheel.delta = evt->xbutton.button == Button4 ? -1 : 1,
						};
						mui_handle_event(mui, &ev);
					}
				}	break;
			}
		}	break;
		case ConfigureNotify:
			if (evt->xconfigure.width != ui->video.window_size.x ||
				evt->xconfigure.height != ui->video.window_size.y) {
				// Window is being resized
				// Handle the resize event here
				ui->video.window_size.x = evt->xconfigure.width;
				ui->video.window_size.y = evt->xconfigure.height;
				ui->video.video_frame = mii_mui_get_video_position(&ui->video);
				mii_mui_update_mouse_card(&ui->video);
			}
			break;
		case MotionNotify: {
			ui->video.mouse.pos.x = evt->xmotion.x;
			ui->video.mouse.pos.y = evt->xmotion.y;
			mii_mui_update_mouse_card(&ui->video);
			if (ui->video.mouse.grabbed)
				break;
			if (ui->video.mui_visible) {
				mui_event_t ev = {
					.type = MUI_EVENT_DRAG,
					.mouse.where = ui->video.mouse.pos,
					.modifiers = mui->modifier_keys,
				};
				mui_handle_event(mui, &ev);
			}
		}	break;
		case ClientMessage:
			if ((Atom)evt->xclient.data.l[0] == ui->wm_delete_window)
				quit = 1;
			break;
		case KeymapNotify:
			XRefreshKeyboardMapping(&evt->xmapping);
			break;
	}
	return quit;
}

static void
mii_x11_terminate(
		mii_x11_t *ui)
{
	glXMakeCurrent(ui->dpy, 0, 0);
	glXDestroyContext(ui->dpy, ui->glContext);
	XUnmapWindow(ui->dpy, ui->win);
	XDestroyWindow(ui->dpy, ui->win);
	XCloseDisplay(ui->dpy);
}

// TODO factor this into a single table, this is dupped from mii_mui_settings.c!
static const struct {
	const char * name;
} mii_slot_driver[MII_SLOT_DRIVER_COUNT] = {
	[MII_SLOT_DRIVER_NONE] 		= { "none", },
	[MII_SLOT_DRIVER_SMARTPORT]	= { "smartport", },
	[MII_SLOT_DRIVER_DISK2] 	= { "disk2", },
	[MII_SLOT_DRIVER_MOUSE] 	= { "mouse", },
	[MII_SLOT_DRIVER_SSC] 		= { "ssc", },
	[MII_SLOT_DRIVER_ROM1MB]	= { "eecard", },
	[MII_SLOT_DRIVER_MOCKINGBOARD] = { "mockingboard", },
#ifdef MII_DANII
	[MII_SLOT_DRIVER_DANII] 	= { "danii", },
#endif
};

void
mii_ui_reconfigure_slot(
		mii_t * mii,
		mii_machine_config_t * config,
		int slot )
{
	printf("%s slot %d\n", __func__, slot);
	int i = slot-1;
	switch (config->slot[i].driver) {
		case MII_SLOT_DRIVER_SMARTPORT: {
			mii_slot_command(mii, slot,
					MII_SLOT_DRIVE_LOAD,
					(void*)config->slot[i].conf.smartport.drive[0].disk);
			mii_slot_command(mii, slot,
					MII_SLOT_DRIVE_LOAD + 1,
					(void*)config->slot[i].conf.smartport.drive[1].disk);
		}	break;
		case MII_SLOT_DRIVER_DISK2: {
			for (int di = 0; di < 2; di++) {
				mii_slot_command(mii, slot,
						MII_SLOT_DRIVE_LOAD + di,
						(void*)config->slot[i].conf.disk2.drive[di].disk);
				int wp = config->slot[i].conf.disk2.drive[di].wp;
				mii_slot_command(mii, slot,
						MII_SLOT_DRIVE_WP + di,
						(void*)&wp);
			}
		}	break;
		case MII_SLOT_DRIVER_ROM1MB: {
			mii_slot_command(mii, slot,
					MII_SLOT_DRIVE_LOAD,
					config->slot[i].conf.rom1mb.use_default ?
						"" :
						(void*)config->slot[i].conf.rom1mb.drive.disk);
		}	break;
		case MII_SLOT_DRIVER_SSC: {
			mii_ssc_setconf_t ssc = {
				.baud = config->slot[i].conf.ssc.baud,
				.bits = config->slot[i].conf.ssc.bits,
				.parity = config->slot[i].conf.ssc.parity,
				.stop = config->slot[i].conf.ssc.stop,
				.handshake = config->slot[i].conf.ssc.hw_handshake,
				.is_device = config->slot[i].conf.ssc.kind == MII_SSC_KIND_DEVICE,
				.is_socket = config->slot[i].conf.ssc.kind == MII_SSC_KIND_SOCKET,
				.is_pty = config->slot[i].conf.ssc.kind == MII_SSC_KIND_PTY,
				.socket_port = config->slot[i].conf.ssc.socket_port,
			};
			strncpy(ssc.device, config->slot[i].conf.ssc.device,
						sizeof(ssc.device)-1);
			mii_slot_command(mii, slot,
					MII_SLOT_SSC_SET_TTY,
					(void*)&ssc);
		}	break;
	}
}

// load the content of the mii_machine_config_t into the initialized
// mii_t struct, similar to what mii_argv_parse does
// TODO: Change argv to modify that struct instead of the mii_t directly
static void
_mii_ui_load_config(
		mii_t * mii,
		mii_machine_config_t * config,
		uint32_t *ioFlags )
{
	if (config->no_slot_clock)
		*ioFlags |= MII_INIT_NSC;
	if (config->titan_accelerator)
		*ioFlags |= MII_INIT_TITAN;
	mii->speaker.muted = config->audio_muted;
	mii->video.color_mode = config->video_mode;
	mii_video_set_mode(mii, config->video_mode);
	for (int i = 0; i < 7; i++) {
		if (config->slot[i].driver == MII_SLOT_DRIVER_NONE)
			continue;
		if (config->slot[i].driver >= MII_SLOT_DRIVER_COUNT) {
			printf("%s invalid driver %d\n", __func__,
					config->slot[i].driver);
			continue;
		}
		int slot =  i + 1;
		if (mii_slot_drv_register(mii, slot,
					mii_slot_driver[config->slot[i].driver].name) < 0) {
			printf("%s failed to register driver %s\n", __func__,
					mii_slot_driver[config->slot[i].driver].name);
		}
		mii_ui_reconfigure_slot(mii, config, slot);
	}
}

// I want at least the 'silent' flags to be 'sticky'
uint32_t g_startup_flags = 0;

void
mii_x11_reload_config(
	struct mii_x11_t *ui )
{
	mii_t * mii = &ui->video.mii;
	mii_machine_config_t * config = &ui->video.config;
	uint32_t flags = MII_INIT_DEFAULT | g_startup_flags;

	if (mii->state != MII_INIT) {
		printf("%s mii is running, terminating thread\n", __func__);
		mii->state = MII_TERMINATE;
		pthread_join(ui->cpu_thread, NULL);
		printf("%s mii terminated\n", __func__);
	}
	mii_mui_menu_slot_menu_update(&ui->video);
	printf("%s (re)loading config\n", __func__);
	// if we're silent from the command line, we are *always* silent.
	mii->speaker.speaker_off = !!(g_startup_flags & MII_INIT_SILENT);
	mii_init(mii);
	_mii_ui_load_config(mii, config, &flags);
	mii_prepare(mii, flags);
	mii_reset(mii, true);
	mii_mui_gl_prepare_textures(&ui->video);

	/* start the CPU/emulator thread */
	ui->cpu_thread = mii_threads_start(mii);
}

mii_x11_t g_mii = {};

int
main(
		int argc,
		const char *argv[])
{
	char * conf_path;
	asprintf(&conf_path, "%s/.local/share/mii", getenv("HOME"));
	mkdir(conf_path, 0755);

	mii_x11_t * ui = &g_mii;
	mii_t * 	mii = &g_mii.video.mii;
	mui_t * 	mui = &g_mii.video.mui;
	bool no_config_found = false;

	if (mii_settings_load(
					&ui->video.cf, getcwd(NULL,0), ".mii_emu_config") < 0 &&
			mii_settings_load(
					&ui->video.cf, conf_path, "mii_emu_gl") < 0) {
		asprintf(&ui->video.cf.path, "%s/.local/share/mii/mii_emu_gl",
					getenv("HOME"));
		printf("%s failed to load settings\n", argv[0]);
		ui->video.config.load_defaults = true;
		no_config_found = true;
	} else {
		mii_emu_load(&ui->video.cf, &ui->video.config);
	}
	{
		mii_init(mii);
		int idx = 1;
		uint32_t flags = MII_INIT_DEFAULT;

		if (!no_config_found) {
			// load the config we have in the UI config struct into the
			// mii config struct
			_mii_ui_load_config(mii, &ui->video.config, &flags);
		}
		int r = mii_argv_parse(mii, argc, argv, &idx, &flags);
		if (r == 0) {
			printf("mii: Invalid argument %s, skipped\n", argv[idx]);
		} else if (r == -1)
			exit(1);
		mii_prepare(mii, flags);
		g_startup_flags = flags;
	}
	{
		mish_prepare(1);
		mish_set_command_parameter(MISH_FCC('m','i','i',' '), mii);
		printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT"));
	}
	mii_x11_init(ui);
	mii_mui_init(&ui->video, C2_PT(WINDOW_WIDTH, WINDOW_HEIGHT));
	mii_mui_gl_init(&ui->video);

	asprintf(&mui->pref_directory, "%s/.local/share/mii", getenv("HOME"));

	// use a 60fps timerfd here as well
	int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
	if (timerfd < 0) {
		perror(__func__);
		goto cleanup;
	}
	mii_thread_set_fps(timerfd, 60);
	/*
	 * If it is the first run, theres no config files, open the
	 * slot dialog to let the user configure some stuff himself
	 */
	if (no_config_found) {
		ui->video.config.reboot_on_load = true;
		mii_config_open_slots_dialog(&ui->video);
	}
	/* start the CPU/emulator thread */
	ui->cpu_thread = mii_threads_start(mii);

	while (mii->state != MII_INIT) {
		/* Input */
		XEvent evt;
		while (XPending(ui->dpy)) {
			XNextEvent(ui->dpy, &evt);
			if (evt.type == ClientMessage) {
				if (evt.xclient.message_type ==
							XInternAtom(ui->dpy, "WM_PROTOCOLS", False) &&
						(Atom)evt.xclient.data.l[0] == ui->wm_delete_window) {
				//	printf("Window close requested!\n");
					goto cleanup;
				}
			}
			if (XFilterEvent(&evt, ui->win))
				continue;
			mii_x11_handle_event(ui, &evt);
		}
		bool draw = mii_mui_gl_run(&ui->video);
		if (draw) {
		//	miigl_counter_tick(&ui->redrawc, miigl_get_time());
		//	XGetWindowAttributes(ui->dpy, ui->win, &ui->attr);
			glViewport(0, 0, ui->video.window_size.x, ui->video.window_size.y);
			mii_mui_showhide_ui_machine(&ui->video);
			mii_mui_gl_render(&ui->video);
			glFlush();
			glXSwapBuffers(ui->dpy, ui->win);
		}
		/* Wait for next frame */
		uint64_t timerv;
		if (read(timerfd, &timerv, sizeof(timerv)) < 0) {
			perror(__func__);
			goto cleanup;
		}
	#if 0
		miigl_counter_tick(&ui->sleepc, miigl_get_time());
		if (!(current_frame % 60))
			printf("VID: %3d Draw:%3d sleep:%3d\n",
					miigl_counter_get_read_size(&ui->videoc),
					miigl_counter_get_read_size(&ui->redrawc),
					miigl_counter_get_read_size(&ui->sleepc));
	#endif
	}
cleanup:
	mii_emu_save(&ui->video.cf, &ui->video.config);
	// this was already done in the mii_thread
//    mii_dispose(mii);
	mii_x11_terminate(ui);
	return 0;
}
