#include <array>
#include "nx.h"
#include "input.h"
#include "console.h"
#include "game.h"
#include "sound/sound.h"
#include "common/stat.h"
#include "settings.h"
#include "intro/title.h"

#include "Networking.h" //for checking chat in the key stuff
#include "NetPlayer.h"
#include "chat.h"

in_action mappings[INPUT_COUNT];

bool inputs[INPUT_COUNT];
bool lastinputs[INPUT_COUNT];
in_action last_sdl_action;
SDL_Joystick *joy;
SDL_Haptic *haptic;

bool input_init(void)
{
	memset(inputs, 0, sizeof(inputs));
	memset(lastinputs, 0, sizeof(lastinputs));
	memset(mappings, -1, sizeof(mappings));
	for (int i=0;i<INPUT_COUNT;i++)
	{
	    mappings[i].key=-1;
	    mappings[i].jbut=-1;
	    mappings[i].jhat=-1;
	    mappings[i].jaxis=-1;
	}
	
	// default mappings
	mappings[LEFTKEY].key      = SDLK_LEFT;
	mappings[RIGHTKEY].key     = SDLK_RIGHT;
	mappings[UPKEY].key        = SDLK_UP;
	mappings[DOWNKEY].key      = SDLK_DOWN;
	mappings[JUMPKEY].key      = SDLK_z;
	mappings[FIREKEY].key      = SDLK_x;
	mappings[PREVWPNKEY].key   = SDLK_a;
	mappings[NEXTWPNKEY].key   = SDLK_s;
	mappings[INVENTORYKEY].key = SDLK_q;
	mappings[MAPSYSTEMKEY].key = SDLK_w;
	
	mappings[ESCKEY].key = SDLK_ESCAPE;
	
	mappings[F1KEY].key  = SDLK_F1;
	mappings[F2KEY].key  = SDLK_F2;
	mappings[F3KEY].key  = SDLK_F3;
	mappings[F4KEY].key  = SDLK_F4;
	mappings[F5KEY].key  = SDLK_F5;
	mappings[F6KEY].key  = SDLK_F6;
	mappings[F7KEY].key  = SDLK_F7;
	mappings[F8KEY].key  = SDLK_F8;
	mappings[F9KEY].key  = SDLK_F9;
	mappings[F10KEY].key = SDLK_F10;
	mappings[F11KEY].key = SDLK_F11;
	mappings[F12KEY].key = SDLK_F12;
#if defined(DEBUG)
	mappings[FREEZE_FRAME_KEY].key  = SDLK_SPACE;
	mappings[FRAME_ADVANCE_KEY].key = SDLK_c;
	mappings[DEBUG_FLY_KEY].key     = SDLK_v;
#endif
	mappings[ENTERKEY].key = SDLK_RETURN;
	
	SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC);
	if (SDL_NumJoysticks() > 0) {
	    // Open joystick
	    joy = SDL_JoystickOpen(0);
	
	    if (joy) {
	        stat("Opened Joystick 0");
	        stat("Name: %s", SDL_JoystickNameForIndex(0));
	        stat("Number of Axes: %d", SDL_JoystickNumAxes(joy));
	        stat("Number of Buttons: %d", SDL_JoystickNumButtons(joy));
	        stat("Number of Balls: %d", SDL_JoystickNumBalls(joy));
	        haptic = SDL_HapticOpenFromJoystick( joy );
	        if (haptic == NULL)
	        {
	            stat("No force feedback support");
	        }
	        else
	        {
	            if (SDL_HapticRumbleInit( haptic ) != 0)
	                stat("Coiuldn't init simple rumble");
	        }
	    } else {
	        stat("Couldn't open Joystick 0");
	    }
	                                                                    
	}
	return 0;
}

void rumble(float str, uint32_t len)
{
    if (haptic != NULL && settings->rumble)
        SDL_HapticRumblePlay(haptic, str, len);
}


// set the SDL key that triggers an input
void input_remap(int keyindex, in_action sdl_key)
{
	stat("input_remap(%d => %d)", keyindex, sdl_key.key);
//	in_action old_mapping = input_get_mapping(keyindex);
//	if (old_mapping != -1)
//		mappings[old_mapping] = 0xff;
	
	mappings[keyindex] = sdl_key;
}

// get which SDL key triggers a given input
in_action input_get_mapping(int keyindex)
{
  return mappings[keyindex];
}

