/*
    PIP - Platform Independent Primitives
    Virtual terminal
    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 "piterminal.h"

#include "piincludes_p.h"
#include "piliterals_time.h"
#include "pisharedmemory.h"
#ifndef MICRO_PIP
#  ifdef WINDOWS
#	include <windows.h>
#	include <wingdi.h>
#	include <winuser.h>
#  else
#	include "piprocess.h"

#	include <csignal>
#	include <fcntl.h>
#	include <sys/ioctl.h>
#	if defined(QNX) || defined(BLACKBERRY)
#	  include <unix.h>
#	else
#	  ifdef MAC_OS
#		include <util.h>
#	  else
#		include <pty.h>
#	  endif
#	endif
#	ifdef ANDROID
#	  if __ANDROID_API__ >= 23
#		define HAS_FORKPTY
#	  endif
#	else
#	  define HAS_FORKPTY
#	endif
#  endif


// extern PIMutex PICout::__mutex__;

#  ifdef WINDOWS
#	define PIPE_BUFFER_SIZE 1024
enum PITerminalAuxMessageType {
	mtKey = 1,
	mtResize,
	mtScroll
};
struct PITerminalAuxData {
	int cursor_x;
	int cursor_y;
	int size_x;
	int size_y;
	int cells_size;
};
#  else
#	define BUFFER_SIZE 4096
enum DECType {
	CKM = 1
};
#  endif


PRIVATE_DEFINITION_START(PITerminal)
#  ifdef WINDOWS
	PISharedMemory * shm;
	HANDLE hConBuf;
	STARTUPINFOA si;
	PROCESS_INFORMATION pi;
	HANDLE pipe;
#  else
	PIString shell;
	PIByteArray read_buf, tmp_buf;
	PIScreenTypes::CellFormat cur_format, line_format;
	int term_type;
	int fd, cur_x, cur_y;
	int save_cur_x, save_cur_y;
	int win_y0, win_y1;
	pid_t pid;
	PIString esc_seq;
	bool is_esc_seq, last_read;
	PIMap<int, bool> DEC;
	PIVector<PIVector<PIScreenTypes::Cell>> cells_save;
#  endif
PRIVATE_DEFINITION_END(PITerminal)


#  ifdef WINDOWS
int writePipe(HANDLE pipe, const PIByteArray & ba) {
	DWORD wrote[2];
	int sz = ba.size_s();
	WriteFile(pipe, &sz, 4, &(wrote[0]), 0);
	WriteFile(pipe, ba.data(), ba.size_s(), &(wrote[1]), 0);
	// piCout << "send" << ba.size_s();
	return int(wrote[0] + wrote[1]);
}
#  endif


PITerminal::PITerminal(): PIThread() {
	setName("terminal");
	initPrivate();
	cursor_blink = false;
	cursor_x = cursor_y = 0;
	dsize_x             = 80;
	dsize_y             = 24;
#  ifdef WINDOWS
	PRIVATE->shm = 0;
#  endif
}


PITerminal::~PITerminal() {
	if (isRunning()) stop();
	PIThread::waitForFinish(1_s);
	destroy();
#  ifdef WINDOWS
	if (PRIVATE->shm) delete PRIVATE->shm;
#  endif
}


void PITerminal::write(const PIByteArray & d) {
#  ifdef WINDOWS
	PIByteArray msg;
	PIVector<PIKbdListener::KeyEvent> ke;
	for (int i = 0; i < d.size_s(); ++i)
		ke << PIKbdListener::KeyEvent(d[i]);
	msg << int(mtKey) << ke;
	writePipe(PRIVATE->pipe, msg);
#  else
#	ifdef HAS_FORKPTY
	if (PRIVATE->fd == 0) return;
	// ssize_t wrote = 0;
	// wrote =
	auto _r = ::write(PRIVATE->fd, d.data(), d.size_s());
	NO_UNUSED(_r);
	// piCout << "wrote" << wrote << d;
#	endif
#  endif
	cursor_tm.reset();
	cursor_blink = true;
}


void PITerminal::write(PIKbdListener::SpecialKey k, PIKbdListener::KeyModifiers m) {
	PIByteArray ba;
#  ifdef WINDOWS
	switch (k) {
	case PIKbdListener::Tab: ba << uchar('\t'); break;
	case PIKbdListener::Return: ba << uchar('\r') << uchar('\n'); break;
	case PIKbdListener::Space: ba << uchar(' '); break;
	default: break;
	}
	// piCout << "write" << ba.size();
	if (!ba.isEmpty())
		write(ba);
	else {
		PIByteArray msg;
		PIVector<PIKbdListener::KeyEvent> ke;
		ke << PIKbdListener::KeyEvent(k, m);
		msg << int(mtKey) << ke;
		writePipe(PRIVATE->pipe, msg);
	}
#  else
	int term  = PRIVATE->term_type;
	int flags = 0;
	switch (k) {
	case PIKbdListener::Tab: ba << uchar('\t'); break;
	case PIKbdListener::Return: ba << uchar('\n'); break;
	case PIKbdListener::Esc: ba << uchar('\e'); break;
	case PIKbdListener::Space: ba << uchar(' '); break;
	case PIKbdListener::Backspace: ba << uchar(0x7f); break;
	case PIKbdListener::UpArrow:
	case PIKbdListener::DownArrow:
	case PIKbdListener::RightArrow:
	case PIKbdListener::LeftArrow:
		if (PRIVATE->DEC.value(CKM, false)) flags = 1;
	/*case PIKbdListener::Home:       //break;
	case PIKbdListener::End:        //break;
	case PIKbdListener::PageUp:     //ba << uchar('\e') << uchar('[') << uchar('5') << uchar('~'); break;
	case PIKbdListener::PageDown:   //ba << uchar('\e') << uchar('[') << uchar('6') << uchar('~'); break;
	case PIKbdListener::Insert:     //ba << uchar('\e') << uchar('[') << uchar('2') << uchar('~'); break;
	case PIKbdListener::Delete:     //ba << uchar('\e') << uchar('[') << uchar('3') << uchar('~'); break;
	case PIKbdListener::F1:         //break;
	case PIKbdListener::F2:         //break;
	case PIKbdListener::F3:         //break;
	case PIKbdListener::F4:         //break;
	case PIKbdListener::F5:         //break;
	case PIKbdListener::F6:         //break;
	case PIKbdListener::F7:         //break;
	case PIKbdListener::F8:         //break;
	case PIKbdListener::F9:         //break;
	case PIKbdListener::F10:        //break;
	case PIKbdListener::F11:        //break;
	case PIKbdListener::F12:        //break;
	*/
	default: {
		// piCout << flags;
		// int mod = 0;
		if (m[PIKbdListener::Shift]) m |= 1;
		if (m[PIKbdListener::Alt]) m |= 2;
		if (m[PIKbdListener::Ctrl]) m |= 4;
		for (int i = 0;; ++i) {
			const PIKbdListener::EscSeq & e(PIKbdListener::esc_seq[i]);
			if (!e.seq) break;
			// piCout << "search" << rc[1] << esc_seq[i].seq;
			if (e.key == k && e.mod == m) {
				if (((e.vt & term) == term) || (((e.flags & flags) == flags) && (flags != 0))) {
					// piCout << "found key" << PIString(e.seq).replaceAll("\e", "\\e");
					PIByteArray d = ("\e" + PIString(e.seq)).toByteArray();
					write(d);
					break;
				}
			}
		}
	} break;
	}
	// piCout << "write" << ba.size();
	if (!ba.isEmpty()) write(ba);
