
#include "microsd.h"
#include "ymodem.h"
#include "uartstdio.h"	// User local version with larger RX buffer
#include "apf.h"
#include "serial.h"

#include "stdint.h"

extern struct apfBootInitGlobals apf;
extern struct apfCfgGlobals apf_eeprom;
extern struct apfVolatileGlobals apf_fram;

uint32_t fileDbgFlg = 0;            // set to 1 to enable uprintf of errors and debug

#define DIRNAME_MAXSIZE     128
char dirName[DIRNAME_MAXSIZE];

#define EXIT -5

//*****************************************************************************
//
// Defines the size of the buffers that hold the path, or temporary data from
// the SD card. The buffer size must be large enough to hold the longest
// expected full path name, including the file name, and a trailing null character.
//
//*****************************************************************************
#define PATH_BUF_SIZE           320

//*****************************************************************************
//
// The following are data structures used by FatFs.
//
//*****************************************************************************
static FATFS g_sFatFs;		//work area (file system object) for logical drivers

//*****************************************************************************
//
// A structure that holds a mapping between an FRESULT numerical code, and a
// string representation.  FRESULT codes are returned from the FatFs FAT file
// system driver.
//
//*****************************************************************************
typedef struct
{
	FRESULT fresult;
	char const * const pcResultStr;
}
tFresultString;

//*****************************************************************************
//
// A macro to make it easy to add result codes to the table.
//
//*****************************************************************************
#define FRESULT_ENTRY(f)        { (f), (#f) }

//*****************************************************************************
//
// A table that holds a mapping between the numerical FRESULT code and it's
// name as a string.  This is used for looking up error codes for printing to
// the console.
//
//*****************************************************************************
tFresultString g_sFresultStrings[] =
{
    FRESULT_ENTRY(FR_OK),
    FRESULT_ENTRY(FR_DISK_ERR),
    FRESULT_ENTRY(FR_INT_ERR),
    FRESULT_ENTRY(FR_NOT_READY),
    FRESULT_ENTRY(FR_NO_FILE),
    FRESULT_ENTRY(FR_NO_PATH),
    FRESULT_ENTRY(FR_INVALID_NAME),
    FRESULT_ENTRY(FR_DENIED),
    FRESULT_ENTRY(FR_EXIST),
    FRESULT_ENTRY(FR_INVALID_OBJECT),
    FRESULT_ENTRY(FR_WRITE_PROTECTED),
    FRESULT_ENTRY(FR_INVALID_DRIVE),
    FRESULT_ENTRY(FR_NOT_ENABLED),
    FRESULT_ENTRY(FR_NO_FILESYSTEM),
    FRESULT_ENTRY(FR_MKFS_ABORTED),
    FRESULT_ENTRY(FR_TIMEOUT),
    FRESULT_ENTRY(FR_LOCKED),
    FRESULT_ENTRY(FR_NOT_ENOUGH_CORE),
    FRESULT_ENTRY(FR_TOO_MANY_OPEN_FILES),
    FRESULT_ENTRY(FR_INVALID_PARAMETER)
};

//*****************************************************************************
//
// A macro that holds the number of result codes.
//
//*****************************************************************************
#define NUM_FRESULT_CODES (sizeof(g_sFresultStrings) / sizeof(tFresultString))


//*****************************************************************************
//
// This function returns a string representation of an error code that was
// returned from a function call to FatFs.  It can be used for printing human
// readable error messages.
//
//*****************************************************************************
const char *
StringFromFresult(FRESULT fresult)
{
	unsigned int uIdx;

	//
	// Enter a loop to search the error code table for a matching error code.
	//
	for(uIdx = 0; uIdx < NUM_FRESULT_CODES; uIdx++)
	{
		//
		// If a match is found, then return the string name of the error code.
		//
		if(g_sFresultStrings[uIdx].fresult == fresult)
		{
			return(g_sFresultStrings[uIdx].pcResultStr);
		}
	}

	//
	// At this point no matching code was found, so return a string indicating
	// an unknown error.
	//
	return("UNKNOWN ERROR CODE");
}

