#include <stdio.h>
#include <time.h>
#include "X.h"
#define NEED_EVENTS
#include "Xproto.h"
#include "miscstruct.h"
#include "window.h"
#include "dix.h"
#include "input.h"
#include "inputstr.h"
#include "scrnintstr.h"
#include "cursorstr.h"
#include "ibmmouse.h"
#include "servermd.h"
#include <dos.h>
#include <i86.h>
#include "ibmfuncs.h"
#include "funcs.h"

static void mouseControlProc();
static void mouse_abort(void);
static int mouseGetMotionProc(DevicePtr, xTimecoord *, CARD32, CARD32, ScreenPtr);
static void mouseGenerateEvent(struct mouse_event, Bool push);
extern void mouseSetCursor(int, int);
static void _loadds far mouse_handler(int conditionMask, int buttonState,
    short m_dx, short m_dy);

extern u_long TimeLastInput;
extern int screenIsSaved;
extern u_char input_ready;

static DevicePtr	mouse_ptr;
static unsigned int lastButtonState=0;

#define NMQUEUE	64
struct mouse_event {
	unsigned short	buttonState;
	short		x, y;
	unsigned long	time;
};
static int m_head, m_tail;
static struct mouse_event m_queue[NMQUEUE];

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

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

int mouse_flags = MOUSE_SIM3;

mouseproc(device, action)
DevicePtr device;
int action;
{
	unsigned char map[6];
	int nbuttons;
	union REGS regs;

	switch (action) {
	    case DEVICE_INIT:
		regs.w.ax = 0;	/* Reset and Status */
		int386(0x33, &regs, &regs);
		if (regs.w.ax != 0xffff) {
		    printf("Warning: Mouse driver not installed.\n");
		    return 1;
		}
		nbuttons = regs.w.bx;

		if (nbuttons > 5)
			nbuttons = 5;	/* Sanity */
		if (nbuttons == 3) {
			mouse_flags &= ~MOUSE_SIM3;
			map[2] = 3;
			map[3] = 2;
		} 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;

		m_head = 0;
		m_tail = 0;
		SetEventHandler(0, mouse_handler);
		regs.w.ax = 2;	/* Hide Cursor */
		int386(0x33, &regs, &regs);

		InitPointerDeviceStruct(device, map, nbuttons,
		    mouseGetMotionProc, mouseControlProc, NMQUEUE);
		return Success;
	    case DEVICE_ON:
		mouse_ptr = device;
		SetEventHandler(0x7f, mouse_handler);
		if (!(mouse_flags & MOUSE_ATEXIT))
			atexit(mouse_abort);
		mouse_flags |= (MOUSE_OPEN|MOUSE_ON|MOUSE_ATEXIT);
		return Success;
	    case DEVICE_OFF:
		mouse_flags &= ~MOUSE_ON;
		HideCursor();
		SetEventHandler(0, mouse_handler);

		return Success;
	    case DEVICE_CLOSE:
		SetEventHandler(0, mouse_handler);
		mouse_flags &= ~MOUSE_OPEN;
		return Success;
	}
	return 1;
}

static void
mouse_abort()
{
	if (mouse_flags & MOUSE_OPEN) {
		SetEventHandler(0, mouse_handler);
		mouse_flags &= ~MOUSE_OPEN;
	}
}

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

	device = device;
	pScr = pScr;

	/* The oldest event is at m_tail.  The newest is m_tail + 1. */
	_disable();
	i = m_tail;
	do {
	    q = &m_queue[i];
	    if (!--i)
		i = NMQUEUE - 1;
	    if (q->time >= start &&
		q->time <= stop) {
		buff->time = q->time;
		buff->x = q->x;
		buff->y = q->y;
		buff++;
		n++;
	    }
	} while (i != m_tail);
	return n;
}

static int accl_num, accl_den, threshold;

static void
mouseControlProc(device, ctrl)
DevicePtr device;
PtrCtrl *ctrl;
{
	device = device;

	threshold = ctrl->threshold;
	accl_num = ctrl->num;
	accl_den = ctrl->den;
}

Bool
mouseSetCursorPosition(pScr, newx, newy, generateEvent)
ScreenPtr pScr;
Bool generateEvent;
int newx, newy;
{
	struct mouse_event mevent;

	pScr = pScr;
	if (!(mouse_flags & MOUSE_ON))
		return FALSE;
	mouse_X = newx;
	mouse_Y = newy;
	if (generateEvent) {
		mevent.x = mouse_X;
		mevent.y = mouse_Y;
		mevent.buttonState = real_state;
		mevent.time = *(u_long *)0x46c * 55;
		mouseGenerateEvent(mevent, 1);
	} else
		mouseSetCursor(newx, newy);
	return TRUE;
}

void
mouseCursorLimits(pScr, pCur, pHotBox, pTopLeftBox)
ScreenPtr pScr;
CursorPtr pCur;
BoxPtr pHotBox, pTopLeftBox;
{
	pScr = pScr;
	pCur = pCur;
	pHotBox = pHotBox;

	pTopLeftBox->x1 = 0;
	pTopLeftBox->y1 = 0;
	pTopLeftBox->x2 = pScr->width;
	pTopLeftBox->y2 = pScr->height;
}

static BoxRec limits;

void
mouseConstrainCursor(pScr, pBox)
ScreenPtr pScr;
BoxPtr pBox;
{
	pScr = pScr;

	if (!(mouse_flags & MOUSE_ON))
		return;

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

#ifdef notdef
	SetHorizontalLimits (limits.x1, limits.x2);
	SetVerticalLimits (limits.y1, limits.y2);
#endif
}

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

