#include "boot_storage.h"
#include <libs/fatfs/ff.h>
#include <fatfs_cfg.h>
#include <storage/sd.h>
#include <storage/emmc.h>
#include <utils/types.h>
#include <gfx_utils.h>
#include <stdlib.h>

#define DEV_INVALID 0xff

static FATFS boot_storage_fs;
static BYTE drive_cur = -1;
static BYTE drive = -1;

static const char* drive_base_paths[] = {
	[DRIVE_SD]         = "sd:",
	[DRIVE_BOOT1]      = "boot1_:",
	[DRIVE_BOOT1_1MB]  = "boot1_1mb:",
	[DRIVE_EMMC]       = "emmc:",
};

static bool _is_eligible(){
	if(f_stat(".no_boot_storage", NULL) == FR_OK){
		return false;
	}
	return true;
} 

bool boot_storage_get_mounted(){
	switch(drive_cur){
	case DRIVE_SD:
		return sd_get_card_mounted();
	case DRIVE_EMMC:
		return emmc_get_mounted();
	case DRIVE_BOOT1:
	case DRIVE_BOOT1_1MB:
		return drive_cur != DEV_INVALID;
	}
	return false;
}

bool boot_storage_get_initialized(){
	switch(drive_cur){
	case DRIVE_BOOT1:
	case DRIVE_EMMC:
	case DRIVE_BOOT1_1MB:
		return emmc_get_initialized();
	case DRIVE_SD:
		return sd_get_card_initialized();
	}
	return false;
}

static bool _boot_storage_initialize(){
	switch(drive_cur){
	case DRIVE_BOOT1:
	case DRIVE_EMMC:
	case DRIVE_BOOT1_1MB:
		return emmc_initialize(false);
	case DRIVE_SD:
		return sd_initialize(false);
	}

	return false;
}

static void _boot_storage_end(bool deinit){
	if(boot_storage_get_mounted()){
		switch(drive_cur){
		case DRIVE_SD:
			sd_unmount();
			break;
		case DRIVE_EMMC:
			emmc_unmount();
			break;
		case DRIVE_BOOT1:
		case DRIVE_BOOT1_1MB:
			f_mount(NULL, drive_base_paths[drive_cur], 0);
		}
		drive_cur = DEV_INVALID;
	}

	if(deinit){
		switch(drive_cur){
		case DRIVE_SD:
			sd_end();
			break;
		case DRIVE_EMMC:
		case DRIVE_BOOT1:
		case DRIVE_BOOT1_1MB:
			emmc_end();
			break;
		}
	}
}

void boot_storage_unmount(){
	_boot_storage_end(false);
}

void boot_storage_end(){
	_boot_storage_end(true);
}

u8 boot_storage_get_drive(){
	return drive;
}

static bool _boot_storage_mount(){
	// may want to check sd card first and prioritize it

	FRESULT res;

	if(!emmc_get_initialized() && !emmc_initialize(false)){
		goto emmc_init_fail;
	}

	static const BYTE emmc_drives[] = {DRIVE_BOOT1_1MB, DRIVE_BOOT1}; 

	for(BYTE i = 0; i < ARRAY_SIZE(emmc_drives); i++){
		res = f_mount(&boot_storage_fs, drive_base_paths[emmc_drives[i]], true);
		if(res == FR_OK){
			res = f_chdrive(drive_base_paths[emmc_drives[i]]);
			if(res == FR_OK && _is_eligible()){
				drive_cur = emmc_drives[i];
				drive = drive_cur;
				break;
			}else{
				f_mount(NULL, drive_base_paths[emmc_drives[i]],false);
				res = FR_INVALID_DRIVE;
			}
		}
	}

	if(res != FR_OK){
		emmc_end();
	}

	if(res == FR_OK){
		return true;
	}

emmc_init_fail:
	if(!emmc_initialize(false)){
		goto emmc_init_fail2;
	}

	if(!emmc_mount()){
		emmc_end();
		goto emmc_init_fail2;
	}

	res = f_chdrive(drive_base_paths[DRIVE_EMMC]);

	if(res == FR_OK && _is_eligible()){
		drive_cur = DRIVE_EMMC;
		drive = drive_cur;
		return true;
	}

emmc_init_fail2:
	if(!sd_initialize(false)){
		goto out;
	}

	if(!sd_mount()){
		sd_end();
		goto out;
	}

	res = f_chdrive(drive_base_paths[DRIVE_SD]);

	if(res == FR_OK && _is_eligible()){
		drive_cur = DRIVE_SD;
		drive = drive_cur;
		return true;
	}

	sd_end();

out:
	return false;
}

bool boot_storage_mount(){
	bool mounted = boot_storage_get_mounted();
	bool initialized = boot_storage_get_initialized();
	bool res = mounted && initialized;
	if(!mounted){
		// not mounted. mounting will also initialize.
		res = _boot_storage_mount();
	}else if(!initialized){
		res = _boot_storage_initialize();
	}

	if(res){
		res = f_chdrive(drive_base_paths[drive_cur]) == FR_OK;
	}

	return res;
}

void *boot_storage_file_read(const char *path, u32 *fsize)
{
	FIL fp;
	if (!boot_storage_get_mounted())
		return NULL;

	if (f_open(&fp, path, FA_READ) != FR_OK)
		return NULL;

	u32 size = f_size(&fp);
	if (fsize)
		*fsize = size;

	void *buf = malloc(size);

	if (f_read(&fp, buf, size, NULL) != FR_OK)
	{
		free(buf);
		f_close(&fp);

		return NULL;
	}

	f_close(&fp);

	return buf;
}

int boot_storage_save_to_file(const void *buf, u32 size, const char *filename)
{
	FIL fp;
	u32 res = 0;
	if (!boot_storage_get_mounted())
		return FR_DISK_ERR;

	res = f_open(&fp, filename, FA_CREATE_ALWAYS | FA_WRITE);
	if (res)
	{
		EPRINTFARGS("Error (%d) creating file\n%s.\n", res, filename);
		return res;
	}

	f_write(&fp, buf, size, NULL);
	f_close(&fp);

	return 0;
}

FATFS *boot_storage_get_fs() {
	switch(drive_cur){
	case DRIVE_BOOT1:
		return &boot_storage_fs;
	case DRIVE_EMMC:
		return &emmc_fs;
	case DRIVE_BOOT1_1MB:
		return &boot_storage_fs;
	case DRIVE_SD:
		return &sd_fs;
	}
	return NULL;
}