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

    input.c

    Handle input from the user.

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

****************************************************************************

    To do:
        * auto-selecting joystick configs
        * per-joystick configs?
        * test half-axis selections
        * add input test menu
        * get rid of osd_customize_inputport_list

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

#include "emu.h"
#include "emuopts.h"
#include "profiler.h"



//**************************************************************************
//  CONSTANTS
//**************************************************************************

// invalid memory value for axis polling
const INT32 INVALID_AXIS_VALUE		= 0x7fffffff;

// additional expanded input codes for sequences
const input_code input_seq::end_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_END);
const input_code input_seq::default_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_DEFAULT);
const input_code input_seq::not_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_NOT);
const input_code input_seq::or_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_OR);

// constant sequences
const input_seq input_seq::empty_seq;



//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************

// ======================> input_device_item

// a single item on an input device
class input_device_item
{
protected:
	// construction/destruction
	input_device_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate, input_item_class itemclass);
	virtual ~input_device_item() { }

public:
	// getters
	input_device &device() const { return m_device; }
	input_manager &manager() const { return m_device.manager(); }
	running_machine &machine() const { return m_device.machine(); }
	const char *name() const { return m_name; }
	void *internal() const { return m_internal; }
	input_item_id itemid() const { return m_itemid; }
	input_item_class itemclass() const { return m_itemclass; }
	const char *token() const { return m_token; }
	INT32 current() const { return m_current; }
	INT32 memory() const { return m_memory; }

	// helpers
	INT32 update_value() { return m_current = (*m_getstate)(m_device.internal(), m_internal); }
	void set_memory(INT32 value) { m_memory = value; }

	// readers
	virtual INT32 read_as_switch(input_item_modifier modifier) = 0;
	virtual INT32 read_as_relative(input_item_modifier modifier) = 0;
	virtual INT32 read_as_absolute(input_item_modifier modifier) = 0;

protected:
	// internal state
	input_device &			m_device;				// reference to our owning device
	astring					m_name;					// string name of item
	void *					m_internal;				// internal callback pointer
	input_item_id			m_itemid;				// originally specified item id
	input_item_class		m_itemclass;			// class of the item
	item_get_state_func		m_getstate;				// get state callback
	astring					m_token;				// tokenized name for non-standard items

	// live state
	INT32					m_current;				// current raw value
	INT32					m_memory;				// "memory" value, to remember where we started during polling
};


// ======================> input_device_switch_item

// derived input item representing a switch input
class input_device_switch_item : public input_device_item
{
public:
	// construction/destruction
	input_device_switch_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);

	// readers
	virtual INT32 read_as_switch(input_item_modifier modifier);
	virtual INT32 read_as_relative(input_item_modifier modifier);
	virtual INT32 read_as_absolute(input_item_modifier modifier);

	// steadykey helper
	bool steadykey_changed();
	void steadykey_update_to_current() { m_steadykey = m_current; }

private:
	// internal state
	INT32					m_steadykey;			// the live steadykey state
	INT32					m_oldkey;				// old live state
};


// ======================> input_device_switch_item

// derived input item representing a relative axis input
class input_device_relative_item : public input_device_item
{
public:
	// construction/destruction
	input_device_relative_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);

	// readers
	virtual INT32 read_as_switch(input_item_modifier modifier);
	virtual INT32 read_as_relative(input_item_modifier modifier);
	virtual INT32 read_as_absolute(input_item_modifier modifier);
};


// ======================> input_device_switch_item

// derived input item representing an absolute axis input
class input_device_absolute_item : public input_device_item
{
public:
	// construction/destruction
	input_device_absolute_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);

	// readers
	virtual INT32 read_as_switch(input_item_modifier modifier);
	virtual INT32 read_as_relative(input_item_modifier modifier);
	virtual INT32 read_as_absolute(input_item_modifier modifier);
};


// ======================> code_string_table

// simple class to match codes to strings
struct code_string_table
{
	UINT32 operator[](const char *string) const
	{
		for (const code_string_table *current = this; current->m_code != ~0; current++)
			if (strcmp(current->m_string, string) == 0)
				return current->m_code;
		return ~0;
	}

	const char *operator[](UINT32 code) const
	{
		for (const code_string_table *current = this; current->m_code != ~0; current++)
			if (current->m_code == code)
				return current->m_string;
		return NULL;
	}

	UINT32					m_code;
	const char *			m_string;
};



//**************************************************************************
//  TOKEN/STRING TABLES
//**************************************************************************

// token strings for device classes
static const code_string_table devclass_token_table[] =
{
	{ DEVICE_CLASS_KEYBOARD, "KEYCODE" },
	{ DEVICE_CLASS_MOUSE,    "MOUSECODE" },
	{ DEVICE_CLASS_LIGHTGUN, "GUNCODE" },
	{ DEVICE_CLASS_JOYSTICK, "JOYCODE" },
	{ ~0,                    "UNKCODE" }
};

// friendly strings for device classes
static const code_string_table devclass_string_table[] =
{
	{ DEVICE_CLASS_KEYBOARD, "Kbd" },
	{ DEVICE_CLASS_MOUSE,    "Mouse" },
	{ DEVICE_CLASS_LIGHTGUN, "Gun" },
	{ DEVICE_CLASS_JOYSTICK, "Joy" },
	{ ~0,                    "Unk" }
};

// token strings for item modifiers
static const code_string_table modifier_token_table[] =
{
	{ ITEM_MODIFIER_POS,     "POS" },
	{ ITEM_MODIFIER_NEG,     "NEG" },
	{ ITEM_MODIFIER_LEFT,    "LEFT" },
	{ ITEM_MODIFIER_RIGHT,   "RIGHT" },
	{ ITEM_MODIFIER_UP,      "UP" },
	{ ITEM_MODIFIER_DOWN,    "DOWN" },
	{ ~0,                    "" }
};

// friendly strings for item modifiers
static const code_string_table modifier_string_table[] =
{
	{ ITEM_MODIFIER_POS,     "+" },
	{ ITEM_MODIFIER_NEG,     "-" },
	{ ITEM_MODIFIER_LEFT,    "Left" },
	{ ITEM_MODIFIER_RIGHT,   "Right" },
	{ ITEM_MODIFIER_UP,      "Up" },
	{ ITEM_MODIFIER_DOWN,    "Down" },
	{ ~0,                    "" }
};

// token strings for item classes
static const code_string_table itemclass_token_table[] =
{
	{ ITEM_CLASS_SWITCH,     "SWITCH" },
	{ ITEM_CLASS_ABSOLUTE,   "ABSOLUTE" },
	{ ITEM_CLASS_RELATIVE,   "RELATIVE" },
	{ ~0,                    "" }
};

