/*
    Microtouch touch screen controller

    Written by Mariusz Wojcieszek

    Notes/ToDo:
    - calibration mode (command CX)
    - only tablet format and decimal format are supported for returning touch screen state

*/

#include "microtch.h"

#define LOG 0

const device_type MICROTOUCH = &device_creator<microtouch_device>;

microtouch_device::microtouch_device(const machine_config &mconfig, device_type type, const char* name, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, type, name, tag, owner, clock)
{
}

microtouch_device::microtouch_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, MICROTOUCH, "Microtouch Touchscreen", tag, owner, clock)
{
}

int microtouch_device::check_command( const char* commandtocheck, int command_len, UINT8* command_data )
{
	if ( (command_len == (strlen(commandtocheck) + 2)) &&
		 (command_data[0] == 0x01) &&
		 (strncmp(commandtocheck, (const char*)command_data + 1, strlen(commandtocheck)) == 0) &&
		 (command_data[command_len-1] == 0x0d) )
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

void microtouch_device::send_format_table_packet(UINT8 flag, int x, int y)
{
	m_tx_buffer[m_tx_buffer_num++] = flag;
	// lower byte (7bits) of x coordinate
	m_tx_buffer[m_tx_buffer_num++] = x & 0x7f;
	// higher byte (7bits) of x coordinate
	m_tx_buffer[m_tx_buffer_num++] = (x >> 7) & 0x7f;
	// lower byte (7bits) of y coordinate
	m_tx_buffer[m_tx_buffer_num++] = y & 0x7f;
	// higher byte (7bits) of y coordinate
	m_tx_buffer[m_tx_buffer_num++] = (y >> 7) & 0x7f;
}

void microtouch_device::send_format_decimal_packet(int x, int y)
{
	int decx, decy;

	decx = x / 16;
	if ( decx > 999 )
		decx = 999;
	decy = y / 16;
	if ( decy > 999 )
		decy = 999;

	// header byte
	m_tx_buffer[m_tx_buffer_num++] = 0x01;
	// x coordinate in decimal mode
	m_tx_buffer[m_tx_buffer_num++] = (decx / 100) + '0';
	m_tx_buffer[m_tx_buffer_num++] = ((decx / 10) % 10) + '0';
	m_tx_buffer[m_tx_buffer_num++] = (decx % 10) + '0';
	// comma (separator)
	m_tx_buffer[m_tx_buffer_num++] = ',';
	// y coordinate in decimal mode
	m_tx_buffer[m_tx_buffer_num++] = (decy / 100) + '0';
	m_tx_buffer[m_tx_buffer_num++] = ((decy / 10) % 10) + '0';
	m_tx_buffer[m_tx_buffer_num++] = (decy % 10) + '0';
	// terminator
	m_tx_buffer[m_tx_buffer_num++] = 0x0d;
}

void microtouch_device::send_touch_packet()
{
	int tx = ioport("TOUCH_X")->read();
	int ty = ioport("TOUCH_Y")->read();

	if ( m_out_touch_cb == NULL ||
		 m_out_touch_cb( &tx, &ty ) != 0 )
	{
		ty = 0x4000 - ty;

		switch( m_format )
		{
			case FORMAT_TABLET:
				send_format_table_packet(0xc8, tx, ty);
				break;
			case FORMAT_DECIMAL:
				send_format_decimal_packet(tx, ty);
				break;
			case FORMAT_UNKNOWN:
				break;
		}
		m_last_touch_state = 1;
		m_last_x = tx;
		m_last_y = ty;
	}
}

void microtouch_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	if ( m_tx_buffer_ptr < m_tx_buffer_num )
	{
		tx( m_tx_buffer[m_tx_buffer_ptr++] );
		if ( m_tx_buffer_ptr == m_tx_buffer_num )
		{
			m_tx_buffer_ptr = m_tx_buffer_num = 0;
		}
		return;
	}

	if ( (m_reset_done == 0) ||
		 (m_format == FORMAT_UNKNOWN) ||
		 (m_mode != MODE_STREAM))
	{
		return;
	}

	// send format tablet packet
	if ( ioport("TOUCH")->read() & 0x01 )
	{
		send_touch_packet();
	}
	else
	{
		if ( m_last_touch_state == 1 )
		{
			m_last_touch_state = 0;
			switch( m_format )
			{
				case FORMAT_TABLET:
					send_format_table_packet(0x88, m_last_x, m_last_y);
					break;
				case FORMAT_DECIMAL:
					send_format_decimal_packet(m_last_x, m_last_y);
					break;
				case FORMAT_UNKNOWN:
					break;
			}
		}
	}
}

void microtouch_device::device_config_complete()
{
	const microtouch_interface *intf = reinterpret_cast<const microtouch_interface *>(static_config());
	if(intf != NULL)
		*static_cast<microtouch_interface *>(this) = *intf;
	else
	{
		memset(&m_out_tx_cb, 0, sizeof(m_out_tx_cb));
		memset(&m_out_touch_cb, 0, sizeof(m_out_touch_cb));
	}
}

void microtouch_device::device_start()
{
	memset(m_rx_buffer, 0, sizeof(m_rx_buffer));
	memset(m_tx_buffer, 0, sizeof(m_tx_buffer));
	m_rx_buffer_ptr = 0;
	m_tx_buffer_ptr = 0;
	m_tx_buffer_num = 0;
	m_reset_done = 0;
	m_format = 0;
	m_mode = 0;
	m_last_x = 0;
	m_last_y = 0;
	m_last_touch_state = -1;

	m_timer = timer_alloc();
	m_timer->adjust(attotime::from_hz(167*5), 0, attotime::from_hz(167*5));

	m_format = FORMAT_UNKNOWN;
	m_mode = MODE_INACTIVE;

	save_item(NAME(m_reset_done));
	save_item(NAME(m_last_touch_state));
	save_item(NAME(m_last_x));
	save_item(NAME(m_last_y));
	save_item(NAME(m_rx_buffer));
	save_item(NAME(m_rx_buffer_ptr));
	save_item(NAME(m_tx_buffer));
	save_item(NAME(m_tx_buffer_num));
	save_item(NAME(m_tx_buffer_ptr));
	save_item(NAME(m_format));
	save_item(NAME(m_mode));
	m_out_tx_func.resolve(m_out_tx_cb, *this);
}


