#define SSD1306_128X32
#include "ch32v003fun.h"
#include "ssd1306_i2c.h"
#include "ssd1306.h"

#include "data.h"

#define PIN_LED PC4
#define PIN_BTN PC7

#define WIDTH 40
#define HEIGHT 32
#define X_OFFSET ((128 - WIDTH) / 2)

void decode_next_frame();
void refresh_screen();
void clear_screen();
u32 reader = 0;

u32 frame[WIDTH];

int main() {
	SystemInit();
	// funGpioInitAll();
	// funPinMode(PIN_LED, GPIO_CFGLR_OUT_10Mhz_PP);
	// funPinMode(PIN_BTN, GPIO_CFGLR_IN_PUPD);

	// initialize OLED
	ssd1306_i2c_init();
	// not using ssd1306_init because it does more than i want
	uint8_t *cmd_list = (uint8_t *)ssd1306_init_array;
	while (*cmd_list != SSD1306_TERMINATE_CMDS)
		ssd1306_cmd(*cmd_list++);
	clear_screen();

	while (1) {
		decode_next_frame();
		refresh_screen();
		Delay_Ms(300);
		if (reader >= sizeof(video))
			reader = 0;
	}
}

void refresh_screen() {
	ssd1306_cmd(SSD1306_COLUMNADDR);
	ssd1306_cmd(X_OFFSET);			   // Column start address (0 = reset)
	ssd1306_cmd(X_OFFSET + WIDTH - 1); // Column end address (127 = reset)

	ssd1306_cmd(SSD1306_PAGEADDR);
	ssd1306_cmd(0); // Page start address (0 = reset)
	ssd1306_cmd(7); // Page end address

	u8 packet[SSD1306_PSZ];
	u8 x;
	u8 packet_index;
	u8 page = 0;
	while (page < 8) {
		/* odd rows are unused so each nybble is expanded */
		packet[packet_index] = expand[(frame[x] >> (page * 4)) & 0xf];
		x++;
		packet_index++;
		if (x == WIDTH) {
			x = 0;
			page++;
		}
		if (packet_index == SSD1306_PSZ) {
			ssd1306_data(packet, SSD1306_PSZ);
			packet_index = 0;
		}
	}
}

void clear_screen() {
	ssd1306_cmd(SSD1306_COLUMNADDR);
	ssd1306_cmd(0);	  // Column start address (0 = reset)
	ssd1306_cmd(127); // Column end address (127 = reset)
	ssd1306_cmd(SSD1306_PAGEADDR);
	ssd1306_cmd(0); // Page start address (0 = reset)
	ssd1306_cmd(7); // Page end address
	for (u32 i = 0; i < 32 * 8; i++)
		ssd1306_data(frame, SSD1306_PSZ);
}

u8 next_byte() {
	u8 byte = video[reader];
	reader += 1;
	return byte;
}

void fill_frame(u8 color) {
	memset(frame, color ? 0xFFFFFFFF : 0x00, sizeof(frame));
}

void set_pixel(u8 x, u8 y, u8 color) {
	if (color)
		frame[x] |= (1 << (y & 31));
	else
		frame[x] &= ~(1 << (y & 31));
}

void rle_horizontal();
void rle_vertical();
void bg_strips_h();
void cell_diff_8v();

void decode_next_frame() {
	Encoding_t encoding = next_byte();
	switch (encoding) {
#ifdef USE_FillBlack
	case Encoding_FillBlack:
		fill_frame(0);
		break;
#endif
#ifdef USE_FillWhite
	case Encoding_FillWhite:
		fill_frame(1);
		break;
#endif
#ifdef USE_RLEHorizontal
	case Encoding_RLEHorizontal:
		rle_horizontal();
		break;
#endif
#ifdef USE_RLEVertical
	case Encoding_RLEVertical:
		rle_vertical();
		break;
#endif
#ifdef USE_BGStripsH16
	case Encoding_BGStripsH16:
		bg_strips_h();
		break;
#endif
#ifdef USE_CellDiff8V
	case Encoding_CellDiff8V:
		cell_diff_8v();
		break;
#endif
	default:
		memset(frame, 0b10101010, sizeof(frame));
		break;
	}
}

#ifdef USE_RLEHorizontal
void rle_horizontal() {
	u8 x = 0;
	u8 y = 0;
	u8 color = 0;
	while (y < HEIGHT) {
		u8 run = next_byte();
		for (int i = 0; i < run; i++) {
			set_pixel(x, y, color);
			x += 1;
			if (x == WIDTH) {
				x = 0;
				y += 1;
			}
		}
		color = 1 - color;
	}
}
#endif

#ifdef USE_RLEVertical
void rle_vertical() {
	u8 x = 0;
	u8 y = 0;
	u8 color = 0;
	while (x < WIDTH) {
		u8 run = next_byte();
		for (int i = 0; i < run; i++) {
			set_pixel(x, y, color);
			y += 1;
			if (y == HEIGHT) {
				y = 0;
				x += 1;
			}
		}
		color = 1 - color;
	}
}
#endif

#ifdef USE_BGStripsH16
void bg_strips_h() {
	u8 head = next_byte();
	u8 bg = head >> 7;
	u8 fg = 1 - bg;
	fill_frame(bg);
	u8 strip_count = head & 127;
	for (u8 i = 0; i < strip_count; i++) {
		u16 packed = (next_byte() << 8) | next_byte();
		u8 y = packed >> 11;
		u8 x_start = (packed >> 5) & 0x3f;
		u8 width = packed & 0x1f;
		for (int x = x_start; x < (x_start + width); x++) {
			set_pixel(x, y, fg);
		}
	}
}
#endif

#ifdef USE_CellDiff8V
void cell_diff_8v() {
	u32 bitmap = (next_byte() << 16) | (next_byte() << 8) | next_byte();

	u8 run_remaining = next_byte();
	u8 color = 0;
	for (u8 x = 0; x < WIDTH; x++) {
		u8 cellx = x >> 3;
		for (u8 y = 0; y < HEIGHT; y++) {
			u8 celly = y >> 3;
			if (bitmap >> (cellx + (WIDTH / 8) * celly) & 1) {
				// advance rle once
				if (run_remaining == 0) {
					run_remaining = next_byte();
					color = 1 - color;
					if (run_remaining == 0) {
						run_remaining = next_byte();
						color = 1 - color;
					}
				}
				set_pixel(x, y, color);
				run_remaining--;
			}
		}
	}
}
#endif
