/*
* Filesystem library
* Handles all filesystem related work for BDOS using CH376.
* Uses only bottom USB port (SPI1 CH376), 
*  allowing top USB top to be used for other USB devices (like HID).
*/

// uses stdlib.c

#define FS_INTERRUPT_ADDR 0xC0272D

//CH376 Codes
#define FS_CMD_GET_IC_VER           0x01
#define FS_CMD_SET_BAUDRATE         0x02
#define FS_CMD_ENTER_SLEEP          0x03
#define FS_CMD_SET_USB_SPEED        0x04
#define FS_CMD_RESET_ALL            0x05
#define FS_CMD_CHECK_EXIST          0x06
#define FS_CMD_SET_SD0_INT          0x0b
#define FS_CMD_SET_RETRY            0x0b
#define FS_CMD_GET_FILE_SIZE        0x0c
#define FS_CMD_SET_FILE_SIZE        0x0d
#define FS_CMD_SET_USB_ADDRESS      0x13
#define FS_CMD_SET_USB_MODE         0x15
#define FS_MODE_HOST_0              0x05
#define FS_MODE_HOST_1              0x07
#define FS_MODE_HOST_2              0x06
#define FS_CMD_GET_STATUS           0x22
#define FS_CMD_RD_USB_DATA0         0x27
#define FS_CMD_WR_USB_DATA          0x2c
#define FS_CMD_WR_REQ_DATA          0x2d
#define FS_CMD_WR_OFS_DATA          0x2e
#define FS_CMD_SET_FILE_NAME        0x2f
#define FS_CMD_DISK_CONNECT         0x30
#define FS_CMD_DISK_MOUNT           0x31
#define FS_CMD_FILE_OPEN            0x32
#define FS_CMD_FILE_ENUM_GO         0x33
#define FS_CMD_FILE_CREATE          0x34
#define FS_CMD_FILE_ERASE           0x35
#define FS_CMD_FILE_CLOSE           0x36
#define FS_CMD_DIR_INFO_READ        0x37
#define FS_CMD_DIR_INFO_SAVE        0x38
#define FS_CMD_BYTE_LOCATE          0x39
#define FS_CMD_BYTE_READ            0x3a
#define FS_CMD_BYTE_RD_GO           0x3b
#define FS_CMD_BYTE_WRITE           0x3c
#define FS_CMD_BYTE_WR_GO           0x3d
#define FS_CMD_DISK_CAPACITY        0x3e
#define FS_CMD_DISK_QUERY           0x3f
#define FS_CMD_DIR_CREATE           0x40
#define FS_CMD_SET_ADDRESS          0x45
#define FS_CMD_GET_DESCR            0x46
#define FS_CMD_SET_CONFIG           0x49
#define FS_CMD_AUTO_CONFIG          0x4D
#define FS_CMD_ISSUE_TKN_X          0x4E

#define FS_ANSW_RET_SUCCESS         0x51
#define FS_ANSW_USB_INT_SUCCESS     0x14
#define FS_ANSW_USB_INT_CONNECT     0x15
#define FS_ANSW_USB_INT_DISCONNECT  0x16
#define FS_ANSW_USB_INT_USB_READY   0x18
#define FS_ANSW_USB_INT_DISK_READ   0x1d
#define FS_ANSW_USB_INT_DISK_WRITE  0x1e
#define FS_ANSW_RET_ABORT           0x5F
#define FS_ANSW_USB_INT_DISK_ERR    0x1f
#define FS_ANSW_USB_INT_BUF_OVER    0x17
#define FS_ANSW_ERR_OPEN_DIR        0x41
#define FS_ANSW_ERR_MISS_FILE       0x42
#define FS_ANSW_ERR_FOUND_NAME      0x43
#define FS_ANSW_ERR_DISK_DISCON     0x82
#define FS_ANSW_ERR_LARGE_SECTOR    0x84
#define FS_ANSW_ERR_TYPE_ERROR      0x92
#define FS_ANSW_ERR_BPB_ERROR       0xa1
#define FS_ANSW_ERR_DISK_FULL       0xb1
#define FS_ANSW_ERR_FDT_OVER        0xb2
#define FS_ANSW_ERR_FILE_CLOSE      0xb4
#define FS_ERR_LONGFILENAME         0x01
#define FS_ATTR_READ_ONLY           0x01
#define FS_ATTR_HIDDEN              0x02
#define FS_ATTR_SYSTEM              0x04
#define FS_ATTR_VOLUME_ID           0x08
#define FS_ATTR_DIRECTORY           0x10 
#define FS_ATTR_ARCHIVE             0x20

