// This code is D 1.0

/**
	Part of my old D 1.0 game helper code that used SDL. I keep it compiling on new D compilers too, but it is not meant to be used in new projects.

	Version: 1.0
	License: GNU General Public License
*/
module arsd.engine; //@-L-lSDL -L-lSDL_mixer -L-lSDL_ttf -L-lSDL_image -L-lGL -L-lSDL_net

pragma(lib, "SDL");
pragma(lib, "SDL_mixer");
pragma(lib, "SDL_ttf");
pragma(lib, "SDL_image");
pragma(lib, "SDL_net");
pragma(lib, "GL");

// FIXME: the difference between directions and buttons should be removed


import sdl.SDL;
import sdl.SDL_net;

version(D_Version2) {
	import random = core.stdc.stdlib;
	alias random.srand srand;

	char[] convToString(int a) {
		return null;
	}
	string immutableString(in char[] a) { return a.idup; }
} else {
	import random = std.random;
	void srand(uint a) {
		random.rand_seed(a, 0);
	}
	alias std.string.toString convToString;

	char[] immutableString(in char[] a) { return a; }
}
import std.math;
public import arsd.screen;
public import arsd.audio;

public import sdl.SDL_keysym_;

version(D_Version2)
	import core.stdc.stdarg;
else
	import std.stdarg;

version(D_Version2) {
	import core.stdc.stdio;
	void writefln(string s) { printf("%*s", s.length, s.ptr); }
	void writefln(string s, int i) { printf(s.ptr, i); }
} else
	import std.stdio;
//version(linux) pragma(lib, "kbhit.o");

int randomNumber(int min, int max){
	if(min == max)
		return min;
	max++; // make it inclusive

	uint a = random.rand();
	a %= (max - min);
	a += min;
	return a;
}


// HACK!
	bool waiting;
	uint globalTimer;
class Callable{
  public:
	  abstract void run(int timer);
	  // after is the other thing that is calling this to be paused and then unpaused
	  final void start(Callable after = null){
		After = after;
		if(After !is null)
			After.paused = true;
		paused = false;
		for(int a = 0; a < objs.length; a++)
			if(objs[a] is null){
				objs[a] = this;
				goto done;

		}
		objs.length = objs.length + 1;
		objs[objs.length - 1] = this;

		done:
		frame();
	}

	  final void queue(){
	  	int a;
		paused = true;
		for(a = 0; a < objs.length; a++)
			if(objs[a] is null){
				objs[a] = this;
				goto done;

		}
		objs.length = a + 1;
		objs[a] = this;

		done:
		if(a == 1){
			objs[0].paused = true;
			paused = false;
		} else {
			objs[a - 1].After = this;
		}
		After = objs[0];

		frame();
	}




	final void terminate(){
		for(int a = 0; a < objs.length; a++)
			if(objs[a] !is null && objs[a] == this)
				objs[a] = null;
		if(After !is null){
			After.paused = false;
			After.frame();
		}
	}

	bool paused;
  private:
	void frame(){
		if(!paused){
			if(globalTimer > lastFrame){
				lastFrame = globalTimer;
				run(Timer);
				Timer++;
			}
		}
	}
	int Timer;
	uint lastFrame;
	Callable After;
}

Callable[] objs;
// end HACK

//	enum {up = 0, down, left, right};
	enum {select = 8, start = 9, square = 3, cross = 2, circle = 1, triangle = 0,
		R1 = 7, R2 = 5, L2 = 4, L1 = 6, L3 = 10, R3 = 11, special = 12,
		up = 13, down = 14, left = 15, right = 16,  // dpad and left stick
		up2 = 17, down2 = 18, left2 = 19, right2 = 20}; // right stick
	const int NUM_BUTTONS = 21;
class Engine{
	static const int NoVideo = 0;
	static const int Video1024x768 = 1;
	static const int Video640x480 = 2;
	static const int Video800x600 = 3;
	static const int Video320x200 = 4;
	static const int Video512x512 = 5;

	static const int VideoFullScreen = 32;


	alias int Direction;
	alias int Buttons;

	static const int MAX_NET = 8;
	static const int NET_PORT = 7777;

	// For being a network server.....
	bool isServer;
	struct NetClient {
		TCPsocket sock;
		int numPlayers;
		int startingPlayer;
		int latency; // in milliseconds

		int state; // Only valid if sock is non null.
				// 0: waiting on initial timestamp
				// 1: waiting on lag response
				// 2: ready for starting
	}
	NetClient[MAX_NET] clients;
	int numberOfClients;

	int maxLatency;


	// For being a network client
	TCPsocket clientsock;

