
// ****************************************************************************
//
//                                   Drawing
//
// ****************************************************************************

#include "../include.h"

// prefix table (exp: -12, -9, -6, -3, 0, +3, +6, +9)
#define NUM_PREFIXES	8	// number of unit prefixes
const u8 PrefixTab[NUM_PREFIXES] = {'p', 'n', 0x60 /*'u'*/, 'm', 0, 'k', 'M', 'G'};

const u8* DrawFont = FONT;		// current draw font
int FontHeight = FONTH;			// height of system font

// clear screen
void DrawClear()
{
	memset(FrameBuf, 0, FRAMESIZE);
}

// ----------------------------------------------------------------------------
//                               Draw point
// ----------------------------------------------------------------------------

// draw pixel fast without limits
void DrawPointFast(int x, int y, u8 col)
{
	u8* d = &FrameBuf[(x>>3) + y*WIDTHBYTE];
	x = 7 - (x & 7);
	x = 1<<x;
	if (col == 0)
		*d &= ~x;
	else
		*d |= x;
}

// draw pixel
void DrawPoint(int x, int y, u8 col)
{
	if ((x >= 0) && (x < WIDTH) && (y >= 0) && (y < HEIGHT)) DrawPointFast(x, y, col);
}

// clear pixel fast without limits
void DrawPointClrFast(int x, int y)
{
	// clear pixel
	u8* d = &FrameBuf[(x>>3) + y*WIDTHBYTE];
	x = 7 - (x & 7);
	*d &= ~(1<<x);
}

// clear pixel
void DrawPointClr(int x, int y)
{
	if ((x >= 0) && (x < WIDTH) && (y >= 0) && (y < HEIGHT)) DrawPointClrFast(x, y);
}

// set pixel fast without limits
void DrawPointSetFast(int x, int y)
{
	// clear pixel
	u8* d = &FrameBuf[(x>>3) + y*WIDTHBYTE];
	x = 7 - (x & 7);
	*d |= (1<<x);
}

// clear pixel
void DrawPointSet(int x, int y)
{
	if ((x >= 0) && (x < WIDTH) && (y >= 0) && (y < HEIGHT)) DrawPointSetFast(x, y);
}

// invert pixel fast without limits
void DrawPointInvFast(int x, int y)
{
	// invert pixel
	u8* d = &FrameBuf[(x>>3) + y*WIDTHBYTE];
	x = 7 - (x & 7);
	*d ^= 1<<x;
}

// invert pixel
void DrawPointInv(int x, int y)
{
	if ((x >= 0) && (x < WIDTH) && (y >= 0) && (y < HEIGHT)) DrawPointInvFast(x, y);
}

// ----------------------------------------------------------------------------
//                            Draw rectangle
// ----------------------------------------------------------------------------

// clear rectangle fast - x and w must be multiply of 8
void DrawRectClrFast(int x, int y, int w, int h)
{
	x >>= 3;
	w >>= 3;
	u8* dst = &FrameBuf[x + y*WIDTHBYTE];
	int wb = WIDTHBYTE - w;
	int i;
	for (; h > 0; h--)
	{
		for (i = w; i > 0; i--) *dst++ = 0;
		dst += wb;
	}
}

// clear rectangle
void DrawRectClr(int x, int y, int w, int h)
{
	// limit x
	if (x < 0) { w += x; x = 0; }

	// limit w
	if (x + w > WIDTH) w = WIDTH - x;
	if (w <= 0) return;

	// limit y
	if (y < 0) { h += y; y = 0; }

	// limit h
	if (y + h > HEIGHT) h = HEIGHT - y;
	if (h <= 0) return;

	// fast mode
	if (((x & 7) == 0) && ((w & 7) == 0))
	{
		DrawRectClrFast(x, y, w, h);
	}
	else
	{
		// slow mode
		int x0 = x;
		int w2;
		for (; h > 0; h--)
		{
			x = x0;
			for (w2 = w; w2 > 0; w2--)
			{
				DrawPointClrFast(x, y);
				x++;
			}
			y++;
		}
	}
}

