/***************************************************************************

	machine/pc_mouse.c

	Code for emulating PC-style serial mouses

***************************************************************************/

#include "driver.h"
#include "machine/uart8250.h"
#include "includes/pc_mouse.h"
#include "inputx.h"

static struct {

	PC_MOUSE_PROTOCOL protocol;

	int serial_port; /* -1 for deactivating mouse */
	int inputs;

	UINT8 queue[256];
	UINT8 head, tail, mb;

	emu_timer *timer;

} pc_mouse;

static TIMER_CALLBACK(pc_mouse_scan);

void pc_mouse_initialise(void)
{
	pc_mouse.head = pc_mouse.tail = 0;
	pc_mouse.timer = timer_alloc(pc_mouse_scan, NULL);
	pc_mouse.inputs=UART8250_HANDSHAKE_IN_DSR|UART8250_HANDSHAKE_IN_CTS;
	if (pc_mouse.serial_port!=-1)
		uart8250_handshake_in(pc_mouse.serial_port, pc_mouse.inputs);
}

void pc_mouse_set_serial_port(int uart_index)
{
	pc_mouse.serial_port = uart_index;
}

/* add data to queue */
static void	pc_mouse_queue_data(int data)
{
	pc_mouse.queue[pc_mouse.head] = data;
	pc_mouse.head = ++pc_mouse.head % 256;
}

/**************************************************************************
 *	Check for mouse moves and buttons. Build delta x/y packets
 **************************************************************************/
static TIMER_CALLBACK(pc_mouse_scan)
{
	int n = param;
	static int ox = 0, oy = 0;
	int nx,ny;
	int dx, dy, nb;
	int mbc;

	/* Do not get deltas or send packets if queue is not empty (Prevents drifting) */
	if (pc_mouse.head==pc_mouse.tail)
	{
		nx = readinputportbytag("pc_mouse_x");

		dx = nx - ox;
		if (dx<=-0x800) dx = nx + 0x1000 - ox; /* Prevent jumping */
		if (dx>=0x800) dx = nx - 0x1000 - ox;
		ox = nx;

		ny = readinputportbytag("pc_mouse_y");

		dy = ny - oy;
		if (dy<=-0x800) dy = ny + 0x1000 - oy;
		if (dy>=0x800) dy = ny - 0x1000 - oy;
		oy = ny;

		nb = readinputportbytag("pc_mouse_misc");
		if ((nb & 0x80) != 0)
		{
			pc_mouse.protocol=TYPE_MOUSE_SYSTEMS;
		}
		else
		{
			pc_mouse.protocol=TYPE_MICROSOFT_MOUSE;
		}
		mbc = nb^pc_mouse.mb;
		pc_mouse.mb = nb;

		/* check if there is any delta or mouse buttons changed */
		if ( (dx!=0) || (dy!=0) || (mbc!=0) )
		{
			switch (pc_mouse.protocol)
			{

			default:
			case TYPE_MICROSOFT_MOUSE:
				{
					/* split deltas into packtes of -128..+127 max */
					do
					{
						UINT8 m0, m1, m2;
						int ddx = (dx < -128) ? -128 : (dx > 127) ? 127 : dx;
						int ddy = (dy < -128) ? -128 : (dy > 127) ? 127 : dy;
						m0 = 0x40 | ((nb << 4) & 0x30) | ((ddx >> 6) & 0x03) | ((ddy >> 4) & 0x0c);
						m1 = ddx & 0x3f;
						m2 = ddy & 0x3f;

						/* KT - changed to use a function */
						pc_mouse_queue_data(m0 | 0x40);
						pc_mouse_queue_data(m1 & 0x03f);
						pc_mouse_queue_data(m2 & 0x03f);
						if ((mbc & 0x04) != 0)  /* If button 3 changed send extra byte */
						{
						pc_mouse_queue_data( (nb & 0x04) << 3);
						}

						dx -= ddx;
						dy -= ddy;
					} while( dx || dy );
				}
				break;

				/* mouse systems mouse
				from "PC Mouse information" by Tomi Engdahl */

				/*
				The data is sent in 5 byte packets in following format:
						D7      D6      D5      D4      D3      D2      D1      D0

				1.      1       0       0       0       0       LB      CB      RB
				2.      X7      X6      X5      X4      X3      X2      X1      X0
				3.      Y7      Y6      Y5      Y4      Y3      Y4      Y1      Y0
				4.      X7'     X6'     X5'     X4'     X3'     X2'     X1'     X0'
				5.      Y7'     Y6'     Y5'     Y4'     Y3'     Y4'     Y1'     Y0'

				LB is left button state (0=pressed, 1=released)
				CB is center button state (0=pressed, 1=released)
				RB is right button state (0=pressed, 1=released)
				X7-X0 movement in X direction since last packet in signed byte
					  format (-128..+127), positive direction right
				Y7-Y0 movement in Y direction since last packet in signed byte
					  format (-128..+127), positive direction up
				X7'-X0' movement in X direction since sending of X7-X0 packet in signed byte
					  format (-128..+127), positive direction right
				Y7'-Y0' movement in Y direction since sending of Y7-Y0 in signed byte
					  format (-128..+127), positive direction up

				The last two bytes in the packet (bytes 4 and 5) contains information about movement data changes which have occured after data butes 2 and 3 have been sent. */

				case TYPE_MOUSE_SYSTEMS:
				{

					dy =-dy;

					do
					{
						int ddx = (dx < -128) ? -128 : (dx > 127) ? 127 : dx;
						int ddy = (dy < -128) ? -128 : (dy > 127) ? 127 : dy;

						/* KT - changed to use a function */
						pc_mouse_queue_data(0x080 | ((((nb & 0x04) >> 1) + ((nb & 0x02) << 1) + (nb & 0x01)) ^ 0x07));
						pc_mouse_queue_data(ddx);
						pc_mouse_queue_data(ddy);
						/* for now... */
						pc_mouse_queue_data(0);
						pc_mouse_queue_data(0);
						dx -= ddx;
						dy -= ddy;
					} while( dx || dy );

				}
				break;
			}
		}
	}

	/* Send any data from this scan or any pending data from a previous scan */

	if( pc_mouse.tail != pc_mouse.head )
	{
		int data;

		data = pc_mouse.queue[pc_mouse.tail];
		uart8250_receive(n, data);
		pc_mouse.tail = ++pc_mouse.tail & 255;
	}
}



