/************************************************************************/
/* Copyright 2003-2013 MBARI						*/
/************************************************************************/
/* Summary  : File I/O Utilities for BEDS				*/
/* Filename : fileUtils.c						*/
/* Author   : Robert Herlien (rah)					*/
/* Project  : Benthic Event Detection System (BEDS)			*/
/* Revision : 1.0							*/
/* Created  : 02/25/2013 from Oasis4 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				*/
/************************************************************************/

#include <mbariTypes.h>		/* MBARI type definitions		*/
#include <mbariConst.h>		/* MBARI constants			*/
#include <cfxpico.h>		/* Persistor PicoDOS definitions	*/
#include <beds.h>		/* BEDS general definitions		*/	
#include <fileUtils.h>		/* BEDS file utilities definitions	*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

#define LINEBUFLEN	256

//#define DEBUG_TIMING


/********************************/
/*	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 char	*noFileMsg = "Cannot open file";
MLocal char	*noLinenoMsg = "Missing line number";


/************************************************************************/
/* Function    : dirScan						*/
/* Purpose     : Execute function for every directory entry matching pattern*/
/* Inputs      : Wildcard pattern to match, function ptr, function parm	*/
/* Outputs     : None							*/
/************************************************************************/
MBool dirScan(char *matchStr, MBool (*func)(struct dirent *, void *), void *parm)
{
    struct dirent	de;
    MBool		rtn = TRUE;

    if (matchStr == NULL)
	matchStr = "*.*";

    if (DIRFindFirst(pathstr, &de) != dsdEndOfDir)
    {
	do
	{
	    if (DIRMatchName((char *) de.d_name, matchStr))
		if ((rtn = (*func)(&de, parm)) == FALSE)
		    break;
	} while (DIRFindNext(&de) != dsdEndOfDir);
    }

    DIRFindEnd(&de);
    return(rtn);

} /* dirScan() */


/************************************************************************/
/* 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;
    int		linecnt = 0;

    while ((*displayFunc)(linecnt, parm))
    {
	linecnt++;

	if ((linecnt%numLines) == 0)
	{
	    puts("---More---");
	    ch = SCIRxGetCharWithTimeout(60000);
	    printf("\n");
	    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, FILE *fp)
{
    int		len;
    char	linebuf[LINEBUFLEN];

    if (fgets(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\n", linenum+1, linebuf);
    return(TRUE);
}


/************************************************************************/
/* Function    : MoreCmd						*/
/* Purpose     : Implements user-level "more filename" command		*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char *MoreCmd(CmdInfoPtr cip)
{
    FILE	*fp;

    if ((fp = fopen(cip->argv[1].str, "r")) == NULL)
	return(noFileMsg);

    printf("\n");
    more(23, moreCmdf, fp);
    fclose(fp);
    return(NULL);
}


/************************************************************************/
/* Function    : TailCmd						*/
/* Purpose     : Implements user-level "tail <n> filename" command	*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char *TailCmd(CmdInfoPtr cip)
{
    FILE	*fp;
    Int32	linecnt, skip, i;
    char	linebuf[LINEBUFLEN];

    if ((cip->argc < 3) || (!cip->argv[1].isval))
    {
	printf("\n%s [n] <filename>\n", cip->argv[0].str);
	return(NULL);
    }

    if ((fp = fopen(cip->argv[2].str, "r")) == NULL)
	return(noFileMsg);

    
    printf("\n");
    for (linecnt = 0; fgets(linebuf, LINEBUFLEN, fp) != NULL; )
	linecnt++;

    rewind(fp);
    if ((i = cip->argv[1].value) < 0)
	i = -i;
    if ((skip = linecnt - i) < 0)
	skip = 0;

    for (i = 0; i < skip; i++)
	fgets(linebuf, LINEBUFLEN, fp);

    while (fgets(linebuf, LINEBUFLEN, fp) != NULL)
    {
	/* Remove line terminator.  We'll replace with \n	*/
	if ((i = strcspn(linebuf, "\n\r")) >= 0)
	    linebuf[i] = '\0';

	printf("%s\n", linebuf);
    }

    fclose(fp);
    return(NULL);

} /* TailCmd() */