#  endif
	cursor_tm.reset();
	cursor_blink = true;
}


void PITerminal::write(PIKbdListener::KeyEvent ke) {
	if (isSpecialKey(ke.key))
		write((PIKbdListener::SpecialKey)ke.key, ke.modifiers);
	else {
		PIByteArray ba;
#  ifdef WINDOWS
		ba << uchar(PIChar((ushort)ke.key).toConsole1Byte());
#  else
		ba = PIString(PIChar((ushort)ke.key)).toUTF8();
#  endif
		write(ba);
	}
}


PIVector<PIVector<PIScreenTypes::Cell>> PITerminal::content() {
	readConsole();
	PIVector<PIVector<PIScreenTypes::Cell>> ret = cells;
	if (cursor_blink && cursor_visible)
		if (cursor_x >= 0 && cursor_x < size_x)
			if (cursor_y >= 0 && cursor_y < size_y) ret[cursor_y][cursor_x].format.flags ^= PIScreenTypes::Inverse;
	return ret;
}


bool PITerminal::isSpecialKey(int k) {
	switch (k) {
	case PIKbdListener::Tab:
	case PIKbdListener::Return:
	case PIKbdListener::Esc:
	case PIKbdListener::Space:
	case PIKbdListener::Backspace:
	case PIKbdListener::UpArrow:
	case PIKbdListener::DownArrow:
	case PIKbdListener::RightArrow:
	case PIKbdListener::LeftArrow:
	case PIKbdListener::Home:
	case PIKbdListener::End:
	case PIKbdListener::PageUp:
	case PIKbdListener::PageDown:
	case PIKbdListener::Insert:
	case PIKbdListener::Delete:
	case PIKbdListener::F1:
	case PIKbdListener::F2:
	case PIKbdListener::F3:
	case PIKbdListener::F4:
	case PIKbdListener::F5:
	case PIKbdListener::F6:
	case PIKbdListener::F7:
	case PIKbdListener::F8:
	case PIKbdListener::F9:
	case PIKbdListener::F10:
	case PIKbdListener::F11:
	case PIKbdListener::F12: return true;
	default: return false;
	}
	return false;
}