/**************************************************************************
 *	Check for mouse control line changes and (de-)install timer
 **************************************************************************/

void pc_mouse_handshake_in(int n, int outputs)
{
    int new_msr = 0x00;

	if (n!=pc_mouse.serial_port) return;

    /* check if mouse port has DTR set */
	if( outputs & UART8250_HANDSHAKE_OUT_DTR )
		new_msr |= UART8250_HANDSHAKE_IN_DSR;	/* set DSR */

	/* check if mouse port has RTS set */
	if( outputs & UART8250_HANDSHAKE_OUT_RTS )
		new_msr |= UART8250_HANDSHAKE_IN_CTS;	/* set CTS */

	/* CTS changed state? */
	if (((pc_mouse.inputs^new_msr) & UART8250_HANDSHAKE_IN_CTS)!=0)
	{
		/* CTS just went to 1? */
		if ((new_msr & 0x010)!=0)
		{
			/* set CTS is now 1 */

			/* reset mouse */
			pc_mouse.head = pc_mouse.tail = pc_mouse.mb = 0;

			if ((readinputportbytag("pc_mouse_misc") & 0x80) == 0 )
			{
				/* Identify as Microsoft 3 Button Mouse */
				pc_mouse.queue[pc_mouse.head] = 'M';
				pc_mouse.head = ++pc_mouse.head % 256;
				pc_mouse.queue[pc_mouse.head] = '3';
				pc_mouse.head = ++pc_mouse.head % 256;
			}

			/* start a timer to scan the mouse input */
			timer_adjust(pc_mouse.timer, attotime_zero, pc_mouse.serial_port, ATTOTIME_IN_HZ(240));
		}
		else
		{
			/* CTS just went to 0 */
			timer_adjust(pc_mouse.timer, attotime_zero, pc_mouse.serial_port, attotime_zero);
			pc_mouse.head = pc_mouse.tail = 0;
		}
	}

	pc_mouse.inputs=new_msr;
	uart8250_handshake_in(pc_mouse.serial_port, new_msr);
}



/**************************************************************************
 *	Mouse INPUT_PORT declarations
 **************************************************************************/

INPUT_PORTS_START( pc_mouse_mousesystems )
	PORT_START_TAG( "pc_mouse_misc" )
	PORT_CONFNAME( 0x80, 0x80, "Mouse Protocol" )
	PORT_CONFSETTING( 0x80, "Mouse Systems" )
	PORT_CONFSETTING( 0x00, "Microsoft" )
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("Mouse Left Button") PORT_CODE(MOUSECODE_BUTTON1)
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("Mouse Middle Button") PORT_CODE(MOUSECODE_BUTTON3)
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_NAME("Mouse Right Button") PORT_CODE(MOUSECODE_BUTTON2)

	PORT_START_TAG( "pc_mouse_x" ) /* Mouse - X AXIS */
	PORT_BIT( 0xfff, 0x00, IPT_MOUSE_X) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1)

	PORT_START_TAG( "pc_mouse_y" ) /* Mouse - Y AXIS */
	PORT_BIT( 0xfff, 0x00, IPT_MOUSE_Y) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1)
INPUT_PORTS_END



INPUT_PORTS_START( pc_mouse_microsoft )
	PORT_START_TAG( "pc_mouse_misc" )
	PORT_CONFNAME( 0x80, 0x00, "Mouse Protocol" )
	PORT_CONFSETTING( 0x00, "Microsoft" )
	PORT_CONFSETTING( 0x80, "Mouse Systems" )
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("Mouse Left Button") PORT_CODE(MOUSECODE_BUTTON1)
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("Mouse Middle Button") PORT_CODE(MOUSECODE_BUTTON3)
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_NAME("Mouse Right Button") PORT_CODE(MOUSECODE_BUTTON2)

	PORT_START_TAG( "pc_mouse_x" ) /* Mouse - X AXIS */
	PORT_BIT( 0xfff, 0x00, IPT_MOUSE_X) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1)

	PORT_START_TAG( "pc_mouse_y" ) /* Mouse - Y AXIS */
	PORT_BIT( 0xfff, 0x00, IPT_MOUSE_Y) PORT_SENSITIVITY(100) PORT_KEYDELTA(0) PORT_PLAYER(1)
INPUT_PORTS_END



INPUT_PORTS_START( pc_mouse_none )
	PORT_START_TAG( "pc_mouse_misc" )      /* IN12 */
	PORT_BIT ( 0xffff, 0x0000, IPT_UNUSED )
	PORT_START_TAG( "pc_mouse_x" )      /* IN13 */
	PORT_BIT ( 0xffff, 0x0000, IPT_UNUSED )
	PORT_START_TAG( "pc_mouse_y" )      /* IN14 */
	PORT_BIT ( 0xffff, 0x0000, IPT_UNUSED )
INPUT_PORTS_END
