#include <Arduino.h>

#include "SPI.h"
#include <Arduino_GFX_Library.h>
#include <U8g2lib.h>

extern "C" {
#include "hsv2rgb.h"
#include "sin1.h"
}


const char name[] = "Your Name";



#define SPI0_SCK			2
#define SPI0_MISO			GFX_NOT_DEFINED
#define SPI0_MOSI			3

#define DISP_NSS			1
#define DISP_RESET  		4
#define DISP_DC     		5

#define TEXT_MAX_SIZE		5
#define TEXT_BORDER_COLOR	DARKGREY

#define ROT_SIZE        	(14 * TEXT_MAX_SIZE)
#define TRANSPARENT_BG		CYAN

#define LETTER_SPACING  	1942    // user-configurable


Arduino_DataBus *bus = new Arduino_RPiPicoSPI(DISP_DC, DISP_NSS, SPI0_SCK, SPI0_MOSI, SPI0_MISO, spi0);
Arduino_GFX *disp = new Arduino_GC9A01(bus, DISP_RESET, 0 /* rotation */, true /* IPS */);

Arduino_Canvas *dout  = new Arduino_Canvas(240 /* width */, 240 /* height */, disp);
Arduino_Canvas *drot  = new Arduino_Canvas(ROT_SIZE, ROT_SIZE, disp);

color_hsv hsv = {0, 255, 255};
color_rgb rgb;


void setup()
{
	Serial.begin(115200);

	Serial.println("starting...");

	delay(50);

	bus->begin(90000000);

	if (!disp->begin()) {
		Serial.println("failed to start disp");
	}

	dout->begin();
	dout->fillScreen(BLACK);
	dout->flush();

	drot->begin();
	drot->setFont(u8g2_font_cubic11_h_cjk);     // u8g2_font_spleen16x32_mr
}

int16_t render_drot_printchar(const char *str, uint8_t idx, uint8_t size, uint16_t color)
{
	if (size > TEXT_MAX_SIZE) size = TEXT_MAX_SIZE;

	// clear the rotation buffer
	drot->fillScreen(TRANSPARENT_BG);

	// print the letter to the center of the rotation buffer
	drot->setCursor(1, (size * 10));
	drot->setTextColor(color);
	drot->print(str[idx]);
	return drot->getCursorX() - size + 1;
}

void render_drot_border(int8_t x_width, uint16_t text_color, uint16_t border_color)
{
	uint16_t *src, *dst;
	int16_t x, y;
	uint8_t is_color;

	// framebuffer pointers
	src = drot->getFramebuffer();
	dst = dout->getFramebuffer();

	// naive bordering
	for (y = 0; y < ROT_SIZE; y++) {
		is_color = 0;
		for (x = 0; x < x_width; x++) {
			if (is_color) {
				if (src[x + (y * ROT_SIZE)] != text_color) {
					is_color = 0;
					src[x + (y * ROT_SIZE)] = border_color;
				}
			} else {
				if (src[x + (y * ROT_SIZE)] == text_color) {
					is_color = 1;
					src[x + (y * ROT_SIZE) - 1] = border_color;
				}
			}
		}
	}

	for (x = 0; x < x_width; x++) {
        is_color = 0;
        for (y = 0; y < ROT_SIZE; y++) {
            if (is_color) {
                if (src[x + (y * ROT_SIZE)] != text_color) {
                    is_color = 0;
                    src[x + (y * ROT_SIZE)] = border_color;
                }
            } else {
                if (src[x + (y * ROT_SIZE)] == text_color) {
                    is_color = 1;
                    src[x + (y * ROT_SIZE) - ROT_SIZE] = border_color;
                }
            }
        }
    }
}

void render_drot_rotate(int16_t x_width, int16_t rot, int16_t out_x, int16_t out_y)
{
	int16_t x, y;
	int16_t nx, ny;
	int16_t sx, sy;

	uint16_t *src, *dst;
	uint16_t src_pxl;


	// position
	int16_t start_x = out_x + 120;
	int16_t start_y = out_y + 120;

	// framebuffer pointers
	src = drot->getFramebuffer();
	dst = dout->getFramebuffer();

	// rotate+copy the buffer
	for (y = 0; y < ROT_SIZE; y++) {
		for (x = 0; x < x_width; x++) {
			src_pxl = src[x + (y * ROT_SIZE)];
			sx = x                + 1 - (x_width >> 1);
			sy = y - (ROT_SIZE/2) + 1;
			if (src_pxl != TRANSPARENT_BG) {
				nx = ((sx * cos1(rot)) - (sy * sin1(rot))) >> 15;
				ny = ((sx * sin1(rot)) + (sy * cos1(rot))) >> 15;

				nx += start_x;
				ny += start_y;
				
				if ((nx >= 0) && (nx < 240)) {
					if ((ny >= 0) && (ny < 240)) {
						dst[nx + (ny*240)] = src_pxl;
					}
				}
			}
		}
	}
}

