/************************************************************************/
/* Copyright 2003-2018 MBARI											*/
/************************************************************************/
/* Summary	: File I/O Utilities for BEDS2 on PIC32MX470 using FatFs	*/
/* Filename : fileUtils.c												*/
/* Author	: Robert Herlien (rah)										*/
/* Project	: Benthic Event Detection System (BEDS)						*/
/* Revision : 1.0														*/
/* Created	: 03/02/2018 from Oasis5 file.c								*/
/*																		*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																		*/
/************************************************************************/
/* Modification History:												*/
/* 25feb2013 rah - created from Oasis4 file.c							*/
/* 02mar2018 rah - ported to BEDS2										*/
/************************************************************************/

#include <mbariTypes.h>			/* MBARI type definitions				*/
#include <beds.h>				/* BEDS general definitions				*/		
#include <fatFs/ff.h>			/* FatFs file system defns				*/
#include <file.h>				/* BEDS File I/O Routines				*/
#include <fileUtils.h>			/* BEDS file utilities definitions		*/
#include <serial.h>				/* BEDS serial I/O						*/
#include <utils.h>				/* BEDS utility functions				*/
#include <clock.h>				/* BEDS Clock module					*/
#include <utils.h>				/* BEDS Utility routines				*/

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

typedef struct
{
	Nat32		fileKB;
	Nat32		filecount;
	Nat32		dircount;
} DirStruct;

#define LINEBUFLEN		256
#define EDIT_TMOUT		120


/********************************/
/*		External Data			*/
/********************************/

Extern Int32	xoff_tmout;				/* XOFF timeout in seconds		*/


/********************************/
/*		Global Data				*/
/********************************/

Nat16 rcdSizes[] = 
{
	sizeof(FileHdr), sizeof(SecondMarker), sizeof(InertialRcd),
	sizeof(PressureRcd), sizeof(IdxRcd), sizeof(SystemRcd)
};


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal char		*pathstr = FILEPATH;
MLocal char		*tmpFileName = "$$$$$$$$.TMP";
MLocal FATFS	fatfs;


/************************************************************************/
/* Function    : fileGetFreeSpace										*/
/* Purpose     : Get disk free space in KB								*/
/* Inputs      : None													*/
/* Outputs     : Disk free space in KB									*/
/************************************************************************/
Nat32 fileGetFreeSpace(void)
{
	FRESULT	frtn;
	DWORD	freeClust;
	FATFS	*fsp;

	frtn = f_getfree(pathstr, &freeClust, &fsp);

	return((frtn == FR_OK) ? (freeClust * fsp->csize / 2) : 0);

} /* fileGetFreeSpace() */


/************************************************************************/
/* Function	   : backupFile												*/
/* Purpose	   : Create <BACKUP_FILES> number of backups for file		*/
/* Inputs	   : Name of file to backup, names of backup files			*/
/* Outputs	   : None													*/
/* Comments	   : backupName should include a "%d" or similar for backup number*/
/************************************************************************/
void backupFile(const char *fileName, const char *backupName)
{
	int			i;
	char		oldName[32], newName[32];

	for (i = BACKUP_FILES-1; i; i--)
	{
		sprintf(newName, backupName, i+1);
		sprintf(oldName, backupName, i);
		f_unlink(newName);
		f_rename(oldName, newName);
	}

	/* oldName now contains most recent backup name, so rename <fileName> to oldName*/
	f_rename(fileName, oldName);

} /* backupFile() */


/************************************************************************/
/* Function	   : dirScan												*/
/* Purpose	   : Execute function for every directory entry matching pattern*/
/* Inputs	   : Wildcard pattern to match, function ptr, function parm */
/* Outputs	   : Number of matching files found							*/
/************************************************************************/
Int32 dirScan(char *matchStr, MBool (*func)(FILINFO *, void *), void *parm)
{
	DIR		dirent;
	FILINFO finfo;
	FRESULT fres;
	MBool	keepGoing;
	Int32	nfound = 0;

	if (matchStr == NULL)
		matchStr = "*";
	
	fres = f_findfirst(&dirent, &finfo, pathstr, matchStr);

	for (keepGoing = TRUE; keepGoing && (fres == FR_OK) && finfo.fname[0]; )
	{
		nfound++;
		keepGoing = (*func)(&finfo, parm);
		fres = f_findnext(&dirent, &finfo);
	}

	f_closedir(&dirent);
	return(nfound);

} /* dirScan() */


