/*
    PIP - Platform Independent Primitives
    Global includes
    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 "piincludes.h"
#include "piconsole.h"

bool isPIInit = false;
bool piDebug = true;
string ifconfigPath;

PIInit piInit;
lconv * currentLocale =
#ifdef ANDROID
		0;
#else
		std::localeconv();
#endif
#ifdef HAS_LOCALE
static locale_t currentLocale_t = 0;
#endif

#ifdef MAC_OS
clock_serv_t __pi_mac_clock;
#endif

PIMutex __PICout_mutex__;
PIString __PICout_string__;


#ifdef WINDOWS
FILETIME __pi_ftjan1970;
long long __pi_perf_freq = -1;
PINtSetTimerResolution setTimerResolutionAddr = 0;
#endif


PIInit::PIInit() {
	if (isPIInit) return;
	isPIInit = true;
#ifndef WINDOWS
	sigset_t ss;
	sigemptyset(&ss);
	sigaddset(&ss, SIGALRM);
	sigprocmask(SIG_BLOCK, &ss, 0);
	pthread_sigmask(SIG_BLOCK, &ss, 0);
	ifconfigPath = "/bin/ifconfig";
	if (!fileExists(ifconfigPath)) {
		ifconfigPath = "/sbin/ifconfig";
		if (!fileExists(ifconfigPath)) {
			ifconfigPath = "/usr/bin/ifconfig";
			if (!fileExists(ifconfigPath)) {
				ifconfigPath = "/usr/sbin/ifconfig";
				if (!fileExists(ifconfigPath)) {
					ifconfigPath = "";
				}
			}
		}
	}
#else
	// WinSock inint
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	
	// Timers init
	SYSTEMTIME jan1970 = {1970, 1, 4, 1, 0, 14, 15, 0};
	SystemTimeToFileTime(&jan1970, &__pi_ftjan1970);
	LARGE_INTEGER pf;
	pf.QuadPart = -1;
	if (QueryPerformanceFrequency(&pf) != 0) __pi_perf_freq = pf.QuadPart;
	if (__pi_perf_freq == 0) __pi_perf_freq = -1;
	
	// Sleep precision init
	ntlib = LoadLibrary("ntdll.dll");
	if (ntlib) setTimerResolutionAddr = (PINtSetTimerResolution)GetProcAddress(ntlib, "NtSetTimerResolution");
	/*if (setTimerResolution) setTimerResolutionAddr(1, TRUE, &prev_res);*/
#endif
	//piDebug = true;
#ifdef HAS_LOCALE
	//cout << "has locale" << endl;
	if (currentLocale_t != 0) {
		freelocale(currentLocale_t);
		currentLocale_t = 0;
	}
	currentLocale_t = newlocale(LC_ALL, setlocale(LC_ALL, ""), 0);
#else
	setlocale(LC_ALL, "");
	setlocale(LC_NUMERIC, "C");
#endif
#ifdef MAC_OS
	host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &__pi_mac_clock);
#endif
}


PIInit::~PIInit() {
#ifdef WINDOWS
	WSACleanup();
	//if (setTimerResolution) setTimerResolutionAddr(prev_res, TRUE, &prev_res);
	if (ntlib) FreeLibrary(ntlib);
#endif
#ifdef MAC_OS
	mach_port_deallocate(mach_task_self(), __pi_mac_clock);
#endif
	//if (currentLocale_t != 0) freelocale(currentLocale_t);
}


#ifdef WINDOWS
void * PICout::hOut = 0;
WORD PICout::dattr = 0;
DWORD PICout::smode = 0;
#endif


bool PICout::buffer_ = false;


PICout::PICout(PIFlags<PICoutControl> controls): fo_(true), cc_(false), fc_(false), cnb_(10), co_(controls) {
#ifdef WINDOWS
	if (hOut == 0) {
		hOut = GetStdHandle(STD_OUTPUT_HANDLE);
		CONSOLE_SCREEN_BUFFER_INFO sbi;
		GetConsoleScreenBufferInfo(hOut, &sbi);
		dattr = sbi.wAttributes;
	}
	attr_ = dattr;
#endif
	__PICout_mutex__.lock();
}


