// Copyright (c) 2026 - 2026 kio@little-bat.de
// BSD-2-Clause license
// https://opensource.org/licenses/BSD-2-Clause

#include "MainWindow.h"
#include "Xoshiro256.h"
#include <QMouseEvent>
#include <QPainter>
#include <QScrollArea>
#include <QScrollBar>
#include <QVector>


const QColor colormap[256] = {
	// 256 VGA colors
	Qt::black,	  Qt::darkRed,	 Qt::darkGreen, Qt::darkYellow, Qt::darkBlue, Qt::darkMagenta,
	Qt::darkCyan, Qt::lightGray, Qt::darkGray,	Qt::red,		Qt::green,	  Qt::yellow,
	Qt::blue,	  Qt::magenta,	 Qt::cyan,		Qt::white,

#define RGB(R, G, B) QColor(R * 51, G * 51, B * 51)
#define RGx(R, G)	 RGB(R, G, 0), RGB(R, G, 1), RGB(R, G, 2), RGB(R, G, 3), RGB(R, G, 4), RGB(R, G, 5)
#define Rxx(R)		 RGx(R, 0), RGx(R, 1), RGx(R, 2), RGx(R, 3), RGx(R, 4), RGx(R, 5)

	Rxx(0),		  Rxx(1),		 Rxx(2),		Rxx(3),			Rxx(4),		  Rxx(5),

#undef RGB
#undef RGx
#undef Rxx

#define W(N) QColor(N * 255 / 26, N * 255 / 26, N * 255 / 26)

	W(1),		  W(2),			 W(3),			W(4),			W(5),		  W(6),
	W(7),		  W(8),			 W(9),			W(10),			W(11),		  W(12),
	W(13),		  W(14),		 W(15),			W(16),			W(17),		  W(18),
	W(19),		  W(20),		 W(21),			W(22),			W(23),		  W(24),

#undef W
};


enum Color {
	black = 0,
	red,
	green,
	yellow,
	blue,
	magenta,
	cyan,
	lightGray,
	darkGray,
	lightRed,
	lightGreen,
	lightYellow,
	lightBlue,
	lightMagenta,
	lightCyan,
	white
};


#define C(r, g, b) 16 + r * 36 + g * 6 + b
#define LR(r)	   C(r, 5, 5)
#define LG(g)	   C(5, g, 5)
#define LB(b)	   C(5, 5, b)
#define DR(r)	   C(r, 0, 0)
#define DG(g)	   C(0, g, 0)
#define DB(b)	   C(0, 0, b)

uint light_color(uint i)
{
	static const uint c[] = {
		LR(0), LR(1), LR(2), LR(3), LR(4), //
		LG(0), LG(1), LG(2), LG(3), LG(4), //
		LB(0), LB(1), LB(2), LB(3), LB(4), //
	};
	return c[i % 15];
}


uint dark_color(uint i)
{
	static const uint c[] = {
		DR(0), DR(1), DR(2), DR(3), DR(4), //
		DG(0), DG(1), DG(2), DG(3), DG(4), //
		DB(0), DB(1), DB(2), DB(3), DB(4), //
	};
	return c[i % 15];
}

using namespace kilipili;
using namespace Graphics;

Xoshiro256	rng(rand());
static bool cleared = true;

uint light_color() { return light_color(uint(rng.next())); } // random color
uint dark_color() { return dark_color(uint(rng.next())); }	 // random color


MainWindow::MainWindow(int w, int h, QWidget* parent) : QMainWindow(parent)
{
	// zoom = 1;
	// hpos = 0;
	// vpos = 0;
	pixmap = new Pixmap_i8(w, h);
	pixmap->clear(::white);
	setMaximumSize(w, h);
	resize(w, h);
}

