/*
 *  JoeyDev
 *  Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
*/


#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"


#include "common.h"
#include "draw.h"


typedef struct {
	jint16      StartX;
	jint16      EndX;
	jint16      Y;
	signed char Dir;         // 'signed' needs to be specified for ORCA/C
	jbool       ScanLeft;
	jbool       ScanRight;
	jbool       padding;     // Aligns structure on x86
} _jlScanDataT;


static int32_t _jlSeed = 0;


static void          _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how);
static void          _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how);
static _jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight);


void jlContextDel(jlContextT **context) {
	jlContextT *c = (jlContextT *)*context;
	DEL(c);
}


void jlContextFlush(jlContextT *c) {
	int          x;
	int          y;
	int          p;
	unsigned int offset = 0;

	// This converts the 4 bit image to true color using the current palette
	// assignments.  This reflects how the image would appear if the palette
	// is changed after drawing which doesn't happen without this extra step.

	for (y=0; y<200; y++) {
		for (x=0; x<320; x++) {
			p = jlDrawPixelGet(c, x, y);
			c->_trueColorPixels[offset]     = c->_jlPalette[p].b * 16;
			c->_trueColorPixels[offset + 1] = c->_jlPalette[p].g * 16;
			c->_trueColorPixels[offset + 2] = c->_jlPalette[p].r * 16;
			// We're using CAIRO_FORMAT_RGB24 so the upper 8 bits are not used.
			c->_trueColorPixels[offset + 3] = 255;  // This is alpha in CAIRO_FORMAT_ARGB32 mode.
			offset += 4;
		}
	}
}


jlContextT *jlContextNew(jbyte *pixels) {
	jlContextT *c = NEW(jlContextT);

	// "pixels" is ARGB32 @ 320x200

	c->_jlDrawColor     = 15;
	c->_jlDrawFillColor = 0;
	c->_jlFillStackTop  = NULL;
	c->_trueColorPixels = pixels;

	jlPaletteDefault(c);

	memset(c->_pixels, 0, 32000);

	return c;
}


void jlDrawBox(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
	jlDrawLine(c, x1, y1, x2, y1);
	jlDrawLine(c, x2, y1, x2, y2);
	jlDrawLine(c, x2, y2, x1, y2);
	jlDrawLine(c, x1, y2, x1, y1);
}


void jlDrawBoxFilled(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
	jint16 y;
	for (y=y1; y<=y2; y++) {
		jlDrawLine(c, x1, y, x2, y);
	}
}


void jlDrawCircle(jlContextT *c, jint16 x0, jint16 y0, jint16 radius) {
	jint16 x   = radius-1;
	jint16 y   = 0;
	jint16 dx  = 1;
	jint16 dy  = 1;
	jint16 err = dx - (jint16)(radius << 1);

	while (x >= y) {
		jlDrawPixelSet(c, x0 + x, y0 + y);
		jlDrawPixelSet(c, x0 + y, y0 + x);
		jlDrawPixelSet(c, x0 - y, y0 + x);
		jlDrawPixelSet(c, x0 - x, y0 + y);
		jlDrawPixelSet(c, x0 - x, y0 - y);
		jlDrawPixelSet(c, x0 - y, y0 - x);
		jlDrawPixelSet(c, x0 + y, y0 - x);
		jlDrawPixelSet(c, x0 + x, y0 - y);

		if (err <= 0) {
			y++;
			err += dy;
			dy += 2;
		} else {
			x--;
			dx += 2;
			err += dx - (radius << 1);
		}
	}
}


void jlDrawClear(jlContextT *c) {
	jlDrawBoxFilled(c, 0, 0, 319, 199);
}


void jlDrawColorSet(jlContextT *c, jbyte index) {
	c->_jlDrawColor = index;
}


// http://members.chello.at/~easyfilter/bresenham.html
void jlDrawEllipse(jlContextT *c, jint16 x0, jint16 y0, jint16 x1, jint16 y1) {
	jint16 a   = (jint16)abs(x1-x0), b = (jint16)abs(y1-y0), b1 = b&1; /* values of diameter */
	long   dx  = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */
	long   err = dx+dy+b1*a*a, e2; /* error of 1.step */

	if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
	if (y0 > y1) y0 = y1; /* .. exchange them */
	y0 += (b+1)/2; y1 = y0-b1;   /* starting pixel */
	a *= 8*a; b1 = 8*b*b;

	do {
		jlDrawPixelSet(c, x1, y0); /*   I. Quadrant */
		jlDrawPixelSet(c, x0, y0); /*  II. Quadrant */
		jlDrawPixelSet(c, x0, y1); /* III. Quadrant */
		jlDrawPixelSet(c, x1, y1); /*  IV. Quadrant */
		e2 = 2*err;
		if (e2 <= dy) { y0++; y1--; err += dy += a; }  /* y step */
		if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */
	} while (x0 <= x1);

	while (y0-y1 < b) {  /* too early stop of flat ellipses a=1 */
		jlDrawPixelSet(c, x0-1, y0); /* -> finish tip of ellipse */
		jlDrawPixelSet(c, x1+1, y0++);
		jlDrawPixelSet(c, x0-1, y1);
		jlDrawPixelSet(c, x1+1, y1--);
	}
}


_jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight) {
	_jlScanDataT *s = NEW(_jlScanDataT);

	(void)c;

	s->StartX = startX;
	s->EndX = endX;
	s->Y = y;
	s->Dir = dir;
	s->ScanLeft = scanLeft;
	s->ScanRight = scanRight;

	return s;
}


void _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how) {
	jint16 regionStart = -1;
	jint16 x;

	for (x=startX; x<endX; x++) {
		if (how) {
			// Unrolled contents to reduce comparison complexity.
			if ((isNextInDir || x < ignoreStart || x >= ignoreEnd) && (jlDrawPixelGet(c, x, y) == c->_jlDrawFillColor)) {
				jlDrawPixelSet(c, x, y);
				if (regionStart < 0) regionStart = x;
			} else if (regionStart >= 0) {
				jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse));
				regionStart = -1;
			}
		} else {
			// Unrolled contents to reduce comparison complexity.
			if ((isNextInDir || x < ignoreStart || x >= ignoreEnd) && (jlDrawPixelGet(c, x, y) != c->_jlDrawFillColor)) {
				jlDrawPixelSet(c, x, y);
				if (regionStart < 0) regionStart = x;
			} else if (regionStart >= 0) {
				jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse));
				regionStart = -1;
			}
		}
		if (!isNextInDir && x < ignoreEnd && x >= ignoreStart) x = ignoreEnd-1;
	}
	if (regionStart >= 0) {
		jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jtrue));
	}
}