// token strings for standard item ids
static const code_string_table itemid_token_table[] =
{
	// standard keyboard codes
	{ ITEM_ID_A,             "A" },
	{ ITEM_ID_B,             "B" },
	{ ITEM_ID_C,             "C" },
	{ ITEM_ID_D,             "D" },
	{ ITEM_ID_E,             "E" },
	{ ITEM_ID_F,             "F" },
	{ ITEM_ID_G,             "G" },
	{ ITEM_ID_H,             "H" },
	{ ITEM_ID_I,             "I" },
	{ ITEM_ID_J,             "J" },
	{ ITEM_ID_K,             "K" },
	{ ITEM_ID_L,             "L" },
	{ ITEM_ID_M,             "M" },
	{ ITEM_ID_N,             "N" },
	{ ITEM_ID_O,             "O" },
	{ ITEM_ID_P,             "P" },
	{ ITEM_ID_Q,             "Q" },
	{ ITEM_ID_R,             "R" },
	{ ITEM_ID_S,             "S" },
	{ ITEM_ID_T,             "T" },
	{ ITEM_ID_U,             "U" },
	{ ITEM_ID_V,             "V" },
	{ ITEM_ID_W,             "W" },
	{ ITEM_ID_X,             "X" },
	{ ITEM_ID_Y,             "Y" },
	{ ITEM_ID_Z,             "Z" },
	{ ITEM_ID_0,             "0" },
	{ ITEM_ID_1,             "1" },
	{ ITEM_ID_2,             "2" },
	{ ITEM_ID_3,             "3" },
	{ ITEM_ID_4,             "4" },
	{ ITEM_ID_5,             "5" },
	{ ITEM_ID_6,             "6" },
	{ ITEM_ID_7,             "7" },
	{ ITEM_ID_8,             "8" },
	{ ITEM_ID_9,             "9" },
	{ ITEM_ID_F1,            "F1" },
	{ ITEM_ID_F2,            "F2" },
	{ ITEM_ID_F3,            "F3" },
	{ ITEM_ID_F4,            "F4" },
	{ ITEM_ID_F5,            "F5" },
	{ ITEM_ID_F6,            "F6" },
	{ ITEM_ID_F7,            "F7" },
	{ ITEM_ID_F8,            "F8" },
	{ ITEM_ID_F9,            "F9" },
	{ ITEM_ID_F10,           "F10" },
	{ ITEM_ID_F11,           "F11" },
	{ ITEM_ID_F12,           "F12" },
	{ ITEM_ID_F13,           "F13" },
	{ ITEM_ID_F14,           "F14" },
	{ ITEM_ID_F15,           "F15" },
	{ ITEM_ID_ESC,           "ESC" },
	{ ITEM_ID_TILDE,         "TILDE" },
	{ ITEM_ID_MINUS,         "MINUS" },
	{ ITEM_ID_EQUALS,        "EQUALS" },
	{ ITEM_ID_BACKSPACE,     "BACKSPACE" },
	{ ITEM_ID_TAB,           "TAB" },
	{ ITEM_ID_OPENBRACE,     "OPENBRACE" },
	{ ITEM_ID_CLOSEBRACE,    "CLOSEBRACE" },
	{ ITEM_ID_ENTER,         "ENTER" },
	{ ITEM_ID_COLON,         "COLON" },
	{ ITEM_ID_QUOTE,         "QUOTE" },
	{ ITEM_ID_BACKSLASH,     "BACKSLASH" },
	{ ITEM_ID_BACKSLASH2,    "BACKSLASH2" },
	{ ITEM_ID_COMMA,         "COMMA" },
	{ ITEM_ID_STOP,          "STOP" },
	{ ITEM_ID_SLASH,         "SLASH" },
	{ ITEM_ID_SPACE,         "SPACE" },
	{ ITEM_ID_INSERT,        "INSERT" },
	{ ITEM_ID_DEL,           "DEL" },
	{ ITEM_ID_HOME,          "HOME" },
	{ ITEM_ID_END,           "END" },
	{ ITEM_ID_PGUP,          "PGUP" },
	{ ITEM_ID_PGDN,          "PGDN" },
	{ ITEM_ID_LEFT,          "LEFT" },
	{ ITEM_ID_RIGHT,         "RIGHT" },
	{ ITEM_ID_UP,            "UP" },
	{ ITEM_ID_DOWN,          "DOWN" },
	{ ITEM_ID_0_PAD,         "0PAD" },
	{ ITEM_ID_1_PAD,         "1PAD" },
	{ ITEM_ID_2_PAD,         "2PAD" },
	{ ITEM_ID_3_PAD,         "3PAD" },
	{ ITEM_ID_4_PAD,         "4PAD" },
	{ ITEM_ID_5_PAD,         "5PAD" },
	{ ITEM_ID_6_PAD,         "6PAD" },
	{ ITEM_ID_7_PAD,         "7PAD" },
	{ ITEM_ID_8_PAD,         "8PAD" },
	{ ITEM_ID_9_PAD,         "9PAD" },
	{ ITEM_ID_SLASH_PAD,     "SLASHPAD" },
	{ ITEM_ID_ASTERISK,      "ASTERISK" },
	{ ITEM_ID_MINUS_PAD,     "MINUSPAD" },
	{ ITEM_ID_PLUS_PAD,      "PLUSPAD" },
	{ ITEM_ID_DEL_PAD,       "DELPAD" },
	{ ITEM_ID_ENTER_PAD,     "ENTERPAD" },
	{ ITEM_ID_PRTSCR,        "PRTSCR" },
	{ ITEM_ID_PAUSE,         "PAUSE" },
	{ ITEM_ID_LSHIFT,        "LSHIFT" },
	{ ITEM_ID_RSHIFT,        "RSHIFT" },
	{ ITEM_ID_LCONTROL,      "LCONTROL" },
	{ ITEM_ID_RCONTROL,      "RCONTROL" },
	{ ITEM_ID_LALT,          "LALT" },
	{ ITEM_ID_RALT,          "RALT" },
	{ ITEM_ID_SCRLOCK,       "SCRLOCK" },
	{ ITEM_ID_NUMLOCK,       "NUMLOCK" },
	{ ITEM_ID_CAPSLOCK,      "CAPSLOCK" },
	{ ITEM_ID_LWIN,          "LWIN" },
	{ ITEM_ID_RWIN,          "RWIN" },
	{ ITEM_ID_MENU,          "MENU" },
	{ ITEM_ID_CANCEL,        "CANCEL" },

	// standard mouse/joystick/gun codes
	{ ITEM_ID_XAXIS,         "XAXIS" },
	{ ITEM_ID_YAXIS,         "YAXIS" },
	{ ITEM_ID_ZAXIS,         "ZAXIS" },
	{ ITEM_ID_RXAXIS,        "RXAXIS" },
	{ ITEM_ID_RYAXIS,        "RYAXIS" },
	{ ITEM_ID_RZAXIS,        "RZAXIS" },
	{ ITEM_ID_SLIDER1,       "SLIDER1" },
	{ ITEM_ID_SLIDER2,       "SLIDER2" },
	{ ITEM_ID_BUTTON1,       "BUTTON1" },
	{ ITEM_ID_BUTTON2,       "BUTTON2" },
	{ ITEM_ID_BUTTON3,       "BUTTON3" },
	{ ITEM_ID_BUTTON4,       "BUTTON4" },
	{ ITEM_ID_BUTTON5,       "BUTTON5" },
	{ ITEM_ID_BUTTON6,       "BUTTON6" },
	{ ITEM_ID_BUTTON7,       "BUTTON7" },
	{ ITEM_ID_BUTTON8,       "BUTTON8" },
	{ ITEM_ID_BUTTON9,       "BUTTON9" },
	{ ITEM_ID_BUTTON10,      "BUTTON10" },
	{ ITEM_ID_BUTTON11,      "BUTTON11" },
	{ ITEM_ID_BUTTON12,      "BUTTON12" },
	{ ITEM_ID_BUTTON13,      "BUTTON13" },
	{ ITEM_ID_BUTTON14,      "BUTTON14" },
	{ ITEM_ID_BUTTON15,      "BUTTON15" },
	{ ITEM_ID_BUTTON16,      "BUTTON16" },
	{ ITEM_ID_START,         "START" },
	{ ITEM_ID_SELECT,        "SELECT" },

	// Hats
	{ ITEM_ID_HAT1UP,        "HAT1UP" },
	{ ITEM_ID_HAT1DOWN,      "HAT1DOWN" },
	{ ITEM_ID_HAT1LEFT,      "HAT1LEFT" },
	{ ITEM_ID_HAT1RIGHT,     "HAT1RIGHT" },
	{ ITEM_ID_HAT2UP,        "HAT2UP" },
	{ ITEM_ID_HAT2DOWN,      "HAT2DOWN" },
	{ ITEM_ID_HAT2LEFT,      "HAT2LEFT" },
	{ ITEM_ID_HAT2RIGHT,     "HAT2RIGHT" },
	{ ITEM_ID_HAT3UP,        "HAT3UP" },
	{ ITEM_ID_HAT3DOWN,      "HAT3DOWN" },
	{ ITEM_ID_HAT3LEFT,      "HAT3LEFT" },
	{ ITEM_ID_HAT3RIGHT,     "HAT3RIGHT" },
	{ ITEM_ID_HAT4UP,        "HAT4UP" },
	{ ITEM_ID_HAT4DOWN,      "HAT4DOWN" },
	{ ITEM_ID_HAT4LEFT,      "HAT4LEFT" },
	{ ITEM_ID_HAT4RIGHT,     "HAT4RIGHT" },

	// Additional IDs
	{ ITEM_ID_ADD_SWITCH1,   "ADDSW1" },
	{ ITEM_ID_ADD_SWITCH2,   "ADDSW2" },
	{ ITEM_ID_ADD_SWITCH3,   "ADDSW3" },
	{ ITEM_ID_ADD_SWITCH4,	 "ADDSW4" },
	{ ITEM_ID_ADD_SWITCH5,	 "ADDSW5" },
	{ ITEM_ID_ADD_SWITCH6,   "ADDSW6" },
	{ ITEM_ID_ADD_SWITCH7,   "ADDSW7" },
	{ ITEM_ID_ADD_SWITCH8,   "ADDSW8" },
	{ ITEM_ID_ADD_SWITCH9,   "ADDSW9" },
	{ ITEM_ID_ADD_SWITCH10,  "ADDSW10" },
	{ ITEM_ID_ADD_SWITCH11,  "ADDSW11" },
	{ ITEM_ID_ADD_SWITCH12,  "ADDSW12" },
	{ ITEM_ID_ADD_SWITCH13,  "ADDSW13" },
	{ ITEM_ID_ADD_SWITCH14,  "ADDSW14" },
	{ ITEM_ID_ADD_SWITCH15,  "ADDSW15" },
	{ ITEM_ID_ADD_SWITCH16,  "ADDSW16" },
	{ ITEM_ID_ADD_ABSOLUTE1, "ADDAXIS1" },
	{ ITEM_ID_ADD_ABSOLUTE2, "ADDAXIS2" },
	{ ITEM_ID_ADD_ABSOLUTE3, "ADDAXIS3" },
	{ ITEM_ID_ADD_ABSOLUTE4, "ADDAXIS4" },
	{ ITEM_ID_ADD_ABSOLUTE5, "ADDAXIS5" },
	{ ITEM_ID_ADD_ABSOLUTE6, "ADDAXIS6" },
	{ ITEM_ID_ADD_ABSOLUTE7, "ADDAXIS7" },
	{ ITEM_ID_ADD_ABSOLUTE8, "ADDAXIS8" },
	{ ITEM_ID_ADD_ABSOLUTE9, "ADDAXIS9" },
	{ ITEM_ID_ADD_ABSOLUTE10,"ADDAXIS10" },
	{ ITEM_ID_ADD_ABSOLUTE11,"ADDAXIS11" },
	{ ITEM_ID_ADD_ABSOLUTE12,"ADDAXIS12" },
	{ ITEM_ID_ADD_ABSOLUTE13,"ADDAXIS13" },
	{ ITEM_ID_ADD_ABSOLUTE14,"ADDAXIS14" },
	{ ITEM_ID_ADD_ABSOLUTE15,"ADDAXIS15" },
	{ ITEM_ID_ADD_ABSOLUTE16,"ADDAXIS16" },
	{ ITEM_ID_ADD_RELATIVE1, "ADDREL1" },
	{ ITEM_ID_ADD_RELATIVE2, "ADDREL2" },
	{ ITEM_ID_ADD_RELATIVE3, "ADDREL3" },
	{ ITEM_ID_ADD_RELATIVE4, "ADDREL4" },
	{ ITEM_ID_ADD_RELATIVE5, "ADDREL5" },
	{ ITEM_ID_ADD_RELATIVE6, "ADDREL6" },
	{ ITEM_ID_ADD_RELATIVE7, "ADDREL7" },
	{ ITEM_ID_ADD_RELATIVE8, "ADDREL8" },
	{ ITEM_ID_ADD_RELATIVE9, "ADDREL9" },
	{ ITEM_ID_ADD_RELATIVE10,"ADDREL10" },
	{ ITEM_ID_ADD_RELATIVE11,"ADDREL11" },
	{ ITEM_ID_ADD_RELATIVE12,"ADDREL12" },
	{ ITEM_ID_ADD_RELATIVE13,"ADDREL13" },
	{ ITEM_ID_ADD_RELATIVE14,"ADDREL14" },
	{ ITEM_ID_ADD_RELATIVE15,"ADDREL15" },
	{ ITEM_ID_ADD_RELATIVE16,"ADDREL16" },

	{ ~0,                    NULL }
};



