/* drivers.c
 * (c) 2002 Mikulas Patocka
 * This file is a part of the Links program, released under GPL
 */

#include "cfg.h"

#ifdef G

#include "links.h"

int F = 0;

struct graphics_driver *drv = NULL;

#ifdef GRDRV_X
extern struct graphics_driver x_driver;
#endif
#ifdef GRDRV_SVGALIB
extern struct graphics_driver svga_driver;
#endif
#ifdef GRDRV_FB
extern struct graphics_driver fb_driver;
#endif
#ifdef GRDRV_DIRECTFB
extern struct graphics_driver directfb_driver;
#endif
#ifdef GRDRV_PMSHELL
extern struct graphics_driver pmshell_driver;
#endif
#ifdef GRDRV_ATHEOS
extern struct graphics_driver atheos_driver;
#endif
#ifdef GRDRV_HAIKU
extern struct graphics_driver haiku_driver;
#endif
#ifdef GRDRV_GRX
extern struct graphics_driver grx_driver;
#endif
#ifdef GRDRV_SDL
extern struct graphics_driver sdl_driver;
#endif
#ifdef GRDRV_SORTIX
extern struct graphics_driver sortix_driver;
#endif


/*
 * On SPAD you must test first svgalib and then X (because X test is slow).
 * On other systems you must test first X and then svgalib (because svgalib
 *	would work in X too and it's undesirable).
 */

static struct graphics_driver *graphics_drivers[] = {
#ifdef GRDRV_PMSHELL
	&pmshell_driver,
#endif
#ifdef GRDRV_ATHEOS
	&atheos_driver,
#endif
#ifdef GRDRV_HAIKU
	&haiku_driver,
#endif
#ifndef SPAD
#ifdef GRDRV_X
	&x_driver,
#endif
#endif
#ifdef GRDRV_FB
	/* use FB before DirectFB because DirectFB has bugs */
	&fb_driver,
#endif
#ifdef GRDRV_DIRECTFB
	&directfb_driver,
#endif
#ifdef GRDRV_SVGALIB
	&svga_driver,
#endif
#ifdef SPAD
#ifdef GRDRV_X
	&x_driver,
#endif
#endif
#ifdef GRDRV_GRX
	&grx_driver,
#endif
#ifdef GRDRV_SDL
	&sdl_driver,
#endif
#ifdef GRDRV_SORTIX
	&sortix_driver,
#endif
	NULL
};

#if 0
static unsigned char *list_graphics_drivers(void)
{
	unsigned char *d = init_str();
	int l = 0;
	struct graphics_driver **gd;
	for (gd = graphics_drivers; *gd; gd++) {
		if (l) add_chr_to_str(&d, &l, ' ');
		add_to_str(&d, &l, (*gd)->name);
	}
	return d;
}
#endif

/* Driver je jednorazovy argument, kterej se preda grafickymu driveru, nikde se dal
 * neuklada.  Param se skladuje v default_driver param a uklada se do konfiguraku. Pred
 * ukoncenim grafickeho driveru se nastavi default_driver_param podle
 * drv->get_driver_param.
 */
static unsigned char *init_graphics_driver(struct graphics_driver *gd, unsigned char *param, unsigned char *display)
{
	unsigned char *r;
	unsigned char *p = param;
	struct driver_param *dp = get_driver_param(gd->name);
	if (!param || !*param) p = dp->param;
	gd->param = dp;
	drv = gd;
	r = gd->init_driver(p,display);
	if (r) drv = NULL;
	else F = 1;
	return r;
}


void add_graphics_drivers(unsigned char **s, int *l)
{
	struct graphics_driver **gd;
	for (gd = graphics_drivers; *gd; gd++) {
		if (gd != graphics_drivers) add_to_str(s, l, cast_uchar ", ");
		add_to_str(s, l, (*gd)->name);
	}
}

