/* sortix.c
 * Sortix display(1) support
 * (c) 2021 Juhani 'nortti' Krekelä
 * This file is a part of the Links program, released under GPL.
 */

#include "cfg.h"

#ifdef GRDRV_SORTIX

#include <sys/keycodes.h>

#include <errno.h>
#include <stdint.h>

#include <display.h>
#include <framebuffer.h>

#include "links.h"

struct window_data
{
	uint32_t window_id;
	int modifiers;
	uint32_t *buffer;
};

static int default_window_width = 600;
static int default_window_height = 500;

static struct display_connection* connection;
static struct display_event_handlers event_handlers;

struct graphics_driver sortix_driver;

static struct graphics_device *current_dev;

static void on_disconnect(void *ctx)
{
	if (current_dev->keyboard_handler)
		current_dev->keyboard_handler(current_dev, KBD_CTRL_C, 0);
}

static void on_quit(void *ctx, uint32_t window_id)
{
	struct window_data *window_data = current_dev->driver_data;
	if (window_id != window_data->window_id) return;
	if (current_dev->keyboard_handler)
		current_dev->keyboard_handler(current_dev, KBD_CLOSE, 0);
}

static void on_keyboard(void *ctx, uint32_t window_id, uint32_t codepoint)
{
	(void) ctx;
	struct window_data *window_data = current_dev->driver_data;
	if (window_id != window_data->window_id) return;

	int kbkey = KBKEY_DECODE(codepoint);
	if (kbkey) {
		switch (kbkey) {
			case KBKEY_LALT: window_data->modifiers |= KBD_ALT; break;
			case -KBKEY_LALT: window_data->modifiers &= ~KBD_ALT; break;
			case KBKEY_LSHIFT: case KBKEY_RSHIFT: window_data->modifiers |= KBD_SHIFT; break;
			case -KBKEY_LSHIFT: case -KBKEY_RSHIFT: window_data->modifiers &= ~KBD_SHIFT; break;
			case KBKEY_LCTRL: case KBKEY_RCTRL: window_data->modifiers |= KBD_CTRL; break;
			case -KBKEY_LCTRL: case -KBKEY_RCTRL: window_data->modifiers &= ~KBD_CTRL; break;
		}
	}

	if (current_dev->keyboard_handler) {
		if (kbkey) codepoint = 0;
		switch (codepoint) {
			case '\n': codepoint = KBD_ENTER; break;
			case 127: codepoint = KBD_BS; break;
			case '\t': codepoint = KBD_TAB; break;
		}
		switch (kbkey) {
			case KBKEY_ESC: codepoint = KBD_ESC; break;
			case KBKEY_LEFT: codepoint = KBD_LEFT; break;
			case KBKEY_RIGHT: codepoint = KBD_RIGHT; break;
			case KBKEY_UP: codepoint = KBD_UP; break;
			case KBKEY_DOWN: codepoint = KBD_DOWN; break;
			case KBKEY_INSERT: codepoint = KBD_INS; break;
			case KBKEY_DELETE: codepoint = KBD_DEL; break;
			case KBKEY_HOME: codepoint = KBD_HOME; break;
			case KBKEY_END: codepoint = KBD_END; break;
			case KBKEY_PGUP: codepoint = KBD_PAGE_UP; break;
			case KBKEY_PGDOWN: codepoint = KBD_PAGE_DOWN; break;
			case KBKEY_F1: codepoint = KBD_F1; break;
			case KBKEY_F2: codepoint = KBD_F2; break;
			case KBKEY_F3: codepoint = KBD_F3; break;
			case KBKEY_F4: codepoint = KBD_F4; break;
			case KBKEY_F5: codepoint = KBD_F5; break;
			case KBKEY_F6: codepoint = KBD_F6; break;
			case KBKEY_F7: codepoint = KBD_F7; break;
			case KBKEY_F8: codepoint = KBD_F8; break;
			case KBKEY_F9: codepoint = KBD_F9; break;
			case KBKEY_F10: codepoint = KBD_F10; break;
			case KBKEY_F11: codepoint = KBD_F11; break;
			case KBKEY_F12: codepoint = KBD_F12; break;
		}
		if (codepoint)
			current_dev->keyboard_handler(current_dev, codepoint, window_data->modifiers);
	}
}