#define FS_LDIR_FNAME_SIZE          16

// 2d array for filename list and 1d array for filesize list used in listdir
char (*fsLdirFnames)[FS_LDIR_FNAME_SIZE] = (char (*)[FS_LDIR_FNAME_SIZE]) FS_LDIR_FNAME_ADDR;
word *fsLdirFsizes= (word *) FS_LDIR_FSIZE_ADDR;
word fsLdirEntries = 0; // index for lists

// Sets SPI1_CS low
void FS_spiBeginTransfer()
{
    asm(
        "; backup regs\n"
        "push r1\n"
        "push r2\n"

        "load32 0xC0272C r2                 ; r2 = FS_SPI1_CS_ADDR\n"

        "load 0 r1                          ; r1 = 0 (enable)\n"
        "write 0 r2 r1                      ; write to SPI1_CS\n"

        "; restore regs\n"
        "pop r2\n"
        "pop r1\n"
        );
}

// Sets SPI1_CS high
void FS_spiEndTransfer()
{
    asm(
        "; backup regs\n"
        "push r1\n"
        "push r2\n"

        "load32 0xC0272C r2                 ; r2 = FS_SPI1_CS_ADDR\n"

        "load 1 r1                          ; r1 = 1 (disable)\n"
        "write 0 r2 r1                      ; write to SPI1_CS\n"

        "; restore regs\n"
        "pop r2\n"
        "pop r1\n"
        );
}

// Write dataByte over SPI1 and return read value
// write 0x00 for a read
word FS_spiTransfer(word dataByte)
{
    word retval = 0;
    asm(
        "load32 0xC0272B r2                 ; r2 = FS_SPI1_ADDR\n"
        "write 0 r2 r4                      ; write r4 over SPI1\n"
        "read 0 r2 r2                       ; read return value\n"
        "write -4 r14 r2                    ; write to stack to return\n"
        );

    return retval;
}



// Get status after waiting for an interrupt
word FS_WaitGetStatus()
{
    word intValue = 1;
    while(intValue)
    {
        word *i = (word *) FS_INTERRUPT_ADDR;
        intValue = *i;
    }
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_GET_STATUS);
    word retval = FS_spiTransfer(0x00);
    FS_spiEndTransfer(); 

    return retval;
}

// Get status without using interrupts
word FS_noWaitGetStatus()
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_GET_STATUS);
    word retval = FS_spiTransfer(0x00);
    FS_spiEndTransfer(); 

    return retval;
}

// Function to send a string (without terminating 0)
void FS_sendString(char* str)
{
    char chr = *str;            // first character of str
    while (chr != 0)            // continue until null value
    {
        FS_spiTransfer(chr);
        str++;                  // go to next character address
        chr = *str;             // get character from address
    }
}

// Function to send data d of size s
void FS_sendData(char* d, word s)
{
    char chr = *d;              // first byte of data

    // write s bytes
    word i;
    for(i = 0; (unsigned int) i < (unsigned int)s; i++)
    {
        FS_spiTransfer(chr);
        d++;                    // go to next data address
        chr = *d;               // get data from address
    }
}

// Returns IC version of CH376 chip
// good test to know if the communication with chip works
word FS_getICver()
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_GET_IC_VER);
    word icVer = FS_spiTransfer(0x00);
    FS_spiEndTransfer();

    return icVer;
}

// Sets USB mode to mode, returns status code
// which should be FS_ANSW_RET_SUCCESS when successful
// probably does not need all three delays though!
word FS_setUSBmode(word mode)
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_SET_USB_MODE);
    FS_spiEndTransfer();
    delay(1);

    FS_spiBeginTransfer();
    FS_spiTransfer(mode);
    FS_spiEndTransfer();
    delay(1);

    FS_spiBeginTransfer();
    word status = FS_spiTransfer(0x00);
    FS_spiEndTransfer();
    delay(1);

    return status;
}