void text_print_circular(const char *str, uint8_t text_size, int16_t spacing, uint16_t fcolor, uint16_t bcolor, int16_t rot)
{
	uint8_t i;
	int16_t x_width;

	int16_t x, y;
	int16_t out_x, out_y;
	
	if (text_size > TEXT_MAX_SIZE) text_size = TEXT_MAX_SIZE;
	drot->setTextSize(text_size);

	for (i = 0; i < strlen(name); i++) {    
		x = cos1(rot);
		x = (x >> 9) + (x >> 11) + (x >> 12);
		
		y = sin1(rot);
		y = (y >> 9) + (y >> 11) + (y >> 12);

		x_width = render_drot_printchar(str, i, text_size, fcolor);	// render character
		render_drot_border(x_width, fcolor, bcolor);				// add border to character
		render_drot_rotate(x_width, rot + 0x6000, x, y);
		rot -= spacing;		
	}
}

int16_t trot = 0;
int16_t brot = 0;

#define HUE_MIN     0x2000-300
#define HUE_MAX     0x2000+300
#define HUE_STEP    3

int16_t huecycle = 0;
uint8_t huedir = 0;
int16_t satcycle = 0;
uint8_t satdir = 0;

void bg_huesatcycle()
{
	dout->fillScreen(BLACK);

	// hue between limits
	if (!huedir) huecycle += HUE_STEP; else huecycle -= HUE_STEP;
	if (huecycle > HUE_MAX) {
		huedir = 1; huecycle = HUE_MAX;
	}
	if (huecycle < HUE_MIN) {
		huedir = 0; huecycle = HUE_MIN;
	}

	// saturation
	if (!satdir) satcycle++; else satcycle--;
	if (satcycle > 240) {
		satdir = 1;
		satcycle = 240;
	}
	if (satcycle < 20) {
		satdir = 0;
		satcycle = 60;
	}

	hsv.h = huecycle >> 3;
	hsv.s = satcycle;

	// draw some circles
	for (int8_t i = 120; i > 0; i--) {
		hsv.h += 2;
		hsv.h %= 1536;
		hsv2rgb_8b(hsv.h, hsv.s, (i * 2), &rgb.r, &rgb.g, &rgb.b);

		dout->drawCircle(120, 120, i, dout->color565(rgb.r, rgb.g, rgb.b));
	}
}

void bg_rainbow_circles(uint8_t saturation)
{
	uint16_t hue;

	// speed
	hsv.h += 24;

	// background fill ("X" pattern)
	// hue = hsv.h + 512;
	// hsv.h %= 1536;
	// hsv2rgb_8b(hue, 255, 128, &rgb.r, &rgb.g, &rgb.b);
	// dout->fillScreen(dout->color565(rgb.r, rgb.g, rgb.b));
	dout->fillScreen(BLACK);

	// draw some circles
	hue = hsv.h;
	for (int8_t i = 120; i > 0; i--) {
		hue += 2;
		hue %= 1536;
		
		// "flat" mode, same brightness
		hsv2rgb_8b(hue, saturation, 192, &rgb.r, &rgb.g, &rgb.b);
		
		// "tunnel" mode, darker center
		// hsv2rgb_8b(hue, saturation, (i * 2), &rgb.r, &rgb.g, &rgb.b);

		dout->drawCircle(120, 120, i, dout->color565(rgb.r, rgb.g, rgb.b));
	}
}

void bg_radarsweep(int16_t rot, int16_t offset, uint8_t lines)
{
	color_rgb rgb;
	uint16_t w;

	int16_t x, y;

	if (lines == 0xff) lines--;

	dout->fillScreen(BLACK);

	for (uint8_t i = 0; i < lines; i++) {
		// linear fade
		w = 256 * lines;
		w -= (256 * i);
		w /= lines;
		w &= 0xff;

		// todo: exponential fade or something

		// get rotational point
		x = cos1(rot);
		x = (x >> 8);
		
		y = sin1(rot);
		y = (y >> 8);

		hsv2rgb_8b((1536/3), 200, (i > 4) ? (w >> 1) : w, &rgb.r, &rgb.g, &rgb.b);
		dout->drawLine(x + 120, y + 120, 128, 128, dout->color565(rgb.r, rgb.g, rgb.b));
		rot -= offset;
	}
}

void texthue_180degree()
{
	uint16_t hue;

	hue = hsv.h + 768;
	hue %= 1536;
	hsv2rgb_8b(hue, 224, 64, &rgb.r, &rgb.g, &rgb.b);
}

void texthue_huesatcycle_circles()
{
	uint16_t hue;
	
	hue = (huecycle - 0x2000);
	if (hue > (1536 << 3)) hue += (1536 << 3);
	hsv2rgb_8b(hue >> 3, 224, 64, &rgb.r, &rgb.g, &rgb.b);
}


void loop()
{
	uint16_t hue;
	color_rgb fc, bc;

	// rainbow circles program
	// bg_rainbow_circles(200);
	// texthue_180degree();
	brot += 400;
	bg_radarsweep(brot, 25, 200);

	// small hue shift with larger saturation shifts circles
	// bg_huesatcycle_circles();
	// texthue_huesatcycle();

	// text rotation - black text, default border
	trot += 420 - 69;
	fc.r = fc.g = fc.b = 240;
	bc.r = bc.g = bc.b = 32;
	text_print_circular(name, TEXT_MAX_SIZE, LETTER_SPACING,
			dout->color565(fc.r, fc.g, fc.b),
			dout->color565(bc.r, bc.g, bc.b),
			trot);

	dout->flush();
}