/main/main.c
#include <ctype.h>
#include <dirent.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/unistd.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"
#include "cwalk.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_sleep.h"
#include "mp3dec.h"
#include "u8g2.h"

#include "sin_table.h"
#include "audio.h"
#include "battery.h"
#include "browse.h"
#include "bt.h"
#include "file.h"
#include "fonts.h"
#include "play.h"
#include "sprites.h"
#include "keyboard.h"
#include "gfx.h"
#include "pm.h"
#include "sd.h"

size_t fill_from_streambuffer(StreamBufferHandle_t sb, uint8_t *buf, int size) {
	size_t n = 0;

	while (n < size) {
		n += xStreamBufferReceive(sb, buf + n, size - n, portMAX_DELAY);
	}

	return n;
}

/**
 * roll shifts bytes in an array leftward by count and fills the rest of the
 * array from StreamBuffer sb.
 *
 * Returns the number of new bytes read
 */
size_t roll(uint8_t *buf, int bufsize, int count, StreamBufferHandle_t sb, int eof) {
	assert(count <= bufsize && count >= 0);

	if (count < bufsize) {
		memmove(buf, buf + count, bufsize - count);
	}

	size_t n;
	if (eof) {
		n = xStreamBufferReceive(sb, buf + bufsize - count, count, 0);
	} else {
		n = fill_from_streambuffer(sb, buf + bufsize - count, count);
	}
	ESP_LOGI("roll", "received %d bytes from streambuffer%s", n, eof ? " (EOF)" : "");
	return n;

	//return fread(buf + bufsize - count, 1, count, f);
}

/**
 * roll_to_syncword searches for a syncword in the buffer and rolls it so that
 * the syncword is at the head of the buffer.
 */
size_t roll_to_syncword(uint8_t *buf, int bufsize, StreamBufferHandle_t sb, int eof) {
	size_t total_roll = 0;
	int pos;

	while ((pos = MP3FindSyncWord(buf, bufsize)) == -1) {
		total_roll += roll(buf, bufsize, bufsize - 3, sb, eof);
	}
	if (pos > 0) {
		total_roll += roll(buf, bufsize, pos, sb, eof);
	}

	return total_roll;
}

