module gedo.input.joystick;



private import std.cstream;
private import std.string;
private import std.ctype;

private import sdl.std;

private import gedo.text.convert;
private import gedo.file.config;



alias void delegate(uint buttonNo) OnButtonDown;
alias void delegate(uint axisNo, int axisValue) OnAxisMotion;



public class Joysticks
{
public:
	static void initialize()
	{
		_instance = new Joysticks();
	}


	static Joysticks opCall()
	{
		return _instance;
	}


	Joystick[] items()
	{
		return _joysticks;
	}


	Joystick keyboard()
	{
		return _joysticks[length - 1];
	}


	void update()
	{
		foreach(j; _joysticks)
		{
			j.updateState();
		}
	}


	void handleEvent(SDL_Event* event)
	{
		switch(event.type)
		{
		case SDL_KEYDOWN:
			if(_joysticks[length-1].onAxisMotion)
			{
				_joysticks[length-1].onAxisMotion(event.key.keysym.sym, 32767);
				if(_joysticks[length-1].onAxisMotion)
				{
					_joysticks[length-1].onAxisMotion(event.key.keysym.sym, 0);
				}
			}
			else if(_joysticks[length-1].onButtonDown)
			{
				_joysticks[length-1].onButtonDown(event.key.keysym.sym);
			}
			break;

		case SDL_JOYBUTTONDOWN:
			if(_joysticks[event.jbutton.which].onButtonDown)
			{
				_joysticks[event.jbutton.which].onButtonDown(event.jbutton.button);
			}
			break;

		case SDL_JOYAXISMOTION:
			if(_joysticks[event.jaxis.which].onAxisMotion)
			{
				_joysticks[event.jaxis.which].onAxisMotion(event.jaxis.axis, event.jaxis.value);
			}
			break;

		default:
			break;
		}
	}


	Joystick detectPressed()
	{
		foreach(j; _joysticks)
		{
			foreach(button; j.buttons)
			{
				if(button.isPushed(0))
				{
					return j;
				}
			}
		}

		return null;
	}


	void config(Config items)
	{
		foreach(joystick; _joysticks)
		{
			auto text = items.get(joystick.name, "");

			auto tokens = .std.string.split(text);

			if(tokens.length >= 6)
			{
				uint[] table;
				table.length = tokens.length;
				foreach(i, token; tokens)
				{
					table[i] = toInt(token);
				}

				joystick.config(table);
			}
		}
	}


	void getConfig(Config items)
	{
		foreach(joystick; _joysticks)
		{
			string[] tokens;

			tokens.length = joystick.config.length;
			foreach(i, c; joystick.config)
			{
				tokens[i] = .std.string.format("%d", c);
			}

			items.set(joystick.name, .std.string.join(tokens, " "));
		}
	}


private:
	static Joysticks _instance;

	Joystick[] _joysticks;


	this()
	{
		_joysticks.length = 1 + SDL_NumJoysticks();

		foreach(i, inout j; _joysticks[0 .. length - 1])
		{
			j = new SDLStick(i);
		}

		_joysticks[length - 1] = new Keyboard();

		dout.writefln("%d joysticks were found.", _joysticks.length);
		dout.writefln("The names of the joysticks are:");

		foreach(joystick; _joysticks)
		{
			dout.writefln("    %s", joystick.name);
		}

		SDL_JoystickEventState(SDL_ENABLE);
	}
}



public class Joystick
{
public:
	OnButtonDown onButtonDown = null;
	OnAxisMotion onAxisMotion = null;


	void config(uint[] table)
	{
		configAxis(table[1 .. 1 + table[0]]);

		configButton(table[1 + table[0] .. length]);
	}


	void configAxis(uint[] table)
	{
		_axisTable = table.dup;

		_axes.length = table.length / 4;

		foreach(inout axis; _axes)
		{
			axis = new Axis();
		}
	}


	void configButton(uint[] table)
	{
		_buttonTable = table.dup;

		_buttons.length = _buttonTable.length;

		foreach(inout button; _buttons)
		{
			button = new Button();
		}
	}


	uint[] config()
	{
		uint[1] head;

		head[0] = _axisTable.length;

		return head ~ _axisTable ~ _buttonTable;
	}


	uint[] axisConfig()
	{
		return _axisTable;
	}


	uint[] buttonConfig()
	{
		return _buttonTable;
	}


	void config(bool b)
	{
	}


	Axis axis()
	{
		return _axes[0];
	}


	Axis[] axes()
	{
		return _axes;
	}


	Button[] buttons()
	{
		return _buttons;
	}


	abstract string name()
	{
		return "";
	}


protected:
	Button[] _buttons;
	Axis[]   _axes;
	uint[]   _axisTable;
	uint[]   _buttonTable;


	abstract void updateState()
	{
	}
}



public class Axis
{
public:
	enum
	{
		LEFT,
		RIGHT,
		DOWN,
		UP,
	}


	this()
	{
		foreach(inout b; _buttons)
		{
			b = new Button();
		}
	}


	int x()
	{
		return _x;
	}


	int y()
	{
		return _y;
	}