unsigned char *init_graphics(unsigned char *driver, unsigned char *param, unsigned char *display)
{
	unsigned char *s = init_str();
	int l = 0;
	struct graphics_driver **gd;
#if defined(GRDRV_PMSHELL) && defined(GRDRV_X)
	if (is_xterm()) {
		static unsigned char swapped = 0;
		if (!swapped) {
			for (gd = graphics_drivers; *gd; gd++) {
				if (*gd == &pmshell_driver) *gd = &x_driver;
				else if (*gd == &x_driver) *gd = &pmshell_driver;
			}
			swapped = 1;
		}
	}
#endif
	for (gd = graphics_drivers; *gd; gd++) {
		if (!driver || !*driver || !casestrcmp((*gd)->name, driver)) {
			unsigned char *r;
			if ((!driver || !*driver) && (*gd)->flags & GD_NOAUTO) continue;
			if (!(r = init_graphics_driver(*gd, param, display))) {
				mem_free(s);
				return NULL;
			}
			if (!l) {
				if (!driver || !*driver) add_to_str(&s, &l, cast_uchar "Could not initialize any graphics driver. Tried the following drivers:\n");
				else add_to_str(&s, &l, cast_uchar "Could not initialize graphics driver ");
			}
			add_to_str(&s, &l, (*gd)->name);
			add_to_str(&s, &l, cast_uchar ":\n");
			add_to_str(&s, &l, r);
			mem_free(r);
		}
	}
	if (!l) {
		add_to_str(&s, &l, cast_uchar "Unknown graphics driver ");
		if (driver) add_to_str(&s, &l, driver);
		add_to_str(&s, &l, cast_uchar ".\nThe following graphics drivers are supported:\n");
		add_graphics_drivers(&s, &l);
		add_to_str(&s, &l, cast_uchar "\n");
	}
	return s;
}

void shutdown_graphics(void)
{
	if (drv) {
		drv->shutdown_driver();
		drv = NULL;
		F = 0;
	}
}

void update_driver_param(void)
{
	if (drv) {
		struct driver_param *dp = drv->param;
		if (dp->param) mem_free(dp->param), dp->param = NULL;
		if (drv->get_driver_param)
			dp->param = stracpy(drv->get_driver_param());
		dp->nosave = 0;
	}
}

int g_kbd_codepage(struct graphics_driver *drv)
{
	if (drv->param->kbd_codepage >= 0)
		return drv->param->kbd_codepage;
	return get_default_charset();
}

int do_rects_intersect(struct rect *r1, struct rect *r2)
{
	return (r1->x1 > r2->x1 ? r1->x1 : r2->x1) < (r1->x2 > r2->x2 ? r2->x2 : r1->x2) && (r1->y1 > r2->y1 ? r1->y1 : r2->y1) < (r1->y2 > r2->y2 ? r2->y2 : r1->y2);
}

void intersect_rect(struct rect *v, struct rect *r1, struct rect *r2)
{
	v->x1 = r1->x1 > r2->x1 ? r1->x1 : r2->x1;
	v->x2 = r1->x2 > r2->x2 ? r2->x2 : r1->x2;
	v->y1 = r1->y1 > r2->y1 ? r1->y1 : r2->y1;
	v->y2 = r1->y2 > r2->y2 ? r2->y2 : r1->y2;
}

void unite_rect(struct rect *v, struct rect *r1, struct rect *r2)
{
	if (!is_rect_valid(r1)) {
		if (v != r2) memcpy(v, r2, sizeof(struct rect));
		return;
	}
	if (!is_rect_valid(r2)) {
		if (v != r1) memcpy(v, r1, sizeof(struct rect));
		return;
	}
	v->x1 = r1->x1 < r2->x1 ? r1->x1 : r2->x1;
	v->x2 = r1->x2 < r2->x2 ? r2->x2 : r1->x2;
	v->y1 = r1->y1 < r2->y1 ? r1->y1 : r2->y1;
	v->y2 = r1->y2 < r2->y2 ? r2->y2 : r1->y2;
}

#define R_GR	8

struct rect_set *init_rect_set(void)
{
	struct rect_set *s;
	s = mem_calloc(sizeof(struct rect_set) + sizeof(struct rect) * R_GR);
	s->rl = R_GR;
	s->m = 0;
	return s;
}

void add_to_rect_set(struct rect_set **s, struct rect *r)
{
	struct rect_set *ss = *s;
	int i;
	if (!is_rect_valid(r)) return;
	for (i = 0; i < ss->rl; i++) if (!ss->r[i].x1 && !ss->r[i].x2 && !ss->r[i].y1 && !ss->r[i].y2) {
		x:
		memcpy(&ss->r[i], r, sizeof(struct rect));
		if (i >= ss->m) ss->m = i + 1;
		return;
	}
	if ((unsigned)ss->rl > (MAXINT - sizeof(struct rect_set)) / sizeof(struct rect) - R_GR) overalloc();
	ss = mem_realloc(ss, sizeof(struct rect_set) + sizeof(struct rect) * (ss->rl + R_GR));
	memset(&(*s = ss)->r[i = (ss->rl += R_GR) - R_GR], 0, sizeof(struct rect) * R_GR);
	goto x;
}

