/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

/**
 * @file    src/ginput/mouse.c
 * @brief   GINPUT mouse/touch code.
 *
 * @defgroup Mouse Mouse
 * @ingroup GINPUT
 * @{
 */
#include "gfx.h"

#if (GFX_USE_GINPUT && GINPUT_NEED_MOUSE) || defined(__DOXYGEN__)

#include "ginput/lld/mouse.h"

#if GINPUT_MOUSE_NEED_CALIBRATION
	#if !defined(GFX_USE_GDISP) || !GFX_USE_GDISP
		#error "GINPUT: GFX_USE_GDISP must be defined when mouse or touch calibration is required"
	#endif

	#define GINPUT_MOUSE_CALIBRATION_FONT		"* Double"
	#define GINPUT_MOUSE_CALIBRATION_FONT2		"* Narrow"
	#define GINPUT_MOUSE_CALIBRATION_TEXT		"Calibration"
	#define GINPUT_MOUSE_CALIBRATION_ERROR_TEXT	"Failed - Please try again!"
	#define GINPUT_MOUSE_CALIBRATION_SAME_TEXT	"Error: Same Reading - Check Driver!"

	#if GINPUT_MOUSE_MAX_CALIBRATION_ERROR < 0
		#define GINPUT_MOUSE_CALIBRATION_POINTS		3
	#else
		#define GINPUT_MOUSE_CALIBRATION_POINTS		4
	#endif

	typedef struct Calibration_t {
	    float ax;
	    float bx;
	    float cx;
	    float ay;
	    float by;
	    float cy;
	} Calibration;
#endif

typedef struct MousePoint_t {
	coord_t		x, y;
	} MousePoint;

static GTIMER_DECL(MouseTimer);

static struct MouseConfig_t {
	MouseReading					t;
	MousePoint						movepos;
	MousePoint						clickpos;
	systemticks_t					clicktime;
	uint16_t						last_buttons;
	uint16_t						flags;
			#define FLG_INIT_DONE		0x8000
			#define FLG_CLICK_TIMER		0x0001
			#define FLG_IN_CAL			0x0010
			#define FLG_CAL_OK			0x0020
			#define FLG_CAL_SAVED		0x0040
			#define FLG_CAL_FREE		0x0080
	#if GINPUT_MOUSE_NEED_CALIBRATION
		GMouseCalibrationSaveRoutine	fnsavecal;
		GMouseCalibrationLoadRoutine	fnloadcal;
		Calibration						caldata;
	#endif
	} MouseConfig;

