/*	Copyright  (c)	Günter Woigk 2016 - 2016
					mailto:kio@little-bat.de

	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.

	Permission to use, copy, modify, distribute, and sell this software and
	its documentation for any purpose is hereby granted without fee, provided
	that the above copyright notice appear in all copies and that both that
	copyright notice and this permission notice appear in supporting
	documentation, and that the name of the copyright holder not be used
	in advertising or publicity pertaining to distribution of the software
	without specific, written prior permission.  The copyright holder makes no
	representations about the suitability of this software for any purpose.
	It is provided "as is" without express or implied warranty.

	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
	PERFORMANCE OF THIS SOFTWARE.
*/

#define SAFE 3
#define LOG 1
#include "kio/kio.h"
#include "MainWindow.h"
#include <QOpenGLWindow>
#include <QPainter>
#include "unix/pthreads.h"
#include <QThread>
#include <QMutex>
#include <QSemaphore>
#include "unix/os_utilities.h"
#include <QKeyEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QMenuBar>
#include <QSerialPortInfo>
#include "IOPort.h"
#include "Painter.h"


// settings:

//#define WIDTH  	800				// Default Terminal Window size
//#define HEIGHT	600
//#define MAX_STREAMS 240			// max. number of stream	max. 240 wg. error correction protocol
QColor background_color(Qt::black);	// 0xAARRGGBB

bool enable_fs  = no;				// expose file system?
bool enable_gfx = no;				// graphics terminal enabled?
bool enable_kbd = no;				// expose keyboard?
bool enable_ptr = no;				// expose pointer device (mouse)?
bool enable_joy = no;				// expose joysticks?
uint32 max_mem  = 64 MB;			// ram usage limit

bool server_selected = no;			// ip socket server in use?
uint socket_port = 20042;			// port in use
bool pipe_selected = no;			// named pipes in use?
cstr pipe_path = "/tmp/GTermPipe";	// fs path
bool stdio_selected = no;			// stdin/stdout in use?
int  selected_sio = 0;				// serial port in use: idx in sio_infos, -1 = none
uint sio_baudrate = 19200;			// baudrate 8n1
uint max_streams = MAX_STREAMS;		// max. stream number
uint transmission_mode = 0;

// state:

MainWindow* main_window = NULL;		// GTerm Window
RCPtr<Object> offscreenobjects;		// objects[0]
RCPtr<Object> framebufferobject;	// objects[1]
QList<QSerialPortInfo> sio_infos;	// List of serial devices
static class Worker* workers[4];
static uint num_workers = 0;
static IOPort* ioport = NULL;

static uint	stream_kbd_events	= 0;
static uint	stream_mouse_events	= 0;
static uint	stream_ffb_events	= 0;
static uint	registered_events	= 0x0000;
enum
{
	KeyDownEventMask	= 1,
	KeyUpEventMask		= 2,
	MouseDownEventMask	= 4,
	MouseUpEventMask	= 8,
	MouseMoveEventMask	= 16,
	PaintFrameEventMask	= 32
};
static bool update_flag = true;		// screen update flag





// ==============================================================
//						Workers:
// ==============================================================


typedef uint WhoID;

class Worker : public QThread
{
	WhoID	wid;
	uchar	bu[4000];
	uint	a;				// start of unescaped data
	uint	i;				// start of raw (escaped) data
	uint	e;				// start of free space

public:
			Worker(WhoID w)		:wid(w){ XLogLine("new Worker %u",w); }
	void	run()				override;

	uint	next_char();		// get, unescape and skip over next char. rval ≥ $100 = CTL code
	void	read_raw();			// read more bytes from ioport
	void	next_bytes(uint n) throw(uint);	// read more bytes for arguments
};


void Worker::read_raw()
{
	if(e >= sizeof(bu)-99)
	{
		memmove(bu,bu+a,e-a);
		e -= a;
		i -= a;
		a  = 0;
	}

a:	uint n = ioport->read(bu+e,sizeof(bu)-e);
	if(n==0) { usleep(5000); goto a; }
	e += n;
}




/*	next char:
	$0xx	= regular char
	$1xx	= CTL + byte: command or stream number
	$2xx	= CTL + CTL + byte: for one drink to many..

	unescapes CTL, XON and XOFF
	handles transparently asynchronous ACK and NAK blocks (TODO: skips them only)
*/
uint Worker::next_char()
{
a:	if(i==e) read_raw();
	uint c = bu[i++];
	if(c != CTL) return c;		// regular char

	if(i==e) read_raw();
	c = bu[i++];
	switch(c)
	{
	case esc_CTL:	i-=2; bu[i++]=CTL;  e--; memmove(bu+i,bu+i+1,e-i); return CTL;	// regular char
	case esc_XOFF:	i-=2; bu[i++]=XOFF; e--; memmove(bu+i,bu+i+1,e-i); return XOFF;	// regular char
	case esc_XON:	i-=2; bu[i++]=XON;  e--; memmove(bu+i,bu+i+1,e-i); return XON;	// regular char

	case ACK:		// async ACK/NAK block:
	case NAK:
	{
		cstr ack = c==ACK ? "ACK" : "NAK";
		LogLine("READ INPUT: CTL + $s: EC mode not supported",ack);
		c = next_char();
		if(c>0xff) { LogLine("%s block truncated",ack); memmove(bu+i-3,bu+i-1,e-i+1); i-=2; e-=2; return c; }
		c = next_char();
		if(c>0xff) { LogLine("%s block truncated",ack); memmove(bu+i-4,bu+i-1,e-i+1); i-=3; e-=3; return c; }

		memmove(bu+i-4,bu+i,e-i); i-=4; e-=4;
		goto a;
	}
	case CTL:
		i--; return 0x100 + next_char();
	}
	return 0x100 + c;
}


