/main/gfx.c
#include <dirent.h>
#include <string.h>
#include <time.h>

#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "gfx.h"
#include "fonts.h"
#include "sprites.h"
#include "keyboard.h"

u8g2_t u8g2;
gfx_data_t gfx_data;
TaskHandle_t gfx_task_handle;
SemaphoreHandle_t gfx_mutex;
bool gfx_enable = true;
const char TAG[] = "GFX";

void draw_clock(gfx_data_t *data) {
	char buf[16];
	struct tm timeinfo;
	clock_scene_t* clock = &data->clock;
	uint64_t t = esp_timer_get_time();

	localtime_r(&clock->time, &timeinfo);
	strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo);

	u8g2_SetFont(&u8g2, hyperspace_13n);
	u8g2_ClearBuffer(&u8g2);
	u8g2_DrawStr(&u8g2, 4, 23, buf);
	u8g2_DrawHLine(&u8g2, 0, 6, 128);
	u8g2_DrawHLine(&u8g2, 0, 26, 128);

	u8g2_SetFont(&u8g2, maximum_overdrift);
	char *bt_status = "";
	if (data->bt.mode == BT_ACTIVE) {
		bt_status = "\x01";
	} else if (data->bt.mode == BT_DISCOVERABLE) {
		bt_status = "(\x01)";
	}
	snprintf(buf, 16, "%s", bt_status);
	u8g2_DrawStr(&u8g2, 2, 6, buf);

	char b = 3;
	if (data->battery.charging) {
		b = 4 + ((t / 300000) % 3);
	}
	snprintf(buf, 10, "%c%d%%", b, data->battery.percentage);
	u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
	u8g2_DrawStr(&u8g2, 126 - w, 6, buf);
}

void draw_menu(menu_scene_t *data) {
	u8g2_SetFont(&u8g2, maximum_overdrift);
	u8g2_ClearBuffer(&u8g2);
	u8g2_DrawLine(&u8g2, 63, 32, 73, 22);
	u8g2_DrawHLine(&u8g2, 74, 22, 54);
	u8g2_DrawStr(&u8g2, 74, 31, data->title);
	
	for (int i = 0; i < data->n_items; i++) {
		int x = (i / 3) * 64;
		int y = (i % 3) * 11 + 2;

		u8g2_DrawXBM(&u8g2, x, y, btn_1_width, btn_1_height, btn_list[i]);
		u8g2_DrawStr(&u8g2, x + 7, y + 7, data->items[i].label);
	}
}

void gfx_message(const char* str) {
	u8g2_SetFont(&u8g2, maximum_overdrift);
	u8g2_SetDrawColor(&u8g2, 0);
	u8g2_DrawBox(&u8g2, 8, 8, 128 - 16, 32 - 16);
	u8g2_SetDrawColor(&u8g2, 1);
	u8g2_DrawFrame(&u8g2, 8, 8, 128 - 16, 32 - 16);

	int w = u8g2_GetStrWidth(&u8g2, str);
	u8g2_DrawStr(&u8g2, 64 - w / 2, 16 + 3, str);
	u8g2_SendBuffer(&u8g2);
	key_get_pressed();
}