void MainWindow::paintEvent(QPaintEvent* e)
{
	super::paintEvent(e);
	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing, false);

	limit(0, hpos, pixmap->width * zoom - width());
	limit(0, vpos, pixmap->height * zoom - height());

	int dx = hpos; // add to all final coords
	int dy = vpos;

	int w = pixmap->width;
	int h = pixmap->height;

	for (int y = 0; y < h; y++)
		for (int x = 0; x < w;)
		{
			int color = pixmap->get_color(x, y); // index in colormap[]
			int x1	  = x;
			while (++x < w && pixmap->get_color(x, y) == color) {}
			painter.fillRect(x1 * zoom - dx, y * zoom - dy, (x - x1) * zoom, zoom, colormap[color]);
		}
}

void MainWindow::keyPressEvent(QKeyEvent* e)
{
	bool alt = e->modifiers() & Qt::AltModifier;
	int	 dx	 = alt ? width() / 4 : 8;
	int	 dy	 = alt ? height() / 4 : 8;

	switch (e->key())
	{
	default: return super::keyPressEvent(e);
	case Qt::Key_Left: hpos -= dx; break;
	case Qt::Key_Right: hpos += dx; break;
	case Qt::Key_Up: vpos -= dy; break;
	case Qt::Key_Down: vpos += dy; break;
	}
	e->accept();
	update();
}

void MainWindow::showEvent(QShowEvent* e) { super::showEvent(e); }
void MainWindow::hideEvent(QHideEvent* e) { super::hideEvent(e); }
void MainWindow::resizeEvent(QResizeEvent* e) { super::resizeEvent(e); }
void MainWindow::keyReleaseEvent(QKeyEvent* e) { super::keyReleaseEvent(e); }
void MainWindow::focusInEvent(QFocusEvent* e) { super::focusInEvent(e); }
void MainWindow::focusOutEvent(QFocusEvent* e) { super::focusOutEvent(e); }
void MainWindow::mousePressEvent(QMouseEvent* e) { super::mousePressEvent(e); }
void MainWindow::mouseReleaseEvent(QMouseEvent* e) { super::mouseReleaseEvent(e); }
void MainWindow::mouseMoveEvent(QMouseEvent* e) { super::mouseMoveEvent(e); }
void MainWindow::wheelEvent(QWheelEvent* e) { super::wheelEvent(e); }

void MainWindow::clear()
{
	pixmap->clear(::white);
	cleared = true;
	update();
}

void MainWindow::setZoom(int z)
{
	if (z == zoom) return;
	hpos = hpos * z / zoom;
	vpos = vpos * z / zoom;
	zoom = z;
	setMaximumSize(pixmap->width * z, pixmap->height * z);
	update();
}

void MainWindow::setCanvasSize(int w, int h)
{
	if (w == pixmap->width && h == pixmap->height) return;

	zoom   = 1;
	hpos   = 0;
	vpos   = 0;
	pixmap = new Pixmap_i8(w, h);
	pixmap->clear(::white);
	setMaximumSize(w, h);
	resize(w, h);
}


// ###############################################

void boxes(Canvas* canvas)
{
	for (uint r = 200; r > 10; r--)
	{
		int color = rng.random(256u);
		int w	  = rng.random(r);
		int h	  = rng.random(r);
		int x	  = rng.random(canvas->width) - w / 2;
		int y	  = rng.random(canvas->height) - h / 2;
		canvas->drawRect(x - 1, y - 1, w + 2, h + 2, ::white);
		canvas->fillRect(x, y, w, h, color);
	}
}

void h_and_vlines(Canvas* canvas)
{
	for (uint r = 200; r > 10; r--)
	{
		uint bgcolor = 256 - rng.random(16u);
		uint fgcolor = 16 + rng.random(6 * 6 * 6u);
		int	 w		 = rng.random(r);
		int	 x		 = rng.random(canvas->width) - w / 2;
		int	 y		 = rng.random(canvas->height);

		canvas->drawRect(x, y - 1, w, 3, bgcolor);
		canvas->drawHLine(x, y, w, fgcolor);
	}

	for (uint r = 200; r > 10; r--)
	{
		uint bgcolor = 240 + rng.random(16u);
		uint fgcolor = rng.random(240u);
		if (colormap[fgcolor] == Qt::white) continue;
		if (colormap[fgcolor] == colormap[bgcolor]) continue;

		int h = rng.random(r);
		int x = rng.random(canvas->width);
		int y = rng.random(canvas->height) - h / 2;

		canvas->drawRect(x - 1, y, 3, h, bgcolor);
		canvas->drawVLine(x, y, h, fgcolor);
	}
}

