/*	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 "TTYPixmap.h"
#include <QPainter>


#define update(...) //update_flag=true


// Menlo, Monaco, Andale Mono
QFont default_tty_font("Menlo"/*family*/, 12/*pointSize*/, -1/*weight*/, false/*italic*/);
#define antialias true						// always
QRgb   default_tty_paper(0xff003300);		// dark green
QRgb   default_tty_pen(0xff33ff33);			// bright green



static//helper:
QRgb contrast_color(QRgb c)
{
	uint r = qRed(c);
	uint g = qGreen(c);
	uint b = qBlue(c);

	if(r+g+b >= 0x180) return qRgb(r/4,g/4,b/4);
	else return qRgb((r*3+255)/4, (g*3+255)/4, (b*3+255)/4);
}


// calculate 8x12 graphics character c
// returns various block or line graphics
// from the LCDisplay Firmware
//
//	0x20 .. 0x2F    4/4 Block Graphics, black&white		with:	8 4
//	0x30 .. 0x3F:   4/4 Block Graphics, grey/white				2 1
//	0x40 .. 0x4F:   4/4 Block Graphics, black/grey
//	0x50 .. 0x57:   1/8  .. 8/8   Bargraph from left   (see unicode U+2580)
//	0x58 .. 0x5F:   1/8  .. 8/8   Bargraph from right  (see unicode U+2580)
//	0x60 .. 0x6B:   1/12 .. 12/12 Bargraph from bottom (see unicode U+2580)
//	0x6C .. 0x77:   1/12 .. 12/12 Bargraph from top    (see unicode U+2580)
//	0x78 .. 0xAF:   (undefined)
//	0xB0 .. 0xFF:   Line Graphics                      (see unicode U+2500)
//
//	The line graphics are calculated with the following formula:
//
//	0xAF + A*27 + B*9 + C*3 * D
//
//	where A/B/C/D = left/top/right/bottom line stub
//	with 0/1/2 = no/thin/thick line
//	code point 0xAF (no line at all) does not exist
//
static
void getGraphicsCharMatrix(uint8* charmatrix, uchar c)
{
	uint8 i=0,b,n;

//	if(c<0x20)			// control codes
//	{}
// 	else

	if(c<0x30)			// 4/4 Blockgrafik schwarz/weiß
	{
		b = (c&8 ? 0xF0 : 0) + (c&4 ? 0x0F : 0);
		c = (c&2 ? 0xF0 : 0) + (c&1 ? 0x0F : 0);
		n = 6;
		goto b;
	}
	else if(c<0x40)		// 4/4 Blockgrafik grau/weiß
	{
		getGraphicsCharMatrix(charmatrix, c-16);
		c = 0xAA; do { charmatrix[i++] &= c; c = ~c; } while(i<12);
	}
	else if(c<0x50)		// 4/4 Blockgrafik schwarz/grau
	{
		getGraphicsCharMatrix(charmatrix, c-32);
		c = 0xAA; do { charmatrix[i++] |= c; c = ~c; } while(i<12);
	}
	else if(c<0x58)		// Balkengrafik von links	schwarz/weiß
	{
		c = 0xff<<(0x57-c);
		goto c;
	}
	else if(c<0x60)		// Balkengrafik von rechts	schwarz/weiß
	{
		c = 0xff>>(0x5F-c);
		goto c;
	}
	else if(c<0x6C)		// Balkengrafik von unten
	{
		n = 0x6B - c;
		b=0x00;c=0xFF;
		goto b;
	}
	else if(c<0x78)		// Balkengrafik von oben
	{
		n = c - 0x6B;
		b=0xFF;c=0x00;
b:		while(i<n)  charmatrix[i++] = b;
c:		while(i<12) charmatrix[i++] = c;
	}

//	else if(c<0x80)		// 8 unused
//	{}

//	else if(c<0xB0)		// 48 unused
//	{}

	else//if(c<0xF0)	// 80 Liniengrafiken: dünne und dicke Linien
	{					// total: 3^4 = 81, aber 4 x ohne => use 'space' oder Grafik '\0'
		uint8 a,d;
		c += 1-0xB0;
		a =  c/27;		// a=0/1/2 => Strich links ohne/dünn/dick
		b = (c/9)%3;	// b				 oben
		d = c%3;		// d				 unten
		c = (c/3)%3;	// c				 rechts

		n = b==1 ? 0x08 : b ? 0x18 : 0;	// vert. Strich oben
		while(i<6)  charmatrix[i++]=n;

		n = d==1 ? 0x08 : d ? 0x18 : 0;	// vert. Strich unten
		while(i<12)  charmatrix[i++]=n;

		if(a)    charmatrix[5] |= 0xF8;	// hor. Strich links
		if(a==2) charmatrix[6] |= 0xF8;

		if(c)    charmatrix[5] |= 0x0F;	// hor. Strich rechts
		if(c==2){charmatrix[6] |= 0x1F; charmatrix[5] |= 0x1F; }
	}
}