PICout::~PICout() {
	if (fc_) applyFormat(PICoutManipulators::Default);
	if (cc_) return;
	newLine();
	__PICout_mutex__.unlock();
}


PICout PICout::operator <<(const PICoutAction v) {
#ifdef WINDOWS
	CONSOLE_SCREEN_BUFFER_INFO sbi;
	COORD coord;
	CONSOLE_CURSOR_INFO curinfo;
#endif
	switch (v) {
	case PICoutManipulators::Flush:
		if (!PICout::buffer_)
			std::cout << std::flush;
		break;
	case PICoutManipulators::Backspace:
		if (!PICout::buffer_) {
#ifdef WINDOWS
			GetConsoleScreenBufferInfo(hOut, &sbi);
			coord = sbi.dwCursorPosition;
			coord.X = piMax<int>(0, int(coord.X) - 1);
			SetConsoleCursorPosition(hOut, coord);
			printf(" ");
			SetConsoleCursorPosition(hOut, coord);
#else
			printf("\e[1D \e[1D");
#endif
		}
		break;
	case PICoutManipulators::ShowCursor:
		if (!PICout::buffer_) {
#ifdef WINDOWS
			GetConsoleCursorInfo(hOut, &curinfo);
			curinfo.bVisible = true;
			SetConsoleCursorInfo(hOut, &curinfo);
#else
			printf("\e[?25h");
#endif
		}
		break;
	case PICoutManipulators::HideCursor:
		if (!PICout::buffer_) {
#ifdef WINDOWS
			GetConsoleCursorInfo(hOut, &curinfo);
			curinfo.bVisible = false;
			SetConsoleCursorInfo(hOut, &curinfo);
#else
			printf("\e[?25l");
#endif
		}
		break;
	case PICoutManipulators::ClearScreen:
		if (!PICout::buffer_) {
#ifdef WINDOWS
			/// TODO !!!
			/*GetConsoleCursorInfo(hOut, &curinfo);
			curinfo.bVisible = false;
			SetConsoleCursorInfo(hOut, &curinfo);
			
			SetConsoleCursorPosition(hOut, ulcoord);
			FillConsoleOutputAttribute(hOut, dattr, width * (height + 1), ulcoord, &written);
			FillConsoleOutputCharacter(hOut, ' ', width * (height + 1), ulcoord, &written);*/
#else
			printf("\e[H\e[J");
#endif
		}
		break;
	case PICoutManipulators::SaveContol: saveControl(); break;
	case PICoutManipulators::RestoreControl: restoreControl(); break;
	default: break;
	};
	return *this;
}


#define PICOUTTOTARGET(v) {if (PICout::buffer_) __PICout_string__ << (v); else std::cout << (v);}
#define PINUMERICCOUT if (cnb_ == 10) PICOUTTOTARGET(v) else PICOUTTOTARGET(PIString::fromNumber(v, cnb_))


PICout PICout::operator <<(const char * v) {space(); quote(); PICOUTTOTARGET(v) quote(); return *this;}

PICout PICout::operator <<(const string & v) {space(); quote(); PICOUTTOTARGET(v) quote(); return *this;}

PICout PICout::operator <<(const bool v) {space(); if (v) PICOUTTOTARGET("true") else PICOUTTOTARGET("false") return *this;}

PICout PICout::operator <<(const char v) {space(); PICOUTTOTARGET(v) return *this;}

PICout PICout::operator <<(const uchar v) {space(); if (cnb_ == 10) PICOUTTOTARGET(ushort(v)) else PICOUTTOTARGET(PIString::fromNumber(v, cnb_)) return *this;}

PICout PICout::operator <<(const short int v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const ushort v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const int v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const uint v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const long v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const ulong v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const llong v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const ullong v) {space(); PINUMERICCOUT return *this;}

PICout PICout::operator <<(const float v) {space(); PICOUTTOTARGET(v) return *this;}

PICout PICout::operator <<(const double v) {space(); PICOUTTOTARGET(v) return *this;}