	// All done.

	void beginServing(){
		TCPsocket serversock;

		socketset = SDLNet_AllocSocketSet(MAX_NET+1);
		if(socketset is null)
			throw new Exception("AllocSocketSet");
		scope(failure)
			SDLNet_FreeSocketSet(socketset);

		IPaddress serverIP;

		SDLNet_ResolveHost(&serverIP, null, NET_PORT);
		serversock = SDLNet_TCP_Open(&serverIP);
		if(serversock is null)
			throw new Exception("Server sock");
		scope(exit)
			SDLNet_TCP_Close(serversock);

		if(SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) serversock) < 0)
			throw new Exception("addsocket");
		scope(exit)
			SDLNet_DelSocket(socketset, cast(SDLNet_GenericSocket) serversock);

		writefln("Waiting for players to join the game.\nPress enter when everyone has joined to start the game.");


		uint randomSeed = random.rand();
		srand(randomSeed);
		writefln("TEST: %d", randomNumber(0, 100));


		bool loopingNeeeded = false; // potential FIXME for later
		while(!kbhit() || loopingNeeeded){
			int n = SDLNet_CheckSockets(socketset, 10);
			if(n < 0)
				throw new Exception("Check sockets");
			if(n == 0)
				continue;
			if(SDLNet_SocketReady(serversock)){
				TCPsocket newsock;

				newsock = SDLNet_TCP_Accept(serversock);
				if(newsock is null){
					continue;
				}


				SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) newsock);

				// accept the connection

				writefln("New player:");

				clients[numberOfClients].sock = newsock;
				numberOfClients++;
			}

			// Check the rest of our sockets for data

			for(int a = 0; a < numberOfClients; a++){
				if(SDLNet_SocketReady(clients[a].sock)){
					byte[16] data; // this needs to be EXACTLY the size we are actually going to get.
					if(SDLNet_TCP_Recv(clients[a].sock, data.ptr, 16) <= 0){
						// the connection was closed
						for(int b = a; b < numberOfClients; b++)
							clients[b] = clients[b+1];
						clients[numberOfClients] = NetClient.init;
						numberOfClients--;
						a--;
						continue;
					}

					// And handle the data here.
					switch(clients[a].state){
					  default: assert(0);
					  case 0: // this is the timestamp and stuff
						int ts = SDLNet_Read32(data.ptr);
						clients[a].numPlayers = SDLNet_Read32(data.ptr+4);

						int startingPlayer = numberOfPlayers;
						numberOfPlayers+= clients[a].numPlayers;
						clients[a].startingPlayer = startingPlayer;

						SDLNet_Write32(ts, data.ptr);
						SDLNet_Write32(SDL_GetTicks(), data.ptr+4);
						SDLNet_Write32(randomSeed, data.ptr+8);
						SDLNet_Write32(startingPlayer, data.ptr+12);

						if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) <= 0)
							throw new Exception("TCP send");

						clients[a].state++;
					  break;
					  case 1: // this is telling of the latency

						clients[a].latency = SDLNet_Read32(data.ptr);

						if(clients[a].latency > maxLatency)
							maxLatency = clients[a].latency;

						writefln("Latency: %d", clients[a].latency);

						clients[a].state++;
					  break;
					  case 2:
					  	throw new Exception("unknown data came in");
					}
				}
			}
		}



		isServer = true;
	}

	void connectTo(in char[] whom){
		socketset = SDLNet_AllocSocketSet(1);
		if(socketset is null)
			throw new Exception("AllocSocketSet");
		scope(failure)
			SDLNet_FreeSocketSet(socketset);

		IPaddress ip;

		if(SDLNet_ResolveHost(&ip, (whom~"\0").ptr, NET_PORT) == -1)
			throw new Exception("Resolve host");

		clientsock = SDLNet_TCP_Open(&ip);

		if(clientsock is null)
			throw new Exception("open socket");

		if(SDLNet_AddSocket(socketset, cast(SDLNet_GenericSocket) clientsock) < 0)
			throw new Exception("addsocket");
		scope(failure) SDLNet_DelSocket(socketset, cast(SDLNet_GenericSocket) clientsock);

		byte[16] data;

		int timeStamp = SDL_GetTicks();
		SDLNet_Write32(timeStamp, data.ptr);
		SDLNet_Write32(numberOfLocalPlayers, data.ptr+4);
		if(SDLNet_TCP_Send(clientsock, data.ptr, 16) <= 0)
			throw new Exception("TCP send");

		if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0)
			throw new Exception("TCP recv");

		int receivedTimeStamp = SDLNet_Read32(data.ptr);
		int serverTimeStamp = SDLNet_Read32(data.ptr+4);
		uint randomSeed = SDLNet_Read32(data.ptr+8);
		firstLocalPlayer = SDLNet_Read32(data.ptr+12);
		writefln("First local player = %d", firstLocalPlayer);

		srand(randomSeed);
		writefln("TEST: %d", randomNumber(0, 100));

		int ourLatency = SDL_GetTicks() - receivedTimeStamp;

		SDLNet_Write32(ourLatency, data.ptr);
		SDLNet_Write32(serverTimeStamp, data.ptr+4);

		if(SDLNet_TCP_Send(clientsock, data.ptr, 16) <= 0)
			throw new Exception("TCP send 2");

		waiting = true;
	}

	// This should be called AFTER most initialization, but BEFORE you initialize your players; you don't
	// know the number of players for sure until this call returns.
	void waitOnNetwork(){
		if(!net)
			return;

		if(isServer){

			// Calculate when to start, then send the signal to everyone.
			int desiredLag = cast(int) round(cast(float) maxLatency / msPerTick) + 2;//1;
			lag = desiredLag;
			writefln("Lag = %d", lag);

			for(int a = 0; a < numberOfClients; a++){
				int delay = maxLatency - clients[a].latency;

				byte[16] data;

				// FIXME: We need to send all player data here!

				SDLNet_Write32(desiredLag, data.ptr);
				SDLNet_Write32(delay, data.ptr + 4);
				SDLNet_Write32(numberOfPlayers, data.ptr + 8);


				if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) < 16)
					throw new Exception("Sending failed");
			}

			SDL_Delay(maxLatency); // After waiting for the signal to reach everyone, we can now begin the game!
			return;
		} else {
			// handle the data
			byte[16] data;

			// FIXME: we need to read special per game player data here!

			if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0)
				throw new Exception("Server closed the connection");

			int lagAmount   = SDLNet_Read32(data.ptr);
			int delayAmount = SDLNet_Read32(data.ptr + 4);
			numberOfPlayers = SDLNet_Read32(data.ptr+8);


			lag = lagAmount;
			writefln("Lag = %d", lag);

			SDL_Delay(delayAmount);
			// And finally, we're done, and the game can begin.
			waiting = false;
			return;
		}
	}

	SDLNet_SocketSet socketset;

	int msPerTick;

	int numberOfPlayers;
	int numberOfLocalPlayers;
	int firstLocalPlayer;

  public:
	int getNumberOfPlayers(){ // good for main looping and controls and such
		return numberOfPlayers;
	}

	// returns < 0 if the player is not local
	int getLocalPlayerNumber(int absolutePlayerNumber){ // useful for split screening
		if(absolutePlayerNumber >= firstLocalPlayer && absolutePlayerNumber < firstLocalPlayer + numberOfLocalPlayers)
			return  absolutePlayerNumber - firstLocalPlayer;

		return -1;
	}

	int getNumberOfLocalPlayers(){ // only useful for deciding how to split the screen
		return numberOfLocalPlayers;
	}

	int getFirstLocalPlayerNumber(){
		return firstLocalPlayer;
	}


	this(int graphics = NoVideo, bool sound = false, int timerClick = 0, int numOfLocalPlayers = 1, in char[] network = null){
		bool joystick = true;

		int init = 0;

		numberOfPlayers = numberOfLocalPlayers = numOfLocalPlayers;



		if(graphics)
			init |= SDL_INIT_VIDEO;
		if(timerClick)
			init |= SDL_INIT_TIMER;
		if(sound)
			init |= SDL_INIT_AUDIO;
		if(joystick)
			init |= SDL_INIT_JOYSTICK;

		msPerTick = timerClick;

		if(SDL_Init(init) == -1 ){
			throw new Exception("SDL_Init");
		}
		scope(failure) SDL_Quit();


// SDL_WM_SetIcon(SDL_LoadBMP("icon.bmp"), NULL);


		if(network !is null){
			if(SDLNet_Init() < 0)
				throw new Exception("SDLNet_Init");
			scope(failure) SDLNet_Quit();


			if(network == "SERVER")
				beginServing();
			else
				connectTo(network);

			net = true;
		}

		switch(graphics & ~32){
			case NoVideo:
				screen = null;
			break;
			case Video1024x768:
				screen = new Screen(1024, 768, 24, true, (graphics & 32) ? true : false);
			break;
			case Video640x480:
				screen = new Screen(640, 480);
			break;
			case Video800x600:
				screen = new Screen(800, 600);
			break;
			case Video512x512:
				screen = new Screen(512, 512);
			break;
			case Video320x200:
				screen = new Screen(320, 200);
			break;
			default:
				throw new Exception("Invalid screen type");
		}
		scope(failure) delete screen;

		if(timerClick)
			SDL_AddTimer(timerClick, &tcallback, null);

		if(sound)
			audio = new Audio;
		else
			audio = new Audio(false);

		scope(failure) delete audio;

		if(joystick && SDL_NumJoysticks() > 0){
			SDL_JoystickEventState(SDL_ENABLE);
			for(int a = 0; a < SDL_NumJoysticks(); a++)
				joyStick[a] = SDL_JoystickOpen(a);
		}
		else
			joyStick[0] = null;

		scope(failure){	for(int a = 0; a < 16; a++) if(joyStick[a]) SDL_JoystickClose(joyStick[a]); }


		//SDL_ShowCursor(SDL_DISABLE); // FIXME: make this a call

//***********************************************************************
	// FIXME: it should load controller maps from a config file

		// My playstation controller
		for(int a = 0; a < 13; a++)
			mapJoystickKeyToButton(a, cast(Buttons) a, 0, firstLocalPlayer);

		leftStickXAxis[0] = 0;
		leftStickYAxis[0] = 1;
		dpadXAxis[0] = 4;
		dpadYAxis[0] = 5;
		rightStickXAxis[0] = 2;
		rightStickYAxis[0] = 3;
		leftTriggerAxis[0] = -1;
		rightTriggerAxis[0] = -1;

		// 360 controllers
		for(int b = 1; b < 16; b++){
			mapJoystickKeyToButton(1, circle, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(0, cross, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(4, square, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(3, triangle, b, firstLocalPlayer + b);

			mapJoystickKeyToButton(16, select, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(8, start, b, firstLocalPlayer + b);

			mapJoystickKeyToButton(10, L3, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(11, R3, b, firstLocalPlayer + b);

			mapJoystickKeyToButton(6, L1, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(7, R1, b, firstLocalPlayer + b);

			mapJoystickKeyToButton(9, special, b, firstLocalPlayer + b);

			mapJoystickKeyToButton(15, left, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(12, up, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(13, right, b, firstLocalPlayer + b);
			mapJoystickKeyToButton(14, down, b, firstLocalPlayer + b);

			leftStickXAxis[b] = 0;
			dpadXAxis[b] = -1;
			leftStickYAxis[b] = 1;
			dpadYAxis[b] = -1;
			rightStickXAxis[b] = 4;
			rightStickYAxis[b] = 3;
			leftTriggerAxis[b] = 2;
			rightTriggerAxis[b] = 5;
		}



		// Some sane keyboard defaults

		keyboardMap['s'] = InputMap(circle, firstLocalPlayer);
		keyboardMap['x'] = InputMap(cross, firstLocalPlayer);
		keyboardMap['w'] = InputMap(triangle, firstLocalPlayer);
		keyboardMap['a'] = InputMap(square, firstLocalPlayer);
		keyboardMap[' '] = InputMap(circle, firstLocalPlayer);
		keyboardMap['d'] = InputMap(L1, firstLocalPlayer);
		keyboardMap['f'] = InputMap(R1, firstLocalPlayer);
		keyboardMap['e'] = InputMap(L2, firstLocalPlayer);
		keyboardMap['r'] = InputMap(R2, firstLocalPlayer);
		keyboardMap['c'] = InputMap(L3, firstLocalPlayer);
		keyboardMap['v'] = InputMap(R3, firstLocalPlayer);
		keyboardMap['['] = InputMap(start, firstLocalPlayer);
		keyboardMap[']'] = InputMap(select, firstLocalPlayer);
		keyboardMap['='] = InputMap(special, firstLocalPlayer);

		keyboardMap[SDLK_UP] = InputMap(up, firstLocalPlayer);
		keyboardMap[SDLK_DOWN] = InputMap(down, firstLocalPlayer);
		keyboardMap[SDLK_LEFT] = InputMap(left, firstLocalPlayer);
		keyboardMap[SDLK_RIGHT] = InputMap(right, firstLocalPlayer);

		keyboardMap['k'] = InputMap(up, firstLocalPlayer);
		keyboardMap['j'] = InputMap(down, firstLocalPlayer);
		keyboardMap['h'] = InputMap(left, firstLocalPlayer);
		keyboardMap['l'] = InputMap(right, firstLocalPlayer);



	}

	void moveMouse(Point pos){
		SDL_WarpMouse(cast(ushort) pos.x, cast(ushort) pos.y);

	}

	bool capturedInput = false;

	void captureInput(){
		if(capturedInput)
			return;
		SDL_WM_GrabInput(1);
		capturedInput = true;
	}

	void unCaptureInput(){
		if(!capturedInput)
			return;
		SDL_WM_GrabInput(0);
		capturedInput = false;
	}

	~this(){
		unCaptureInput();
		if(net){
			SDLNet_FreeSocketSet(socketset);
			SDLNet_Quit();
		}
		for(int a = 0; a < 16; a++)
		if(joyStick[a])
			SDL_JoystickClose(joyStick[a]);
		delete audio;
		delete screen;

		foreach(a; objs)
			if(a !is null)
				delete a;
		SDL_Quit();
	}

	void run(){
		eventLoop();
	}

	void setTitle(in char[] title){
		SDL_WM_SetCaption((title~"\0").ptr, null);
	}

	bool buttonWasPressed(Buttons button, int which = 0){
		if(!buttonsChecked[button][which] && buttonsDown[button][which]){
			buttonsChecked[button][which] = true;
			return true;
		}
		return false;
	}

	bool buttonIsDown(Buttons button, int which = 0){
		if(button < NUM_BUTTONS && button >= 0)
			return buttonsDown[button][which];
		return false;
	}

	bool keyWasPressed(int button){
		if(button < 400 && button >= 0)
		if(!keysChecked[button] && keysDown[button]){
			keysChecked[button] = true;
			return true;
		}
		return false;
	}

	bool keyIsDown(int button){
		if(button < 400 && button >= 0)
			return keysDown[button];
		assert(0);
	}

	int getStickX(int stick, int which = 0){
		if( stick >= 0 && stick < 2)
			return stickX[stick][which];
		else
			return 0;
	}

	int getStickY(int stick, int which = 0){
		if( stick >= 0 && stick < 2)
			return stickY[stick][which];
		else
			return 0;
	}

	int getMouseX(){
		return mouseX;
	}

	int getMouseY(){
		return mouseY;
	}

	int getMouseChangeX(){
		int a = mousedx;
		mousedx = 0;
		return a;
	}

	int getMouseChangeY(){
		int a = mousedy;
		mousedy = 0;
		return a;
	}

	bool mouseHasMoved(){
		return (getMouseChangeY != 0 || getMouseChangeX != 0);
	}

	bool mouseButtonWasPressed(int button){
		if(!mouseButtonsChecked[button] && mouseButtonsDown[button]){
			mouseButtonsChecked[button] = true;
			return true;
		}
		return false;
	}

	bool mouseButtonIsDown(int button){
		if(button < 8 && button >= 0)
			return mouseButtonsDown[button];
		return false;
	}

	Point mouseLocation(){
		return XY(mouseX, mouseY);
	}

	void quit(){
		wantToQuit = true;
	}

	bool isAltDown(){
		return (SDL_GetModState() & KMOD_ALT) ? true : false;
	}
	bool isControlDown(){
		return  (SDL_GetModState() & KMOD_CTRL) ? true : false;
	}
	bool isShiftDown(){
		return  (SDL_GetModState() & KMOD_SHIFT) ? true : false;
	}

  protected:
	const int BUTTONDOWN = 0;
	const int BUTTONUP   = 1;
	const int MOTION     = 2;

	void keyEvent(int type, int keyCode, int character, int modifiers){
		defaultKeyEvent(type, keyCode, character, modifiers);
	}

	void defaultKeyEvent(int type, int keyCode, int character, int modifiers){
		if(character == 'q' || keyCode == 'q')
			quit();
		if(type == BUTTONUP && keyCode == SDLK_F3){
			if(capturedInput)
				unCaptureInput();
			else
				captureInput();
		}
	}

	void mouseEvent(int type, int x, int y, int xrel, int yrel, int button, int flags){
		defaultMouseEvent(type, x, y, xrel, yrel, button, flags);
	}

	final void defaultMouseEvent(int type, int x, int y, int xrel, int yrel, int button, int flags){

	}

	void joystickEvent(int type, int whichStick, int button, int state){
		defaultJoystickEvent(type, whichStick, button, state);
	}

	final void defaultJoystickEvent(int type, int whichStick, int button, int state){

	}

	bool quitEvent(){
		return true;
	}

	void timerEvent(){

		// Need to add network receives and the lag timer loops
		if(net)
			getNetworkControllerData();

		// do we actually need to lag here? hmmm.....

		globalTimer++;
		foreach(a; objs){//copy){
			if(a is null)
				continue;
			a.frame();
		}

		if(lag)
			updateControllers();

	}

	public Screen screen;
	public Audio audio;

  private:

	bool net;


	SDL_Joystick*[16] joyStick;
		struct InputMap{
			int button; // or direction
			int which; // which player
		}
	InputMap[int] keyboardMap;
	InputMap[int][16] joystickMap; // joystickMap[which][button] = translated val

	int[16] leftStickXAxis;
	int[16] dpadXAxis;
	int[16] leftStickYAxis;
	int[16] dpadYAxis;
	int[16] rightStickXAxis;
	int[16] rightStickYAxis;
	int[16] leftTriggerAxis;
	int[16] rightTriggerAxis;




	bool[400] keysDown;
	bool[400] keysChecked;

	bool wantToQuit;

	bool[16][NUM_BUTTONS] buttonsDown;
	bool[16][NUM_BUTTONS] buttonsChecked;

	const int LAG_QUEUE_SIZE = 10;
	// This lag is used for network games. It sends you old data until the lag time is up,
	// to try and keep all the players synchronized.
	int[LAG_QUEUE_SIZE][16][NUM_BUTTONS] buttonLagRemaining;

	// This way we can queue up activities happening while the lag is waiting
	int[16][NUM_BUTTONS] buttonLagQueueStart;
	int[16][NUM_BUTTONS] buttonLagQueueEnd;
	int[16][NUM_BUTTONS] buttonLagQueueLength;

	// These store what the state was before the lag began; it is what is returned while
	// waiting on the lag to complete
	bool[LAG_QUEUE_SIZE][16][NUM_BUTTONS] lagbuttonsDown;



	int[16][3] stickX;
	int[16][3] stickY;

	bool[8] mouseButtonsDown;
	bool[8] mouseButtonsChecked;
	const int LEFT = SDL_BUTTON_LEFT;//1;
	const int MIDDLE = SDL_BUTTON_MIDDLE;//2;
	const int RIGHT = SDL_BUTTON_RIGHT;//3;
	const int SCROLL_UP = 4;
	const int SCROLL_DOWN = 5;

	int mouseX;
	int mouseY;
	int mousedx;
	int mousedy;

	bool altDown;
	bool controlDown;
	bool shiftDown;

	void mapJoystickKeyToButton(int a, Buttons b, int whichJoystick, int whichPlayer){
		if(b > NUM_BUTTONS)
			return;
		joystickMap[whichJoystick][a] = InputMap(cast(int) b, whichPlayer);
	}

	/*
		How does this work?

		when and local are the fancy ones.

		Maybe when should always be globalTimer + 1. This way, you have a local wait of 1 frame
		and the remote ones are set to go off one frame later, which gives them time to get down the wire.

		I think that works.

	*/


	uint lag = 0; // should not be > 10 XXX

	void getNetworkControllerData(){
		int type, when, which, button;

		if(!net) return;

			int n = SDLNet_CheckSockets(socketset, 0); // timeout of 1000 might be good too
			if(n < 0)
				throw new Exception("Check sockets");
			if(n == 0)
				return;

			if(isServer){
			for(int a = 0; a < numberOfClients; a++){
				if(SDLNet_SocketReady(clients[a].sock)){
					byte[16] data;
					if(SDLNet_TCP_Recv(clients[a].sock, data.ptr, 16) <= 0){
						throw new Exception("someone closed");
					}

					type = SDLNet_Read32(data.ptr);
					when = SDLNet_Read32(data.ptr+4);
					which = SDLNet_Read32(data.ptr+8);
					button = SDLNet_Read32(data.ptr+12);

					changeButtonState(cast(Buttons) button, type == 0 ? true : false, which, when);

					// don't forget to forward the data to everyone else
					for(int b = 0; b< numberOfClients; b++)
						if(b != a)
						if(SDLNet_TCP_Send(clients[b].sock, data.ptr, 16) < 16)
							throw new Exception("network send failure");

				}
			}
			} else if(SDLNet_SocketReady(clientsock)) {
				byte[16] data;
				if(SDLNet_TCP_Recv(clientsock, data.ptr, 16) <= 0){
					throw new Exception("connection closed");
				}
				type = SDLNet_Read32(data.ptr);
				when = SDLNet_Read32(data.ptr+4);
				which = SDLNet_Read32(data.ptr+8);
				button = SDLNet_Read32(data.ptr+12);

				changeButtonState(cast(Buttons) button, type == 0 ? true : false, which, when);
			}


	}

	void changeButtonState(Buttons button, bool type, int which, uint when, bool sendToNet = false){
	if(when  <= lag)
		return;
		if(when > globalTimer){
			lagbuttonsDown[button][which][buttonLagQueueEnd[button][which]] = type;
			buttonLagRemaining[button][which][buttonLagQueueEnd[button][which]] = when - globalTimer;

			buttonLagQueueLength[button][which]++;
			buttonLagQueueEnd[button][which]++;
			if(buttonLagQueueEnd[button][which] == LAG_QUEUE_SIZE)
				buttonLagQueueEnd[button][which] = 0;
		} else {
			if(when < globalTimer)
				throw new Exception(immutableString("Impossible control timing"));//  " ~ convToString(when) ~ " @ " ~ convToString(globalTimer)));
			buttonsDown[button][which] = type;
			buttonsChecked[button][which] = false;
		}

		if(net && sendToNet){
			byte[16] data;
			SDLNet_Write32(type ? 0 : 1, data.ptr);
			SDLNet_Write32(when, data.ptr+4);
			SDLNet_Write32(which, data.ptr+8);
			SDLNet_Write32(button, data.ptr+12);
			if(isServer){
				for(int a = 0; a< numberOfClients; a++)
					if(SDLNet_TCP_Send(clients[a].sock, data.ptr, 16) < 16)
						throw new Exception("network send failure");

			} else {
				if(SDLNet_TCP_Send(clientsock, data.ptr, 16) < 16)
					throw new Exception("network send failure");
			}
		}
	}

	void updateControllers(){
		for(int a = 0; a < 16; a++){ // FIXME: should be changed to number of players
			for(int b = 0; b < NUM_BUTTONS; b++)
				for(int co = 0, q = buttonLagQueueStart[b][a]; co < buttonLagQueueLength[b][a]; q++, co++){
				if(q == LAG_QUEUE_SIZE)
					q = 0;
				if(buttonLagRemaining[b][a][q]){
					buttonLagRemaining[b][a][q]--;
					if(!buttonLagRemaining[b][a][q]){
						changeButtonState(cast(Buttons) b, lagbuttonsDown[b][a][q], a, globalTimer);
						buttonLagQueueStart[b][a]++;
						buttonLagQueueLength[b][a]--;
						if(buttonLagQueueStart[b][a] == LAG_QUEUE_SIZE)
							buttonLagQueueStart[b][a] = 0;
					}
				}
				}
		}
	}

	int eventLoop(){
		SDL_Event event;
	  while(SDL_WaitEvent(&event) >= 0 && !wantToQuit){
	    switch(event.type){
		case SDL_KEYUP:
		case SDL_KEYDOWN:
			bool type = event.key.type == SDL_KEYDOWN ? true : false;
				if(event.key.keysym.sym in keyboardMap){
					int button = keyboardMap[event.key.keysym.sym].button;
					int which = keyboardMap[event.key.keysym.sym].which;
					changeButtonState(cast(Buttons) button, type, which, globalTimer + lag, true);
				}

			if(event.key.keysym.sym < 400){
				keysDown[event.key.keysym.sym] = (event.key.type == SDL_KEYDOWN) ? true : false;
				keysChecked[event.key.keysym.sym] = false;
			}


			keyEvent(
				event.key.type == SDL_KEYDOWN ? BUTTONDOWN : BUTTONUP,
				event.key.keysym.sym,
				event.key.keysym.unicode,
				event.key.keysym.mod
			);
		break;
		case SDL_JOYAXISMOTION:
		// the things here are to avoid little changes around the center if the stick isn't perfect
		if ( ( event.jaxis.value < -3200 ) || (event.jaxis.value > 3200 ) || event.jaxis.value == 0){
			int stick;
			if(event.jaxis.axis >= 0 && event.jaxis.axis < 6)

				stick = event.jaxis.axis;
			else
				break;

			int which = event.jaxis.which;
			if(stick == leftStickXAxis[which] || stick == dpadXAxis[which]){
				changeButtonState(left, event.jaxis.value < -28000, which, globalTimer + lag, true);
				changeButtonState(right, event.jaxis.value > 28000, which, globalTimer + lag, true);

				stickX[0][which] = event.jaxis.value;
			}
			if(stick == leftStickYAxis[which] || stick == dpadYAxis[which]){
				changeButtonState(up, event.jaxis.value < -28000, which, globalTimer + lag, true);
				changeButtonState(down, event.jaxis.value > 28000, which, globalTimer + lag, true);

				stickY[0][which] = event.jaxis.value;
			}
			if(stick == rightStickXAxis[which]){
				stickX[1][which] = event.jaxis.value;
				changeButtonState(left2, event.jaxis.value < -28000, which, globalTimer + lag, true);
				changeButtonState(right2, event.jaxis.value > 28000, which, globalTimer + lag, true);
			}
			if(stick == rightStickYAxis[which]){
				stickY[1][which] = event.jaxis.value;
				changeButtonState(up2, event.jaxis.value < -28000, which, globalTimer + lag, true);
				changeButtonState(down2, event.jaxis.value > 28000, which, globalTimer + lag, true);
			}
			// x-box 360 controller stuff
			if(stick == leftTriggerAxis[which]){
				stickX[2][which] = event.jaxis.value;

				changeButtonState(L2, event.jaxis.value > 32000, which, globalTimer + lag, true);
			}
			if(stick == rightTriggerAxis[which]){
				stickY[2][which] = event.jaxis.value;
				changeButtonState(R2, event.jaxis.value > 32000, which, globalTimer + lag, true);
			}


			joystickEvent(
				MOTION,
				event.jaxis.which,
				event.jaxis.axis,
				event.jaxis.value
			);
		}
		break;
		case SDL_JOYHATMOTION:
		/+
			joystickEvent(
				HATMOTION,
				event.jhat.which,
				event.jhat.hat,
				event.jhat.value
			);
		+/
		break;
		case SDL_JOYBUTTONDOWN:
		case SDL_JOYBUTTONUP:
			if(event.jbutton.button in joystickMap[event.jbutton.which]){
				int which = joystickMap[event.jbutton.which][event.jbutton.button].which;
				int button = joystickMap[event.jbutton.which][event.jbutton.button].button;

				changeButtonState(cast(Buttons) button, event.jbutton.type == SDL_JOYBUTTONDOWN ? true : false, which, globalTimer + lag, true);
			}

			joystickEvent(
				event.jbutton.type == SDL_JOYBUTTONDOWN ? BUTTONDOWN : BUTTONUP,
				event.jbutton.which,
				event.jbutton.button,
				event.jbutton.state
			);
		break;

		case SDL_MOUSEBUTTONDOWN:
		case SDL_MOUSEBUTTONUP:
			mouseButtonsDown[event.button.button] = event.button.type == SDL_MOUSEBUTTONDOWN ? true : false;
			mouseButtonsChecked[event.button.button] = false;
			mouseEvent(
				event.button.type == SDL_MOUSEBUTTONDOWN ? BUTTONDOWN : BUTTONUP,
				event.button.x,
				event.button.y,
				0,		// xrel
				0,		// yrel
				event.button.button,
				0		//state
			);
		break;
		case SDL_MOUSEMOTION:
			mouseX = event.motion.x;
			mouseY = event.motion.y;
			mousedx += event.motion.xrel;
			mousedy += event.motion.yrel;

			mouseEvent(
				MOTION,
				event.motion.x,
				event.motion.y,
				event.motion.xrel,
				event.motion.yrel,
				0,
				event.motion.state
			);
		break;

		case SDL_USEREVENT:
			timerEvent();
		break;
		case SDL_QUIT:
			if(quitEvent() == true)
				quit();
		break;
		default:
	    }
	  }
		return 0;
	}

}
extern(C){
	Uint32 tcallback(Uint32 interval, void* param){
		if(waiting)
			return interval;
		SDL_Event event;

		event.type = SDL_USEREVENT;
		event.user.code = 0;
		event.user.data1 = null;
		event.user.data2 = null;
		SDL_PushEvent(&event);

		return interval;
	}
}

int SDLNet_SocketReady(void* sock) {
        SDLNet_GenericSocket s = cast(SDLNet_GenericSocket)sock;
        return sock != cast(TCPsocket)0 && s.ready;
}



Engine engine;

bool buttonWasPressed(Engine.Buttons button, int which = 0){
	return engine.buttonWasPressed(button, which);
}

bool buttonIsDown(Engine.Buttons button, int which = 0){
	return engine.buttonIsDown(button, which);
}
/*
bool directionWasPressed(Engine.Direction direction, int which = 0){
	return engine.directionWasPressed(direction, which);
}

bool directionIsDown(Engine.Direction d, int which = 0){
	return engine.directionIsDown(d, which);
}
*/



version(linux) {
	version(D_Version2) {
		import sys = core.sys.posix.sys.select;
		version=CustomKbhit;

		int kbhit()
		{
			sys.timeval tv;
			sys.fd_set read_fd;

			tv.tv_sec=0;
			tv.tv_usec=0;
			sys.FD_ZERO(&read_fd);
			sys.FD_SET(0,&read_fd);

			if(sys.select(1, &read_fd, null, null, &tv) == -1)
				return 0;

			if(sys.FD_ISSET(0,&read_fd))
				return 1;

			return 0;
		}
	}

	// else, use kbhit.o from the C file
}

version(CustomKbhit) {} else
	extern(C) bool kbhit();