// =============================================================
//				Pixmap for TTYPainter
//				adds TTY state to the Pixmap
// =============================================================


TTYPixmap::TTYPixmap(int w, int h, const QFont& font, QRgb paper, QRgb pen)
:
	Pixmap(w,h,paper),
	font(font),
	dflt_pen(pen),
	udg(NULL),
	painter(this)
{
	calc_font_metrics();
	reset();
}

TTYPixmap::TTYPixmap(int w, int h, QRgb paper, QRgb pen)
:
	TTYPixmap(w,h,default_tty_font,paper,pen)
{}

TTYPixmap::TTYPixmap(int w, int h, QRgb paper)
:
	TTYPixmap(w,h,default_tty_font,paper,contrast_color(paper))
{}

TTYPixmap::TTYPixmap(int w, int h)
:
	TTYPixmap(w,h,default_tty_font,default_tty_paper,default_tty_pen)
{}



//helper
void TTYPixmap::set_attr()
{
	uint a = this->attr;
	if(a & DOUBLE_HEIGHT && this->rows<2) a -= DOUBLE_HEIGHT;		// security
	if(a & DOUBLE_WIDTH  && this->cols<2) a -= DOUBLE_WIDTH; 		// else App will hang!
	QFont& font = this->font;
	font.setUnderline(a & UNDERLINE);
	font.setBold(a & BOLD);
	font.setItalic(a & ITALIC);

	if(a & DOUBLE_HEIGHT)
	{
		font.setPointSize(this->pointsize*2);
		font.setStretch(a & DOUBLE_WIDTH ? QFont::Unstretched : QFont::UltraCondensed);
	}
	else
	{
		font.setPointSize(this->pointsize);
		font.setStretch(a & DOUBLE_WIDTH ? QFont::UltraExpanded : QFont::Unstretched);
		//LogLine("XXX average char width = %i",painter.fontMetrics().averageCharWidth());
	}
	painter.setFont(font);

	painter.setBackgroundMode(a & OVERPRINT ? Qt::TransparentMode : Qt::OpaqueMode);

	set_colors();
}

//helper
void TTYPixmap::set_colors()
{
	QBrush brush(attr & INVERTED ? pen : paper);
	painter.setPen(attr & INVERTED ? paper : pen);
	painter.setBackground(brush);
	painter.setBrush(brush);
}