/************************************************************************/
/* Function	   : fileOpen												*/
/* Purpose	   : f_open a file											*/
/* Inputs	   : File struct, file name, open mode						*/
/* Outputs	   : FRESULT												*/
/* Comment	   : Assumes caller owns fileSem							*/
/************************************************************************/
MLocal FRESULT fileOpen(FIL *fp, const char *name, BYTE mode)
{
	FRESULT	fres;

	if ((fres = f_open(fp, name, mode)) == FR_OK)
		return(FR_OK);

	printf("Could not open ");
	fperr(fres, name);

	return(fres);

} /* fileOpen() */


/************************************************************************/
/* Function	   : setFileTime											*/
/* Purpose	   : f_open a file											*/
/* Inputs	   : File struct, file name, open mode						*/
/* Outputs	   : FRESULT												*/
/* Comment	   : Assumes caller owns fileSem							*/
/************************************************************************/
FRESULT setFileTime(char *fname, time_t ftime)
{
	struct tm	*tp;
	FILINFO		finfo;
	FRESULT		fres;

	if ((tp = localtime(&ftime)) == NULL)
		return(FR_INVALID_PARAMETER);

	memset(&finfo, 0, sizeof(FILINFO));

	finfo.fdate = (WORD)((((tp->tm_year - 80) << 9) & 0xfe00) |
						 (((tp->tm_mon + 1) << 5) & 0x1e0) |
						 (tp->tm_mday & 0x1f));
	finfo.ftime = (WORD)(((tp->tm_hour << 11) & 0xf800) | 
						 ((tp->tm_min << 5) & 0x7e0) |
						 (tp->tm_sec/2 & 0x1f));
	fres = f_utime(fname, &finfo);
	return(fres);
}


/************************************************************************/
/* Function	   : magnitudeFunc											*/
/* Purpose	   : Function comparing two Index entry magnitudes			*/
/* Inputs	   : Two IdxRcd pointers									*/
/* Outputs	   : TRUE if mag1 > mag2									*/
/************************************************************************/
MBool magnitudeFunc(IdxRcd *idx1, IdxRcd *idx2)
{
	return(idx1->maxVal > idx2->maxVal);
}


/************************************************************************/
/* Function	   : getNextRec												*/
/* Purpose	   : Get next record of any BEDS-formatted file				*/
/* Inputs	   : Open file, record type to look for, place for record	*/
/* Outputs	   : TRUE if found a record of type rtype					*/
/************************************************************************/
MBool getNextRec(FIL *fp, FileRcdType rtype, RcdUnion *recp)
{
	Byte		recType;
	Nat32		br, btr;
	Byte		recBuf[64];

	if ((rtype < FileHdrType) || (rtype > SysRecType))
		return(FALSE);

	while((f_read(fp, &recType, 1, &br) == FR_OK) && (br >= 1))
	{
		if (recType == rtype)
		{
			recp->index.recType = recType;
			btr = rcdSizes[recType]-1;
			if ((f_read(fp, &recp->index.rsvd, btr, &br) == FR_OK) &&
				(br == btr))
				return(TRUE);
			else
				return(FALSE);
		}
		else if ((recType >= FileHdrType) && (recType <= SysRecType))
			f_read(fp, recBuf, rcdSizes[recType]-1, &br);
	}
	return(FALSE);
}


