
#define SSD1306_128X32
#include "ch32v003fun.h"
#include <stdio.h>
#include "ssd1306_i2c.h"
#include "ssd1306.h"

#include "data.h"

#define PIN_LED PC4
#define PIN_BTN PC7

// #define JUMP_POWER 48
// #define GRAVITY 3
#define JUMP_POWER 36
#define GRAVITY 2
#define SCROLL_SPEED 1
#define FRAME_DELAY 1000 / 60
#define SCORE_DIGITS 4
#define RUN_ANIM_SPEED 4
#define PLAYER_X 16
#define Y_PRECISION 4

u32 time = 0;
u16 score = 0;
u16 highscore = 0;
s16 player_y = 0;
s16 player_vel = 0;
#define MAX_OBSTACLES 16
u8 obstacle_positions[MAX_OBSTACLES];
u8 obstacle_types[MAX_OBSTACLES]; // todo
u8 obstacle_count = 0;
u8 is_running = 1;
u8 time_to_obstacle = 0;

uint32_t lfsr = 1; // PRNG state

u8 rand8();
void update();
void render();
void restart();

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

	ssd1306_i2c_init();
	ssd1306_init();

	restart();
	while (1)
	{
		ssd1306_refresh();

		if (is_running)
			update();
		render();
		time += 1;

		if (funDigitalRead(PIN_BTN))
		{
			if (is_running)
			{
				if (player_y == 0 && player_vel == 0)
				{
					player_vel = JUMP_POWER;
				}
			}
			else
				restart();
		}
		if (is_running == 0)
		{
			funDigitalWrite(PIN_LED, (time >> 2) & 1);
		}
		else
		{
			funDigitalWrite(PIN_LED, FUN_LOW);
		}
		Delay_Ms(FRAME_DELAY);
	}
}

void restart()
{
	lfsr = SysTick->CNT;
	is_running = 1;
	player_vel = 0;
	player_y = 0;
	obstacle_count = 0;
	time_to_obstacle = 0;
	if (highscore < score)
		highscore = score;
	score = 0;
}

void remove_obstacle()
{
	if (obstacle_count == 0)
		return;
	for (u8 i = 1; i < obstacle_count; i++)
	{
		obstacle_positions[i - 1] = obstacle_positions[i];
		obstacle_types[i - 1] = obstacle_types[i];
	}
	obstacle_count--;
}

void spawn_ground_obstacle(u8 pos)
{
	if (obstacle_count >= MAX_OBSTACLES)
		return;
	obstacle_positions[obstacle_count] = pos;
	obstacle_types[obstacle_count] = rand8() % GROUND_OBSTACLE_TYPES;
	obstacle_count++;
}
void spawn_air_obstacle(u8 pos)
{
	spawn_ground_obstacle(pos);
	obstacle_types[obstacle_count - 1] = 5;
}

void update()
{
	score += (time & 4) != 0;
	if (player_vel != 0 || player_y != 0)
	{
		player_vel -= GRAVITY;
		player_y += player_vel;
		if (player_y < 0)
		{
			player_y = 0;
			player_vel = 0;
		}
	}
	for (u8 i = 0; i < obstacle_count; i++)
	{
		obstacle_positions[i] -= SCROLL_SPEED;
		u8 type = obstacle_types[i];
		u8 dist_x = gfx_abs((s16)obstacle_positions[i] - (s16)PLAYER_X);
		u8 dist_y = gfx_abs((s16)obstacle_y_offset[type] - (s16)(player_y >> Y_PRECISION));

		if (dist_x < obstacle_widths[type] && dist_y < obstacle_heights[type])
		{
			is_running = 0;
		}
	}
	if (obstacle_count > 0 && obstacle_positions[0] == 0)
	{
		remove_obstacle();
	}
	if (time_to_obstacle == 0)
	{
		if (rand8() & rand8() & 1)
			spawn_air_obstacle(128);
		else
		{
			spawn_ground_obstacle(128);
			if (rand8() & rand8() & 1 && obstacle_types[obstacle_count - 1] != 5)
				spawn_ground_obstacle(136);
		}

		time_to_obstacle = (rand8() & 31) + 45;
	}
	time_to_obstacle -= 1;
}