//helper:
void TTYPixmap::calc_font_metrics()
{
	// init font:
	font.setFixedPitch(true);
	font.setStyleStrategy(antialias ? QFont::ForceIntegerMetrics :
							 (QFont::StyleStrategy)(QFont::ForceIntegerMetrics|QFont::NoAntialias));

	painter.setFont(font);
	QFontMetrics m = painter.fontMetrics();
	char_width  = m.averageCharWidth();
	line_height = m.lineSpacing();
	ascent = m.ascent();
	descent = m.descent();
	char_height = m.height();
	leading = m.leading();
	pointsize = font.pointSize();

	cols = minmax(1, (width()+1) / char_width, 254);
	rows = max(1, (height()+leading) / line_height);

	XXLogLine("FONT FAMILY = %s",font.family().toUtf8().data());
	XXLogLine("pointsize   = %i",pointsize);	// 12
	XXLogLine("char_width  = %i",char_width);	// 7
	XXLogLine("line_height = %i",line_height);	// 16
	XXLogLine("ascent  = %i",ascent);			// 12
	XXLogLine("descent = %i",descent);			// 2
	XXLogLine("height  = %i",char_height);		// 15
	XXLogLine("leading = %i",leading);			// 1
	XXLogLine("cols    = %i",cols);				// 100
	XXLogLine("rows    = %i",rows);				// 30
}



void TTYPixmap::reset()
{
	pushed_paper = paper = fillcolor;
	pushed_pen   = pen	 = dflt_pen;
	pushed_attr  = attr	 = 0x00;
	pushed_row   = row	 = 0;
	pushed_col	 = col	 = 0;
	pushed_blob  = blob  = no;
	set_attr();
	clearScreen();
}

void TTYPixmap::pushState()
{
	pushed_paper = paper;
	pushed_pen   = pen;
	pushed_attr  = attr;
	pushed_row   = row;
	pushed_col	 = col;
	pushed_blob  = blob;
}

void TTYPixmap::popState()
{
	paper = pushed_paper;
	pen   = pushed_pen;
	attr  = pushed_attr;
	row   = pushed_row;
	col	  = pushed_col;
	blob  = pushed_blob;
	set_attr();
}

void TTYPixmap::setAttr(uint a)
{
	if(a==attr) return;
	attr = a;
	set_attr();
}

void TTYPixmap::setPen(QRgb c)
{
	if(c==pen) return;
	pen = c;
	set_colors();
}

void TTYPixmap::setPaper(QRgb c)
{
	if(c==paper) return;
	paper = c;
	set_colors();
}


void TTYPixmap::setUDG(uchar c, uint8* bmp)
{
	if(udg==NULL) { udg = new uint8[32*12]; memset(udg,0,32*12); }
	memcpy(udg+(c&0x1f)*12, bmp, 12);
}








// ---------------------------------------------------------------------
// Cursor Position:


/*	Remove the cursor blob; if visible
	Zwinge 'col' in den Bereich [0 ... cols-1]
	und 'row' in den Bereich [0 ... rows-1]
	so dass mindestens ein Zeichen oder der Blob gedruckt werden können
*/
void TTYPixmap::forceInside()
{
	int& col = this->col;
	int& row = this->row;
	int cols = this->cols;		bool dw = this->attr & DOUBLE_WIDTH;
	int rows = this->rows;		bool dh = this->attr & DOUBLE_HEIGHT;

	// handle h overflow:
	while(col<0)		{ col+=cols; row-=1; }
	while(col>=cols-dw)	{ col-=cols; row+=1; }

	// handle v overflow:
	if((uint16)row >= (uint16)rows-dh)
		scrollScreenUp(row < 0 ? row : row-rows+1+dh);
}


/*	show or hide blob
*/
void TTYPixmap::invert_blob()
{
	painter.setCompositionMode(QPainter::CompositionMode_Difference);
	QColor color( min(255,qRed(pen)+qRed(paper)),
				  min(255,qGreen(pen)+qGreen(paper)),
				  min(255,qBlue(pen)+qBlue(paper)) );
	painter.fillRect(col*char_width, row*line_height, char_width, char_height, color);
	painter.setCompositionMode(QPainter::CompositionMode_SourceOver);

	blob ^= 1;
}


void TTYPixmap::gotoPosition(int row, int col)
{
	hideBlob();
	if(col > cols && (col - cols)*4 >= (256 - cols)*3) col -= 256;
	if(row > rows && (row - rows)*4 >= (256 - rows)*3) row -= 256;
	this->row = row;
	this->col = col;
}