void PITerminal::initPrivate() {
#  ifdef WINDOWS
	PRIVATE->hConBuf     = INVALID_HANDLE_VALUE;
	PRIVATE->pipe        = INVALID_HANDLE_VALUE;
	PRIVATE->pi.hProcess = 0;
#  else
	PRIVATE->shell = "/bin/bash";
	PRIVATE->read_buf.reserve(BUFFER_SIZE);
	PRIVATE->read_buf.fill(0);
	PRIVATE->fd = PRIVATE->cur_x = PRIVATE->cur_y = 0;
	PRIVATE->save_cur_x = PRIVATE->save_cur_y = 0;
	PRIVATE->pid                              = 0;
	PRIVATE->term_type                        = 0;
	PRIVATE->is_esc_seq                       = false;
	PRIVATE->last_read                        = true;
	PRIVATE->esc_seq.clear();
	PRIVATE->cur_format = PIScreenTypes::CellFormat();
#  endif
	cursor_blink = cursor_visible = true;
	size_x = size_y = 0;
}


void PITerminal::readConsole() {
#  ifdef WINDOWS
	if (!PRIVATE->shm) return;
	PITerminalAuxData data;
	PRIVATE->shm->read(&data, sizeof(data));
	if (data.cells_size <= 4) return;
	cursor_x = data.cursor_x;
	cursor_y = data.cursor_y;
	size_x   = data.size_x;
	size_y   = data.size_y;
	PIByteArray ba;
	ba.resize(data.cells_size);
	PRIVATE->shm->read(ba.data(), ba.size_s(), sizeof(data));
	ba >> cells;
#  endif
	// piCout << cursor_x << cursor_y;
}


void PITerminal::getCursor(int & x, int & y) {
#  ifdef WINDOWS
	if (!PRIVATE->shm) return;
	int sz = 0;
	PRIVATE->shm->read(&sz, 4);
#  else
	x = PRIVATE->cur_x;
	y = PRIVATE->cur_y;
#  endif
}


uchar PITerminal::invertColor(uchar c) {
	switch ((PIScreenTypes::Color)c) {
	case PIScreenTypes::Black: return PIScreenTypes::White;
	case PIScreenTypes::Red: return PIScreenTypes::Cyan;
	case PIScreenTypes::Green: return PIScreenTypes::Magenta;
	case PIScreenTypes::Blue: return PIScreenTypes::Yellow;
	case PIScreenTypes::Cyan: return PIScreenTypes::Red;
	case PIScreenTypes::Magenta: return PIScreenTypes::Green;
	case PIScreenTypes::Yellow: return PIScreenTypes::Blue;
	case PIScreenTypes::White: return PIScreenTypes::Black;
	default: break;
	}
	return PIScreenTypes::White;
}


void PITerminal::run() {
	getCursor(cursor_x, cursor_y);
	if (cursor_tm.elapsed_m() >= 500) {
		cursor_tm.reset();
		cursor_blink = !cursor_blink;
	}
#  ifndef WINDOWS
#	ifdef HAS_FORKPTY
	if (PRIVATE->fd == 0) return;
	PRIVATE->tmp_buf.resize(BUFFER_SIZE);
	int readed = ::read(PRIVATE->fd, PRIVATE->tmp_buf.data(), BUFFER_SIZE - PRIVATE->read_buf.size_s());
	bool used  = false;
	if (readed > 0) {
		PRIVATE->last_read = true;
		// piCoutObj << "readed" << readed << PIString(PRIVATE->tmp_buf.resized(readed)).replaceAll("\e", "\\e");
		// piCoutObj << "readed" << readed << (PRIVATE->tmp_buf.resized(readed));
		PRIVATE->read_buf.append(PRIVATE->tmp_buf.resized(readed));
		for (;;) {
			int ind = -1;
			for (int i = PRIVATE->read_buf.size_s() - 1; i >= 0; --i)
				if (PRIVATE->read_buf[i] == uchar('\n') || PRIVATE->read_buf[i] == uchar('\e')) {
					ind = i;
					break;
				}
			if (ind <= 0) break;
			used = true;
			parseInput(PIString((const char *)PRIVATE->read_buf.data(), ind));
			PRIVATE->read_buf.remove(0, ind);
		}
		bool parse = PRIVATE->read_buf.size_s() >= BUFFER_SIZE;
		if (PRIVATE->read_buf.size_s() == 1)
			if (PRIVATE->read_buf[0] < 0x80) parse = true;
		if (parse) {
			parseInput(PIString::fromUTF8(PRIVATE->read_buf));
			PRIVATE->read_buf.clear();
		}
		// printf("%s", PRIVATE->read_buf.data());
	}
	if (!used && !PRIVATE->last_read && !PRIVATE->read_buf.isEmpty()) {
		parseInput(PIString::fromUTF8(PRIVATE->read_buf));
		PRIVATE->read_buf.clear();
	}
	PRIVATE->last_read = false;
#	endif
#  endif
}