//*****************************************************************************
//
// This function implements the "ls" command.  It opens the current directory
// and enumerates through the contents, and prints a line for each item it
// finds.  It shows details such as file attributes, time and date, and the
// file size, along with the name.  It shows a summary of file sizes at the end
// along with free space.
//
//*****************************************************************************
int
Cmd_ls(int argc, char *argv[])
{
    DWORD    ulTotalSize = 0;
	uint32_t ulFileCount = 0;
	uint32_t ulDirCount  = 0;
	DIR      dir = { };

	char const * dir_path;
	if(argc < 3)
	{
	    dir_path = ".";
	}
	else
	{
	    dir_path = argv[2];
	}

	//
	// Open the current directory for access.
	//
	FRESULT fresult = f_opendir(&dir, dir_path);
	if(fresult != FR_OK)
	{
		return (fresult);
	}

	//
	// Give an extra blank line before the listing.
	//
	prn_nl();

	//
	// Enter loop to enumerate through all directory entries.
	//
	for(;;)
	{
		//
		// Read an entry from the directory.
		//
	    FILINFO info;
		fresult = f_readdir(&dir, &info);
		if(fresult != FR_OK)
		{
			return(fresult);
		}

		//
		// If the file name is blank, then this is the end of the listing.
		//
		if(!info.fname[0])
		{
			break;
		}

		//
		// If the attribue is directory, then increment the directory count.
		//
		if(info.fattrib & AM_DIR)
		{
			ulDirCount++;
		}

		//
		// Otherwise, it is a file.  Increment the file count, and add in the
		// file size to the total.
		//
		else
		{
			ulFileCount++;
			ulTotalSize += info.fsize;
		}

		//
		// Print the entry information on a single line with formatting to show
		// the attributes, date, time, size, and name.
		//
		uprintf("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9u  %s\r\n",
				(info.fattrib & AM_DIR) ? 'D' : '-',
						(info.fattrib & AM_RDO) ? 'R' : '-',
								(info.fattrib & AM_HID) ? 'H' : '-',
										(info.fattrib & AM_SYS) ? 'S' : '-',
												(info.fattrib & AM_ARC) ? 'A' : '-',
														(info.fdate >> 9) + 1980,
														(info.fdate >> 5) & 15,
														info.fdate & 31,
														(info.ftime >> 11),
														(info.ftime >> 5) & 63,
														info.fsize,
														info.fname);
		wait_consoleTx();

	    if ( kbhit() )      //for very long ls, need a way to exit  new 10 May 2025
	    {
	        break;
	    }
	}

	//
	// Print summary lines showing the file, dir, and size totals.
	//
	uprintf("\r\n%4u File(s)\r\n%4u Dir(s)",
			ulFileCount, ulDirCount);

	//
	// Get the free space.
	//
	{
	    FATFS *pFatFs;
	    fresult = f_getfree("/", &ulTotalSize, &pFatFs);
	    if(fresult != FR_OK)
	    {
	        uprintf("\r\n\r\nf_getfree error! ");
	        return(fresult);
	    }

	    prn_nl();
	    uprintf("%10uK bytes free", pFatFs->free_clst * pFatFs->csize / 2); prn_nl(); prn_nl();
	}

	fresult = f_closedir(&dir);
    if(fresult != FR_OK)
    {
        uprintf("Cmd_ls f_closedir error: %s\r\n", StringFromFresult(fresult));
        return fresult;
    }

	return FR_OK;
}

// return totalSize and freeSpace in Kbytes
FRESULT get_storage_stat(uint32_t *ulTotalSizeKb, uint32_t *ulFreeSpaceKb)
{
    DWORD free_clusters;
    uint64_t total_sectors;
    uint64_t free_sectors;
    uint32_t sector_size;
    uint64_t card_size_bytes;
    uint64_t free_size_bytes;

    // Get free clusters and filesystem info
    FATFS *pfs;
    FRESULT fr = f_getfree("", &free_clusters, &pfs);
    if (fr != FR_OK)
    {
        *ulTotalSizeKb = 0;
        *ulFreeSpaceKb = 0;
        return fr;
    }

    // Get total number of sectors on the volume
    total_sectors = (pfs->n_fatent - 2) * pfs->csize;

    // Get number of free sectors
    free_sectors = free_clusters * pfs->csize;

    // Get sector size (usually 512 bytes for SD cards)
    #if FF_MAX_SS != FF_MIN_SS
        // FATFS is in dynamic sector size mode, so use the provided value
        sector_size = pfs->ssize;
    #else
        // FATFS is in single sector size mode, so just set it to the min
        sector_size = FF_MIN_SS;
    #endif

    // Calculate the total size of the SD card in bytes
    card_size_bytes = total_sectors * sector_size;
    free_size_bytes = free_sectors * sector_size;

    *ulTotalSizeKb = (uint32_t)(card_size_bytes/1024);

    //uprintf("uSD Card Size: %llu bytes\n", card_size_bytes);  prn_nl();
    //uprintf("uSD Card Size: %llu KB\n", card_size_bytes / 1024);  prn_nl();
    //uprintf("uSD Card Size: %llu MB\n", card_size_bytes / (1024 * 1024));  prn_nl();
    //uprintf("uSD Card Size: %llu GB\n", card_size_bytes / (1024 * 1024 * 1024));  prn_nl();

    //uprintf("Free Space: %llu bytes\n", free_size_bytes);  prn_nl();
    //uprintf("Free Space: %llu KB\n", free_size_bytes / 1024);  prn_nl();
    //uprintf("Free Space: %llu MB\n", free_size_bytes / (1024 * 1024));  prn_nl();

    *ulFreeSpaceKb = (uint32_t)(free_size_bytes/1024);

    return(fr);
}