#if GINPUT_MOUSE_NEED_CALIBRATION
	static inline void _tsDrawCross(const MousePoint *pp) {
		gdispDrawLine(pp->x-15, pp->y, pp->x-2, pp->y, White);
		gdispDrawLine(pp->x+2, pp->y, pp->x+15, pp->y, White);
		gdispDrawLine(pp->x, pp->y-15, pp->x, pp->y-2, White);
		gdispDrawLine(pp->x, pp->y+2, pp->x, pp->y+15, White);

		gdispDrawLine(pp->x-15, pp->y+15, pp->x-7, pp->y+15, RGB2COLOR(184,158,131));
		gdispDrawLine(pp->x-15, pp->y+7, pp->x-15, pp->y+15, RGB2COLOR(184,158,131));

		gdispDrawLine(pp->x-15, pp->y-15, pp->x-7, pp->y-15, RGB2COLOR(184,158,131));
		gdispDrawLine(pp->x-15, pp->y-7, pp->x-15, pp->y-15, RGB2COLOR(184,158,131));

		gdispDrawLine(pp->x+7, pp->y+15, pp->x+15, pp->y+15, RGB2COLOR(184,158,131));
		gdispDrawLine(pp->x+15, pp->y+7, pp->x+15, pp->y+15, RGB2COLOR(184,158,131));

		gdispDrawLine(pp->x+7, pp->y-15, pp->x+15, pp->y-15, RGB2COLOR(184,158,131));
		gdispDrawLine(pp->x+15, pp->y-15, pp->x+15, pp->y-7, RGB2COLOR(184,158,131));
	}

	static inline void _tsClearCross(const MousePoint *pp) {
		gdispFillArea(pp->x - 15, pp->y - 15, 42, 42, Blue);
	}

	static inline void _tsTransform(MouseReading *pt, const Calibration *c) {
		pt->x = (coord_t) (c->ax * pt->x + c->bx * pt->y + c->cx);
		pt->y = (coord_t) (c->ay * pt->x + c->by * pt->y + c->cy);
	}

	static inline void _tsDo3PointCalibration(const MousePoint *cross, const MousePoint *points, Calibration *c) {
		float dx, dx0, dx1, dx2, dy0, dy1, dy2;

		/* Compute all the required determinants */
		dx = ((float)(points[0].x - points[2].x)) * ((float)(points[1].y - points[2].y))
			- ((float)(points[1].x - points[2].x)) * ((float)(points[0].y - points[2].y));

		dx0 = ((float)(cross[0].x - cross[2].x)) * ((float)(points[1].y - points[2].y))
			- ((float)(cross[1].x - cross[2].x)) * ((float)(points[0].y - points[2].y));

		dx1 = ((float)(cross[1].x - cross[2].x)) * ((float)(points[0].x - points[2].x))
			- ((float)(cross[0].x - cross[2].x)) * ((float)(points[1].x - points[2].x));

		dx2 = cross[0].x * ((float)points[1].x * (float)points[2].y - (float)points[2].x * (float)points[1].y) -
			cross[1].x * ((float)points[0].x * (float)points[2].y - (float)points[2].x * (float)points[0].y) +
			cross[2].x * ((float)points[0].x * (float)points[1].y - (float)points[1].x * (float)points[0].y);

		dy0 = ((float)(cross[0].y - cross[2].y))  *  ((float)(points[1].y - points[2].y))
			- ((float)(cross[1].y - cross[2].y))  *  ((float)(points[0].y - points[2].y));

		dy1  = ((float)(cross[1].y - cross[2].y)) * ((float)(points[0].x - points[2].x))
			- ((float)(cross[0].y - cross[2].y)) * ((float)(points[1].x - points[2].x));

		dy2  = cross[0].y * ((float)points[1].x * (float)points[2].y - (float)points[2].x * (float)points[1].y) -
			cross[1].y * ((float)points[0].x * (float)points[2].y - (float)points[2].x * (float)points[0].y) +
			cross[2].y * ((float)points[0].x * (float)points[1].y - (float)points[1].x * (float)points[0].y);

		/* Now, calculate all the required coefficients */
		c->ax = dx0 / dx;
		c->bx = dx1 / dx;
		c->cx = dx2 / dx;

		c->ay = dy0 / dx;
		c->by = dy1 / dx;
		c->cy = dy2 / dx;
	}
#endif

#if GINPUT_MOUSE_READ_CYCLES > 1
	static void get_raw_reading(MouseReading *pt) {
		int32_t x, y, z;
		unsigned i;

		x = y = z = 0;
		for(i = 0; i < GINPUT_MOUSE_READ_CYCLES; i++) {
			ginput_lld_mouse_get_reading(pt);
			x += pt->x;
			y += pt->y;
			z += pt->z;
		}

		/* Take the average of the readings */
		pt->x = x / GINPUT_MOUSE_READ_CYCLES;
		pt->y = y / GINPUT_MOUSE_READ_CYCLES;
		pt->z = z / GINPUT_MOUSE_READ_CYCLES;
	}
#else
	#define get_raw_reading(pt)		ginput_lld_mouse_get_reading(pt)
#endif

static void get_calibrated_reading(MouseReading *pt) {
	#if GINPUT_MOUSE_NEED_CALIBRATION || GDISP_NEED_CONTROL
		coord_t		w, h;
	#endif

	get_raw_reading(pt);

	#if GINPUT_MOUSE_NEED_CALIBRATION || GDISP_NEED_CONTROL
		w = gdispGetWidth();
		h = gdispGetHeight();
	#endif

	#if GINPUT_MOUSE_NEED_CALIBRATION
		_tsTransform(pt, &MouseConfig.caldata);
	#endif

	#if GDISP_NEED_CONTROL
		switch(gdispGetOrientation()) {
			case GDISP_ROTATE_0:
				break;
			case GDISP_ROTATE_90:
				{
					coord_t t = pt->y;
					pt->y = h - 1 - pt->x;
					pt->x = t;
				}
				break;
			case GDISP_ROTATE_180:
				pt->x = w - 1 - pt->x;
				pt->y = h - 1 - pt->y;
				break;
			case GDISP_ROTATE_270:
				{
					coord_t t = pt->x;
					pt->x = w - 1 - pt->y;
					pt->y = t;
				}
				break;
		}
	#endif

	#if GINPUT_MOUSE_NEED_CALIBRATION
		if (pt->x < 0)	pt->x = 0;
		else if (pt->x >= w) pt->x = w-1;
		if (pt->y < 0)	pt->y = 0;
		else if (pt->y >= h) pt->y = h-1;
	#endif
}

