/****************************************************************************/
/* Copyright 2016 MBARI                                                     */
/****************************************************************************/
/* Summary  : File test routines for OASIS5									*/
/* Filename : fileTest.c                                                    */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 03/25/2016                                                    */
/*                                                                          */
/* 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:                                                    */
/* 25mar2016 rah - created                                                  */
/* 29jun2016 rah - modified as sub-menu for o5debug							*/
/****************************************************************************/

#include <xc.h>
#include <GenericTypeDefs.h>

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>

#include <serial.h>
#include <dig_io.h>
#include <file.h>
#include <timer.h>
#include <malloc.h>
#include <syslog.h>

#include <fatfs/ff.h>

#define FILENAME	"FILE%03u.DAT"

//#define FT_STANDALONE
#define TEST_USRDIR

#define INBUF_SIZE       256
#define TESTBUF_SIZE     65536
#define MAX_FILE_SIZE    65536
#define FNAME_SIZE       32


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

static UINT     fileIndex=0;
static UINT     filesize=1024, ioSize=16;    /* Note - sizes are in KB */

#ifdef FT_STANDALONE
static FATFS    fatfs;
#else
extern FATFS    fatfs;
#endif
static FIL      file;
static DIR      dirObj;

static char     fileName[FNAME_SIZE];
static char     inputBuf[INBUF_SIZE];
static BYTE     *buffer = NULL;

static int getline(int port, char *buf, int buflen);
static int getnum(int port, int *datp, char *prompt1, char *prompt2, int limit);
static void diskMenu(int port);
static void diskCmd(int port, char cmd);

static 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    : fileTest                                               */
/* Purpose     : File Test routine										*/
/* Inputs      : Serial port number										*/
/* Outputs     : OK or ERROR											*/
/************************************************************************/
int fileTest(int port)
{
    int			b;
    FRESULT     fRes;

    sprintf(inputBuf, "\r\nOASIS5 Disk Test Program Built on %s at %s\r\n",
            __DATE__, __TIME__);
	serPutString(port, inputBuf);

#ifdef FT_STANDALONE
    if ((fRes = f_mount(&fatfs, "0", 1)) != FR_OK)
    {                                               
        sprintf(inputBuf, "\r\nError mounting file system, result = %d\r\n", fRes);
		serPutString(port, inputBuf);
		return(ERROR);
    }
#endif

	if ((buffer = malloc(TESTBUF_SIZE)) == NULL)
	{
		iprintf("\r\nError - can't malloc test buffer\r\n");
		return(ERROR);
	}

    diskMenu(port);
    fileIndex = 0;
    memset(&file, 0, sizeof(file));

    while(TRUE)
    {
        if ((b = ser_getc(port)) != ERROR)
        {
            ser_putc(port, '\r');
            ser_putc(port, '\n');
			if ((b == 'X') || (b == 'x'))
			{
				if (buffer)
					free(buffer);
				return(OK);
			}
            diskCmd(port, b);
            diskMenu(port);
        }
		else
			task_delay(20);
    }
}


/************************************************************************/
/* Function    : getline                                                */
/* Purpose     : Get one line of user input                             */
/* Inputs      : Serial port, buffer pointer, buf length                */
/* Outputs     : Length of returned line                                */
/************************************************************************/
static int getline(int port, char *buf, int buflen)
{
    int         len;
    int         b;

    for (len = 0; len < buflen-1; )
    {
        if ((b = ser_getc(port)) != ERROR)
        {
            ser_putc(port, b);
            buf[len++] = b;

            if (b == '\r')
            {
                ser_putc(port, (BYTE)'\n');
                buf[len] = '\0';
                return(len);
            }
        }
    }

    buf[buflen] = '\0';
    return(buflen);
}


/************************************************************************/
/* Function    : getnum                                                 */
/* Purpose     : Get a number from the user                             */
/* Inputs      : Ptr to data to return                                  */
/* Outputs     : 0 if no number found, 1 if found                       */
/************************************************************************/
static int getnum(int port, int *datp, char *prompt1, char *prompt2, int limit)
{
    int tmp;

    sprintf(inputBuf, "%s %d %s:  ", prompt1, *datp, prompt2);
    serPutString(port, inputBuf);

    if ((getline(port, inputBuf, sizeof(inputBuf)) < 1) ||
        (sscanf(inputBuf, "%d", &tmp) < 1) || (tmp < 1) || (tmp > limit))
    {
        serPutString(port, "\nBad input value\n");
        return(0);
    }

    *datp = tmp;

    sprintf(inputBuf, "\r\n%s is %d\r\n", prompt2, tmp);
    serPutString(port, inputBuf);

    return(1);
}