/************************************************************************/
/* Function	   : getAnyRec												*/
/* Purpose	   : Get next record of any BEDS-formatted file				*/
/* Inputs	   : Open file, place for record							*/
/* Outputs	   : TRUE if found any record								*/
/* Comment	   : recp buffer must be large enough to hold any record type*/
/************************************************************************/
MBool getAnyRec(FIL *fp, RcdUnion *recp)
{
	int			recType;
	Nat32		br;

	while((f_read(fp, &recType, 1, &br) == FR_OK) && (br >= 1))
	{
		if ((recType >= FileHdrType) && (recType <= SysRecType))
		{
			recp->index.recType = recType;
			f_read(fp, (Byte *)recp + 1, rcdSizes[recType]-1, &br);
			return(TRUE);
		}
	}

	return(FALSE);
}

/************************************************************************/
/* Function	   : insertIndexRec											*/
/* Purpose	   : Insert Index record into temp file						*/
/* Inputs	   : IndexStruct, record to insert, compare func for insertion*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void insertIndexRec(IndexStruct *ip, IdxRcd *idxToInsert, MBool (*compareFunc)())
{
	IdxRcd		*idxp, *src;
	int			i, j;

	for (i = 0, idxp = ip->index; i < ip->entries; i++, idxp++)
	{
		if ((*compareFunc)(idxToInsert, idxp))
		{
			src = &ip->index[ip->entries-1];
			for (j = ip->entries-1; j >= i; j--, src--)
				memcpy(src+1, src, sizeof(IdxRcd));
			break;
		}
	}

	memcpy(idxp, idxToInsert, sizeof(IdxRcd));
	ip->entries += 1;
}


/************************************************************************/
/* CAUTION on all the usrXxx commands that allocate a FIL structure		*/
/* on the stack -- This is only benign when ffconf.h defines FF_FS_TINY	*/
/* to be 1.  If _FS_TINY == 0, the FIL structure becomes 512 bytes larger,*/
/* which may be enough to blow the stack.								*/
/************************************************************************/

/************************************************************************/
/* Function	   : createSortedIndex										*/
/* Purpose	   : Sort index records in file fname, return sorted index	*/
/* Inputs	   : File name to sort, function for sorting entries		*/
/* Outputs	   : Allocated and filled-in IndexStruct					*/
/* Comments	   : Calling function must free() the IndexStruct when done */
/************************************************************************/
IndexStruct *createSortedIndex(char *fname, MBool (*compareFunc)())
{
	FIL			fp;
	IndexStruct *ip;
	IdxRcd		idxRec;
	FILINFO		finfo;

	if (f_stat(fname, &finfo) != FR_OK)
		return(NULL);

	if ((ip = calloc((finfo.fsize+sizeof(IndexStruct))/sizeof(Nat32), sizeof(Nat32))) == NULL)
		return(NULL);

	if (f_open(&fp, fname, FA_READ) != FR_OK)
		return(NULL);

	ip->entries = 0;

	while (getNextRec(&fp, IndexType, (RcdUnion *)&idxRec))
		insertIndexRec(ip, &idxRec, compareFunc);

	f_close(&fp);
	return(ip);

} /* createSortedIndex() */


/************************************************************************/
/* Function	   : usrTail												*/
/* Purpose	   : Implements user-level "tail <n> filename" command		*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : NULL if OK, else error message							*/
/************************************************************************/
CmdRtn usrTail(int argc, char **argv)
{
	FIL			fp;
	Int32		linecnt, skip, i, tailNum = 10;
	char		*fname;
	char		linebuf[LINEBUFLEN];

	if (argc < 2)
	{
		printf("\r\n%s [n] <filename>\r\n", argv[0]);
		return(ERROR);
	}

	if (argc > 2)
	{
		tailNum = atoi(argv[1]);
		fname = argv[2];
	}
	else
		fname = argv[1];

	if (fileOpen(&fp, fname, FA_READ) != FR_OK)
		return(ERROR);

	newline();

	for (linecnt = 0; f_gets(linebuf, LINEBUFLEN, &fp) == linebuf; )
		linecnt++;

	f_lseek(&fp, 0);

	if ((skip = linecnt - tailNum) < 0)
		skip = 0;

	for (i = 0; i < skip; i++)
		f_gets(linebuf, LINEBUFLEN, &fp);

	while (f_gets(linebuf, LINEBUFLEN, &fp) == linebuf)
	{
		/* Remove line terminator.	We'll replace with \n		*/
		if ((i = strcspn(linebuf, "\n\r")) >= 0)
			linebuf[i] = '\0';

		printf("%s\r\n", linebuf);
	}

	f_close(&fp);
	return(OK);

} /* usrTail() */


