#include "X.h"
#define NEED_EVENTS
#include "Xproto.h"
#include "miscstru.h"
#include "window.h"
#include "dix.h"
#include "input.h"
#include "inputstr.h"
#include "scrnints.h"
#include "cursorst.h"
#include "ibmmouse.h"
#include "servermd.h"
#include <dos.h>
#include "mouse.h"
#include "ibmfuncs.h"

static void mouseControlProc();
static int mouseGetMotionProc(DevicePtr, xTimecoord *, CARD32, CARD32, ScreenPtr);
void _far mouseIntr();
static void _fastcall _near mouseGenerateEvent(struct _IBM_Mouse _near *, int);
extern void _fastcall _near mouseSetCursor(int, int);
extern void HideCursor(void);

extern u_short near input_ready;
extern u_long near TimeLastInput;
extern int near screenIsSaved;

static struct _IBM_Mouse	near m_queue[NMQUEUE];
static struct _IBM_Mouse	near *m_head, near * near * near m_tail;
static struct _IBM_Mouse	near *m_free, near * near * near m_tfree;
static DevicePtr	mouse_ptr;
static Bool near DriverInstalled(void);
static void near SetEventHandler(unsigned mask, void (_far *proc)());

static struct _IBM_Mouse _near fake;	/* fake mouse event */

/* global variables the keyboard routine can use */
u_short near button_state=0;
int	near mouse_X, near mouse_Y;

/* stuff used in the code to simulate button 3 */
int _near real_state = 0;
u_long _near mouse_lasttime;

int _near mouse_flags = MOUSE_SIM3;

mouseproc(device, action)
DevicePtr device;
int action;
{
	unsigned char map[6];
	int status, nbuttons;
	int i;

	switch (action) {
	    case DEVICE_INIT:

		if (!DriverInstalled()) {
			printf("No mouse driver!\n");
			return 1;
		}
		m_head = 0;
		m_tail = &m_head;
		m_free = 0;
		m_tfree = &m_free;
		for (i = 0; i < NMQUEUE; i++) {
			*m_tfree = &m_queue[i];
			m_tfree = &m_queue[i].next;
		}
		/* This call does a reset also */
		/*
		FlagReset(&status, &nbuttons);
		*/
		_asm {
			mov	ax, 0
			int	0x33	  ; call mouse driver function 
			mov	status,ax
			mov	nbuttons,bx
		}
		if (!status) {
			printf("Mouse not installed.\n");
			return 1;
		}
		if (nbuttons > 5)
			nbuttons = 5;	/* Sanity */
		if (nbuttons == 3) {
			map[2] = 3;
			map[3] = 2;
			mouse_flags &= ~MOUSE_SIM3;
		} else if (nbuttons == 2) {
			if (mouse_flags & MOUSE_SIM3) {
				nbuttons = 3;
				map[2] = 3;
				map[3] = 2;
			} else
				map[2] = 2;
		}
		map[0] = 0;
		map[1] = 1;
		SetEventHandler(0, mouseIntr);
		{
			/* HideCursor(); */
			_asm	mov	ax,2
			_asm	int	0x33
		}
		InitPointerDeviceStruct(device, map, nbuttons,
		    mouseGetMotionProc, mouseControlProc, NMQUEUE);
		return Success;
	    case DEVICE_ON:
		SetEventHandler((unsigned)0x7f, mouseIntr);
		mouse_ptr = device;
		_disable();
		mouse_flags |= MOUSE_ON;
		_enable();
		return Success;
	    case DEVICE_OFF:
		HideCursor();
		SetEventHandler(0, mouseIntr);
		mouse_flags &= ~MOUSE_ON;
		return Success;
	    case DEVICE_CLOSE:
		ClearEventHandler();
		return Success;
	}
	return 1;
}

static int
mouseGetMotionProc(device, buff, start, stop, pScr)
DevicePtr device;
xTimecoord *buff;
CARD32 start, stop;
ScreenPtr pScr;
{
	struct _IBM_Mouse near *q;
	int i=0;

	/* The free list has past events */
	for (q = m_free; q; q = q->next) {
		if (q->time < start ||
		    q->time > stop)
			continue;
		buff->time = q->time;
		buff->x = q->x;
		buff->y = q->y;
		buff++;
		i++;
	}
	return i;
}

static int accl_num, accl_den, threshold;
static BoxRec limits;

static void
mouseControlProc(device, ctrl)
DevicePtr device;
PtrCtrl *ctrl;
{
	threshold = ctrl->threshold;
	accl_num = ctrl->num;
	accl_den = ctrl->den;
}