void
mouseProcessInput()
{
	struct mouse_event event;
	int dosend=0;

	if (screenIsSaved == SCREEN_SAVER_ON)
		SaveScreens(SCREEN_SAVER_OFF, ScreenSaverReset);

	if (m_head == m_tail)
	    return;
	_disable();
	while (m_head != m_tail) {
		event = m_queue[m_head];
		m_head = (m_head + 1) % NMQUEUE;
		_enable();

		if (lastButtonState != event.buttonState) {
			lastButtonState = event.buttonState;
			mouseGenerateEvent(event, 0);
			dosend = 0;
		} else
			dosend++;

		_disable();
	}
	if (mouse_flags & MOUSE_PEND) {
		if (!dosend) {
			event.buttonState = real_state;
			dosend++;
		}
	} else
		input_ready &= ~INPUT_MOUSE;
	_enable();
	if (dosend)
		mouseGenerateEvent(event, 0);
}

static void
mouseGenerateEvent(mevent, push)
struct mouse_event mevent;
Bool push;
{
	int i, mask;
	xEvent Event;

	mouseSetCursor(mevent.x, mevent.y);
	if (mouse_flags & MOUSE_SIM3) {
	    if (!push) {
		mevent.buttonState &= 3;
		mask = real_state ^ mevent.buttonState;
		if (mouse_flags & MOUSE_PEND) {
		    if (mevent.time - mouse_lasttime < 110) {
			if (mask) {	/* button change */
			    if (mevent.buttonState == 3) {
				real_state = 3;
				mevent.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 = mevent.buttonState;
				mevent.buttonState = real_state;
				mouseGenerateEvent(mevent, 1);
				mevent.buttonState = save_state;
				real_state = mevent.buttonState;
				/* now fall through */
			    }
			    mouse_flags &= ~MOUSE_PEND;
			} else
			    mevent.buttonState = button_state;	/* no change */
		    } else {
			if (mask)
			    goto extra;
			real_state = mevent.buttonState;
			/* timeout -- let real button state through */
			mouse_flags &= ~MOUSE_PEND;
		    }
		} else if (mask) {
		    /* nothing pending */
		    real_state = mevent.buttonState;
		    if ((mask & mevent.buttonState) == 3) {
			/* both went down simultaneously */
			mevent.buttonState = 4;		/* simulate 3 */
			mouse_flags |= MOUSE_DOWN;
		    } else if (mouse_flags & MOUSE_DOWN) {
			if (mevent.buttonState == 0)
			    mouse_flags &= ~MOUSE_DOWN;
			else
			    mevent.buttonState = button_state;
		    } else if (mevent.buttonState == mask) {
			mouse_flags |= MOUSE_PEND;
			mevent.buttonState = button_state;
			mouse_lasttime = mevent.time;
		    }
		} else if (mouse_flags & MOUSE_DOWN)
		    mevent.buttonState = button_state;
	    }
	}

	/*
	 * At this point mevent.buttonState is the state of the LOGICAL
	 * buttons (after simulating the middle button).
	 */
	mask = button_state ^ mevent.buttonState;
	Event.u.keyButtonPointer.time = mevent.time;
	Event.u.keyButtonPointer.rootX = mevent.x;
	Event.u.keyButtonPointer.rootY = mevent.y;
	if (mask) {
		button_state = mevent.buttonState;
		do {
			i = (-mask) & mask;
			Event.u.u.type = (i & mevent.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);
	}
}

/* called by spawn_shell */
void
mouse_save()
{
	if (mouse_flags & MOUSE_OPEN)
	    SetEventHandler(0, mouse_handler);
}

void
mouse_reset()
{
	if (!(mouse_flags & MOUSE_OPEN))
		return;
	SetEventHandler(0x7f, mouse_handler);
}


static void
SetEventHandler(mask, proc)
unsigned short mask;
void (far *proc)();
{
    union REGS regs;
    struct SREGS sregs;

    segread(&sregs);

    regs.w.ax = 0x0c;	/* Set Interrupt Subroutine */
    regs.w.cx = mask;
    regs.x.edx = FP_OFF(proc);
    sregs.es   = FP_SEG(proc);
    int386x(0x33, &regs, &regs, &sregs);
}

#pragma off (check_stack)
static void _loadds far
mouse_handler(int conditionMask, int buttonState, short m_dx, short m_dy)
{
#pragma aux mouse_handler parm [EAX] [EBX] [ESI] [EDI]
    static short lastx = 0, lasty = 0;
    struct mouse_event *m;
    short dx, dy;

    if (conditionMask & 1) {
	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_X < limits.x1)
		mouse_X = limits.x1;
	if (mouse_X > limits.x2)
		mouse_X = limits.x2;
    }
    if (!(mouse_flags & MOUSE_ON))
	    return;

    input_ready |= INPUT_MOUSE;
    m = &m_queue[m_tail];
    m_tail = (m_tail + 1) % NMQUEUE;

    /* If the mouse queue was full, drop the oldest event. */
    if (m_head == m_tail)
	m_head = (m_head + 1) % NMQUEUE;
    m->buttonState = buttonState;
    m->x = mouse_X;
    m->y = mouse_Y;
    TimeLastInput = *(u_long *)0x46c * 55;
    m->time = TimeLastInput;
}
