/* BSD mouse system-specific routines. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "osdep/system.h"

#ifdef CONFIG_SYSMOUSE
#ifdef HAVE_SYS_CONSIO_H
#include <sys/consio.h>
#include <sys/fbio.h>
#else
#include <machine/console.h>
#endif	/* HAVE_SYS_CONSIO_H */
#endif	/* CONFIG_SYSMOUSE */

#include "elinks.h"

#include "main/select.h"
#include "osdep/osdep.h"
#include "osdep/signals.h"
#include "terminal/event.h"
#include "terminal/mouse.h"


#if defined(CONFIG_SYSMOUSE) && defined(CONFIG_MOUSE)

struct sysmouse_spec {
	void *itrm;
	int cheight;
	int cwidth;
	void (*fn)(void *, unsigned char *, int);
};

static void
sysmouse_handler(void *data)
{
	static struct term_event_mouse prev_mouse;
	static int prev_buttons;
	struct sysmouse_spec *sp = data;
	void *itrm = sp->itrm;
	int fd = get_output_handle();
	int buttons, change;
	mouse_info_t mi;
	struct term_event_mouse mouse;
	struct term_event ev;

	mi.operation = MOUSE_GETINFO;
	if (ioctl(fd, CONS_MOUSECTL, &mi) == -1) return;
	mouse.x = int_max(mi.u.data.x / sp->cwidth, 0);
	mouse.y = int_max(mi.u.data.y / sp->cheight, 0);

	/* for cosmetic bug in syscons.c on FreeBSD 3.3/3.4 */
#ifdef HAVE_MACHINE_CONSOLE_H
	mi.operation = MOUSE_HIDE;
	ioctl(fd, CONS_MOUSECTL, &mi);
	mi.operation = MOUSE_SHOW;
	ioctl(fd, CONS_MOUSECTL, &mi);
#endif
	buttons = mi.u.data.buttons & 7;
	change = (mouse.x != prev_mouse.x || mouse.y != prev_mouse.y);
	prev_mouse = mouse;
	/* It's horrible. */
	switch (buttons) {
		case 0:
			switch (prev_buttons) {
				case 0:
					return;
					break;
				case 1:
				case 3:
				case 5:
				case 7:
					mouse.button = B_LEFT | B_UP;
					break;
				case 2:
				case 6:
					mouse.button = B_MIDDLE | B_UP;
					break;
				case 4:
					mouse.button = B_RIGHT | B_UP;
					break;
			}
			break;
		case 1:
		case 3:
		case 5:
		case 7:
			switch (prev_buttons) {
				case 0:
				case 2:
				case 4:
				case 6:
					mouse.button = B_LEFT | B_DOWN;
					break;
				case 1:
				case 3:
				case 5:
				case 7:
					if (change)
						mouse.button = B_LEFT | B_DRAG;
					else mouse.button = B_LEFT | B_DOWN;
					break;
			}
			break;
		case 2:
		case 6:
			switch (prev_buttons) {
				case 1:
				case 3:
				case 5:
				case 7:
					mouse.button = B_LEFT | B_UP;
		    			break;
				case 0:
				case 4:
					mouse.button = B_MIDDLE | B_DOWN;
					break;
				case 2:
				case 6:
					if (change)
					mouse.button = B_MIDDLE | B_DRAG;
					else mouse.button = B_MIDDLE | B_DOWN;
					break;
			}
			break;
		case 4:
			switch (prev_buttons) {
				case 1:
				case 3:
				case 5:
				case 7:
					mouse.button = B_LEFT | B_UP;
					break;
				case 2:
				case 6:
					mouse.button = B_MIDDLE | B_UP;
					break;
				case 0:
					mouse.button = B_RIGHT | B_DOWN;
					break;
				case 4:
					if (change)
						mouse.button = B_RIGHT | B_DRAG;
					else mouse.button = B_RIGHT | B_DOWN;
					break;
			}
			break;
	}

	prev_buttons = buttons;
	set_mouse_term_event(&ev, mouse.x, mouse.y, mouse.button);
	sp->fn(itrm, (unsigned char *)&ev, sizeof(ev));
}

static void
sysmouse_signal_handler(void *data)
{
	register_bottom_half(sysmouse_handler, data);
}

void *
handle_mouse(int cons, void (*fn)(void *, unsigned char *, int),
	     void *data)
{
	static struct sysmouse_spec mouse_spec;
	video_info_t vi;
	mouse_info_t mi;
	int fd = get_output_handle();

	if (is_xterm()) return NULL;
	mouse_spec.itrm = data;
	mouse_spec.fn = fn;

	if (ioctl(fd, FBIO_GETMODE, &vi.vi_mode) != -1 &&
		ioctl(fd, FBIO_MODEINFO, &vi) != -1) {
		mouse_spec.cwidth = vi.vi_cwidth;
		mouse_spec.cheight = vi.vi_cheight;
	} else {
		return NULL;
	}

	install_signal_handler(SIGUSR2, NULL, NULL, 0);
	mi.operation = MOUSE_MODE;
	mi.u.mode.mode = 0;
	mi.u.mode.signal = SIGUSR2;
	if (ioctl(fd, CONS_MOUSECTL, &mi) != -1) {
		install_signal_handler(SIGUSR2,
		(void (*)(void *))sysmouse_signal_handler, &mouse_spec, 0);
		mi.operation = MOUSE_SHOW;
		ioctl(fd, CONS_MOUSECTL, &mi);
		return &mouse_spec;
	} else {
		return NULL;
	}

}

void
unhandle_mouse(void *data)
{
	if (data) install_signal_handler(SIGUSR2, NULL, NULL, 0);
}

void
suspend_mouse(void *data)
{
	if (data) install_signal_handler(SIGUSR2, NULL, NULL, 0);
}

void
resume_mouse(void *data)
{
	if (data) install_signal_handler(SIGUSR2,
		(void (*)(void *))sysmouse_signal_handler, data, 0);
}

#endif