void TTYPixmap::gotoColumn(int col)
{
	hideBlob();
	if(col > cols && (col - cols)*4 >= (256 - cols)*3) col -= 256;
	this->col = col;
}


// ----------------------------------------------------------
// Clear, scroll, copy:

void TTYPixmap::clearScreen()
{
	painter.fillRect(this->rect(), this->paper);

	this->row = this->col = 0;
	this->pushed_blob = this->blob = off;
	update();
}


void TTYPixmap::clearRect(int height, int width)
{
	hideBlob();

	height = min(height,rows-row); if(height<=0) return;
	width  = min(width,cols-col);  if(width<=0)  return;

	int h = line_height;
	int w = char_width;

	painter.fillRect(col*w, row*h, width*w, height*h, paper);
	update(col*w, row*h, width*w, height*h);
}


/*	Scroll screen right (n>0) or left (n<0)
	takes care for cursor position and blob
*/
void TTYPixmap::scrollScreenRight(int n)
{
	int w = this->char_width;
	int cols = this->cols;

	this->col += n;        if((uint)this->col >= (uint)cols) this->blob = off;
	this->pushed_col += n; if((uint)this->pushed_col >= (uint)cols) this->pushed_blob = off;

	if(n>0)		// right
	{
		n = min(n,cols);
		QPixmap z = this->copy(0, 0, (cols-n)*w, this->height());
		painter.drawPixmap(QPoint(n*w,0),z);
		painter.fillRect(0, 0, n*w, this->height(), this->paper);
	}
	else		// left
	{
		n = min(-n,cols);
		QPixmap z = this->copy(n*w,0, (cols-n)*w, this->height());
		painter.drawPixmap(QPoint(0,0),z);
		painter.fillRect((cols-n)*w, 0, n*w, this->height(), this->paper);
	}

	update();
}

/*	Scroll screen up (n>0) or down (n<0)
	takes care for cursor position and blob
*/
void TTYPixmap::scrollScreenUp(int n)
{
	int h = this->line_height;
	int rows = this->rows;

	this->row += n;		if((uint)this->row >= (uint)rows)        this->blob = off;
	this->pushed_row += n;	if((uint)this->pushed_row >= (uint)rows) this->pushed_blob = off;

	if(n>0)			// up
	{
		n = min(n,rows);
		QPixmap z = this->copy(0, n*h, this->width(), (rows-n)*h);
		painter.drawPixmap(QPoint(0,0),z);
		painter.fillRect(0, (rows-n)*h, this->width(), n*h, this->paper);
	}
	else if(n<0)	// down
	{
		n = min(-n,rows);
		QPixmap z = this->copy(0, 0, this->width(), (rows-n)*h);
		painter.drawPixmap(QPoint(0,n*h),z);
		painter.fillRect(0, 0, this->width(), n*h, this->paper);
	}
	update();
}


/*	copy r with size rows * cols from current cursor position to destination position z
*/
void TTYPixmap::copyRect(int z_row, int z_col, int z_rows, int z_cols)
{
	hideBlob();

	z_rows = min(z_rows, this->rows - max(this->row,z_row)); if(z_rows<=0) return;
	z_cols = min(z_cols, this->cols - max(this->col,z_col)); if(z_cols<=0) return;

	int h = this->line_height;
	int w = this->char_width;

	QPixmap z = this->copy(this->col*w, this->row*h, z_cols*w, z_rows*h);
	painter.drawPixmap(QPoint(z_col*w,z_row*h),z);
	update(z_col*w, z_row*h, z_cols*w, z_rows*h);
}