static void MousePoll(void *param) {
	(void) param;
	GSourceListener	*psl;
	GEventMouse		*pe;
	unsigned 		meta;
	uint16_t		tbtns;
	uint32_t		cdiff;
	uint32_t		mdiff;

	// Save the last mouse state
	MouseConfig.last_buttons = MouseConfig.t.buttons;

	// Get the new mouse reading
	get_calibrated_reading(&MouseConfig.t);

	// Calculate out new event meta value and handle CLICK and CXTCLICK
	meta = GMETA_NONE;

	// Calculate the position difference from our movement reference (update the reference if out of range)
	mdiff = (MouseConfig.t.x - MouseConfig.movepos.x) * (MouseConfig.t.x - MouseConfig.movepos.x) +
		(MouseConfig.t.y - MouseConfig.movepos.y) * (MouseConfig.t.y - MouseConfig.movepos.y);
	if (mdiff > GINPUT_MOUSE_MAX_MOVE_JITTER * GINPUT_MOUSE_MAX_MOVE_JITTER) {
		MouseConfig.movepos.x = MouseConfig.t.x;
		MouseConfig.movepos.y = MouseConfig.t.y;
	}
	
	// Check if the click has moved outside the click area and if so cancel the click
	if ((MouseConfig.flags & FLG_CLICK_TIMER)) {
		cdiff = (MouseConfig.t.x - MouseConfig.clickpos.x) * (MouseConfig.t.x - MouseConfig.clickpos.x) +
			(MouseConfig.t.y - MouseConfig.clickpos.y) * (MouseConfig.t.y - MouseConfig.clickpos.y);
		if (cdiff > GINPUT_MOUSE_MAX_CLICK_JITTER * GINPUT_MOUSE_MAX_CLICK_JITTER)
			MouseConfig.flags &= ~FLG_CLICK_TIMER;
	}

	// Mouse down
	tbtns = MouseConfig.t.buttons & ~MouseConfig.last_buttons;
	if ((tbtns & GINPUT_MOUSE_BTN_LEFT))
		meta |= GMETA_MOUSE_DOWN;
	if ((tbtns & (GINPUT_MOUSE_BTN_LEFT|GINPUT_MOUSE_BTN_RIGHT))) {
		MouseConfig.clickpos.x = MouseConfig.t.x;
		MouseConfig.clickpos.y = MouseConfig.t.y;
		MouseConfig.clicktime = gfxSystemTicks();
		MouseConfig.flags |= FLG_CLICK_TIMER;
	}

	// Mouse up
	tbtns = ~MouseConfig.t.buttons & MouseConfig.last_buttons;
	if ((tbtns & GINPUT_MOUSE_BTN_LEFT))
		meta |= GMETA_MOUSE_UP;
	if ((tbtns & (GINPUT_MOUSE_BTN_LEFT|GINPUT_MOUSE_BTN_RIGHT))) {
		if ((MouseConfig.flags & FLG_CLICK_TIMER)) {
			if ((tbtns & GINPUT_MOUSE_BTN_LEFT)
					#if GINPUT_MOUSE_CLICK_TIME != TIME_INFINITE
						&& gfxSystemTicks() - MouseConfig.clicktime < gfxMillisecondsToTicks(GINPUT_MOUSE_CLICK_TIME)
					#endif
					)
				meta |= GMETA_MOUSE_CLICK;
			else
				meta |= GMETA_MOUSE_CXTCLICK;
			MouseConfig.flags &= ~FLG_CLICK_TIMER;
		}
	}

	// Send the event to the listeners that are interested.
	psl = 0;
	while ((psl = geventGetSourceListener((GSourceHandle)(&MouseConfig), psl))) {
		if (!(pe = (GEventMouse *)geventGetEventBuffer(psl))) {
			// This listener is missing - save the meta events that have happened
			psl->srcflags |= meta;
			continue;
		}

		// If we haven't really moved (and there are no meta events) don't bother sending the event
		if (mdiff <= GINPUT_MOUSE_MAX_MOVE_JITTER * GINPUT_MOUSE_MAX_MOVE_JITTER && !psl->srcflags && !meta && !(psl->listenflags & GLISTEN_MOUSENOFILTER))
			continue;

		// Send the event if we are listening for it
		if (((MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT) && (psl->listenflags & GLISTEN_MOUSEDOWNMOVES))
				|| (!(MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT) && (psl->listenflags & GLISTEN_MOUSEUPMOVES))
				|| (meta && (psl->listenflags & GLISTEN_MOUSEMETA))) {
			pe->type = GINPUT_MOUSE_EVENT_TYPE;
			pe->instance = 0;
			pe->x = MouseConfig.t.x;
			pe->y = MouseConfig.t.y;
			pe->z = MouseConfig.t.z;
			pe->current_buttons = MouseConfig.t.buttons;
			pe->last_buttons = MouseConfig.last_buttons;
			pe->meta = meta;
			if (psl->srcflags) {
				pe->current_buttons |= GINPUT_MISSED_MOUSE_EVENT;
				pe->meta |= psl->srcflags;
				psl->srcflags = 0;
			}
			geventSendEvent(psl);
		}
	}
}