/************************************************************************/
/* Function	   : usrType												*/
/* Purpose	   : Implements user-level "type filename" command			*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrType(int argc, char **argv)
{
	FIL		fp;
	int		i, ch;
	SerPort	port;
	Nat32	xoff_start;
	char	inbuf[32];

	if (argc < 2)
		return(ERROR);

	if (fileOpen(&fp, argv[1], FA_READ) != FR_OK)
		return(ERROR);

	port = getConsole();

	while (f_gets(inbuf, sizeof(inbuf), &fp) != NULL)
	{
		for (i = 0; i < strlen(inbuf); )
			if (ser_putc(port, inbuf[i]))
				i++;

		if ((ch = ser_getc(port)) == XOFF)
		{
			xoff_start = clkGetTick();
			while ((ch = ser_getc(port)) != XON)
			{	
				if (ch != ERROR)
					return(OK);
				
				if ((clkGetTick() - xoff_start) >= (xoff_tmout * TICKS_PER_SEC))
				{
					printf("TIMEOUT\r\n");
					return(OK);
				}
			}
		}
		else if (ch != ERROR)
			return(OK);
	}

	f_close(&fp);
	return(OK);

} /* usrType() */


/************************************************************************/
/* Function	   : more													*/
/* Purpose	   : Performs "more"-like display for any display function	*/
/* Inputs	   : Num lines per "screen", display function ptr, parm for func*/
/* Outputs	   : None													*/
/************************************************************************/
void more(int numLines, MBool (*displayFunc)(), void *parm)
{
	short		ch;
	SerPort		port = getConsole();
	int			linecnt = 0;

	if (numLines < 1)
		numLines = 1;

	while ((*displayFunc)(linecnt, parm))
	{
		linecnt++;

		if ((linecnt%numLines) == 0)
		{
			printf("---More---");
			ch = ser_getc_tmout(port, xoff_tmout*TICKS_PER_SEC);
			newline();
			if ((toupper(ch) == 'Q') || (toupper(ch) == 'X'))
				return;
		}		
	}

} /* more() */


/************************************************************************/
/* Function	   : moreCmdf												*/
/* Purpose	   : Helper function for MoreCmd() passed to more()			*/
/* Inputs	   : Line number, file to 'more'							*/
/* Outputs	   : TRUE if more lines										*/
/************************************************************************/
static MBool moreCmdf(int linenum, FIL *fp)
{
	int			len;
	char		linebuf[LINEBUFLEN];

	if (f_gets(linebuf, LINEBUFLEN, fp) == NULL)
		return(FALSE);
	
	/* Remove line terminator.	We'll replace with \n	*/
	if ((len = strcspn(linebuf, "\n\r")) >= 0)
		linebuf[len] = '\0';

	printf("%04hd: %s\r\n", linenum+1, linebuf);
	return(TRUE);
}


/************************************************************************/
/* Function	   : usrMore												*/
/* Purpose	   : Implements user-level "more filename" command			*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrMore(int argc, char **argv)
{
	FIL		fp;

	if (argc < 2)
		return(ERROR);

	if (fileOpen(&fp, argv[1], FA_READ) != FR_OK)
		return(ERROR);

	newline();
	more(23, moreCmdf, &fp);
	f_close(&fp);
	return(OK);
}

/************************************************************************/
/* Function	   : usrCopy												*/
/* Purpose	   : Implements user-level file copy command				*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn usrCopy(int argc, char **argv)
{
	FIL		srcFp, dstFp;
	char	inbuf[32];
	UINT	br, bw;
	FRESULT	fr;

	if (argc < 3)
		return(ERROR);

	if (fileOpen(&srcFp, argv[1], FA_READ) == FR_OK)
	{
		if (fileOpen(&dstFp, argv[2],
					 (BYTE)(FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_WRITE)) == FR_OK)
		{
			do
			{
				if ((fr = f_read(&srcFp, inbuf, 32, &br)) != FR_OK)
				{
					fperr(fr, "Error in file copy\r\n");
					break;
				}

				if ((fr = f_write(&dstFp, inbuf, br, &bw)) != FR_OK)
				{
					fperr(fr, "Error in file copy\r\n");
					break;
				}

			} while ((bw == br) && (br > 0));

			f_close(&dstFp);
		}

		f_close(&srcFp);
	}

	return(OK);

} /* usrCopy() */