int Cmd_cd(int argc, char *argv[])
{
    if(argc < 2)
    {
        return FR_INVALID_PARAMETER;
    }

    FRESULT ret = f_chdir(argv[1]);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cd f_chdir error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    strcpy(dirName, argv[1]);

    //uprintf("DBG: dirName %s", dirName); prn_nl();

    return FR_OK;
}

int Cmd_pwd(int argc, char *argv[])
{
    char buf[PATH_BUF_SIZE];

    FRESULT ret = f_getcwd(buf, sizeof(buf));
    if(ret != FR_OK)
    {
        uprintf("Cmd_pwd f_getcwd error: %s", StringFromFresult(ret));  prn_nl();
        return ret;
    }

    uprintf("%s\r\n", buf);

    return FR_OK;
}

int get_pwd_str(char *dirStr)
{
    //char buf[PATH_BUF_SIZE];

    FRESULT ret = f_getcwd(dirStr, DIRNAME_MAXSIZE);
    if(ret != FR_OK)
    {
        //uprintf("get_pwd_str f_getcwd error: %s", StringFromFresult(ret));  prn_nl();
        uprintf("Unable to retrieve directory name, check microSD card");   prn_nl();
        return ret;
    }

    //uprintf("DBG: dirStr %s\r\n", dirStr);

    return FR_OK;
}


//*****************************************************************************
//
// This function implements the "cat" command.  It reads the contents of a file
// and prints it to the console.  This should only be used on text files.  If
// it is used on a binary file, then a bunch of garbage is likely to printed on
// the console.
//
//*****************************************************************************
int
Cmd_cat(int argc, char *argv[])
{
    if(argc < 2)
    {
        return FR_INVALID_PARAMETER;
    }

    FIL file = { };
	FRESULT fresult;
	UINT num_read;
	uint8_t buf[256];

	//
	// Open the file for reading.
	//
	fresult = f_open(&file, argv[1], FA_READ);
	if(fresult != FR_OK)
	{
		return fresult;
	}

	//
	// Enter a loop to repeatedly read data from the file and display it, until
	// the end of the file is reached.
	//
	do
	{
		//
		// Read a block of data from the file.  Read as much as can fit in the
		// temporary buffer, including a space for the trailing null.
		//
		fresult = f_read(&file, buf, sizeof(buf) - 1, &num_read);

		//
		// If there was an error reading, then print a newline and return the
		// error to the user.
		//
		if(fresult != FR_OK)
		{
			prn_nl();
			return fresult;
		}

		//
		// Null terminate the last block that was read to make it a null
		// terminated string that can be used with printf.
		//
		buf[num_read] = 0;

		//
		// Print the last chunk of the file that was received.
		//
		//while( UARTTxBytesFree() <= 80 );	// Wait till there's room in console TX buffer. RCG 5/14
		wait_consoleTx();

		uprintf("%s", buf);
	}
	while((num_read == (sizeof(buf) - 1)) && (!kbhit()));	// Added kbhit(). RCG 5/14

	fresult = f_close(&file);

	//
	// Return success.
	//
	return FR_OK;
}

//*****************************************************************************
//
// This function implements the "help" command.  It prints a simple list of the
// available commands with a brief description.
//
//*****************************************************************************
tCmdLineEntry g_psCmdTable[];

int
Cmd_help(int argc, char *argv[])
{
	tCmdLineEntry *pEntry;
//	tCmdLineEntry *g_sCmdTable;
	//
	// Print some header text.
	//
	uprintf("\r\nAvailable commands\r\n");
	uprintf("------------------\r\n");

	//
	// Point at the beginning of the command table.
	//
	pEntry = &g_psCmdTable[0];

	//
	// Enter a loop to read each entry from the command table.  The end of the
	// table has been reached when the command name is NULL.
	//
	while(pEntry->pcCmd)
	{
		//
		// Print the command name and the brief description.
		//
		uprintf("%s%s\r\n", pEntry->pcCmd, pEntry->pcHelp);

		//
		// Advance to the next entry in the table.
		//
		pEntry++;
	}

	//
	// Return success.
	//
	return(0);
}


//*****************************************************************************
//
// This function implements the "exit" command.  Returns EXIT (-5), to indicate
// to exit the file viewer loop.
//*****************************************************************************
int
Cmd_exit(int argc, char *argv[])
{
	return(EXIT);
}

