/*
    PIP - Platform Independent Primitives
    Terminal client for windows, used by PITerminal and pisd
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "piincludes_p.h"
#ifndef WINDOWS
int main(int argc, char * argv[]) {
	return 0;
}
#else
#  define PIP_CONSOLE_STATIC_DEFINE
#  include "../../libs/console/piterminal.cpp"
#  include "pifile.h"
#  include "piliterals_time.h"
#  include "pip_console_export.h"
#  include "piscreentypes.h"
#  include "pisharedmemory.h"

// clang-format off
#  include <wingdi.h>
#  include <wincon.h>
// clang-format on


PIVector<PIVector<PIScreenTypes::Cell>> cells;
CONSOLE_SCREEN_BUFFER_INFO sbi;
CHAR_INFO * chars = 0;
HANDLE console = 0, cstdin = 0, pipe = 0, cmd_proc = 0;
PITerminalAuxData data_out;
PIMutex con_mutex;
int con_w = -1, con_h = -1;


PIScreenTypes::Cell CharInfo2Cell(const CHAR_INFO & c) {
	PIScreenTypes::Cell ret;
	ret.symbol            = PIChar::fromConsole(c.Char.AsciiChar);
	ret.format.color_char = PIScreenTypes::Black;
	if ((c.Attributes & (FOREGROUND_RED)) == FOREGROUND_RED) ret.format.color_char = PIScreenTypes::Red;
	if ((c.Attributes & (FOREGROUND_GREEN)) == FOREGROUND_GREEN) ret.format.color_char = PIScreenTypes::Green;
	if ((c.Attributes & (FOREGROUND_BLUE)) == FOREGROUND_BLUE) ret.format.color_char = PIScreenTypes::Blue;
	if ((c.Attributes & (FOREGROUND_GREEN | FOREGROUND_BLUE)) == (FOREGROUND_GREEN | FOREGROUND_BLUE))
		ret.format.color_char = PIScreenTypes::Cyan;
	if ((c.Attributes & (FOREGROUND_RED | FOREGROUND_BLUE)) == (FOREGROUND_RED | FOREGROUND_BLUE))
		ret.format.color_char = PIScreenTypes::Magenta;
	if ((c.Attributes & (FOREGROUND_RED | FOREGROUND_GREEN)) == (FOREGROUND_RED | FOREGROUND_GREEN))
		ret.format.color_char = PIScreenTypes::Yellow;
	if ((c.Attributes & (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)) == (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE))
		ret.format.color_char = PIScreenTypes::White;
	ret.format.color_back = PIScreenTypes::Black;
	if ((c.Attributes & (BACKGROUND_RED)) == (BACKGROUND_RED)) ret.format.color_back = PIScreenTypes::Red;
	if ((c.Attributes & (BACKGROUND_GREEN)) == (BACKGROUND_GREEN)) ret.format.color_back = PIScreenTypes::Green;
	if ((c.Attributes & (BACKGROUND_BLUE)) == (BACKGROUND_BLUE)) ret.format.color_back = PIScreenTypes::Blue;
	if ((c.Attributes & (BACKGROUND_GREEN | BACKGROUND_BLUE)) == (BACKGROUND_GREEN | BACKGROUND_BLUE))
		ret.format.color_back = PIScreenTypes::Cyan;
	if ((c.Attributes & (BACKGROUND_RED | BACKGROUND_BLUE)) == (BACKGROUND_RED | BACKGROUND_BLUE))
		ret.format.color_back = PIScreenTypes::Magenta;
	if ((c.Attributes & (BACKGROUND_RED | BACKGROUND_GREEN)) == (BACKGROUND_RED | BACKGROUND_GREEN))
		ret.format.color_back = PIScreenTypes::Yellow;
	if ((c.Attributes & (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)) == (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE))
		ret.format.color_back = PIScreenTypes::White;
	if ((c.Attributes & (FOREGROUND_INTENSITY)) > 0) ret.format.flags |= PIScreenTypes::Bold;
	return ret;
}


int SpecialKey2VirtualKeyCode(PIKbdListener::SpecialKey k) {
	switch (k) {
	case PIKbdListener::Tab: return '\t'; break;
	case PIKbdListener::Return: return '\n'; break;
	case PIKbdListener::Space: return ' '; break;
	case PIKbdListener::Backspace: return 8;
	case PIKbdListener::PageUp: return 33;
	case PIKbdListener::PageDown: return 34;
	case PIKbdListener::End: return 35;
	case PIKbdListener::Home: return 36;
	case PIKbdListener::LeftArrow: return 37;
	case PIKbdListener::UpArrow: return 38;
	case PIKbdListener::RightArrow: return 39;
	case PIKbdListener::DownArrow: return 40;
	case PIKbdListener::Insert: return 45;
	case PIKbdListener::Delete: return 46;
	case PIKbdListener::F1: return 112;
	case PIKbdListener::F2: return 113;
	case PIKbdListener::F3: return 114;
	case PIKbdListener::F4: return 115;
	case PIKbdListener::F5: return 116;
	case PIKbdListener::F6: return 117;
	case PIKbdListener::F7: return 118;
	case PIKbdListener::F8: return 119;
	case PIKbdListener::F9: return 120;
	case PIKbdListener::F10: return 121;
	case PIKbdListener::F11: return 122;
	case PIKbdListener::F12: return 123;
	default: break;
	}
	return 0;
}


int KeyModifiers2ControlKeyState(PIKbdListener::KeyModifiers m) {
	int ret(0);
	if (m[PIKbdListener::Ctrl]) ret |= LEFT_CTRL_PRESSED;
	if (m[PIKbdListener::Shift]) ret |= SHIFT_PRESSED;
	if (m[PIKbdListener::Alt]) ret |= LEFT_ALT_PRESSED;
	return ret;
}


void readConsole(int x, int y, int w, int h) {
	GetConsoleScreenBufferInfo(console, &sbi);
	COORD bs, bc;
	bs.X = w;
	bs.Y = h;
	bc.X = bc.Y = 0;
	piZeroMemory(chars, w * h * sizeof(CHAR_INFO));
	ReadConsoleOutput(console, chars, bs, bc, &(sbi.srWindow));
	for (int i = 0; i < h; ++i)
		for (int j = 0; j < w; ++j)
			cells[i][j] = CharInfo2Cell(chars[i * w + j]);
}


void resizeCells(int w, int h) {
	if (chars) delete[] chars;
	chars = new CHAR_INFO[w * h];
	cells.resize(h);
	for (int i = 0; i < h; ++i)
		cells[i].resize(w);
}


void resizeConsole(int w, int h) {
	if (con_w == w && con_h == h) return;
	con_w = w;
	con_h = h;
	GetConsoleScreenBufferInfo(console, &sbi);
	sbi.srWindow.Left   = 0;
	sbi.srWindow.Right  = sbi.srWindow.Left + w - 1;
	sbi.srWindow.Bottom = sbi.srWindow.Top + h - 1;
	COORD sz;
	sz.X = w;
	sz.Y = h;
	SetConsoleScreenBufferSize(console, sz);
	SetConsoleWindowInfo(console, TRUE, &(sbi.srWindow));
	// system(("mode CON: COLS=" + PIString::fromNumber(w) + " LINES=" + PIString::fromNumber(h)).dataAscii());
	resizeCells(w, h);
}


class PipeReader: public PIThread {
public:
	PipeReader(): PIThread() {
		wrote = readed = 0;
		msg_size       = 0;
		start(1_ms);
	}
	~PipeReader() {
		stop();
		if (!waitForFinish(100_ms)) terminate();
	}
	void run() {
		in.resize(PIPE_BUFFER_SIZE);
		ReadFile(pipe, in.data(), in.size_s(), &readed, 0);
		if (GetLastError() == ERROR_BROKEN_PIPE) {
			stop();
			return;
		}
		// piCout << errorString();
		if (readed > 0) {
			in.resize(readed);
			stream.append(in);
			if (msg_size == 0) {
				if (stream.size_s() < 4) return;
				stream >> msg_size;
			}
			if (stream.size_s() >= msg_size) {
				msg = PIByteArray(stream.data(), msg_size);
				stream.remove(0, msg_size);
				msg_size = 0;
				parseMessage();
			}
			if (msg_size == 0 && stream.size_s() < 4) return;
		}
	}
	void parseMessage() {
		if (msg.size_s() < 4) return;
		PIMutexLocker _ml(con_mutex);
		int type = 0;
		msg >> type;
		// piCout << "msg" << type;
		switch ((PITerminalAuxMessageType)type) {
		case mtKey: {
			PIVector<PIKbdListener::KeyEvent> ke;
			msg >> ke;
			PIVector<INPUT_RECORD> ir(ke.size() * 2);
			for (int i = 0; i < ke.size_s(); ++i) {
				PIKbdListener::KeyEvent k(ke[i]);
				int j = i + i, z = j + 1;
				ir[j].EventType                        = KEY_EVENT;
				ir[j].Event.KeyEvent.wRepeatCount      = 1;
				ir[j].Event.KeyEvent.dwControlKeyState = KeyModifiers2ControlKeyState(k.modifiers);
				if (PITerminal::isSpecialKey(k.key)) {
					ir[j].Event.KeyEvent.wVirtualKeyCode = SpecialKey2VirtualKeyCode((PIKbdListener::SpecialKey)k.key);
					ir[j].Event.KeyEvent.uChar.AsciiChar = PIChar((ushort)piMaxi(k.key, 0)).toAscii();
				} else
					ir[j].Event.KeyEvent.uChar.UnicodeChar = PIChar((ushort)piMaxi(k.key, 0)).toWChar();
				// piCout << ir[j].Event.KeyEvent.wVirtualKeyCode << int(ir[j].Event.KeyEvent.uChar.AsciiChar);
				ir[j].Event.KeyEvent.bKeyDown = true;
				ir[z]                         = ir[j];
				ir[z].Event.KeyEvent.bKeyDown = false;
			}
			WriteConsoleInput(cstdin, ir.data(), ir.size_s(), &wrote);
		} break;
		case mtResize: {
			int rw = -1, rh = -1;
			msg >> rw >> rh;
			if (rw > 0 && rh > 0) resizeConsole(rw, rh);
		} break;
		default: break;
		}
	}
	DWORD wrote, readed;
	int msg_size;
	PIByteArray in, stream, msg;
};


void getCursor(int & x, int & y) {
	GetConsoleScreenBufferInfo(console, &sbi);
	x = sbi.dwCursorPosition.X - sbi.srWindow.Left;
	y = sbi.dwCursorPosition.Y - sbi.srWindow.Top;
}


int main(int argc, char * argv[]) {
	// piCout << "start";
	STARTUPINFOA si;
	PROCESS_INFORMATION pi;
	piZeroMemory(si);
	piZeroMemory(pi);
	piZeroMemory(sbi);
	PIString shmh, pname;
	if (argc > 1) shmh = argv[1];
	if (argc > 2) pname = argv[2];
	if (!CreateProcessA(0, (LPSTR) "cmd", 0, 0, true, 0, 0, 0, &si, &pi)) {
		return 1;
	}
	PISharedMemory shm("piterm_aux" + shmh, 1024 * 1024);
	pipe = CreateFileA((LPSTR)pname.dataAscii(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
	if (pipe == INVALID_HANDLE_VALUE) {
		return 1;
	}
	CloseHandle(pi.hThread);
	cmd_proc = pi.hProcess;
	console  = GetStdHandle(STD_OUTPUT_HANDLE);
	cstdin   = GetStdHandle(STD_INPUT_HANDLE);
	resizeConsole(80, 24);
	PipeReader pipe_reader;
	pipe_reader.waitForStart();
	PIByteArray scr;
	while (true) {
		// piCout << "loop";
		if (!pipe_reader.isRunning()) break;
		con_mutex.lock();
		getCursor(data_out.cursor_x, data_out.cursor_y);
		readConsole(0, 0, con_w, con_h);
		scr.clear();
		scr << cells;
		data_out.size_x     = con_w;
		data_out.size_y     = con_h;
		data_out.cells_size = scr.size_s();
		con_mutex.unlock();
		shm.write(&data_out, sizeof(data_out));
		shm.write(scr, sizeof(data_out));
		piMSleep(25);
	}
	// piCout << "exit";
	TerminateProcess(pi.hProcess, 0);
	CloseHandle(pi.hProcess);
	return 0;
}


#endif
