/main/browse.c
#include <dirent.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "cwalk.h"
#include "esp_vfs_fat.h"

#include "gfx.h"
#include "keyboard.h"
#include "sd.h"
#include "browse.h"

typedef struct browse_file_entry {
	char* name;
	unsigned char type;
} browse_file_entry_t;

typedef struct browse_file_list {
	browse_file_entry_t** files;
	size_t count;
	size_t capacity;
} browse_file_list_t;

void bfl_init(browse_file_list_t* bfl) {
	bfl->count = 0;
	bfl->capacity = 10;
	bfl->files = (browse_file_entry_t**) malloc(bfl->capacity * sizeof(browse_file_entry_t*));
}

void bfl_free_entry(browse_file_list_t* bfl, size_t index) {
	browse_file_entry_t* be = bfl->files[index];
	free(be->name);
	free(be);
}

void bfl_free(browse_file_list_t* bfl) {
	for (int i = 0; i < bfl->count; i++) {
		bfl_free_entry(bfl, i);
	}
	free(bfl->files);
}

void bfl_resize(browse_file_list_t* bfl, size_t new_capacity) {
	if (new_capacity < bfl->count) {
		for (int i = new_capacity; i < bfl->count; i++) {
			bfl_free_entry(bfl, i);
		}
		bfl->count = new_capacity;
	}

	bfl->capacity = new_capacity;
	bfl->files = (browse_file_entry_t**) realloc(bfl->files, bfl->capacity * sizeof(browse_file_entry_t*));
}

void bfl_clear(browse_file_list_t* bfl) {
	bfl_free(bfl);
	bfl_init(bfl);
}

void bfl_add_entry(browse_file_list_t* bfl, const char* name, unsigned char type) {
	browse_file_entry_t* be = (browse_file_entry_t*) malloc(sizeof(browse_file_entry_t));
	size_t l = strlen(name);
	be->name = (char*) malloc(l + 1);
	strncpy(be->name, name, l);
	be->name[l] = 0;
	be->type = type;
	if (bfl->count == bfl->capacity) {
		bfl_resize(bfl, bfl->capacity * 2);
	}
	bfl->files[bfl->count] = be;
	bfl->count++;
}

void bfl_fetch(browse_file_list_t* bfl, const char* path) {
	struct dirent *e;
	DIR* d = opendir(path);

	bfl_clear(bfl);
	while ((e = readdir(d)) != NULL) {
		bfl_add_entry(bfl, e->d_name, e->d_type);
	}
	closedir(d);
}

void browse() {
	const char TAG[] = "browse_files";
	browse_file_list_t bfl;
	char* path = (char*) malloc(FILENAME_MAX);
	strcpy(path, "/sd");

	FATFS* fs = sd_get();
	if (fs == NULL) {
		printf("No SD card\n");
		return;
	}

	bfl_init(&bfl);
	bfl_fetch(&bfl, path);

	int cursor = 0;
	key_event_t k;

	while (1) {
		gfx_data_t *gd = gfx_acquire_data(SCENE_BROWSE);
		gd->browse.state = BROWSE_NONE;
		if (cursor == 0) {
			gd->browse.state = BROWSE_AT_TOP;
		}
		if (cursor >= bfl.count - 4) {
			gd->browse.state = BROWSE_AT_BOTTOM;
		}
		for (int i = 0; i < 4; i++) {
			if (cursor + i < bfl.count) {
				gd->browse.names[i] = bfl.files[cursor + i]->name;
				gd->browse.types[i] = bfl.files[cursor + i]->type;
			} else {
				gd->browse.names[i] = NULL;
			}
		}

		gfx_release_data();

		int selected = -1;
		k = key_get_event(portMAX_DELAY);
		if (k.type == KEY_PRESSED) {
			switch (k.code) {
			case KEY_BAND:
				if (strncmp(path, "/sd", FILENAME_MAX) == 0) {
					goto browse_end;
				}
				size_t l;
				cwk_path_get_dirname(path, &l);
				path[l - 1] = 0;
				ESP_LOGI(TAG, "new path = %s", path);
				bfl_clear(&bfl);
				bfl_fetch(&bfl, path);
				cursor = 0;
				break;
			case KEY_TUNE_PLUS:
				if (cursor >= 4) {
					cursor -= 4;
				}
				break;
			case KEY_TUNE_MINUS:
				if (cursor < bfl.count - 4) {
					cursor += 4;
				}
				break;
			case KEY_1:
				selected = 0;
				break;
			case KEY_2:
				selected = 1;
				break;
			case KEY_3:
				selected = 2;
				break;
			case KEY_4:
				selected = 3;
				break;
			default:
				break;
			}
		}
		if (selected != -1 && cursor + selected < bfl.count) {
			char* name = bfl.files[cursor + selected]->name;
			unsigned char t = bfl.files[cursor + selected]->type;

			if (strlen(path) + strlen(name) + 2 > FILENAME_MAX) {
				ESP_LOGE(TAG, "File path would be too long; cannot navigate to %s", name);
				continue;
			}
			char* fullpath = (char*) malloc(FILENAME_MAX);
			cwk_path_join(path, name, fullpath, FILENAME_MAX);

			if (t == DT_REG) {
				play_file(fullpath);
				free(fullpath);
				break;
			} else if (t == DT_DIR) {
				free(path);
				path = fullpath;
				ESP_LOGI(TAG, "new path = %s", path);
				bfl_clear(&bfl);
				bfl_fetch(&bfl, path);
				cursor = 0;
			}
		}
	}

browse_end:
	bfl_free(&bfl);
	free(path);
}