/*
void play_mp3() {
	const char TAG[] = "play_mp3";
	BaseType_t xerr;
	TaskHandle_t rt;
	StreamBufferHandle_t sb = xStreamBufferCreate(BUFSIZE, 256);
	read_task_parameter_t params = {
		.filename = "/sd/Da Funk.mp3",
		.sb = sb,
		.eof = 0,
	};
	xerr = xTaskCreate(read_task, "read_task", 4096, &params, 2, &rt);
	if (xerr != pdPASS) {
		ESP_LOGE(TAG, "Could not launch read task: %d", xerr);
	}

	size_t n;
	HMP3Decoder dec = MP3InitDecoder();
	int16_t *samples = NULL;

	uint8_t *buf = (uint8_t *) malloc(BUFSIZE);
	if (buf == NULL) {
		ESP_LOGE(TAG, "Could not allocate read buffer");
		return;
	}

	n = fill_from_streambuffer(sb, buf, BUFSIZE);
	printf("received initial %d bytes\n", n);
	while (!params.eof) {
		n += roll_to_syncword(buf, BUFSIZE, sb, params.eof);
		printf("Found syncword at %d\n", n);

		MP3FrameInfo fi;
		int err = MP3GetNextFrameInfo(dec, &fi, buf);
		if (err != 0) {
			ESP_LOGE(TAG, "Error getting next frame info: %d", err);
			n += roll(buf, BUFSIZE, 1, sb, params.eof);
			continue;
		}

		printf("Frame info:\n");
		printf("  bitrate: %d\n", fi.bitrate);
		printf("  channels: %d\n", fi.nChans);
		printf("  sample rate: %d\n", fi.samprate);
		printf("  bits per sample: %d\n", fi.bitsPerSample);
		printf("  output samples: %d\n", fi.outputSamps);
		printf("  layer: %d\n", fi.layer);
		printf("  version: %d\n", fi.version);

		int sample_buffer_size = (fi.bitsPerSample / 8) * fi.outputSamps;
		if (samples == NULL) {
			// Allocate sample buffer
			printf("allocating %d byte sample buffer\n", sample_buffer_size);
			samples = (int16_t *) malloc(sample_buffer_size);
			if (samples == NULL) {
				ESP_LOGE(TAG, "Error allocating sample buffer");
				return;
			}
		}

		uint8_t *p = buf;
		int bytesLeft = BUFSIZE;
		while (1) {
			uint8_t *p_tmp = p;
			int bytesLeft_tmp = bytesLeft;
			err = MP3Decode(dec, &p_tmp, &bytesLeft_tmp, samples, 0);
			if (err == 0) {
				p = p_tmp;
				bytesLeft = bytesLeft_tmp;
				printf("decoded; %d bytes left\n", bytesLeft);

				size_t bytes_written;
				esp_err_t e = i2s_write(I2S_NUM_0, samples, sample_buffer_size, &bytes_written, portMAX_DELAY);
				if (e != ESP_OK) {
					ESP_LOGE("i2s_write", "error %d", e);
				}
				if (bytes_written < sample_buffer_size) {
					ESP_LOGE("i2s_write", "only wrote %d bytes", bytes_written);
				}
			} else if (err == ERR_MP3_INDATA_UNDERFLOW) {
				int c = BUFSIZE - bytesLeft;
				int nn = roll(buf, BUFSIZE, c, sb, params.eof);
				printf("rolled %d (actual %d, bytes left %d) bytes due to underflow\n", c, nn, bytesLeft);
				p = buf;
				bytesLeft = BUFSIZE;
				n += nn;
			} else {
				printf("MP3Decode returned %d\n", err);
				// Some other error. Roll forward one byte and
				// attempt to resync.
				n += roll(buf, BUFSIZE, 1, sb, params.eof);
				break;
			}
		};
	}
}

void play_mod() {
	const char TAG[] = "play_mod";
	xmp_context c;
	FILE *f;

	f = fopen("/sd/Menu.MOD", "r");

	c = xmp_create_context();
	if (c == NULL) {
		ESP_LOGE(TAG, "could not create XMP context");
		return;
	}
	ESP_LOGI(TAG, "created XMP context");

	struct xmp_test_info test_info;
	int ret = xmp_test_module_from_file(f, &test_info);
	if (ret != 0) {
		ESP_LOGE(TAG, "could not test module: %d", ret);
		return;
	}

	printf("Title: %s\n", test_info.name);
	printf("Type: %s\n", test_info.type);

	ret = xmp_load_module_from_file(c, f, 0);
	if (ret != 0) {
		ESP_LOGE(TAG, "could not load module: %d", ret);
		return;
	}
	ESP_LOGI(TAG, "Loaded");

	ret = xmp_start_player(c, 44100, 0);
	if (ret != 0) {
		ESP_LOGE(TAG, "could not start module playback: %d", ret);
		return;
	}

	struct xmp_frame_info info;
	while (1) {
		ret = xmp_play_frame(c);
		if (ret == -XMP_END) {
			break;
		} else if (ret != 0) {
			ESP_LOGE(TAG, "frame decode error: %d", ret);
			return;
		}
		
		xmp_get_frame_info(c, &info);

		
		size_t bytes_written;
		esp_err_t e = i2s_write(I2S_NUM_0, info.buffer, info.buffer_size, &bytes_written, portMAX_DELAY);
		if (e != ESP_OK) {
			ESP_LOGE("i2s_write", "error %d", e);
		}
		if (bytes_written < info.buffer_size) {
			ESP_LOGE("i2s_write", "only wrote %d bytes", bytes_written);
		}
		ESP_LOGI(TAG, "wrote %d samples", bytes_written);
	}
}
*/

void print_task_list(void *pvParameters) {
	while (1) {
		char buf[1024];
		vTaskDelay(5000 / portTICK_PERIOD_MS);
		vTaskGetRunTimeStats(buf);
		puts(buf);
	}
}