// Resets and intitializes CH376
// returns FS_ANSW_RET_SUCCESS on success
word FS_init()
{
    FS_asmDefines(); // prevent deletion of function by optimizer
    FS_spiEndTransfer(); // start with cs high
    delay(10);

    // reset
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_RESET_ALL);
    FS_spiEndTransfer();
    delay(100); // wait after reset

    // USB mode 0
    return FS_setUSBmode(FS_MODE_HOST_0);
}

// Connects a drive:
// - waits for drive connection,
// - sets usb host mode
// - waits for drive to be ready
// - mounts drive
// - also initializes current path to /
// returns FS_ANSW_USB_INT_SUCCESS on success
word FS_connectDrive() 
{
    // wait forever until an USB device is connected
    while(FS_WaitGetStatus() != FS_ANSW_USB_INT_CONNECT);

    // USB mode 1
    word retval = FS_setUSBmode(FS_MODE_HOST_1);
    // return on error
    if (retval != FS_ANSW_RET_SUCCESS)
    {
        return retval;
    }

    // USB mode 2
    retval = FS_setUSBmode(FS_MODE_HOST_2);
    // return on error
    if (retval != FS_ANSW_RET_SUCCESS)
    {
        return retval;
    }

    // need to check again for device connection after changing USB mode
    while(FS_WaitGetStatus() != FS_ANSW_USB_INT_CONNECT);

    // connect to drive
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_DISK_CONNECT);
    FS_spiEndTransfer();

    retval = FS_WaitGetStatus();
    // return on error
    if (retval != FS_ANSW_USB_INT_SUCCESS)
    {
        return retval;
    }

    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_DISK_MOUNT);
    FS_spiEndTransfer();

    // reset path variable to /
    SHELL_path[0] = '/';
    SHELL_path[1] = 0; // terminate string

    // also reset path backup
    strcpy(SHELL_pathBackup, SHELL_path);

    return FS_WaitGetStatus();
}

// Returns file size of currently opened file (32 bits)
word FS_getFileSize()
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_GET_FILE_SIZE);
    FS_spiTransfer(0x68);
    word retval = FS_spiTransfer(0);
    retval = retval + (FS_spiTransfer(0) << 8);
    retval = retval + (FS_spiTransfer(0) << 16);
    retval = retval + (FS_spiTransfer(0) << 24);
    FS_spiEndTransfer();

    return retval;
}

// Sets cursor to position s
// returns FS_ANSW_USB_INT_SUCCESS on success
word FS_setCursor(word s) 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_BYTE_LOCATE);
    FS_spiTransfer(s);
    FS_spiTransfer((unsigned) s >> 8);
    FS_spiTransfer((unsigned) s >> 16);
    FS_spiTransfer((unsigned) s >> 24);
    FS_spiEndTransfer();

    return FS_WaitGetStatus();
}