void draw_play(gfx_data_t *data) {
	struct tm localtime;
	int len, x;
	char buf[68];
	uint64_t t = esp_timer_get_time();

	u8g2_ClearBuffer(&u8g2);

	u8g2_DrawHLine(&u8g2, 0, 20, 59);
	u8g2_DrawLine(&u8g2, 59, 19, 68, 10);
	u8g2_DrawHLine(&u8g2, 69, 9, 59);

	if (data->play.play_state == PLAY_STATE_PLAYING) {
		x = (t / 4000) % 256;
		if (x < 128) {
			u8g2_SetDrawColor(&u8g2, 0);
			u8g2_DrawBox(&u8g2, x, 9, 13, 12);
			u8g2_SetDrawColor(&u8g2, 1);
		}
	}

	u8g2_DrawXBM(&u8g2, 2, 0, btn_rtrk_1_width, btn_rtrk_1_height, btn_rtrk_1_bits);
	u8g2_DrawXBM(&u8g2, 13, 0, btn_rrev_2_width, btn_rrev_2_height, btn_rrev_2_bits);
	if (data->play.play_state == PLAY_STATE_PLAYING) {
		u8g2_DrawXBM(&u8g2, 22, 0, btn_pause_3_width, btn_pause_3_height, btn_pause_3_bits);
	} else {
		u8g2_DrawXBM(&u8g2, 22, 0, btn_play_3_width, btn_play_3_height, btn_play_3_bits);
	}
	u8g2_DrawXBM(&u8g2, 31, 0, btn_ffwd_4_width, btn_ffwd_4_height, btn_ffwd_4_bits);
	u8g2_DrawXBM(&u8g2, 40, 0, btn_ftrk_5_width, btn_ftrk_5_height, btn_ftrk_5_bits);

	u8g2_SetFont(&u8g2, hyperspace_7n);

	gmtime_r(&data->playing_meta.track_position, &localtime);
	strftime(buf, sizeof(buf), "%M:%S", &localtime);
	len = u8g2_GetStrWidth(&u8g2, buf);
	u8g2_DrawStr(&u8g2, 58 - len, 18, buf);

	gmtime_r(&data->playing_meta.track_length, &localtime);
	strftime(buf, sizeof(buf), "%M:%S", &localtime);
	u8g2_DrawStr(&u8g2, 70, 19, buf);

	// TODO: actual font
	u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
	if (data->playing_meta.artist[0]) {
		snprintf(buf, 68, "%s - %s", data->playing_meta.artist, data->playing_meta.title);
	} else {
		snprintf(buf, 68, "%s", data->playing_meta.title);
	}

	u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf);
	if (w > 128) {
		int l = w - 128;
		int x = (t / 100000) % l;
		u8g2_DrawStr(&u8g2, -x, 30, buf);
	} else {
		u8g2_DrawStr(&u8g2, 0, 30, buf);
	}
}

void draw_bt(bt_info_t* info) {
	char buf[22];
	u8g2_SetFont(&u8g2, maximum_overdrift);

	char *bt_status = "";
	u8g2_ClearBuffer(&u8g2);
	switch (info->mode) {
	case BT_INACTIVE:
		bt_status = "INACTIVE";
		break;
	case BT_ACTIVE:
		bt_status = "ACTIVE";
		break;
	case BT_DISCOVERABLE:
		bt_status = "DISCOVERABLE";
		break;
	}

	if (info->confirm) {
		sprintf(buf, "CONFIRM: %06d", info->confirm);
		u8g2_DrawStr(&u8g2, 2, 6, buf);

		u8g2_DrawXBM(&u8g2, 2, 25, btn_1_width, btn_1_height, btn_1_bits);
		u8g2_DrawStr(&u8g2, 8, 32, "YES");

		u8g2_DrawXBM(&u8g2, 64, 25, btn_2_width, btn_2_height, btn_2_bits);
		u8g2_DrawStr(&u8g2, 72, 32, "NO");
	} else {
		sprintf(buf, "STATUS: %s", bt_status);
		u8g2_DrawStr(&u8g2, 2, 6, buf);

		u8g2_DrawXBM(&u8g2, 2, 25, btn_1_width, btn_1_height, btn_1_bits);
		u8g2_DrawStr(&u8g2, 8, 32, "TOGGLE");
	}
}

void draw_browse(browse_scene_t* bs) {
	u8g2_ClearBuffer(&u8g2);
	u8g2_SetFont(&u8g2, maximum_overdrift);

	for (int i = 0; i < 4; i++) {
		if (bs->names[i] == 0) {
			break;
		}
		u8g2_DrawXBM(&u8g2, 2, i * 8, btn_1_width, btn_1_height, btn_list[i]);
		u8g2_DrawStr(&u8g2, 9, i * 8 + 7, bs->types[i] == DT_DIR ? "\x10" : "\x8");
		u8g2_DrawStr(&u8g2, 16, i * 8 + 7, bs->names[i]);
	}

	u8g2_SetDrawColor(&u8g2, 0);
	u8g2_DrawBox(&u8g2, 122, 0, 6, 32);
	if (bs->state == BROWSE_NONE || bs->state == BROWSE_AT_BOTTOM) {
		u8g2_SetDrawColor(&u8g2, 1);
		u8g2_DrawBox(&u8g2, 123, 0, 5, 15);
		u8g2_SetDrawColor(&u8g2, 0);
		u8g2_DrawStr(&u8g2, 124, 11, "+");
	}
	if (bs->state == BROWSE_NONE || bs->state == BROWSE_AT_TOP) {
		u8g2_SetDrawColor(&u8g2, 1);
		u8g2_DrawBox(&u8g2, 123, 17, 5, 15);
		u8g2_SetDrawColor(&u8g2, 0);
		u8g2_DrawStr(&u8g2, 124, 28, "-");
	}
	u8g2_SetDrawColor(&u8g2, 1);
}