//**************************************************************************
//  GLOBAL VARIABLES
//**************************************************************************

// standard joystick mappings
const char			joystick_map_8way[] = "7778...4445";
const char			joystick_map_4way_sticky[] = "s8.4s8.44s8.4445";
const char			joystick_map_4way_diagonal[] = "4444s8888..444458888.444555888.ss5.222555666.222256666.2222s6666.2222s6666";



//**************************************************************************
//  JOYSTICK MAP
//**************************************************************************

//-------------------------------------------------
//  joystick_map - constructor
//-------------------------------------------------

joystick_map::joystick_map()
	: m_lastmap(JOYSTICK_MAP_NEUTRAL)
{
	// parse the standard 8-way map as default
	parse(joystick_map_8way);
}


//-------------------------------------------------
//  parse - parse a string into a joystick map
//-------------------------------------------------

bool joystick_map::parse(const char *mapstring)
{
	// save a copy of the original string
	m_origstring = mapstring;

	// iterate over rows
	for (int rownum = 0; rownum < 9; rownum++)
	{
		// if we're done, copy from another row
		if (*mapstring == 0 || *mapstring == '.')
		{
			bool symmetric = (rownum >= 5 && *mapstring == 0);
			const UINT8 *srcrow = &m_map[symmetric ? (8 - rownum) : (rownum - 1)][0];

			// if this is row 0, we don't have a source row -- invalid
			if (rownum == 0)
				return false;

			// copy from the srcrow, applying up/down symmetry if in the bottom half
			for (int colnum = 0; colnum < 9; colnum++)
			{
				UINT8 val = srcrow[colnum];
				if (symmetric)
					val = (val & (JOYSTICK_MAP_LEFT | JOYSTICK_MAP_RIGHT)) | ((val & JOYSTICK_MAP_UP) << 1) | ((val & JOYSTICK_MAP_DOWN) >> 1);
				m_map[rownum][colnum] = val;
			}
		}

		// otherwise, parse this column
		else
		{
			for (int colnum = 0; colnum < 9; colnum++)
			{
				// if we're at the end of row, copy previous to the middle, then apply left/right symmetry
				if (colnum > 0 && (*mapstring == 0 || *mapstring == '.'))
				{
					bool symmetric = (colnum >= 5);
					UINT8 val = m_map[rownum][symmetric ? (8 - colnum) : (colnum - 1)];
					if (symmetric)
						val = (val & (JOYSTICK_MAP_UP | JOYSTICK_MAP_DOWN)) | ((val & JOYSTICK_MAP_LEFT) << 1) | ((val & JOYSTICK_MAP_RIGHT) >> 1);
					m_map[rownum][colnum] = val;
				}

				// otherwise, convert the character to its value
				else
				{
					static const UINT8 charmap[] =
					{
						JOYSTICK_MAP_UP | JOYSTICK_MAP_LEFT,
						JOYSTICK_MAP_UP,
						JOYSTICK_MAP_UP | JOYSTICK_MAP_RIGHT,
						JOYSTICK_MAP_LEFT,
						JOYSTICK_MAP_NEUTRAL,
						JOYSTICK_MAP_RIGHT,
						JOYSTICK_MAP_DOWN | JOYSTICK_MAP_LEFT,
						JOYSTICK_MAP_DOWN,
						JOYSTICK_MAP_DOWN | JOYSTICK_MAP_RIGHT,
						JOYSTICK_MAP_STICKY
					};
					static const char validchars[] = "789456123s";
					const char *ptr = strchr(validchars, *mapstring++);

					// invalid characters exit immediately
					if (ptr == NULL)
						return false;
					m_map[rownum][colnum] = charmap[ptr - validchars];
				}
			}
		}

		// if we ended with a period, advance to the next row
		if (*mapstring == '.')
			mapstring++;
	}
	return true;
}


//-------------------------------------------------
//  to_string - output the map as a string for
//  friendly display
//-------------------------------------------------

const char *joystick_map::to_string(astring &string) const
{
	string.printf("%s\n", m_origstring.cstr());
	for (int rownum = 0; rownum < 9; rownum++)
	{
		string.catprintf("  ");
		for (int colnum = 0; colnum < 9; colnum++)
			switch (m_map[rownum][colnum])
			{
				case JOYSTICK_MAP_UP | JOYSTICK_MAP_LEFT:	string.catprintf("7");	break;
				case JOYSTICK_MAP_UP:						string.catprintf("8");	break;
				case JOYSTICK_MAP_UP | JOYSTICK_MAP_RIGHT:	string.catprintf("9");	break;
				case JOYSTICK_MAP_LEFT:						string.catprintf("4");	break;
				case JOYSTICK_MAP_NEUTRAL:					string.catprintf("5");	break;
				case JOYSTICK_MAP_RIGHT:					string.catprintf("6");	break;
				case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_LEFT:	string.catprintf("1");	break;
				case JOYSTICK_MAP_DOWN:						string.catprintf("2");	break;
				case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_RIGHT:string.catprintf("3");	break;
				case JOYSTICK_MAP_STICKY:					string.catprintf("s");	break;
				default:									string.catprintf("?");	break;
			}

		string.catprintf("\n");
	}
	return string;
}


//-------------------------------------------------
//  update - update the state of the joystick
//  map based on the given X/Y axis values
//-------------------------------------------------