// Reads s bytes into buf
// if bytesToWord is true, four bytes will be stored in one address/word
// can read 65536 bytes per call
// returns FS_ANSW_USB_INT_SUCCESS on success
// TODO: this surely can be optimized for speed in some way! (hint: assembly)
word FS_readFile(char* buf, word s, word bytesToWord) 
{
    if (s == 0)
    {
        return FS_ANSW_USB_INT_SUCCESS;
    }

    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_BYTE_READ);
    FS_spiTransfer(s);
    FS_spiTransfer((unsigned) s >> 8);
    FS_spiEndTransfer();

    word retval = FS_WaitGetStatus();
    // return on error
    if (retval != FS_ANSW_USB_INT_DISK_READ)
    {
        return retval;
    }

    word bytesRead = 0; 
    word wordsRead = 0;
    word doneReading = 0;

    // clear first address
    buf[wordsRead] = 0;

    // used for shifting bytes to word
    word currentByteShift = 24;

    while (doneReading == 0)
    {
        // read set of bytes (max 255)
        FS_spiBeginTransfer();
        FS_spiTransfer(FS_CMD_RD_USB_DATA0);
        word readLen = FS_spiTransfer(0x00);

        word readByte;

        word i;
        for (i = 0; i < readLen; i++) 
        {
            readByte = FS_spiTransfer(0x00);

            // read 4 bytes into one word, from left to right
            if (bytesToWord)
            {
                readByte = readByte << currentByteShift;
                buf[wordsRead] = buf[wordsRead] + readByte;

                if (currentByteShift == 0)
                {
                    currentByteShift = 24;
                    wordsRead++;
                    buf[wordsRead] = 0;
                }
                else
                {
                    currentByteShift -= 8;
                }
            }
            else
            {
                buf[bytesRead] = (char)readByte;
                bytesRead = bytesRead + 1;
            }
        }
        FS_spiEndTransfer();

        // requesting another set of data
        FS_spiBeginTransfer();
        FS_spiTransfer(FS_CMD_BYTE_RD_GO);
        FS_spiEndTransfer();

        retval = FS_WaitGetStatus();
        if (retval == FS_ANSW_USB_INT_SUCCESS)
        {
            doneReading = 1;
        }
        else if (retval == FS_ANSW_USB_INT_DISK_READ)
        {
            // read another block
        }
        else
        {
            GFX_PrintConsole("E: Error while reading data\n");
            return retval;
        }
    }

    return retval;
}


// Writes data d of size s
// can only write 65536 bytes at a time
// returns FS_ANSW_USB_INT_SUCCESS on success
// TODO: optimize for speed
word FS_writeFile(char* d, word s) 
{
    if (s == 0)
    {
        return FS_ANSW_USB_INT_SUCCESS;
    }

    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_BYTE_WRITE);
    FS_spiTransfer(s);
    FS_spiTransfer((unsigned) s >> 8);
    FS_spiEndTransfer();

    word retval = FS_WaitGetStatus();
    // return on error
    if (retval != FS_ANSW_USB_INT_DISK_WRITE)
    {
        return retval;
    }

    word bytesWritten = 0;
    word doneWriting = 0;

    while (doneWriting == 0)
    {
        // write set of bytes (max 255)
        FS_spiBeginTransfer();
        FS_spiTransfer(FS_CMD_WR_REQ_DATA);
        word wrLen = FS_spiTransfer(0x00);

        FS_sendData(d + bytesWritten, wrLen);
        bytesWritten = bytesWritten + wrLen;
        FS_spiEndTransfer();

        // update file size
        FS_spiBeginTransfer();
        FS_spiTransfer(FS_CMD_BYTE_WR_GO);
        FS_spiEndTransfer();

        retval = FS_WaitGetStatus();
        if (retval == FS_ANSW_USB_INT_SUCCESS)
        {
            doneWriting = 1;
        }
        else if (retval == FS_ANSW_USB_INT_DISK_WRITE)
        {
            // write another block
        }
        else
        {
            GFX_PrintConsole("E: Error while writing data\n");
            return retval;
        }

    }
    
    return retval;
}

// Returns status of opening a file/directory
// usually the successful status codes are:
//  FS_ANSW_USB_INT_SUCCESS || FS_ANSW_ERR_OPEN_DIR || FS_ANSW_USB_INT_DISK_READ
word FS_open() 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_FILE_OPEN);
    FS_spiEndTransfer();

    return FS_WaitGetStatus();
}

// Returns FS_ANSW_USB_INT_SUCCESS on success
word FS_delete() 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_FILE_ERASE);
    FS_spiEndTransfer();

    return FS_WaitGetStatus();
}

// Returns FS_ANSW_USB_INT_SUCCESS on success
word FS_close() 
{  
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_FILE_CLOSE);
    FS_spiTransfer(0x01); //0x01 if update filesize, else 0x00
    FS_spiEndTransfer();

    return FS_WaitGetStatus();
}

// Returns FS_ANSW_USB_INT_SUCCESS on success
word FS_createDir() 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_DIR_CREATE);
    FS_spiEndTransfer();

    return FS_WaitGetStatus();
}