/************************************************************************/
/* Function	   : usrRename												*/
/* Purpose	   : Implements user-level file rename command				*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn usrRename(int argc, char **argv)
{
	FRESULT rtn;

	if (argc < 3)
		return(ERROR);

	if ((rtn = f_rename(argv[1], argv[2])) != FR_OK)
		fperr(rtn, "Error in rename");

	return((rtn == FR_OK) ? OK : ERROR);

} /* usrRename() */


/************************************************************************/
/* Function	   : dirFileDelete											*/
/* Purpose	   : Function passed to dirScan for "DEL" command			*/
/* Inputs	   : Directory entry ptr, unused ptr						*/
/* Outputs	   : TRUE (to continue the scan)							*/
/************************************************************************/
MBool dirFileDelete(FILINFO *de, void *p)
{
	FRESULT rtn;

	if ((de->fattrib & (AM_DIR | AM_VOL)) == 0)
		if ((rtn = f_unlink(de->fname)) == FR_OK)
		{
			if (p)
				(*(Nat32 *)p)++;
		}
		else
			fperr(rtn, "Error in delete");

	return(TRUE);
}

/************************************************************************/
/* Function	   : usrDelete												*/
/* Purpose	   : Implements user-level file delete						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn usrDelete(int argc, char **argv)
{
	Nat32	i, filesDeleted = 0;

	if (argc < 2)
		return(ERROR);

	for (i = 1; i < argc; i++)
		dirScan(argv[i], dirFileDelete, &filesDeleted);
	
	printf("%u file(s) deleted\r\n", filesDeleted);
	return(OK);

} /* usrDelete() */


/************************************************************************/
/* Function	   : usrChdir												*/
/* Purpose	   : Change working directory								*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrChdir(int argc, char **argv)
{
	if (argc >= 2)
		f_chdir(argv[1]);
	return(OK);
}


/************************************************************************/
/* Function	   : usrPwd													*/
/* Purpose	   : Show working directory									*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrPwd(int argc, char **argv)
{
	char		pwdBuf[32];

	if (f_getcwd(pwdBuf, sizeof(pwdBuf)) != FR_OK)
		return(ERROR);
	
	printf("%s\r\n", pwdBuf);
	return(OK);	
}


/************************************************************************/
/* Function	   : usrMkdir												*/
/* Purpose	   : Make new directory										*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrMkdir(int argc, char **argv)
{
	return(f_mkdir(argv[1]) == FR_OK ? OK : ERROR);
}


/************************************************************************/
/* Function	   : usrHexDump												*/
/* Purpose	   : Implements user-level "hexdump filename" command		*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn usrHexDump(int argc, char **argv)
{
	FIL			fp;
	Nat32		pos, endPos, rtn, i;
	SerPort		port;
	FRESULT		fr;
	Byte		buf[16];

	if (argc < 2)
		return(ERROR);

	fr = fileOpen(&fp, argv[1], FA_READ);

	pos = 0;
	rtn = 16;
	port = getConsole();

	if (argc >= 3)
	{
		pos = atoi(argv[2]);
		if (fr == FR_OK)
			if ((fr = f_lseek(&fp, pos)) != FR_OK)
				rtn = 0;
	}
	
	if (fr != FR_OK)
	{
		fperr(fr, NULL);
		return(ERROR);
	}

	endPos = (argc >= 4) ? pos + atoi(argv[3]) : NAT32_MAX;

	while ((rtn > 0) && (pos < endPos))
	{
		f_read(&fp, buf, 16, &rtn);

		printf("%08x  ", pos);
  
		for (i = 0; i < rtn; i++)
			printf("%02x ", buf[i]);

		ser_putc(port, ' ');

		for (i = 0; i < rtn; i++)
			ser_putc(port, (isprint(buf[i]) ? buf[i] : '.'));
		
		newline();
		if ( ser_getc(port) != ERROR )
			break;
		pos += rtn;
	}

	f_close(&fp);
	return(OK);

} /* usrHexDump() */