void gfx_thread(void *pvParameters) {
	while (1) {
		// wait for data update
		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
		ESP_LOGD(TAG, "data update");
		xSemaphoreTake(gfx_mutex, portMAX_DELAY);
		ESP_LOGD(TAG, "mutex acquired");
		// At this point we have ownership of gfx_data

		if (!gfx_enable) {
			// disable drawing
			xSemaphoreGive(gfx_mutex);
			continue;
		}

		switch (gfx_data.scene) {
		case SCENE_CLOCK:
			draw_clock(&gfx_data);
			break;
		case SCENE_MENU:
			draw_menu(&gfx_data.menu);
			break;
		case SCENE_PLAY:
			draw_play(&gfx_data);
			break;
		case SCENE_BT:
			draw_bt(&gfx_data.bt);
			break;
		case SCENE_BROWSE:
			draw_browse(&gfx_data.browse);
			break;
		case SCENE_NO_CHANGE:
			ESP_LOGE(TAG, "SCENE_NO_CHANGE somehow set in gfx_data");
			esp_system_abort("invalid state");
		}

		// And we're done now
		xSemaphoreGive(gfx_mutex);
		ESP_LOGD(TAG, "mutex released");

		// Send the data
		u8g2_SendBuffer(&u8g2);
	}
}

gfx_data_t * gfx_acquire_data(scene_t scene) {
	xSemaphoreTake(gfx_mutex, portMAX_DELAY);
	size_t start_of_data = offsetof(gfx_data_t, clock);
	if (scene != SCENE_NO_CHANGE) {
		memset(((uint8_t*)&gfx_data) + start_of_data, 0, sizeof(gfx_data_t) - start_of_data);
		gfx_data.scene = scene;
	}

	return &gfx_data;
}

void gfx_release_data() {
	xSemaphoreGive(gfx_mutex);
	xTaskNotifyGive(gfx_task_handle);
}

int gfx_init() {
	u8g2_esp32_hal_t hal = U8G2_ESP32_HAL_DEFAULT;
	hal.sda = 17;
	hal.scl = 16;
	hal.reset = 21;
	u8g2_esp32_hal_init(hal);

	u8g2_Setup_ssd1305_i2c_128x32_adafruit_f(
		&u8g2,
		U8G2_R0,
		u8g2_esp32_i2c_byte_cb,
		u8g2_esp32_gpio_and_delay_cb
	);
	u8x8_SetI2CAddress(&u8g2.u8x8, 0x78);

	u8g2_InitDisplay(&u8g2);
	u8g2_SetPowerSave(&u8g2, 0);
	u8g2_SetContrast(&u8g2, 10);

	gfx_mutex = xSemaphoreCreateMutex();
	xSemaphoreGive(gfx_mutex);

	memset(&gfx_data, 0, sizeof(gfx_data_t));

	xTaskCreate(gfx_thread, "gfx", 4096, NULL, 1, &gfx_task_handle);

	return 1;
}

void gfx_set_power(int on) {
	u8g2_SetPowerSave(&u8g2, !on);
}

void gfx_set_enabled(bool v) {
	xSemaphoreTake(gfx_mutex, portMAX_DELAY);
	gfx_enable = v;
	xSemaphoreGive(gfx_mutex);
	if (v) {
		// trigger a re-render after re-enabling graphics
		xTaskNotifyGive(gfx_task_handle);
	}
	ESP_LOGI(TAG, "gfx %s", v ? "enabled" : "disabled");
}