/*
** ==============================================================================
**
** File System Test Lab Code
**
** suggestion:
**    use a monospaced font in the terminal display
**
** References:
**    https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841771/xilffs - confluence page for xilffs
**    http://elm-chan.org/fsw/ff/00index_e.html - details API
**
** Implementation notes:
**    Can target RAM or SDcard. Selected through fs_interface (in the BSP library configuration) where RAM = 2, SD/eMMC = 1
**
** History:
**    2024/04/29 - WK,RS - 2023.2 - Cleared the conflicts arised during one of the commits to SVN. 
**    2023/07/14 - RS - 2023.1 - Update the source code according to 0.15 FATFS. Changes in f_mkfs function
**    2021/12/16 - wk - 2021.2 - reworked I/O using serialPort_helper instead of explicit addressing
**    2021/08/30 - wk - 2021.1 - cleaned up - replaced "echo" with meaningful messages
**    2020/02/18 - wk - 2019.2, retargeting MPSoC, moved to xilffs instead of xilfat - major rewrite of file system code
**    2019/??/?? - nk - 2019.1 - first move to xilffs
**    2017/11/23 - wk - refactored for 2017.3, issue with UARTlite behavior - keeps last character in FIFO if no new characters arrive
**    2016/12/13 - WK - removed LCD, button, LED support. details:
**            FMC-CE board deprecated and most development boards no longer
**            contain an LCD, so the LCD content must be removed for compatability
**            all messages that went to the LCD are redirected to the UART
**            buttons are replaced by input from the UART, LEDs were superfluous anyway
**    2010/03/24 - dy = update
**    2008/11/11 - wk - updated for mfs support
**    2008/09/30 - wk - initial
**
** ------------------------------------------------------------------------------
**
** missing function call for line 167: mfs_get_usage
**
** ------------------------------------------------------------------------------
**
****************************************************************************************/

#include <stdio.h>
#include "stdlib.h"
#include "xparameters.h"
#include "ff.h"             // ffs library header file to support file handling
#include "xil_printf.h"
#include "serialPort_helper.h"

#define SUCCESS 1
#define FAILURE 0

// function prototypes
char* interpretStatus (FRESULT status);
int   isFile(TCHAR* fName);
int   getFileSize(TCHAR* fName);
int   deleteFile(TCHAR* fName);
int   openFile(TCHAR* fName, char* mode);
BYTE  convertPOSIX2FA(char* mode);
void  dumpError(char* errMsg, FRESULT status);
void  interpretFileInfo(TCHAR* fName);
char* monthName(int m);
char* attributeName(BYTE a);
MKFS_PARM mkfs_parm;
// define the file system variables - global so that all fns have access to them
static FATFS   fatfs;      // file system basis
static FIL      fObj;      // File object
static DIR      dObj;      // directory object
FATFS *fatfsPtr = &fatfs;

#ifdef __ICCARM__
#pragma data_alignment = 32
u8 DestinationAddress[10*1024];
#pragma data_alignment = 32
u8 SourceAddress[10*1024];
#else
u8 DestinationAddress[10*1024] __attribute__ ((aligned(32)));
u8 SourceAddress[10*1024] __attribute__ ((aligned(32)));
#endif

// ********** MAIN **********