/************************************************************************/
/* Function    : diskMenu                                               */
/* Purpose     : Print menu                                             */
/* Inputs      : Serial port number										*/
/* Outputs     : None                                                   */
/************************************************************************/
static void diskMenu(int port)
{
	sprintf(fileName, FILENAME, fileIndex);
	iprintf("\r\nType 'R' to read file %s using f_read\r\n", fileName);
    serPutString(port, "Type 'V' to read and verify file using f_read\r\n");
    serPutString(port, "Type 'W' to write file using f_write\r\n");
    serPutString(port, "Type 'P' to write file using f_putc (use small file)\r\n");
    serPutString(port, "Type 'H' to hex dump file\r\n");

    serPutString(port, "Type 'S' to change size of test file\r\n");
    serPutString(port, "Type 'N' to change size of each f_write or f_read\r\n");
    serPutString(port, "Type 'I' to change file number (suffix of name)\r\n");
    serPutString(port, "Type 'B' to change SPI baud rate divisor\r\n");
    serPutString(port, "Type 'Y' to format SD card\r\n");
    serPutString(port, "Type 'F' to get disk free space\r\n");
    serPutString(port, "Type 'D' to get directory listing\r\n");
    serPutString(port, "Type 'Z' to get FSINFO\r\n");
    serPutString(port, "Type 'M' to 'more OASIS.CFG'\r\n");
    serPutString(port, "Type 'L' to delete a line from OASIS.CFG\r\n");
    serPutString(port, "Type 'X' to return to previous menu\r\n");
}