int Cmd_rm(int argc, char *argv[])
{
    if(argc < 2)
    {
        return FR_INVALID_PARAMETER;
    }

	FRESULT ret = f_unlink(argv[1]);
	if(ret != FR_OK)
	{
	    uprintf("Cmd_rm f_unlink error: %s\r\n", StringFromFresult(ret));
		return ret;
	}

	return FR_OK;
}

int Cmd_mkdir(int argc, char *argv[])
{
    if(argc < 2)
    {
        return FR_INVALID_PARAMETER;
    }

    FRESULT ret = f_mkdir(argv[1]);
    if(ret != FR_OK)
    {
        uprintf("Cmd_mkdir f_mkdir error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    return FR_OK;
}

int Cmd_mv(int argc, char *argv[])
{
	if(argc < 3)
	{
	    return FR_INVALID_PARAMETER;
	}

	FRESULT ret = f_rename(argv[1], argv[2]);
	if(ret != FR_OK)
	{
	    uprintf("Cmd_mv f_rename error: %s\r\n", StringFromFresult(ret));
		return ret;
	}

	return FR_OK;
}


//*****************************************************************************
//
// This is the table that holds the command names, implementing functions, and
// brief description.
//
//*****************************************************************************
tCmdLineEntry g_psCmdTable[] =
{
        { "help",   Cmd_help,        "  : Display list of commands" },
        { "h",      Cmd_help,        "  : Alias for help" },
        { "?",      Cmd_help,        "  : Alias for help" },
        { "cat",    Cmd_cat,         "  : Display contents of a text file" },
        { "cd",     Cmd_cd,          "  : Alias for chdir" },
        { "chdir",  Cmd_cd,          "  : Change directory" },
        { "chkdsk", Cmd_chkdsk,      "  : Check Disk" },
        { "cp",     Cmd_cp,          "  : Copy file" },
        { "format", Cmd_format,      "  : Format Disk" },
        { "ls",     Cmd_ls,          "  : Display list of files" },
        { "mkdir",  Cmd_mkdir,       "  : Make new directory"},
        { "mv",     Cmd_mv,          "  : Rename or move a file or directory" },
        { "pwd",    Cmd_pwd,         "  : Display current working directory" },
        { "rm",     Cmd_rm,          "  : Remove a file or directory" },
        { "touch",  Cmd_touch,       "  : Create file or update file timestamp" },
        { "ys",     ymodem_send,     "  : Send file via YMODEM" },
        { "yr",     ymodem_receive,  "  : Receive file via YMODEM" },
        { "exit",   Cmd_exit,        "  : Exit the file viewer" },
        { 0, 0, 0 }
};




/*
 * Enter a command loop to view the file system on the SD card.
 * Helpful for accessing the following commands:
 * ls (display list of files),
 * cat (show contexts of a text file).
 */
void viewFiles(void)
{
	int nStatus;

	uprintf("\r\n\r\nUnix-like command line. Type 'help' for list of commands\r\n");

	char cmd_buf[PATH_BUF_SIZE];

	FRESULT ret = f_getcwd(cmd_buf, sizeof(cmd_buf));
	if(ret != FR_OK)
	{
	    uprintf("Failed to get cwd, aborting");
	    return;
	}

	// Enter an infinite loop for reading and processing commands from the user.
	while(1)
	{
		// Print a prompt to the console.  Show the CWD.
		uprintf("\r\n%s> ", cmd_buf);       //bug, prints command for directory name
		print_prompt();     //defd in serial.c

		// Get a line of text from the user.
		getUserInput(cmd_buf, sizeof(cmd_buf));

		// Pass the line from the user to the command processor.  It will be
		// parsed and valid commands executed.
		nStatus = CmdLineProcess(cmd_buf);

		// Handle the case of bad command.
		if(nStatus == CMDLINE_BAD_CMD)
		{
			uprintf("Bad command!\r\n");
		}

		// Handle the case of too many arguments.
		else if(nStatus == CMDLINE_TOO_MANY_ARGS)
		{
			uprintf("Too many arguments for command processor!\r\n");
		}

		//exit this function ("viewFiles()") on command 'exit'
		else if(nStatus == EXIT)
		{
			return;
		}

		// Otherwise the command was executed.  Print the error code if one was returned.
		else if(nStatus != 0)
		{
			uprintf("Command returned error code %s\r\n", StringFromFresult((FRESULT)nStatus));
		}
	}
}

/*
 * Append a buffer to the end of our file (sys_data.fileName)
 * on the SD card.
 * ASSUMES THAT THE PASSED IN BUFFER IS A C STRING (uses strlen to determine
 * how much of the buffer to write in).
 */
int appendBufferToSDCard(char const * const buffer)
{
    FIL fileObject;     // File object
    FRESULT fresult;
    UINT bw;            // Bytes written
    size_t num_to_write;
    int retVal = 1;
    bool is_open = false;

    if( ! microSD_flg )
    {
        return 0;
    }

    memset(&fileObject, 0, sizeof(fileObject));
    num_to_write = strlen(buffer);

    do
    {
        // Open a file
        fresult = f_open(&fileObject, sys_data.fileName, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
        //MAP_SysCtlDelay(500*MILLISECOND);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_open error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }
        is_open = true;

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Seek to the end, to append our file
        fresult = f_lseek(&fileObject, f_size(&fileObject));
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_lseek error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Write the buffer to the SD card
        fresult = f_write( &fileObject, buffer, num_to_write, &bw);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_write error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        if(num_to_write != bw)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_write error: write underrun"); prn_nl(); }
            break;
        }
    } while(false);

    if(is_open)
    {
		// Close the file
		fresult = f_close( &fileObject);
		if(fresult != FR_OK)
		{
			if(fileDbgFlg == 1) { uprintf("appendBufferToSDCard f_close error: %s", StringFromFresult(fresult)); prn_nl(); }
		}
    }

	return retVal;
}

/*
 * Append a buffer to the end of our file (apf_eeprom.logIsus) stored on uSD card.
 * ASSUMES THAT THE PASSED IN BUFFER IS A C STRING (uses strlen to determine
 * how much of the buffer to write in).
 */
int appendIsusProfileDataToSDCard(char const * const buffer)
{
    FIL fileObject;     // File object
    FRESULT fresult;
    UINT bw;            // Bytes written
    size_t num_to_write;
    int retVal = 1;
    bool is_open = false;

    if( ! microSD_flg )
    {
        if(fileDbgFlg == 1) { uprintf("DBG MicroSD is disabled, likely due to f_open error"); prn_nl(); } //APFTODO, remove extraneous prints
        //error_store("MicroSD is disabled");
        return 0;
    }

    memset(&fileObject, 0, sizeof(fileObject));
    num_to_write = strlen(buffer);

    do
    {
        // Open a file
        fresult = f_open(&fileObject, apf_eeprom.logIsus, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);  //FA_OPEN_APPEND seeks to end
        //MAP_SysCtlDelay(500*MILLISECOND);       //APFTODO - why 500msec??   For allowing microsd card controller
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_open error: %s, uSD disabled with microSD_flg=0", StringFromFresult(fresult)); prn_nl(); }
            microSD_flg = 0;       // Disable further writes
            break;
        }
        is_open = true;

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Seek to the end, to append our file
        apf_fram.logIsus_fsize = f_size(&fileObject);
        fresult = f_lseek(&fileObject, apf_fram.logIsus_fsize);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_lseek error: %s", StringFromFresult(fresult)); prn_nl(); }
            microSD_flg = 0;       // Disable further writes
            break;
        }

        // Write the buffer to the SD card
        fresult = f_write( &fileObject, buffer, num_to_write, &bw);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_write error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        if(num_to_write != bw)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_write error: write underrun"); prn_nl(); }
            microSD_flg = 0;       // Disable further writes  APFTODO, review after test
            break;
        }
    } while(false);

    if(is_open)
    {
        // Close the file
        fresult = f_close( &fileObject);
        if(fresult != FR_OK)
        {
            if(fileDbgFlg == 1) { uprintf("appendIsusProfileDataToSDCard f_close error: %s", StringFromFresult(fresult)); prn_nl(); }
        }
    }

    return retVal;
}