void draw_circles(Canvas* canvas)
{
	for (uint r = 200; r > 0; r--)
	{
		int bgcolor = 240 + rng.random(16u);
		int fgcolor = rng.random(240u);
		if (colormap[fgcolor] == Qt::white) continue;
		if (colormap[fgcolor] == colormap[bgcolor]) continue;

		int x = rng.random(canvas->width) - r / 2;
		int y = rng.random(canvas->height) - r / 2;
		// canvas->fillRect(x, y, r, r, bgcolor);
		canvas->drawCircle(x, y, r, r, fgcolor);
	}
}

void fill_circles(Canvas* canvas)
{
	for (uint r = 200; r > 0; r--)
	{
		uint bgcolor = 240u + rng.random(16u);
		uint fgcolor = rng.random(240u);
		if (colormap[fgcolor] == Qt::white) continue;
		if (colormap[fgcolor] == colormap[bgcolor]) continue;

		int x = rng.random(canvas->width) - r / 2;
		int y = rng.random(canvas->height) - r / 2;
		//canvas->fillRect(x, y, r, r, bgcolor);
		canvas->fillCircle(x, y, x + r, y + r, fgcolor);
	}
}

void draw_lines(Canvas* canvas)
{
	for (int i = 0; i < 200; i++)
	{
		int	 x1		 = rng.random(canvas->width);
		int	 x2		 = rng.random(canvas->width);
		int	 y1		 = rng.random(canvas->height);
		int	 y2		 = rng.random(canvas->height);
		uint fgcolor = rng.random(256u);
		if (colormap[fgcolor] == Qt::white) continue;

		canvas->drawLine(x1, y1, x2, y2, fgcolor);
	}
}

void draw_polygons(Canvas* canvas)
{
	for (int n = 0; n < 5; n++)
	{
		uint fgcolor = rng.random(256u);
		if (colormap[fgcolor] == Qt::white) continue;
		int cnt = rng.random(10) + 1;

		Point points[20];
		for (int i = 0; i < cnt; i++)
		{
			int x	  = rng.random(canvas->width);
			int y	  = rng.random(canvas->height);
			points[i] = Point(x, y);
		}
		points[cnt] = points[0];

		canvas->drawPolygon(points, cnt + 1, fgcolor);
	}
}

static constexpr double pi = 3.1415926535898;

void fill_forms(Canvas* canvas)
{
	canvas->clear(::white);

	static int test = 0;
	test			= cleared ? 0 : (test + 1) % 3;
	int h			= canvas->height;
	int w			= canvas->width;

	switch (test)
	{
	case 0: // star, black = fg
	{
		for (int i = 0; i < 30; i++) canvas->drawHLine(0, h * i / 30, w, light_color());
		for (int i = 0; i < 40; i++) canvas->drawVLine(w * i / 40, 0, h, light_color());
		int r = w * 11 / 20;
		canvas->drawCircle(w / 2 - r, h / 2 - r, r * 2, r * 2, ::black);
		Point triangle[4] = {Point(w / 2, h / 2), Point(), Point(), Point(w / 2, h / 2)};
		r				  = r * 18 / 30;
		const int d		  = 20;
		for (int i = 0; i < d; i += 2)
		{
			float x		= sin(2 * pi / d * i) * r;
			float y		= cos(2 * pi / d * i) * r;
			triangle[1] = Point(w / 2 + coord(x), h / 2 + coord(y));
			x			= sin(2 * pi / d * ++i) * r;
			y			= cos(2 * pi / d * i--) * r;
			triangle[2] = Point(w / 2 + coord(x), h / 2 + coord(y));
			canvas->drawPolygon(triangle, 4, ::black);
		}
		canvas->floodFill(0, h / 2, ::black);
		break;
	}
	case 1: // rhombs with yellow dot: black = fg
	{
		for (int y = 0; y < h; y += 4)	   //
			for (int x = 0; x < w; x += 4) //
			{
				canvas->setPixel(x, y, ::yellow);
				canvas->setPixel(x + 1, y, ::black);
				canvas->setPixel(x, y + 1, ::black);
				canvas->setPixel(x - 1, y, ::black);
				canvas->setPixel(x, y - 1, ::black);
			}
		canvas->floodFill(w / 2 + 2, h / 2 + 2, ::black);
		break;
	}
	case 2: // grid of dots, black = fg
	{
		for (int y = 0; y < h; y += 2)	   //
			for (int x = 0; x < w; x += 2) //
				canvas->setPixel(x, y, ::black);
		canvas->floodFill(w / 2 + 1, h / 2 + 1, ::black);
		break;
	}
		//case 3: // star, white = bg
		//case 4: // rhombs with red dot: white = bg
		//case 5: // grid of dots, white = bg
	}
}