GSourceHandle ginputGetMouse(uint16_t instance) {
	#if GINPUT_MOUSE_NEED_CALIBRATION
		Calibration		*pc;
	#endif

	// We only support a single mouse instance currently
	//	Instance 9999 is the same as instance 0 except that it installs
	//	a special "raw" calibration if there isn't one we can load.
	if (instance && instance != 9999)
		return 0;

	// Do we need to initialise the mouse subsystem?
	if (!(MouseConfig.flags & FLG_INIT_DONE)) {
		ginput_lld_mouse_init();

		#if GINPUT_MOUSE_NEED_CALIBRATION
			#if GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE
				if (!MouseConfig.fnloadcal) {
					MouseConfig.fnloadcal = ginput_lld_mouse_calibration_load;
					MouseConfig.flags &= ~FLG_CAL_FREE;
				}
				if (!MouseConfig.fnsavecal)
					MouseConfig.fnsavecal = ginput_lld_mouse_calibration_save;
			#endif
			if (MouseConfig.fnloadcal && (pc = (Calibration *)MouseConfig.fnloadcal(instance))) {
				MouseConfig.caldata = pc[0];
				MouseConfig.flags |= (FLG_CAL_OK|FLG_CAL_SAVED);
				if ((MouseConfig.flags & FLG_CAL_FREE))
					gfxFree((void *)pc);
			} else if (instance == 9999) {
				MouseConfig.caldata.ax = 1;
				MouseConfig.caldata.bx = 0;
				MouseConfig.caldata.cx = 0;
				MouseConfig.caldata.ay = 0;
				MouseConfig.caldata.by = 1;
				MouseConfig.caldata.cy = 0;
				MouseConfig.flags |= (FLG_CAL_OK|FLG_CAL_SAVED);
			} else
				ginputCalibrateMouse(instance);
		#endif

		// Get the first reading
		MouseConfig.last_buttons = 0;
		get_calibrated_reading(&MouseConfig.t);

		// Mark init as done and start the Poll timer
		MouseConfig.flags |= FLG_INIT_DONE;
		gtimerStart(&MouseTimer, MousePoll, 0, TRUE, GINPUT_MOUSE_POLL_PERIOD);
	}

	// Return our structure as the handle
	return (GSourceHandle)&MouseConfig;
}

bool_t ginputGetMouseStatus(uint16_t instance, GEventMouse *pe) {
	// Win32 threads don't seem to recognise priority and/or pre-emption
	// so we add a sleep here to prevent 100% polled applications from locking up.
	gfxSleepMilliseconds(1);

	if (instance || (MouseConfig.flags & (FLG_INIT_DONE|FLG_IN_CAL)) != FLG_INIT_DONE)
		return FALSE;

	pe->type = GINPUT_MOUSE_EVENT_TYPE;
	pe->instance = instance;
	pe->x = MouseConfig.t.x;
	pe->y = MouseConfig.t.y;
	pe->z = MouseConfig.t.z;
	pe->current_buttons = MouseConfig.t.buttons;
	pe->last_buttons = MouseConfig.last_buttons;
	if (pe->current_buttons & ~pe->last_buttons & GINPUT_MOUSE_BTN_LEFT)
		pe->meta = GMETA_MOUSE_DOWN;
	else if (~pe->current_buttons & pe->last_buttons & GINPUT_MOUSE_BTN_LEFT)
		pe->meta = GMETA_MOUSE_UP;
	else
		pe->meta = GMETA_NONE;
	return TRUE;
}

