#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <SDL.h>

#include "Keyboard.h"

static Uint8 _keys_b_n_m_ss_sp = 0xFF;
static Uint8 _keys_h_j_k_l_en = 0xFF;
static Uint8 _keys_y_u_i_o_p = 0xFF;
static Uint8 _keys_6_7_8_9_0 = 0xFF;
static Uint8 _keys_5_4_3_2_1 = 0xFF;
static Uint8 _keys_t_r_e_w_q = 0xFF;
static Uint8 _keys_g_f_d_s_a = 0xFF;
static Uint8 _keys_v_c_x_z_cs = 0xFF;
static Uint8 _bad_input = 0xFF;

struct optToSDLKeysym {
	const char* commandLineOption;
	SDL_Keycode SDLKeysym;
};

#define NUM_SUPPORTED_COMMAND_LINE_OPTIONS 40
static struct optToSDLKeysym _commandLineOptionsToSDLKeysym[NUM_SUPPORTED_COMMAND_LINE_OPTIONS] = {
	{"p", SDLK_p},
	{"o", SDLK_o},
	{"i", SDLK_i},
	{"u", SDLK_u},
	{"y", SDLK_y},

	{"enter", SDLK_RETURN},
	{"l", SDLK_l},
	{"k", SDLK_k},
	{"j", SDLK_j},
	{"h", SDLK_h},

	{"space", SDLK_SPACE},
	{"symshift", SDLK_RSHIFT},
	{"m", SDLK_m},
	{"n", SDLK_n},
	{"b", SDLK_b},

	{"0", SDLK_0},
	{"9", SDLK_9},
	{"8", SDLK_8},
	{"7", SDLK_7},
	{"6", SDLK_6},

	{"1", SDLK_1},
	{"2", SDLK_2},
	{"3", SDLK_3},
	{"4", SDLK_4},
	{"5", SDLK_5},

	{"q", SDLK_q},
	{"w", SDLK_w},
	{"e", SDLK_e},
	{"r", SDLK_r},
	{"t", SDLK_t},

	{"a", SDLK_a},
	{"s", SDLK_s},
	{"d", SDLK_d},
	{"f", SDLK_f},
	{"g", SDLK_g},

	{"capsshift", SDLK_LSHIFT},
	{"z", SDLK_z},
	{"x", SDLK_x},
	{"c", SDLK_c},
	{"v", SDLK_v},
};

/// <summary>
/// Resolves SDL Keysim keycode from a command line option
/// </summary>
/// <param name="key">command line option</param>
/// <returns>NULL when not found</returns>
SDL_Keycode* _getSDLKeysymForCommandLineOption(const char* key) {
	for (int i = 0; i < NUM_SUPPORTED_COMMAND_LINE_OPTIONS; i++) {
		if (!strcmp(_commandLineOptionsToSDLKeysym[i].commandLineOption, key)) {
			return &_commandLineOptionsToSDLKeysym[i].SDLKeysym;
		}
	}

	return NULL;
}

Uint8* _keyboard_get_storage_ptr(SDL_Keycode keyCode) {
	switch (keyCode) {
	case SDLK_p: case SDLK_o: case SDLK_i: case SDLK_u: case SDLK_y: return &_keys_y_u_i_o_p;
	case SDLK_RETURN: case SDLK_l: case SDLK_k: case SDLK_j: case SDLK_h: return &_keys_h_j_k_l_en;
	case SDLK_SPACE: case SDLK_RSHIFT: case SDLK_m: case SDLK_n: case SDLK_b: return &_keys_b_n_m_ss_sp;
	case SDLK_0: case SDLK_9: case SDLK_8: case SDLK_7: case SDLK_6: return &_keys_6_7_8_9_0;

	case SDLK_1: case SDLK_2: case SDLK_3: case SDLK_4: case SDLK_5: return &_keys_5_4_3_2_1;
	case SDLK_q: case SDLK_w: case SDLK_e: case SDLK_r: case SDLK_t: return &_keys_t_r_e_w_q;
	case SDLK_a: case SDLK_s: case SDLK_d: case SDLK_f: case SDLK_g: return &_keys_g_f_d_s_a;
	case SDLK_LSHIFT: case SDLK_z: case SDLK_x: case SDLK_c: case SDLK_v: return &_keys_v_c_x_z_cs;

	default: return &_bad_input;
	}
}