static void on_resize(void *ctx, uint32_t window_id, uint32_t width, uint32_t height)
{
	(void) ctx;
	struct window_data *window_data = current_dev->driver_data;
	if (window_id != window_data->window_id) return;

	if (!width || !height) return;

	free(window_data->buffer);
	// TODO: Overflow when multiplying
	window_data->buffer = mem_alloc(width * height * sizeof(uint32_t));

	current_dev->size.x2 = width;
	current_dev->size.y2 = height;
	if (current_dev->resize_handler)
		current_dev->resize_handler(current_dev);
}

static void sortix_process_events(void *data)
{
	(void) data;
	if (!current_dev) return;
	while (display_poll_event(connection, &event_handlers) == 0);
}

static unsigned char *sortix_init_driver(unsigned char *param, unsigned char *display)
{
	if (param) {
		if (sscanf(param, "%ix%i", &default_window_width, &default_window_height) != 2)
			return stracpy("-mode syntax is WIDTHxHEIGHT\n");
	}
	(void) display; // Only used by the X11 driver

	connection = display_connect_default();
	if (!connection && errno == ECONNREFUSED)
		display_spawn(g_argc, g_argv);

	if (!connection) {
		char err[256];
		snprintf(err, sizeof(err), "Error connecting to display: %s\n", strerror(errno));
		return stracpy(err);
	}

	sortix_driver.get_color = get_color_fn(sortix_driver.depth);

	event_handlers.disconnect_handler = on_disconnect;
	event_handlers.keyboard_handler = on_keyboard;
	event_handlers.quit_handler = on_quit;
	event_handlers.resize_handler = on_resize;
	set_handlers(display_connection_fd(connection), sortix_process_events, NULL, NULL);

	return NULL;
}

static struct graphics_device *sortix_init_device(void)
{
	// TODO: Multi-window support
	struct graphics_device *dev = mem_calloc(sizeof(struct graphics_device));

	dev->size.x1 = 0;
	dev->size.y1 = 0;
	dev->size.x2 = default_window_width;
	dev->size.y2 = default_window_height;

	dev->clip = dev->size;

	struct window_data *window_data = mem_calloc(sizeof(struct window_data));
	dev->driver_data = window_data;
	window_data->window_id = 0;
	// TODO: Overflow when multiplying
	window_data->buffer = mem_alloc(default_window_width * default_window_height * sizeof(uint32_t));

	display_create_window(connection, window_data->window_id);
	display_resize_window(connection, window_data->window_id, dev->size.x2, dev->size.y2);
	display_show_window(connection, window_data->window_id);

	current_dev = dev;

	return dev;
}

static void sortix_shutdown_device(struct graphics_device *dev)
{
	struct window_data *window_data = dev->driver_data;
	display_destroy_window(connection, window_data->window_id);
	free(window_data->buffer);
	free(window_data);
}

static void sortix_shutdown_driver(void)
{
	display_disconnect(connection);
}

static void sortix_after_fork(void)
{
	if (connection)
		display_disconnect(connection);
}

static unsigned char *sortix_get_driver_param(void)
{
	return NULL; //TODO: driver parameters?
}

static int sortix_get_empty_bitmap(struct bitmap *dest)
{
	// TODO: Overflow when multiplying
	dest->data = mem_alloc_mayfail((size_t)dest->x * (size_t)dest->y * sizeof(uint32_t));
	if (!dest->data)
		return -1;
	// TODO: Overflow when multiplying
	dest->skip = (ssize_t)dest->x * sizeof(uint32_t);
	dest->flags = 0;
	return 0;
}

static void sortix_register_bitmap(struct bitmap *bmp)
{
}