// set rectangle fast - x and w must be multiply of 8
void DrawRectSetFast(int x, int y, int w, int h)
{
	x >>= 3;
	w >>= 3;
	u8* dst = &FrameBuf[x + y*WIDTHBYTE];
	int wb = WIDTHBYTE - w;
	int i;
	for (; h > 0; h--)
	{
		for (i = w; i > 0; i--) *dst++ = 0xff;
		dst += wb;
	}
}

// set rectangle
void DrawRectSet(int x, int y, int w, int h)
{
	// limit x
	if (x < 0) { w += x; x = 0; }

	// limit w
	if (x + w > WIDTH) w = WIDTH - x;
	if (w <= 0) return;

	// limit y
	if (y < 0) { h += y; y = 0; }

	// limit h
	if (y + h > HEIGHT) h = HEIGHT - y;
	if (h <= 0) return;

	// fast mode
	if (((x & 7) == 0) && ((w & 7) == 0))
	{
		DrawRectSetFast(x, y, w, h);
	}
	else
	{
		// slow mode
		int x0 = x;
		int w2;
		for (; h > 0; h--)
		{
			x = x0;
			for (w2 = w; w2 > 0; w2--)
			{
				DrawPointSetFast(x, y);
				x++;
			}
			y++;
		}
	}
}

// invert rectangle fast - x and w must be multiply of 8
void DrawRectInvFast(int x, int y, int w, int h)
{
	x >>= 3;
	w >>= 3;
	u8* dst = &FrameBuf[x + y*WIDTHBYTE];
	int wb = WIDTHBYTE - w;
	int i;
	for (; h > 0; h--)
	{
		for (i = w; i > 0; i--)
		{
			*dst ^= 0xff;
			dst++;
		}
		dst += wb;
	}
}

// invert rectangle
void DrawRectInv(int x, int y, int w, int h)
{
	// limit x
	if (x < 0) { w += x; x = 0; }

	// limit w
	if (x + w > WIDTH) w = WIDTH - x;
	if (w <= 0) return;

	// limit y
	if (y < 0) { h += y; y = 0; }

	// limit h
	if (y + h > HEIGHT) h = HEIGHT - y;
	if (h <= 0) return;

	// fast mode
	if (((x & 7) == 0) && ((w & 7) == 0))
	{
		DrawRectInvFast(x, y, w, h);
	}
	else
	{
		// slow mode
		int x0 = x;
		int w2;
		for (; h > 0; h--)
		{
			x = x0;
			for (w2 = w; w2 > 0; w2--)
			{
				DrawPointInvFast(x, y);
				x++;
			}
			y++;
		}
	}
}

// draw rectangle fast - x and w must be multiply of 8
void DrawRectFast(int x, int y, int w, int h, u8 col)
{
	if (col == 0)
		DrawRectClrFast(x, y, w, h);
	else
		DrawRectSetFast(x, y, w, h);
}

// draw rectangle
void DrawRect(int x, int y, int w, int h, u8 col)
{
	if (col == 0)
		DrawRectClr(x, y, w, h);
	else
		DrawRectSet(x, y, w, h);
}

// ----------------------------------------------------------------------------
//                 Draw horizontal line, vertical line, frame
// ----------------------------------------------------------------------------

// draw horizontal line (fast version: x and w must be multiply of 8, does not check coordinates)
void DrawHLineFast(int x, int y, int w, u8 col) { DrawRectFast(x, y, w, 1, col); }
void DrawHLine(int x, int y, int w, u8 col) { DrawRect(x, y, w, 1, col); }

// clear horizontal line (fast version: x and w must be multiply of 8, does not check coordinates)
void DrawHLineClrFast(int x, int y, int w) { DrawRectClrFast(x, y, w, 1); }
void DrawHLineClr(int x, int y, int w) { DrawRectClr(x, y, w, 1); }

