

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.0.0.170915_Beta4 (15 September 2017)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2017
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.0 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.0 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.0 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.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.0.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.0 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.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "Console.h"

namespace	cmlabs{



static Console* MainConsole = NULL;

void signal_handler(int sig) {
	if (MainConsole) {
		signal(sig, SIG_IGN);
		MainConsole->processSignal(0, sig);
	}
}


Console::Console() {
	lastID = 0;
	lastBreak = 0;
	breakCount = 0;
	networkManager = NULL;
	node = NULL;
	welcomeText = NULL;
}

Console::~Console() {
	MainConsole = NULL;
	stop();
	node = NULL;
	networkManager = NULL;
	delete [] welcomeText;
	welcomeText = NULL;
}

bool Console::init(Node* parent, NetworkManager* manager, const char* welcomeText) {

	if (!parent || !manager)
		return false;

	if (MainConsole)
		return false;
	MainConsole = this;

	#ifdef WINDOWS
		signal(SIGINT, signal_handler);
		//signal(SIGTERM, signal_handler);
		//signal(SIGABRT, signal_handler);
		//signal(SIGFPE, signal_handler);
		//signal(SIGILL, signal_handler);
		//signal(SIGSEGV, signal_handler);
		//signal(SIGBREAK, signal_handler);
	#else
	#endif

	if (this->welcomeText)
		delete [] this->welcomeText;
	if (welcomeText) {
		this->welcomeText = new char[strlen(welcomeText)+1];
		utils::strcpyavail(this->welcomeText, welcomeText, (uint32)strlen(welcomeText), true);
	}
	else
		welcomeText = NULL;

	networkManager = manager;
	node = parent;

	clients[0].id = 0;
	clients[0].conid = 0;
	clients[0].lastinput = 0;
	memcpy(clients[0].logLevelVerbose, LogSystem::LogSingleton->logLevelVerbose, sizeof(uint8)*LOG_MAXCOUNT);
	memcpy(clients[0].logLevelDebug, LogSystem::LogSingleton->logLevelDebug, sizeof(uint8)*LOG_MAXCOUNT);
	lastID = 0;

	if (!ThreadManager::CreateThread(ConsoleRun, this, threadID)) {
		LogPrint(0, LOG_SYSTEM, 0, "Could not start Console thread...");
		return false;
	}
	return true;
}

bool Console::connectClient(uint64 conid) {
	uint32 id = ++lastID;
	clients[id].id = id;
	clients[id].conid = conid;
	clients[id].lastinput = 0;
	memcpy(clients[id].logLevelVerbose, LogSystem::LogSingleton->logLevelVerbose, sizeof(uint8)*LOG_MAXCOUNT);
	memcpy(clients[id].logLevelDebug, LogSystem::LogSingleton->logLevelDebug, sizeof(uint8)*LOG_MAXCOUNT);
	return true;
}

bool Console::disconnectClient(uint64 conid) {
	uint32 id = getClientID(conid);
	if (!id)
		return false;
	clients.erase(id);
	return true;
}

uint32 Console::getClientID(uint64 conid) {
	std::map<uint32, ChannelProfile>::iterator it, itEnd;
	for (it = clients.begin(), itEnd = clients.end(); it != itEnd; it++) {
		if ( (it->second.conid == conid) && (it->second.id > 0) )
			return it->second.id;
	}
	return 0;
}

bool Console::setLogLevelDebug(uint32 id, uint8 level) {
	if (clients[id].id != id)
		return false;
	for (uint8 n=1; n<LOG_MAXCOUNT; n++) {
		clients[id].logLevelDebug[n] = level;
		if (LogSystem::LogSingleton->logLevelDebug[n] < level)
			LogSystem::SetLogLevelDebug(n, level);
	}
	return true;
}

bool Console::setLogLevelDebug(uint32 id, uint8 subject, uint8 level) {
	if (subject >= LOG_MAXCOUNT)
		return false;
	if (clients[id].id != id)
		return false;
	clients[id].logLevelDebug[subject] = level;
	if (LogSystem::LogSingleton->logLevelDebug[subject] < level)
		LogSystem::SetLogLevelDebug(subject, level);
	return true;
}

bool Console::setLogLevelVerbose(uint32 id, uint8 level) {
	if (clients[id].id != id)
		return false;
	for (uint8 n=1; n<LOG_MAXCOUNT; n++) {
		clients[id].logLevelVerbose[n] = level;
		if (LogSystem::LogSingleton->logLevelVerbose[n] < level)
			LogSystem::SetLogLevelVerbose(n, level);
	}
	return true;
}

bool Console::setLogLevelVerbose(uint32 id, uint8 subject, uint8 level) {
	if (subject >= LOG_MAXCOUNT)
		return false;
	if (clients[id].id != id)
		return false;
	clients[id].logLevelVerbose[subject] = level;
	if (LogSystem::LogSingleton->logLevelVerbose[subject] < level)
		LogSystem::SetLogLevelVerbose(subject, level);
	return true;
}

bool Console::printToClient(uint32 id, const char* format, ...) {
	uint64 conid;
	TelnetLine* line;
	NetworkChannel* channel;

	va_list args;
	va_start(args, format);
	uint32 len;
	char* text = utils::StringFormatVA(len, format, args);
	va_end(args);

	if (id == 0) {
		printf("[%u] %s\n", node->nodeID, text);
		fflush(stdout);
	}
	else if ( networkManager && (clients.find(id) != clients.end()) && (conid = clients[id].conid) && (channel = networkManager->getConnection(conid)) ) {
		line = new TelnetLine(0);
		line->setLine(text, true);
		channel->sendTelnetLine(line, conid);
		delete(line);
	}
	else {
		free(text);
		return false;
	}
	free(text);
	return true;
}

bool Console::logEntry(LogEntry* entry) {
	
	uint32 len;
	std::map<uint32, ChannelProfile>::iterator it, itEnd;
	for (it = clients.begin(), itEnd = clients.end(); it != itEnd; it++) {
		if ((entry->type == LOGPRINT) && (entry->level <= it->second.logLevelVerbose[entry->subject])) {
			printToClient(it->first, "%s %s", PrintTimeString(entry->time).c_str(), entry->getText(len));
		}
		else if ((entry->type == LOGDEBUG) && (entry->level <= it->second.logLevelDebug[entry->subject])) {
			printToClient(it->first, "Debug %s %s", PrintTimeString(entry->time).c_str(), entry->getText(len));
		}
	}
	return true;
}

bool Console::receiveTelnetLine(TelnetLine* line, NetworkChannel* channel, uint64 conid) {
	if (!line || !channel || !conid)
		return false;
	uint32 id = getClientID(conid);
	if (!id) {
		connectClient(conid);
		if (!(id = getClientID(conid)))
			return false;
		if (welcomeText) {
			TelnetLine* reply = new TelnetLine(0);
			reply->setLine(welcomeText, true);
			channel->sendTelnetLine(reply, conid);
			delete(reply);
		}
	}
	return processInput(id, line->data);
}


bool Console::processInput(uint32 id, const char* text) {
	if (!text || !strlen(text))
		return true;

	std::vector<std::string> args = utils::TextListSplit(text, " ");
	const char* cmd = args[0].c_str();

	int val;
	bool global = false;
	if ((args.size() > 1) && (args[1].size())) {
		if (args.size() > 2)
			global = (stricmp(args[2].c_str(), "global") == 0);
		for (int n=0; n<SUBJECT_COUNT; n++) {
			if (stricmp(cmd, VerboseSubjects[n]) == 0) {
				val = utils::Ascii2Int32(args[1].c_str());
				if (val >= 0) {
					if (global) {
						if (!n)
							LogSystem::SetLogLevelVerbose(val);
						else
							LogSystem::SetLogLevelVerbose(n, val);
						printToClient(id, "Global log level '%s' set to %d.", LogSubjects[n], val);
					}
					else {
						if (!n)
							setLogLevelVerbose(id, val);
						else
							setLogLevelVerbose(id, n, val);
						printToClient(id, "Local log level '%s' set to %d.", LogSubjects[n], val);
					}
					return true;
				}
			}
			else if (stricmp(cmd, DebugSubjects[n]) == 0) {
				val = utils::Ascii2Int32(args[1].c_str());
				if (val >= 0) {
					if (global) {
						if (!n)
							LogSystem::SetLogLevelDebug(val);
						else
							LogSystem::SetLogLevelDebug(n, val);
						printToClient(id, "Global debug level '%s' set to %d.", LogSubjects[n], val);
					}
					else {
						if (!n)
							setLogLevelDebug(id, val);
						else
							setLogLevelDebug(id, n, val);
						printToClient(id, "Local debug level '%s' set to %d.", LogSubjects[n], val);
					}
					return true;
				}
			}
		}
	}

	if (stricmp(cmd, "restart") == 0) {
		printToClient(id, "Restarting system, please wait... ");
		if (node->restart())
			printToClient(id, "SUCCESS, please wait...");
		else
			printToClient(id, "FAILED!");
		return true;
	}
	else if (stricmp(cmd, "reinit") == 0) {
		printToClient(id, "Reinit system, please wait... ");
		if (node->reinit(args[1].c_str()))
			printToClient(id, "SUCCESS, please wait...");
		else
			printToClient(id, "FAILED!");
		return true;
	}
	else if (stricmp(cmd, "shutdown") == 0) {
		printToClient(id, "Shutting down system, please wait... ");
		if (node->shutdown(1000))
			printToClient(id, "SUCCESS, please wait...");
		else
			printToClient(id, "FAILED!");
		return true;
	}
	else if (stricmp(cmd, "kill") == 0) {
		printToClient(id, "Killing system now, please wait... ");
		exit(0);
	}


	// Process input
	// ########################
	printToClient(id, "Unknown command '%s'...", cmd);
	return true;
}

bool Console::processSignal(uint32 id, int sig) {

	switch (sig) {
		case SIGABRT: //   SIGABRT Abnormal termination 
			processInput(0, "SIGABRT");
			break;
		case SIGTERM: //   SIGTERM Termination request 
			processInput(0, "SIGTERM");
			break;
		case SIGINT: //   SIGINT CTRL+C signal 
			if (GetTimeAgeMS(lastBreak) > 5000)
				breakCount = 0;
			lastBreak = GetTimeNow();
			if (++breakCount > 2) {
				printToClient(id, "Killing system now, please wait... ");
				// For now just exit
				exit(0);
			}
			//printToClient(id, "Shutting down system now, please wait... ");
			processInput(0, "shutdown");
			//utils::Sleep(3000);
			//processInput(0, "SIGINT");
			// Temporary ##############
			//exit(0);
			break;

		#ifdef WINDOWS
			case SIGBREAK: //   SIGBREAK CTRL+Break signal 
				processInput(0, "SIGBREAK");
				break;
		#else
			case SIGHUP:
				processInput(0, "SIGHUP");
				break;
		#endif // WIN32

		case SIGFPE: //   SIGFPE Floating-point error 
			processInput(0, "SIGFPE");
			break;
		case SIGILL: //   SIGILL Illegal instruction 
			processInput(0, "SIGILL");
			break;
		case SIGSEGV: //   SIGSEGV Illegal storage access 
			processInput(0, "SIGSEGV");
			break;
		default:
			processInput(0, "Unknown Signal");
			break;
	}
	signal(sig, signal_handler);
	return true;
}


bool Console::run() {

	isRunning = true;

	#ifdef WINDOWS
		HANDLE input_handle = GetStdHandle(STD_INPUT_HANDLE);
		DWORD events = 0;      // how many events took place
		INPUT_RECORD input_record;  // a record of input events
		DWORD input_size = 1;    // how many characters to read
	#else
		struct timeval tv;
		fd_set fds;
		tv.tv_sec = 0;
		tv.tv_usec = 0;
	#endif

	uint8 inputchar;
	uint32 maxSize = 1024;
	char* buffer = new char[maxSize];
	uint32 pos = 0;
	buffer[0] = 0;

	while (shouldContinue) {
		// Listen for inputs from local keyboard
		inputchar = 0;

		#ifdef WINDOWS
			if ((PeekConsoleInput(input_handle, &input_record, input_size, &events)) && events) {
				if (ReadConsoleInput(input_handle, &input_record, input_size, &events)) {
					if(input_record.EventType == KEY_EVENT) {
						if(input_record.Event.KeyEvent.bKeyDown) {
							// PeekConsoleInput succeeded and a key was pressed, so set control_key and return vk.
							// control_key = input_record.Event.KeyEvent.dwControlKeyState;
							if (input_record.Event.KeyEvent.uChar.AsciiChar)
								inputchar = input_record.Event.KeyEvent.uChar.AsciiChar;
						}
					}
				}
			}
		#else
			struct timeval tv;
			fd_set fds;
			tv.tv_sec = 0;
			tv.tv_usec = 0;
			FD_ZERO(&fds);
			FD_SET(STDIN, &fds); //STDIN_FILENO is 0
			select(STDIN+1, &fds, NULL, NULL, &tv);
			if (FD_ISSET(STDIN, &fds))
				inputchar = fgetc(stdin);
			//	inputchar = getch();
		#endif

			if (inputchar) {
				switch(inputchar) {
					case 10:
					case 13:
						if (pos) {
							processInput(0, buffer);
							pos = 0;
						}
						break;
					default:
						if (pos == maxSize-10) {
							maxSize *= 2;
							char* tmp = new char[maxSize];
							memcpy(tmp, buffer, pos+1);
							delete [] buffer;
							buffer = tmp;
						}
						buffer[pos++] = inputchar;
						buffer[pos] = 0;
						printf("%c", inputchar);
				}
			}
			else
				utils::Sleep(50);
	}
	
	delete [] buffer;
	isRunning = false;
	return true;
}


THREAD_RET THREAD_FUNCTION_CALL ConsoleRun(THREAD_ARG arg) {
	if (arg == NULL) thread_ret_val(1);
	thread_ret_val((int)(((Console*)arg)->run() ? 0 : 1));
}

}