int main() {
	// local variable declarations
    char    user_prompt[] = "Enter command (C,w,D,d,r,f,i):";
    char    receivedChar = '\0';

    // file system stuff...
    FRESULT status = FR_OK;
    UINT    nBytesRead, nBytesWritten;
    UINT    fSize;
    DWORD   nFreeClusters, totalSectors, freeSectors, percentFreeSectors;
    TCHAR  *path = "0:/";                                             // ffs uses logical drive indicators. logical drive 0 represented as "0:/", drive 1 "1:/" etc. Up to 10 drives supported and must be set in the BSP
    BYTE    work[FF_MAX_SS];
    void*   workBuffer = work;
    char    readBuffer[65535];
    char    msgToWriteToFile[] = "test\nmessage\n1, 2, 3\n";          // message to write to the file when it is created

    TCHAR*  fileName = "test.txt";
    TCHAR*  dirName  = "testDir";

    // create the definition for the FAT16 configuration
    //  BYTE fmt;           /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
    //  BYTE n_fat;         /* Number of FATs */
    //  UINT align;         /* Data area alignment (sector) */
    //  UINT n_root;        /* Number of root directory entries */
    //  DWORD au_size;      /* Cluster size (byte) */
    static const MKFS_PARM fat16 = {FM_FAT32, 0, 0, 0, 0};

    // welcome message via stdout
    xil_printf("\n\r"); // start on a new line
    xil_printf("\t====================================\r");
    xil_printf("\t==   Memory and File System Test  ==\r");
    xil_printf("\t====================================\n");

    // Register volume work area, initialize device
 /*** todo: add line of code to mount the file system ***/
    if (status != FR_OK) {                                        // verify that the mounting of the RAM drive was successful
		dumpError("Failed to mount the FAT file system: ",status);
        return -1;
    }

   // create the file system in the memory region
   mkfs_parm.fmt = FM_FAT32;
   status = f_mkfs(path, &mkfs_parm, work, sizeof work);
   if (status != FR_OK) {
      dumpError("Failed to create the file system on the mounted device\n\r",status);
      return -1;
   }

    // create the file system in the memory region
    status = f_mkfs(path, &fat16, workBuffer, sizeof work);
    if (status != FR_OK) {
        dumpError("Failed to create the file system on the mounted device\n\r",status);
        return -1;
    }
	
    // prompt the user
    xil_printf(user_prompt);

    // Main Loop
    while (1) {
        // read a character from the UART (no character reads back as -1)
        receivedChar = getCharFromUART(0);

        // decode the received character
        switch (receivedChar) {

			// find out how many blocks are used and are free in the file system
			case 'f':
				// Get volume information and free clusters of drive 0

 /*** todo: add line of code to determine the amount of free space that is available ***/
				if (status == FR_OK) {
					totalSectors = (fatfs.n_fatent - 2) * fatfs.csize / 2;
					freeSectors  = nFreeClusters * fatfs.csize / 2;

					// display the percentage of free space
					percentFreeSectors = freeSectors * 100 / totalSectors;
					xil_printf("\n\r%10lu %% free of %10lu Kbytes\n\r", percentFreeSectors , totalSectors/1024);
				} else {
					dumpError("Could not determine the amount of free space: ",status);
				}
			break;

			// get file information
			case 'i':
				interpretFileInfo(fileName);
			break;

			// Delete File
			case 'd':
				xil_printf("\n\rDeleting file: %s...",fileName);
				status = deleteFile(fileName);
				if (status == FR_OK) { xil_printf("deleted!\n\r"); }
				else                 { /* any issues would be reported through deleteFile */  }
			break;

			// read the text file
			case 'r':
				xil_printf("\n\rReading from file %s...",fileName);
				if (openFile(fileName,"r") == SUCCESS) {
					fSize = getFileSize(fileName);                        // how big is the file?
					status = f_read(&fObj,readBuffer,fSize,&nBytesRead);  // read the file into the buffer
					if (status == FR_OK) {
						if (fSize == nBytesRead) {                         // complete read
							xil_printf("All data read from the file and here it is...\n\r");
						} else {                                           // problem extracting all the data from the file
							xil_printf("Couldn't read all the data from the file, but here's what I got:\n\r");
						}
					xil_printf("%s\n\r ",readBuffer);                  // display one line of data on the stdout
					xil_printf("--- end of file ---\n\r");
					f_close(&fObj);                                    // and close the file
				} else {                                              // problem with accessing the file
					dumpError("Couldn't properly read from the file: ",status);
				}
			}
			break;

			// Create file and write a message to it
			case 'w':
				// if file exists, then open for appending, otherwise create it
				if (isFile(fileName) == FR_EXIST) {      // file already exists - append
					xil_printf("\nAppending to file %s and adding a message to it...\n\r",fileName);
					status = openFile(fileName,"a");
				} else {                  // file doesn't exist - create
					xil_printf("\n\rCreating file %s and writing a message to it...\n\r",fileName);
					status = openFile(fileName,"w");
				}

				// if the file was opened for either writing or appending successfully, then write/append a message to it
				if (status == SUCCESS) {
					int i=0; for (i=0; i<2048; i++) { }       // delay loop
					status = f_write(&fObj,msgToWriteToFile,sizeof(msgToWriteToFile)-1,&nBytesWritten);  // write something into the file (the -1 removes the null character at the end of the string)
					for (i=0; i<2048; i++) { }       // delay loop
					//status = f_write(&fObj,msgToWriteToFile,sizeof(msgToWriteToFile),&nBytesWritten);

					if (nBytesWritten != sizeof(msgToWriteToFile)-1) {
						dumpError("didn't get the whole message out to the file: ",status);
					} else {
						xil_printf("message written to the file!\n\r");
					}
					status = f_close(&fObj);                              // close the file
					if (status != FR_OK) { dumpError("failed to close the file: ",status); }
				} else {                                                 // failed to open the file
					// no need to present an error message as the detailed message would be provided through the "openFile" call
				}
			break;

			// Create directory
			case 'C':
				xil_printf("\n\rCreating directory %s...",dirName);
				status = f_mkdir(dirName);                               // Function call to see if testdir exists or not
				if (status == FR_EXIST) {                                // return code if directory already exists
					xil_printf("Directory %s already exists\r\n",dirName);
				} else {
					status = f_opendir(&dObj,dirName);                   // attempt to create the directory
					if (status == FR_OK) { xil_printf("Created directory %s\r\n",dirName); }
					else                 { dumpError("Failed to create directory: ",status); }
				}
			break;

			// Delete the directory
			case 'D':
				xil_printf("\n\rDeleting directory %s...",dirName);
				status = f_unlink(dirName);                             // Function call to delete testdir
				if (status == FR_OK) { xil_printf("directory deleted\r\n"); }
				else                 { dumpError("Failed to delete directory: ",status); }
			break;

			// print an "end-of-preloaded commands" message
			case '*':
				xil_printf("\nEnd-of-preloaded commands!\n");

			case '\r':      // carriage return
			case '\n':      // line feed
			case '\377':    // -1 - no character received
				continue;   // no action, just consume the character and continue

			default:        // illegal character received
				xil_printf("\n\r----------------------\r\n");
				xil_printf("\tD: Delete directory\r\n");
				xil_printf("\td: Delete file\r\n");
				xil_printf("\tC: Create directory\r\n");
				xil_printf("\tw: Create file and write a message to it\r\n");
				xil_printf("\tr: Read file\r\n");
				xil_printf("\tf: Number of Free blocks\r\n");
				xil_printf("----------------------\r\n\n");
		} // end of switch block

		// prompt the user
		xil_printf(user_prompt);

	} // main loop

    // should never reach...
    return(0);
} // main()