// set horizontal line (fast version: x and w must be multiply of 8, does not check coordinates)
void DrawHLineSetFast(int x, int y, int w) { DrawRectSetFast(x, y, w, 1); }
void DrawHLineSet(int x, int y, int w) { DrawRectSet(x, y, w, 1); }

// invert horizontal line (fast version: x and w must be multiply of 8, does not check coordinates)
void DrawHLineInvFast(int x, int y, int w) { DrawRectInvFast(x, y, w, 1); }
void DrawHLineInv(int x, int y, int w) { DrawRectInv(x, y, w, 1); }

// draw vertical line
void DrawVLine(int x, int y, int h, u8 col) { DrawRect(x, y, 1, h, col); }

// clear vertical line
void DrawVLineClr(int x, int y, int h) { DrawRectClr(x, y, 1, h); }

// set vertical line
void DrawVLineSet(int x, int y, int h) { DrawRectSet(x, y, 1, h); }

// invert vertical line
void DrawVLineInv(int x, int y, int h) { DrawRectInv(x, y, 1, h); }

// draw frame
void DrawFrame(int x, int y, int w, int h, u8 col)
{
	if (w <= 1)
	{
		DrawVLine(x, y, h, col);
	}
	else if (h <= 1)
	{
		DrawHLine(x, y, w, col);
	}
	else
	{
		DrawHLine(x, y, w, col);
		DrawHLine(x, y+h-1, w, col);
		DrawVLine(x, y+1, h-2, col);
		DrawVLine(x+w-1, y+1, h-2, col);
	}
}

// clear frame
void DrawFrameClr(int x, int y, int w, int h)
{
	if (w <= 1)
	{
		DrawVLineClr(x, y, h);
	}
	else if (h <= 1)
	{
		DrawHLineClr(x, y, w);
	}
	else
	{
		DrawHLineClr(x, y, w);
		DrawHLineClr(x, y+h-1, w);
		DrawVLineClr(x, y+1, h-2);
		DrawVLineClr(x+w-1, y+1, h-2);
	}
}

// set frame
void DrawFrameSet(int x, int y, int w, int h)
{
	if (w <= 1)
	{
		DrawVLineSet(x, y, h);
	}
	else if (h <= 1)
	{
		DrawHLineSet(x, y, w);
	}
	else
	{
		DrawHLineSet(x, y, w);
		DrawHLineSet(x, y+h-1, w);
		DrawVLineSet(x, y+1, h-2);
		DrawVLineSet(x+w-1, y+1, h-2);
	}
}

// invert frame
void DrawFrameInv(int x, int y, int w, int h)
{
	if (w <= 1)
	{
		DrawVLineInv(x, y, h);
	}
	else if (h <= 1)
	{
		DrawHLineInv(x, y, w);
	}
	else
	{
		DrawHLineInv(x, y, w);
		DrawHLineInv(x, y+h-1, w);
		DrawVLineInv(x, y+1, h-2);
		DrawVLineInv(x+w-1, y+1, h-2);
	}
}

// ----------------------------------------------------------------------------
//                                  Draw line
// ----------------------------------------------------------------------------

// draw line
void DrawLine(int x1, int y1, int x2, int y2, u8 col)
{
	// difference of coordinates
	int dx = x2 - x1;
	int dy = y2 - y1;

	// increment X
	int sx = 1;
	if (dx < 0)
	{
		sx = -1;
		dx = -dx;
	}

	// increment Y
	int sy = 1;
	if (dy < 0)
	{
		sy = -1;
		dy = -dy;
	}

	// steeply in X direction, X is prefered as base
	if (dx > dy)
	{
		int m = 2*dy;
		int p = m - dx;
		dx = 2*dx;
		x2 += sx;
		for (; x1 != x2; x1 += sx)
		{
			DrawPoint(x1, y1, col);

			if (p > 0)
			{
				y1 += sy;
				p -= dx;
			}
			p += m;
		}
	}

	// steeply in Y direction, Y is prefered as base
	else
	{
		int m = 2*dx;
		int p = m - dy;
		dy = 2*dy;
		y2 += sy;
		for (; y1 != y2; y1 += sy)
		{
			DrawPoint(x1, y1, col);

			if (p > 0)
			{
				x1 += sx;
				p -= dy;
			}
			p += m;
		}
	}
}