bool_t ginputCalibrateMouse(uint16_t instance) {
	#if !GINPUT_MOUSE_NEED_CALIBRATION
		(void) instance;
		
		return FALSE;
	#else

		const coord_t height  =  gdispGetHeight();
		const coord_t width  =  gdispGetWidth();
		const MousePoint cross[]  =  {{(width / 4), (height / 4)},
										{(width - (width / 4)) , (height / 4)},
										{(width - (width / 4)) , (height - (height / 4))},
										{(width / 2), (height / 2)}}; /* Check point */
		MousePoint points[GINPUT_MOUSE_CALIBRATION_POINTS];
		const MousePoint	*pc;
		MousePoint *pt;
		int32_t px, py;
		unsigned i, j;
		font_t	font1, font2;
		#if GINPUT_MOUSE_MAX_CALIBRATION_ERROR >= 0
			unsigned	err;
		#endif

		if (instance || (MouseConfig.flags & FLG_IN_CAL))
			return FALSE;

		font1 = gdispOpenFont(GINPUT_MOUSE_CALIBRATION_FONT);
		font2 = gdispOpenFont(GINPUT_MOUSE_CALIBRATION_FONT2);

		MouseConfig.flags |= FLG_IN_CAL;
		gtimerStop(&MouseTimer);
		MouseConfig.flags &= ~(FLG_CAL_OK|FLG_CAL_SAVED);

		#if GDISP_NEED_CONTROL
			gdispSetOrientation(GDISP_ROTATE_0);
		#endif

		#if GDISP_NEED_CLIP
			gdispSetClip(0, 0, width, height);
		#endif

		#if GINPUT_MOUSE_MAX_CALIBRATION_ERROR >= 0
			while(1) {
		#endif
				gdispClear(Blue);

				gdispFillStringBox(0, 5, width, 30, GINPUT_MOUSE_CALIBRATION_TEXT, font1,  White, Blue, justifyCenter);

				for(i = 0, pt = points, pc = cross; i < GINPUT_MOUSE_CALIBRATION_POINTS; i++, pt++, pc++) {
					_tsDrawCross(pc);

					do {

						/* Wait for the mouse to be pressed */
						while(get_raw_reading(&MouseConfig.t), !(MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT))
							gfxSleepMilliseconds(20);

						/* Average all the samples while the mouse is down */
						for(px = py = 0, j = 0;
								gfxSleepMilliseconds(20),			/* Settling time between readings */
								get_raw_reading(&MouseConfig.t),
								(MouseConfig.t.buttons & GINPUT_MOUSE_BTN_LEFT);
								j++) {
							px += MouseConfig.t.x;
							py += MouseConfig.t.y;
						}

					} while(!j);

					pt->x = px / j;
					pt->y = py / j;

					_tsClearCross(pc);

					if (i >= 1 && pt->x == (pt-1)->x && pt->y == (pt-1)->y) {
						gdispFillStringBox(0, 35, width, 40, GINPUT_MOUSE_CALIBRATION_SAME_TEXT, font2,  Red, Yellow, justifyCenter);
						gfxSleepMilliseconds(5000);
						gdispFillArea(0, 35, width, 40, Blue);
					}

				}

				/* Apply 3 point calibration algorithm */
				_tsDo3PointCalibration(cross, points, &MouseConfig.caldata);

				 /* Verification of correctness of calibration (optional) :
				 *  See if the 4th point (Middle of the screen) coincides with the calibrated
				 *  result. If point is within +/- Squareroot(ERROR) pixel margin, then successful calibration
				 *  Else, start from the beginning.
				 */
		#if GINPUT_MOUSE_MAX_CALIBRATION_ERROR >= 0
				/* Transform the co-ordinates */
				MouseConfig.t.x = points[3].x;
				MouseConfig.t.y = points[3].y;
				_tsTransform(&MouseConfig.t, &MouseConfig.caldata);

				/* Calculate the delta */
				err = (MouseConfig.t.x - cross[3].x) * (MouseConfig.t.x - cross[3].x) +
					(MouseConfig.t.y - cross[3].y) * (MouseConfig.t.y - cross[3].y);

				if (err <= GINPUT_MOUSE_MAX_CALIBRATION_ERROR * GINPUT_MOUSE_MAX_CALIBRATION_ERROR)
					break;

				gdispFillStringBox(0, 35, width, 40, GINPUT_MOUSE_CALIBRATION_ERROR_TEXT, font2,  Red, Yellow, justifyCenter);
				gfxSleepMilliseconds(5000);
			}
		#endif

		// Restart everything
		gdispCloseFont(font1);
		gdispCloseFont(font2);
		MouseConfig.flags |= FLG_CAL_OK;
		MouseConfig.last_buttons = 0;
		get_calibrated_reading(&MouseConfig.t);
		MouseConfig.flags &= ~FLG_IN_CAL;
		if ((MouseConfig.flags & FLG_INIT_DONE))
			gtimerStart(&MouseTimer, MousePoll, 0, TRUE, GINPUT_MOUSE_POLL_PERIOD);
		
		// Save the calibration data (if possible)
		if (MouseConfig.fnsavecal) {
			MouseConfig.fnsavecal(instance, (const uint8_t *)&MouseConfig.caldata, sizeof(MouseConfig.caldata));
			MouseConfig.flags |= FLG_CAL_SAVED;
		}

		// Clear the screen using the GWIN default background color
		#if GFX_USE_GWIN
			gdispClear(gwinGetDefaultBgColor());
		#else
			gdispClear(Black);
		#endif
	
		return TRUE;
	#endif
}