// Creates file (recreates if exists)
// new files are 1 byte long
// have not found a way to set it to 0 bytes, 
//  since FS_CMD_SET_FILE_SIZE does not work
// automatically closes file
// returns FS_ANSW_USB_INT_SUCCESS on success
word FS_createFile() 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_FILE_CREATE);
    FS_spiEndTransfer();

    word retval = FS_WaitGetStatus();
    // Return on error
    if (retval != FS_ANSW_USB_INT_SUCCESS)
    {
        return retval;
    }

    // open and close file
    FS_open();
    FS_close();

    return FS_ANSW_USB_INT_SUCCESS;
}

// Sends single path f
// no processing of f is done, it is directly sent
// this means no error checking on file length!
void FS_sendSinglePath(char* f) 
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_SET_FILE_NAME);
    FS_sendString(f);      // send file name
    FS_spiTransfer(0);       // close with null
    FS_spiEndTransfer();
}

// Sends path f
// can be relative or absolute file or directory
// REQUIRES CAPITAL LETTERS and must conform the 8.3 filename standard
// function can handle folders with forward slashes
// returns FS_ANSW_USB_INT_SUCCESS on success
word FS_sendFullPath(char* f) 
{
    // path to uppercase and replace backslash by slash
    word i = 0;
    while (f[i] != 0)
    {
        f[i] = toUpper(f[i]);
        if (f[i] == '\\')
        {
            f[i] = '/';
        }
        i++;
    }

    word removedSlash = 0; // to restore removed slash
    // remove (single) trailing slash if exists
    // we reuse i here since it points to the end of the path
    if (i > 1) // ignore the root path
    {
        if (f[i-1] == '/')
        {
            f[i-1] = 0;
            removedSlash = 1;
        }
    }

    // if first char is a /, then we go back to root
    if (f[0] == '/')
    {
        FS_sendSinglePath("/");
        FS_open();
    }

    i = 0;
    char buf[16]; // buffer for single folder/file name
    word bufi = 0;

    while (f[i] != 0)
    {
        // handle all directories
        if (f[i] == 47) // forward slash
        {
            // return error on opening folders containing a single or double dot
            if ((bufi == 1 && buf[0] == '.') || (bufi == 2 && buf[0] == '.' && buf[1] == '.'))
            {
                return FS_ANSW_ERR_OPEN_DIR;
            }

            // add null to end of buf
            buf[bufi] = 0;
            // send buf
            FS_spiBeginTransfer();
            FS_spiTransfer(FS_CMD_SET_FILE_NAME);
            FS_sendString(buf);      // send folder name
            FS_spiTransfer(0);       // close with null
            FS_spiEndTransfer();
            // reset bufi
            bufi = 0;
            // open folder
            word retval = FS_open();
            // return on if failure / folder not found
            if (retval != FS_ANSW_USB_INT_SUCCESS && retval != FS_ANSW_ERR_OPEN_DIR)
            {
                return retval;
            }
        }
        else
        {
            buf[bufi] = f[i];
            bufi++;
            if (bufi > 13)
            {
                return FS_ERR_LONGFILENAME; // exit if folder/file name is too long
            }
        }
        i++;
    }
    /*
    if (removedSlash) // restore removed slash if there
    {
        f[i] = '/';
        f[i+1] = 0;
    }*/

    // handle filename itself (if any)
    // add null to end of buf
    buf[bufi] = 0;
    if (bufi == 0)
    {
        return FS_ANSW_USB_INT_SUCCESS; // exit if there is no filename after the folder
    }

    // return error on opening folders containing a single or double dot
    if ((bufi == 1 && buf[0] == '.') || (bufi == 2 && buf[0] == '.' && buf[1] == '.'))
    {
        return FS_ANSW_ERR_OPEN_DIR;
    }

    // send buf (last part of string)
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_SET_FILE_NAME);
    FS_sendString(buf);      // send file name
    FS_spiTransfer(0);       // close with null
    FS_spiEndTransfer();

    return FS_ANSW_USB_INT_SUCCESS;
}