/************************************************************************/
/* Function	   : dirFunc												*/
/* Purpose	   : Function passed to dirScan for "DIR" command			*/
/* Inputs	   : Directory entry ptr, DirStruct ptr						*/
/* Outputs	   : TRUE (to continue the scan)							*/
/************************************************************************/
MLocal MBool dirFunc(FILINFO *finfo, void *parm)
{
	DirStruct	*ds = (DirStruct *)parm;

	if ((finfo->fattrib & (AM_VOL | AM_HID)) != 0)
		return(TRUE);

	if (finfo->fattrib & AM_DIR)
		ds->dircount++;
	else
	{
		ds->filecount++;
		ds->fileKB += (finfo->fsize+1023) / 1024;
	}
								
	printf("%-14s", finfo->fname);
		
	if (finfo->fattrib & AM_DIR)
		printf("  <DIR>         ");
	else
		printf("%14u  ", finfo->fsize);

	printf("%04d/%02d/%02d %02u:%02u:%02u\r\n",
		   (finfo->fdate>>9 & 0x7f) + 1980,
		   (finfo->fdate>>5 & 0x0f),
		   finfo->fdate & 0x1f,
		   (finfo->ftime>>11) & 0x1f,
		   (finfo->ftime>>5) & 0x3f,
		   (finfo->ftime & 0x1f) << 1);

	return(kbhit() == 0);
}

/************************************************************************/
/* Function	   : usrDir													*/
/* Purpose	   : Get directory on flash file system						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK														*/
/************************************************************************/
CmdRtn usrDir(int argc, char **argv)
{
	FRESULT		fr;
	int			i;
	DWORD		volid;
	DirStruct	dirStruct;
	char		volname[32];

	fr = f_getlabel("", volname, &volid);

	if (fr == FR_OK)
		printf("Volume label is %s, Serial number is %u\r\n",
			   volname, volid);

	printf(" Directory of %s\r\n\r\n", pathstr);

	memset(&dirStruct, 0, sizeof(dirStruct));

	if (argc < 2)
		dirScan("*", dirFunc, &dirStruct);
	else for (i = 0; i < argc; i++)
			 dirScan(argv[i], dirFunc, &dirStruct);

	printf("%8u file(s) %14u KB\r\n", dirStruct.filecount, dirStruct.fileKB);

	printf("%8u dir(s)	 %14u KB free\r\n",
			dirStruct.dircount, fileGetFreeSpace());

	return(OK);

} /* usrDir() */


