/****************************************************************************/
/* Copyright 2012-2018 MBARI												*/
/****************************************************************************/
/* Summary	: File I/O routines for BEDS2 on PIC32MX470 using FatFs			*/
/* Filename : file.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: BEDS (Benthic Event Detection System)							*/
/* Revision : 1.0															*/
/* Created	: 10/02/2012													*/
/****************************************************************************/
/* Modification History:													*/
/* 02oct2012 rah - created													*/
/* 23feb2018 rah - Ported to BEDS2 using FatFs								*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>			/* MBARI type definitions				*/
#include <beds.h>				/* BEDS general definitions				*/
#include <file.h>				/* BEDS file I/O definitions			*/
#include <clock.h>				/* BEDS Clock module					*/
#include <syslog.h>				/* System logger						*/
#include <fatFs/ff.h>			/* FatFs definitions					*/

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <errno.h>


/********************************/
/*		External Data			*/
/********************************/

Extern Int32	platformID;
Extern Int32	deploymentID;


/********************************/
/*		Global Data				*/
/********************************/

Global const char		*watchIdxName = "WATCH.IDX";
Global const char		*eventIdxName = "EVENT.IDX";
Global MBool			sdOK = TRUE;


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal FATFS	fatfs;
MLocal FIL		idxFil;
MLocal char *ferrStr[] =
    { "FR_OK", "FR_DISK_ERR", "FR_INT_ERR", "FR_NOT_READY", "FR_NO_FILE",
      "FR_NO_PATH", "FR_INVALID_NAME", "FR_DENIED", "FR_EXIST",
      "FR_INVALID_OBJECT", "FR_WRITE_PROTECTED", "FR_INVALID_DRIVE",
      "FR_NOT_ENABLED",	"FR_NO_FILESYSTEM", "FR_MKFS_ABORTED",
      "FR_TIMEOUT", "FR_LOCKED", "FR_NOT_ENOUGH_CORE", "FR_TOO_MANY_OPEN_FILES",
      "FR_INVALID_PARAMETER"
    };


/************************************************************************/
/* Function	   : writeFileHdr											*/
/* Purpose	   : Write a file header record								*/
/* Inputs	   : Open file ptr, rate of primary data in samples/min		*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
MLocal Errno writeFileHdr(FIL *fp, Nat16 dataRate)
{
	FileHdr		hdr;
	FRESULT		res;
	Nat32		bw = 0;

	hdr.recType = FileHdrType;
	hdr.rsvd = 0;
	hdr.fmtVersion = FMT_VERSION;
	hdr.platformID = (Nat16)(platformID & 0xffff);
	hdr.dataRate = dataRate;
	clkGetTime(&hdr.startTime, &hdr.startMs);
	hdr.duration = 0;
	res = f_write(fp, &hdr, sizeof(hdr), &bw);
	return(((res == FR_OK) && (bw == sizeof(hdr))) ? OK : ERROR);

} /* writeFileHdr() */


/************************************************************************/
/* Function	   : fileInit												*/
/* Purpose	   : Initialize file module, create data subdirectory		*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno fileInit(void)
{
    FRESULT     fRes;

	sdOK = TRUE;
    if ((fRes = f_mount(&fatfs, "0", 1)) != FR_OK)
    {
		fperr(fRes, "\r\nError mounting file system");
		sdOK = FALSE;
		return(ERROR);
	}

	backupFile(eventIdxName, "EVENTIDX.%03d");
	backupFile(watchIdxName, "WATCHIDX.%03d");

	if ((fRes = f_open(&idxFil, eventIdxName, FA_WRITE | FA_CREATE_ALWAYS)) == FR_OK)
	{
		writeFileHdr(&idxFil, 0);
		f_close(&idxFil);
	}
	else
		sdOK = FALSE;
	
	if ((fRes = f_open(&idxFil, watchIdxName, FA_WRITE | FA_CREATE_ALWAYS)) == FR_OK)
	{
		writeFileHdr(&idxFil, 0);
		f_close(&idxFil);
	}
	else
		sdOK = FALSE;
	
	return(sdOK ? OK : ERROR);

} /* fileInit() */