#  ifndef WINDOWS
void PITerminal::parseInput(const PIString & s) {
	// piCoutObj << s.replaceAll("\e", "\\e");
	// printf("%s", s.data());
	for (int i = 0; i < s.size_s(); ++i) {
		if (s[i].unicode16Code() == 0) break;
		if (PRIVATE->is_esc_seq) {
			if (s[i] == '\e') {
				applyEscSeq(PRIVATE->esc_seq);
				PRIVATE->esc_seq.clear();
				PRIVATE->is_esc_seq = true;
			} else {
				PRIVATE->esc_seq += s[i];
				if (isCompleteEscSeq(PRIVATE->esc_seq)) {
					PRIVATE->is_esc_seq = false;
					applyEscSeq(PRIVATE->esc_seq);
					// piCoutObj << PRIVATE->esc_seq;
				}
			}
		} else {
			if (s[i] == '\e') {
				PRIVATE->esc_seq.clear();
				PRIVATE->is_esc_seq = true;
			} else {
				if (s[i] == '\a') continue;
				if (s[i] == '\b') {
					moveCursor(-1, 0);
					continue;
				}
				if (s[i] == '\r') continue;
				if (s[i] == '\n') {
					// piCoutObj << "new line";
					for (int i = PRIVATE->cur_x; i < size_x; ++i)
						cells[PRIVATE->cur_y][i].format = PRIVATE->cur_format;
					PRIVATE->line_format = PRIVATE->cur_format;
					PRIVATE->cur_x       = 0;
					moveCursor(0, 1);
					continue;
				}
				// piCoutObj << "char" << s[i] << s[i].unicode16Code() << "at" << PRIVATE->cur_x << PRIVATE->cur_y;
				cells[PRIVATE->cur_y][PRIVATE->cur_x].symbol = s[i];
				cells[PRIVATE->cur_y][PRIVATE->cur_x].format = PRIVATE->cur_format;
				moveCursor(1, 0);
			}
		}
	}
}


bool PITerminal::isCompleteEscSeq(const PIString & es) {
	if (es.size_s() < 2) return false;
	if (es.front() == ']') {
		if (es.back().toAscii() == '\\' || es.back().toAscii() == '\a') return true;
	} else {
		if (es.back().toAscii() >= 64 && es.back().toAscii() <= 126) return true;
	}
	return false;
}