UINT8 joystick_map::update(INT32 xaxisval, INT32 yaxisval)
{
	// now map the X and Y axes to a 9x9 grid using the raw values
	xaxisval = ((xaxisval - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1);
	yaxisval = ((yaxisval - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1);
	UINT8 mapval = m_map[yaxisval][xaxisval];

	// handle stickiness
	if (mapval == JOYSTICK_MAP_STICKY)
		mapval = m_lastmap;
	else
		m_lastmap = mapval;

	// return based on whether the appropriate bit is set
	return mapval;
}



//**************************************************************************
//  INPUT CODE
//**************************************************************************

//-------------------------------------------------
//  input_code - construct an input code from a
//  device/item pair
//-------------------------------------------------

input_code::input_code(input_device &device, input_item_id itemid)
{
	assert(itemid < ITEM_ID_ABSOLUTE_MAXIMUM);
	input_device_item *item = device.item(itemid);
	assert(item != NULL);
	m_internal = ((device.devclass() & 0xf) << 28) | ((device.devindex() & 0xff) << 20) | ((item->itemclass() & 0xf) << 16) | (ITEM_MODIFIER_NONE << 12) | (item->itemid() & 0xfff);
}



//**************************************************************************
//  INPUT SEQ
//**************************************************************************

//-------------------------------------------------
//  operator+= - append a code to the end of an
//  input sequence
//-------------------------------------------------

input_seq &input_seq::operator+=(input_code code)
{
	// if not enough room, return FALSE
	int curlength = length();
	if (curlength < ARRAY_LENGTH(m_code) - 1)
	{
		m_code[curlength++] = code;
		m_code[curlength] = end_code;
	}
	return *this;
}


//-------------------------------------------------
//  operator|= - append a code to a sequence; if
//  the sequence is non-empty, insert an OR
//  before the new code
//-------------------------------------------------

input_seq &input_seq::operator|=(input_code code)
{
	// overwrite end/default with the new code
	if (m_code[0] == end_code || m_code[0] == default_code)
		m_code[0] = code;

	// otherwise, append an OR token and then the new code
	else
	{
		*this += or_code;
		*this += code;
	}
	return *this;
}


//-------------------------------------------------
//  length - return the length of the sequence
//-------------------------------------------------

int input_seq::length() const
{
	// find the end token; error if none found
	for (int seqnum = 0; seqnum < ARRAY_LENGTH(m_code); seqnum++)
		if (m_code[seqnum] == end_code)
			return seqnum;
	return ARRAY_LENGTH(m_code);
}


//-------------------------------------------------
//  is_valid - return true if a given sequence is
//  valid
//-------------------------------------------------

bool input_seq::is_valid() const
{
	// "default" can only be of length 1
	if (m_code[0] == default_code)
		return (length() == 1);

	// scan the sequence for valid codes
	input_item_class lastclass = ITEM_CLASS_INVALID;
	input_code lastcode = INPUT_CODE_INVALID;
	int positive_code_count = 0;
	for (int seqnum = 0; seqnum < ARRAY_LENGTH(m_code); seqnum++)
	{
		// invalid codes are never permitted
		input_code code = m_code[seqnum];
		if (code == INPUT_CODE_INVALID)
			return false;

		// if we hit an OR or the end, validate the previous chunk
		if (code == or_code || code == end_code)
		{
			// must be at least one positive code
			if (positive_code_count == 0)
				return false;

			// last code must not have been an internal code
			if (lastcode.internal())
				return false;

			// if this is the end, we're ok
			if (code == end_code)
				return true;

			// reset the state for the next chunk
			positive_code_count = 0;
			lastclass = ITEM_CLASS_INVALID;
		}

		// if we hit a NOT, make sure we don't have a double
		else if (code == not_code)
		{
			if (lastcode == not_code)
				return false;
		}

		// anything else
		else
		{
			// count positive codes
			if (lastcode != not_code)
				positive_code_count++;

			// non-switch items can't have a NOT
			input_item_class itemclass = code.item_class();
			if (itemclass != ITEM_CLASS_SWITCH && lastcode == not_code)
				return FALSE;

			// absolute/relative items must all be the same class
			if ((lastclass == ITEM_CLASS_ABSOLUTE && itemclass != ITEM_CLASS_ABSOLUTE) ||
				(lastclass == ITEM_CLASS_RELATIVE && itemclass != ITEM_CLASS_RELATIVE))
				return false;
		}

		// remember the last code
		lastcode = code;
	}

	// if we got here, we were missing an END token; fail
	return false;
}


//-------------------------------------------------
//  set - directly set up to the first 7 codes
//-------------------------------------------------

void input_seq::set(input_code code0, input_code code1, input_code code2, input_code code3, input_code code4, input_code code5, input_code code6)
{
	m_code[0] = code0;
	m_code[1] = code1;
	m_code[2] = code2;
	m_code[3] = code3;
	m_code[4] = code4;
	m_code[5] = code5;
	m_code[6] = code6;
	for (int codenum = 7; codenum < ARRAY_LENGTH(m_code); codenum++)
		m_code[codenum] = end_code;
}


//-------------------------------------------------
//  backspace - "backspace" over the last entry in
//  a sequence
//-------------------------------------------------

void input_seq::backspace()
{
	// if we have at least one entry, remove it
	int curlength = length();
	if (curlength > 0)
		m_code[curlength - 1] = end_code;
}


//-------------------------------------------------
//  replace - replace all instances of oldcode
//  with newcode in a sequence
//-------------------------------------------------

void input_seq::replace(input_code oldcode, input_code newcode)
{
	for (int codenum = 0; codenum < ARRAY_LENGTH(m_code); codenum++)
		if (m_code[codenum] == oldcode)
			m_code[codenum] = newcode;
}



//**************************************************************************
//  INPUT DEVICE
//**************************************************************************

//-------------------------------------------------
//  input_device - constructor
//-------------------------------------------------

input_device::input_device(input_class &_class, int devindex, const char *name, void *internal)
	: m_class(_class),
	  m_name(name),
	  m_devindex(devindex),
	  m_maxitem(input_item_id(0)),
	  m_internal(internal),
	  m_joystick_deadzone((INT32)(_class.manager().machine().options().joystick_deadzone() * INPUT_ABSOLUTE_MAX)),
	  m_joystick_saturation((INT32)(_class.manager().machine().options().joystick_saturation() * INPUT_ABSOLUTE_MAX)),
	  m_steadykey_enabled(_class.manager().machine().options().steadykey()),
	  m_lightgun_reload_button(_class.manager().machine().options().offscreen_reload())
{
	// reset the items
	memset(m_item, 0, sizeof(m_item));

	// additional work for joysticks
	if (devclass() == DEVICE_CLASS_JOYSTICK)
	{
		// get the default joystick map
		const char *mapstring = machine().options().joystick_map();
		if (mapstring[0] == 0 || strcmp(mapstring, "auto") == 0)
			mapstring = joystick_map_8way;

		// parse it
		astring tempstr;
		if (!m_joymap.parse(mapstring))
		{
			mame_printf_error("Invalid joystick map: %s\n", mapstring);
			m_joymap.parse(joystick_map_8way);
		}
		else if (mapstring != joystick_map_8way)
			mame_printf_verbose("Input: Default joystick map = %s", m_joymap.to_string(tempstr));
	}
}


//-------------------------------------------------
//  add_item - add a new item to an input device
//-------------------------------------------------

input_item_id input_device::add_item(const char *name, input_item_id itemid, item_get_state_func getstate, void *internal)
{
	assert_always(machine().phase() == MACHINE_PHASE_INIT, "Can only call input_device::add_item at init time!");
	assert(name != NULL);
	assert(itemid > ITEM_ID_INVALID && itemid < ITEM_ID_MAXIMUM);
	assert(getstate != NULL);

	// if we have a generic ID, pick a new internal one
	input_item_id originalid = itemid;
	if (itemid >= ITEM_ID_OTHER_SWITCH && itemid <= ITEM_ID_OTHER_AXIS_RELATIVE)
		for (itemid = (input_item_id)(ITEM_ID_MAXIMUM + 1); itemid <= ITEM_ID_ABSOLUTE_MAXIMUM; itemid++)
			if (m_item[itemid] == NULL)
				break;
	assert(itemid <= ITEM_ID_ABSOLUTE_MAXIMUM);

	// make sure we don't have any overlap
	assert(m_item[itemid] == NULL);

	// determine the class and create the appropriate item class
	input_device_item *item = NULL;
	switch (m_class.standard_item_class(originalid))
	{
		case ITEM_CLASS_SWITCH:
			item = auto_alloc(machine(), input_device_switch_item(*this, name, internal, itemid, getstate));
			break;

		case ITEM_CLASS_RELATIVE:
			item = auto_alloc(machine(), input_device_relative_item(*this, name, internal, itemid, getstate));
			break;

		case ITEM_CLASS_ABSOLUTE:
			item = auto_alloc(machine(), input_device_absolute_item(*this, name, internal, itemid, getstate));
			break;

		default:
			assert(false);
	}

	// assign the new slot and update the maximum
	m_item[itemid] = item;
	m_maxitem = MAX(m_maxitem, itemid);
	return itemid;
}


//-------------------------------------------------
//  apply_deadzone_and_saturation - apply global
//  deadzone and saturation parameters to an
//  absolute value
//-------------------------------------------------

INT32 input_device::apply_deadzone_and_saturation(INT32 result) const
{
	// ignore for non-joysticks
	if (devclass() != DEVICE_CLASS_JOYSTICK)
		return result;

	// properties are symmetric
	bool negative = false;
	if (result < 0)
	{
		negative = true;
		result = -result;
	}

	// if in the deadzone, return 0
	if (result < m_joystick_deadzone)
		result = 0;

	// if saturated, return the max
	else if (result > m_joystick_saturation)
		result = INPUT_ABSOLUTE_MAX;

	// otherwise, scale
	else
		result = (INT64)(result - m_joystick_deadzone) * (INT64)INPUT_ABSOLUTE_MAX / (INT64)(m_joystick_saturation - m_joystick_deadzone);

	// re-apply sign and return
	return negative ? -result : result;
}


//-------------------------------------------------
//  apply_steadykey - apply steadykey option if
//  enabled
//-------------------------------------------------

void input_device::apply_steadykey() const
{
	// ignore if not enabled
	if (!m_steadykey_enabled)
		return;

	// update the state of all the keys and see if any changed state
	bool anything_changed = false;
	for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= m_maxitem; itemid++)
	{
		input_device_item *item = m_item[itemid];
		if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
			if (downcast<input_device_switch_item *>(item)->steadykey_changed())
				anything_changed = true;
	}

	// if the keyboard state is stable, flush the current state
	if (!anything_changed)
		for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= m_maxitem; itemid++)
		{
			input_device_item *item = m_item[itemid];
			if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
				downcast<input_device_switch_item *>(item)->steadykey_update_to_current();
		}
}



//**************************************************************************
//  INPUT CLASS
//**************************************************************************

//-------------------------------------------------
//  input_class - constructor
//-------------------------------------------------

input_class::input_class(input_manager &manager, input_device_class devclass, bool enabled, bool multi)
	: m_manager(manager),
	  m_devclass(devclass),
	  m_maxindex(0),
	  m_enabled(enabled),
	  m_multi(multi)
{
	memset(m_device, 0, sizeof(m_device));

	// request a per-frame callback for the keyboard class
	if (devclass == DEVICE_CLASS_KEYBOARD)
		machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(FUNC(input_class::frame_callback), this));
}


//-------------------------------------------------
//  add_device - add a new input device
//-------------------------------------------------

input_device *input_class::add_device(const char *name, void *internal)
{
	// find the next empty index
	int devindex;
	for (devindex = 0; devindex < DEVICE_INDEX_MAXIMUM; devindex++)
		if (m_device[devindex] == NULL)
			break;

	// call through
	return add_device(devindex, name, internal);
}

input_device *input_class::add_device(int devindex, const char *name, void *internal)
{
	assert_always(machine().phase() == MACHINE_PHASE_INIT, "Can only call input_class::add_device at init time!");
	assert(name != NULL);
	assert(devindex >= 0 && devindex < DEVICE_INDEX_MAXIMUM);
	assert(m_device[devindex] == NULL);

	// allocate a new device
	input_device *device = m_device[devindex] = auto_alloc(machine(), input_device(*this, devindex, name, internal));

	// update the maximum index found
	m_maxindex = MAX(m_maxindex, devindex);

	mame_printf_verbose("Input: Adding %s #%d: %s\n", (*devclass_string_table)[m_devclass], devindex, name);
	return device;
}


//-------------------------------------------------
//  standard_item_class - return the class of a
//  standard item
//-------------------------------------------------