/**
 * isFile
 * @param fName - name, with full path
 * @return FR_EXIST if the passed name refers to a file, FR_NO_FILE if a directory or error occurred
 */
int isFile(TCHAR* fName) {
    FRESULT  status;
    FILINFO  fno;

	status = f_stat(fName,&fno);
	switch (status) {
		case FR_EXIST:                 // if the file exists
			return FR_EXIST;
		case FR_OK:                    // operation on this file was successful and the specifics are listed in fno
			if (fno.fattrib & AM_DIR) {  // take the attributes and mask it against the directory bit, if it is non-zero then this is not a file but a directory
				return FR_NO_FILE;      // it's a directory, not a file, return an error
			}
			return FR_EXIST;
		case FR_NO_FILE:               // file was not found therefore no information can be extracted
		return FR_NO_FILE;
		default:
			dumpError("isFile: unable to check the status because: ",status);
   }
   return FR_NO_FILE;
}

/**
 * isDir
 * @param fName - name, with full path
 * @return 1 if the passed name refers to a directory, 0 if a file or error occurred
 */
int isDir(TCHAR* fName) {
    FRESULT  status;
    FILINFO  fno;

    status = f_stat(fName,&fno);
    switch (status) {
		case FR_EXIST:
		case FR_OK:                   // operation on this file was successful and the specifics are listed in fno
			if (fno.fattrib & AM_DIR) { // take the attributes and mask it against the directory bit, if it is non-zero then this is not a file but a directory
				return 1;
			}
		case FR_NO_FILE:              // file was not found therefore no information can be extracted
			return 0;                  // don't need a message, directory just wasn't found
		default:
			dumpError("isDir: unable to check the status because: ",status);
	}
	return 0;
}