/*	get n unescaped bytes for CTL arguments
	an unexpected CTL code stops argument reading
	return 0:		all bytes have been read and i is updated accordingly
	return > $FF:	a CTL code was encountered. the code is returned and i is already stepped over it
					$1xx: one CTL before byte $xx, $2xx: two CTLs before byte $xx, etc.
	transmission_mode is expected to be != 0
*/
void Worker::next_bytes(uint n) throw(uint)
{
	for(;n;n--)
	{
		if(i==e) read_raw();
		if(bu[i] != CTL) { i++; continue; }

		uint c = next_char();
		if(c>0xff) throw c;		// preempted
	}
}



void Worker::run()
{
	XLogLine("worker running");

	XXASSERT(ioport);

	Painter painter;
	Object* object = getObject(0);
	Objects ostack;

	auto get_object = [&] () throw(uint) -> Object*
	{
		uint c = next_char();
		switch(c)
		{
		case OFFSCREENOBJECTS:	return offscreenobjects.ptr();
		case FRAMEBUFFEROBJECT:	return framebufferobject.ptr();
		case PARENT:			return object->parent;
		case CURRENT:			return object;
		case PREVIOUS:			return ostack.count() ? ostack.pop() : nullptr;
		case FIRSTCHILD:		return object->firstChild();		// may be NULL
		case LASTCHILD:			return object->lastChild();			// may be NULL
		case NOTHING:			return NULL;
		case NEXTSIBLING:		return object->nextSibbling();		// expensive!
		case PREVSIBLING:		return object->prevSibbling();		// expensive!
		case CHILDAT:
			next_bytes(2);
			return object->childAt(peek2X(bu+i-2));
		default:
			if(c>0xff) throw c;
			uint c2 = next_char();
			if(c2>0xff) throw c2;
			return getObject((c<<8)+c2);
		}
	};


	a = 0;
	i = 0;
	e = 0;
	int n;

	for(;;)
	{
		// read more bytes:
		if(i==e) read_raw();

		// unescaped mode:
		if(transmission_mode==0)
		{
			n = painter.paint(object,bu+a,e-a);		// n = bytes not yet painted
			if(n==0) {a=i=e=0; continue; }

			// not all processed:
			a = i = e-n;							// step over what has been painted
			if(bu[a] == Escape)
			{
				if(n<2) continue;					// need 1 more byte
				// skip ESC command
				LogLine("Escape: TODO");		// TODO
				a = i += 2;
			}
			continue;
		}

		// escaped transmission mode

		// search for CTL:
		uint c = 0;
		for(; i<e; i++)
		{
			if(bu[i] == CTL && (c = next_char()) > 0xff) break;
		}

		// no CTL found:
		if(c <= 0xff)
		{
			// paint what's available:
			XXXASSERT(i>a);
			n = painter.paint(object,bu+a,i-a);		// n = bytes not yet painted
			if(n==0) { a = i; continue; }

			// not all processed:
			a = i-n;								// step over what has been painted

			// test for Escape:
			if(bu[a] == Escape)
			{
				if(n<2) continue;					// need 1 more byte
				// skip ESC command
				LogLine("Escape: TODO");		// TODO
				a += 2;
			}
			continue;				// need more bytes
		}

		// CTL was found in input data:

		i -= 2; //1+(c>>8);			// step back over CTL code

		// paint what's available:
		while(i>a)
		{
			n = painter.paint(object,bu+a,i-a);		// n = bytes not yet painted

			if(n)	// not all processed?
			{
				if(bu[a] == Escape)
				{
					if(n >= 2)
					{
						LogLine("Escape: TODO");	// TODO
						a += 2;
						continue;
					}
				}

				LogLine("PAINT: truncated command");
				a = i;
			}
		}

		i += 2; //1+(c>>8);			// step over CTL code again

		// handle CTL:
d:		try
		{
			switch(c&0xff)
			{
			case RESET:				// CTL, RESET, 0,0,0,0
			{
				next_bytes(4);
				a = i;

				switch(peek4X(bu+i-4))
				{
				case 0x00000000:	// RESET
				{
					throw("RESET: TODO");
				}
				case 0x09090909:	// BREAK: EC transmission
				{
					throw("BREAK: EC transmission not in use");
				}
				case TTY:	// RESTART_AS with TTY as framebuffer object
				{
					ostack.purge();
					objects.purge();
					offscreenobjects = new RectObject(0,0,0);
					object = framebufferobject =
						new TTYObject(new TTYPixmap(main_window->width(),main_window->height()),0,0);
					break;
				}
				case GFX:	// RESTART_AS with GFX as framebuffer object
				{
					ostack.purge();
					objects.purge();
					offscreenobjects = new RectObject(0,0,0);
					object = framebufferobject =
						new GFXObject(new GFXPixmap(main_window->width(),main_window->height()),0,0);
					break;
				}
				case CRT:	// RESTART_AS with CRT as framebuffer object
				{
					throw("RESTART_AS CRT: TODO");
				}
				case UNI:	// RESTART_AS with Rect as framebuffer object
				{
					throw("RESTART_AS RECT: TODO");
				}
				//case AUDIO:
				//case ALIAS:
				default:
					throw("RESET: wrong magic: ignored");
				}
				break;
			}
			case CREATE:	// CTL, CREATE, TYPE, WW, HH, ARGB		create & select
			{
				//	// CREATE.TYPE:
				//	TTY		= 1,
				//	CRT		= 2,
				//	GFX		= 3,
				//	UNI		= 4,
				//	AUDIO	= 5,			// AY sound chip TODO
				//	ALIAS	= 6,			// same type as current object, sharing the same pixmap. TTY and GFX only.
				//	AND_APPENTO	= 0x10,		// --> NN objID follows, else it is appended to offscreenobjects
				//	AND_MOVETO  = 0x40,		// --> XX,YY follow
				//	AND_SELECT	= 0x80,		// implied if !AND_APPENDTO

				uint type = next_char(); if(type > 0xff) throw type;

				Object* o;

				if((type & 0x0f) == ALIAS)
				{
					// w,h,c nicht nötig!
					if(object->isa_id==IsaGFXObject)
						o = new GFXObject(static_cast<GFXObject*>(object)->pixmap,0,0);
					else if(object->isa_id==IsaTTYObject)
						o = new TTYObject(static_cast<TTYObject*>(object)->pixmap,0,0);
					else
						throw("CREATE ALIAS: current object must be TTY or GFX");
				}
				else if((type & 0x0f) == AUDIO)
				{
					throw("CREATE AUDIO: TODO");		// TODO
				}
				else
				{
					next_bytes(8);

					uint w = peek2X(bu+i-8);
					uint h = peek2X(bu+i-6);
					uint c = peek4X(bu+i-4);

					switch(type&0x0F)
					{
					default: throw("CREATE: illegal type");
					case TTY:	o = new TTYObject(w,h,c); break;
					case GFX:	o = new GFXObject(w,h,c); break;
					case CRT:	o = new CRTObject(w,h,c); break;
					case UNI:	o = new RectObject(w,h,c); break;
					}
				}

				if(type&AND_APPENTO)
				{
					Object* p = get_object();
					if(p) p->appendChild(o);
					else LogLine("CREATE AND_APPEND: object does not exist");
				}
				else offscreenobjects->appendChild(o);

				if(type&AND_MOVETO)
				{
					next_bytes(4);
					o->zbox.setLeft((int)peek2X(bu+i-4));
					o->zbox.setTop((int)peek2X(bu+i-2));
				}
				if(type&AND_SELECT)
				{
					ostack.append(object);
					object = o;
				}
				break;
			}
			case SELECT:	// CTL, SELECT, NN						and push old selection
			{
				Object* o = get_object();
				if(!o) throw("SELECT: Object does not exist");
				if(o!=object) { ostack.append(object); object = o; }
				break;
			}
			case DISPOSE:	// CTL, DISPOSE, NN
			{
				Object* o = get_object();
				if(!o) throw("DISPOSE: Object does not exist");
				if(o->obj_id<=1)
					throw(o->obj_id ? "DISPOSE: can't dispose framebuffer" : "DISPOSE: can't dispose offscreen root");
				if(object==o)
				{
					if(ostack.count()) object = ostack.pop();
					else object = object->parent; // framebufferobject;		// DENK..
				}
				o->dispose();
				break;
			}
			case QUERY:		// CTL, QUERY, NN, WHAT		--> reply: CTL, QUERY, NN, ..
			{
				//	// QUERY.WHAT:
				//	GET_OBJECT_ID	= 0,	// --> NN		always sent: 2-byte obj_id or 1-byte NOTHING => message ends here
				//	GET_TYPE		= 1,	// --> TYPE
				//	GET_INDEX		= 2,	// --> NN		index in parent's child list,  -1 = no parent
				//	GET_CHILDCOUNT	= 4,	// --> NN
				//	GET_ZPOS		= 8,	// --> XX, YY	relative to parent
				//	GET_ZSIZE		= 16,	// --> WW, HH
				//	GET_QSIZE		= 32,	// --> WW, HH

				Object* o = get_object();
				uint what = next_char(); if(what>0xff) throw what;
				if(!o)
				{
					static uchar qmsg[] = { CTL, QUERY, NOTHING };
					ioport->write(qmsg,sizeof(qmsg));
					break;
				}
				uchar rbu[20] = { CTL, QUERY };
				n = 2;
				if(what & GET_TYPE) rbu[n++] = o->isa_id;
				if(what & GET_INDEX) { poke2X(rbu+n, o->index()); n+=2; }
				if(what & GET_CHILDCOUNT) { poke2X(rbu+n, o->children.count()); n+=2; }
				if(what & GET_ZPOS)  { poke2X(rbu+n, o->zbox.left()); poke2X(rbu+n+2, o->zbox.top()); n+=4; }
				if(what & GET_ZSIZE) { poke2X(rbu+n, o->zbox.width()); poke2X(rbu+n+2, o->zbox.height()); n+=4; }
				if(what & GET_QSIZE) { poke2X(rbu+n, o->width()); poke2X(rbu+n+2, o->height()); n+=4; }
				ioport->write(rbu,n);
				break;
			}
			case REORDER:	// CTL, REORDER, NN, HOW, ..
			{
				//	REORDER.HOW:
				//	HIGHER		= 1,		// move object in it's parent's child list --> index -= 1
				//	LOWER		= 2,		// move object in it's parent's child list --> index += 1
				//	TOSTART		= 3,		// index = 0
				//	TOEND		= 4,		// index = last
				//	TOINDEX		= 5,		// index NN follows, inserts
				//	BEHIND		= 6,		// object_id NN follows
				//	BEFORE		= 7,		// object_id NN follows
				//	EXCHANGE	= 8,		// object_id NN follows
				//	REPLACE		= 9,		// object_id NN follows
				//	ATSTART		= 10,		// object_id of new parent NN follows
				//	ATEND		= 11,		// object_id of new parent NN follows
				//	ATINDEX		= 12,		// object_id of new parent NN  and index NN follow, inserts

				Object* o = get_object(); if(!o) throw "REORDER: object not found";
				uint how = next_char(); if(how>0xff) throw how;
				Object* o2 = 0; if(how>=BEHIND && how<=ATINDEX)
								{ o2 = get_object(); if(!o2) throw "REORDER: object#2 not found"; }
				if(how==TOINDEX || how==ATINDEX) { next_bytes(2); n=peek2X(bu+i-2); }

				switch(how)
				{
				int mi;
				case HIGHER	:	o2 = o->nextSibbling(); if(o2) o->swap_with(o2); break;
				case LOWER	:	o2 = o->prevSibbling(); if(o2) o->swap_with(o2); break;
				case TOSTART:	mi = o->index(); if(mi>=0) o->parent->children.ror(0,mi+1); break;
				case TOEND	:	mi = o->index(); if(mi>=0) o->parent->children.rol(mi,o->parent->children.count()); break;
				case TOINDEX:
						mi = o->index(); if(mi<0) break;
						if(n<mi) o->parent->children.ror(n,mi+1);
						if(n>mi) o->parent->children.rol(mi,min(n+1,(int)o->parent->children.count()));
						break;
				case BEHIND	:
					n = o2->index();
					if(n>=0) { o2->parent->appendChild(o); o2->parent->children.ror(n+1,o2->parent->children.count()); }
					break;
				case BEFORE	:
					n = o2->index();
					if(n>=0) { o2->parent->appendChild(o); o2->parent->children.ror(n,o2->parent->children.count()); }
					break;
				case REPLACE:
					TODO();
				case EXCHANGE:
					TODO();
				case ATSTART:
					TODO();
				case ATEND	:
					TODO();
				case ATINDEX:
					TODO();
				default:		throw "REODER: unknown 'how'";
				}
				break;
			}
			case GEOMETRY:	// CTL, GEOMETRY, NN, WHAT, ..
			{
				//	GEOMETRY.FLAGS:
				//	RESET_GEOMETRY	= 0x00,	// ZSIZE=QSIZE, QPOS=0,0, ROT=0
				//	SET_ZPOS		= 1,	// --> XX, YY follow
				//	SET_ZSIZE		= 2,	// --> WW, HH follow
				//	SET_QPOS		= 4,	// --> XX, YY follow
				//	SET_QSIZE		= 8,	// --> WW, HH follow
				//	//SET_SCALE
				//	//SET_ROTATION
				TODO();
			}
			case CONFIGURE:	// CTL, CONFIGURE, WHAT
			{
				//	CONFIGURE.WHAT:
				//	PTR_EVENTS_OFF	= 0,	// %00=off, %01=btns, %10=drag, %11=move
				//	PTR_EVENTS_BTN	= 1,
				//	PTR_EVENTS_DRAG	= 2,
				//	PTR_EVENTS_MOVE	= 3,
				//	RESIZE_EVENTS	= 4,	// bit0: on/off
				//	JOY_EVENTS		= 6,	// bit0: on/off
				//	KBD_EVENTS		= 8,	// bit0: on/off, default = ON: send control code and printable characters "as is"
				//	FFB_EVENTS		= 10,	// bit0: on/off, will be sent on PTR or JOY events too
				TODO();
			}
			case WAIT_FFB:	// CTL, WAIT_FFB				--> CTL, WAIT_FFB, FLAGS, ..
			{
				//	// WAIT_FFB.FLAGS:
				//	// event infos are attached to the FFB reply only if changed:
				//	PTR_EVENT	 = 1,		// --> 1 byte button mask new state %00000mrl, XX, YY
				//	RESIZE_EVENT = 8,		// --> WW, HH
				//	JOY_EVENT	 = 0x10,	// --> 2 bytes button mask new state: %xxxfudlr 1=active
				//	JOY2_EVENT	 = 0x20,	//
				//	JOY3_EVENT	 = 0x40,	//
				//	JOY4_EVENT	 = 0x80,	//
				TODO();
			}
			case 0xff:
			default:		// SWITCH STREAM
				throw("SELECT STREAM: logical streams not supported");
			}//switch

			a = i;
			continue;
		}
		catch(uint e)
		{
			LogLine("Command 0x%02X truncated",c);
			c = e;
			goto d;
		}
		catch(cstr s)
		{
			LogLine("%s", s);
			a = i;
			continue;
		}

	}//loop
}