/************************************************************************/
/* 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);
	unlink(newName);
	rename(oldName, newName);
    }

    /* oldName now contains most recent backup name, so rename <fileName> to oldName*/
    rename(fileName, oldName);

} /* backupFile() */


/************************************************************************/
/* Function    : trueFunc						*/
/* Purpose     : Function returning TRUE				*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/* Comments    : Useful for passing as a compare function		*/
/************************************************************************/
MBool trueFunc(void)
{
    return(TRUE);
}


/************************************************************************/
/* 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(FILE *fp, FileRcdType rtype, RcdUnion *recp)
{
    int		recType;
    Byte	recBuf[64];

    if ((rtype < FileHdrType) || (rtype > SysRecType))
	return(FALSE);

    while((recType = fgetc(fp)) != EOF)
    {
	if (recType == rtype)
	{
	    recp->index.recType = recType;
	    fread(&recp->index.rsvd, rcdSizes[recType]-1, 1, fp);
	    return(TRUE);
	}
	else if ((recType >= FileHdrType) && (recType <= SysRecType))
	    fread(recBuf, rcdSizes[recType]-1, 1, fp);
    }
    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(FILE *fp, RcdUnion *recp)
{
    int		recType;

    while((recType = fgetc(fp)) != EOF)
    {
	if ((recType >= FileHdrType) && (recType <= SysRecType))
	{
	    recp->index.recType = recType;
	    fread((Byte *)recp + 1, rcdSizes[recType]-1, 1, fp);
	    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--)
		LoopModeMemCopyBytes(src+1, src, sizeof(IdxRcd));

	    break;
	}
    }

    LoopModeMemCopyBytes(idxp, idxToInsert, sizeof(IdxRcd));
    ip->entries += 1;
}


/************************************************************************/
/* 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)())
{
    FILE	*fp;
    IndexStruct	*ip;
    IdxRcd	idxRec;
    struct stat st;
#ifdef DEBUG_TIMING
    RTCTimer	tmr;
    ulong	elapsed;

    RTCElapsedTimerSetup(&tmr);
#endif
    if (stat(fname, &st) < 0)
	return(NULL);

    if ((ip = calloc((st.st_size+sizeof(IndexStruct))/sizeof(Nat32), sizeof(Nat32))) == NULL)
	return(NULL);

    if ((fp = fopen(fname, "r")) == NULL)
	return(NULL);

    ip->entries = 0;

    while (getNextRec(fp, IndexType, &idxRec))
	insertIndexRec(ip, &idxRec, compareFunc);

    fclose(fp);
#ifdef DEBUG_TIMING
    elapsed = RTCElapsedTime(&tmr);
    printf("\nSort elapsed time = %01ld.%03ld seconds\n",
	   elapsed/1000000, (elapsed%1000000)/1000);
#endif

    return(ip);

} /* createSortedIndex() */