void exclude_rect_from_set(struct rect_set **s, struct rect *r)
{
	int i, a;
	struct rect *rr;
	do {
		a = 0;
		for (i = 0; i < (*s)->m; i++) if (do_rects_intersect(rr = &(*s)->r[i], r)) {
			struct rect r1, r2, r3, r4;
			r1.x1 = rr->x1;
			r1.x2 = rr->x2;
			r1.y1 = rr->y1;
			r1.y2 = r->y1;

			r2.x1 = rr->x1;
			r2.x2 = r->x1;
			r2.y1 = r->y1;
			r2.y2 = r->y2;

			r3.x1 = r->x2;
			r3.x2 = rr->x2;
			r3.y1 = r->y1;
			r3.y2 = r->y2;

			r4.x1 = rr->x1;
			r4.x2 = rr->x2;
			r4.y1 = r->y2;
			r4.y2 = rr->y2;

			intersect_rect(&r2, &r2, rr);
			intersect_rect(&r3, &r3, rr);
			rr->x1 = rr->x2 = rr->y1 = rr->y2 = 0;
#ifdef DEBUG
			if (is_rect_valid(&r1) && do_rects_intersect(&r1, r)) internal_error("bad intersection 1");
			if (is_rect_valid(&r2) && do_rects_intersect(&r2, r)) internal_error("bad intersection 2");
			if (is_rect_valid(&r3) && do_rects_intersect(&r3, r)) internal_error("bad intersection 3");
			if (is_rect_valid(&r4) && do_rects_intersect(&r4, r)) internal_error("bad intersection 4");
#endif
			add_to_rect_set(s, &r1);
			add_to_rect_set(s, &r2);
			add_to_rect_set(s, &r3);
			add_to_rect_set(s, &r4);
			a = 1;
		}
	} while (a);
}

void set_clip_area(struct graphics_device *dev, struct rect *r)
{
	dev->clip = *r;
	if (dev->clip.x1 < 0) dev->clip.x1 = 0;
	if (dev->clip.x2 > dev->size.x2) dev->clip.x2 = dev->size.x2;
	if (dev->clip.y1 < 0) dev->clip.y1 = 0;
	if (dev->clip.y2 > dev->size.y2) dev->clip.y2 = dev->size.y2;
	if (!is_rect_valid(&dev->clip)) {
		/* Empty region */
		dev->clip.x1 = dev->clip.x2 = dev->clip.y1 = dev->clip.y2 = 0;
	}
	if (drv->set_clip_area)
		drv->set_clip_area(dev);
}

/* memory address r must contain one struct rect
 * x1 is leftmost pixel that is still valid
 * x2 is leftmost pixel that isn't valid any more
 * y1, y2 analogically
 */
int restrict_clip_area(struct graphics_device *dev, struct rect *r, int x1, int y1, int x2, int y2)
{
	struct rect v, rr;
	rr.x1 = x1, rr.x2 = x2, rr.y1 = y1, rr.y2 = y2;
	if (r) memcpy(r, &dev->clip, sizeof(struct rect));
	intersect_rect(&v, &dev->clip, &rr);
	set_clip_area(dev, &v);
	return is_rect_valid(&v);
}

struct rect_set *g_scroll(struct graphics_device *dev, int scx, int scy)
{
	struct rect_set *rs = init_rect_set();

	if (!scx && !scy)
		return rs;
	if (abs(scx) >= dev->clip.x2 - dev->clip.x1 ||
	    abs(scy) >= dev->clip.y2 - dev->clip.y1) {
		add_to_rect_set(&rs, &dev->clip);
		return rs;
	}

	if (drv->scroll(dev, &rs, scx, scy)) {
		struct rect q = dev->clip;
		if (scy >= 0)
			q.y2 = q.y1 + scy;
		else
			q.y1 = q.y2 + scy;
		add_to_rect_set(&rs, &q);

		q = dev->clip;
		if (scy >= 0)
			q.y1 += scy;
		else
			q.y2 += scy;
		if (scx >= 0)
			q.x2 = q.x1 + scx;
		else
			q.x1 = q.x2 + scx;
		add_to_rect_set(&rs, &q);
	}

	return rs;
}

#ifdef GRDRV_VIRTUAL_DEVICES

struct graphics_device **virtual_devices;
int n_virtual_devices = 0;
struct graphics_device *current_virtual_device;

static struct timer *virtual_device_timer;

void init_virtual_devices(struct graphics_driver *drv, int n)
{
	if (n_virtual_devices) {
		internal_error("init_virtual_devices: already initialized");
		return;
	}
	if ((unsigned)n > MAXINT / sizeof(struct graphics_device *)) overalloc();
	virtual_devices = mem_calloc(n * sizeof(struct graphics_device *));
	n_virtual_devices = n;
	virtual_device_timer = NULL;
	current_virtual_device = NULL;
}