void test_io() {
	gfx_set_enabled(false);

	char buf[32];
	u8g2_SetFont(&u8g2, maximum_overdrift);

	key_event_t ke;
	while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
		u8g2_ClearBuffer(&u8g2);

		sprintf(buf, "CD: %c", gpio_get_level(GPIO_NUM_13) ? 'H' : 'L');
		u8g2_DrawStr(&u8g2, 0, 6, buf);

		sprintf(buf, "CHG: %c", gpio_get_level(GPIO_NUM_15) ? 'H' : 'L');
		u8g2_DrawStr(&u8g2, 0, 12, buf);

		battery_info_t bi = battery_get_state();
		sprintf(buf, "BATT: %0.3fV %d%% %s", (float)bi.mV / 1000.0, bi.percentage, bi.charging ? "CHG" : "!CHG");
		u8g2_DrawStr(&u8g2, 0, 18, buf);

		u8g2_SendBuffer(&u8g2);

		vTaskDelay(100 / portTICK_PERIOD_MS);
	}

	gfx_set_enabled(true);
}

void test_clock() {
	gfx_set_enabled(false);

	const char TAG[] = "test_clock";
	char buf[64];
	time_t now;
	struct tm timeinfo;
	key_event_t ke;

	u8g2_SetFont(&u8g2, maximum_overdrift);

	while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
		time(&now);
		localtime_r(&now, &timeinfo);
		size_t n = strftime(buf, sizeof(buf), "%c", &timeinfo);
		for (int i = 0; i < n - 1; i++) {
			buf[i] = toupper(buf[i]);
		}

		u8g2_ClearBuffer(&u8g2);
		u8g2_DrawStr(&u8g2, 0, 6, buf);

		sprintf(buf, "CLOCK REG: %08X", RTC_CNTL_CLK_CONF_REG);
		u8g2_DrawStr(&u8g2, 0, 12, buf);

		u8g2_DrawXBM(&u8g2, 0, 24, btn_1_width, btn_1_height, btn_1_bits);
		u8g2_DrawStr(&u8g2, 7, 31, "LSLEEP");

		u8g2_DrawXBM(&u8g2, 48, 24, btn_2_width, btn_2_height, btn_2_bits);
		u8g2_DrawStr(&u8g2, 55, 31, "DSLEEP");

		u8g2_SendBuffer(&u8g2);

		if (ke.type == KEY_PRESSED) {
			switch (ke.code) {
			case KEY_1:
				ESP_LOGI(TAG, "Entering light sleep.");
				esp_sleep_enable_timer_wakeup(5000000);
				esp_light_sleep_start();
				break;
			case KEY_2:
				ESP_LOGI(TAG, "Entering deep sleep.");
				esp_sleep_enable_timer_wakeup(5000000);
				esp_deep_sleep_start();
				break;
			default:
				break;
			}
		}
	}

	gfx_set_enabled(true);
}

void test_audio() {
	gfx_set_enabled(false);

	key_event_t ke;
	uint16_t samples[400];

	for (int i = 0; i < 200; i++) {
		samples[i*2] = samples[i*2+1] = sinewave[i];
	}

	u8g2_ClearBuffer(&u8g2);
	u8g2_DrawStr(&u8g2, 0, 6, "WAVE: SINE");
	u8g2_SendBuffer(&u8g2);
	
	int ret = audio_open();
	if (!ret) {
		gfx_message("NO AUDIO");
		gfx_set_enabled(true);
		return;
	}
	while (ke = key_get_event(0), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
		audio_send(samples, 400);
	}
	audio_close();

	gfx_set_enabled(true);
}

void do_menu(const char *title, int n_items, menu_def_t *items) {
	assert(n_items > 0 && n_items <= 5);
	gfx_data_t *gd = gfx_acquire_data(SCENE_MENU);
	gd->menu.title = title; // data unsafety! referencing external pointer
	gd->menu.n_items = n_items;
	memcpy(gd->menu.items, items, sizeof(menu_def_t) * n_items);
	gfx_release_data();

	keycode_t k;
	while (1) {
		k = key_get_pressed();
		printf("Key: %d\n", k);
		switch (k) {
			case KEY_1:
				items[0].func();
				return do_menu(title, n_items, items);
			case KEY_2:
				if (n_items >= 2) items[1].func();
				return do_menu(title, n_items, items);
			case KEY_3:
				if (n_items >= 3) items[2].func();
				return do_menu(title, n_items, items);
			case KEY_4:
				if (n_items >= 4) items[3].func();
				return do_menu(title, n_items, items);
			case KEY_5:
				if (n_items >= 5) items[4].func();
				return do_menu(title, n_items, items);
			case KEY_BAND:
				return;
			case KEY_OFF:
				deep_sleep();
				return;
			default:
				continue;
		}
	}
}