// set line fast (no checks)
void DrawLineSetFast(int x1, int y1, int x2, int y2)
{
	// difference of coordinates
	int dx = x2 - x1;
	int dy = y2 - y1;

	// increment X
	int sx = 1;
	if (dx < 0)
	{
		sx = -1;
		dx = -dx;
	}

	// increment Y
	int sy = 1;
	if (dy < 0)
	{
		sy = -1;
		dy = -dy;
	}

	// steeply in X direction, X is prefered as base
	if (dx > dy)
	{
		int m = 2*dy;
		int p = m - dx;
		dx = 2*dx;
		x2 += sx;
		for (; x1 != x2; x1 += sx)
		{
			DrawPointSetFast(x1, y1);

			if (p > 0)
			{
				y1 += sy;
				p -= dx;
			}
			p += m;
		}
	}

	// steeply in Y direction, Y is prefered as base
	else
	{
		int m = 2*dx;
		int p = m - dy;
		dy = 2*dy;
		y2 += sy;
		for (; y1 != y2; y1 += sy)
		{
			DrawPointSetFast(x1, y1);

			if (p > 0)
			{
				x1 += sx;
				p -= dy;
			}
			p += m;
		}
	}
}

// ----------------------------------------------------------------------------
//                               Text
// ----------------------------------------------------------------------------

// draw character fast (x must be multiply of 8, does not check coordinates)
void DrawCharFast(char ch, int x, int y)
{
	int i;
	x >>= 3;
	u8 m;
	const u8* src = &DrawFont[(u8)ch];
	u8* dst = &FrameBuf[x + y*WIDTHBYTE];
	u8 inv = ((s8)ch < 0) ? 0xff : 0;
	for (i = FontHeight; i > 0; i--)
	{
		*dst = *src ^ inv;
		src += 128;
		dst += WIDTHBYTE;
	}
}

// Draw character
void DrawChar(char ch, int x, int y)
{
	// fast version
	if ((x >= 0) && (x <= WIDTH-8) && ((x & 7) == 0) && (y >= 0) && (y <= HEIGHT-FontHeight))
	{
		DrawCharFast(ch, x, y);
	}
	else
	{
		// slow version
		u8 inv = ((s8)ch < 0) ? 0xff : 0;
		const u8* src = &DrawFont[(u8)ch];
		int i, j;
		u8 m;
		for (i = FontHeight; i > 0; i--)
		{
			m = *src ^ inv;
			for (j = 8; j > 0; j--)
			{
				if ((m & B7) != 0)
					DrawPointSet(x, y);
				else
					DrawPointClr(x, y);
				m <<= 1;
				x++;
			}
			x -= 8;
			y++;
			src += 128;
		}
	}
}

// draw character inverted fast (x must be multiply of 8, does not check coordinates)
void DrawCharInvFast(char ch, int x, int y)
{
	int i;
	x >>= 3;
	u8 m;
	const u8* src = &DrawFont[(u8)ch];
	u8* dst = &FrameBuf[x + y*WIDTHBYTE];
	for (i = FontHeight; i > 0; i--)
	{
		*dst = ~*src;
		src += 128;
		dst += WIDTHBYTE;
	}
}

// Draw character inverted
void DrawCharInv(char ch, int x, int y)
{
	// fast version
	if ((x >= 0) && (x <= WIDTH-8) && ((x & 7) == 0) && (y >= 0) && (y <= HEIGHT-FontHeight))
	{
		DrawCharInvFast(ch, x, y);
	}
	else
	{
		// slow version
		const u8* src = &DrawFont[(u8)ch];
		int i, j;
		u8 m;
		for (i = FontHeight; i > 0; i--)
		{
			m = *src;
			for (j = 8; j > 0; j--)
			{
				if ((m & B7) == 0)
					DrawPointSet(x, y);
				else
					DrawPointClr(x, y);
				m <<= 1;
				x++;
			}
			x -= 8;
			y++;
			src += 128;
		}
	}
}

