#include <math.h>
#include <string.h>

#include "filesystem.h"
#include "memory.h"
#include "util.h"

enum {
    SECTOR_HEADER_SIZE = 24,
    SECTOR_SIZE = 2048,
    SECTOR_SIZE_RAW = 2352
};

static struct {
    FILE* file;

    // This is a cache of the files that have been read from the filesystem.
    // This is useful for lazy loading files and not having to read the same
    // file multiple times.
    struct {
        u8* files[F_FILE_COUNT];
        usize count;
        usize size;
    } cache;
} _state;

usize filesystem_cached_count(void) { return _state.cache.count; }
usize filesystem_cached_size(void) { return _state.cache.size; }

// This is a list of description for all files in the filesystem.
//
// Examples:
//   [F_BATTLE_BIN] =      { .sector = 1000,  .size = 1397096, .name = "BATTLE.BIN"},
//   [F_EVENT__TEST_EVT] = { .sector = 3707,  .size = 4096000, .name = "EVENT/TEST.EVT"},
//   [F_MAP__MAP001_GNS] = { .sector = 11304, .size = 2388,    .name = "MAP/MAP000.GNS"},
//
const file_desc_t file_list[F_FILE_COUNT] = {
#define X(oname, osector, osize, opath) [oname] = { .sector = osector, .size = osize, .name = opath },
    FILESYSTEM_INDEX
#undef X
};

// Forward declarations
static void _read_file(file_entry_e, u8*);

void filesystem_init(void) {
    _state.file = fopen("fft.bin", "rb");
    ASSERT(_state.file != NULL, "Failed to open fft.bin");
}

void filesystem_shutdown(void) {
    for (usize i = 0; i < F_FILE_COUNT; i++) {
        if (_state.cache.files[i] != NULL) {
            memory_free(_state.cache.files[i]);
        }
    }
    fclose(_state.file);
}

span_t filesystem_read_file(file_entry_e file) {
    if (_state.cache.files[file] == NULL) {
        u8* bytes = memory_allocate(file_list[file].size);
        _read_file(file, bytes);
        _state.cache.files[file] = bytes;

        _state.cache.count++;
        _state.cache.size += file_list[file].size;
    }

    span_t span = (span_t) {
        .data = _state.cache.files[file],
        .size = file_list[file].size,
    };

    return span;
}

file_entry_e filesystem_entry_by_sector(u32 sector) {
    for (usize i = 0; i < F_FILE_COUNT; i++) {
        if (file_list[i].sector == sector) {
            return (file_entry_e)i;
        }
    }
    ASSERT(false, "Failed to find file by sector %d", sector);
}

static void _read_file(file_entry_e file, u8* out_bytes) {
    file_desc_t desc = file_list[file];

    usize offset = 0;
    usize occupied_sectors = ceil(desc.size / (f64)SECTOR_SIZE);

    for (usize i = 0; i < occupied_sectors; i++) {
        usize seek_to = ((desc.sector + i) * SECTOR_SIZE_RAW) + SECTOR_HEADER_SIZE;
        usize sn = fseek(_state.file, seek_to, SEEK_SET);
        ASSERT(sn == 0, "Failed to seek to sector");

        u8 sector[SECTOR_SIZE];
        usize rn = fread(sector, sizeof(u8), SECTOR_SIZE, _state.file);
        ASSERT(rn == SECTOR_SIZE, "Failed to read correct number of bytes from sector");

        usize remaining_size = desc.size - offset;
        usize bytes_to_copy = (remaining_size < SECTOR_SIZE) ? remaining_size : SECTOR_SIZE;

        memcpy(out_bytes + offset, sector, bytes_to_copy);
        offset += bytes_to_copy;
    }

    return;
}