#pragma check_stack(off)
/* Danger: Interrupt routine! */
void _far
mouseIntr()
{
	static short lastx = 0, lasty = 0;
	u_short conditionMask, buttonState;
	struct _IBM_Mouse near *m;
	int dx, dy;
	short m_dx, m_dy;

	{
		_asm	mov	conditionMask,ax
		_asm	mov	ax,SEG input_ready
		_asm	mov	ds,ax
		_asm	mov	buttonState,bx
		_asm	mov	m_dx,si
		_asm	mov	m_dy,di
	}
	dx = m_dx - lastx;
	lastx = m_dx;
	dy = m_dy - lasty;
	lasty = m_dy;
	if ((dx > 0 ? dx : -dx) > threshold)
		dx = dx * accl_num / accl_den;
	if ((dy > 0 ? dy : -dy) > threshold)
		dy = dy * accl_num / accl_den;
	mouse_X += dx;
	mouse_Y += dy;
	if (mouse_Y < limits.y1)
		mouse_Y = limits.y1;
	if (mouse_Y > limits.y2)
		mouse_Y = limits.y2;
	if (!(mouse_flags & MOUSE_ON))
		return;

	input_ready |= INPUT_MOUSE;
	if (m = m_free) {
		m_free = m_free->next;
		if (!m_free)
			m_tfree = &m_free;
	} else {
		/* No free queue elements, take the first one off the input
		 * list. */
		m = m_head;
		m_head = m_head->next;
		mouse_flags |= MOUSE_OFLO;
	}
	m->next = 0;
	m->buttonState = buttonState;
	m->x = mouse_X;
	m->y = mouse_Y;
	_disable();
	m->time = TimeLastInput = clock();
	_enable();
	*m_tail = m;
	m_tail = &m->next;
}
#pragma check_stack()

Bool
mouseSetCursorPosition(pScr, newx, newy, generateEvent)
ScreenPtr pScr;
Bool generateEvent;
int newx, newy;
{
	_disable();
	mouse_X = newx;
	mouse_Y = newy;
	_enable();
	if (generateEvent) {
		fake.buttonState = button_state;
		_disable();
		fake.x = mouse_X;
		fake.y = mouse_Y;
		fake.time = clock();
		_enable();
		mouseGenerateEvent(&fake, 1);
	}
	else
		mouseSetCursor(newx, newy);
	return TRUE;
}

void
mouseCursorLimits(pScr, pCur, pHotBox, pTopLeftBox)
ScreenPtr pScr;
CursorPtr pCur;
BoxPtr pHotBox, pTopLeftBox;
{
	pTopLeftBox->x1 = 0;
	pTopLeftBox->y1 = 0;
	pTopLeftBox->x2 = pScr->width;
	pTopLeftBox->y2 = pScr->height;
}

void
mouseConstrainCursor(pScr, pBox)
ScreenPtr pScr;
BoxPtr pBox;
{
	int Min, Max;

	if (!(mouse_flags & MOUSE_ON))
		return;

	limits = *pBox;
	limits.x2--;
	limits.y2--;
}

void
mousePointerNonInterestBox(pScr, pBox)
ScreenPtr pScr;
BoxPtr pBox;
{
}

static unsigned int lastButtonState=0;

void near
mouseProcessInput()
{
	struct _IBM_Mouse near *q, near *last = 0;

	if (!(mouse_flags & MOUSE_ON))
		return;
	if (MOUSE_OFLO & mouse_flags) {
		printf("Mouse: overflow\n");
		_disable();
		mouse_flags &= ~MOUSE_OFLO;
		_enable();
	}
	if (screenIsSaved == SCREEN_SAVER_ON)
		SaveScreens(SCREEN_SAVER_OFF, ScreenSaverReset);
again:
	while (m_head) {
		_disable();
		q = m_head;
		m_head = m_head->next;
		if (!m_head)
			m_tail = &m_head;
		_enable();
		q->next = 0;
		if (last) {
			_disable();
			*m_tfree = last;
			m_tfree = &last->next;
			_enable();
		}
		last = q;
		if (lastButtonState != last->buttonState) {
			lastButtonState = last->buttonState;
			mouseGenerateEvent(last, 0);
			_disable();
			*m_tfree = last;
			m_tfree = &last->next;
			_enable();
			last = 0;
		}
	}

	if (mouse_flags & MOUSE_PEND) {
		if (!last) {
			fake.next = 0;
			fake.buttonState = real_state;
			_disable();
			fake.x = mouse_X;
			fake.y = mouse_Y;
			fake.time = clock();
			_enable();
			mouseGenerateEvent(&fake, 0);
		}
	} else {
		_disable();
		input_ready &= ~INPUT_MOUSE;
		_enable();
	}

	if (last) {
		lastButtonState = last->buttonState;
		mouseGenerateEvent(last, 0);
		_disable();
		*m_tfree = last;
		m_tfree = &last->next;
		_enable();
	}
	if (m_head) {		/* race condition */
		goto again;
	}
}