#if 0

				}
			}
		}

		// at i there's a problem, probably a CTL or i==e
		// paint what we have:

		if(i==a) continue;	// need more data

		n = painter.paint(object,bu+a,i-a);
		if(n==0)
		{
			a = i;
		}
		else if(n<0)	// error code:
		{
			LogLine("Painter returned error %i",n);
			a = i;		// flush
		}
		else			// n>0 => painter did not process all data
		{
			a = i-n;
			switch(bu[i])
			{
			case Escape:
				TODO();
			case Identify:
				TODO();
			//default:		// vermutlich braucht der Painter noch mehr Argumente
			}
		}

	} // infinite loop
}
#endif


/*

		create object on screen as child of current object & select it
			paint s.th.					may be visible too early
			select parent
		...

		create object on screen as child of current object with width=0 & select it
			geht nicht für TTY

		create object on screen as child of current object with x=9999 & select it
			paint s.th.
			select parent
		...

		create offscreen object			& select it
			paint s.th.
			select parent geht nicht
			select parent id			as remembered

		create offscreen object			& select it
			paint s.th.
			attach to parent ID			as remembered
			select parent

*/


// =================================================================
//					The MainWindow
// =================================================================



MainWindow::MainWindow(QWindow* parent)
:
	QOpenGLWindow(QOpenGLWindow::NoPartialUpdate, parent)
{
	XLogIn("new MainWindow");

	XXASSERT(main_window == NULL);

	main_window = this;
	this->setMinimumSize(QSize(200,160));
	this->setWidth(WIDTH);
	this->setHeight(HEIGHT);


// find serial ports:
	sio_infos = QSerialPortInfo::availablePorts();
	if(selected_sio >= sio_infos.count()) selected_sio = -1;
	for(int i=0;i<sio_infos.count(); i++)
	{
		QSerialPortInfo& info = sio_infos[i];
		XLogIn("%s",info.portName().toUtf8().data());
		XLogLine("description: %s",info.description().toUtf8().data());
		XLogLine("manufacturer: %s",info.manufacturer().toUtf8().data());
		XLogLine("serial number: %s",info.serialNumber().toUtf8().data());
		XLogLine("system location: %s",info.systemLocation().toUtf8().data());
	}

	offscreenobjects = new RectObject(0,0,0);
	framebufferobject = new TTYObject(new TTYPixmap(800,600),0,0);


	num_workers = 1;//minmax(1, numCPUs(), (int)NELEM(workers));
	for(uint n=0; n<num_workers; n++) (workers[n] = new Worker(n))->start();

	if(1)	// Add test images:
	{
//		Image* image = new Image(40,40,RGB);
		GFXPixmap* pixmap = new GFXPixmap(40,40,0xFFcc0000);	// 0xAARRGGBB
		framebufferobject->appendChild(new GFXObject(pixmap,100,10));

		pixmap = new GFXPixmap(80,80,0xFFcccc00);		// 0xAARRGGBB
		framebufferobject->appendChild(new GFXObject(pixmap,760,560));

		pixmap = new GFXPixmap(20,20,0xFF00cccc);		// 0xAARRGGBB
		framebufferobject->lastChild()->appendChild(new GFXObject(pixmap,30,-10));

//		pixmap = new Pixmap(800,600);
//		QPainter p5(pixmap);
//		p5.setBrush(QBrush(0xFF66cccc));	// 0xAARRGGBB
//		p5.setPen(Qt::NoPen);
//		p5.drawRect(pixmap->rect());
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
	}


// Menu bar and main menus:
	QMenuBar* menubar = new QMenuBar();
	QMenu*	  m_connect = menubar->addMenu("Connect");
	QMenu*	  m_settings = menubar->addMenu("Settings");
	QMenu*	  m_actions = menubar->addMenu("Actions");
	QMenu*	  m_help = menubar->addMenu("Help");		// invisible: Qt moves all items to the appl menu


// Mutual exclusive groups:
	QActionGroup* g_connection = new QActionGroup(this);
	QActionGroup* g_speed      = new QActionGroup(this);


// "Connect" Menu:
	QAction* a_stdio = g_connection->addAction(m_connect->addAction("Stdin/stdout"));
	a_stdio->setCheckable(true); a_stdio->setChecked(stdio_selected);

	m_connect->addSeparator();
	for(int i=0; i<sio_infos.count(); i++)
	{
		QString text = sio_infos.at(i).portName(); if(text.startsWith("cu.")) text = text.mid(3);
		if(sio_infos.at(i).manufacturer().count()) text += QString(" (") + sio_infos.at(i).manufacturer() + ")";
		QAction* a = m_connect->addAction(text);
		a->setCheckable(true);
		a->setChecked(selected_sio == i);
		g_connection->addAction(a);
	}
	QMenu* m_sio_speed = m_connect->addMenu("Speed");
		   m_sio_speed->setEnabled(selected_sio>=0);

	m_connect->addSeparator();
	QAction* a_named_pipe = g_connection->addAction(m_connect->addAction("Named Pipe"));
			 a_named_pipe->setCheckable(true); a_named_pipe->setChecked(pipe_selected);
	QAction* a_pipe_name  = m_connect->addAction(catstr("Path: ",pipe_path));
			 a_pipe_name->setEnabled(pipe_selected);

	m_connect->addSeparator();
	QAction* a_webserver = g_connection->addAction(m_connect->addAction("IP Socket Server"));
			 a_webserver->setCheckable(true); a_webserver->setChecked(server_selected);
	QAction* a_port      = m_connect->addAction(usingstr("Port: %u",socket_port));
			 a_port->setEnabled(server_selected);

// "Settings" Menu:
	QAction* a_str = m_settings->addAction(usingstr("Max. Streams: %u",max_streams)); a_str->setEnabled(no);
	cstr s = max_mem >= 1 GB ? usingstr("%.1f GB",max_mem/(1.0 GB)) : usingstr("%u MB",max_mem/(1 MB));
	QAction* a_mem = m_settings->addAction(usingstr("Max. Memory: %s",s)); a_mem->setEnabled(no);
	QAction* a_fs  = m_settings->addAction("Expose File System");
			 a_fs->setCheckable(true);	a_fs->setChecked(enable_fs);
	QAction* a_gfx = m_settings->addAction("Graphics Terminal");
			 a_gfx->setCheckable(true);	a_gfx->setChecked(enable_gfx);
	QAction* a_kbd = m_settings->addAction("Enable Keyboard");
			 a_kbd->setCheckable(true);	a_kbd->setChecked(enable_kbd);
	QAction* a_ptr = m_settings->addAction("Enable Mouse");
			 a_ptr->setCheckable(true);	a_ptr->setChecked(enable_ptr);
	QAction* a_joy = m_settings->addAction("Enable Joysticks");
			 a_joy->setCheckable(true);	a_joy->setChecked(enable_joy);
	m_settings->addSeparator();
	QAction* a_reset = m_actions->addAction("Reset Terminal");


// "Application" Menu:
	QAction* a_about = m_help->addAction("About…");			// will be moved to appl menu
	QAction* a_prefs = m_help->addAction("Preferences…");	// will be moved to appl menu


// "Sio Speed" Menu:
	QAction* a_speed[] =
	{
		m_sio_speed->addAction("2400"),
		m_sio_speed->addAction("4800"),
		m_sio_speed->addAction("9600"),
		m_sio_speed->addAction("19200"),
		m_sio_speed->addAction("38400"),
		m_sio_speed->addAction("76800"),
		m_sio_speed->addSeparator(),
		m_sio_speed->addAction("3600"),
		m_sio_speed->addAction("7200"),
		m_sio_speed->addAction("14400"),
		m_sio_speed->addAction("28800"),
		m_sio_speed->addAction("57600"),
		m_sio_speed->addAction("115200")
	};

	s = numstr(sio_baudrate);
	for(uint i=0;i<NELEM(a_speed);i++)
	{
		g_speed->addAction(a_speed[i])->setCheckable(true);
		a_speed[i]->setChecked(a_speed[i]->text()==s);
	}

	bool f = 1;
	f = connect(a_about,&QAction::triggered,this,&MainWindow::slotShowAbout) && f;
	f = connect(a_prefs,&QAction::triggered,this,&MainWindow::slotShowPreferences) && f;
	f = connect(a_port,&QAction::triggered,this,&MainWindow::slotSetTCPPort) && f;
	f = connect(a_pipe_name,&QAction::triggered,this,&MainWindow::slotSetPipeName) && f;
	f = connect(a_reset,&QAction::triggered,this,&MainWindow::slotResetTerminal) && f;
	XXASSERT(f);
}