// Parses and prints a string (useful for name and extension) after removing trailing spaces
// len should be <= 8 chars
// does not add a new line at the end.
// returns length of written string
word FS_parseFATstring(char* fatBuffer, word len, char* b)
{
    if (len > 8)
    {
        GFX_PrintConsole("FATstring: Len argument > 8\n");
        return 0;
    }

    word retval = 0;

    word bufLen = 0;

    // buffer of parsed string
    char nameBuf[9];
    nameBuf[len] = 0;

    // loop backwards until a non-space character is found
    // then, write string to nameBuf from there, keeping spaces in filename if any
    word foundChar = 0;
    word i;
    for (i = 0; i < len; i++)
    {
        if (!foundChar)
        {
            if (fatBuffer[len-1-i] == ' ')
            {
                nameBuf[len-1-i] = 0; // set null until a non-space char is found
            }
            else
            {
                foundChar = 1;
                retval = len-i;
                nameBuf[len-1-i] = fatBuffer[len-1-i]; // write the non-space char
            }
        }
        else
        {
            nameBuf[len-1-i] = fatBuffer[len-1-i]; // copy char
        }
    }

    // write to buffer
    i = 0;
    while (nameBuf[i] != 0)
    {
        b[bufLen] = nameBuf[i];
        bufLen++;
        i++;
    }
    
    return retval;
}

// Parses and writes name.extension and filesize on one line
// ignores lines with '.' and ".." as filename
void FS_parseFATdata(word datalen, char* fatBuffer)
{
    if (datalen != 32)
    {
        memcpy(fsLdirFnames[fsLdirEntries], "", 1);
        fsLdirFsizes[fsLdirEntries] = 0;
        GFX_PrintConsole("Unexpected FAT table length\n");
        return;
    }

    // ignore '.'
    if (memcmp(fatBuffer, ".          ", 11))
    {
        memcpy(fsLdirFnames[fsLdirEntries], ".", 2);
        fsLdirFsizes[fsLdirEntries] = -1;
        return;
    }

    // ignore ".."
    if (memcmp(fatBuffer, "..         ", 11))
    {
        memcpy(fsLdirFnames[fsLdirEntries], "..", 3);
        fsLdirFsizes[fsLdirEntries] = -1;
        return;
    }

    // parse filename
    word fnameLen = FS_parseFATstring(fatBuffer, 8, fsLdirFnames[fsLdirEntries]);

    // add '.' and parse extension
    if (fatBuffer[8] != ' ' || fatBuffer[9] != ' ' || fatBuffer[10] != ' ')
    {
        fsLdirFnames[fsLdirEntries][fnameLen] = '.';
        fnameLen++;
        fnameLen += FS_parseFATstring(fatBuffer+8, 3, fsLdirFnames[fsLdirEntries] + fnameLen);
    }

    // check if dir
    word attributes = fatBuffer[11];
    word isDir = 0;
    if (attributes & 0x10)
    {
        isDir = 1;
    }

    fsLdirFnames[fsLdirEntries][fnameLen] = 0; // terminate

    // stop if dir
    if (isDir)
    {
        fsLdirFsizes[fsLdirEntries] = -1;
        return;
    }

    // filesize
    word fileSize = 0;
    fileSize += fatBuffer[28];
    fileSize += (fatBuffer[29] << 8);
    fileSize += (fatBuffer[30] << 16);
    fileSize += (fatBuffer[31] << 24);

    fsLdirFsizes[fsLdirEntries] = fileSize;
}

// Reads FAT data for single entry
// FAT data is parsed by FS_parseFatData()
void FS_readFATdata()
{
    FS_spiBeginTransfer();
    FS_spiTransfer(FS_CMD_RD_USB_DATA0);
    word datalen = FS_spiTransfer(0x0);
    char fatbuf[32];
    word i;
    for (i = 0; i < datalen; i++)
    {
        fatbuf[i] = FS_spiTransfer(0x00);
    }
    FS_parseFATdata(datalen, fatbuf);
    FS_spiEndTransfer();
}