double sin(int r) { return sin(r * pi / 180); }
double cos(int r) { return cos(r * pi / 180); }

void test_triangles(Canvas* canvas)
{
	Point triangles[] = {
		{8, 0}, {10, 10}, {0, 4},  // left pointing
		{1, 0}, {10, 6},  {0, 10}, // right pointing
		{0, 0}, {10, 0},  {7, 10}, // top=hline
		{8, 0}, {10, 10}, {1, 10}  // bottom=hline
	};

	int x0	   = 5;
	int y0	   = canvas->height / 50;
	int width  = canvas->width - canvas->height - x0; // left strip
	int height = (canvas->height - 2 * y0) / 4;

	for (int i = 0; i < 12; i += 3)
	{
		Point p1 {x0 + triangles[i + 0].x * width / 10, y0 + triangles[i + 0].y * height / 10};
		Point p2 {x0 + triangles[i + 1].x * width / 10, y0 + triangles[i + 1].y * height / 10};
		Point p3 {x0 + triangles[i + 2].x * width / 10, y0 + triangles[i + 2].y * height / 10};
		canvas->fillRect(x0, y0, width, height, ::lightGreen);
		canvas->drawTriangle(p1, p2, p3, ::lightRed);
		canvas->fillTriangle(p1, p2, p3, ::lightGray);
		y0 += height + 2;
	}

	height = canvas->height;
	Point mp {canvas->width - canvas->height / 2, height / 2};
	int	  ri = height / 2 / 5; // inner radius
	int	  ro = height / 2 - 5; // outer radius

	static int w0 = 0;
	w0 += 10;

	for (int w = 0; w < 360; w += 60)
	{
		Point p1 = mp + Dist(ri * sin(w0 + w - 30), ri * cos(w0 + w - 30));
		Point p2 = mp + Dist(ri * sin(w0 + w + 30), ri * cos(w0 + w + 30));
		Point p3 = mp + Dist(ro * sin(w0 + w), ro * cos(w0 + w));
		Point p4 = mp + Dist(ro * sin(w0 + w + 60), ro * cos(w0 + w + 60));
		canvas->fillTriangle(p1, p2, mp, ::red + w / 60);
		canvas->fillTriangle(p1, p2, p3, ::black + w / 60);
		canvas->fillTriangle(p2, p3, p4, ::lightRed + w / 60);
	}
}

QVector<QString> MainWindow::names {
	"some boxes",	 //
	"h&&v lines",	 //
	"draw circles",	 //
	"fill circles",	 //
	"draw lines",	 //
	"draw polygons", //
	"flood fill",	 //
	"triangle test", //
};

void MainWindow::runTest(int i)
{
	switch (i)
	{
	case 0: boxes(pixmap); break;
	case 1: h_and_vlines(pixmap); break;
	case 2: draw_circles(pixmap); break;
	case 3: fill_circles(pixmap); break;
	case 4: draw_lines(pixmap); break;
	case 5: draw_polygons(pixmap); break;
	case 6: fill_forms(pixmap); break;
	case 7: test_triangles(pixmap); break;
	}
	cleared = false;
	update();
}


/*












































*/