/*
 * Append a buffer to the end of our file (apf_eeprom.logIsus)
 * on the SD card.
 * ASSUMES THAT THE PASSED IN BUFFER IS A C STRING (uses strlen to determine
 * how much of the buffer to write in).
 */
int appendDuraProfileDataToSDCard(char const * const buffer)
{
    FIL fileObject;     // File object
    FRESULT fresult;
    UINT bw;            // Bytes written
    size_t num_to_write;
    int retVal = 1;
    bool is_open = false;

    if( ! microSD_flg )
    {
        return 0;
    }

    memset(&fileObject, 0, sizeof(fileObject));
    num_to_write = strlen(buffer);

    do
    {
        // Open a file
        fresult = f_open(&fileObject, apf_eeprom.logDura, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
        //MAP_SysCtlDelay(500*MILLISECOND);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_open error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }
        is_open = true;

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Seek to the end, to append our file
        fresult = f_lseek(&fileObject, f_size(&fileObject));
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_lseek error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Write the buffer to the SD card
        fresult = f_write( &fileObject, buffer, num_to_write, &bw);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_write error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        if(num_to_write != bw)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_write error: write underrun"); prn_nl(); }
            break;
        }
    } while(false);

    if(is_open)
    {
        // Close the file
        fresult = f_close( &fileObject);
        if(fresult != FR_OK)
        {
            if(fileDbgFlg == 1) { uprintf("appendDuraProfileDataToSDCard f_close error: %s", StringFromFresult(fresult)); prn_nl(); }
        }
    }

    return retVal;
}