/************************************************************************/
/* Function    : diskCmd                                                */
/* Purpose     : Perform one disk command                               */
/* Inputs      : Char indicating command                                */
/* Outputs     : None                                                   */
/************************************************************************/
static void diskCmd(int port, char cmd)
{
    UINT32      tick, stTick, errs, i, cnt, remain, fileKB;
    BYTE        *p;
    UINT        tmp, curSize, nIO, dirCount, fileCount;
    FRESULT     fRes;
    FATFS       *fs;
    FIL         *fp = &file;
    FILINFO     finfo;
    char        cmdByte = toupper(cmd);

    switch(cmdByte)
    {
      case 'S':
          getnum(port, &filesize, "Current file size is", "KB. New file size in KBytes",
                 MAX_FILE_SIZE);
          break;

      case 'N':
          getnum(port, &ioSize, "Current chunk size is", "KB. New chunk size in KBytes", 64);
          break;

      case 'I':
          getnum(port, &fileIndex, "Current file number is", ". New file number", 999);
          break;

      case 'B':
          serPutString(port, "New SPI2BRG (0 <= BRG <= 31):  ");

          if ((getline(port, inputBuf, sizeof(inputBuf)) > 0) &&
              (sscanf(inputBuf, "%u", &tmp) > 0) && (tmp < 32))
              SPI2BRG = tmp;
          break;

      case 'F':
          stTick = tmrGetTicks();
          fRes = f_getfree("", &i, &fs);
          tick = tmrGetTicks();

          sprintf(inputBuf, "f_getfree() returns %d (%s) in %lu ms\r\n",
                  fRes, ferrStr[fRes], (tick - stTick)/MS_PER_TICK);
          serPutString(port, inputBuf);

          if (fRes == FR_OK)
          {
              sprintf(inputBuf, "%10lu KB free, %10lu KB total\r\n",
                      (i * fs->csize)/2, ((fs->n_fatent - 2) * fs->csize)/2);
              serPutString(port, inputBuf);
          }
          break;

      case 'R':
      case 'V':
          errs = 0;
          sprintf(fileName, FILENAME, fileIndex);
          stTick = tmrGetTicks();
          fRes=f_open(&file, fileName, (BYTE)(FA_READ | FA_OPEN_EXISTING));
          tick = tmrGetTicks();

          sprintf(inputBuf, "f_open(%s) returns %d (%s), time %u ms\r\n",
                  fileName, fRes, ferrStr[fRes], (tick - stTick)/MS_PER_TICK);
          serPutString(port, inputBuf);

          cnt = 0;
          stTick = tmrGetTicks();

          do
          {
              curSize = 1024L * ioSize;

              if ((fRes = f_read(&file, buffer, curSize, &nIO)) != FR_OK)
                      break;

              if (cmdByte == 'V')
              {
                  for (i = 0, p = buffer; i < nIO; i++, p++)
                      if (*p != ((i + fileIndex) & 0xff))
                      {
                          if (errs++ < 16)
                          {
                              sprintf(inputBuf, "Address %06x Read 0x%x s/b 0x%x\r\n",
                                      cnt+i, *p, ((i + fileIndex) & 0xff));
                                  
                              serPutString(port, inputBuf);
                          }
                      }
              }

              cnt += nIO;

          } while (nIO);

          tick = tmrGetTicks();

          sprintf(inputBuf, "Read %lu bytes from %s in %lu ms, result = %d (%s)",
                  cnt, fileName, (tick - stTick)/MS_PER_TICK, fRes, ferrStr[fRes]);
          serPutString(port, inputBuf);

          if (cmdByte == 'V')
          {
              sprintf(inputBuf, ", %lu errors\r\n", errs);
              serPutString(port, inputBuf);
          }
          else
              serPutString(port, "\r\n");
          
          stTick = tmrGetTicks();
          fRes = f_close(&file);
          tick = tmrGetTicks();

          sprintf(inputBuf, "f_close(%s) returns %d (%s), time %lu ms\r\n",
                  fileName, fRes, ferrStr[fRes], (tick - stTick)/MS_PER_TICK, fRes);
          serPutString(port, inputBuf);
          break;

      case 'H':
          sprintf(fileName, FILENAME, fileIndex);
          fRes=f_open(&file, fileName, (BYTE)(FA_READ | FA_OPEN_EXISTING));

          cnt = 0;
          do
          {
              curSize = 1024l * ioSize;

              if ((fRes = f_read(&file, buffer, curSize, &nIO)) != FR_OK)
                      break;

              for (i = 0, p = buffer; i < nIO; i++, p++)
              {
                  if ((i % 16) == 0)
                  {
                      if (serRxCount(port))
                      {
                          serRxFlush(port);
                          goto dumpStop;
                      }
                      sprintf(inputBuf, "%\r\n%06x ", i+cnt);
                      serPutString(port, inputBuf);
                  }
                  
                  sprintf(inputBuf, "%02x ", *p);
                  serPutString(port, inputBuf);
              }

              cnt += nIO;

          } while (nIO);

    dumpStop:
          serPutString(port, "\r\n");
          break;

      case 'W':
      case 'P':
          sprintf(fileName, FILENAME, fileIndex);
          stTick = tmrGetTicks();
          fRes=f_open(&file, fileName,
                      (BYTE)(FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_WRITE | FA_READ));
          tick = tmrGetTicks();

          sprintf(inputBuf, "f_open(%s) returns %d (%s), time %u ms\r\n",
                  fileName, fRes, ferrStr[fRes], (tick - stTick)/MS_PER_TICK);
          serPutString(port, inputBuf);

          for (i = 0, p = buffer; i < TESTBUF_SIZE; i++, p++)
              *p = i + fileIndex;

          stTick = tmrGetTicks();
          fRes = FR_OK;

          if (cmdByte == 'P')
          {
              p = buffer;
              for (remain = 1024L * filesize; remain > 0L; remain--)
              {
                  if (f_putc(*p++, &file) < 1)
                  {
                      fRes = FR_DISK_ERR;
                      break;
                  }

                  if (p >= buffer + sizeof(buffer))
                      p = buffer;
              }
          }
          else
          {
              for (remain = 1024L * filesize; remain > 0L; )
              {
                  curSize = 1024L * ((ioSize <= filesize) ? ioSize : filesize);
                  if (curSize > remain)
                      curSize = remain;

                  if ((fRes = f_write(&file, buffer, curSize, &nIO)) != FR_OK)
                      break;
                  remain -= nIO;
				  portYIELD();
              }
          }

          tick = tmrGetTicks();

          sprintf(inputBuf, "Wrote %lu bytes to %s in %lu ms, result = %d\r\n",
                  1024L * filesize - remain, fileName, (tick - stTick)/MS_PER_TICK, fRes);
          serPutString(port, inputBuf);

          stTick = tmrGetTicks();
          fRes = f_close(&file);
          tick = tmrGetTicks();

          sprintf(inputBuf, "f_close(%s) returns %d (%s), time %lu ms\r\n",
                  fileName, fRes, ferrStr[fRes], (tick - stTick)/MS_PER_TICK, fRes);
          serPutString(port, inputBuf);

          break;

      case 'D':
#ifdef TEST_USRDIR
		  usrDir(0, NULL);
#else
          buffer[0] = '\0';             /* Use test buffer for volume label */
          if (f_getlabel("", buffer, &i) == FR_OK)
          {
              sprintf(inputBuf, "Volume label is %s, Serial number is %u\r\n",
                      buffer, i);
              serPutString(port, inputBuf);
          }
          serPutString(port, "Directory of SD card\r\n\n");

          dirCount = 0;
          fileCount = 0;
          fileKB = 0;
          fRes = f_findfirst(&dirObj, &finfo, "", "*");

          while ((fRes == FR_OK) && finfo.fname[0])
          {
              if ((finfo.fattrib & (AM_VOL | AM_HID)) == 0)
              {

                  if (finfo.fattrib & AM_DIR)
                      dirCount++;
                  else
                  {
                      fileCount++;
                      fileKB += (finfo.fsize+1023) / 1024;
                  }
				
                  sprintf(inputBuf, "%04d/%02d/%02d %2u:%02u:%02u",
                          (finfo.fdate>>9 & 0x7f) + 1980,
                          (finfo.fdate>>5 & 0x0f),
                          finfo.fdate & 0x1f,
                          (finfo.ftime>>11) & 0x1f,
                          (finfo.ftime>>5) & 0x3f,
                          (finfo.ftime & 0x1f) << 1);

                  serPutString(port, inputBuf);

                  if (finfo.fattrib & AM_DIR)
                      serPutString(port, "   <DIR>        ");
                  else {
                      sprintf(inputBuf, "%12lu ", finfo.fsize);
                      serPutString(port, inputBuf);
                  }

                  sprintf(inputBuf, "%s\r\n", finfo.fname);
                  serPutString(port, inputBuf);

                  fRes = f_findnext(&dirObj, &finfo);
              }
          }

          f_closedir(&dirObj);

          sprintf(inputBuf, "%8u file(s) %14lu KB\r\n", fileCount, fileKB);
          serPutString(port, inputBuf);

          if (f_getfree("", &i, &fs) == FR_OK)
          {
              sprintf(inputBuf, "%8u dir(s)  %14lu KB free\r\n",
                      dirCount, (i * fs->csize)/2);
              serPutString(port, inputBuf);
          }
#endif
          serPutString(port, "Type any key to continue");

		  while (serRxCount(port) == 0)
              ;

		  serRxFlush(port);
          break;

      case 'M':
		  usrMore(1, "OASIS.CFG");
          break;

      case 'L':
          iprintf("Line number to delete:  ");
          if ((getline(port, inputBuf, sizeof(inputBuf)) > 0) &&
              (sscanf(inputBuf, "%u", &tmp) > 0) && (tmp >= 0))
			  deleteLine(3, tmp, "OASIS.CFG");
          break;

      case 'Y':
          iprintf("Cluster size in Kbytes (1/2/4/8/16/32/64), return for default:  ");
		  tmp = 0;
          if ((getline(port, inputBuf, sizeof(inputBuf)) > 0) &&
              (sscanf(inputBuf, "%u", &tmp) > 0))
		  {
			  if ((tmp != 1) && (tmp != 2) && (tmp != 4) && (tmp != 8) &&
				  (tmp != 16) && (tmp != 32) && (tmp != 64))
			  {
				  iprintf("Invalid cluster size, using default\r\n");
				  tmp = 0;
			  }
		  }

		  iprintf("Formatting SD card as FAT32, cluster size = ");
		  if (tmp)
			  iprintf("%uK\r\n", tmp);
		  else
			  iprintf("default for disk size\r\n");
		  
		  tmp <<= 10;
		  fRes = f_mkfs(FILEPATH, FM_FAT32, tmp, buffer, TESTBUF_SIZE);

		  fperr(fRes, "Result");
		  iprintf("Remounting file system...");
		  f_mount(NULL, "0", 1);
		  fRes = f_mount(&fatfs, "0", 1);
		  iprintf("%s\r\n", fRes == FR_OK ? "OK" : "ERROR");
		  sysLogPrintf("SD Card reformatted, cluster size = %u sectors",
					   fatfs.csize);
		  break;

      case 'Z':
          sprintf(inputBuf, "fs_type = %d\r\n", fatfs.fs_type);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "drv = %d\r\n", fatfs.drv);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "n_fats = %d\r\n", fatfs.n_fats);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "wflag = %d\r\n", fatfs.wflag);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "fsi_flag = %d\r\n", fatfs.fsi_flag);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "id = %u\r\n", fatfs.id);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "n_rootdir = %u\r\n", fatfs.n_rootdir);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "csize = %d\r\n", fatfs.csize);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "last_clust = %lu\r\n", fatfs.last_clst);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "free_clust = %lu\r\n", fatfs.free_clst);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "cdir = %lu\r\n", fatfs.cdir);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "n_fatent = %lu\r\n", fatfs.n_fatent);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "fsize = %lu\r\n", fatfs.fsize);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "volbase = %lu\r\n", fatfs.volbase);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "fatbase = %lu\r\n", fatfs.fatbase);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "dirbase = %lu\r\n", fatfs.dirbase);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "database = %lu\r\n", fatfs.database);
          serPutString(port, inputBuf);
          sprintf(inputBuf, "winsect = %lu\r\n", fatfs.winsect);
          serPutString(port, inputBuf);
          serPutString(port, "Type any key to continue");

		  while (serRxCount(port) == 0)
              ;

		  serRxFlush(port);
          break;
    }


}