/************************************************************************/
/* Function	   : fileClose												*/
/* Purpose	   : Terminate this module									*/
/* Inputs	   : None													*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno fileClose(void)
{
	f_mount(0, "", 0);                     /* Unmount the default drive */
	return(OK);
}


/************************************************************************/
/* Function    : fperr                                                  */
/* Purpose     : Print file system error string                         */
/* Inputs      : Error number, serial port, string						*/
/* Outputs     : None													*/
/************************************************************************/
void fperr(int errnum, const char *s)
{
    if (errnum <= sizeof(ferrStr))
    {
        if (s)
            printf("%s: errno %d  %s\r\n", s, errnum, ferrStr[errnum]);
        else 
            printf("errno %d  %s\r\n", errnum, ferrStr[errnum]);
    }
}


/************************************************************************/
/* Function	   : getDataFileName										*/
/* Purpose	   : Get the file name of a data file						*/
/* Inputs	   : File number, file suffix, buffer to place name into	*/
/* Outputs	   : None													*/
/************************************************************************/
void getDataFileName(long fileNum, char *suffix, char *nameBuf)
{
#ifndef USE_LFN
	char platformChar;

	if ((platformID < 0) || (platformID > 35))
		platformChar = '_';
	else if (platformID < 10)
		platformChar = platformID + '0';
	else
		platformChar = platformID - 10 + 'A';

	sprintf(nameBuf, "%c%02x%05d.%s",
			platformChar, (deploymentID & 0xff), fileNum, suffix);
#else
	sprintf(nameBuf, "BEDS%02x_%02x_%05d.%s",
			(platformID & 0xff), (deploymentID & 0xff), fileNum, suffix);
#endif

} /* getDataFileName() */


/************************************************************************/
/* Function	   : writeDataFile											*/
/* Purpose	   : Write data to DataFileStruct buffer, flush if necessary*/
/* Inputs	   : DataFileStruct, ptr to data, lenth of data				*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno writeDataFile(DataFileStruct *dfp, void *buf, Nat32 len)
{
	Nat32	lenToCopy, remain = len, bw = 0;
	char	*p = buf;
	FRESULT res;

	if (dfp == NULL)
		return(ERROR);

	if ((len + dfp->bufBytes) >= FBUF_SIZE)
	{
		lenToCopy = FBUF_SIZE - dfp->bufBytes;
		remain -= lenToCopy;
		memcpy(&dfp->buf[dfp->bufBytes], buf, lenToCopy);

		if (f_open(&dfp->fil, dfp->fileName, FA_WRITE | FA_OPEN_APPEND) != FR_OK)
			return(ERROR);
		res = f_write(&dfp->fil, dfp->buf, FBUF_SIZE, &bw);
		f_close(&dfp->fil);

		if ((res != FR_OK) || (bw < FBUF_SIZE))
			return(ERROR);

		p += lenToCopy;
		dfp->bufBytes = 0;
	}

	if (remain)
	{
		memcpy(&dfp->buf[dfp->bufBytes], p, remain);
		dfp->bufBytes += remain;
	}

	return(OK);

} /* writeDataFile() */