input_item_class input_class::standard_item_class(input_item_id itemid)
{
	// most everything standard is a switch, apart from the axes
	if (itemid == ITEM_ID_OTHER_SWITCH || itemid < ITEM_ID_XAXIS || (itemid > ITEM_ID_SLIDER2 && itemid < ITEM_ID_ADD_ABSOLUTE1))
		return ITEM_CLASS_SWITCH;

	// standard mouse axes are relative
	else if (m_devclass == DEVICE_CLASS_MOUSE || itemid == ITEM_ID_OTHER_AXIS_RELATIVE || (itemid >= ITEM_ID_ADD_RELATIVE1 && itemid <= ITEM_ID_ADD_RELATIVE16))
		return ITEM_CLASS_RELATIVE;

	// all other standard axes are absolute
	else
		return ITEM_CLASS_ABSOLUTE;
}


//-------------------------------------------------
//  frame_callback - per-frame callback for various
//  bookkeeping
//-------------------------------------------------

void input_class::frame_callback()
{
	// iterate over all devices in our class
	for (int devnum = 0; devnum <= m_maxindex; devnum++)
		if (m_device[devnum] != NULL)
			m_device[devnum]->apply_steadykey();
}



//**************************************************************************
//  INPUT MANAGER
//**************************************************************************

//-------------------------------------------------
//  input_manager - constructor
//-------------------------------------------------

input_manager::input_manager(running_machine &machine)
	: m_machine(machine),
	  m_keyboard_class(*this, DEVICE_CLASS_KEYBOARD, true, machine.options().multi_keyboard()),
	  m_mouse_class(*this, DEVICE_CLASS_MOUSE, machine.options().mouse(), machine.options().multi_mouse()),
	  m_joystick_class(*this, DEVICE_CLASS_JOYSTICK, machine.options().joystick(), true),
	  m_lightgun_class(*this, DEVICE_CLASS_LIGHTGUN, machine.options().lightgun(), true),
	  m_poll_seq_last_ticks(0),
	  m_poll_seq_class(ITEM_CLASS_SWITCH)
{
	// reset code memory
	reset_memory();

	// create pointers for the classes
	memset(m_class, 0, sizeof(m_class));
	m_class[DEVICE_CLASS_KEYBOARD] = &m_keyboard_class;
	m_class[DEVICE_CLASS_MOUSE] = &m_mouse_class;
	m_class[DEVICE_CLASS_JOYSTICK] = &m_joystick_class;
	m_class[DEVICE_CLASS_LIGHTGUN] = &m_lightgun_class;
}


//-------------------------------------------------
//  code_value - return the value of a given
//  input code
//-------------------------------------------------

INT32 input_manager::code_value(input_code code)
{
	g_profiler.start(PROFILER_INPUT);
	INT32 result = 0;

	// dummy loop to allow clean early exits
	do
	{
		// return 0 for any invalid devices
		input_device *device = device_from_code(code);
		if (device == NULL)
			break;

		// also return 0 if the device class is disabled
		input_class &devclass = *m_class[code.device_class()];
		if (!devclass.enabled())
			break;

		// if this is not a multi device, only return data for item 0 and iterate over all
		int startindex = code.device_index();
		int stopindex = startindex;
		if (!devclass.multi())
		{
			if (startindex != 0)
				break;
			stopindex = devclass.maxindex();
		}

		// iterate over all device indices
		input_item_class targetclass = code.item_class();
		for (int curindex = startindex; curindex <= stopindex; curindex++)
		{
			// lookup the item for the appropriate index
			code.set_device_index(curindex);
			input_device_item *item = item_from_code(code);
			if (item == NULL)
				continue;

			// process items according to their native type
			switch (targetclass)
			{
				case ITEM_CLASS_ABSOLUTE:
					if (result == 0)
						result = item->read_as_absolute(code.item_modifier());
					break;

				case ITEM_CLASS_RELATIVE:
					result += item->read_as_relative(code.item_modifier());
					break;

				case ITEM_CLASS_SWITCH:
					result |= item->read_as_switch(code.item_modifier());
					break;

				default:
					break;
			}
		}
	} while (0);

	// stop the profiler before exiting
	g_profiler.stop();
	return result;
}


//-------------------------------------------------
//  code_pressed_once - return non-zero if a given
//  input code has transitioned from off to on
//  since the last call
//-------------------------------------------------

bool input_manager::code_pressed_once(input_code code)
{
	// look for the code in the memory
	bool curvalue = code_pressed(code);
	int empty = -1;
	for (int memnum = 0; memnum < ARRAY_LENGTH(m_switch_memory); memnum++)
	{
		// were we previous pressed on the last time through here?
		if (m_switch_memory[memnum] == code)
		{
			// if no longer pressed, clear entry
			if (curvalue == false)
				m_switch_memory[memnum] = INPUT_CODE_INVALID;

			// always return false
			return false;
		}

		// remember the first empty entry
		if (empty == -1 && m_switch_memory[memnum] == INPUT_CODE_INVALID)
			empty = memnum;
	}

	// if we get here, we were not previously pressed; if still not pressed, return 0
	if (curvalue == false)
		return false;

	// otherwise, add ourself to the memory and return 1
	assert(empty != -1);
	if (empty != -1)
		m_switch_memory[empty] = code;
	return true;
}


//-------------------------------------------------
//  reset_polling - reset memories in preparation
//  for polling
//-------------------------------------------------

void input_manager::reset_polling()
{
	// reset switch memory
	reset_memory();

	// iterate over device classes and devices
	for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
		for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
		{
			// fetch the device; ignore if NULL
			input_device *device = m_class[devclass]->device(devnum);
			if (device == NULL)
				continue;

			// iterate over items within each device
			for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
			{
				// for any non-switch items, set memory equal to the current value
				input_device_item *item = device->item(itemid);
				if (item != NULL && item->itemclass() != ITEM_CLASS_SWITCH)
					item->set_memory(code_value(input_code(*device, itemid)));
			}
		}
}


//-------------------------------------------------
//  poll_switches - poll for any input
//-------------------------------------------------

input_code input_manager::poll_switches()
{
	// iterate over device classes and devices
	for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
		for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
		{
			// fetch the device; ignore if NULL
			input_device *device = m_class[devclass]->device(devnum);
			if (device == NULL)
				continue;

			// iterate over items within each device
			for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
			{
				input_device_item *item = device->item(itemid);
				if (item != NULL)
				{
					input_code code(*device, itemid);

					// if the item is natively a switch, poll it
					if (item->itemclass() == ITEM_CLASS_SWITCH)
					{
						if (code_pressed_once(code))
							return code;
						else
						    continue;
					}

					// skip if there is not enough axis movement
					if (!code_check_axis(*item, code))
						continue;

					// otherwise, poll axes digitally
					code.set_item_class(ITEM_CLASS_SWITCH);

					// if this is a joystick X axis, check with left/right modifiers
					if (devclass == DEVICE_CLASS_JOYSTICK && code.item_id() == ITEM_ID_XAXIS)
					{
						code.set_item_modifier(ITEM_MODIFIER_LEFT);
						if (code_pressed_once(code))
							return code;
						code.set_item_modifier(ITEM_MODIFIER_RIGHT);
						if (code_pressed_once(code))
							return code;
					}

					// if this is a joystick Y axis, check with up/down modifiers
					else if (devclass == DEVICE_CLASS_JOYSTICK && code.item_id() == ITEM_ID_YAXIS)
					{
						code.set_item_modifier(ITEM_MODIFIER_UP);
						if (code_pressed_once(code))
							return code;
						code.set_item_modifier(ITEM_MODIFIER_DOWN);
						if (code_pressed_once(code))
							return code;
					}

					// any other axis, check with pos/neg modifiers
					else
					{
						code.set_item_modifier(ITEM_MODIFIER_POS);
						if (code_pressed_once(code))
							return code;
						code.set_item_modifier(ITEM_MODIFIER_NEG);
						if (code_pressed_once(code))
							return code;
					}
				}
			}
		}

	// if nothing, return an invalid code
	return INPUT_CODE_INVALID;
}


//-------------------------------------------------
//  poll_keyboard_switches - poll for any
//  keyboard-specific input
//-------------------------------------------------

input_code input_manager::poll_keyboard_switches()
{
	// iterate over devices within each class
	for (int devnum = 0; devnum < m_keyboard_class.maxindex(); devnum++)
	{
		// fetch the device; ignore if NULL
		input_device *device = m_keyboard_class.device(devnum);
		if (device == NULL)
			continue;

		// iterate over items within each device
		for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
		{
			input_device_item *item = device->item(itemid);
			if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
			{
				input_code code(*device, itemid);
				if (code_pressed_once(code))
					return code;
			}
		}
	}

	// if nothing, return an invalid code
	return INPUT_CODE_INVALID;
}


//-------------------------------------------------
//  code_check_axis - see if axis has moved far
//  enough to trigger a read when polling
//-------------------------------------------------

bool input_manager::code_check_axis(input_device_item &item, input_code code)
{
	// if we've already reported this one, don't bother
	if (item.memory() == INVALID_AXIS_VALUE)
		return false;

    // ignore min/max for lightguns
    // so the selection will not be affected by a gun going out of range
	INT32 curval = code_value(code);
    if (code.device_class() == DEVICE_CLASS_LIGHTGUN &&
		(code.item_id() == ITEM_ID_XAXIS || code.item_id() == ITEM_ID_YAXIS) &&
		(curval == INPUT_ABSOLUTE_MAX || curval == INPUT_ABSOLUTE_MIN))
        return false;

	// compute the diff against memory
	INT32 diff = curval - item.memory();
	if (diff < 0)
		diff = -diff;

	// for absolute axes, look for 25% of maximum
	if (item.itemclass() == ITEM_CLASS_ABSOLUTE && diff > (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN) / 4)
	{
		item.set_memory(INVALID_AXIS_VALUE);
		return true;
	}

	// for relative axes, look for ~20 pixels movement
	if (item.itemclass() == ITEM_CLASS_RELATIVE && diff > 20 * INPUT_RELATIVE_PER_PIXEL)
	{
		item.set_memory(INVALID_AXIS_VALUE);
		return true;
	}
    return false;
}


