#include "io.h"
#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

page_cache_entry_t *_file_load_page(file_t* file, page_t page);
void _file_flush_page(file_t *file, page_cache_entry_t *entry);

file_t* file_open(const char* filename, const char* mode)
{
    FILE* handle = fopen(filename, mode);

    if (!handle) {
        printfv(VERBOSITY_NORMAL, "[io] Can't open file %s.\n", filename);
        return NULL;
    }

    printfv(VERBOSITY_DEBUG, "[io] File %s opened in %s with page size %zu.\n", filename, mode, PAGE_SIZE);
    file_t* result = malloc(sizeof(file_t));

    result->file     = handle;
    result->filename = malloc(strlen(filename) + 1); 

    strcpy(result->filename, filename);

    return result;
}

void file_close(file_t* file)
{
    file_flush(file);

    printfv(VERBOSITY_DEBUG, "[io] Closing file %s.\n", file->filename);
    fclose(file->file);

    free(file->filename);
    free(file);
}

size_t file_read(file_t* file, page_t page, void* buffer, size_t length)
{
    page_cache_entry_t *entry = _file_load_page(file, page);
    memcpy(buffer, entry->data, length);

    return entry->size;
}

size_t file_write(file_t* file, page_t page, const void* buffer, size_t length)
{
    page_cache_entry_t *entry = _file_load_page(file, page);

    memset(entry->data, 0, PAGE_SIZE);
    memcpy(entry->data, buffer, length);

    entry->flags |= PAGE_DIRTY;
    entry->size   = length;

    return entry->size;
}

void file_flush(file_t* file)
{
    for (unsigned i = 0; i < CACHE_ENTRIES; i++) {
        _file_flush_page(file, file->cache + i);
    }
}

page_cache_entry_t *_file_load_page(file_t* file, page_t page)
{
    page_cache_entry_t *entry = file->cache + (page % CACHE_ENTRIES);

    if (entry->page == page && entry->flags & PAGE_PRESENT) {
        // already in cache
        return entry;
    }

    _file_flush_page(file, entry);
    
    int result = fseek(file->file, page * PAGE_SIZE, SEEK_SET);

    memset(entry->data, 0, PAGE_SIZE); 
    entry->size = fread(entry->data, 1, PAGE_SIZE, file->file);
    if (entry->size) {
        printfv(VERBOSITY_DEBUG, "[io] Loading page %u of %s (%zu bytes).\n", page, file->filename, entry->size);
        reads++;
    }

    entry->page = page;
    entry->flags |= PAGE_PRESENT;

    return entry;
}

void _file_flush_page(file_t *file, page_cache_entry_t *entry) 
{
    if (entry->flags & PAGE_PRESENT && entry->flags & PAGE_DIRTY) {
        printfv(VERBOSITY_DEBUG, "[io] Flushing page %u to %s (%zu bytes).\n", entry->page, file->filename, entry->size);

        fseek(file->file, entry->page * PAGE_SIZE, SEEK_SET);
        fwrite(entry->data, 1, entry->size, file->file); 

        // disable dirty flag
        entry->flags &= ~PAGE_DIRTY;

        writes++;
    }
}