	void update(int xn, int yn)
	{
		const int THRESHOLD = 16 * 1024;
		_x = xn;
		_y = yn;

		bool[4] isPushed;

		if(xn > THRESHOLD)
		{
			isPushed[RIGHT] = true;
		}
		else if(xn < -THRESHOLD)
		{
			isPushed[LEFT] = true;
		}
		if(yn > THRESHOLD)
		{
			isPushed[UP] = true;
		}
		else if(yn < -THRESHOLD)
		{
			isPushed[DOWN] = true;
		}

		foreach(i, b; _buttons)
		{
			b.update(isPushed[i]);
		}
	}


	Button[] asButtons()
	{
		return _buttons;
	}


private:
	Button[4] _buttons;
	int       _x;
	int       _y;
}



public class Button
{
public:
	bool isPushed()
	{
		return _isPushed;
	}


	bool isPushed(uint c)
	{
		if(_isEnabled)
		{
			_isEnabled = false;

			return (_counter <= c);
		}

		return false;
	}


	bool isRepeatPushed()
	{
		if(_isPushed)
		{
			if(_counter < 16)
			{
				return (_counter == 0);
			}
			else
			{
				return ((_counter & 7) == 0);
			}
		}

		return false;
	}


	uint counter()
	{
		return _counter;
	}


	void update(bool s)
	{
		if(s != _isPushed)
		{
			_isPushed = s;

			if(s)
			{
				_counter = 0;

				_isEnabled = true;

				return;
			}
		}

		++_counter;
	}


private:
	uint _counter;
	bool _isPushed;
	bool _isEnabled;
}



private class Keyboard : Joystick
{
public:
	this()
	{
		configButton(DEFAULT_BUTTON_TABLE);

		configAxis(DEFAULT_AXIS_TABLE);
	}


	override void updateState()
	{
		auto state = cast(ubyte*)SDL_GetKeyState(null);

		foreach(i, axis; _axes)
		{
			auto table = _axisTable[4*i .. 4*(i+1)];
			if(!_isConfigEnable)
			{
				table = DEFAULT_AXIS_TABLE;
			}

			auto axo = 0;
			if(state[table[0]])
			{
				axo = -30000;
			}
			else if(state[table[1]])
			{
				axo = 30000;
			}

			auto ayo = 0;
			if(state[table[2]])
			{
				ayo = -30000;
			}
			else if	(state[table[3]])
			{
				ayo = 30000;
			}

			axis.update(axo, ayo);
		}

		if(_isConfigEnable)
		{
			foreach(i, e; _buttonTable)
			{
				_buttons[i].update(state[e] != 0);
			}
		}
		else
		{
			foreach(i, e; DEFAULT_BUTTON_TABLE)
			{
				if(i < _buttons.length)
				{
					_buttons[i].update(state[e] != 0);
				}
			}
		}
	}


	override string name()
	{
		return "keyboard";
	}


	override void config(bool b)
	{
		_isConfigEnable = b;
	}


private:
	static const uint[] DEFAULT_AXIS_TABLE   = [SDLK_LEFT, SDLK_RIGHT, SDLK_DOWN, SDLK_UP];
	static const uint[] DEFAULT_BUTTON_TABLE = [SDLK_RETURN, SDLK_BACKSPACE, SDLK_0, SDLK_1, SDLK_2, SDLK_3];

	bool _isConfigEnable = true;
}



private class SDLStick : Joystick
{
public:
	this(int no)
	{
		_stick = SDL_JoystickOpen(no);
		if(!_stick)
		{
			throw new Exception("couldn't open joystick");
		}

		uint[] table;

		table.length = SDL_JoystickNumButtons(_stick);

		foreach(i, inout e; table)
		{
			e = i;
		}
		configButton(table);

		table.length = SDL_JoystickNumAxes(_stick) * 2;
		foreach(i, inout e; table)
		{
			e = i/2;
		}
		configAxis(table);

		auto sz = cast(ubyte*)SDL_JoystickName(no);
		int   l = 0;
		while(sz[l] != 0)
		{
			++l;
		}

		auto buffer = new char[l*2];
		auto ib     = 0;
		foreach(c; sz[0 .. l])
		{
			if(.std.ctype.isalnum(c))
			{
				buffer[ib++] = c;
			}
			else
			{
				buffer[ib++] = .std.string.hexdigits[c>>4];
				buffer[ib++] = .std.string.hexdigits[c&0xF];
			}
		}

		_name = .std.string.format("%s_%d", buffer[0 .. ib], no);
	}


	override void updateState()
	{
		foreach(i, axis; _axes)
		{
			auto axi = _axisTable[4*i+0];
			auto ayi = _axisTable[4*i+2];

			int axo = SDL_JoystickGetAxis(_stick, axi & 0x7F);
			int ayo = SDL_JoystickGetAxis(_stick, ayi & 0x7F);

			if(axi & 0x80)
			{
				axo = -axo;
			}
			if(ayi & 0x80)
			{
				ayo = -ayo;
			}

			axis.update(axo, ayo);
		}

		foreach(i, e; _buttonTable)
		{
			_buttons[i].update(SDL_JoystickGetButton(_stick, e) != 0);
		}
	}


	override string name()
	{
		return _name;
	}


private:
	SDL_Joystick* _stick;
	string        _name;
}

