/*
   client/keyboard.cc
   This file is part of the Osirion project and is distributed under
   the terms and conditions of the GNU General Public License version 2
*/

#include <iostream>
#include <iomanip>
#include <fstream>

#include "auxiliary/functions.h"
#include "client/keyboard.h"
#include "core/application.h"
#include "core/commandbuffer.h"
#include "filesystem/filesystem.h"
#include "sys/sys.h"

namespace client
{

/*
Notes:
http://docs.mandragor.org/files/Common_libs_documentation/SDL/SDL_Documentation_project_en/sdlkey.html
*/

Keyboard::Keyboard()
{
	numlock = false;
	capslock = false;

	// ------------------ ACTIONS

	// FIXME actions should be state keys and not use key repeat
	add_action("+console", Action::Console, "console key");

	add_action("+left", Action::Left, "rotate left");
	add_action("+right", Action::Right, "rotate right");
	add_action("+up", Action::Up, "rotate up");
	add_action("+down", Action::Down, "rotate down");

	add_action("+rollleft", Action::RollLeft, "roll left");
	add_action("+rollright", Action::RollRight, "roll right");

	add_action("+camleft", Action::CamLeft, "rotate camera left");
	add_action("+camright", Action::CamRight, "rotate camera right");
	add_action("+camup", Action::CamUp, "rotate camera up");
	add_action("+camdown", Action::CamDown, "rotate camera down");

	add_action("+zoomin", Action::ZoomIn, "zoom camera in");
	add_action("+zoomout", Action::ZoomOut, "zoom camera out");

	add_action("+thrustup", Action::ThrustUp, "increase thruster");
	add_action("+thrustdown", Action::ThrustDown, "decrease thruster");

	add_action("+strafeleft", Action::StrafeLeft, "strafe left");
	add_action("+straferight", Action::StrafeRight, "strafe right");
	add_action("+strafeup", Action::StrafeUp, "strafe up");
	add_action("+strafedown", Action::StrafeDown, "strafe down");

	add_action("+afterburner", Action::Afterburner, "afterburner");
	add_action("+reverse", Action::Reverse, "reverse engine");

	add_action("+control", Action::Control, "enable mouse control while pressed");

	// ------------------ KEYS
	Key *key = 0;

	add_key("backspace", SDLK_BACKSPACE);
	add_key("tab", SDLK_TAB, 0, "impulse");
	add_key("clear", SDLK_CLEAR);
	key = add_key("enter", SDLK_RETURN, 0, "ui_chat");
	key->assign(Key::Alt, "toggle r_fullscreen");
	add_key("pause", SDLK_PAUSE);
	add_key("esc", SDLK_ESCAPE);
	add_key("space", SDLK_SPACE, ' ', "ui_control");
	add_key("!", SDLK_EXCLAIM, '!');
	add_key("\"", SDLK_QUOTEDBL, '"');
	add_key("#", SDLK_HASH, '#');
	add_key("$", SDLK_DOLLAR, '$');
	add_key("&", SDLK_AMPERSAND, '&');
	add_key("'", SDLK_QUOTE, '\'');
	add_key("(", SDLK_LEFTPAREN, '(');
	add_key(")", SDLK_RIGHTPAREN, ')');
	add_key("*", SDLK_ASTERISK, '*');
	add_key("+", SDLK_PLUS, '+');
	add_key(",", SDLK_COMMA, ',');
	add_key("-", SDLK_MINUS, '-');
	add_key(".", SDLK_PERIOD, '.');
	add_key("/", SDLK_SLASH, '/');

	add_key("0", SDLK_0, '0');
	add_key("1", SDLK_1, '1');
	add_key("2", SDLK_2, '2');
	add_key("3", SDLK_3, '3');
	add_key("4", SDLK_4, '4');
	add_key("5", SDLK_5, '5');
	add_key("6", SDLK_6, '6');
	add_key("7", SDLK_7, '7');
	add_key("8", SDLK_8, '8');
	add_key("9", SDLK_9, '9');

	add_key(":", SDLK_COLON, ':');
	add_key(";", SDLK_SEMICOLON, ';');
	add_key("<", SDLK_LESS, '<');
	add_key("=", SDLK_EQUALS, '=');
	add_key(">", SDLK_GREATER, '>');
	add_key("?", SDLK_QUESTION, '?');
	add_key("@", SDLK_AT, '@');
	add_key("[", SDLK_LEFTBRACKET, '[');
	add_key("\\", SDLK_BACKSLASH, '\\');
	add_key("]", SDLK_RIGHTBRACKET, ']');
	add_key("^", SDLK_CARET, '^');
	add_key("_", SDLK_UNDERSCORE, '_');
	add_key("`", SDLK_BACKQUOTE, '`', "+console");

	add_key("a", SDLK_a, 'a', "+strafeleft");
	add_key("b", SDLK_b, 'b', "beam");
	add_key("c", SDLK_c, 'c');
	add_key("d", SDLK_d, 'd', "+straferight");
	add_key("e", SDLK_e, 'e', "+rollright");
	add_key("f", SDLK_f, 'f', "+strafedown");
	add_key("g", SDLK_g, 'g');
	add_key("h", SDLK_h, 'h');
	add_key("i", SDLK_i, 'i');
	add_key("j", SDLK_j, 'j');
	add_key("k", SDLK_k, 'k');
	add_key("l", SDLK_l, 'l');
	add_key("m", SDLK_m, 'm');
	key = add_key("n", SDLK_n, 'n', "target_next");
	key->assign(Key::Shift, "target_prev");
	key->assign(Key::Ctrl, "target_none");
	add_key("o", SDLK_o, 'o');
	add_key("p", SDLK_p, 'p');
	add_key("q", SDLK_q, 'q', "+rollleft");
	add_key("r", SDLK_r, 'r', "+strafeup");
	add_key("s", SDLK_s, 's', "+reverse");
	add_key("t", SDLK_t, 't', "ui_chatbar");
	add_key("u", SDLK_u, 'u');
	key = add_key("v", SDLK_v, 'v', "view_next");
	key->assign(Key::Shift, "view_prev");

	add_key("w", SDLK_w, 'w', "+afterburner");
	add_key("x", SDLK_x, 'x', "target_center");
	add_key("y", SDLK_y, 'y');
	add_key("z", SDLK_z, 'z');

	add_key("del", SDLK_DELETE);
	add_key("kp0", SDLK_KP0);
	add_key("kp1", SDLK_KP1);
	add_key("kp2", SDLK_KP2, 0, "+up");
	add_key("kp3", SDLK_KP3);
	add_key("kp4", SDLK_KP4, 0, "+left");
	add_key("kp5", SDLK_KP5);
	add_key("kp6", SDLK_KP6, 0, "+right");
	add_key("kp7", SDLK_KP7, 0, "+rollleft");
	add_key("kp8", SDLK_KP8, 0, "+down");
	add_key("kp9", SDLK_KP9, 0, "+rollright");

	add_key("kpperiod", SDLK_KP_PERIOD, '.');
	add_key("kpdiv", SDLK_KP_DIVIDE, '/', "+zoomin");
	add_key("kpmul", SDLK_KP_MULTIPLY, '*', "+zoomout");
	add_key("kpmin", SDLK_KP_MINUS, '-', "+thrustdown");
	add_key("kpplus", SDLK_KP_PLUS, '+', "+thrustup");
	add_key("kpenter", SDLK_KP_ENTER, '\n', "ui_chat");
	add_key("kpequal", SDLK_KP_EQUALS, '=');

	add_key("up", SDLK_UP, 0, "+camup");
	add_key("down", SDLK_DOWN, 0, "+camdown");
	add_key("right", SDLK_RIGHT, 0, "+camright");
	add_key("left", SDLK_LEFT, 0, "+camleft");

	add_key("insert", SDLK_INSERT);
	add_key("home", SDLK_HOME);
	add_key("end", SDLK_END);
	add_key("pageup", SDLK_PAGEUP);
	add_key("pagedown", SDLK_PAGEDOWN);

	add_key("f1", SDLK_F1);
	add_key("f2", SDLK_F2);
	add_key("f3", SDLK_F3, 0, "@dock");
	key = add_key("f4", SDLK_F4);
#ifdef _WIN32
	key->assign(Key::Alt, "quit");
#endif
	add_key("f5", SDLK_F5, 0, "ui_inventory");
	add_key("f6", SDLK_F6, 0, "ui_map");
	add_key("f7", SDLK_F7);
	add_key("f8", SDLK_F8);
	add_key("f9", SDLK_F9);
	add_key("f10", SDLK_F10);
	add_key("f11", SDLK_F11);
	add_key("f12", SDLK_F12);
	add_key("f13", SDLK_F13);
	add_key("f14", SDLK_F14);
	add_key("f15", SDLK_F15);

	add_key("numlock", SDLK_NUMLOCK);
	add_key("capslock", SDLK_CAPSLOCK);
	add_key("scrollock", SDLK_SCROLLOCK);

	add_key("rshift", SDLK_RSHIFT);
	add_key("lshift", SDLK_LSHIFT);

	add_key("rctrl", SDLK_RCTRL);
	add_key("lctrl", SDLK_LCTRL);

	add_key("ralt", SDLK_RALT);
	add_key("lalt", SDLK_LALT);

	add_key("rmeta", SDLK_RMETA);
	add_key("lmeta", SDLK_LMETA);
	add_key("lwin", SDLK_LSUPER);
	add_key("rwin", SDLK_RSUPER);
	add_key("mode", SDLK_MODE);

	add_key("help", SDLK_HELP);
	add_key("print", SDLK_PRINT, 0, "screenshot");
	add_key("sysrq", SDLK_SYSREQ);
	add_key("break", SDLK_BREAK);
	add_key("menu", SDLK_MENU);
	add_key("power", SDLK_POWER);
	add_key("euro", SDLK_EURO);

	// mouse button aliases
	add_key("mouse1", 512 + SDL_BUTTON_LEFT, 0, "+control");
	add_key("mouse2", 512 + SDL_BUTTON_RIGHT);
	add_key("mouse3", 512 + SDL_BUTTON_MIDDLE);

	add_key("mouse4", 512 + SDL_BUTTON_WHEELUP, 0, "+thrustup");
	add_key("mouse5", 512 + SDL_BUTTON_WHEELDOWN, 0, "+thrustdown");

	// joystick button aliases
	add_key("joy0", 564);
	add_key("joy1", 565, 0, "target_center");
	add_key("joy2", 566);
	add_key("joy3", 567);
	add_key("joy4", 568);
	add_key("joy5", 569);
	add_key("joy6", 570);
	add_key("joy7", 571);
	add_key("joy8", 572);
	add_key("joy9", 573);
	add_key("joy10", 574);
	add_key("joy11", 575);
	add_key("joy12", 576);
	add_key("joy13", 577);
	add_key("joy14", 578);
	add_key("joy15", 579);
}

Keyboard::~Keyboard()
{
	// clear key map
	for (iterator it = begin(); it != end(); it++)
		delete(*it).second;
	keys.clear();

	// clear action list
	for (std::list<Action *>::iterator ait = actions.begin(); ait != actions.end(); ait++)
		delete(*ait);
	actions.clear();
}

void Keyboard::save_binds()
{
	std::string filename(filesystem::writedir());
	filename.append("binds.cfg");
	std::ofstream ofs(filename.c_str());

	if (!ofs.is_open()) {
		con_warn << "Could not write " << filename << std::endl;
		return;
	}

	con_print << "  writing keyboard binds to " << filename << std::endl;

	ofs << "# binds.cfg - osirion keyboard binds" << std::endl;
	ofs << "# this file is automaticly generated" << std::endl;
	ofs << std::endl;

	iterator it;
	for (it = begin(); it != end(); it++) {
		Key *key = (*it).second;
		if (key->bind(Key::None).size()) {
			ofs << "bind " << key->name() << " \"" << key->bind(Key::None) << '\"' << std::endl;
		}
		if (key->bind(Key::Shift).size()) {
			ofs << "bind shift+" << key->name() << " \"" << key->bind(Key::Shift) << '\"' << std::endl;
		}
		if (key->bind(Key::Ctrl).size()) {
			ofs << "bind ctrl+" << key->name() << " \"" << key->bind(Key::Ctrl) << '\"' << std::endl;
		}
		if (key->bind(Key::Alt).size()) {
			ofs << "bind alt+" << key->name() << " \"" << key->bind(Key::Alt) << '\"' << std::endl;
		}
		/*
		} else {
			ofs << "unbind " << key->name() << std::endl;
		}
		*/
	}
	ofs.close();
}

void Keyboard::load_binds()
{
	std::string filename(filesystem::writedir());
	filename.append("binds.cfg");
	std::ifstream ifs(filename.c_str(), std::ifstream::in);

	if (!ifs.is_open()) {
		con_warn << "Could not read " << filename << std::endl;
		return;
	}

	con_print << "  reading keyboard binds from " << filename << std::endl;

	char line[MAXCMDSIZE];
	while (ifs.getline(line, MAXCMDSIZE - 1)) {
		if (line[0] && line[0] != '#' && line[0] != ';') {
			core::CommandBuffer::exec(line);
		}
	}
}

Key * Keyboard::release(unsigned int sym)
{
	Key *key = find(sym);
	if (!key) {
		return 0;
	}

	key->key_waspressed = (core::application()->time() - key->pressed());
	key->key_pressed = 0;
	key->key_lastpressed = 0;

	return key;
}

Key * Keyboard::press(unsigned int sym)
{
	Key *key = find(sym);
	if (!key) {
		return 0;
	}

	return press(key);
}

Key * Keyboard::press(Key *key)
{
	if (key->pressed() == 0) {
		key->key_pressed = core::application()->time();
		key->key_waspressed = 0;
	}
	key->key_lastpressed = core::application()->time();
	return key;
}

Key *Keyboard::find(std::string const & name)
{
	Key *key = 0;
	for (iterator it = begin(); it != end() && !key; it++) {
		if ((*it).second->name().compare(name) == 0) {
			key = (*it).second;
		}
	}
	return key;
}

Key *Keyboard::find(unsigned int keysym)
{
	iterator it = keys.find(keysym);
	if (it == end())
		return 0;
	else
		return (*it).second;
}

void Keyboard::bind(std::string const &name, const std::string str)
{
	Key::Modifier modifier = Key::None;
	std::string keyname(name);

	if ((keyname.size() > 6) && (keyname.substr(0, 6).compare("shift+") == 0)) {
		keyname.erase(0, 6);
		modifier = Key::Shift;
	} else if ((keyname.size() > 5) && (keyname.substr(0, 5).compare("ctrl+") == 0)) {
		keyname.erase(0, 5);
		modifier = Key::Ctrl;
	} else if ((keyname.size() > 4) && (keyname.substr(0, 4).compare("alt+") == 0)) {
		keyname.erase(0, 4);
		modifier = Key::Alt;

	} else if ((keyname.size() > 6) && (keyname.substr(keyname.size() - 6, 6).compare("+shift") == 0)) {
		keyname.erase(keyname.size() - 6, 6);
		modifier = Key::Shift;
	} else if ((keyname.size() > 5) && (keyname.substr(keyname.size() - 5, 5).compare("+ctrl") == 0)) {
		keyname.erase(keyname.size() - 5, 5);
		modifier = Key::Ctrl;
	} else if ((keyname.size() > 4) && (keyname.substr(keyname.size() - 4, 4).compare("+alt") == 0)) {
		keyname.erase(0, keyname.size() - 46);
		modifier = Key::Alt;
	}

	Key *key = find(keyname);
	if (key) {
		// assign new bind of requested
		if (str.size()) {
			Action *action = 0;
			for (std::list<Action *>::iterator it = actions.begin(); it != actions.end(); it++) {
				if ((*it)->name().compare(str) == 0) {
					action = (*it);
				}
			}
			
			if (action && (modifier != Key::None)) {
				if (modifier == Key::Shift) {
					con_warn << "Key with modifier 'shift+" << key->name() << "' can not be bound to action '" << action->name() << "'!" << std::endl;								
				} else if (modifier == Key::Ctrl) {
					con_warn << "Key with modifier 'ctrl+" << key->name() << "' can not be bound to action '" << action->name() << "'!" << std::endl;				
				} else if (modifier == Key::Alt) {
					con_warn << "Key with modifier 'alt+" << key->name() << "' can not be bound to action '" << action->name() << "'!" << std::endl;
				}
				return;
			}			
			key->assign(modifier, str.c_str(), action);
		}

		// print current bind to console, even when no new bind was assigned
		if (modifier == Key::None) {
			con_print << "       " << aux::pad_right(key->name(), 6) << " " << key->bind(Key::None) << std::endl;
		} else if (modifier == Key::Shift) {
			con_print << " shift+" << aux::pad_right(key->name(), 6) << " " << key->bind(Key::Shift) << std::endl;
		} else if (modifier == Key::Ctrl) {
			con_print << "  ctrl+" << aux::pad_right(key->name(), 6) << " " << key->bind(Key::Ctrl) << std::endl;		
		} else if (modifier == Key::Alt) {
			con_print << "   alt+" << aux::pad_right(key->name(), 6) << " " << key->bind(Key::Alt) << std::endl;
		}
			
	} else {
		con_warn << "Key '" << name << "' not found!" << std::endl;
	}
}

void Keyboard::unbind(std::string const &name)
{
	Key::Modifier modifier = Key::None;
	std::string keyname(name);

	if ((keyname.size() > 6) && (keyname.substr(0, 6).compare("shift+") == 0)) {
		keyname.erase(0, 6);
		modifier = Key::Shift;
	} else if ((keyname.size() > 5) && (keyname.substr(0, 5).compare("ctrl+") == 0)) {
		keyname.erase(0, 5);
		modifier = Key::Ctrl;
	} else if ((keyname.size() > 4) && (keyname.substr(0, 4).compare("alt+") == 0)) {
		keyname.erase(0, 4);
		modifier = Key::Alt;

	} else if ((keyname.size() > 6) && (keyname.substr(keyname.size() - 6, 6).compare("+shift") == 0)) {
		keyname.erase(keyname.size() - 6, 6);
		modifier = Key::Shift;
	} else if ((keyname.size() > 5) && (keyname.substr(keyname.size() - 5, 5).compare("+ctrl") == 0)) {
		keyname.erase(keyname.size() - 5, 5);
		modifier = Key::Ctrl;
	} else if ((keyname.size() > 4) && (keyname.substr(keyname.size() - 4, 4).compare("+alt") == 0)) {
		keyname.erase(0, keyname.size() - 46);
		modifier = Key::Alt;
	}

	Key *key = find(name);
	if (key) {
		key->clear(modifier);
	} else {
		con_print << "Key '" << name << "' not found." << std::endl;
	}
}

void Keyboard::unbindall()
{
	for (iterator it = begin(); it != end(); it++) {
		Key *key = (*it).second;
		key->clear();
	}
}

Key * Keyboard::add_key(const char *name, const unsigned int keysym, const char ascii, const char *bind)
{
	Key *newkey = new Key(name, keysym, ascii);
	keys[keysym] =  newkey;
	if (bind) {
		std::string bindstr(bind);
		this->bind(newkey->name(), bindstr);
	}
		
	return newkey;
}

Action * Keyboard::add_action(const char *name, Action::Identifier action, const char *info)
{
	Action *newaction = new Action(name, action, info);
	actions.push_back(newaction);
	return newaction;
}

void Keyboard::list_actions()
{
	for (std::list<Action *>::iterator it = actions.begin(); it != actions.end(); it++) {
		con_print << "  " << (*it)->name() << " " << (*it)->info() << std::endl;
	}
	con_print  << actions.size() << " registered actions" << std::endl;
}

void Keyboard::list_keys()
{
	for (iterator it = begin(); it != end(); it++) {
		con_print << "  " << aux::pad_left((*it).second->name(), 6) << " " << (*it).second->bind(Key::None) << std::endl;
	}
	con_print  << keys.size() << " registered keys" << std::endl;
}

void Keyboard::list_binds()
{
	size_t n = 0;
	for (iterator it = begin(); it != end(); it++) {
		if ((*it).second->bind(Key::None).size()) {
			con_print << "       " << aux::pad_right((*it).second->name(), 6) << " " << (*it).second->bind(Key::None) << std::endl;
			n++;
		}
		if ((*it).second->bind(Key::Shift).size()) {
			con_print << " shift+" << aux::pad_right((*it).second->name(), 6) << " " << (*it).second->bind(Key::Shift) << std::endl;
			n++;
		}
		if ((*it).second->bind(Key::Ctrl).size()) {
			con_print << "  ctrl+" << aux::pad_right((*it).second->name(), 6) << " " << (*it).second->bind(Key::Ctrl) << std::endl;
			n++;
		}
		if ((*it).second->bind(Key::Alt).size()) {
			con_print << "   alt+" << aux::pad_right((*it).second->name(), 6) << " " << (*it).second->bind(Key::Alt) << std::endl;
			n++;
		}

	}
	con_print  << n << " registered binds" << std::endl;
}

unsigned int Keyboard::translate_keysym(int keysym, int modifier)
{
	bool shift = false;

	// keypad keys
	if (modifier & KMOD_NUM) {
		switch (keysym) {
			case SDLK_KP0:
				return '0';
				break;
			case SDLK_KP1:
				return '1';
				break;
			case SDLK_KP2:
				return '2';
				break;
			case SDLK_KP3:
				return '3';
				break;
			case SDLK_KP4:
				return '4';
				break;
			case SDLK_KP5:
				return '5';
				break;
			case SDLK_KP6:
				return '6';
				break;
			case SDLK_KP7:
				return '7';
				break;
			case SDLK_KP8:
				return '8';
				break;
			case SDLK_KP9:
				return '9';
				break;
			case SDLK_KP_PERIOD:
				return '.';
				break;
		}
	} else {
		switch (keysym) {
			case SDLK_KP0:
				return SDLK_INSERT;
				break;
			case SDLK_KP1:
				return SDLK_END;
				break;
			case SDLK_KP2:
				return SDLK_DOWN;
				break;
			case SDLK_KP3:
				return SDLK_PAGEDOWN;
				break;
			case SDLK_KP4:
				return SDLK_LEFT;
				break;
			case SDLK_KP6:
				return SDLK_RIGHT;
				break;
			case SDLK_KP7:
				return SDLK_HOME;
				break;
			case SDLK_KP8:
				return SDLK_UP;
				break;
			case SDLK_KP9:
				return SDLK_PAGEUP;
				break;
			case SDLK_KP_PERIOD:
				return SDLK_DELETE;
				break;
		}
	}

	// special keys
	switch (keysym) {
		case SDLK_ESCAPE:
			return SDLK_ESCAPE;
			break;
		case SDLK_KP_ENTER:
			return SDLK_RETURN;
			break;
		case SDLK_KP_DIVIDE:
			return '/';
			break;
		case SDLK_KP_MULTIPLY:
			return '*';
			break;
		case SDLK_KP_MINUS:
			return '-';
			break;
		case SDLK_KP_PLUS:
			return '+';
			break;
		case SDLK_KP_EQUALS:
			return '=';
			break;
	}

	// caps lock
	if (modifier & KMOD_CAPS)
		shift = true;

	// left/right shift
	if ((KMOD_LSHIFT + KMOD_RSHIFT) & modifier) {
		shift = !shift;
	}

	if (shift) {
		if ((keysym >= 'a' && keysym <= 'z')) {
			return keysym + 'A' - 'a';
		}

		switch (keysym) {
			case '`':
				return '~';
				break;
			case '1':
				return '!';
				break;
			case '2':
				return '@';
				break;
			case '3':
				return '#';
				break;
			case '4':
				return '$';
				break;
			case '5':
				return '%';
				break;
			case '6':
				return '^';
				break;
			case '7':
				return '&';
				break;
			case '8':
				return '*';
				break;
			case '9':
				return '(';
				break;
			case '0':
				return ')';
				break;
			case '-':
				return '_';
				break;
			case '=':
				return '+';
				break;
				// second row
			case '[':
				return '{';
				break;
			case ']':
				return '}';
				break;
			case '|':
				return '\\';
				break;
				// third row
			case ';':
				return ':';
				break;
			case '\'':
				return '"';
				break;
				// fourth row
			case ',':
				return '<';
				break;
			case '.':
				return '>';
				break;
			case '/':
				return '?';
				break;
		}
	}

	return keysym;
}

/*
void setkeyboardmode(bool input)
{
	if(input)
		SDL_EnableKeyRepeat(250,  SDL_DEFAULT_REPEAT_INTERVAL);
	else
		SDL_EnableKeyRepeat(10,  SDL_DEFAULT_REPEAT_INTERVAL);

}
*/
} // namespace client