#include <EEPROM.h>
void keyHandler();
#include "commands.h"
static const char welcome_str[] PROGMEM  =
		"\e[32mWelcome to Arduino console interface\e[0m";         //! welcome string
#define COMMAND_HISTORY_SIZE 8                                     //! size of command history
String command[COMMAND_HISTORY_SIZE];                              //! command history
int cur_com = COMMAND_HISTORY_SIZE-1;                              //! current history index
String cmd;                                                        //! current command
int cur_pos = 0;                                                   //! current line position
bool in_hist = false;                                              //! flag set if in history

void keyEvent(KeyEvent ke);
void moveLeft(int n=1);
void moveRight(int n=1);
void moveEnd();
void moveBegin();
void updateLine(bool sv);
void parseCommand(String);
void writeLine(int prev_pos);
int historySearchUp(int size, String cur);
int historySearchDown(int n, String cur);

static const char str_unknown_command[] PROGMEM  = "Unknown command";
static const char str_too_few_arguments[] PROGMEM  = " : too few arguments";

void setup() {
	wdt_disable();
	pinMode(LED_BUILTIN, OUTPUT);
	Serial.begin(115200);
	int l = 0;
	while(!Serial) {
		if (l < 3) {
			digitalWrite(LED_BUILTIN, HIGH);
			delay(30);
			digitalWrite(LED_BUILTIN, LOW);
			delay(30);
		} else {
			digitalWrite(LED_BUILTIN, HIGH);
			delay(300);
			if (Serial) break;
			digitalWrite(LED_BUILTIN, LOW);
			delay(500);
			l = 0;
		}
		l++;
	}
	welcome();
}

void welcome() {
	int len = strlen_P(welcome_str);
	for (int k=0; k<len; k++)
		Serial.write(pgm_read_byte_near(welcome_str + k));
	Serial.println();
	cmd = String();
	load(ComArgs());
	nobg(ComArgs());
	writeLine(0);
}

void keyHandler() {
	int ret;
	int rc[8];
	for (int i=0; i<8; i++) {
		rc[i] = Serial.read();
		if (rc[i] == -1) {
			ret = i;
			break;
		}
		/*if (rc[i] < 127) {
			ret = i + 1;
			parseKey(rc, ret, keyEvent);
			--i;
		}*/
	}
	if (rc[0] == 0) {delay(10); return;}
	if (ret < 0 || ret > 7) {delay(10); return;}
	parseKey(rc, ret, keyEvent);
}

void heartbeat(int p) {
	if (p < 10) analogWrite(LED_BUILTIN, 25 * p);
	if (p > 10 && p < 21) analogWrite(LED_BUILTIN, 255 - 25 * (p-10));
	if (p > 25 && p < 35) analogWrite(LED_BUILTIN, 25 * (p-25));
	if (p > 35 && p < 45) analogWrite(LED_BUILTIN, 255 - 25 * (p-35));
	if (p > 50) analogWrite(LED_BUILTIN, 0);
}

void loop() {
	cancel = false;
	if (Serial.available() > 0)
		keyHandler();
	if (led_heart > 0) {
		heartbeat(led_heart/10);
		led_heart++;
		if (led_heart > 1000) led_heart = 1;
	}
	if (bg_com != 0) {
		bg_com(bg_arg);
		if (bg_printing) {
			Serial.println();
			writeLine(cur_pos);
			bg_printing = false;
		}
	}
	delay(1);
}