// Lists directory of full path f
// f needs to start with / and not end with /
// returns FS_ANSW_USB_INT_SUCCESS if successful
// uses fsLdirX variables to sort all entries
// writes parsed result to address b
// result is terminated with a \0
word FS_listDir(char* f, char* b)
{
    b[0] = 0; // initialize output string

    word retval = FS_sendFullPath(f);
    // return on failure
    if (retval != FS_ANSW_USB_INT_SUCCESS)
    {
        return retval;
    }

    retval = FS_open();
    // return on failure
    if (retval != FS_ANSW_USB_INT_SUCCESS && retval != FS_ANSW_ERR_OPEN_DIR)
    {
        return retval;
    }

    FS_sendSinglePath("*");

    retval = FS_open();
    // return on failure
    if (retval != FS_ANSW_USB_INT_DISK_READ)
    {
        return retval;
    }

    fsLdirEntries = 0; // reset

    while (retval == FS_ANSW_USB_INT_DISK_READ)
    {
        FS_readFATdata();
        fsLdirEntries++;
        FS_spiBeginTransfer();
        FS_spiTransfer(FS_CMD_FILE_ENUM_GO);
        FS_spiEndTransfer();
        retval = FS_WaitGetStatus();
    }

    // print lists
    word i, j, tmpFsize;
    char tmpFname[FS_LDIR_FNAME_SIZE];
    char fsizeBuf[12]; // max 4.29B which fits in 12b + term

    // sort ascending on filesize to get folders on top
    for (i = 0; i < fsLdirEntries; ++i)
    {
        for (j = i + 1; j < fsLdirEntries; ++j)
        {
            if (fsLdirFsizes[i] > fsLdirFsizes[j]) // signed comparison
            {
                tmpFsize = fsLdirFsizes[i];
                strcpy(tmpFname, fsLdirFnames[i]);

                fsLdirFsizes[i] = fsLdirFsizes[j];
                strcpy(fsLdirFnames[i], fsLdirFnames[j]);

                fsLdirFsizes[j] = tmpFsize;
                strcpy(fsLdirFnames[j], tmpFname);
            }
        }
    }

    // sort ascending on filename without mixing files and folders
    for (i = 0; i < fsLdirEntries; ++i)
    {
        for (j = i + 1; j < fsLdirEntries; ++j)
        {
            if ((fsLdirFsizes[i] >= 0 && fsLdirFsizes[j] >= 0) || (fsLdirFsizes[i] < 0 && fsLdirFsizes[j] < 0)) // do not mix
            {
                if (strcmp(fsLdirFnames[i], fsLdirFnames[j]) > 0) // signed comparison
                {
                    tmpFsize = fsLdirFsizes[i];
                    strcpy(tmpFname, fsLdirFnames[i]);

                    fsLdirFsizes[i] = fsLdirFsizes[j];
                    strcpy(fsLdirFnames[i], fsLdirFnames[j]);

                    fsLdirFsizes[j] = tmpFsize;
                    strcpy(fsLdirFnames[j], tmpFname);
                }
            } 
        }
    }

    // write printable string to b
    for (i = 0; i < fsLdirEntries; i++)
    {
        // skip "." and ".."
        if (strcmp(fsLdirFnames[i], ".")  && strcmp(fsLdirFnames[i], ".."))
        {
            // print filename.ext
            strcat(b, fsLdirFnames[i]);

            // skip filesize if directory
            if (fsLdirFsizes[i] >= 0)
            {
                // append with spaces until a width of 16 is reached
                word fnameLen = strlen(fsLdirFnames[i]);
                while (fnameLen < 16)
                {
                    strcat(b, " ");
                    fnameLen++;
                }

                itoa(fsLdirFsizes[i], fsizeBuf);
                strcat(b, fsizeBuf);
            }  
            strcat(b, "\n");
        }
    }

    return FS_ANSW_USB_INT_SUCCESS;
}


// Returns FS_ANSW_USB_INT_SUCCESS on successful change of dir
// will return error FS_ANSW_ERR_FILE_CLOSE if dir is a file
word FS_changeDir(char* f)
{
    // special case for root, since FS_open() returns as if it opened a file
    if (f[0] == '/' && f[1] == 0)
    {
        FS_sendSinglePath("/");
        FS_open();
        return FS_ANSW_USB_INT_SUCCESS;
    }

    word retval = FS_sendFullPath(f);
    // return on failure
    if (retval != FS_ANSW_USB_INT_SUCCESS)
    {
        return retval;
    }

    retval = FS_open();
    
    // return sucess on open dir
    if (retval == FS_ANSW_ERR_OPEN_DIR)
    {
        return FS_ANSW_USB_INT_SUCCESS;
    }

    // close and return on opening file
    if (retval == FS_ANSW_USB_INT_SUCCESS)
    {
        FS_close();
        return FS_ANSW_ERR_FILE_CLOSE;
    }
    else // otherwise return error code
    {
        return retval;
    }
}