int appendBufferToSDCardErrorLog(char const * const buffer)
{
    FIL fileObject;     // File object
    FRESULT fresult;
    UINT bw;            // Bytes written
    size_t num_to_write;
    int retVal = 1;
    bool is_open = false;

    if( ! microSD_flg )
    {
        return 0;
    }

    memset(&fileObject, 0, sizeof(fileObject));
    num_to_write = strlen(buffer);

    do
    {
        // Open a file
        fresult = f_open(&fileObject, "error.log", FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_open error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }
        is_open = true;

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Seek to the end, to append our file
        fresult = f_lseek(&fileObject, f_size(&fileObject));
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_lseek error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Write the buffer to the SD card
        fresult = f_write(&fileObject, buffer, num_to_write, &bw);

        apf_fram.errorLog_errorCnt++;        //used for CalcRecsPending

        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_write error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        // Sync to shrink critical section
        fresult = f_sync(&fileObject);
        if(fresult != FR_OK)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_sync error: %s", StringFromFresult(fresult)); prn_nl(); }
            break;
        }

        if(num_to_write != bw)
        {
            retVal = 0;
            if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_write error: write underrun"); prn_nl(); }
            break;
        }
    } while(false);

    if(is_open)
    {
	    // Close the file
	    fresult = f_close(&fileObject);
	    if(fresult != FR_OK)
	    {
	        retVal = 0;
	        if(fileDbgFlg == 1) { uprintf("appendBufferToSDCardErrorLog f_close error: %s", StringFromFresult(fresult)); prn_nl(); }
	    }
    }

    return retVal;
}

/*
 * Init_SDCard()
 * Initializes SD card to use with FatFS file system
 */
int init_uSDCard(void)
{
	FRESULT fresult;
	uint32_t ulDataRx[1];

#if BOARD_MFET >= 1 || BOARD_MPHOX >= 1 || BOARD_NANOFET >=1
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

	// Configure the pin muxing
	MAP_GPIOPinConfigure(GPIO_PA2_SSI0CLK);
	MAP_GPIOPinConfigure(GPIO_PA3_SSI0FSS);
	MAP_GPIOPinConfigure(GPIO_PA4_SSI0RX);
	MAP_GPIOPinConfigure(GPIO_PA5_SSI0TX);

	// Configure the GPIO settings for the SSI pins.
	MAP_GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);

	// Configure and enable the SSI port for SPI master mode.
	//MOTO_MODE_0 is best for SDCards.
	MAP_SSIConfigSetExpClk(SSI0_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
			SSI_MODE_MASTER, 1000000, 8);
	MAP_SSIEnable(SSI0_BASE);

	// Flush receive FIFO before starting to talk to SDCard.
	while(MAP_SSIDataGetNonBlocking(SSI0_BASE, &ulDataRx[0]))
	{
		;
	}
#endif
#if BOARD_MSC == 3
#ifdef PART_TM4C1290NCPDT


    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);   //(already done in init_TM4C1290NCPDT_128pin.c
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);  //

    // Configure the pin muxing (PA4/PA5 are swapped from TM4c123g, confusing!)
    MAP_GPIOPinConfigure(GPIO_PA2_SSI0CLK);    // Configure the GPIO Pin Mux for PA2 for SSI0CLK
    MAP_GPIOPinConfigure(GPIO_PA3_SSI0FSS);    // Configure the GPIO Pin Mux for PA3 for SSI0FSS
    MAP_GPIOPinConfigure(GPIO_PA5_SSI0XDAT1);  // Configure the GPIO Pin Mux for PA5 for SSI0XDAT1 - MISO, was GPIO_PA4_SSI0RX
    MAP_GPIOPinConfigure(GPIO_PA4_SSI0XDAT0);  // Configure the GPIO Pin Mux for PA4 for SSI0XDAT0 - MOSI, was GPIO_PA5_SSI0TX

    // Configure the GPIO settings for the SSI pins. (already done in init_)
    MAP_GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);

    // Configure and enable the SSI port for SPI master mode.
    //MOTO_MODE_0 is best for SDCards.
    MAP_SSIConfigSetExpClk(SSI0_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_0,
            SSI_MODE_MASTER, 1000000, 8);
    MAP_SSIEnable(SSI0_BASE);

    // Flush receive FIFO before starting to talk to uSDCard.
    while(MAP_SSIDataGetNonBlocking(SSI0_BASE, &ulDataRx[0]))
    {
        ;
    }