MainWindow::~MainWindow()
{}


/*	This virtual function is called once before the first call to paintGL() or resizeGL().
	Reimplement it in a subclass.
	This function should set up any required OpenGL resources and state.
	There is no need to call makeCurrent() because this has already been done when this function is called.
	Note however that the framebuffer, in case partial update mode is used, is not yet available at this stage,
	so avoid issuing draw calls from here. Defer such calls to paintGL() instead.
*/
void MainWindow::initializeGL()
{
	XLogIn("MainWindow::initializeGL");
}


/*	This virtual function is called whenever the window contents needs to be painted.
	Reimplement it in a subclass.
	There is no need to call makeCurrent() because this has already been done when this function is called.
	Before invoking this function, the context and the framebuffer, if there is one, are bound,
	and the viewport is set up by a call to glViewport().
	No other state is set and no clearing or drawing is performed by the framework.
	Note: When using a partial update behavior, like PartialUpdateBlend, the output of the previous paintGL() call
	is preserved and, after the additional drawing perfomed in the current invocation of the function,
	the content is blitted or blended over the content drawn directly to the window in paintUnderGL().
*/
void MainWindow::paintGL()
{
	XXLogIn("MainWindow::paintGL");

	// due to double buffering we need to paint everything twice:
	static bool u2 = false;

	if(u2 || update_flag)				// else nothing was drawn
	{
		XXLogLine("painting ...");

		QPainter p(this);
		QRect r(0,0,width(),height());	// 0/0 = top/left

		u2 = update_flag;
		update_flag = false;

		// wenn das erste Image nicht den gesamten Hintergrund abdeckt, dann den Hintergrund schwarz malen:
		Object* o = framebufferobject.ptr();
		if(!o->isOpaque() || (o->zbox & r) != r)
			p.fillRect(r,background_color);

		o->paint(p,0,0);
	}
	else
	{
		XXLogLine("no painting required");
	}

	// send registered events:
	// mouse, joysticks, ffb
	static uint8 msg = NextFrameEvent;
	if(registered_events & PaintFrameEventMask)
		streams[stream_ffb_events].replies.write(&msg,1);

	update();
}