void keyEvent(KeyEvent ke) {
	int c = ke.key;
	if (com_run) {
		if (c == Esc || c == 'q' || c == 'Q')// || c == F10)
			cancel = true;
		return;
	}
	switch (c) {
		case Space:
			echo(c);
		break;
		case Return:
			Serial.println();
			cmd.trim();
			if (cmd.length() > 0) {
				parseCommand(cmd);
				if (cmd != command[COMMAND_HISTORY_SIZE-1]) {
					for (int i=1; i<COMMAND_HISTORY_SIZE; i++) {
						command[i-1] = command[i];
					}
					command[COMMAND_HISTORY_SIZE-1] = cmd;
				}
			}
			cur_com = COMMAND_HISTORY_SIZE-1;
			in_hist = false;
			cmd = String();
			writeLine(0);
			cur_pos = 0;
		break;
		case Backspace:
			if (cur_pos != 0) {
				if (cur_pos == cmd.length()) {
					cmd = cmd.substring(0, cmd.length()-1);
					Serial.write(Backspace);
					cur_pos--;
				} else {
					cmd = cmd.substring(0, cur_pos-1) + cmd.substring(cur_pos, cmd.length());
					updateLine(true);
					moveLeft();
				}
			}
		break;
		case Tab:
		{
			int cnt = 0;
			String alcmd = String();
			String cr = cmd.substring(0, cur_pos);
			cr.trim();
			for (int i=0; ; i++) {
				if (coms[i].slot == 0) break;
				if (cr.length() == 0 || coms[i].comm.startsWith(cr)) {
					alcmd += coms[i].comm + String(" ");
					cnt++;
				}
			}
			if (cnt == 1) {
				cmd = alcmd;
				updateLine(false);
			}
			if (cnt > 1) {
				Serial.println();
				Serial.println(alcmd);
				writeLine(cur_pos);
			}
		}
		break;
		case Esc:
			cmd = String();
			in_hist = false;
			cur_com = COMMAND_HISTORY_SIZE-1;
			updateLine(false);
		break;
		case LeftArrow:
			if (cur_pos > 0) {
				if (ke.modifiers[Ctrl]) {
					bool sp = (cmd[cur_pos-1] == ' ');
					for(int i=cur_pos-1; i<cmd.length(); i--) {
						if (sp && cmd[i] != ' ') sp = false;
						if (cmd[i] == ' ' && !sp) break;
						moveLeft();
					}
				} else moveLeft();
			}
		break;
		case RightArrow:
			if (cur_pos < cmd.length()) {
				if (ke.modifiers[Ctrl]) {
					bool sp = (cmd[cur_pos] == ' ');
					for(int i=cur_pos+1; i<cmd.length(); i++) {
						if (sp && cmd[i] != ' ') sp = false;
						if (cmd[i] == ' ' && !sp) break;
						moveRight();
					}
				}
				moveRight();
			}
		break;
		case UpArrow:
			if (cur_com > 0) {
				if (!in_hist && command[COMMAND_HISTORY_SIZE-1].length() > 0) {
					in_hist = true;
					cur_com = COMMAND_HISTORY_SIZE;
				}
				while (command[cur_com-1].length() > 0) {
					cur_com--;
					cmd = command[cur_com];
					updateLine(false);
					if (!ke.modifiers[Ctrl]) break;
				}
			}
		break;
		case DownArrow:
			if (cur_com < COMMAND_HISTORY_SIZE-1 & in_hist) {
				if (ke.modifiers[Ctrl]) cur_com = COMMAND_HISTORY_SIZE-1;
				else cur_com++;
				cmd = command[cur_com];
				updateLine(false);
			}
		break;
		case Delete:
			if (cur_pos != cmd.length()) {
				cmd = cmd.substring(0, cur_pos) + cmd.substring(cur_pos + 1, cmd.length());
				updateLine(true);
			}
		break;
		case Insert:
			//Serial.print("[ins]");
		break;
		case PageUp:
		if (cur_com > 0) {
			String cr = cmd.substring(0, cur_pos);
			cr.trim();
			if (cr.length() > 0) {
				int cp = historySearchUp(cur_com, cr);
				if (cp > 0) {
					in_hist = true;
					cur_com = cp;
					cmd = command[cp];
					int tp = cur_pos;
					updateLine(false);
					moveLeft(cur_pos - tp);
				}
			}
		}
		break;
		case PageDown:
		if (cur_com < COMMAND_HISTORY_SIZE-1 && in_hist) {
			String cr = cmd.substring(0, cur_pos);
			cr.trim();
			if (cr.length() > 0) {
				int cp = historySearchDown(cur_com+1, cr);
				if (cp > 0) {
					cur_com = cp;
					cmd = command[cp];
					int tp = cur_pos;
					updateLine(false);
					moveLeft(cur_pos - tp);
				}
			}
		}
		break;
		case End:
			moveEnd();
		break;
		case Home:
			moveBegin();
		break;
//		case F1:
//		case F2:
//		case F3:
//		case F4:
//		case F5:
//		case F6:
//		case F7:
//		case F8:
//		case F9:
//		case F10:
//		case F11:
//		case F12:
//			if (ke.modifiers[Alt]) Serial.print("Alt+");
//			if (ke.modifiers[Ctrl]) Serial.print("Ctrl+");
//			if (ke.modifiers[Shift]) Serial.print("Shift+");
//			Serial.print("[F");
//			Serial.print(-(ke.key+10));
//			Serial.print("]");
		break;
		default:
//			if (ke.modifiers[Alt]) Serial.print("Alt+");
//			if (ke.modifiers[Ctrl]) Serial.print("Ctrl+");
//			if (ke.modifiers[Shift]) Serial.print("Shift+");
			if (c > 0x20 && c < 0x7f) echo(c);
		break;
	}
}