/**
 * interpretFileInfo
 *    pretty prints the file information
 * @param fInfo
 * @return - none.
 */
void interpretFileInfo (TCHAR* fName) {
    // local variables
    FRESULT status;
    FILINFO fInfo;
    BYTE i,mask = 1;

    // get the file information
    status = f_stat(fName,&fInfo);                        // get the status of the file. if the file exists, then FR_OK will be returned otherwise FR_NO_FILE (other return values exists for system issues)
    if (status != FR_OK) {                                // if we couldn't get the file information, dump an error and return
		dumpError("getFileSize couldn't extract file size: ",status);  // find out what went wrong...
        return;
	}

	// start reporting on the file
    xil_printf("File/Dir name: %s\n\r",fInfo.fname);      // present the file or directory name
 /*** todo: add line of code to display the file size from the fInfo structure ***/ 

	// get the date and time of the last modification
	int day   = fInfo.fdate & 0x001F;                     // day is stored in bits 4:0
	int month = (fInfo.fdate & 0x01e0) >> 5;              // month is stored in bits 8:5
	int year  = ((fInfo.fdate & 0xfe00) >> 9)  + 1990;    // year is in remaining bits 15:9 from 1980 (according to doc.,but it appears as though it is really from 1990)
	xil_printf("  Last modified: %02d %s %04d\n\r",day,monthName(month),year);
	int hour  = (fInfo.ftime & 0xf800) >> 11;             // hour is stored in bits 15:11 as 0-23
	int min   = (fInfo.ftime & 0x07e0) >>  5;             // minute is stored in bits 10:5 as 0-59
	int sec   = fInfo.ftime & 0x001f;                     // every other second (2,4,6,...,58) in bits 4:0 0-29
	xil_printf("             at: %02d:%02d:%02d\n\r",hour,min,sec*2);

	// analyze and report the attributes
	xil_printf("   with attributes: ");
	for (i=0; i<8; i++) {                                 // check all positions of the attribute BYTE
		xil_printf(attributeName(fInfo.fattrib & mask));
		xil_printf(" ");
		mask <<= 1;                                        // shift the mask bit by one to the left
	}

	// done
	xil_printf("\n\r*** end of info report ---\n\r");
}

char* attributeName(BYTE a) {
    switch (a) {
		case AM_RDO: return "read-only";
		case AM_HID: return "hidden";
		case AM_SYS: return "system";
		case AM_ARC: return "archive";
		case AM_DIR: return "directory";
		case 0:      return "";       // option is not enabled
		default:
			return "unknown";
   }
}

/**
 * monthName
 * @param m - month number 1:12
 * @return 3 character string representing the abbreviated month name
 */
char* monthName(int m) {
    switch (m) {
		case  1: return "JAN";
		case  2: return "FEB";
		case  3: return "MAR";
		case  4: return "APR";
		case  5: return "MAY";
		case  6: return "JUN";
		case  7: return "JUL";
		case  8: return "AUG";
		case  9: return "SEP";
		case 10: return "OCT";
		case 11: return "NOV";
		case 12: return "DEC";
		default: return "???";
	}
}

/**
 * getFileSize
 *
 * @param fName - name of file
 * @return size of file or -1 if the file couldn't be found or other error
 */
int getFileSize(TCHAR* fName) {
	FRESULT status;
	FILINFO fno;

	// determine if it exists as a file, as a directory, or doesn't exist at all...
	status = f_stat(fName,&fno);                    // get the status of the file. if the file exists, then FR_OK will be returned otherwise FR_NO_FILE (other return values exists for system issues)

	if (status == FR_OK) {
		return fno.fsize;
	} else {
		dumpError("getFileSize couldn't extract file size: ",status); // report out what went wrong...
	}

	return status;
}
/**
 * deleteFile
 *    displays status message for success or failure attempt
 * @param fName
 * @return 1 for successful deletion, 0 for failure
 */