//-------------------------------------------------
//  poll_axes - poll for any input
//-------------------------------------------------

input_code input_manager::poll_axes()
{
	// iterate over device classes and devices
	for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
		for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
		{
			// fetch the device; ignore if NULL
			input_device *device = m_class[devclass]->device(devnum);
			if (device == NULL)
				continue;

			// iterate over items within each device
			for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
			{
				input_device_item *item = device->item(itemid);
				if (item != NULL && item->itemclass() != ITEM_CLASS_SWITCH)
				{
					input_code code(*device, itemid);
					if (code_check_axis(*item, code))
						return code;
				}
			}
		}

	// if nothing, return an invalid code
	return INPUT_CODE_INVALID;
}


//-------------------------------------------------
//  device_from_code - given an input_code return
//  a pointer to the associated device
//-------------------------------------------------

input_device *input_manager::device_from_code(input_code code) const
{
	// if the class is valid, return the appropriate device pointer
	input_device_class devclass = code.device_class();
	if (devclass >= DEVICE_CLASS_FIRST_VALID && devclass <= DEVICE_CLASS_LAST_VALID)
		return m_class[devclass]->device(code.device_index());

	// otherwise, return NULL
	return NULL;
}


//-------------------------------------------------
//  item_from_code - given an input_code return
//  a pointer to the appropriate input_device_item
//-------------------------------------------------

input_device_item *input_manager::item_from_code(input_code code) const
{
	// first get the device; if none, then we don't have an item
	input_device *device = device_from_code(code);
	if (device == NULL)
		return NULL;

	// then return the device's item
	return device->item(code.item_id());
}


//-------------------------------------------------
//  reset_memory - reset the array of memory for
//  pressed switches
//-------------------------------------------------

void input_manager::reset_memory()
{
	// reset all entries in switch memory to invalid
	for (int memnum = 0; memnum < ARRAY_LENGTH(m_switch_memory); memnum++)
		m_switch_memory[memnum] = INPUT_CODE_INVALID;
}


//-------------------------------------------------
//  code_from_itemid - translates an input_item_id
//  to an input_code
//-------------------------------------------------

input_code input_manager::code_from_itemid(input_item_id itemid) const
{
	// iterate over device classes and devices
	for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
		for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
		{
			input_device *device = m_class[devclass]->device(devnum);
			if (device != NULL && device->item(itemid) != NULL)
				return input_code(*device, itemid);
		}

	return INPUT_CODE_INVALID;
}


//-------------------------------------------------
//  code_name - convert an input code into a
//  friendly name
//-------------------------------------------------

const char *input_manager::code_name(astring &string, input_code code) const
{
	string.reset();

	// if nothing there, return an empty string
	input_device_item *item = item_from_code(code);
	if (item == NULL)
		return string;

	// determine the devclass part
	const char *devclass = (*devclass_string_table)[code.device_class()];

	// determine the devindex part
	astring devindex;
	devindex.printf("%d", code.device_index() + 1);

	// if we're unifying all devices, don't display a number
	if (!m_class[code.device_class()]->multi())
		devindex.reset();

	// keyboard 0 doesn't show a class or index if it is the only one
	input_device_class device_class = item->device().devclass();
	if (device_class == DEVICE_CLASS_KEYBOARD && m_keyboard_class.maxindex() == 0)
	{
		devclass = "";
		devindex.reset();
	}

	// devcode part comes from the item name
	const char *devcode = item->name();

	// determine the modifier part
	const char *modifier = (*modifier_string_table)[code.item_modifier()];

	// devcode is redundant with joystick switch left/right/up/down
	if (device_class == DEVICE_CLASS_JOYSTICK && code.item_class() == ITEM_CLASS_SWITCH)
		if (code.item_modifier() >= ITEM_MODIFIER_LEFT && code.item_modifier() <= ITEM_MODIFIER_DOWN)
			devcode = "";

	// concatenate the strings
	string.cpy(devclass);
	if (devindex)
		string.cat(" ").cat(devindex);
	if (devcode[0] != 0)
		string.cat(" ").cat(devcode);
	if (modifier != NULL)
		string.cat(" ").cat(modifier);

	// delete any leading spaces
	return string.trimspace();
}


//-------------------------------------------------
//  code_to_token - create a token for a given code
//-------------------------------------------------

const char *input_manager::code_to_token(astring &string, input_code code) const
{
	// determine the devclass part
	const char *devclass = (*devclass_token_table)[code.device_class()];

	// determine the devindex part; keyboard 0 doesn't show an index
	astring devindex;
	devindex.printf("%d", code.device_index() + 1);
	if (code.device_class() == DEVICE_CLASS_KEYBOARD && code.device_index() == 0)
		devindex.reset();

	// determine the itemid part; look up in the table if we don't have a token
	input_device_item *item = item_from_code(code);
	const char *devcode = (item != NULL) ? item->token() : "UNKNOWN";

	// determine the modifier part
	const char *modifier = (*modifier_token_table)[code.item_modifier()];

	// determine the itemclass part; if we match the native class, we don't include this
	const char *itemclass = "";
	if (item == NULL || item->itemclass() != code.item_class())
		itemclass = (*itemclass_token_table)[code.item_class()];

	// concatenate the strings
	string.cpy(devclass);
	if (devindex)
		string.cat("_").cat(devindex);
	if (devcode[0] != 0)
		string.cat("_").cat(devcode);
	if (modifier != NULL)
		string.cat("_").cat(modifier);
	if (itemclass[0] != 0)
		string.cat("_").cat(itemclass);
	return string;
}


//-------------------------------------------------
//  code_from_token - extract an input code from a
//  token
//-------------------------------------------------