void test_mode() {
	menu_def_t menu[] = {
		{"KEYS", key_test},
		{"I/O", test_io},
		{"CLOCK", test_clock},
		{"AUDIO", test_audio},
		{"SD", test_sd},
	};
	do_menu("TEST", 5, menu);
}

void bt_config_mode() {
	key_event_t ke;

	while (ke = key_get_event(10), !(ke.type == KEY_PRESSED && ke.code == KEY_BAND)) {
		int confirm_mode = 0;
		gfx_data_t* gd = gfx_acquire_data(SCENE_BT);
		confirm_mode = gd->bt.confirm != 0;
		gfx_release_data();

		if (confirm_mode) {
			if (ke.type == KEY_PRESSED) {
				if (ke.code == KEY_1) {
					bt_confirm_pair(1);
				} else if (ke.code == KEY_2) {
					bt_confirm_pair(0);
				}
			}
		} else {
			if (ke.type == KEY_PRESSED) {
				switch (ke.code) {
				case KEY_1:
					switch (bt_get_mode()) {
					case BT_INACTIVE:
						bt_set_mode(BT_ACTIVE);
						break;
					case BT_ACTIVE:
						bt_set_mode(BT_DISCOVERABLE);
						break;
					case BT_DISCOVERABLE:
						bt_set_mode(BT_INACTIVE);
						break;
					}
					break;
				default:
					break;
				}
			}
		}
	}
}

void erase_nvs() {
	nvs_flash_erase();
	esp_restart();
}

void do_nothing() { }

void main_menu() {
	menu_def_t menu[] = {
		{"PLAY", play_controller},
		{"FILE", browse},
		{"TEST", test_mode},
		{"BT", bt_config_mode},
	};
	do_menu("MAIN", 4, menu);
}

void clock_mode() {
	time_t now;
	struct tm timeinfo;
	struct timeval tv = { 0, 0 };
	key_event_t k;

	while (true) {
		gfx_data_t *gd = gfx_acquire_data(SCENE_CLOCK);
		
		time(&gd->clock.time);
		gfx_release_data();

		k = key_get_event(100 / portTICK_PERIOD_MS);
		if (k.type != KEY_PRESSED) {
			continue;
		}
		switch (k.code) {
		case KEY_ON:
			main_menu();
			break;
		case KEY_OFF:
			deep_sleep();
			break;
		case KEY_1:
			time(&now);
			localtime_r(&now, &timeinfo);
			timeinfo.tm_hour = (timeinfo.tm_hour + 1) % 24;
			now = mktime(&timeinfo);
			tv.tv_sec = now;
			settimeofday(&tv, NULL);
			break;
		case KEY_2:
			time(&now);
			localtime_r(&now, &timeinfo);
			timeinfo.tm_hour = (timeinfo.tm_hour - 1) % 24;
			now = mktime(&timeinfo);
			tv.tv_sec = now;
			settimeofday(&tv, NULL);
			break;
		case KEY_3:
			time(&now);
			localtime_r(&now, &timeinfo);
			timeinfo.tm_min = (timeinfo.tm_min + 1) % 60;
			now = mktime(&timeinfo);
			tv.tv_sec = now;
			settimeofday(&tv, NULL);
			break;
		case KEY_4:
			time(&now);
			localtime_r(&now, &timeinfo);
			timeinfo.tm_min = (timeinfo.tm_min - 1) % 60;
			now = mktime(&timeinfo);
			tv.tv_sec = now;
			settimeofday(&tv, NULL);
			break;
		default:
			break;
		}
	}
}