int deleteFile(TCHAR* fName) {
	FRESULT status;
	FILINFO fno;

    // determine if it exists as a file, as a directory, or doesn't exist at all...
    status = f_stat(fName,&fno);                               // get the status of the file. if the file exists, then FR_OK will be returned otherwise FR_NO_FILE (other return values exists for system issues)
    if (status != FR_OK) {
		dumpError("deleteFile: could not delete file because: ",status);
    } else {                                                   // file exists in some fashion
		// if the file both exists and is a file (as opposed to a directory)
		if (isFile(fName)) {                                    // check if file exists and is a file (not a directory)
			status = f_unlink(fName);                            // unlink (i.e. delete)
			if (status == FR_OK) {
				xil_printf("file deleted successfully\n\r");       // tell the user that the operation failed via stdot
			} else {
				xil_printf("Could not delete file\n\r",status);   // tell the user that the operation failed via stdout and tell them why
			}
		} else {                                                // other errors could occur (FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, etc.)
			dumpError("deleteFile: could not get file information - delete failed: ",status);
		}
    }
    return status;
}

/**
 * openFile
 *    will display success or failure message
 * @param fName - name of file with full path to create
 * @param mode - uses standard POSIX arguments
 * @return SUCCESS (1) for success, FAILURE (0) for failure;
 */
int openFile(TCHAR* fName, char* mode) {
    FRESULT  status;
    BYTE     modeFlags;

    // if the fName refers to a directory, then return as we can't do anything with it here
    if (isDir(fName)) {
		xil_printf("%s is a directory which cannot be handled as a file\n\r");
		return 0;
	}

	// since it is a file, we have to see what the mode is to see how to behave
	modeFlags = convertPOSIX2FA(mode);                   // take the standard POSIX modes (r, w+, etc.) and convert them into a BYTE used by ff

	// enumerate the possible situations:
	u8 open_for_reading = (isFile(fName) == FR_EXIST)   && (modeFlags & FA_READ);   // read occurs only when fname does exist
	u8 open_for_writing = (isFile(fName) == FR_NO_FILE) && (modeFlags & FA_WRITE);   // write occurs only when fname doesn't exist
	u8 open_for_append  = (isFile(fName) == FR_EXIST)   && (modeFlags & FA_OPEN_APPEND); // appending occurs when file exists and append is requested

	// check if this mixture of the file existing and the open options is legal
	if (open_for_reading || open_for_writing || open_for_append) {
		status = f_open(&fObj,fName,modeFlags);
		if (status == FR_OK) {
			xil_printf("openFile: %s opened with %s\n\r",fName,mode);
			return SUCCESS;
		} else {
			xil_printf("openFile: can't open file %s with %s options: ",fName,mode);
			xil_printf(interpretStatus(status));
			xil_printf("\n\r");
		}
	} else {
		xil_printf("openFile: can't open file %s with %s options: ",fName,mode);
		xil_printf("as a read from a non-existent file or a write to an existing file was attempted and aborted\n\r");
	}

	return FAILURE;
}

/**
 * convertPOSIX2FA
 *    converts POSIX standard notation into a BYTE value for this file system
 * @param mode = POSIX notation {r, w, r+, w+, a, etc. }
 * @return appropriately coded BYTE, 0 on error
 */
BYTE convertPOSIX2FA(char* mode) {
	BYTE rtnByte = 0;
	int modeLength = strlen(mode);
	if (modeLength == 0) { return 0; }                    // no mode, return ziltch
	if (mode[0] == 'r') {                                 // deal with the reading stuff
		rtnByte = FA_READ;
		if (mode[1] == '+') { rtnByte |= FA_WRITE; }
	} else if (mode[0] == 'w') {                          // deal with the writing stuff
		rtnByte = FA_CREATE_ALWAYS | FA_WRITE;
		if (mode[1] == '+') { rtnByte |= FA_READ; }
		else if (mode[1] == 'x') { rtnByte = FA_CREATE_NEW | FA_WRITE; }
	} else if (mode[0] == 'a') {                          // deal with the appending stuff
		rtnByte = FA_OPEN_APPEND | FA_WRITE;
		if (mode[1] == '+') { rtnByte |= FA_READ; }
	} else {
		xil_printf("unsupported mode");
	}
	return rtnByte;
}