int input_get_action(int32_t sdlkey)
{
  for (int i=0;i<INPUT_COUNT;i++)
  {
    if(mappings[i].key == sdlkey)
    {
      return i;
    }
  }
  return -1;
}

int input_get_action_but(int32_t jbut)
{
  for (int i=0;i<INPUT_COUNT;i++)
  {
    if(mappings[i].jbut == jbut)
    {
      return i;
    }
  }
  return -1;
}

int input_get_action_hat(int32_t jhat, int32_t jvalue)
{
  for (int i=0;i<INPUT_COUNT;i++)
  {
    if((mappings[i].jhat == jhat) && (jvalue & mappings[i].jhat_value))
    {
      return i;
    }
  }
  return -1;
}

int input_get_action_axis(int32_t jaxis, int32_t jvalue)
{
  for (int i=0;i<INPUT_COUNT;i++)
  {
    if( (mappings[i].jaxis == jaxis) && ( (jvalue > 0 && mappings[i].jaxis_value > 0) || (jvalue < 0 && mappings[i].jaxis_value < 0) ))
    {
      return i;
    }
  }
  return -1;
}

const std::string input_get_name(int index)
{
static std::array<std::string, 27> input_names =
{
	"Left", "Right", "Up", "Down",
	"Jump", "Fire", "Wpn Prev", "Wpn Next",
	"Inventory", "Map",
	"Pause",
	"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12",
	"freeze frame", "frame advance", "debug fly", "Enter"
};

	if (index < 0 || index >= INPUT_COUNT)
		return "invalid";
	
	return input_names[index];
}

void input_set_mappings(in_action *array)
{
	memset(mappings, 0xff, sizeof(mappings));
	for(int i=0;i<INPUT_COUNT;i++)
		mappings[i] = array[i];
}

/*
void c------------------------------() {}
*/

// keys that we don't want to send to the console
// even if the console is up.
static int IsNonConsoleKey(int key)
{
static const int nosend[] = { SDLK_LEFT, SDLK_RIGHT, 0 };

	for(int i=0;nosend[i];i++)
		if (key == nosend[i])
			return true;
	
	return false;
}