/*	This virtual function is called whenever the widget has been resized. Reimplement it in a subclass.
	The new size is passed in w and h.
	Note: This is merely a convenience function in order to provide an API that is compatible with QOpenGLWidget.
	Unlike with QOpenGLWidget, derived classes are free to choose to override resizeEvent() instead of this function.

	Note: Avoid issuing OpenGL commands from this function as there may not be a context current when it is invoked.
	If it cannot be avoided, call makeCurrent().

	Note: Scheduling updates from here is not necessary. The windowing systems will send expose events
	that trigger an update automatically.
*/
void MainWindow::resizeGL(int w, int h)
{
	XLogIn("MainWindow::resizeGL");

	QRect& zbox = objects[0]->zbox;

	zbox.moveLeft(zbox.width()>w ? 0 : (w-zbox.width())/2);
	zbox.moveTop(zbox.height()>h ? 0 : (h-zbox.height())/2);

	update_flag = true;
}




#if 0
/*	This virtual function is called after each invocation of paintGL().
	When the update mode is set to NoPartialUpdate, there is no difference between this function and paintGL(),
	performing rendering in either of them leads to the same result.
	Like paintUnderGL(), rendering in this function targets the default framebuffer of the window,
	regardless of the update behavior. It gets called after paintGL() has returned
	and the blit (PartialUpdateBlit) or quad drawing (PartialUpdateBlend) has been done.
*/
void MainWindow::paintOverGL()
{
	printf("MainWindow::paintOverGL\n");
}