// Goes up a directory by removing the last directory from the global path
// does nothing if already at root
// assumes no trailing / in path
void FS_goUpDirectory()
{
    // do nothing if at root
    if (SHELL_path[0] == '/' && SHELL_path[1] == 0)
    {
        return;
    }

    word i = 0;
    word lastSlash = 0;

    // loop through path
    while (SHELL_path[i] != 0)
    {
        // save location of lastest /
        if (SHELL_path[i] == '/')
        {
            lastSlash = i;
        }

        i++;
    }

    // set location of last slash to end of string
    SHELL_path[lastSlash] = 0;

    // rix removal of root directory /
    if (SHELL_path[0] != '/')
    {
        SHELL_path[0] = '/';
        SHELL_path[1] = 0; // terminate string
    }
}

// Appends relative path f to current path
// reverts when invalid resulting path
// returns FS_ANSW_USB_INT_SUCCESS on success
word FS_setRelativePath(char* f)
{
    // get length of currentPath
    word currentPathLength = 0;
    while (SHELL_path[currentPathLength] != 0)
    {
        currentPathLength++;
    }

    // append current path with /
    //  only when not at root
    if (currentPathLength == 1)
    {
        currentPathLength -= 1;
    }
    else
    {
        SHELL_path[currentPathLength] = '/';
    }


    // append relative path
    word currentPathIndex = currentPathLength + 1;
    word relativePathIndex = 0;
    while (f[relativePathIndex] != 0)
    {
        SHELL_path[currentPathIndex] = f[relativePathIndex];
        currentPathIndex++;
        relativePathIndex++;
    }

    // terminate new current path
    SHELL_path[currentPathIndex] = 0;

    // test new path and revert if failure
    word retval = FS_changeDir(SHELL_path);
    if (retval != FS_ANSW_USB_INT_SUCCESS)
    {
        // in case of being at root path
        if (currentPathLength == 0)
        {
            SHELL_path[0] = '/';
            SHELL_path[1] = 0;
        }
        else
        {
            SHELL_path[currentPathLength] = 0; // set terminator back to original place
        }
    }
    
    return retval;
}

// Changes current path based on f
// if f is relative, then the path is added
// if f is absolute, then the path will be replaced by f
// if f is "..", then the last directory will be removed from f
// if f is empty, then nothing will be done and FS_ANSW_USB_INT_SUCCESS is returned
// TODO: remove trailing / if any
// TODO: make sure that path will be < max_pathlength characters
// returns FS_ANSW_USB_INT_SUCCESS for valid paths
word FS_changePath(char* f)
{
    if (f[0] == 0)
    {
        return FS_ANSW_USB_INT_SUCCESS;
    }

    // if f == "..", go up a directory
    if (f[0] == '.' && f[1] == '.' && f[2] == 0)
    {
        FS_goUpDirectory();
        return FS_ANSW_USB_INT_SUCCESS;
    }
    // if absolute path
    else if (f[0] == '/')
    {
        // if absolute path is valid
        word retval = FS_changeDir(f);
        if (retval == FS_ANSW_USB_INT_SUCCESS)
        {
            // copy f to SHELL_path
            strcpy(SHELL_path, f);
        }
        
        return retval;
    }
    // if relative path
    else
    {
        return FS_setRelativePath(f);
    }
}

// Calculates full path given arg f.
// f can be relative or absolute.
// full path is written to SHELL_path
// no checks are done to verify if the path is valid
// so backup of SHELL_path is advised
void FS_getFullPath(char* f)
{
    if (f[0] == 0)
    {
        return;
    }

    // absolutee path
    if (f[0] == '/')
    {
        strcpy(SHELL_path, f); // copy f to SHELL_path
    }
    else
    {
        strcat(SHELL_path, "/"); // append / to SHELL_path
        strcat(SHELL_path, f); // append f to SHELL_path
    }
}