/*	Print character

	Character dimensions:
		w=8 pixel, (incl. character spacing)
		h=12 pixel (incl. line spacing)

	Normal characters:
		0x20 .. 0x7F	ASCII
		0x80 .. 0x9F    UDG from EEPROM
		0xA0 .. 0xFF    LATIN-1

	Graphics characters
	when the graphics attribute is set:

		0x20 .. 0x2F    4/4 Block Graphics, black&white
		0x30 .. 0x3F:   4/4 Block Graphics, grey/white
		0x40 .. 0x4F:   4/4 Block Graphics, black/grey
		0x50 .. 0x57:   1/8  .. 8/8   Bargraph from left
		0x58 .. 0x5F:   1/8  .. 8/8   Bargraph from right
		0x60 .. 0x6B:   1/12 .. 12/12 Bargraph from bottom
		0x6C .. 0x77:   1/12 .. 12/12 Bargraph from top
		0xB0 .. 0xFF:   Line Graphics
						formula for calculating line graphics character code:
							char_code = 0xAF + A*27 + B*9 + C*3 * D
						where A/B/C/D = left/top/right/bottom line stub
						with 0/1/2 = no/thin/thick line
						note: code point 0xAF (no line at all) does not exist
*/
void TTYPixmap::draw_text(uchar* text, int len)
{
	int cw = this->char_width;
	int ch = this->char_height;
	int lh = this->line_height;
	int as = this->ascent;
	int x  = this->col*cw;
	int y  = this->row*lh;
	int a  = this->attr;

	bool dw = a & DOUBLE_WIDTH;
	cw <<= dw;

	// Double height Zeichen können nicht auf die gleiche Grundlinie wie der normalen Zeichen gedruckt werden,
	// weil ihre Unterlänge dann in die nächste Zeile ragen würde.
	// Also keine Schönheitsexperimente. Sie werden oben bündig gedruckt.
	// Note: In der untersten Zeile wird der Bildschirm hochgescrollt.

	if(a & DOUBLE_HEIGHT)
	{
//		if(this->row+2 > this->rows) { scrollScreenUp(1); y -= lh; }
		ch *= 2; lh *= 2; as *= 2;
	}

	if(a & GRAPHICS_CHARACTERS)
	{
		uint8 bu[12];
		QImage z(8, 12, QImage::QImage::Format_Mono);

		for(int i=0;i<len;i++)
		{
			getGraphicsCharMatrix(bu,text[i]);
			for(int row=0; row<12; row++) z.scanLine(row)[0] = bu[row];
			draw_bmp(z,1);
		}
	}
	else
	{
		painter.drawText(x, y+as, QLatin1String((cstr)text,len));		// TODO: UDG
	}

	// paint leading:
	if(lh>ch && (~a & OVERPRINT))
		painter.fillRect(x, y+ch, len*cw, lh-ch, painter.background());

	update(x, y, n*cw, lh);
	col += len<<dw;
}

void TTYPixmap::drawText(uchar* text, int len)
{
	hideBlob();
	bool dw = attr & DOUBLE_WIDTH;

	while(len)
	{
		forceInside();
		int n = (cols - col) >> dw;
		XXXASSERT(n>0);
		n = min(n,len);
		draw_text(text,n);
		text += n;
		len -= n;
	}
}

void TTYPixmap::drawChar(uchar c)
{
	hideBlob();
	forceInside();
	draw_text(&c,1);
}

void TTYPixmap::drawChar(uchar c, int n)
{
	uchar text[n];
	memset(text,c,n);
	drawText(text,n);
}

void TTYPixmap::draw_bmp(QImage& bmp, int n)	// TODO: background mode, attr
{
	bool dw = attr & DOUBLE_WIDTH;
	bool dh = attr & DOUBLE_HEIGHT;
	int  cw = char_width;
	int  ch = char_height;

	while(n--)
	{
		forceInside();
		painter.drawImage(QRect(col*cw, row*ch, cw<<dw, ch<<dh), bmp);
		col += 1<<dw;
	}
}

void TTYPixmap::drawBmp(uint8* p, int h, int n)
{
	hideBlob();
	QImage z(8, h, QImage::QImage::Format_Mono);
	for(int i=0; i<h; i++) z.scanLine(i)[0] = *p++;
	draw_bmp(z,n);
}