/*	The virtual function is called before each invocation of paintGL().
	When the update mode is set to NoPartialUpdate, there is no difference between this function and paintGL(),
	performing rendering in either of them leads to the same result.
	The difference becomes significant when using PartialUpdateBlend, where an extra framebuffer object is used.
	There, paintGL() targets this additional framebuffer object, which preserves its contents,
	while paintUnderGL() and paintOverGL() target the default framebuffer, i.e. directly the window surface,
	the contents of which is lost after each displayed frame.
	Note: Avoid relying on this function when the update behavior is PartialUpdateBlit.
	This mode involves blitting the extra framebuffer used by paintGL() onto the default framebuffer
	after each invocation of paintGL(), thus overwriting all drawing generated in this function.
*/
void MainWindow::paintUnderGL()
{
	printf("MainWindow::paintUnderGL\n");
}

/*	Reimplemented from QPaintDeviceWindow::paintEvent().
	Paint event handler. Calls paintGL().
*/
void MainWindow::paintEvent(QPaintEvent* e)
{
	static uint n=0; printf("MainWindow::paintEvent %u\n",++n);
	QOpenGLWindow::paintEvent(e);
}

/*	Reimplemented from QWindow::resizeEvent().
	Resize event handler. Calls resizeGL().
*/
void MainWindow::resizeEvent(QResizeEvent* e)
{
	static uint n=0; printf("MainWindow::resizeEvent %u\n",++n);
	QOpenGLWindow::resizeEvent(e);
}
#endif



//helper
//identification of a physical key:
uint keyforkey(uint qtkey)
{
	uint16 key = qtkey;
	switch(qtkey >> 16)
	{
	case 0x0000:			// 0x20 .. 0xFF = Latin-1 code
		return key;

	case 0x0100:
		// 0x01000000 - 26	esc, tab, backspace, enter, insert, home, cursors, modifiers capslock o.Ä.
		// 0x01000030 - 60	function keys o.Ä.
		// 0x01000061 - 124	multimedia/internet keys - ignored by default - see QKeyEvent c'tor
		//					-> 0xFCxx .. 0xFDxx
		// 0x01001100 - 40	International & multi-key character composition
		//					-> FE00 .. FE50
		// 0x01001250 - 62	dead keys (X keycode - 0xED00 to avoid the conflict)
		//					-> FE50 .. FE70

		if(key < 0x0200)	return 0xFC00 + key;				// FCxx, FDxx
		if(key < 0x1100)	break;
		if(key < 0x1150)	return 0xFE00 + key - 0x1100;		// 0xFE00..50
		if(key < 0x1250)	break;
		if(key < 0x1270)	return 0xFE50 + key - 0x1250;		// 0xFE50..70
		break;

	case 0x0102:
		// 0x01020001 - 0A	Newer misc keys
		if(key<0x10) return 0xFE80 + key;						// 0xFE80..90
		break;

	case 0x0110:
		// 0x01100000 - 21	(mobile) Device keys
		if(key<0x20) return 0xFEA0 + key;						// 0xFEA0..C0
		break;

	case 0x0101:
		// 0x01010000 - 03	Keypad navigation keys
		if(key<0x10) return 0xFED0 + key;						// 0xFED0..E0
		break;
	}
	return 0xffff;			// don't know
}