static void *sortix_prepare_strip(struct bitmap *bmp, int top, int lines)
{
	if (!bmp->data)
		return NULL;
	// TODO: Overflow when multiplying
	return ((unsigned char *)bmp->data) + bmp->skip * top;
}

static void sortix_commit_strip(struct bitmap *bmp, int top, int lines)
{
}

static void sortix_unregister_bitmap(struct bitmap *bmp)
{
	if (bmp->data)
		mem_free(bmp->data);
}

static void sortix_draw_bitmap(struct graphics_device *dev, struct bitmap *bmp, int x, int y)
{
	CLIP_DRAW_BITMAP

	struct window_data *window_data = dev->driver_data;
	int pitch = dev->size.x2;

	int width = bmp->x;
	int height = bmp->y;
	if (!width || !height)
		return;

	for (int bitmap_y = 0; bitmap_y < height; bitmap_y++) {
		for (int bitmap_x = 0; bitmap_x < width; bitmap_x++) {
			void *pixel_data = ((unsigned char*)bmp->data) + bitmap_y * bmp->skip + bitmap_x * sizeof(uint32_t);
			uint32_t pixel;
			memcpy(&pixel, pixel_data, sizeof(uint32_t));
			// Set alpha (see sortix_fill_area() for more details)
			pixel |= 0xff000000;
			window_data->buffer[(y + bitmap_y) * pitch + x + bitmap_x] = pixel;
		}
	}

	display_render_window(connection, window_data->window_id, 0, 0, dev->size.x2, dev->size.y2, window_data->buffer);
}

static void sortix_fill_area(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	CLIP_FILL_AREA

	struct window_data *window_data = dev->driver_data;
	int pitch = dev->size.x2;

	// Links uses a pixel format where the top byte is clear
	// Sortix stores alpha there, so set it to 255
	uint32_t pixel = color | 0xff000000;

	for (int y = y1; y < y2; y++) {
		for (int x = x1; x < x2; x++) {
			window_data->buffer[y * pitch + x] = pixel;
		}
	}

	display_render_window(connection, window_data->window_id, 0, 0, dev->size.x2, dev->size.y2, window_data->buffer);
}

static void sortix_draw_hline(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	CLIP_DRAW_HLINE
	sortix_fill_area(dev, x1, y, x2, y + 1, color);
}

static void sortix_draw_vline(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	CLIP_DRAW_VLINE
	sortix_fill_area(dev, x, y1, x + 1, y2, color);
}

static void sortix_set_title(struct graphics_device *dev, unsigned char *title)
{
	struct window_data *window_data = dev->driver_data;
	display_title_window(connection, window_data->window_id, title);
}

struct graphics_driver sortix_driver =
{
	(unsigned char *)"sortix",
	sortix_init_driver,
	sortix_init_device,
	sortix_shutdown_device,
	sortix_shutdown_driver,
	NULL, // emergency_shutdown
	sortix_after_fork,
	sortix_get_driver_param,
	NULL, // get_af_unix_name
	NULL, // get_margin
	NULL, // set_margin
	sortix_get_empty_bitmap,
	sortix_register_bitmap,
	sortix_prepare_strip,
	sortix_commit_strip,
	sortix_unregister_bitmap,
	sortix_draw_bitmap,
	NULL, // get_color, set in sortix_init_driver()
	sortix_fill_area,
	sortix_draw_hline,
	sortix_draw_vline,
	NULL, // scroll
	NULL, // set_clip_area
	NULL, // flush
	NULL, // block
	NULL, // unblock
	NULL, // set_palette
	NULL, // get_real_colors
	sortix_set_title,
	NULL, // exec
	NULL, // set_clipboard_text
	NULL, // get_clipboard_text
	(24 << 3) | 4, // depth: 24bpp, 4 bytes per pixel
	0, 0, // size (x, y), unused
	GD_DONT_USE_SCROLL | GD_UNICODE_KEYS, //flags
	NULL, // param
};

#endif /* GRDRV_SORTIX */