Uint8 _keyboard_get_spectrum_key(SDL_Keycode keyCode) {
	switch (keyCode) {
	case SDLK_p: return KEY_P;
	case SDLK_o: return KEY_O;
	case SDLK_i: return KEY_I;
	case SDLK_u: return KEY_U;
	case SDLK_y: return KEY_Y;

	case SDLK_RETURN: return KEY_ENTER;
	case SDLK_l: return KEY_L;
	case SDLK_k: return KEY_K;
	case SDLK_j: return KEY_J;
	case SDLK_h: return KEY_H;

	case SDLK_SPACE: return KEY_SPACE;
	case SDLK_RSHIFT: return KEY_SYMBOL_SHIFT;
	case SDLK_m: return KEY_M;
	case SDLK_n: return KEY_N;
	case SDLK_b: return KEY_B;

	case SDLK_0: return KEY_0;
	case SDLK_9: return KEY_9;
	case SDLK_8: return KEY_8;
	case SDLK_7: return KEY_7;
	case SDLK_6: return KEY_6;

	case SDLK_1: return KEY_1;
	case SDLK_2: return KEY_2;
	case SDLK_3: return KEY_3;
	case SDLK_4: return KEY_4;
	case SDLK_5: return KEY_5;

	case SDLK_q: return KEY_Q;
	case SDLK_w: return KEY_W;
	case SDLK_e: return KEY_E;
	case SDLK_r: return KEY_R;
	case SDLK_t: return KEY_T;

	case SDLK_a: return KEY_A;
	case SDLK_s: return KEY_S;
	case SDLK_d: return KEY_D;
	case SDLK_f: return KEY_F;
	case SDLK_g: return KEY_G;

	case SDLK_LSHIFT: return KEY_CAPS_SHIFT;
	case SDLK_z: return KEY_Z;
	case SDLK_x: return KEY_X;
	case SDLK_c: return KEY_C;
	case SDLK_v: return KEY_V;

	default: return 0;
	}
}

void keyboard_start() {

}

void keyboard_destroy() {

}

Uint8 keyboard_read8(Uint16 address) {
	Uint8 msb = address >> 8;
	Uint8 result = 0xFF;
	
	if (!(msb & (1 << 0))) result &= _keys_v_c_x_z_cs;
	if (!(msb & (1 << 1))) result &= _keys_g_f_d_s_a;
	if (!(msb & (1 << 2))) result &= _keys_t_r_e_w_q;
	if (!(msb & (1 << 3))) result &= _keys_5_4_3_2_1;
	if (!(msb & (1 << 4))) result &= _keys_6_7_8_9_0;
	if (!(msb & (1 << 5))) result &= _keys_y_u_i_o_p;
	if (!(msb & (1 << 6))) result &= _keys_h_j_k_l_en;
	if (!(msb & (1 << 7))) result &= _keys_b_n_m_ss_sp;
	
	return result;
}

Uint8 keyboard_is_supported_address(Uint16 address) {
	Uint8 lsb = (Uint8)(address & 0x00FF);
	switch (lsb) {
	case 0xFE:
		return 1;
	default:
		return 0;
	}
}

void keyboard_keyup_by_code(SDL_Keycode key)
{
	Uint8* keyChunk = _keyboard_get_storage_ptr(key);
	Uint8 spectrumKey = _keyboard_get_spectrum_key(key);

	*keyChunk |= spectrumKey;
}

void keyboard_keydown_by_code(SDL_Keycode key)
{
	Uint8* keyChunk = _keyboard_get_storage_ptr(key);
	Uint8 spectrumKey = _keyboard_get_spectrum_key(key);

	*keyChunk &= ~spectrumKey;
}

void keyboard_keyup(SDL_Keysym key) {
	keyboard_keyup_by_code(key.sym);
}

void keyboard_keydown(SDL_Keysym key) {
	keyboard_keydown_by_code(key.sym);
}

Uint8 keyboard_try_resolve_keysym_from_override(const char* override, SDL_Keycode* sdlKeysymOUT)
{
	SDL_Keycode* temp;
	temp = _getSDLKeysymForCommandLineOption(override);
	if (temp == NULL) {
		return 0;
	}

	*sdlKeysymOUT = *temp;
	return 1;
}