/*	Qt callback: key down:

	e.key()                 Großbuchstabe der Taste, wenn kein Modifierkey gedrückt wäre
	e.modifiers()           Maske aller gedrückter Modifier
								Qt::SHIFT   = Shift-Taste
								Qt::META    = Control-Taste
								Qt::CTRL    = Cmd-Taste
								Qt::ALT     = Alt-Taste
	e.nativeModifiers()     OSX-Modifiermaske, außer wenn Modifiertaste alleine gedrückt ist, dann 0
	e.nativeScanCode()      nutzlos: 0 oder 1
	e.nativeVirtualKey()    OSX-Keycode, außer wenn Modifiertaste alleine gedrückt ist, dann 0
							ACHTUNG: Der Tastencode für 'A' ist auch 0!
	e.text()                Resultierendes druckbares Zeichen, außer bei Controlcodes, CTRL+Taste und CMD+Taste: leer
*/
void MainWindow::keyPressEvent(QKeyEvent* e)
{
	if(~registered_events & KeyDownEventMask)
		return QOpenGLWindow::keyPressEvent(e);

	XXLogLine("key down:   0x%08x (%i)",(int)e->key(),(int)e->key());
	XXLogLine("modifiers:  0x%08x", (int)e->modifiers());
	XXLogLine("native key: 0x%08x", (int)e->nativeVirtualKey());
	XXLogLine("text:       %s",     e->text().toUtf8().data());

	uint32 qtmodifiers = e->modifiers();           // Modifier-Maske
	uint8 modifiers = e->isAutoRepeat() ? RepeatedKeyMask : 0x00;
	if(qtmodifiers)
	{
		if(qtmodifiers & Qt::ShiftModifier)   modifiers |= ShiftKeyMask;
		if(qtmodifiers & Qt::MetaModifier)    modifiers |= CtrlKeyMask;
		if(qtmodifiers & Qt::AltModifier)	  modifiers |= AltKeyMask;
		if(qtmodifiers & Qt::ControlModifier) modifiers |= CmdKeyMask;
	}

	uint32 qtkey = e->key();		// Taste ohne Modifier, Uppercase
	if(qtkey&0x01000000)
	{
		if(qtkey==Qt::Key_Shift)	modifiers |= ShiftKeyMask; else
		if(qtkey==Qt::Key_Meta)		modifiers |= CtrlKeyMask;  else
		if(qtkey==Qt::Key_Alt )		modifiers |= AltKeyMask;   else
		if(qtkey==Qt::Key_Control)	modifiers |= CmdKeyMask;
	}

	// Resulting printable character:
	// Resultierendes druckbares Zeichen, außer bei Controlcodes, CTRL+Taste und CMD+Taste: leer
	QString	qttext	= e->text();
	QChar	qtchar	= qttext.count()==0 ? 0 : qttext.at(0);
	uint16  unicode	= qtchar.unicode();

	#define HI(N) (uint8)((N)>>8)
	#define LO(N) (uint8)((N)&0xFF)

	if(registered_events & KeyUpEventMask)
	{
		// identification of a physical key:
		uint16 key = qtkey <= 0xFF ? qtkey : keyforkey(qtkey);

		uint8 msg[] =
		{
			KeyDownEvent,
			(uint8)(modifiers|KeyCodeMask), // modifier keys, incl. RepeatedKeyMask and KeyCodeMask=1
			HI(unicode), LO(unicode),		// printable character or 0
			HI(key), LO(key)				// physical key identifier, same as uppercase char for normal keys
		};
		streams[stream_kbd_events].storeReply(msg,sizeof(msg));
	}
	else
	{
		uint8 msg[] =
		{
			KeyDownEvent,
			modifiers,						// modifier keys, incl. RepeatedKeyMask and KeyCodeMask=0
			HI(unicode), LO(unicode)		// printable character or 0
		};
		streams[stream_kbd_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
	}
}

void MainWindow::keyReleaseEvent(QKeyEvent* e)
{
	if(~registered_events & KeyUpEventMask)
		return QOpenGLWindow::keyReleaseEvent(e);

	XXLogLine("key up:     0x%08x (%i)",(int)e->key(),(int)e->key());
	XXLogLine("modifiers:  0x%08x", (int)e->modifiers());
	XXLogLine("native key: 0x%08x", (int)e->nativeVirtualKey());
	XXLogLine("text:       %s",     e->text().toUtf8().data());

	uint32 qtmodifiers = e->modifiers();           // Modifier-Maske
	uint8 modifiers = 0x00;
	if(qtmodifiers)
	{
		if(qtmodifiers & Qt::ShiftModifier)   modifiers |= ShiftKeyMask;
		if(qtmodifiers & Qt::MetaModifier)    modifiers |= CtrlKeyMask;
		if(qtmodifiers & Qt::AltModifier)	  modifiers |= AltKeyMask;
		if(qtmodifiers & Qt::ControlModifier) modifiers |= CmdKeyMask;
	}

	uint32 qtkey = e->key();		// Taste ohne Modifier, Uppercase
	if(qtkey&0x01000000)
	{
		if(qtkey==Qt::Key_Shift)	modifiers &= ~ShiftKeyMask; else
		if(qtkey==Qt::Key_Meta)		modifiers &= ~CtrlKeyMask;  else
		if(qtkey==Qt::Key_Alt )		modifiers &= ~AltKeyMask;   else
		if(qtkey==Qt::Key_Control)	modifiers &= ~CmdKeyMask;
	}

	// identification of a physical key:
	uint16 key = qtkey <= 0xFF ? qtkey : keyforkey(qtkey);

	uint8 msg[] =
	{
		KeyUpEvent,
		(uint8)(modifiers|KeyCodeMask), 	// modifier keys, incl. RepeatedKeyMask=0 and KeyCodeMask=1
		HI(key), LO(key)					// physical key identifier, same as uppercase char for normal keys
	};
	streams[stream_kbd_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}


// mouse move timer:
// mouse move events are collected and transmitted once per 1/100 sec.
static QTimer m_timer;
static bool   m_timer_valid = no;
static int32  m_ts_down = 0;
static int32  m_ts_up   = 0;

void MainWindow::mouseMoveEvent(QMouseEvent* e)
{
	if(~registered_events & MouseMoveEventMask)
		return QOpenGLWindow::mouseMoveEvent(e);

	if(m_timer.isActive()) return;

	static int buttons,x,y;
	buttons = e->buttons();
	x = e->x();
	y = e->y();

	if(!m_timer_valid)
	{
		m_timer_valid = yes;
		m_timer.setSingleShot(true);
		connect(&m_timer,&QTimer::timeout,[]()
		{
			uint8 msg[] =
			{
				MouseMoveEvent,
				(uint8)buttons,		// state of mouse buttons
				HI(x),LO(x),		// latest position
				HI(y),LO(y)
			};
			streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
		});
	}
	m_timer.start(10);
}

void MainWindow::mousePressEvent(QMouseEvent* e)
{
	if(~registered_events & MouseDownEventMask)
		return QOpenGLWindow::mousePressEvent(e);

	m_timer.stop();
	int x = e->x();
	int y = e->y();
	m_ts_down = e->timestamp() / 10;	// hopefully ms -> 1/100s
	uint8 delay = min(m_ts_down-m_ts_up,255);

	uint8 msg[] =
	{
		MouseDownEvent,
		(uint8)e->button(),			// the button which toggled
		(uint8)e->buttons(),		// new state of mouse buttons
		HI(x),LO(x),				// position of event
		HI(y),LO(y),
		delay						// delay [1/100s] since last mouse up
	};
	streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}

void MainWindow::mouseReleaseEvent(QMouseEvent* e)
{
	if(~registered_events & MouseUpEventMask)
		return QOpenGLWindow::mouseReleaseEvent(e);

	m_timer.stop();
	int x = e->x();
	int y = e->y();
	m_ts_up = e->timestamp() / 10;	// hopefully ms -> 1/100s
	uint8 delay = min(m_ts_up-m_ts_down,255);

	uint8 msg[] =
	{
		MouseDownEvent,
		(uint8)e->button(),
		(uint8)e->buttons(),
		HI(x),LO(x),
		HI(y),LO(y),
		delay						// delay [1/100s] since last mouse down
	};
	streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}





void MainWindow::slotSetTCPPort(){LogIn("slotSetIPAddress TODO");}
void MainWindow::slotSetPipeName(){LogIn("slotSetQueueNames TODO");}
void MainWindow::slotShowAbout(){LogIn("slotShowAbout TODO");}
void MainWindow::slotShowPreferences(){LogIn("slotShowPreferences TODO");}
void MainWindow::slotResetTerminal(){LogIn("slotResetTerminal TODO");}




/*
enum
{
	XOFF	 = 0x11,
	XON		 = 0x13,
	CTL		 = 0xF5,

	esc_XON	 = 0xF1,
	esc_XOFF = 0xF2,
	esc_CTL	 = 0xF3,
	RST		 = 0xFB,		// 0,0,0,0 must follow
	NAK		 = 0xFA,		// N, CRC-8 must follow
	ACK		 = 0xF9,		// N, CRC-8 must follow
	BRK		 = 0xFD			// 9,9,9,9 must follow
};
*/

typedef int(*Reader)(uchar*,uint);
typedef int(*Writer)(const uchar*,uint);

Reader reader;
Writer writer;


class Receiver : public QThread
{
public:
	void run();
};



/*	Escaped Transmission mode, single or multiple streams:
*/
void Receiver::run()
{
	uchar bu[1<<10];
	int str = 0;
	int a = 0;
	int i = 0;
	int e = 0;

	for(;;)
	{
		i = e;
		e += reader(bu+e,sizeof(bu)-e);
		if(e<i) break;								// reader() returned -1
		i = 0;

	a:	while(i<e && bu[i]!=CTL) i++;				// search for CTL tag
		streams[str].jobs.write(bu+a,i-a);			// store everything up to CTL tag or data end
		a = i;
		if(i==e) { a=e=0; continue; }					// it was data end
		if(++i==e) { bu[0]=CTL; a=0; e=1; continue; }	// CTL at end of data => need 2nd byte

		uint c2 = bu[i++];
		if(c2<max_streams && max_streams>1) { str=c2; a=i; goto a; }	// CTL + N (N<240) => stream change

		// CTL + $Fx:
		switch(c2)
		{
		case esc_XON:	bu[++a]=XON;  goto a;		// escaped char
		case esc_XOFF:	bu[++a]=XOFF; goto a;		// ""
		case esc_CTL:	bu[++a]=CTL;  goto a;		// ""

		case CTL:
			// CTL+CTL: expect an escaped stream number:
			if(i==e) { bu[0]=bu[1]=CTL; a=0; e=2; continue; }	// need 3rd char
			switch(bu[i++]) // c3
			{
			case esc_XOFF:
				if(XOFF<max_streams){str=XOFF; a=i; goto a;}	// escaped stream 0x11
				else break;
			case esc_XON:
				if(XON<max_streams) {str=XON;  a=i; goto a;}	// escaped stream 0x13
				else break;
			//case esc_CTL:									// there's no stream 0xF5
			case RST:	break;	//TODO
			//case BRK:	break;	//EC only
			//case ACK:	break;	//EC only
			//case NAK:	break;	//EC only
			default:	break;	// CTL+CTL+garbage => store CTL as is
			}
			break;

		case RST:	break;	// TODO
		//case BRK:	break;	// EC only
		//case ACK:	break;	// EC only
		//case NAK:	break;	// EC only
		default:	break;	// CTL+garbage => store CTL as is
		}

		i = a+1; 	// CTL+garbage => store CTL as is
		goto a;
	}
}