// Stole this from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html
void _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how) {
	jint16        height = 200;
	jint16        width  = 320;
	_jlScanDataT *r;
	jint16        startX;
	jint16        endX;

	// how == true;   Fill on top of _jlDrawFillColor
	// how == false;  Fill on top of any color until we encounter _jlDrawFillColor

	jlDrawPixelSet(c, x, y);

	jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, x, x+1, y, 0, jtrue, jtrue));
	while ((r = (_jlScanDataT *)jlUtilStackPop(c, c->_jlFillStackTop)) != NULL) {
		startX = r->StartX;
		endX = r->EndX;
		if (r->ScanLeft) {
			if (how) {
				while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y);
			} else {
				while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y);
			}
		}
		if (r->ScanRight) {
			if (how) {
				while(endX < width && (jlDrawPixelGet(c, endX, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y);
			} else {
				while(endX < width && (jlDrawPixelGet(c, endX, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y);
			}
		}
		r->StartX--;
		r->EndX++;
		if (r->Y > 0) _jlDrawFillAddLine(c, startX, endX, r->Y-1, r->StartX, r->EndX, -1, r->Dir <= 0, how);
		if (r->Y < height-1) _jlDrawFillAddLine(c, startX, endX, r->Y+1, r->StartX, r->EndX, 1, r->Dir >= 0, how);
		DEL(r);
	}
}


void jlDrawFill(jlContextT *c, jint16 x, jint16 y) {
	c->_jlDrawFillColor = jlDrawPixelGet(c, x, y);
	_jlDrawFill(c, x, y, jtrue);
}


void jlDrawFillTo(jlContextT *c, jint16 x, jint16 y, jbyte color) {
	c->_jlDrawFillColor = color;
	_jlDrawFill(c, x, y, jfalse);
}


void jlDrawLine(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
	jint16 x;
	jint16 y;
	jint16 dx;
	jint16 dy;
	jint16 incX;
	jint16 incY;
	jint16 balance;

	if (x2 >= x1) {
		dx = x2 - x1;
		incX = 1;
	} else {
		dx = x1 - x2;
		incX = -1;
	}

	if (y2 >= y1) {
		dy = y2 - y1;
		incY = 1;
	} else {
		dy = y1 - y2;
		incY = -1;
	}

	x = x1;
	y = y1;

	if (dx >= dy) {
		dy <<= 1;
		balance = dy - dx;
		dx <<= 1;
		while (x != x2) {
			jlDrawPixelSet(c, x, y);
			if (balance >= 0) {
				y += incY;
				balance -= dx;
			}
			balance += dy;
			x += incX;
		}
		jlDrawPixelSet(c, x, y);
	} else {
		dx <<= 1;
		balance = dx - dy;
		dy <<= 1;
		while (y != y2) {
			jlDrawPixelSet(c, x, y);
			if (balance >= 0) {
				x += incX;
				balance -= dy;
			}
			balance += dx;
			y += incY;
		}
		jlDrawPixelSet(c, x, y);
	}
}


jbyte jlDrawPixelGet(jlContextT *c, jint16 x, jint16 y) {
	int p = x / 2 + y * 160;

	// Clip at edge.  We can do this on modern machines!
	if (x <   0) return 0;
	if (x > 319) return 0;
	if (y <   0) return 0;
	if (y > 199) return 0;

	return (jlUtilIsOdd(x) ? c->_pixels[p].l : c->_pixels[p].r);
}


void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y) {

	// Clip at edge.  We can do this on modern machines!
	if (x <   0) return;
	if (x > 319) return;
	if (y <   0) return;
	if (y > 199) return;

	jlPixelPairT *pixelPair = c->_pixels + (y * 160) + (x / 2);
	if (jlUtilIsOdd(x)) {
		pixelPair->l = c->_jlDrawColor;
	} else {
		pixelPair->r = c->_jlDrawColor;
	}
}


void jlPaletteDefault(jlContextT *c) {
	// Set palette.
	c->_jlPalette[ 0].r =  0;  c->_jlPalette[ 0].g =  0;  c->_jlPalette[ 0].b =  0;  // 000000 Black
	c->_jlPalette[ 1].r =  0;  c->_jlPalette[ 1].g =  0;  c->_jlPalette[ 1].b = 10;  // 0000AA Blue
	c->_jlPalette[ 2].r =  0;  c->_jlPalette[ 2].g = 10;  c->_jlPalette[ 2].b =  0;  // 00AA00 Green
	c->_jlPalette[ 3].r =  0;  c->_jlPalette[ 3].g = 10;  c->_jlPalette[ 3].b = 10;  // 00AAAA Cyan
	c->_jlPalette[ 4].r = 10;  c->_jlPalette[ 4].g =  0;  c->_jlPalette[ 4].b =  0;  // AA0000 Red
	c->_jlPalette[ 5].r = 10;  c->_jlPalette[ 5].g =  0;  c->_jlPalette[ 5].b = 10;  // AA00AA Magenta
	c->_jlPalette[ 6].r = 10;  c->_jlPalette[ 6].g =  5;  c->_jlPalette[ 6].b =  0;  // AA5500 Brown
	c->_jlPalette[ 7].r = 10;  c->_jlPalette[ 7].g = 10;  c->_jlPalette[ 7].b = 10;  // AAAAAA Light Gray
	c->_jlPalette[ 8].r =  5;  c->_jlPalette[ 8].g =  5;  c->_jlPalette[ 8].b =  5;  // 555555 Dark Gray
	c->_jlPalette[ 9].r =  5;  c->_jlPalette[ 9].g =  5;  c->_jlPalette[ 9].b = 15;  // 5555FF Bright Blue
	c->_jlPalette[10].r =  5;  c->_jlPalette[10].g = 15;  c->_jlPalette[10].b =  5;  // 55FF55 Bright Green
	c->_jlPalette[11].r =  5;  c->_jlPalette[11].g = 15;  c->_jlPalette[11].b = 15;  // 55FFFF Bright Cyan
	c->_jlPalette[12].r = 15;  c->_jlPalette[12].g =  5;  c->_jlPalette[12].b =  5;  // FF5555 Bright Red
	c->_jlPalette[13].r = 15;  c->_jlPalette[13].g =  5;  c->_jlPalette[13].b = 15;  // FF55FF Bright Magenta
	c->_jlPalette[14].r = 15;  c->_jlPalette[14].g = 15;  c->_jlPalette[14].b =  5;  // FFFF55 Bright Yellow
	c->_jlPalette[15].r = 15;  c->_jlPalette[15].g = 15;  c->_jlPalette[15].b = 15;  // FFFFFF White
}


void jlPaletteGet(jlContextT *c, jbyte index, jbyte *r, jbyte *g, jbyte *b) {
	// This is not a standard JoeyLib API.  It returns color values in 0-255 instead of 0-15.
	*r = c->_jlPalette[index].r;
	*g = c->_jlPalette[index].g;
	*b = c->_jlPalette[index].b;
}


void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b) {
	c->_jlPalette[index].r = r;
	c->_jlPalette[index].g = g;
	c->_jlPalette[index].b = b;
}


int16_t jlUtilRandom(void) {
	_jlSeed = _jlSeed * 1103515245 + 12345;
	return _jlSeed / 65536;
}


int jlUtilRandomSeedGet(void) {
	return _jlSeed;
}


void jlUtilRandomSeedSet(int seed) {
	_jlSeed = seed;
}


void *_jlUtilStackPop(jlContextT *c, jlStackT **stack) {
	void *d = NULL;
	jlStackT *s;

	(void)c;

	if (*stack != NULL) {
		s = *stack;
		d = s->data;
		*stack = s->previous;
		DEL(s);
	}
	return d;
}


void _jlUtilStackPush(jlContextT *c, jlStackT **stack, void *data, jint16 line, const char *file) {
	jlStackT *s = NULL;

	(void)line;
	(void)file;
	(void)c;

	s = NEW(jlStackT);
	s->previous = *stack;
	s->data = data;
	*stack = s;
}


#pragma clang diagnostic pop