void render_digit(u8 digit, u8 x, u8 y)
{
	u16 pixels = digit_font[digit];
	ssd1306_drawPixel(x + 0, y + 0, (pixels >> 14) & 1);
	ssd1306_drawPixel(x + 1, y + 0, (pixels >> 13) & 1);
	ssd1306_drawPixel(x + 2, y + 0, (pixels >> 12) & 1);
	ssd1306_drawPixel(x + 0, y + 1, (pixels >> 11) & 1);
	ssd1306_drawPixel(x + 1, y + 1, (pixels >> 10) & 1);
	ssd1306_drawPixel(x + 2, y + 1, (pixels >> 9) & 1);
	ssd1306_drawPixel(x + 0, y + 2, (pixels >> 8) & 1);
	ssd1306_drawPixel(x + 1, y + 2, (pixels >> 7) & 1);
	ssd1306_drawPixel(x + 2, y + 2, (pixels >> 6) & 1);
	ssd1306_drawPixel(x + 0, y + 3, (pixels >> 5) & 1);
	ssd1306_drawPixel(x + 1, y + 3, (pixels >> 4) & 1);
	ssd1306_drawPixel(x + 2, y + 3, (pixels >> 3) & 1);
	ssd1306_drawPixel(x + 0, y + 4, (pixels >> 2) & 1);
	ssd1306_drawPixel(x + 1, y + 4, (pixels >> 1) & 1);
	ssd1306_drawPixel(x + 2, y + 4, (pixels >> 0) & 1);
}

void render_number(u32 s, u8 x, u8 y, u8 digits)
{
	u8 painter_x = x + digits * 4;
	u32 val = s;

	for (int i = 0; i < digits; i++)
	{
		u8 digit = val % 10;
		render_digit(digit, painter_x, y);
		val /= 10;
		painter_x -= 4;
	}
}

void render()
{
	ssd1306_setbuf(0);
	ssd1306_drawFastHLine(0, 31, 128, 1);

	render_number(score, 0, 0, SCORE_DIGITS);
	render_number(highscore, 0, 6, SCORE_DIGITS);
	// render_number(obstacle_count, 20, 0, 2);
	// render_number(abs(player_vel), 30, 0, 2);
	// render_number(time_to_obstacle, 20, 6, 3);

	for (u8 i = 0; i < obstacle_count; i++)
	{
		u8 type = obstacle_types[i];
		ssd1306_drawImage(obstacle_positions[i], 31 - 8 - obstacle_y_offset[type], obstacle_sprites[type], 8, 8, 0);
	}

	if (time & RUN_ANIM_SPEED && is_running)
		ssd1306_drawImage(PLAYER_X, 31 - 8 - (player_y >> Y_PRECISION), soweli_a, 8, 8, 0);
	else
		ssd1306_drawImage(PLAYER_X, 31 - 8 - (player_y >> Y_PRECISION), soweli_b, 8, 8, 0);
}

/* White Noise Generator State */
#define NOISE_BITS 8
#define NOISE_MASK ((1 << NOISE_BITS) - 1)
#define NOISE_POLY_TAP0 31
#define NOISE_POLY_TAP1 21
#define NOISE_POLY_TAP2 1
#define NOISE_POLY_TAP3 0
/*
 * random byte generator, taken from ch32v003fun examples
 */
uint8_t rand8(void)
{
	uint8_t bit;
	uint32_t new_data;

	for (bit = 0; bit < NOISE_BITS; bit++)
	{
		new_data = ((lfsr >> NOISE_POLY_TAP0) ^
					(lfsr >> NOISE_POLY_TAP1) ^
					(lfsr >> NOISE_POLY_TAP2) ^
					(lfsr >> NOISE_POLY_TAP3));
		lfsr = (lfsr << 1) | (new_data & 1);
	}

	return lfsr & NOISE_MASK;
}