/************************************************************************/
/* Function    : lineEdit						*/
/* Purpose     : Line editor function					*/
/* Inputs      : File and line number to edit, function to do editing	*/
/* Outputs     : OK or ERROR						*/
/************************************************************************/
MLocal char *lineEdit(char *fileName, Nat32 linenum, Nat32 parm, Void (*linefunc)())
{
    FILE	*rfp, *wfp;
    Int16	linecnt;
    char	*p;
    char	linebuf[LINEBUFLEN];

    if ((rfp = fopen(fileName, "r")) == NULL)
	return(noFileMsg);

    if ((wfp = fopen(tmpFileName, "w")) == NULL)
	return(noFileMsg);

    for (linecnt = 1; (linecnt < linenum) && 
	     (fgets(linebuf, sizeof(linebuf), rfp) != NULL); linecnt++)
    {
	fputs(linebuf, wfp);
    }
    
    (*linefunc)(rfp, wfp, parm, linebuf);

    while (fgets(linebuf, sizeof(linebuf), rfp) != NULL)
	fputs(linebuf, wfp);

    fclose(rfp);
    fclose(wfp);

    strncpy(linebuf, fileName, sizeof(linebuf));
    if ((p = strchr(linebuf, '.')) == NULL)
	p = linebuf + strlen(linebuf);
    strcpy(p, ".BAK");

    remove(linebuf);
    rename(fileName, linebuf);
    remove(fileName);
    rename(tmpFileName, fileName);

    return(NULL);

} /* 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(FILE *rfp, FILE *, Nat32 , char linebuf[])
{
    fgets(linebuf, LINEBUFLEN, rfp);
}


MLocal Void delChrFunc(FILE *rfp, FILE *, Nat32 parm, char [])
{
    Nat32	i;
    for (i = 0; i < parm; i++)
	fgetc(rfp);
}

MLocal Void insertLineFunc(FILE *rfp, FILE *wfp, Nat32 linenum, char linebuf[])
{
    long rpos = ftell(rfp);

    fgets(linebuf, LINEBUFLEN, rfp);
    printf("\n%04ld: %s", linenum+1, linebuf);
    fseek(rfp, rpos, SEEK_SET);
    printf("%04ld:  ", linenum);
    CIOgets(linebuf, LINEBUFLEN);
    fprintf(wfp, "%s\r\n", linebuf);
}


MLocal Void insertChrFunc(FILE *rfp, FILE *wfp, Nat32 linenum, char linebuf[])
{
    long rpos = ftell(rfp);

    fgets(linebuf, LINEBUFLEN, rfp);
    printf("\n%04ld: %s", linenum, linebuf);
    fseek(rfp, rpos, SEEK_SET);
    printf("%04ld: ", linenum);
    CIOgets(linebuf, LINEBUFLEN);
    fputs(linebuf, wfp);
}


/************************************************************************/
/* Function    : lineFuncUsage						*/
/* Purpose     : Print usage message for line editor funcs		*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : noLineNoMsg						*/
/************************************************************************/
MLocal char *lineFuncUsage(CmdInfoPtr cip)
{
    printf("\n%s <lineno> <filename>\n", cip->argv[0].str);
    return(noLinenoMsg);
}


/************************************************************************/
/* Function    : deleteLine						*/
/* Purpose     : Delete a line in a file				*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char *deleteLine(CmdInfoPtr cip)
{
    if ((cip->argc < 3) || (!cip->argv[1].isval))
	return(lineFuncUsage(cip));

    return(lineEdit(cip->argv[2].str, cip->argv[1].value, 1, delLineFunc));

} /* deleteLine() */


/************************************************************************/
/* Function    : deleteChars						*/
/* Purpose     : Delete chars from a line in a file			*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char *deleteChars(CmdInfoPtr cip)
{
    Nat32 numChars = 1;

    if ((cip->argc < 3) || (!cip->argv[1].isval))
    {
	printf("%s <lineno> <filename> [numChars]\n", cip->argv[0].str);
	return(noLinenoMsg);
    }

    if ((cip->argc > 3) && (cip->argv[3].isval))
	numChars = cip->argv[3].value;

    return(lineEdit(cip->argv[2].str, cip->argv[1].value, numChars, delChrFunc));

} /* deleteChars() */


/************************************************************************/
/* Function    : insertLine						*/
/* Purpose     : Insert a line in a file				*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char	*insertLine(CmdInfoPtr cip)
{
    if ((cip->argc < 3) || (!cip->argv[1].isval))
	return(lineFuncUsage(cip));

    return(lineEdit(cip->argv[2].str, cip->argv[1].value,
		    cip->argv[1].value, insertLineFunc));

} /* insertLine() */



/************************************************************************/
/* Function    : insertChars						*/
/* Purpose     : Insert chars at the front of a line			*/
/* Inputs      : CmdInfoPtr						*/
/* Outputs     : NULL if OK, else error message				*/
/************************************************************************/
char	*insertChars(CmdInfoPtr cip)
{
    if ((cip->argc < 3) || (!cip->argv[1].isval))
	return(lineFuncUsage(cip));

    return(lineEdit(cip->argv[2].str, cip->argv[1].value,
		    cip->argv[1].value, insertChrFunc));


} /* insertChars() */