/* Set the routines to save and fetch calibration data.
 * This function should be called before first calling ginputGetMouse() for a particular instance
 *	as the gdispGetMouse() routine may attempt to fetch calibration data and perform a startup calibration if there is no way to get it.
 *	If this is called after gdispGetMouse() has been called and the driver requires calibration storage, it will immediately save the data is has already obtained.
 * The 'requireFree' parameter indicates if the fetch buffer must be free()'d to deallocate the buffer provided by the Fetch routine.
 */
void ginputSetMouseCalibrationRoutines(uint16_t instance, GMouseCalibrationSaveRoutine fnsave, GMouseCalibrationLoadRoutine fnload, bool_t requireFree) {
	#if GINPUT_MOUSE_NEED_CALIBRATION
		if (instance)
			return;

		MouseConfig.fnloadcal = fnload;
		MouseConfig.fnsavecal = fnsave;
		if (requireFree)
			MouseConfig.flags |= FLG_CAL_FREE;
		else
			MouseConfig.flags &= ~FLG_CAL_FREE;
		#if GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE
			if (!MouseConfig.fnloadcal) {
				MouseConfig.fnloadcal = ginput_lld_mouse_calibration_load;
				MouseConfig.flags &= ~FLG_CAL_FREE;
			}
			if (!MouseConfig.fnsavecal)
				MouseConfig.fnsavecal = ginput_lld_mouse_calibration_save;
		#endif
		if (MouseConfig.fnsavecal && (MouseConfig.flags & (FLG_CAL_OK|FLG_CAL_SAVED)) == FLG_CAL_OK) {
			MouseConfig.fnsavecal(instance, (const uint8_t *)&MouseConfig.caldata, sizeof(MouseConfig.caldata));
			MouseConfig.flags |= FLG_CAL_SAVED;
		}
	#else
		(void)instance, (void)fnsave, (void)fnload, (void)requireFree;
	#endif
}

/* Test if a particular mouse instance requires routines to save its calibration data. */
bool_t ginputRequireMouseCalibrationStorage(uint16_t instance) {
	if (instance)
		return FALSE;

	#if GINPUT_MOUSE_NEED_CALIBRATION && !GINPUT_MOUSE_LLD_CALIBRATION_LOADSAVE
		return TRUE;
	#else
		return FALSE;
	#endif
}

/* Wake up the mouse driver from an interrupt service routine (there may be new readings available) */
void ginputMouseWakeup(void) {
	gtimerJab(&MouseTimer);
}

/* Wake up the mouse driver from an interrupt service routine (there may be new readings available) */
void ginputMouseWakeupI(void) {
	gtimerJabI(&MouseTimer);
}

#endif /* GFX_USE_GINPUT && GINPUT_NEED_MOUSE */
/** @} */