/************************************************************************/
/* Function	   : openNewDataFile										*/
/* Purpose	   : Open a new data file									*/
/* Inputs	   : DataFileType, data rate in samples/minute				*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno openNewDataFile(DataFileStruct *dfp)
{
	FIL			*fp;
	FRESULT		res;
	Nat32		bw = 0;
	IdxRcd		indexRcd;
	FileHdr		hdr;

	if (dfp == NULL)
		return(ERROR);

	getDataFileName(dfp->fileIndex, dfp->fileSuffix, dfp->fileName);

	dfp->bufBytes = 0;
	dfp->fileIndex++;							/* Incr file number		 */
	sysLogPrintf("Opening data file %s", dfp->fileName);

	if (f_open(&dfp->fil, dfp->fileName, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
		return(ERROR);							/* Open data file to clear it*/

	f_close(&dfp->fil);							/* And close it again	*/

	if (f_open(&idxFil, dfp->indexFile, FA_WRITE | FA_OPEN_APPEND) != FR_OK)
		return(ERROR);							/* Open Index file		*/

	hdr.recType = FileHdrType;					/* Create File Hdr		*/
	hdr.rsvd = 0;
	hdr.rsvdWrd = 0;
	hdr.fmtVersion = FMT_VERSION;
	hdr.platformID = (Nat16)(platformID & 0xffff);
	hdr.dataRate = dfp->dataRate;
	clkGetTime(&hdr.startTime, &hdr.startMs);
	hdr.duration = 0;
	writeDataFile(dfp, &hdr, sizeof(hdr));		/* Write file hdr		*/

	dfp->indexRecOffset = f_tell(&idxFil);		/* Remember where we put idx rec*/
	indexRcd.recType = IndexType;				/* Create Index record	 */
	indexRcd.rsvd = 0;
	indexRcd.startTime = hdr.startTime;
	indexRcd.startMs = hdr.startMs;
	indexRcd.duration = 0;
	indexRcd.maxVal = 0;
	strncpy(indexRcd.fileName, dfp->fileName, 16);
	res = f_write(&idxFil, &indexRcd, sizeof(indexRcd), &bw);
	f_close(&idxFil);							/* Close index file		 */
	return((res == FR_OK) ? OK : ERROR);

} /* openNewDataFile() */


/************************************************************************/
/* Function	   : closeDataFile											*/
/* Purpose	   : Close the Event or Watch data file						*/
/* Inputs	   : DataFileStruct											*/
/* Outputs	   : Errno													*/
/************************************************************************/
Errno	closeDataFile(DataFileStruct *dfp)
{
	FileHdr		hdr;
	time_t		now;
	Nat16		ms;
	Nat32		bw, duration = 0;
	Errno		rtn = OK;

	if ((dfp == NULL) ||				/* Open the data file			*/
		(f_open(&dfp->fil, dfp->fileName, FA_READ | FA_WRITE | FA_OPEN_APPEND)
		 != FR_OK))
		return(ERROR);

	if (dfp->bufBytes)					/* If unwritten bytes,			*/
		if (f_write(&dfp->fil, dfp->buf, dfp->bufBytes, &bw) != FR_OK)
		{
			f_close(&dfp->fil);
			return(ERROR);
		}
					
	clkGetTime(&now, &ms);				/* Get current time plus ms		*/
	f_lseek(&dfp->fil, 0);				/* Rewind to beginning of data file*/

	if ((f_read(&dfp->fil, &hdr, sizeof(hdr), &bw) == FR_OK) &&
		(bw == sizeof(hdr)))
	{
		duration = 1000L * (now - hdr.startTime) + ms - hdr.startMs;
		f_lseek(&dfp->fil, OffsetOf(FileHdr, duration));
		f_write(&dfp->fil, &duration, sizeof(Nat32), &bw);
	}
	else
		rtn = ERROR;

	f_close(&dfp->fil);					/* Close data file				*/
	dfp->bufBytes = 0;

	if (f_open(&idxFil, dfp->indexFile, FA_READ | FA_WRITE) != FR_OK)
		return(ERROR);					/* Open index file				*/

										/* Write duration and maxVal	*/
	f_lseek(&idxFil, dfp->indexRecOffset + OffsetOf(IdxRcd, duration));
	f_write(&idxFil, &duration, sizeof(Nat32), &bw);
	f_write(&idxFil, &dfp->maxVal, sizeof(Int32), &bw);

	f_close(&idxFil);					/* Close index file				*/
	return(rtn);

} /* closeDataFile() */


/************************************************************************/
/* Function	   : writeSecondMarker										*/
/* Purpose	   : Write a second marker into watch or event data file	*/
/* Inputs	   : Ping-pong buf to write record into, current time		*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno writeSecondMarker(DataFileStruct *dfp, time_t secs)
{
	SecondMarker sm;

	memset(&sm, 0, sizeof(sm));
	sm.recType = SecMarkerType;
	sm.rcdTime = secs;
	return(writeDataFile(dfp, &sm, sizeof(sm)));

} /* writeSecondMarker() */