/************************************************************************/
/* Function	   : lineEdit												*/
/* Purpose	   : Line editor function									*/
/* Inputs	   : File and line number to edit, function to do editing	*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal CmdRtn lineEdit(char *fileName, Nat32 linenum, Nat32 parm, void (*linefunc)())
{
	FIL			rfp, wfp;
	Int16		linecnt;
	char		*p;
	char		linebuf[LINEBUFLEN];

	if (fileOpen(&rfp, fileName, FA_READ) != FR_OK)
		return(ERROR);

	if (fileOpen(&wfp, tmpFileName,
				 (BYTE)(FA_CREATE_ALWAYS | FA_WRITE)) != FR_OK)
	{
		f_close(&rfp);
		return(ERROR);
	}

	for (linecnt = 1; (linecnt < linenum) && 
			 (f_gets(linebuf, LINEBUFLEN, &rfp) != NULL); linecnt++)
	{
		f_puts(linebuf, &wfp);
	}
	
	(*linefunc)(&rfp, &wfp, parm, linebuf);

	while (f_gets(linebuf, sizeof(linebuf), &rfp) != NULL)
		f_puts(linebuf, &wfp);

	f_close(&rfp);
	f_close(&wfp);

	strncpy(linebuf, fileName, LINEBUFLEN);
	if ((p = strchr(linebuf, '.')) == NULL)
		p = linebuf + strlen(linebuf);
	strcpy(p, ".BAK");

	f_unlink(linebuf);
	f_rename(fileName, linebuf);
	f_unlink(fileName);
	f_rename(tmpFileName, fileName);

	return(OK);

} /* lineEdit() */


/************************************************************************/
/* Function	   : delLineFunc, delChrFunc, insertLIneFunc, insertChrFunc */
/* Purpose	   : Functions passed to lineEdit()							*/
/* Inputs	   : Read file ptr, write file ptr, parm, line buffer		*/
/* Outputs	   : None													*/
/************************************************************************/
MLocal void delLineFunc(FIL *rfp, FIL *wfp, Nat32 parm, char linebuf[])
{
	f_gets(linebuf, LINEBUFLEN, rfp);
}


MLocal void delChrFunc(FIL *rfp, FILE *wfp, Nat32 parm, char linebuf[])
{
	UINT		br;
	f_read(rfp, linebuf, parm, &br);
}

MLocal void insertLineFunc(FIL *rfp, FIL *wfp, Nat32 linenum, char linebuf[])
{
	FSIZE_t rpos = f_tell(rfp);

	f_gets(linebuf, LINEBUFLEN, rfp);
	printf("%04u: %s", linenum+1, linebuf);
	f_lseek(rfp, rpos);
	printf("%04u:	 ", linenum);
	gets_lpt(linebuf, LINEBUFLEN);
	f_printf(wfp, "%s\r\n", linebuf);
}


MLocal void insertChrFunc(FIL *rfp, FIL *wfp, Nat32 linenum, char linebuf[])
{
	FSIZE_t rpos = f_tell(rfp);

	f_gets(linebuf, LINEBUFLEN, rfp);
	printf("%04u: %s", linenum, linebuf);
	f_lseek(rfp, rpos);
	printf("%04u: ", linenum);
	gets_lpt(linebuf, LINEBUFLEN);
	f_puts(linebuf, wfp);
}


/************************************************************************/
/* Function	   : deleteLine												*/
/* Purpose	   : Delete a line in a file								*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn deleteLine(int argc, char **argv)
{
	if (argc < 3)
		return(ERROR);

	return(lineEdit(argv[2], atoi(argv[1]), 1, delLineFunc));

} /* deleteLine() */


/************************************************************************/
/* Function	   : deleteChars											*/
/* Purpose	   : Delete chars from a line in a file						*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn deleteChars(int argc, char **argv)
{
	Nat32 numChars = 1;

	if (argc < 3)
		return(ERROR);

	if (argc > 3)
		numChars = atoi(argv[3]);

	return(lineEdit(argv[2], atoi(argv[1]), numChars, delChrFunc));

} /* deleteChars() */


/************************************************************************/
/* Function	   : insertLine												*/
/* Purpose	   : Insert a line in a file								*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn insertLine(int argc, char **argv)
{
	if (argc < 3)
		return(ERROR);

	return(lineEdit(argv[2], atoi(argv[1]), atoi(argv[1]), insertLineFunc));

} /* insertLine() */



/************************************************************************/
/* Function	   : insertChars											*/
/* Purpose	   : Insert chars at the front of a line					*/
/* Inputs	   : argc, argv												*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
CmdRtn insertChars(int argc, char **argv)
{
	if (argc < 3)
		return(ERROR);

	return(lineEdit(argv[2], atoi(argv[1]), atoi(argv[1]), insertChrFunc));

} /* insertChars() */