void echo(int c) {
	if (cur_pos == cmd.length()) {
		Serial.write(c);
		cur_pos++;
		cmd += String(char(c));
	} else {
		cmd = cmd.substring(0, cur_pos) + String(char(c)) + cmd.substring(cur_pos, cmd.length());
		updateLine(true);
		moveRight();
	}
}

void moveBegin() {
	moveLeft(cur_pos);
}

void updateLine(bool sv) {
	int cp = cur_pos;
	moveBegin();
	clearLine();
	Serial.print(cmd);
	cur_pos = cmd.length();
	if (sv) moveLeft(cur_pos - cp);
}

void moveEnd() {
	moveRight(cmd.length() - cur_pos);
}

void moveLeft(int n) {
	if (cur_pos != 0 && n > 0 && n <= cur_pos) {
		leftCursor(n);
		cur_pos -= n;
	}
}

void moveRight(int n) {
	if (cur_pos != cmd.length() && n > 0 && n <= (cmd.length() - cur_pos)) {
		rightCursor(n);
		cur_pos += n;
	}
}

void writeLine(int prev_pos) {
	Serial.print(hostname_prefix);
	Serial.print(hostname);
	Serial.print(hostname_sufix);
	Serial.print(cmd);
	leftCursor(cmd.length() - prev_pos);
}

void parseCommand(String com) {
	ComArgs ar;
	for (int i=0; ;i++) {
		if (coms[i].slot == 0) {
			int len = strlen_P(str_unknown_command);
			for (int k=0; k<len; k++)
				Serial.write(pgm_read_byte_near(str_unknown_command + k));
			Serial.println();
			break;
		}
		if (com == coms[i].comm && coms[i].flag[NoArgs]) {
			ar.arg = String();
			if (com == coms[i].comm) {
				if (coms[i].flag[IsBackground])
					bg_com = coms[i].slot;
				else
					coms[i].slot(ar);
			}
			break;
		}
		if (coms[i].flag[HasArgs]) {
			if (com.startsWith(coms[i].comm + String(" "))) {
				ar.arg = com.substring(coms[i].comm.length());
				ar.arg.trim();
				if (coms[i].flag[IsBackground]) {
					bg_com = coms[i].slot;
					bg_arg = ar;
				} else
					coms[i].slot(ar);
				break;
			} else if (com == coms[i].comm) {
				Serial.print(coms[i].comm);
				int len = strlen_P(str_too_few_arguments);
				for (int k=0; k<len; k++)
					Serial.write(pgm_read_byte_near(str_too_few_arguments + k));
				Serial.println();
				break;
			}
		}
	}
}

int historySearchUp(int size, String cur) {
	for (int i=size-1; i>=0; i--) {
		if (command[i].startsWith(cur)) return i;
	}
	return -1;
}

int historySearchDown(int n, String cur) {
	for (int i=n; i<COMMAND_HISTORY_SIZE; i++) {
		if (command[i].startsWith(cur)) return i;
	}
	return -1;
}