#endif

#ifdef PART_TM4C123GH6PM
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    // Configure the pin muxing
    MAP_GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    MAP_GPIOPinConfigure(GPIO_PA3_SSI0FSS);
    MAP_GPIOPinConfigure(GPIO_PA4_SSI0RX);
    MAP_GPIOPinConfigure(GPIO_PA5_SSI0TX);

    // Configure the GPIO settings for the SSI pins.
    MAP_GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5);

    // Configure and enable the SSI port for SPI master mode.
    //MOTO_MODE_0 is best for SDCards.
    MAP_SSIConfigSetExpClk(SSI0_BASE, MAP_SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
            SSI_MODE_MASTER, 1000000, 8);
    MAP_SSIEnable(SSI0_BASE);

    // Flush receive FIFO before starting to talk to SDCard.
    while(MAP_SSIDataGetNonBlocking(SSI0_BASE, &ulDataRx[0]))
    {
        ;
    }
#endif
#endif

	// Mount the file system, using logical disk 0.
	//
	fresult = f_mount(&g_sFatFs, "", 1);
	if(fresult != FR_OK)
	{
		if(fileDbgFlg == 1) { uprintf("f_mount error: %s", StringFromFresult(fresult)); prn_nl(); }
		return 0;
	}

	//dirName is used for prompt (serial.c)
	memset(dirName, 0x00, DIRNAME_MAXSIZE);

	return 1;
}

/*
 * sd_fprintf()
 * Prints text to SD card file with floating point support.
 * 255 characters maximum.
 * Returns number of characters written if less than 255 (lookup vsnprintf())
 * RCG 5/14
 */
int sd_fprintf(FIL *fil, const char *format, ...)
{
   va_list args;
   char buff[256];
   UINT bw;
   FRESULT fresult;

   va_start (args, format);

   // Print formatted string to buffer
   vsnprintf(buff, 256, format, args);

	// Write the buffer to the SD card
	fresult = f_write( fil, buff, strlen(buff), &bw );
	if(fresult != FR_OK)
	{
		uprintf("f_write error: %s\r\n", StringFromFresult(fresult));
	}

	va_end (args);


   return bw;
}

#if BOARD_NANOFET == 1
// Added by Thom Maughan to support Winbond W25N01GVZEIG  SPI Serial Flash
//
void sd_format(void)
{


    FATFS fs;           /* Filesystem object */
    FIL fil;            /* File object */
    FRESULT res;        /* API result code */
    UINT bw;            /* Bytes written */
    BYTE work[1024]; /* Work area (larger is better for processing time) */ //Valid values are 512, 1024, 2048 and 4096


    /* Format the default drive with default parameters */
    res = f_mkfs ("", 0, 0);
    //res = f_mkfs("", 0, work, sizeof work);
    if (res) {  }

    /* Give a work area to the default drive */
    f_mount(&fs, "", 0);

    /* Create a file as new */
    res = f_open(&fil, "howdy.txt", FA_CREATE_NEW | FA_WRITE);
    if (res) {  }

    /* Write a message */
    f_write(&fil, "Hello from Thom!\r\n", 18, &bw);
    if (bw != 18)  {  }

    /* Close the file */
    f_close(&fil);

    /* Unregister work area */
    f_mount(0, "", 0);
}
#endif