input_code input_manager::code_from_token(const char *_token)
{
	// copy the token and break it into pieces
	astring token[6];
	int numtokens;
	for (numtokens = 0; numtokens < ARRAY_LENGTH(token); )
	{
		// make a token up to the next underscore
		char *score = (char *)strchr(_token, '_');
		token[numtokens++].cpy(_token, (score == NULL) ? strlen(_token) : (score - _token));

		// if we hit the end, we're done, else advance our pointer
		if (score == NULL)
			break;
		_token = score + 1;
	}

	// first token should be the devclass
	int curtok = 0;
	input_device_class devclass = input_device_class((*devclass_token_table)[token[curtok++].cstr()]);
	if (devclass == ~0)
		return INPUT_CODE_INVALID;

	// second token might be index; look for number
	int devindex = 0;
	if (numtokens > 2 && sscanf(token[curtok], "%d", &devindex) == 1)
	{
		curtok++;
		devindex--;
	}
	if (curtok >= numtokens)
		return INPUT_CODE_INVALID;

	// next token is the item ID
	input_item_id itemid = input_item_id((*itemid_token_table)[token[curtok].cstr()]);
	bool standard = (itemid != ~0);

	// if we're a standard code, default the itemclass based on it
	input_item_class itemclass = ITEM_CLASS_INVALID;
	if (standard)
		itemclass = m_class[devclass]->standard_item_class(itemid);

	// otherwise, keep parsing
	else
	{
		// if this is an invalid device, we have nothing to look up
		input_device *device = m_class[devclass]->device(devindex);
		if (device == NULL)
			return INPUT_CODE_INVALID;

		// if not a standard code, look it up in the device specific codes
		for (itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
		{
			input_device_item *item = device->item(itemid);
			if (item != NULL && token[curtok].cmp(item->token()) == 0)
			{
				// take the itemclass from the item
				itemclass = item->itemclass();
				break;
			}
		}

		// bail on fail
		if (itemid > device->maxitem())
			return INPUT_CODE_INVALID;
	}
	curtok++;

	// if we have another token, it is probably a modifier
	input_item_modifier modifier = ITEM_MODIFIER_NONE;
	if (curtok < numtokens)
	{
		modifier = input_item_modifier((*modifier_token_table)[token[curtok].cstr()]);
		if (modifier != ~0)
			curtok++;
		else
			modifier = ITEM_MODIFIER_NONE;
	}

	// if we have another token, it is the item class
	if (curtok < numtokens)
	{
		UINT32 temp = (*itemclass_token_table)[token[curtok].cstr()];
		if (temp != ~0)
		{
			curtok++;
			itemclass = input_item_class(temp);
		}
	}

	// we should have consumed all tokens
	if (curtok != numtokens)
		return INPUT_CODE_INVALID;

	// assemble the final code
	return input_code(devclass, devindex, itemclass, modifier, itemid);
}


//-------------------------------------------------
//  seq_pressed - return true if the given sequence
//  of switch inputs is "pressed"
//-------------------------------------------------

bool input_manager::seq_pressed(const input_seq &seq)
{
	// iterate over all of the codes
	bool result = false;
	bool invert = false;
	bool first = true;
	for (int codenum = 0; ; codenum++)
	{
		// handle NOT
		input_code code = seq[codenum];
		if (code == input_seq::not_code)
			invert = true;

		// handle OR and END
		else if (code == input_seq::or_code || code == input_seq::end_code)
		{
			// if we have a positive result from the previous set, we're done
			if (result || code == input_seq::end_code)
				break;

			// otherwise, reset our state
			result = false;
			invert = false;
			first = true;
		}

		// handle everything else as a series of ANDs
		else
		{
			// if this is the first in the sequence, result is set equal
			if (first)
				result = code_pressed(code) ^ invert;

			// further values are ANDed
			else if (result)
				result &= code_pressed(code) ^ invert;

			// no longer first, and clear the invert flag
			first = invert = false;
		}
	}

	// return the result if we queried at least one switch
	return result;
}


//-------------------------------------------------
//  seq_axis_value - return the value of an axis
//  defined in an input sequence
//-------------------------------------------------

INT32 input_manager::seq_axis_value(const input_seq &seq, input_item_class &itemclass)
{
	// start with no valid classes
	input_item_class itemclasszero = ITEM_CLASS_INVALID;
	itemclass = ITEM_CLASS_INVALID;

	// iterate over all of the codes
	INT32 result = 0;
	bool invert = false;
	bool enable = true;
	for (int codenum = 0; ; codenum++)
	{
		// handle NOT
		input_code code = seq[codenum];
		if (code == input_seq::not_code)
			invert = true;

		// handle OR and END
		else if (code == input_seq::or_code || code == input_seq::end_code)
		{
			// if we have a positive result from the previous set, we're done
			if (itemclass != ITEM_CLASS_INVALID || code == input_seq::end_code)
				break;

			// otherwise, reset our state
			result = 0;
			invert = false;
			enable = true;
		}

		// handle everything else only if we're still enabled
		else if (enable)
		{
			// switch codes serve as enables
			if (code.item_class() == ITEM_CLASS_SWITCH)
			{
				// AND against previous digital codes
				if (enable)
					enable &= code_pressed(code) ^ invert;
			}

			// non-switch codes are analog values
			else
			{
				INT32 value = code_value(code);

				// if we got a 0 value, don't do anything except remember the first type
				if (value == 0)
				{
					if (itemclasszero == ITEM_CLASS_INVALID)
						itemclasszero = code.item_class();
				}

				// non-zero absolute values stick
				else if (code.item_class() == ITEM_CLASS_ABSOLUTE)
				{
					itemclass = ITEM_CLASS_ABSOLUTE;
					result = value;
				}

				// non-zero relative values accumulate
				else if (code.item_class() == ITEM_CLASS_RELATIVE)
				{
					itemclass = ITEM_CLASS_RELATIVE;
					result += value;
				}
			}

			// clear the invert flag
			invert = false;
		}
	}

	// if the caller wants to know the type, provide it
	if (result == 0)
		itemclass = itemclasszero;
	return result;
}


//-------------------------------------------------
//  seq_poll_start - begin polling for a new
//  sequence of the given itemclass
//-------------------------------------------------

void input_manager::seq_poll_start(input_item_class itemclass, const input_seq *startseq)
{
	assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE);

	// reset the recording count and the clock
	m_poll_seq_last_ticks = 0;
	m_poll_seq_class = itemclass;
	m_poll_seq.reset();

	// grab the starting sequence to append to, and append an OR
	if (startseq != NULL)
	{
		m_poll_seq = *startseq;
		if (m_poll_seq.length() > 0)
			m_poll_seq += input_seq::or_code;
	}

	// flush out any goobers
	reset_polling();
	input_code dummycode = KEYCODE_ENTER;
	while (dummycode != INPUT_CODE_INVALID)
		dummycode = (m_poll_seq_class == ITEM_CLASS_SWITCH) ? poll_switches() : poll_axes();
}


//-------------------------------------------------
//  input_seq_poll - continue polling
//-------------------------------------------------

bool input_manager::seq_poll()
{
	int curlen = m_poll_seq.length();
	input_code lastcode = m_poll_seq[curlen - 1];

	// switch case: see if we have a new code to process
	input_code newcode;
	if (m_poll_seq_class == ITEM_CLASS_SWITCH)
	{
		newcode = poll_switches();
		if (newcode != INPUT_CODE_INVALID)
		{
			// if code is duplicate, toggle the NOT state on the code
			if (curlen > 0 && newcode == lastcode)
			{
				// back up over the existing code
				m_poll_seq.backspace();

				// if there was a NOT preceding it, delete it as well, otherwise append a fresh one
				if (m_poll_seq[curlen - 2] == input_seq::not_code)
					m_poll_seq.backspace();
				else
					m_poll_seq += input_seq::not_code;
			}
		}
	}

	// absolute/relative case: see if we have an analog change of sufficient amount
	else
	{
		bool has_or = false;
		if (lastcode == input_seq::or_code)
		{
			lastcode = m_poll_seq[curlen - 2];
			has_or = true;
		}
		newcode = poll_axes();

		// if the last code doesn't match absolute/relative of this code, ignore the new one
		if ((lastcode.item_class() == ITEM_CLASS_ABSOLUTE && newcode.item_class() != ITEM_CLASS_ABSOLUTE) ||
			(lastcode.item_class() == ITEM_CLASS_RELATIVE && newcode.item_class() != ITEM_CLASS_RELATIVE))
			newcode = INPUT_CODE_INVALID;

		// if the new code is valid, check for half-axis toggles on absolute controls
		if (newcode != INPUT_CODE_INVALID && curlen > 0 && newcode.item_class() == ITEM_CLASS_ABSOLUTE)
		{
			input_code last_nomodifier = lastcode;
			last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE);
			if (newcode == last_nomodifier)
			{
				// increment the modifier, wrapping back to none
				switch (lastcode.item_modifier())
				{
					case ITEM_MODIFIER_NONE:	newcode.set_item_modifier(ITEM_MODIFIER_POS);	break;
					case ITEM_MODIFIER_POS:		newcode.set_item_modifier(ITEM_MODIFIER_NEG);	break;
					default:
					case ITEM_MODIFIER_NEG:		newcode.set_item_modifier(ITEM_MODIFIER_NONE);	break;
				}

				// back up over the previous code so we can re-append
				if (has_or)
					m_poll_seq.backspace();
				m_poll_seq.backspace();
			}
		}
	}

	// if we got a new code to append it, append it and reset the timer
	if (newcode != INPUT_CODE_INVALID)
	{
		m_poll_seq += newcode;
		m_poll_seq_last_ticks = osd_ticks();
	}

	// if we're recorded at least one item and 2/3 of a second has passed, we're done
	if (m_poll_seq_last_ticks != 0 && osd_ticks() > m_poll_seq_last_ticks + osd_ticks_per_second() * 2 / 3)
	{
		// if the final result is invalid, reset to nothing
		if (!m_poll_seq.is_valid())
			m_poll_seq.reset();

		// return true to indicate that we are finished
		return true;
	}

	// return false to indicate we are still polling
	return false;
}


//-------------------------------------------------
//  seq_name - generate the friendly name of a
//  sequence
//-------------------------------------------------

const char *input_manager::seq_name(astring &string, const input_seq &seq) const
{
	// make a copy of our sequence, removing any invalid bits
	input_code clean_codes[sizeof(seq) / sizeof(input_code)];
	int clean_index = 0;
	astring codestr;
	for (int codenum = 0; seq[codenum] != input_seq::end_code; codenum++)
	{
		// if this is a code item which is not valid, don't copy it and remove any preceding ORs/NOTs
		input_code code = seq[codenum];
		if (!code.internal() && strlen(code_name(codestr, code)) == 0)
		{
			while (clean_index > 0 && clean_codes[clean_index - 1].internal())
				clean_index--;
		}
		else if (clean_index > 0 || !code.internal())
			clean_codes[clean_index++] = code;
	}

	// special case: empty
	if (clean_index == 0)
		return string.cpy((seq.length() == 0) ? "None" : "n/a");

	// start with an empty buffer
	string.reset();

	// loop until we hit the end
	for (int codenum = 0; codenum < clean_index; codenum++)
	{
		// append a space if not the first code
		if (codenum != 0)
			string.cat(" ");

		// handle OR/NOT codes here
		input_code code = clean_codes[codenum];
		if (code == input_seq::or_code)
			string.cat("or");
		else if (code == input_seq::not_code)
			string.cat("not");

		// otherwise, assume it is an input code and ask the input system to generate it
		else
			string.cat(code_name(codestr, code));
	}
	return string;
}


//-------------------------------------------------
//  seq_to_tokens - generate the tokenized form of
//  a sequence
//-------------------------------------------------

const char *input_manager::seq_to_tokens(astring &string, const input_seq &seq) const
{
	// start with an empty buffer
	string.reset();

	// loop until we hit the end
	astring codestr;
	for (int codenum = 0; seq[codenum] != input_seq::end_code; codenum++)
	{
		// append a space if not the first code
		if (codenum != 0)
			string.cat(" ");

		// handle OR/NOT codes here
		input_code code = seq[codenum];
		if (code == input_seq::or_code)
			string.cat("OR");
		else if (code == input_seq::not_code)
			string.cat("NOT");
		else if (code == input_seq::default_code)
			string.cat("DEFAULT");

		// otherwise, assume it is an input code and ask the input system to generate it
		else
			string.cat(code_to_token(codestr, code));
	}
	return string;
}


//-------------------------------------------------
//  seq_from_tokens - generate the tokenized form
//  of a sequence
//-------------------------------------------------