PICout PICout::operator <<(const void * v) {space(); PICOUTTOTARGET("0x") PICOUTTOTARGET(PIString::fromNumber(ullong(v), 16)) return *this;}

PICout PICout::operator <<(const PIObject * v) {
	space();
	if (v == 0) PICOUTTOTARGET("PIObject*(0x0)")
	else {
		PICOUTTOTARGET(v->className())
		PICOUTTOTARGET("*(0x")
		PICOUTTOTARGET(PIString::fromNumber(ullong(v), 16))
		PICOUTTOTARGET(", \"")
		PICOUTTOTARGET(v->name())
		PICOUTTOTARGET("\")")
	}
	return *this;
}

PICout PICout::operator <<(const PICoutSpecialChar v) {
	switch (v) {
	case Null:
		if (PICout::buffer_) __PICout_string__ << PIChar(0);
		else std::cout << char(0);
		break;
	case NewLine:
		if (PICout::buffer_) __PICout_string__ << "\n";
		else std::cout << '\n';
		fo_ = true;
		break;
	case Tab:
		if (PICout::buffer_) __PICout_string__ << "\t";
		else std::cout << '\t';
		break;
	case Esc:
#ifdef CC_VC
		if (PICout::buffer_) __PICout_string__ << PIChar(27);
		else std::cout << char(27);
#else
		if (PICout::buffer_) __PICout_string__ << "\e";
		else std::cout << '\e';
#endif
		break;
	case Quote:
		if (PICout::buffer_) __PICout_string__ << "\"";
		else std::cout << '"';
		break;
	};
	return *this;
}

#undef PICOUTTOTARGET
#undef PINUMERICCOUT

PICout & PICout::space() {
	if (!fo_ && co_[AddSpaces]) {
		if (PICout::buffer_) __PICout_string__ << " ";
		else std::cout << ' ';
	}
	fo_ = false;
	return *this;
}

PICout & PICout::quote() {
	if (co_[AddQuotes]) {
		if (PICout::buffer_) __PICout_string__ << "\"";
		else std::cout << '"';
	}
	fo_ = false;
	return *this;
}

PICout & PICout::newLine() {
	if (co_[AddNewLine]) {
		if (PICout::buffer_) __PICout_string__ << "\n";
		else std::cout << std::endl;
	}
	fo_ = false;
	return *this;
}