void input_poll(void)
{
SDL_Event evt;
int32_t key;
int ino;//, key;
	
	while(SDL_PollEvent(&evt))
	{
		switch(evt.type)
		{
			case SDL_KEYDOWN:
			case SDL_KEYUP:
			{
				key = evt.key.keysym.sym;
				
				static uint8_t shiftstates = 0;

				if (key == SDLK_LSHIFT)
				{
					if (evt.type == SDL_KEYDOWN)
						shiftstates |= LEFTMASK;
					else
						shiftstates &= ~LEFTMASK;
				}
				else if (key == SDLK_RSHIFT)
				{
					if (evt.type == SDL_KEYDOWN)
						shiftstates |= RIGHTMASK;
					else
						shiftstates &= ~RIGHTMASK;
				}
				
				if ((console.IsVisible() && !IsNonConsoleKey(key)) || (chatstate.typing == 1))
				{

					if (key != SDLK_RSHIFT && key != SDLK_LSHIFT)
					{
						int ch = key;
						if (shiftstates != 0)
						{
							ch = toupper(ch);
							if (ch == '.') ch = '>';
							if (ch == '-') ch = '_';
							if (ch == '/') ch = '?';
							if (ch == '1') ch = '!';
						}
						
						if (host == -1) {
							if (evt.type == SDL_KEYDOWN)
								console.HandleKey(ch);
							else
								console.HandleKeyRelease(ch);
						}
						else if (evt.type == SDL_KEYDOWN) {
							Chat_AddChar(ch);
						}
					}
				}
				else
				{
					ino = input_get_action(key);//mappings[key];
					
					if (ino != -1)
						inputs[ino] = (evt.type == SDL_KEYDOWN);
					
					if (evt.type == SDL_KEYDOWN)
					{
						if (host != -1 && !chatstate.typing && key == 116) { //press t to chat
							chatstate.typing = 1;
						}
						// Special case: multiplayer IP input menu
						if (Multiplayer == 2 && ((key >= 48 && key <= 57) || key == 46) && strlen(IPAddress) < 128) {
							char *tmp = (char*)&key;
							strcat(IPAddress, tmp);
						}
						if (Multiplayer == 2 && key == 8 && strlen(IPAddress) > 0) {
							// backspace in IP menu
							IPAddress[strlen(IPAddress) - 1] = 0;
						}
						// and names
						if (Multiplayer == 4 && key != 8 && strlen(name) < 14 && (key >= 32 && key <= 126) && key != SDLK_LCTRL && key != SDLK_RCTRL && key != SDLK_LALT && key != SDLK_RALT && key != SDLK_LSHIFT && key != SDLK_RSHIFT && key != SDLK_DOWN && key != SDLK_UP && key != SDLK_LEFT && key != SDLK_RIGHT && key != SDLK_RETURN) {
							char *tmp = (char*)&key;
							if (shiftstates != 0)
							{
								key = toupper(key);
								if (key == '.') key = '>';
								if (key == '-') key = '_';
								if (key == '/') key = '?';
								if (key == '1') key = '!';
							}
							strcat(name, tmp);
						}
						if (Multiplayer == 4 && key == 8 && strlen(name) > 0) {
							// backspace in IP menu
							name[strlen(name)-1] = 0;
						}
						if (key == '`')		// bring up console
						{
#if defined(DEBUG)
							extern bool freezeframe;
							if (!freezeframe)
							{
								sound(SND_SWITCH_WEAPON);
								console.SetVisible(true);
							}
#endif
						}
						else
						{
							last_sdl_action.key = key;
						}
					}
				}
			}
			break;
			
			case SDL_QUIT:
			{
				inputs[ESCKEY] = true;
				game.running = false;
			}
			break;
			
			case SDL_JOYBUTTONDOWN:
			{
			    Uint8 but = evt.jbutton.button;
			    last_sdl_action.jbut = but;
			    ino = input_get_action_but(but);//mappings[key];
				if (ino != -1)
					inputs[ino] = (evt.jbutton.state == SDL_PRESSED);
			}
			break;

			case SDL_JOYBUTTONUP:
			{
			    Uint8 but = evt.jbutton.button;
			    ino = input_get_action_but(but);//mappings[key];
				if (ino != -1)
					inputs[ino] = (evt.jbutton.state == SDL_PRESSED);
			}
			break;
			
			case SDL_JOYHATMOTION:
			{
			    if (evt.jhat.value != SDL_HAT_CENTERED)
			    {
			        last_sdl_action.jhat = evt.jhat.hat;
			        last_sdl_action.jhat_value = evt.jhat.value;
			    }
			    ino = input_get_action_hat(evt.jhat.hat,evt.jhat.value);//mappings[key];
			    //cleanup all hat-binded states
			    for (int i=0;i<INPUT_COUNT;i++)
			    {
			        if (mappings[i].jhat!=-1)
			            inputs[i] = false;
			    }
			
				if (ino != -1)
					inputs[ino] = true;
			}
			break;

			case SDL_JOYAXISMOTION:
			{
			    // this only used for control remapping.
			    // for actual in-game handling see below.
			    if (evt.jaxis.value > 20000 || evt.jaxis.value < -20000) //dead zone
			    {
			        last_sdl_action.jaxis = evt.jaxis.axis;
			        last_sdl_action.jaxis_value = evt.jaxis.value;
			    }
			}
			break;

		}
	}
	
	// handle gamepad sticks
	
	for (int i=0;i<INPUT_COUNT;i++)
	{
		if( mappings[i].jaxis >= 0 ) // reset all mapped axises
		{
			inputs[i] = false;
		}
	}
	
	// now get current values of all axises
	for (int ax=0;ax<SDL_JoystickNumAxes(joy);ax++)
	{
		int value = SDL_JoystickGetAxis(joy, ax);
		ino = input_get_action_axis(ax, value); // this returns actual mapping for axis direction
		
		if (ino != -1 && (value > 20000 || value < -20000))
			inputs[ino] = true;
	}
}



void input_close(void)
{
    // Close if opened
    if (SDL_JoystickGetAttached(joy)) {
        SDL_JoystickClose(joy);
    }
}

/*
void c------------------------------() {}
*/

static const int buttons[] = { JUMPKEY, FIREKEY, 0 };

bool buttondown(void)
{
	for(int i=0;buttons[i];i++)
	{
		if (inputs[buttons[i]])
			return 1;
	}
	
	return 0;
}

bool buttonjustpushed(void)
{
	for(int i=0;buttons[i];i++)
	{
		if (inputs[buttons[i]] && !lastinputs[buttons[i]])
			return 1;
	}
	
	return 0;
}

bool justpushed(int k)
{
	return (inputs[k] && !lastinputs[k]);
}