void PITerminal::applyEscSeq(PIString es) {
	piCoutObj << es;
	if (es.size_s() < 2) return;
	//	PIScreenTypes::Cell line_cell = PIScreenTypes::Cell(' ', PRIVATE->line_format);
	PIScreenTypes::Cell def_cell = PIScreenTypes::Cell(' ', PRIVATE->cur_format);
	if (es[1] == '?' && es.size_s() >= 2) {
		char a   = es.takeRight(1)[0].toAscii();
		bool val = false;
		if (a == 'l') val = false;
		if (a == 'h') val = true;
		int dec = es.mid(2).toInt();
		piCoutObj << "DEC" << dec << val;
		PRIVATE->DEC[dec] = val;
		switch (dec) {
		case 25: cursor_visible = val; break;
		case 1049:
			if (val) {
				PRIVATE->save_cur_x = PRIVATE->cur_x;
				PRIVATE->save_cur_y = PRIVATE->cur_y;
			} else {
				PRIVATE->cur_x = PRIVATE->save_cur_x;
				PRIVATE->cur_y = PRIVATE->save_cur_y;
			}
		case 1047:
			if (val) {
				PRIVATE->cells_save = cells;
				for (int i = 0; i < size_y; ++i)
					cells[i].fill(def_cell);
			} else {
				cells = PRIVATE->cells_save;
			}
			break;
		}
	}
	if (es[0] == '[') { // CSI
		if (es.back() == 'm') {
			es.cutLeft(1).cutRight(1);
			if (es.isEmpty()) {
				PRIVATE->cur_format = PIScreenTypes::CellFormat();
				return;
			}
			PIStringList args = es.split(";");
			for (const auto & a: args) {
				int av = a.toInt();
				switch (av) {
				case 0: PRIVATE->cur_format = PIScreenTypes::CellFormat(); break;
				case 1: PRIVATE->cur_format.flags |= PIScreenTypes::Bold; break;
				case 4: PRIVATE->cur_format.flags |= PIScreenTypes::Underline; break;
				case 5: PRIVATE->cur_format.flags |= PIScreenTypes::Blink; break;
				case 7: PRIVATE->cur_format.flags |= PIScreenTypes::Inverse; break;
				default: {
					bool col = false, target = false;
					int cid = av % 10;
					if (av >= 30 && av <= 37) {
						col    = true;
						target = false;
					}
					if (av >= 40 && av <= 47) {
						col    = true;
						target = true;
					}
					if (col) {
						int cfl = 0;
						switch (cid) {
						case 0: cfl = PIScreenTypes::Black; break;
						case 1: cfl = PIScreenTypes::Red; break;
						case 2: cfl = PIScreenTypes::Green; break;
						case 3: cfl = PIScreenTypes::Yellow; break;
						case 4: cfl = PIScreenTypes::Blue; break;
						case 5: cfl = PIScreenTypes::Magenta; break;
						case 6: cfl = PIScreenTypes::Cyan; break;
						case 7: cfl = PIScreenTypes::White; break;
						}
						if (target)
							PRIVATE->cur_format.color_back = cfl;
						else
							PRIVATE->cur_format.color_char = cfl;
						break;
					}
				} break;
				}
			}
			/*if ((PRIVATE->cur_format.flags & PIScreenTypes::Inverse) == PIScreenTypes::Inverse) {
			    uchar t = PRIVATE->cur_format.color_char;
			    PRIVATE->cur_format.color_char = PRIVATE->cur_format.color_back;
			    PRIVATE->cur_format.color_back = t;
			}*/
		}
		if (es.back() == 'r') {
			piCoutObj << es;
			es.cutLeft(1).cutRight(1);
			PIStringList args = es.split(";");
			args.resize(2);
			int y0(0), y1(0);
			if (!args[0].isEmpty()) y0 = args[0].toInt() - 1;
			if (!args[1].isEmpty()) y1 = args[1].toInt() - 1;
			PRIVATE->win_y0 = piClamp(y0, 0, size_y - 1);
			PRIVATE->win_y1 = piClamp(y1, 0, size_y - 1);
		}
		if (es.back() == 's') {
			PRIVATE->save_cur_x = PRIVATE->cur_x;
			PRIVATE->save_cur_y = PRIVATE->cur_y;
		}
		if (es.back() == 'u') {
			PRIVATE->cur_x = PRIVATE->save_cur_x;
			PRIVATE->cur_y = PRIVATE->save_cur_y;
		}
		if (es.back() == 'H' || es.back() == 'f' || es.back() == 'r') {
			es.cutLeft(1).cutRight(1);
			PIStringList args = es.split(";");
			args.resize(2);
			int x(0), y(0);
			if (!args[0].isEmpty()) y = args[0].toInt() - 1;
			if (!args[1].isEmpty()) x = args[1].toInt() - 1;
			// piCoutObj << x << y;
			PRIVATE->cur_x       = piClamp(x, 0, size_x - 1);
			PRIVATE->cur_y       = piClamp(y, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'A') { // cursor up
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_y       = piClamp(PRIVATE->cur_y - v, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'B') { // cursor down
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_y       = piClamp(PRIVATE->cur_y + v, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'C' || es.back() == 'a') { // cursor forward, next column
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_x       = piClamp(PRIVATE->cur_x + v, 0, size_x - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'D') { // cursor back
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_x       = piClamp(PRIVATE->cur_x - v, 0, size_x - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'G' || es.back() == '`') { // goto column
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v) PRIVATE->cur_x = piClamp(v - 1, 0, size_x - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'd') { // goto line
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_x       = 0;
			PRIVATE->cur_y       = piClamp(v - 1, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'E' || es.back() == 'e') { // next line
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_x       = 0;
			PRIVATE->cur_y       = piClamp(PRIVATE->cur_y + v, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'F') { // previous line
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			PRIVATE->cur_x       = 0;
			PRIVATE->cur_y       = piClamp(PRIVATE->cur_y - v, 0, size_y - 1);
			PRIVATE->line_format = PRIVATE->cur_format;
		}
		if (es.back() == 'L') { // insert lines
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			for (int i = piClamp(size_y - 1, PRIVATE->win_y0, PRIVATE->win_y1);
			     i >= piClamp(PRIVATE->cur_y + v, PRIVATE->win_y0, PRIVATE->win_y1);
			     --i)
				cells[i] = cells[i - v];
			for (int j = piClamp(PRIVATE->cur_y, PRIVATE->win_y0, PRIVATE->win_y1);
			     j < piClamp(PRIVATE->cur_y + v, PRIVATE->win_y0, PRIVATE->win_y1);
			     ++j)
				for (int i = 0; i < PRIVATE->cur_x; ++i)
					cells[j][i] = def_cell;
		}
		if (es.back() == 'M') { // delete lines
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			for (int i = piClamp(PRIVATE->cur_y, PRIVATE->win_y0, PRIVATE->win_y1);
			     i < piClamp(size_y - v, PRIVATE->win_y0, PRIVATE->win_y1);
			     ++i)
				cells[i] = cells[i + v];
			for (int j = piClamp(size_y - v, PRIVATE->win_y0, PRIVATE->win_y1); j < piClamp(size_y, PRIVATE->win_y0, PRIVATE->win_y1); ++j)
				for (int i = 0; i < PRIVATE->cur_x; ++i)
					cells[j][i] = def_cell;
		}
		if (es.back() == 'P') { // delete characters
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			for (int i = PRIVATE->cur_x; i < size_x - v; ++i)
				cells[PRIVATE->cur_y][i] = cells[PRIVATE->cur_y][i + v];
			for (int i = size_x - v; i < size_x; ++i)
				cells[PRIVATE->cur_y][i] = def_cell;
		}
		if (es.back() == '@') { // delete characters
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			if (v == 0) v = 1;
			for (int i = size_x - 1; i >= PRIVATE->cur_x + v; --i)
				cells[PRIVATE->cur_y][i] = cells[PRIVATE->cur_y][i - v];
			for (int i = PRIVATE->cur_x; i < PRIVATE->cur_x + v; ++i)
				cells[PRIVATE->cur_y][i] = def_cell;
		}
		if (es.back() == 'J') { // erase data
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			switch (v) {
			case 0:
				for (int i = PRIVATE->cur_x; i < size_x; ++i)
					cells[PRIVATE->cur_y][i] = def_cell;
				for (int i = PRIVATE->cur_y + 1; i < size_y; ++i)
					cells[i].fill(def_cell);
				break;
			case 1:
				for (int i = 0; i <= PRIVATE->cur_x; ++i)
					cells[PRIVATE->cur_y][i] = def_cell;
				for (int i = 0; i < PRIVATE->cur_y; ++i)
					cells[i].fill(def_cell);
				break;
			case 2:
				for (int i = 0; i < size_y; ++i)
					cells[i].fill(def_cell);
				// PRIVATE->cur_x = PRIVATE->cur_y = 0;
				break;
			}
		}
		if (es.back() == 'K') { // erase in line
			es.cutLeft(1).cutRight(1);
			int v = es.toInt();
			switch (v) {
			case 0:
				for (int i = PRIVATE->cur_x; i < size_x; ++i)
					cells[PRIVATE->cur_y][i] = def_cell;
				break;
			case 1:
				for (int i = 0; i <= PRIVATE->cur_x; ++i)
					cells[PRIVATE->cur_y][i] = def_cell;
				break;
			case 2: cells[PRIVATE->cur_y].fill(def_cell); break;
			}
		}
	}
}


void PITerminal::moveCursor(int dx, int dy) {
	PRIVATE->cur_x += dx;
	PRIVATE->cur_y += dy;
	if (PRIVATE->cur_x < 0) PRIVATE->cur_x = 0;
	if (PRIVATE->cur_y < 0) PRIVATE->cur_y = 0;
	if (PRIVATE->cur_x >= size_x) {
		PRIVATE->line_format = PRIVATE->cur_format;
		PRIVATE->cur_x       = 0;
		PRIVATE->cur_y++;
	}
	if (PRIVATE->cur_y >= size_y) {
		int scroll     = piMini(PRIVATE->cur_y - size_y + 1, size_y - 1);
		// piCout << "scroll" << size_x << size_y << size_y - scroll - 1;
		PRIVATE->cur_y = size_y - 1;
		for (int y = 0; y < size_y - scroll; ++y)
			cells[y] = cells[y + scroll];
		for (int y = size_y - scroll; y < size_y; ++y)
			cells[y].fill(PIScreenTypes::Cell());
	}
}


int PITerminal::termType(const PIString & t) {
	if (t == "xterm")
		return PIKbdListener::vt_xterm;
	else if (t == "linux")
		return PIKbdListener::vt_linux;
	return PIKbdListener::vt_none;
}
#  endif


bool PITerminal::initialize() {
	destroy();
#  ifdef WINDOWS
	/*SECURITY_ATTRIBUTES sa;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = true;
	sa.lpSecurityDescriptor = 0;
	if (!CreatePipe(&(PRIVATE->pipe_in[0]), &(PRIVATE->pipe_in[1]), &sa, 0)) {
	    piCoutObj << "CreatePipe error," << errorString();
	    initPrivate();
	    return false;
	}
	PRIVATE->hConBuf = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, &sa, CONSOLE_TEXTMODE_BUFFER, 0);
	if (PRIVATE->hConBuf == INVALID_HANDLE_VALUE) {
	    piCoutObj << "CreateConsoleScreenBuffer error," << errorString();
	    destroy();
	    return false;
	}*/
	// CreatePipe(&(PRIVATE->pipe_out[0]), &(PRIVATE->pipe_out[1]), &sa, 0);
	// SetHandleInformation(PRIVATE->pipe_in[1], HANDLE_FLAG_INHERIT, 0);
	// SetHandleInformation(PRIVATE->hConBuf, HANDLE_FLAG_INHERIT, 0);
	// GetStartupInfoA(&PRIVATE->si);
	piZeroMemory(PRIVATE->si);
	PRIVATE->si.cb = sizeof(STARTUPINFO);
	// PRIVATE->si.dwFlags |= STARTF_USESTDHANDLES;
	PRIVATE->si.dwFlags |= STARTF_USESHOWWINDOW;
	PRIVATE->si.dwFlags |= STARTF_USECOUNTCHARS;
	// PRIVATE->si.hStdInput = PRIVATE->pipe;
	// PRIVATE->si.hStdOutput = PRIVATE->hConBuf;
	// PRIVATE->si.hStdError = PRIVATE->hConBuf;
	PRIVATE->si.wShowWindow   = SW_HIDE;
	PRIVATE->si.dwXCountChars = 80;
	PRIVATE->si.dwYCountChars = 24;

	piZeroMemory(PRIVATE->pi);

	PIString shmh  = PIString::fromNumber(randomi() % 10000);
	PIString pname = "\\\\.\\pipe\\piterm" + shmh;
	PIString cmd   = "piterminal \"" + shmh + "\" \"" + pname + "\"";
	if (!CreateProcessA(0,
	                    (LPSTR)cmd.dataAscii(),
	                    0,
	                    0,
	                    false,
	                    CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
	                    0,
	                    0,
	                    &PRIVATE->si,
	                    &PRIVATE->pi)) {
		piCoutObj << "CreateProcess error," << errorString();
		destroy();
		return false;
	}
	PRIVATE->pipe = CreateNamedPipeA((LPSTR)pname.dataAscii(),
	                                 PIPE_ACCESS_DUPLEX,
	                                 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
	                                 2,
	                                 PIPE_BUFFER_SIZE,
	                                 PIPE_BUFFER_SIZE,
	                                 1000,
	                                 NULL);
	if (PRIVATE->pipe == INVALID_HANDLE_VALUE) {
		piCoutObj << "CreateNamedPipe error," << errorString();
		destroy();
		return false;
	}
	PITimeMeasurer tm;
	bool ok = false;
	while (tm.elapsed_m() < 1000) {
		if (ConnectNamedPipe(PRIVATE->pipe, 0) == TRUE) {
			ok = true;
			break;
		}
	}
	if (!ok) {
		piCoutObj << "ConnectNamedPipe error," << errorString();
		destroy();
		return false;
	}
	if (PRIVATE->shm) delete PRIVATE->shm;
	PRIVATE->shm = new PISharedMemory("piterm_aux" + shmh, 1024 * 1024);
	CloseHandle(PRIVATE->pi.hThread);
	resize(dsize_x, dsize_y);
#  else
#	ifdef HAS_FORKPTY
	char pty[256];
	piZeroMemory(pty, 256);
	winsize ws;
	ws.ws_col        = dsize_x;
	ws.ws_row        = dsize_y;
	PIStringList env = PIProcess::currentEnvironment();
	for (const auto & e: env)
		if (e.startsWith("TERM=")) {
			PRIVATE->term_type = termType(e.mid(5).trim().toLowerCase());
			// piCout << PRIVATE->term_type;
			break;
		}
	pid_t fr = forkpty(&(PRIVATE->fd), pty, 0, &ws);
	// piCoutObj << fr << PRIVATE->fd << pty;
	if (fr == 0) {
		char ** argv      = new char *[2];
		PIByteArray shell = PRIVATE->shell.toByteArray();
		argv[0]           = new char[shell.size() + 1];
		memcpy(argv[0], shell.data(), shell.size());
		argv[0][shell.size()] = 0;
		argv[1]               = 0;
		execvp(argv[0], argv);
		delete[] argv[0];
		delete[] argv;
		exit(errno);
	} else {
		if (fr < 0 || PRIVATE->fd < 0) {
			piCoutObj << "forkpty error," << errorString();
			initPrivate();
			return false;
		}
		PRIVATE->pid = fr;
		fcntl(PRIVATE->fd, F_SETFL, O_NONBLOCK);
		/*
		tcgetattr(PRIVATE->fd, &PRIVATE->desc);
		PRIVATE->desc.c_oflag = PRIVATE->desc.c_lflag = PRIVATE->desc.c_cflag = 0;
		PRIVATE->desc.c_iflag = IGNBRK;
		PRIVATE->desc.c_cflag = CLOCAL | HUPCL;
		PRIVATE->desc.c_cflag |= (CSIZE & CS8);
		PRIVATE->desc.c_cflag |= CREAD;
		PRIVATE->desc.c_cc[VMIN] = 1;
		PRIVATE->desc.c_cc[VTIME] = 1;

		cfsetispeed(&PRIVATE->desc, B38400);
		cfsetospeed(&PRIVATE->desc, B38400);

		if (tcsetattr(PRIVATE->fd, TCSANOW, &PRIVATE->desc) < 0) {
		    piCoutObj << "Can`t set attributes for \"" << pty << "\"";
		    destroy();
		    return false;
		}
		*/
		size_x = dsize_x;
		size_y = dsize_y;
		resize(size_x, size_y);
	}
#	endif
#  endif
	cursor_blink = false;
	cursor_tm.reset();
	start(25_Hz);
	return true;
}


void PITerminal::destroy() {
	// piCout << "destroy ...";
	stop();
	waitForFinish(1_s);
#  ifdef WINDOWS
	if (PRIVATE->pi.hProcess) {
		// piCout << "term";
		// TerminateProcess(PRIVATE->pi.hProcess, 0);
		GenerateConsoleCtrlEvent(CTRL_C_EVENT, PRIVATE->pi.dwProcessId);
		CloseHandle(PRIVATE->pi.hProcess);
	}
	if (PRIVATE->pipe != INVALID_HANDLE_VALUE) CloseHandle(PRIVATE->pipe);
	if (PRIVATE->hConBuf != INVALID_HANDLE_VALUE) CloseHandle(PRIVATE->hConBuf);
	// piCout << "destroy" << size_y;
#  else
#	ifdef HAS_FORKPTY
	if (PRIVATE->pid != 0) kill(PRIVATE->pid, SIGKILL);
	if (PRIVATE->fd != 0) ::close(PRIVATE->fd);
#	endif
#  endif
	initPrivate();
}


bool PITerminal::resize(int cols, int rows) {
	bool ret = true;
	dsize_x  = cols;
	dsize_y  = rows;
#  ifdef WINDOWS
	if (PRIVATE->pipe == INVALID_HANDLE_VALUE) return false;
	PIByteArray msg;
	msg << int(mtResize) << dsize_x << dsize_y;
	writePipe(PRIVATE->pipe, msg);
#  else
#	ifdef HAS_FORKPTY
	if (PRIVATE->fd == 0) return false;
	size_x = dsize_x;
	size_y = dsize_y;
	// piCout << "resize" << PRIVATE->fd << size_x << size_y;
	winsize ws;
	ws.ws_col = cols;
	ws.ws_row = rows;
	ioctl(PRIVATE->fd, TIOCSWINSZ, &ws);
	PRIVATE->win_y0 = 0;
	PRIVATE->win_y1 = size_y - 1;
	PRIVATE->cells_save.resize(size_y);
	for (int i = 0; i < size_y; ++i)
		PRIVATE->cells_save[i].resize(size_x);
#	endif
#  endif
	cells.resize(size_y);
	for (int i = 0; i < size_y; ++i)
		cells[i].resize(size_x);
	return ret;
}

#endif // MICRO_PIP