// Draw character double-sized
void DrawChar2(char ch, int x, int y)
{
	const u8* src = &DrawFont[(u8)ch];
	int i, j;
	u8 m;
	for (i = FontHeight; i > 0; i--)
	{
		m = *src;
		for (j = 8; j > 0; j--)
		{
			if ((m & B7) != 0) 
			{
				DrawPointSet(x, y);
				DrawPointSet(x+1, y);
				DrawPointSet(x, y+1);
				DrawPointSet(x+1, y+1);
			}
			else
			{
				DrawPointClr(x, y);
				DrawPointClr(x+1, y);
				DrawPointClr(x, y+1);
				DrawPointClr(x+1, y+1);
			}
			m <<= 1;
			x += 2;
		}
		x -= 2*8;
		y += 2;
		src += 128;
	}
}

// draw text fast (x must be multiply of 8, does not check coordinates)
void DrawTextFast(const char* text, int x, int y)
{
	char ch;
	while ((ch = *text++) != 0)
	{
		DrawCharFast(ch, x, y);
		x += 8;
	}
}

// Draw text
void DrawText(const char* text, int x, int y)
{
	char ch;
	while ((ch = *text++) != 0)
	{
		DrawChar(ch, x, y);
		x += 8;
	}
}

// draw text inverted fast (x must be multiply of 8, does not check coordinates)
void DrawTextInvFast(const char* text, int x, int y)
{
	char ch;
	while ((ch = *text++) != 0)
	{
		DrawCharInvFast(ch, x, y);
		x += 8;
	}
}

// Draw text inverted
void DrawTextInv(const char* text, int x, int y)
{
	char ch;
	while ((ch = *text++) != 0)
	{
		DrawCharInv(ch, x, y);
		x += 8;
	}
}

// Draw text double-sized
void DrawText2(const char* text, int x, int y)
{
	char ch;
	while ((ch = *text++) != 0)
	{
		DrawChar2(ch, x, y);
		x += 2*8;
	}
}

// set print font
void SetFont(const char* font, int fonth)
{
	DrawFont = font;
	FontHeight = fonth;
}