void PICout::applyFormat(PICoutFormat f) {
	if (PICout::buffer_) return;
	fc_ = true;
#ifdef WINDOWS
	static int mask_fore = ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
	static int mask_back = ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE);
	switch (f) {
	case Bin: case Oct: case Dec: case Hex: break;
	case PICoutManipulators::Bold: attr_ |= FOREGROUND_INTENSITY; break;
	case PICoutManipulators::Underline: attr_ |= COMMON_LVB_UNDERSCORE; break;
	case PICoutManipulators::Black: attr_ = (attr_ & mask_fore); break;
	case PICoutManipulators::Red: attr_ = (attr_ & mask_fore) | FOREGROUND_RED; break;
	case PICoutManipulators::Green: attr_ = (attr_ & mask_fore) | FOREGROUND_GREEN; break;
	case PICoutManipulators::Blue: attr_ = (attr_ & mask_fore) | FOREGROUND_BLUE; break;
	case PICoutManipulators::Yellow: attr_ = (attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_GREEN; break;
	case PICoutManipulators::Magenta: attr_ = (attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_BLUE; break;
	case PICoutManipulators::Cyan: attr_ = (attr_ & mask_fore) | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
	case PICoutManipulators::White: attr_ = (attr_ & mask_fore) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break;
	case PICoutManipulators::BackBlack: attr_ = (attr_ & mask_back); break;
	case PICoutManipulators::BackRed: attr_ = (attr_ & mask_back) | BACKGROUND_RED; break;
	case PICoutManipulators::BackGreen: attr_ = (attr_ & mask_back) | BACKGROUND_GREEN; break;
	case PICoutManipulators::BackBlue: attr_ = (attr_ & mask_back) | BACKGROUND_BLUE; break;
	case PICoutManipulators::BackYellow: attr_ = (attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_GREEN; break;
	case PICoutManipulators::BackMagenta: attr_ = (attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_BLUE; break;
	case PICoutManipulators::BackCyan: attr_ = (attr_ & mask_back) | BACKGROUND_GREEN | BACKGROUND_BLUE; break;
	case PICoutManipulators::BackWhite: attr_ = (attr_ & mask_back) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; break;
	case PICoutManipulators::Default: attr_ = dattr; break;
	default: break;
	}
	SetConsoleTextAttribute(hOut, attr_);
#else
	switch (f) {
	case Bin: case Oct: case Dec: case Hex: break;
	case PICoutManipulators::Bold: printf("\e[1m"); break;
	case PICoutManipulators::Faint: printf("\e[2m"); break;
	case PICoutManipulators::Italic: printf("\e[3m"); break;
	case PICoutManipulators::Underline: printf("\e[4m"); break;
	case PICoutManipulators::Blink: printf("\e[5m"); break;
	case PICoutManipulators::Black: printf("\e[30m"); break;
	case PICoutManipulators::Red: printf("\e[31m"); break;
	case PICoutManipulators::Green: printf("\e[32m"); break;
	case PICoutManipulators::Blue: printf("\e[34m"); break;
	case PICoutManipulators::Yellow: printf("\e[33m"); break;
	case PICoutManipulators::Magenta: printf("\e[35m"); break;
	case PICoutManipulators::Cyan: printf("\e[36m"); break;
	case PICoutManipulators::White: printf("\e[37m"); break;
	case PICoutManipulators::BackBlack: printf("\e[40m"); break;
	case PICoutManipulators::BackRed: printf("\e[41m"); break;
	case PICoutManipulators::BackGreen: printf("\e[42m"); break;
	case PICoutManipulators::BackBlue: printf("\e[44m"); break;
	case PICoutManipulators::BackYellow: printf("\e[43m"); break;
	case PICoutManipulators::BackMagenta: printf("\e[45m"); break;
	case PICoutManipulators::BackCyan: printf("\e[46m"); break;
	case PICoutManipulators::BackWhite: printf("\e[47m"); break;
	case PICoutManipulators::Default: printf("\e[0m"); break;
	default: break;
	}
#endif
}


bool PICout::setBufferActive(bool on, bool clear) {
	PIMutexLocker ml(__PICout_mutex__);
	bool ret = PICout::buffer_;
	if (clear) __PICout_string__.clear();
	PICout::buffer_ = on;
	return ret;
}


bool PICout::isBufferActive() {
	return PICout::buffer_;
}


PIString PICout::buffer(bool clear) {
	PIMutexLocker ml(__PICout_mutex__);
	PIString ret = __PICout_string__;
	if (clear) __PICout_string__.clear();
	return ret;
}


void PICout::clearBuffer() {
	PIMutexLocker ml(__PICout_mutex__);
	__PICout_string__.clear();
}


/*! \class PICout
 * \brief Class for formatted output similar std::cout
 * 
 * \section PICout_sec0 Synopsis
 * This class provide many stream operators for output with some features.
 * Output to PICout is thread-sequential, i.e. doesn`t mixed from parallel
 * threads.
 * 
 * \section PICout_sec1 Features
 * - insertion spaces between entries
 * - insertion new line at the end of output
 * - strings are quoted
 * - custom output operator can be easily written
 * 
 * \section PICout_ex0 Usage
 * \snippet picout.cpp 0
 * 
 * \section PICout_ex1 Writing your own output operator
 * \snippet picout.cpp own
 */


/*! \mainpage What is PIP
 * PIP - Platform-Independent Primitives - is crossplatform library for C++ developers.
 * It is wrap around STL and pure C++. This library can help developers write non-GUI
 * projects much more quickly, efficiently and customizable than on pure C++.
 * Library contains many classes, some of them are pure abstract, some classes
 * can be used as they are, some classes should be inherited to new classes.
 * PIP provide classes:
 * * direct output to console (\a PICout)
 * * containers (\a PIVector, \a PIList, \a PIMap, \a PIStack)
 * * byte array (\a PIByteArray)
 * * string (\a PIString, \a PIStringList)
 * * base object (events and handlers) (\a PIObject)
 * * thread (\a PIThread)
 * * timer (\a PITimer)
 * * console (information output) (\a PIConsole)
 *  * stand-alone
 *  * server
 *  * client
 * * i/o devices
 *  * base class (\a PIIODevice)
 *  * file (\a PIFile)
 *  * serial port (\a PISerial)
 *  * ethernet (\a PIEthernet)
 *  * USB (\a PIUSB)
 *  * packets extractor (\a PIPacketExtractor)
 *  * binary log (\a PIBinaryLog)
 * * connection quality diagnotic (\a PIDiagnostics)
 * * command-line arguments parser (\a PICLI)
 * * math evaluator (\a PIEvaluator)
 * * peering net node (\a PIPeer)
 * * process (\a PIProcess)
 * * state machine (\a PIStateMachine)
 * \n \n Basic using of PIP described at page \ref using_basic */


/*! \page using_basic Getting started
 * Many novice programmers are solved many common task with system integrity: output to console,
 * keyboard buttons press detecting, working with serial ports, ethernet or files, and many other.
 * These tasks can solve this library, and code, based only on PIP will be compile and work
 * similar on many systems: Windows, any Linux, Red Hat, FreeBSD, MacOS X and QNX.
 * Typical application on PIP looks like this: \n
\code{.cpp}
#include <pip.h>


// declare key press handler
void key_event(char key, void * );


PIConsole console(false, key_event); // don`t start now, key handler is "key_event"


// some vars
int i = 2, j = 3;


// implicit key press handler
void key_event(char key, void * ) {
	switch (key) {
	case '-':
		i--;
		break;
	case '+':
		i++;
		break;
	case '(':
		j--;
		break;
	case ')':
		j++;
		break;
	};
};


class MainClass: public PITimer {
	PIOBJECT(MainClass)
public:
	MainClass() {}
protected:
	void tick(void * data, int delimiter) {
		piCout << "timer tick";
		// timer tick
	}
};


MainClass main_class;


int main(int argc, char * argv[]) {
	// enabling auto-detection of exit button press, by default 'Q' (shift+q)
	console.enableExitCapture();
	
	// if we want to parse command-line arguments
	PICLI cli(argc, argv);
	cli.addArgument("console"); // "-c" or "--console"
	cli.addArgument("debug"); // "-d" or "--debug"
	
	// enabling or disabling global debug flag
	piDebug = cli.hasArgument("debug");
	
	// configure console
	console.addTab("first tab", '1');
	console.addString("PIP console", 1, PIConsole::Bold);
	console.addVariable("int var (i)", &i, 1);
	console.addVariable("int green var (j)", &j, 1, PIConsole::Green);
	console.addString("'-' - i--", 2);
	console.addString("'+' - i++", 2);
	console.addString("'(' - j--", 2);
	console.addString("')' - j++", 2);
	console.addTab("second tab", '2');
	console.addString("col 1", 1);
	console.addString("col 2", 2);
	console.addString("col 3", 3);
	console.setTab("first tab");
	
	// start output to console if "console" argument exists
	if (cli.hasArgument("console"))
		console.start();
	
	// start main class, e.g. 40 Hz
	main_class.start(25.);
	
	// wait for 'Q' press, independently if console is started or not
	console.waitForFinish();
	
	return 0;
};
\endcode
 * This code demonstrates simple interactive configurable program, which can be started with console
 * display or not, and with debug or not. \b MainClass is central class that also can be inherited from
 * \a PIThread and reimplement \a run() function.
 * \n Many PIP classes has events and event handlers, which can be connected one to another.
 * Details you can see at \a PIObject reference page (\ref PIObject_sec0).
 * \n To configure your program from file use \a PIConfig.
 * \n If you want more information see \ref using_advanced */


/*! \page using_advanced Advanced using
 * Sorry, creativity crisis xD
 */