/**
 * dumpError - quick little routine to handle output formatting for dumping the error to the serial port
 * @param errMsg - specific error message
 * @param status - FR status code which is expended to a more meaningful message
 * @return none
 */
void dumpError(char* errMsg, FRESULT status) {
    xil_printf(errMsg);
    xil_printf(interpretStatus(status));
    xil_printf("\n\r");
}

/**
 * interpretStatus
 * http://elm-chan.org/fsw/ff/doc/open.html for details on many of these return values
 * @param status - FRESULT code
 * @return - char* describing error code
 */
char* interpretStatus (FRESULT status) {
	switch (status) {
		case FR_OK:                  return "no issue with operation"; break;
        case FR_NO_FILE:             return "could not find specified file/directory"; break;
        case FR_DISK_ERR:            return "unrecoverable hard error in lower layer"; break;
        case FR_INT_ERR:             return "assertion failed due to: error in FAT structure, wrong lower layer sw implementation, stack overflow, etc."; break;
        case FR_NOT_READY:           return "device not ready - could be missing medium, wrong hw config, storage device broken, bad lower layer sw implementation"; break;
        case FR_NO_PATH:             return "could not find specified path"; break;
        case FR_INVALID_NAME:        return "bad file/directory name (maybe char encoding, illegal symbol,...)"; break;
        case FR_INVALID_DRIVE:       return "Invalid drive number or null pointer in name"; break;
        case FR_NOT_ENABLED:         return "device not mounted"; break;
        case FR_NO_FILESYSTEM:       return "file system not impressed on memory region or bad lower layer sw"; break;
        case FR_TIMEOUT:             return "operation canceled due to thread-save control"; break;
        case FR_NOT_ENOUGH_CORE:     return "not enough memory to do what was requested"; break;
        case FR_MKFS_ABORTED:        return "can't format with specified parameters, vol < 128 sectors, or partition tied to logical drive not found"; break;
        case FR_LOCKED:              return "operation rejected by file sharing control"; break;
        case FR_TOO_MANY_OPEN_FILES: return "Number of open objects at max - can't open any more until some objects are closed"; break;
        case FR_INVALID_PARAMETER:   return "Parameter invalid or an inconsistency in parameters"; break;
        case FR_WRITE_PROTECTED:     return "Attempted to write to a write-protected area"; break;
        case FR_INVALID_OBJECT:      return "file or directory object invalid or null pointer passed"; break;
        case FR_EXIST:               return "Object of the same name already exists"; break;
        case FR_DENIED:              return "access denied - i.e. attempt to write to a write protected file, read from a file not opened with FA_READ, deleting non-empty directory, etc."; break;
        default:                     return "Unknown status code";
    }
}

//<copyright-disclaimer-start>
//<copyright-disclaimer-start>
//  **************************************************************************************************************
//  * © 2024 Advanced Micro Devices, Inc. All rights reserved.                                                   *
//  * DISCLAIMER                                                                                                 *
//  * The information contained herein is for informational purposes only, and is subject to change              *
//  * without notice. While every precaution has been taken in the preparation of this document, it              *
//  * may contain technical inaccuracies, omissions and typographical errors, and AMD is under no                *
//  * obligation to update or otherwise correct this information.  Advanced Micro Devices, Inc. makes            *
//  * no representations or warranties with respect to the accuracy or completeness of the contents of           *
//  * this document, and assumes no liability of any kind, including the implied warranties of noninfringement,  *
//  * merchantability or fitness for particular purposes, with respect to the operation or use of AMD            *
//  * hardware, software or other products described herein.  No license, including implied or                   *
//  * arising by estoppel, to any intellectual property rights is granted by this document.  Terms and           *
//  * limitations applicable to the purchase or use of AMD’s products are as set forth in a signed agreement     *
//  * between the parties or in AMD's Standard Terms and Conditions of Sale. GD-18                               *
//  *                                                                                                            *
//  **************************************************************************************************************
//<copyright-disclaimer-end>
//<copyright-disclaimer-end>