int io_init() {
	const char TAG[] = "io_init";
	esp_err_t ret;

	gpio_config_t conf = {
		pin_bit_mask: 1ull << 13 | 1ull << 15,
		mode: GPIO_MODE_INPUT,
		pull_up_en: GPIO_PULLUP_ENABLE,
		pull_down_en: GPIO_PULLDOWN_DISABLE,
		intr_type: GPIO_INTR_DISABLE,
	};
	ret = gpio_config(&conf);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not configure card detect GPIO: %s", esp_err_to_name(ret));
		return 0;
	}

	conf.pin_bit_mask = 1ull << 14;
	conf.mode = GPIO_MODE_OUTPUT;
	conf.pull_up_en = GPIO_PULLUP_DISABLE;
	conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
	conf.intr_type = GPIO_INTR_DISABLE;
	ret = gpio_config(&conf);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not configure card detect GPIO: %s", esp_err_to_name(ret));
		return 0;
	}

	ret = adc2_config_channel_atten(ADC2_CHANNEL_5, ADC_ATTEN_DB_11);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Could not configure ADC2 channel attenuation: %s", esp_err_to_name(ret));
		return 0;
	}

	return 1;
}

int nvs_init() {
	int ret = nvs_flash_init();
	if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
		ESP_ERROR_CHECK(nvs_flash_erase());
		ret = nvs_flash_init();
	}
	ESP_ERROR_CHECK(ret);
	return 1;
}

void print_hexdump_line(uint32_t addr, uint8_t* buf) {
	printf("%08x: ", addr);
	for (int j = 0; j < 16; j++) {
		printf("%02x", buf[j]);
		if (j % 4 == 3) {
			printf(" ");
		}
	}
	printf("  ");
	for (int j = 0; j < 16; j++) {
		if (buf[j] >= 0x20 && buf[j] < 0x7F) {
			putc(buf[j], stdout);
		} else {
			putc('.', stdout);
		}
	}
	printf("\n");
}

/*
void stream_test() {
	const char TAG[] = "stream_test";
	uint8_t* buf = malloc(256);
	StreamBufferHandle_t h = xStreamBufferCreate(256, 1);
	uint32_t addr = 0;

	sd_get();

	ESP_LOGI(TAG, "-------- start --------");

	FILE* f = fopen("/sd/cooler.flac", "r");
	int64_t t0 = esp_timer_get_time();
	for (int i = 0; i < 8 * 1048576; i += 256) {
		size_t len = fread(buf, 1, 256, f);
		addr += len;
	}
	int64_t t1 = esp_timer_get_time();
	ESP_LOGI(TAG, "Read %d bytes in %lld us: %0.3f KB/sec", addr, t1 - t0, 1000.0 * (double)addr / (double)(t1 - t0));
	fclose(f);

	file_reader_open("/sd/cooler.flac");
	file_reader_subscribe(h);
	file_reader_start();

	int64_t t0 = esp_timer_get_time();
	for (int i = 0; i < 8 * 1048576; i += 256) {
		size_t len = xStreamBufferReceive(h, buf, 256, portMAX_DELAY);
		//print_hexdump_line(addr, buf);
		addr += len;
	}
	int64_t t1 = esp_timer_get_time();
	ESP_LOGI(TAG, "Read %d bytes in %lld us: %0.3f KB/sec", addr, t1 - t0, 1000.0 * (double)addr / (double)(t1 - t0));
	file_reader_seek(0, SEEK_SET);
	ESP_LOGI(TAG, "--------- seek --------");
	for (int i = 0; i < 256 / 16; i++) {
		addr += xStreamBufferReceive(h, buf, 16, portMAX_DELAY);
		print_hexdump_line(addr, buf);
	}
	file_reader_close();

	ESP_LOGI(TAG, "--------- end ---------");
}
*/

void app_main(void) {
	nvs_init();
	key_init();
	io_init();
	audio_init();
	gfx_init();
	sd_init();
	file_init();
	battery_init();
	bt_init();

	//xTaskCreate(print_task_list, "task list", 4096, NULL, 1, NULL);

	//stream_test();

	clock_mode();
}