WRITE8_MEMBER(microtouch_device::rx)
{
	m_rx_buffer[m_rx_buffer_ptr] = data;
	m_rx_buffer_ptr++;
	if(m_rx_buffer_ptr == 16)
		return;

	if (m_rx_buffer_ptr > 0 && (m_rx_buffer[m_rx_buffer_ptr-1] == 0x0d))
	{
		if (LOG)
		{
			char command[16];
			memset(command, 0, sizeof(command));
			strncpy( command, (const char*)m_rx_buffer + 1, m_rx_buffer_ptr - 2 );
			logerror("Microtouch: received command %s\n", command);
		}
		// check command
		if ( check_command( "MS", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_mode = MODE_STREAM;
		}
		else if ( check_command( "MI", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_mode = MODE_INACTIVE;
		}
		else if ( check_command( "MP", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_mode = MODE_POINT;
		}
		else if ( check_command( "R", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_tx_buffer_num = 0;
			m_reset_done = 1;
		}
		else if ( check_command( "FT", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_format = FORMAT_TABLET;
		}
		else if ( check_command( "FD", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			m_format = FORMAT_DECIMAL;
		}
		else if ( check_command("OI", m_rx_buffer_ptr, m_rx_buffer ) )
		{
			// output identity - SMT3, ver 01.00
			m_tx_buffer[m_tx_buffer_num++] = 0x01;
			m_tx_buffer[m_tx_buffer_num++] = 'Q';
			m_tx_buffer[m_tx_buffer_num++] = '1';
			m_tx_buffer[m_tx_buffer_num++] = '0';
			m_tx_buffer[m_tx_buffer_num++] = '1';
			m_tx_buffer[m_tx_buffer_num++] = '0';
			m_tx_buffer[m_tx_buffer_num++] = '0';
			m_tx_buffer[m_tx_buffer_num++] = 0x0d;
			m_rx_buffer_ptr = 0;
			return;
		}
		// send response
		m_tx_buffer[m_tx_buffer_num++] = 0x01;
		m_tx_buffer[m_tx_buffer_num++] = 0x30;
		m_tx_buffer[m_tx_buffer_num++] = 0x0d;
		m_rx_buffer_ptr = 0;
	}
};

INPUT_CHANGED_MEMBER( microtouch_device::touch )
{
	if ( newval && ( m_mode == MODE_POINT ) )
	{
		send_touch_packet();
	}
}

static INPUT_PORTS_START(microtouch)
	PORT_START("TOUCH")
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME( "Touch screen" ) PORT_CHANGED_MEMBER( DEVICE_SELF,microtouch_device, touch, 0 )
	PORT_START("TOUCH_X")
	PORT_BIT( 0x3fff, 0x2000, IPT_LIGHTGUN_X ) PORT_CROSSHAIR(X, 1.0, 0.0, 0) PORT_SENSITIVITY(45) PORT_KEYDELTA(15)
	PORT_START("TOUCH_Y")
	PORT_BIT( 0x3fff, 0x2000, IPT_LIGHTGUN_Y ) PORT_CROSSHAIR(Y, 1.0, 0.0, 0) PORT_SENSITIVITY(45) PORT_KEYDELTA(15)
INPUT_PORTS_END

ioport_constructor microtouch_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(microtouch);
}

const device_type MICROTOUCH_SERIAL = &device_creator<microtouch_serial_device>;

microtouch_serial_device::microtouch_serial_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: microtouch_device(mconfig, MICROTOUCH_SERIAL, "Microtouch Serial Touchscreen", tag, owner, clock),
	  device_serial_interface(mconfig, *this)
{
}

void microtouch_serial_device::device_config_complete()
{
	const microtouch_serial_interface *intf = reinterpret_cast<const microtouch_serial_interface *>(static_config());
	if(intf != NULL)
		*static_cast<microtouch_serial_interface *>(this) = *intf;
	else
	{
		memset(&m_out_stx_cb, 0, sizeof(m_out_stx_cb));
	}
	memset(&(microtouch_interface::m_out_tx_cb), 0, sizeof(microtouch_interface::m_out_tx_cb));
	memset(&m_out_touch_cb, 0, sizeof(m_out_touch_cb));
}

void microtouch_serial_device::device_start()
{
	microtouch_device::device_start();
	set_data_frame(8, 1, SERIAL_PARITY_NONE); //8N1?
	set_tra_rate(clock());
	set_rcv_rate(clock());
	m_out_stx_func.resolve(m_out_stx_cb, *this);
	m_output_valid = false;

	save_item(NAME(m_output_valid));
	save_item(NAME(m_output));
}

void microtouch_serial_device::tx(UINT8 data)
{
	m_output = data;
	m_output_valid = true;
	if(is_transmit_register_empty())
		tra_complete();
}

void microtouch_serial_device::tra_callback()
{
	m_out_stx_func(transmit_register_get_data_bit());
}

void microtouch_serial_device::tra_complete()
{
	if(m_output_valid)
	{
		transmit_register_setup(m_output);
		m_output_valid = false;
	}
}

void microtouch_serial_device::rcv_complete()
{
	receive_register_extract();
	microtouch_device::rx(*machine().memory().first_space(), 0, get_received_char());
}
