
#ifndef inputmanager_h
#define inputmanager_h

#pragma once

namespace Input {
	struct Keyboard { enum { elements = 256 }; };

	struct Mouse {
		enum { buttons = 8 };
		enum { x, y, z, button, elements = button + buttons };
	};

	struct Joypad {
		enum { count = 16 };
		enum { hats = 4 };
		enum { axes = 6 };
		enum { buttons = 96 };
		enum { hat_up, hat_right, hat_down, hat_left, hat_directions };

		enum { hat, axis = hat + (hats * hat_directions), button = axis + axes, elements = button + buttons };
	};

	enum { ElementCounter = Keyboard::elements + Mouse::elements + Joypad::count * Joypad::elements };
}

class Inputmanager
{
public:
	enum DeviceID {
		IdNone,
		IdJoypad1,
		IdJoypad2,
		IdLightgun1,
		IdLightgun2,
		IdPause,
	};

	enum IObjectID {
		Up		=	0, Down	=	1,
		Left	=	2, Right=	3,
		A		=	4, B	=	5,
		X		=	6, Y	=	7,
		Trigger	=	8, Pause=	9,
		TH		=	10
	};

	struct InputElement {
		enum Type { KeyboardButton, MouseAxis, MouseButton, JoypadAxis, JoypadButton };

		static Type getElementType(unsigned element);

		static bool isButton(unsigned element) {
			Type id = getElementType(element);
			return (id == KeyboardButton || id == MouseButton || id == JoypadButton);
		}

		static bool isAxis(unsigned element) {
			Type id = getElementType(element);
			return (id == MouseAxis || id == JoypadAxis);
		}
	};

	struct InputObject {
		IObjectID id;
		enum Type { Button, Axis } type;
		char* name;
		unsigned element; //preset by user configuration
		i16 state;

		InputObject(Type type, char* name, IObjectID id)
		: type(type), name(name), id(id), element(0) {}
	};

	struct Device {
		Inputmanager* inputmanager;
		DeviceID id;
		enum Port { None = 0, Port1 = 1, Port2 = 2 } port;
		char* name;
		unsigned vectorPos;
		vector<InputObject*> objectList;

		InputObject* getInputObject(IObjectID iobjectId);
		unsigned countInputObjects() const { return vectorPos; }

		i16 getState(IObjectID iobjectId);

		void updateMap(IObjectID iobjectId, unsigned element);
		unsigned readMap(IObjectID iobjectId);

		bool isHiToLoTransition(i16* table, i16* tableLast, IObjectID iobjectId);
		void unmap();
		
		void attach(InputObject& iobject) {
			objectList[vectorPos++] = &iobject;
		}

		unsigned getInputObjectIdList(u8* list);
		char* getEntry(IObjectID iobjectId);
		void updateState(i16* table);

		Device(DeviceID id, Port port, char* name, Inputmanager* inputmanager)
		: objectList(10), name(name), id(id), port(port), inputmanager(inputmanager) { vectorPos = 0; }
	};

	struct Joypad : Device {
		InputObject up, down, left, right, a, b;

		Joypad(DeviceID id, Port port, char* name, Inputmanager* inputmanager)
		:
		Device(id, port, name, inputmanager),

		up		(InputObject::Button, "Up", IObjectID::Up),
		down	(InputObject::Button, "Down", IObjectID::Down),
		left	(InputObject::Button, "Left", IObjectID::Left),
		right	(InputObject::Button, "Right", IObjectID::Right),
		a		(InputObject::Button, "A", IObjectID::A),
		b		(InputObject::Button, "B", IObjectID::B)
		{
			attach(up); attach(down); attach(left); attach(right); attach(a); attach(b); 
		}
	};

	struct Lightgun : Device {
		InputObject x, y, trigger;

		Lightgun(DeviceID id, Port port, char* name, Inputmanager* inputmanager)
		:
		Device(id, port, name, inputmanager),
		x      (InputObject::Axis,   "X-Axis", IObjectID::X),
		y      (InputObject::Axis,   "Y-Axis", IObjectID::Y),
		trigger(InputObject::Button, "Trigger", IObjectID::Trigger)
		{
			attach(x); attach(y); attach(trigger);
		}
	};

	struct PauseButton : Device {
		InputObject pause;

		PauseButton(DeviceID id, Port port, char* name, Inputmanager* inputmanager)
		:
		Device(id, port, name, inputmanager),
		pause(InputObject::Button, "Pause", IObjectID::Pause)
		{
			attach(pause);
		}
	};

	struct SmsPort {
		Device* device;
	} smsport[2];

	DInput& dinput;
	Joypad joypad1;
	Joypad joypad2;
	Lightgun lightgun1;
	Lightgun lightgun2;
	PauseButton pause;

	vector<Device*> deviceList;
	unsigned vectorPos;

	void attach(Device& device) {
		deviceList[vectorPos++] = &device;
	}
	i16 getInputState(DeviceID deviceId, IObjectID iobjectId);

	void setPort(Device::Port port, DeviceID deviceId);
	u8 readPort(Device::Port port, IObjectID iobjectId);

	void captureReady(DeviceID deviceId, IObjectID iobjectId);
	unsigned getDeviceCount() const { return vectorPos; }
	unsigned getDeviceObjectCount(DeviceID deviceId);
	void getDeviceName(DeviceID deviceId, char* name);
	Device::Port getDevicePort(DeviceID deviceId);
	void getDeviceObjectEntry(DeviceID deviceId, IObjectID iobjectId, char* entry);

	void updateMap(DeviceID deviceId, IObjectID iobjectId, unsigned element);
	unsigned readMap(DeviceID deviceId, IObjectID iobjectId);

	Device* getDevice(DeviceID deviceId);

	void unmap(Inputmanager::DeviceID deviceId);

	i16 inputTable[2][Input::ElementCounter];

	bool isHiToLoTransition(DeviceID deviceId, IObjectID iobjectId);

	unsigned getDeviceIdList(u8* list);
	unsigned getObjectIdList(DeviceID deviceId, u8* list);

	bool isCapturing() { return waiting; }
	void endCapturing() { waiting = false; }

	bool activeState;
	bool waiting;
	bool pollDevices();
	InputObject* captureObject;

	void flush() {
		for(unsigned i = 0; i < Input::ElementCounter; i++) {
			inputTable[0][i] = inputTable[1][i] = 0;
		}
	}

	void update() {
		for(unsigned i=0; i < vectorPos; i++) {
			deviceList[i]->updateState( inputTable[activeState] );
		}
	}

	Inputmanager(DInput& dinput)
	:
	joypad1( IdJoypad1, Device::Port::Port1, "Joypad", this),
	joypad2( IdJoypad2, Device::Port::Port2, "Joypad", this),
	lightgun1( IdLightgun1, Device::Port::Port1, "Lightgun", this),
	lightgun2( IdLightgun2, Device::Port::Port2, "Lightgun", this),
	pause( IdPause, Device::Port::None, "Pause", this),
	dinput(dinput),
	deviceList(10)
	{
		smsport[0].device = 0;
		smsport[1].device = 0;
		vectorPos = 0;
		activeState = 0;
		waiting = false;
		flush();
		captureObject = 0;
		attach(joypad1); attach(joypad2); attach(lightgun1); attach(lightgun2); attach(pause);
	}
};

#endif