/*
    PIP - Platform Independent Primitives
    Keyboard grabber for console
    Copyright (C) 2014  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pikbdlistener.h"


/** \class PIKbdListener
 * \brief Keyboard console input listener
 * \details This class provide listening of console keyboard input.
 * There is two ways to receive pressed key:
 * * external static functionwith format "void func(char key, void * data_)"
 * * event \a keyPressed()
 * 
 * Also there is static variable \a exiting which by default is set to 
 * \b false. If \a enableExitCapture() was called and listener was started
 * with function \a start(), this variable will be set to \b true if exit
 * key will be pressed. By default exit key is 'Q' = shift + 'q'.
 * To wait for this variable changes to \b true there is WAIT_FOR_EXIT macro
 * \snippet pikbdlistener.cpp main
 * */


bool PIKbdListener::exiting;

PIKbdListener::PIKbdListener(KBFunc slot, void * _data): PIThread() {
#ifdef WINDOWS
	hIn = GetStdHandle(STD_INPUT_HANDLE);
	GetConsoleMode(hIn, &smode);
#else
	struct termios term;
	tcgetattr(0, &term);
	sterm = term;
#endif
	is_active = true;
	ret_func = slot;
	data_ = _data;
	PIKbdListener::exiting = exit_enabled = false;
	start();
}


void PIKbdListener::begin() {
	//cout << "list begin" << endl;
#ifdef WINDOWS
	GetConsoleMode(hIn, &tmode);
	SetConsoleMode(hIn, ENABLE_PROCESSED_INPUT);
#else
	struct termios term;
	tcgetattr(0, &term);
	term.c_lflag &= ~(ECHO | ICANON) | NOFLSH;
	tterm = term;
	tcsetattr(0, TCSAFLUSH, &term);
#endif
}


void PIKbdListener::run() {
	rc = 0;
	char lc = 0;
#ifdef WINDOWS
	INPUT_RECORD ir;
	ReadConsoleInput(hIn, &ir, 1, &ret);
	if (ir.EventType == KEY_EVENT) {
		KEY_EVENT_RECORD ker = ir.Event.KeyEvent;
		if (ker.bKeyDown) {
			bool ctrl = ((ker.dwControlKeyState & LEFT_CTRL_PRESSED) || (ker.dwControlKeyState & RIGHT_CTRL_PRESSED));
			bool shift = (ker.dwControlKeyState & SHIFT_PRESSED);
			if (ker.dwControlKeyState & CAPSLOCK_ON) shift = !shift;
			//cout << "key " << int(ker.wVirtualKeyCode) << endl;
			switch (ker.wVirtualKeyCode) {
			case 37: ret = 1; lc = (ctrl ? CtrlLeftArrow : LeftArrow); break;
			case 38: ret = 1; lc = (ctrl ? CtrlUpArrow : UpArrow); break;
			case 39: ret = 1; lc = (ctrl ? CtrlRightArrow : RightArrow); break;
			case 40: ret = 1; lc = (ctrl ? CtrlDownArrow : DownArrow); break;
			default: ret = 1; lc = (shift ? char(toupper(ker.uChar.AsciiChar)) : ker.uChar.AsciiChar); break;
			}
			if (lc == 0) {piMSleep(10); return;}
		} else {piMSleep(10); return;}
	} else {piMSleep(10); return;}
	/*if (lc == 0) {
		ReadConsole(hIn, &rc, 1, &ret, 0);
		//cout << "read console" << endl;
		lc = char(rc);
	}*/
	/*if (ret < 0 || ret > 3) return;
	lc = char(((uchar * )&rc)[ret - 1]);
	for (int i = 0; i < ret; ++i)
		cout << std::hex << int(((uchar * )&rc)[i]) << ' ';
	cout << endl << std::hex << rc << endl;*/
#else
	ret = read(0, &rc, 4);
	if (ret < 0 || ret > 3) {piMSleep(10); return;}
	lc = char(((uchar * )&rc)[ret - 1]);
	//for (int i = 0; i < ret; ++i)
	//	cout << std::hex << int(((uchar * )&rc)[i]) << ' ';
	//cout << endl << std::hex << rc << endl;
	if (((char * )&rc)[0] == '\e' && ret == 3) {
		if (((char * )&rc)[1] == '[') {
			switch (((char * )&rc)[2]) {
			case 'A': lc = UpArrow; break; // up
			case 'B': lc = DownArrow; break; // down
			case 'C': lc = RightArrow; break; // right
			case 'D': lc = LeftArrow; break; // left
			}
		}
	}
	if (((char * )&rc)[0] == '5' && ret == 2) {
		switch (((char * )&rc)[1]) {
		case 'A': lc = CtrlUpArrow; break; // up
		case 'B': lc = CtrlDownArrow; break; // down
		case 'C': lc = CtrlRightArrow; break; // right
		case 'D': lc = CtrlLeftArrow; break; // left
		}
	}
#endif
	if (exit_enabled && ret == 1 && lc == exit_key) {
		PIKbdListener::exiting = true;
		return;
	}
	if (ret > 0) {
		keyPressed(lc, data_);
		if (ret_func != 0) ret_func(lc, data_);
	}
}


void PIKbdListener::end() {
	//cout << "list end" << endl;
#ifdef WINDOWS
	SetConsoleMode(hIn, smode);
#else
	tcsetattr(0, TCSANOW, &sterm);
#endif
}


void PIKbdListener::setActive(bool yes) {
	is_active = yes;
	if (is_active) {
#ifdef WINDOWS
		SetConsoleMode(hIn, tmode);
#else
		tcsetattr(0, TCSANOW, &tterm);
#endif
	} else {
#ifdef WINDOWS
		SetConsoleMode(hIn, smode);
#else
		tcsetattr(0, TCSANOW, &sterm);
#endif
	}
}