void input_manager::seq_from_tokens(input_seq &seq, const char *string)
{
	// start with a blank sequence
	seq.reset();

	// loop until we're done
	astring strcopy = string;
	char *str = const_cast<char *>(strcopy.cstr());
	while (1)
	{
		// trim any leading spaces
		while (*str != 0 && isspace((UINT8)*str))
			str++;

		// bail if we're done
		if (*str == 0)
			return;

		// find the end of the token and make it upper-case along the way
		char *strtemp;
		for (strtemp = str; *strtemp != 0 && !isspace((UINT8)*strtemp); strtemp++)
			*strtemp = toupper((UINT8)*strtemp);
		char origspace = *strtemp;
		*strtemp = 0;

		// look for common stuff
		input_code code;
		if (strcmp(str, "OR") == 0)
			code = input_seq::or_code;
		else if (strcmp(str, "NOT") == 0)
			code = input_seq::not_code;
		else if (strcmp(str, "DEFAULT") == 0)
			code = input_seq::default_code;
		else
			code = code_from_token(str);

		// translate and add to the sequence
		seq += code;

		// advance
		if (origspace == 0)
			return;
		str = strtemp + 1;
	}
}


//-------------------------------------------------
//  set_global_joystick_map - set the joystick map
//  for all devices
//-------------------------------------------------

bool input_manager::set_global_joystick_map(const char *mapstring)
{
	// parse the map
	joystick_map map;
	if (!map.parse(mapstring))
		return false;

	astring tempstr;
	mame_printf_verbose("Input: Changing default joystick map = %s", map.to_string(tempstr));

	// iterate over joysticks and set the map
	for (int joynum = 0; joynum <= m_joystick_class.maxindex(); joynum++)
	{
		input_device *device = m_joystick_class.device(joynum);
		if (device != NULL)
			device->set_joystick_map(map);
	}
	return true;
}



//**************************************************************************
//  INPUT DEVICE ITEM
//**************************************************************************

//-------------------------------------------------
//  input_device_item - constructor
//-------------------------------------------------

input_device_item::input_device_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate, input_item_class itemclass)
	: m_device(device),
	  m_name(name),
	  m_internal(internal),
	  m_itemid(itemid),
	  m_itemclass(itemclass),
	  m_getstate(getstate),
	  m_current(0),
	  m_memory(0)
{
	// use a standard token name for know item IDs
	if (itemid <= ITEM_ID_MAXIMUM && (*itemid_token_table)[itemid] != NULL)
		m_token.cpy((*itemid_token_table)[itemid]);

	// otherwise, create a tokenized name
	else
		m_token.cpy(name).makeupper().delchr(' ').delchr('_');
}



//**************************************************************************
//  INPUT DEVICE SWITCH ITEM
//**************************************************************************

//-------------------------------------------------
//  input_device_switch_item - constructor
//-------------------------------------------------

input_device_switch_item::input_device_switch_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
	: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_SWITCH),
	  m_steadykey(0),
	  m_oldkey(0)
{
}


//-------------------------------------------------
//  read_as_switch - return the raw switch value,
//  modified as necessary
//-------------------------------------------------

INT32 input_device_switch_item::read_as_switch(input_item_modifier modifier)
{
	// if we're doing a lightgun reload hack, button 1 and 2 operate differently
	input_device_class devclass = m_device.devclass();
	if (devclass == DEVICE_CLASS_LIGHTGUN && m_device.lightgun_reload_button())
	{
		// button 1 is pressed if either button 1 or 2 are active
		if (m_itemid == ITEM_ID_BUTTON1)
		{
			input_device_item *button2_item = m_device.item(ITEM_ID_BUTTON2);
			if (button2_item != NULL)
				return button2_item->update_value() | update_value();
		}

		// button 2 is never officially pressed
		if (m_itemid == ITEM_ID_BUTTON2)
			return 0;
	}

	// steadykey for keyboards
	if (devclass == DEVICE_CLASS_KEYBOARD && m_device.steadykey_enabled())
		return m_steadykey;

	// everything else is just the current value as-is
	return update_value();
}


//-------------------------------------------------
//  read_as_relative - return the switch input as
//  a relative axis value
//-------------------------------------------------

INT32 input_device_switch_item::read_as_relative(input_item_modifier modifier)
{
	// no translation to relative
	return 0;
}


//-------------------------------------------------
//  read_as_absolute - return the switch input as
//  an absolute axis value
//-------------------------------------------------

INT32 input_device_switch_item::read_as_absolute(input_item_modifier modifier)
{
	// no translation to absolute
	return 0;
}


//-------------------------------------------------
//  steadykey_changed - update for steadykey
//  behavior, returning true if the current state
//  has changed since the last call
//-------------------------------------------------

bool input_device_switch_item::steadykey_changed()
{
	INT32 old = m_oldkey;
	m_oldkey = update_value();
	if (((m_current ^ old) & 1) == 0)
		return false;

	// if the keypress was missed, turn it on for one frame
	if (((m_current | m_steadykey) & 1) == 0)
		m_steadykey = 1;
	return true;
}



//**************************************************************************
//  INPUT DEVICE RELATIVE ITEM
//**************************************************************************

//-------------------------------------------------
//  input_device_relative_item - constructor
//-------------------------------------------------

input_device_relative_item::input_device_relative_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
	: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_RELATIVE)
{
}


//-------------------------------------------------
//  read_as_switch - return the relative value as
//  a switch result based on the modifier
//-------------------------------------------------

INT32 input_device_relative_item::read_as_switch(input_item_modifier modifier)
{
	// process according to modifiers
	if (modifier == ITEM_MODIFIER_POS || modifier == ITEM_MODIFIER_RIGHT || modifier == ITEM_MODIFIER_DOWN)
		return (update_value() > 0);
	else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP)
		return (update_value() < 0);

	// all other cases just return 0
	return 0;
}


//-------------------------------------------------
//  read_as_relative - return the relative input
//  as a relative axis value
//-------------------------------------------------

INT32 input_device_relative_item::read_as_relative(input_item_modifier modifier)
{
	// just return directly
	return update_value();
}


//-------------------------------------------------
//  read_as_absolute - return the relative input
//  as an absolute axis value
//-------------------------------------------------

INT32 input_device_relative_item::read_as_absolute(input_item_modifier modifier)
{
	// no translation to absolute
	return 0;
}



//**************************************************************************
//  INPUT DEVICE ABSOLUTE ITEM
//**************************************************************************

//-------------------------------------------------
//  input_device_absolute_item - constructor
//-------------------------------------------------

input_device_absolute_item::input_device_absolute_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
	: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_ABSOLUTE)
{
}


//-------------------------------------------------
//  read_as_switch - return the absolute value as
//  a switch result based on the modifier
//-------------------------------------------------

INT32 input_device_absolute_item::read_as_switch(input_item_modifier modifier)
{
	// start with the current value
	INT32 result = m_device.apply_deadzone_and_saturation(update_value());
	assert(result >= INPUT_ABSOLUTE_MIN && result <= INPUT_ABSOLUTE_MAX);

	// left/right/up/down: if this is a joystick, fetch the paired X/Y axis values and convert
	if (m_device.devclass() == DEVICE_CLASS_JOYSTICK && modifier >= ITEM_MODIFIER_LEFT && modifier <= ITEM_MODIFIER_DOWN)
	{
		input_device_item *xaxis_item = m_device.item(ITEM_ID_XAXIS);
		input_device_item *yaxis_item = m_device.item(ITEM_ID_YAXIS);
		if (xaxis_item != NULL && yaxis_item != NULL)
		{
			// determine which item we didn't update, and update it
			assert(this == xaxis_item || this == yaxis_item);
			if (this == xaxis_item)
				yaxis_item->update_value();
			else
				xaxis_item->update_value();

			// now map the X and Y axes to a 9x9 grid using the raw values
			return (m_device.joymap().update(xaxis_item->current(), yaxis_item->current()) >> (modifier - ITEM_MODIFIER_LEFT)) & 1;
		}
	}

	// positive/negative: TRUE if past the deadzone in either direction
	if (modifier == ITEM_MODIFIER_POS || modifier == ITEM_MODIFIER_RIGHT || modifier == ITEM_MODIFIER_DOWN)
		return (result > 0);
	else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP)
		return (result < 0);

	// all other cases just return 0
	return 0;
}


//-------------------------------------------------
//  read_as_relative - return the absolute input
//  as a relative axis value
//-------------------------------------------------

INT32 input_device_absolute_item::read_as_relative(input_item_modifier modifier)
{
	// no translation to relative
	return 0;
}


//-------------------------------------------------
//  read_as_absolute - return the absolute input
//  as an absolute axis value, with appropriate
//  tweaks
//-------------------------------------------------

INT32 input_device_absolute_item::read_as_absolute(input_item_modifier modifier)
{
	// start with the current value
	INT32 result = m_device.apply_deadzone_and_saturation(update_value());
	assert(result >= INPUT_ABSOLUTE_MIN && result <= INPUT_ABSOLUTE_MAX);

	// if we're doing a lightgun reload hack, override the value
	if (m_device.devclass() == DEVICE_CLASS_LIGHTGUN && m_device.lightgun_reload_button())
	{
		// if it is pressed, return (min,max)
		input_device_item *button2_item = m_device.item(ITEM_ID_BUTTON2);
		if (button2_item != NULL && button2_item->update_value())
			result = (m_itemid == ITEM_ID_XAXIS) ? INPUT_ABSOLUTE_MIN : INPUT_ABSOLUTE_MAX;
	}

	// positive/negative: scale to full axis
	if (modifier == ITEM_MODIFIER_POS)
		result = MAX(result, 0) * 2 + INPUT_ABSOLUTE_MIN;
	if (modifier == ITEM_MODIFIER_NEG)
		result = MAX(-result, 0) * 2 + INPUT_ABSOLUTE_MIN;
	return result;
}
