#include <string.h>
#include <pico/stdlib.h>
#include <pico/multicore.h>
#include "common/config.h"
#include "common/buffers.h"
#include "fs/businterface.h"
#include "fs/fs.h"
#include "pico_hal.h"

typedef struct {
    int handle;
    bool valid;
} fs_file_t;

#define FS_MAXFILE 8
fs_file_t fs_file[FS_MAXFILE];

void fs_open(volatile uint8_t *fsregs) {
    int file;
    for(int i = 0; i < FS_MAXFILE; i++) {
        if(!fs_file[i].valid) {
            int flags = 0;
            if(fsregs[FS_FLAGS] & FS_O_RD)
                flags |= LFS_O_RDONLY;
            if(fsregs[FS_FLAGS] & FS_O_WR)
                flags |= LFS_O_WRONLY;
            if(fsregs[FS_FLAGS] & FS_O_CREATE)
                flags |= LFS_O_CREAT;
            if(fsregs[FS_FLAGS] & FS_O_APPEND)
                flags |= LFS_O_APPEND;
            if(fsregs[FS_FLAGS] & FS_O_EXISTING)
                flags |= LFS_O_EXCL;
            if(fsregs[FS_FLAGS] & FS_O_TRUNC)
                flags |= LFS_O_TRUNC;

            if((flags & LFS_O_RDWR) != 0) {
                // Ensure pathname is 128 bytes or less.
                apple_memory[0xC880] = 0x00;
                file = pico_open((char*)(apple_memory + 0xC800), flags);
            } else {
                fsregs[FS_STATUS] = FS_ERR_INVAL;
                fsregs[FS_FILE] = FS_ERR_INVAL;
                return;
            }

            if(file < 0) {
                fsregs[FS_STATUS] = file;
                fsregs[FS_FILE] = file;
                return;
            } else {
                fs_file[i].handle = file;
                fs_file[i].valid = true;
                fsregs[FS_STATUS] = FS_ERR_OK;
                fsregs[FS_FILE] = i;
                return;
            }
        }
    }
    
    fsregs[FS_STATUS] = FS_ERR_NOMEM;
}

void fs_close(volatile uint8_t *fsregs) {
    int i = fsregs[FS_FILE];
    if((i >= FS_MAXFILE) || (!fs_file[i].valid) || (fs_file[i].handle < 0)) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }
    
    pico_close(fs_file[i].handle);
    fs_file[i].handle = -1;
    fs_file[i].valid = false;
}

void fs_read(volatile uint8_t *fsregs) {
    int i = fsregs[FS_FILE];
    uint16_t size = (((uint16_t)fsregs[FS_OFFMSB]) << 8) | ((uint16_t)fsregs[FS_SIZELSB]);

    if((i >= FS_MAXFILE) || (!fs_file[i].valid) || (fs_file[i].handle < 0) || (size > 512)) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }
    if(size == 0) {
        fsregs[FS_STATUS] = FS_ERR_OK;
    }

    int rv = pico_read(fs_file[i].handle, (uint8_t*)(apple_memory+0xC800), size);
    if(rv < 0) {
        fsregs[FS_STATUS] = rv;
    } else {
        fsregs[FS_SIZELSB] = rv & 0xFF;
        fsregs[FS_SIZEMSB] = (rv >> 8);
        fsregs[FS_STATUS] = FS_ERR_OK;
    }
}

void fs_write(volatile uint8_t *fsregs) {
    int i = fsregs[FS_FILE];
    uint16_t size = (((uint16_t)fsregs[FS_OFFMSB]) << 8) | ((uint16_t)fsregs[FS_SIZELSB]);

    if((i >= FS_MAXFILE) || (!fs_file[i].valid) || (fs_file[i].handle < 0) || (size > 512)) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }
    if(size == 0) {
        fsregs[FS_STATUS] = FS_ERR_OK;
    }

    int rv = pico_write(fs_file[i].handle, (uint8_t*)(apple_memory+0xC800), size);
    if(rv < 0) {
        fsregs[FS_STATUS] = rv;
    } else {
        fsregs[FS_SIZELSB] = rv & 0xFF;
        fsregs[FS_SIZEMSB] = (rv >> 8);
        fsregs[FS_STATUS] = FS_ERR_OK;
    }
}

void fs_seek(volatile uint8_t *fsregs) {
    int i = fsregs[FS_FILE];
    int16_t off = (int16_t)((((uint16_t)fsregs[FS_OFFMSB]) << 8) | ((uint16_t)fsregs[FS_OFFLSB]));
    int whence = fsregs[FS_WHENCE];

    if((i >= FS_MAXFILE) || (!fs_file[i].valid) || (fs_file[i].handle < 0)) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }

    int rv = pico_lseek(fs_file[i].handle, off, whence);
    if(rv < 0) {
        fsregs[FS_STATUS] = rv;
    } else {
        fsregs[FS_OFFLSB] = rv & 0xFF;
        fsregs[FS_OFFMSB] = (rv >> 8);
        fsregs[FS_STATUS] = FS_ERR_OK;
    }
}

void fs_tell(volatile uint8_t *fsregs) {
    int i = fsregs[FS_FILE];

    if((i >= FS_MAXFILE) || (!fs_file[i].valid) || (fs_file[i].handle < 0)) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }

    int rv = pico_tell(fs_file[i].handle);
    if(rv < 0) {
        fsregs[FS_STATUS] = rv;
    } else {
        fsregs[FS_OFFLSB] = rv & 0xFF;
        fsregs[FS_OFFMSB] = (rv >> 8);
        fsregs[FS_STATUS] = FS_ERR_OK;
    }
}

void prodos_command(volatile uint8_t *fsregs) {
}

void fs_handler(uint8_t slot) {
    volatile uint8_t *fsregs = apple_memory + 0xC080 + (slot << 4);
    if(fsregs[FS_EXECUTE] != 0x00) {
        fsregs[FS_STATUS] = FS_ERR_INVAL;
        return;
    }
    
    fsregs[FS_BUSY] = 0xff;

    switch(fsregs[FS_COMMAND]) {
    case FS_OPEN:
        fs_open(fsregs);
        break;
    case FS_CLOSE:
        fs_close(fsregs);
        break;
    case FS_READ:
        fs_read(fsregs);
        break;
    case FS_WRITE:
        fs_write(fsregs);
        break;
    case FS_SEEK:
        fs_seek(fsregs);
        break;
    case FS_TELL:
        fs_tell(fsregs);
        break;
    case PRODOS_COMMAND:
        prodos_command(fsregs);
        break;
    }

    fsregs[FS_BUSY] = 0x00;
}

void fsmain() {
    int i;

    memset((uint8_t*)(apple_memory+0xC000), 0xFF, 0x1000);

    for(i = 0; i < FS_MAXFILE; i++) {
        fs_file[i].handle = -1;
        fs_file[i].valid = false;
    }

    // Attempt to open boot disk image
    fs_file[0].handle = pico_open("boot.dsk", LFS_O_RDONLY);
    fs_file[0].valid = (fs_file[0].handle >= 0);

    strcpy((char*)(apple_memory+0xC0E0+(cardslot << 8)), "FSREADY.");

    while(current_mode == MODE_FS) {
        config_handler();
        if(cardslot != 0)
            fs_handler(cardslot);
        sleep_ms(50);
    }

    // Close any open handles
    for(i = 0; i < FS_MAXFILE; i++) {
        if(fs_file[i].valid) {
            pico_close(fs_file[i].handle);
            fs_file[i].handle = -1;
            fs_file[i].valid = false;
        }
    }
}