static void _fastcall _near
mouseGenerateEvent(pq, push)
struct _IBM_Mouse _near *pq;
int push;
{
	int i, mask;
	xEvent Event;

	mouseSetCursor(pq->x, pq->y);
	if (mouse_flags & MOUSE_SIM3) {
	    if (!push) {
		pq->buttonState &= 3;
		mask = real_state ^ pq->buttonState;
		if (mouse_flags & MOUSE_PEND) {
		    if (pq->time - mouse_lasttime < 100) {
			if (mask) {	/* button change */
			    if (pq->buttonState == 3) {
				real_state = 3;
				pq->buttonState = 4;		/* simulate 3 */
				mouse_flags |= MOUSE_DOWN;
			    } else {
				/* oh oh - must have been a down/up of
				 * one button.  We have to simulate an extra
				 * event.
				 */
				u_short save_state;
extra:
				save_state = pq->buttonState;
				pq->buttonState = real_state;
				mouseGenerateEvent(pq, 1);
				pq->buttonState = save_state;
				real_state = save_state;
				/* now fall through */
			    }
			    mouse_flags &= ~MOUSE_PEND;
			} else
			    pq->buttonState = button_state;	/* no change */
		    } else {
			if (mask)
			    goto extra;
			real_state = pq->buttonState;
			/* timeout -- let real button state through */
			mouse_flags &= ~MOUSE_PEND;
		    }
		} else if (mask) {
		    /* nothing pending */
		    real_state = pq->buttonState;
		    if ((mask & pq->buttonState) == 3) {
			/* both went down simultaneously */
			pq->buttonState = 4;		/* simulate 3 */
			mouse_flags |= MOUSE_DOWN;
		    } else if (mouse_flags & MOUSE_DOWN) {
			if (pq->buttonState == 0)
			    mouse_flags &= ~MOUSE_DOWN;
			else
			    pq->buttonState = button_state;
		    } else if (pq->buttonState == mask) {
			mouse_flags |= MOUSE_PEND;
			pq->buttonState = button_state;
			mouse_lasttime = pq->time;
		    }
		} else if (mouse_flags & MOUSE_DOWN)
		    pq->buttonState = button_state;
	    }
	}
	mask = button_state ^ pq->buttonState;
	button_state = pq->buttonState;
	Event.u.keyButtonPointer.time = pq->time;
	Event.u.keyButtonPointer.rootX = pq->x;
	Event.u.keyButtonPointer.rootY = pq->y;
	if (mask) {
		do {
			i = (-mask) & mask;
			Event.u.u.type = (i & pq->buttonState) ? ButtonPress :
				ButtonRelease;
			/* This is an efficient implemtation of ffs 
			 * for the case of only bits 0, 1 and 2. */
			Event.u.u.detail = (i >> 1) + 1;
			(*mouse_ptr->processInputProc)(&Event, mouse_ptr, 1);
			mask &= ~i;
		} while (mask);
	} else {
		Event.u.u.type = MotionNotify;
		(*mouse_ptr->processInputProc)(&Event, mouse_ptr, 1);
	}
}

static Bool near
DriverInstalled()
{
	register u_char _far *driver;

	if (*(u_short _far *)0x000000CC == 0)
		return FALSE;
	driver = *(u_char _far * _far *)0x000000CC;
	if (*driver == 0xcf)	/* CF = iret */
		return FALSE;
	return TRUE;
}

static void near
SetEventHandler(mask, proc)
unsigned mask;
void (_far *proc)();
{
	_asm	mov	cx,mask
	_asm	mov	dx,word ptr [proc]
	_asm	mov	es,word ptr [proc+2]
	_asm	mov	ax,12
	_asm	int	0x33
}

void
ClearEventHandler()
{
	_asm	sub	cx,cx
	_asm	sub	dx,dx
	_asm	mov	es,dx
	_asm	mov	ax,12
	_asm	int	0x33
}