struct graphics_device *init_virtual_device(void)
{
	int i;
	for (i = 0; i < n_virtual_devices; i++) if (!virtual_devices[i]) {
		struct graphics_device *dev;
		dev = mem_calloc(sizeof(struct graphics_device));
		dev->size.x2 = drv->x;
		dev->size.y2 = drv->y;
		current_virtual_device = virtual_devices[i] = dev;
		set_clip_area(dev, &dev->size);
		return dev;
	}
	return NULL;
}

static void virtual_device_timer_fn(void *p)
{
	virtual_device_timer = NULL;
	if (current_virtual_device && current_virtual_device->redraw_handler) {
		set_clip_area(current_virtual_device, &current_virtual_device->size);
		current_virtual_device->redraw_handler(current_virtual_device, &current_virtual_device->size);
	}
}

void switch_virtual_device(int i)
{
	if (i == VD_NEXT) {
		int j;
		int t = 0;
		for (j = 0; j < n_virtual_devices * 2; j++)
			if (virtual_devices[j % n_virtual_devices] == current_virtual_device) t = 1;
			else if (virtual_devices[j % n_virtual_devices] && t) {
				current_virtual_device = virtual_devices[j % n_virtual_devices];
				goto ok_switch;
			}
		return;
	}
	if (i < 0 || i >= n_virtual_devices || !virtual_devices[i]) return;
	current_virtual_device = virtual_devices[i];
	ok_switch:
	if (virtual_device_timer == NULL)
		virtual_device_timer = install_timer(0, virtual_device_timer_fn, NULL);
}

void shutdown_virtual_device(struct graphics_device *dev)
{
	int i;
	for (i = 0; i < n_virtual_devices; i++) if (virtual_devices[i] == dev) {
		virtual_devices[i] = NULL;
		mem_free(dev);
		if (current_virtual_device != dev) return;
		for (; i < n_virtual_devices; i++) if (virtual_devices[i]) {
			switch_virtual_device(i);
			return;
		}
		for (i = 0; i < n_virtual_devices; i++) if (virtual_devices[i]) {
			switch_virtual_device(i);
			return;
		}
		current_virtual_device = NULL;
		return;
	}
	mem_free(dev);
	/*internal_error("shutdown_virtual_device: device not initialized");*/
}

void resize_virtual_devices(int x, int y)
{
	int i;
	drv->x = x;
	drv->y = y;
	for (i = 0; i < n_virtual_devices; i++) {
		struct graphics_device *dev = virtual_devices[i];
		if (dev) {
			dev->size.x2 = x;
			dev->size.y2 = y;
			dev->resize_handler(dev);
		}
	}
}

void shutdown_virtual_devices(void)
{
	int i;
	if (!n_virtual_devices) {
		internal_error("shutdown_virtual_devices: already shut down");
		return;
	}
	for (i = 0; i < n_virtual_devices; i++) if (virtual_devices[i]) internal_error("shutdown_virtual_devices: virtual device %d is still active", i);
	mem_free(virtual_devices);
	n_virtual_devices = 0;
	if (virtual_device_timer != NULL) kill_timer(virtual_device_timer), virtual_device_timer = NULL;
}

#endif

#if defined(GRDRV_X) || defined(GRDRV_HAIKU)

/* This is executed in a helper thread, so we must not use mem_alloc */

static void addchr(unsigned char **str, size_t *l, unsigned char c)
{
	unsigned char *s;
	if (!*str) return;
	if ((*str)[*l]) *l = strlen(cast_const_char *str);
	if (*l > MAXINT - 2) overalloc();
	s = realloc(*str, *l + 2);
	if (!s) {
		free(*str);
		*str = NULL;
		return;
	}
	*str = s;
	s[(*l)++] = c;
	s[*l] = 0;
}

int x_exec(unsigned char *command, int fg)
{
	unsigned char *pattern, *final;
	size_t i, j, l;
	int retval;

	if (!fg) {
		retval = system(cast_const_char command);
		return retval;
	}

	l = 0;
	if (*drv->param->shell_term) {
		pattern = cast_uchar strdup(cast_const_char drv->param->shell_term);
	} else {
		pattern = cast_uchar strdup(cast_const_char links_xterm());
		if (*command) {
			addchr(&pattern, &l, ' ');
			addchr(&pattern, &l, '%');
		}
	}
	if (!pattern) return -1;

	final = cast_uchar strdup("");
	l = 0;
	for (i = 0; pattern[i]; i++) {
		if (pattern[i] == '%') {
			for (j = 0; j < strlen(cast_const_char command); j++)
				addchr(&final, &l, command[j]);
		} else {
			addchr(&final, &l, pattern[i]);
		}
	}
	free(pattern);
	if (!final) return -1;

	retval = system(cast_const_char final);
	free(final);
	return retval;
}

#endif

#endif