// display unsigned value (returns next X coordinate; need to call DispUpdate())
//  x ... X coordinate
//  y ... Y coordinate
//  val ... value
//  dig ... number of valid digits
//  ex ... decimal exponent related to base unit, in range -18..+8
//  unit ... unit character, 0=none
//  small ... use small unit characters
//  allsmall ... display all text small
int DispUVal(int x, int y, u32 val, int dig, int ex, char unit, Bool small, Bool allsmall)
{
	int i;
	int pref = 0; // prefix (0=none)
	int off = 0; // exponent offset to next 10^3 step
	int inx; // index ID
	int len; // string length
	char* s; // text pointer

	// select font
	SelFont12();

	// clear to rest of line
	DrawRectClr(x, y, ((y<TITLE_H) ? (WIDTH-1) : WIDTH)-x, allsmall ? FONTH : (FONTH2));

	// prepare minimal (4 digits: 1000) and maximal (4 digits: 9999) value
	int min = 1;
	for (i = dig-1; i > 0; i--) min *= 10;
	int max = min*10 - 1;

	// special case '0'
	if (val == 0)
	{
		if (allsmall)
		{
			DrawChar('0', x, y); x += 8;
			DrawRectSet(x+1, y+8, 2, 2); x += 4; // dot
			for (dig--; dig > 0; dig--) { DrawChar('0', x, y); x += 8; } // 0.0
		}
		else
		{
			DrawChar2('0', x, y); x += 16;
			DrawRectSet(x+2, y+16, 4, 4); x += 8; // dot
			for (dig--; dig > 0; dig--) { DrawChar2('0', x, y); x += 16; } // 0.0
		}

		if (unit != 0)
		{
			if (allsmall)
			{
				DrawChar(unit, x, y);
				x += 8;
			}
			else if (small)
			{
				DrawChar(unit, x, y+SMALL_Y);
				x += 8;
			}
			else
			{
				DrawChar2(unit, x, y);
				x += 16;
			}
		}
		return x;
	}

	// normalize value
	while (val > max)
	{
		val += 5;	// rounding
		val /= 10;	// scale value down by 10
		ex++;		// increase exponent
	}

	while (val < min)
	{
		val *= 10;	// scale value up by 10
		ex--;		// decrease exponent
	}

	// determine prefix and offset
	ex += 11+dig; // shift exponent to be >=0
	inx = ex / 3; // number of 10^3 steps
	off = ex - inx*3; // offset of lower 10^3 steps
	off = dig-1 - off; // reverse offset 1..3

	// get prefix (inx: 0='p', 1='n', ... 7='G')
	pref = PrefixTab[inx];

	// decode number into buffer
	len = DecUNum(DecNumBuf, val, 0);
	s = DecNumBuf;

	// dot position
	ex = len - off - 1;

	// prepend zero 0.
	if (ex < 0)
	{
		if (allsmall)
		{
			DrawChar('0', x, y); x += 8;
			DrawRectSet(x+1, y+8, 2, 2); x += 4; // dot
			if (ex < -1) { DrawChar('0', x, y); x += 8; } // 0.0
			if (ex < -2) { DrawChar('0', x, y); x += 8; } // 0.00
		}
		else
		{
			DrawChar2('0', x, y); x += 16;
			DrawRectSet(x+2, y+16, 4, 4); x += 8; // dot
			if (ex < -1) { DrawChar2('0', x, y); x += 16; } // 0.0
			if (ex < -2) { DrawChar2('0', x, y); x += 16; } // 0.00
		}
	}

	// display value
	for (inx = 0; inx < len; inx++)
	{
		if (allsmall)
		{
			DrawChar(*s++, x, y); x += 8;
			if ((inx == ex) && (inx < len-1))
			{
				DrawRectSet(x+1, y+8, 2, 2); x += 4; // dot
			}
		}
		else
		{
			DrawChar2(*s++, x, y); x += 16;
			if ((inx == ex) && (inx < len-1))
			{
				DrawRectSet(x+2, y+16, 4, 4); x += 8; // dot
			}
		}
	}

	// display prefix and unit
	if ((pref != 0) || (unit != 0))
	{
		if (pref != 0)
		{
			if (allsmall)
			{
				DrawChar(pref, x, y);
				x += 8;
			}
			else if (small)
			{
				DrawChar(pref, x, y+SMALL_Y);
				x += 8;
			}
			else
			{
				DrawChar2(pref, x, y);
				x += 16;
			}
		}

		if (unit != 0)
		{
			if (allsmall)
			{
				DrawChar(unit, x, y);
				x += 8;
			}
			else if (small)
			{
				DrawChar(unit, x, y+SMALL_Y);
				x += 8;
			}
			else
			{
				DrawChar2(unit, x, y);
				x += 16;
			}
		}
	}
	return x;
}

// display signed value (returns next X coordinate; need to call DispUpdate())
//  x ... X coordinate
//  y ... Y coordinate
//  val ... value
//  dig ... number of valid digits
//  ex ... decimal exponent related to base unit, in range -18..+8
//  unit ... unit character, 0=none
//  small ... use small unit characters
//  allsmall ... display all text small
int DispVal(int x, int y, s32 val, int dig, int ex, char unit, Bool small, Bool allsmall)
{
	char ch = ' ';
	if (val < 0)
	{
		ch = '-';
		val = -val;
	}

	SelFont12();
	if (allsmall)
	{
		DrawChar(ch, x, y);
		x += 8;
	}
	else
	{
		DrawChar2(ch, x, y);
		x += 16;
	}
	return DispUVal(x, y, val, dig, ex, unit, small, allsmall);
}