int Cmd_chkdsk(int argc, char *argv[])
{
    uprintf("Cmd_chkdsk not implemented - FatFS does not have a chkdsk\r\n");
    return FR_INVALID_PARAMETER;
}
int Cmd_cp(int argc, char *argv[])
{
    if(argc < 3)
    {
        return FR_INVALID_PARAMETER;
    }
    char const * const src_path = argv[1];
    char const * const dst_path = argv[2];

    FIL src_file = { };
    FRESULT ret = f_open(&src_file, src_path, FA_OPEN_EXISTING | FA_READ);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cp f_open src error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    FIL dst_file = { };
    ret = f_open(&dst_file, dst_path, FA_CREATE_ALWAYS | FA_WRITE);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cp f_open dst error: %s\r\n", StringFromFresult(ret));
        f_close(&src_file);
        return ret;
    }

    // sync to commit file creation
    ret = f_sync(&dst_file);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cp f_sync dst error: %s\r\n", StringFromFresult(ret));
        f_close(&src_file);
        f_close(&dst_file);
        return ret;
    }

    // read/write until done or error
    // if num_read < sizeof(file_buf) eof is reached
    // if num_written < num_read probably volume is full
    uint8_t file_buf[256]; // TODO: maybe a full flash page, 4kB? maybe on the heap?
    UINT num_read;
    UINT num_written;
    do
    {
        ret = f_read(&src_file, file_buf, sizeof(file_buf), &num_read);
        if(ret != FR_OK)
        {
            uprintf("Cmd_cp f_read src error: %s\r\n", StringFromFresult(ret));
            f_close(&src_file);
            f_close(&dst_file);
            return ret;
        }

        ret = f_write(&dst_file, file_buf, num_read, &num_written);
        if(ret != FR_OK)
        {
            uprintf("Cmd_cp f_write dst error: %s\r\n", StringFromFresult(ret));
            f_close(&src_file);
            f_close(&dst_file);
            return ret;
        }

    } while( (num_read == sizeof(file_buf)) && (num_read == num_written));

    ret = f_sync(&dst_file);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cp f_sync dst error: %s\r\n", StringFromFresult(ret));
        f_close(&src_file);
        f_close(&dst_file);
        return ret;
    }

    // we ignore errors closing src_file as it is read only
    ret = f_close(&src_file);

    // since we just synced dst_file this should usually succeed
    ret = f_close(&dst_file);
    if(ret != FR_OK)
    {
        uprintf("Cmd_cp f_close dst error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    return FR_OK;
}
int Cmd_format(int argc, char *argv[])
{
    if(argc < 1)
    {
        return FR_INVALID_PARAMETER;
    }

    // TODO: umount before format?

    char logical_drive[5] = ""; // 123:, eg up to three digit drives follwed by null
    if(argc < 2)
    {
        strncat(logical_drive, "0:", sizeof(logical_drive)-1);
    }
    else
    {
        if(strlen(argv[1]) > (sizeof(logical_drive)-1))
        {
            return FR_INVALID_PARAMETER;
        }
        strncat(logical_drive, argv[1], sizeof(logical_drive)-1);
    }

    // Figure out which FATFS instance this drive is
    // Fail if we don't know that driver number
    FATFS* disk_ptr = NULL;
    if(strcmp(logical_drive, "0:") == 0)
    {
        disk_ptr = &g_sFatFs;
    }
    else
    {
        uprintf("Cmd_format unknown drive: %s\r\n", logical_drive);
        return FR_INVALID_PARAMETER;
    }

    const  MKFS_PARM opt = {
                     .fmt     = FM_ANY, // Auto based on volume size and au_size
                     .n_fat   = 0,      // default
                     .align   = 0,      // asks lower layer
                     .n_root  = 0,      // default
                     .au_size = 0       // auto based on volume size
    };
    uint8_t work[FF_MAX_SS]; // FF_MAX_SS is the smallest allowed, we may need larger especially for NAND flash with 4kB pages
    FRESULT ret = f_mkfs(logical_drive, &opt, work, FF_MAX_SS);
    if(ret != FR_OK)
    {
        uprintf("Cmd_format f_mkfs error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    // Remount using the correct drive pointer for this logical drive
    ret = f_mount(disk_ptr, logical_drive, 1);
    if(ret != FR_OK)
    {
        uprintf("Cmd_format f_mount error: %s\r\n", StringFromFresult(ret));
        return ret;
    }

    return FR_OK;
}
int Cmd_touch(int argc, char *argv[])
{
    if(argc < 2)
    {
        return FR_INVALID_PARAMETER;
    }

    bool create_file = false;

    FILINFO file_info = { };
    FRESULT ret = f_stat(argv[1], &file_info);
    switch(ret)
    {
        case FR_OK:
        {
            create_file = false;
            break;
        }
        case FR_NO_FILE:
        {
            create_file = true;
            break;
        }
        default:
        {
            // Causing FR_NO_PATH to fail is intentional to match behavior of POSIX touch. Call mkdir first before touch.
            uprintf("Cmd_touch f_stat error: %s\r\n", StringFromFresult(ret));
            return FR_INVALID_PARAMETER;
        }
    }

    if(create_file)
    {
        FIL file = { };

        ret = f_open(&file, argv[1], FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
        if(ret != FR_OK)
        {
            uprintf("Cmd_touch f_open error: %s\r\n", StringFromFresult(ret));
            return ret;
        }

        ret = f_close(&file);
        if(ret != FR_OK)
        {
            uprintf("Cmd_touch f_close error: %s\r\n", StringFromFresult(ret));
            return ret;
        }
    }
    else
    {
        // update timestamp
        const DWORD t_i = get_fattime();
        file_info.fdate = (t_i & 0xFFFF0000U) >> 16;
        file_info.ftime = (t_i & 0x0000FFFFU) >>  0;
        ret = f_utime(argv[1], &file_info);
        if(ret != FR_OK)
        {
            uprintf("Cmd_touch f_utime error: %s\r\n", StringFromFresult(ret));
            return ret;
        }
    }

    return FR_OK;
}
