/****************************************************************************/
/* Copyright 2016 MBARI                                                     */
/****************************************************************************/
/* Summary  : Main Module for file test routines for OASIS5                 */
/* Filename : main.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:                                                    */
/* 25marjul2016 rah - created                                               */
/****************************************************************************/

#include <xc.h>
#include <GenericTypeDefs.h>

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>

#include "file.h"
#include "serial.h"
#include "dig_io.h"
#include "periph.h"
#include "sys_timer.h"

#include "fatfs/ff.h"
//#include "fatfs/diskio.h"


#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 */

static FATFS    fatfs;
static FIL      file;
static DIR      dirObj;

static char     fileName[FNAME_SIZE];
static char     inputBuf[INBUF_SIZE];
static BYTE     buffer[TESTBUF_SIZE];

static int getline(char *buf, int buflen);
static int getnum(int *datp, char *prompt1, char *prompt2, int limit);
static void diskMenu(void);
static void diskCmd(char cmd);
static void fperr(int errnum, const char *s);

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    : exceptInit                                             */
/* Purpose     : Initialize interrupt handlint                          */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
static void exceptInit()
{
    unsigned int    temp_CP0;

    __builtin_disable_interrupts();
   
    _CP0_SET_EBASE(0x9fc01000);
    temp_CP0 = _CP0_GET_CAUSE();
    temp_CP0 |= 0x00800000;
    _CP0_SET_CAUSE(temp_CP0);

    INTCONSET = _INTCON_MVEC_MASK;

    temp_CP0 = _CP0_GET_STATUS();
    _CP0_SET_STATUS(temp_CP0 & ~_CP0_STATUS_IPL_MASK);

    __builtin_enable_interrupts();
}


/************************************************************************/
/* Function    : sdSpiInit                                              */
/* Purpose     : Initialize SPI I/O for SD card driver                  */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comment     : May want to put this elsewhere?  dig_io.c?  periph.c?  */
/************************************************************************/
static void sdSpiInit()
{
    SDI2R = 0x0a;                       /* MISO2 = RPC4, pin 9          */
    RPC1R = 0x06;                       /* MOSI2 = RPC1, pin 6          */

    SPI2CON = 0x0269;                   /* 8 bit, master, std buf       */
    SPI2BRG = 31;                       /* 400 KHz to start             */

    IPC8bits.SPI2IP = 5;                /* Same priority as UARTs       */
    IPC8bits.SPI2IS = 3;                /* Higher sub-prio than UARTs   */
}


/************************************************************************/
/* Function    : main                                                   */
/* Purpose     : Main Module                                            */
/* Inputs      : None                                                   */
/* Outputs     : None, never exits                                      */
/************************************************************************/
int main()
{
    UINT8       b;
    UINT32      tick, lastTick;
    FRESULT     fRes;

    SYS_Initialize();           //Harmony initializer

    digInit();
    periphInit();
    serInit();
    tmrInit();
    sdSpiInit();
    rtcInit();
    exceptInit();

    digBlinkyOn();
    digTx0En();
    tmrDelayMs(10);             /* Wait for charge pumps */

    sprintf(inputBuf, "\r\nOASIS5 Disk Test Program Built on %s at %s\r\n",
            __DATE__, __TIME__);
    serPutString(SER_CONSOLE, inputBuf);        /* spit out the banner */

    while ((fRes = f_mount(&fatfs, "0", 1)) != FR_OK)
    {                                               
        sprintf(inputBuf, "\r\nError mounting file system, result = %d\r\n", fRes);
        serPutString(SER_CONSOLE, inputBuf);
        serPutString(SER_CONSOLE, "Looping forever to echo chars\r\n");
        serPutString(SER_CONSOLE, "Type ^R to retry f_mount()\r\n");

        while(TRUE)
            if (serGetByte(SER_CONSOLE, &b))
            {
                serPutByte(SER_CONSOLE, b);
                if (b == 0x12)
                    break;
            }
    }

    diskMenu();
    fileIndex = 0;
    memset(&file, 0, sizeof(file));

    while(TRUE)
    {
        LATEINV = 0x80;

        if (serGetByte(SER_CONSOLE, &b))
        {
            serPutByte(SER_CONSOLE, '\r');
            serPutByte(SER_CONSOLE, '\n');
            diskCmd(b);
            diskMenu();
        }

        tick = tmrGetTicks();
        if ((tick - lastTick) >= TICKS_PER_SEC/2)
        {
            digToggleBlinky();
            lastTick = tick;
        }
    }
}


/************************************************************************/
/* Function    : getline                                                */
/* Purpose     : Get one line of user input                             */
/* Inputs      : Buffer pointer, buf length                             */
/* Outputs     : Length of returned line                                */
/************************************************************************/
static int getline(char *buf, int buflen)
{
    int         len;
    BYTE        b;

    for (len = 0; len < buflen-1; )
    {
        if (serGetByte(SER_CONSOLE, &b))
        {
            serPutByte(SER_CONSOLE, b);
            buf[len++] = b;

            if (b == '\r')
            {
                serPutByte(SER_CONSOLE, (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 *datp, char *prompt1, char *prompt2, int limit)
{
    int tmp;

    sprintf(inputBuf, "%s %d %s:  ", prompt1, *datp, prompt2);
    serPutString(SER_CONSOLE, inputBuf);

    if ((getline(inputBuf, sizeof(inputBuf)) < 1) ||
        (sscanf(inputBuf, "%d", &tmp) < 1) || (tmp < 1) || (tmp > limit))
    {
        serPutString(SER_CONSOLE, "\nBad input value\n");
        return(0);
    }

    *datp = tmp;

    sprintf(inputBuf, "\r\n%s is %d\r\n", prompt2, tmp);
    serPutString(SER_CONSOLE, inputBuf);

    return(1);
}


/************************************************************************/
/* Function    : fperr                                                  */
/* Purpose     : Print file system error string                         */
/* Inputs      : 
/* Outputs     : 0 if no number found, 1 if found                       */
/************************************************************************/
static void fperr(int errnum, const char *s)
{
    if (errnum <= sizeof(ferrStr))
    {
        if (s)
            sprintf(inputBuf, "%s: %d  %s", s, errnum, ferrStr[errnum]);
        else 
            sprintf(inputBuf, "%d  %s", errnum, ferrStr[errnum]);

        serPutString(SER_CONSOLE, inputBuf);
    }
}


/************************************************************************/
/* Function    : diskMenu                                               */
/* Purpose     : Print menu                                             */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
static void diskMenu(void)
{
    serPutString(SER_CONSOLE, "\r\nType 'R' to read file using f_read\r\n");
    serPutString(SER_CONSOLE, "Type 'V' to read and verify file using f_read\r\n");
    serPutString(SER_CONSOLE, "Type 'W' to write file using f_write\r\n");
    serPutString(SER_CONSOLE, "Type 'P' to write file using f_putc (use small file)\r\n");
    serPutString(SER_CONSOLE, "Type 'H' to hex dump file\r\n");

    serPutString(SER_CONSOLE, "Type 'S' to change size of file\r\n");
    serPutString(SER_CONSOLE, "Type 'N' to change size of each f_write or f_read\r\n");
    serPutString(SER_CONSOLE, "Type 'I' to change file number (suffix of name)\r\n");
    serPutString(SER_CONSOLE, "Type 'B' to change SPI baud rate divisor\r\n");
    serPutString(SER_CONSOLE, "Type 'F' to get disk free space\r\n");
    serPutString(SER_CONSOLE, "Type 'D' to get directory listing\r\n");
    serPutString(SER_CONSOLE, "Type 'Z' to get FSINFO\r\n");
}


/************************************************************************/
/* Function    : diskCmd                                                */
/* Purpose     : Perform one disk command                               */
/* Inputs      : Char indicating command                                */
/* Outputs     : None                                                   */
/************************************************************************/
static void diskCmd(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(&filesize, "Current file size is", "KB. New file size in KBytes",
                 MAX_FILE_SIZE);
          break;

      case 'N':
          getnum(&ioSize, "Current chunk size is", "KB. New chunk size in KBytes", 64);
          break;

      case 'I':
          getnum(&fileIndex, "Current file number is", ". New file number", 999);
          break;

      case 'B':
          serPutString(SER_CONSOLE, "New SPI2BRG (0 <= BRG <= 31):  ");

          if ((getline(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(SER_CONSOLE, 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(SER_CONSOLE, inputBuf);
          }
          break;

      case 'R':
      case 'V':
          errs = 0;
          sprintf(fileName, "FILE%03u.DAT", 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(SER_CONSOLE, 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(SER_CONSOLE, 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(SER_CONSOLE, inputBuf);

          if (cmdByte == 'V')
          {
              sprintf(inputBuf, ", %lu errors\r\n", errs);
              serPutString(SER_CONSOLE, inputBuf);
          }
          else
              serPutString(SER_CONSOLE, "\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(SER_CONSOLE, inputBuf);
          break;

      case 'H':
          sprintf(fileName, "FILE%03u.DAT", 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 (serRxAvail(SER_CONSOLE))
                      {
                          serRxFlush(SER_CONSOLE);
                          goto dumpStop;
                      }
                      sprintf(inputBuf, "%\r\n%06x ", i+cnt);
                      serPutString(SER_CONSOLE, inputBuf);
                  }
                  
                  sprintf(inputBuf, "%02x ", *p);
                  serPutString(SER_CONSOLE, inputBuf);
              }

              cnt += nIO;

          } while (nIO);

    dumpStop:
          serPutString(SER_CONSOLE, "\r\n");
          break;

      case 'W':
      case 'P':
          sprintf(fileName, "FILE%03u.DAT", 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(SER_CONSOLE, 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;
              }
          }

          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(SER_CONSOLE, 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(SER_CONSOLE, inputBuf);

          break;

      case 'D':
          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(SER_CONSOLE, inputBuf);
          }
          serPutString(SER_CONSOLE, "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(SER_CONSOLE, inputBuf);

                  if (finfo.fattrib & AM_DIR)
                      serPutString(SER_CONSOLE, "   <DIR>        ");
                  else {
                      sprintf(inputBuf, "%12lu ", finfo.fsize);
                      serPutString(SER_CONSOLE, inputBuf);
                  }

                  sprintf(inputBuf, "%s\r\n", finfo.fname);
                  serPutString(SER_CONSOLE, inputBuf);

                  fRes = f_findnext(&dirObj, &finfo);
              }
          }

          f_closedir(&dirObj);

          sprintf(inputBuf, "%8u file(s) %14lu KB\r\n", fileCount, fileKB);
          serPutString(SER_CONSOLE, inputBuf);

          if (f_getfree("", &i, &fs) == FR_OK)
          {
              sprintf(inputBuf, "%8u dir(s)  %14lu KB free\r\n",
                      dirCount, (i * fs->csize)/2);
              serPutString(SER_CONSOLE, inputBuf);
          }

          serPutString(SER_CONSOLE, "Type any key to continue");

          while (serGetByte(SER_CONSOLE, &cmdByte) == 0)
              ;

          break;

      case 'Z':
          sprintf(inputBuf, "fs_type = %d\r\n", fatfs.fs_type);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "drv = %d\r\n", fatfs.drv);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "n_fats = %d\r\n", fatfs.n_fats);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "wflag = %d\r\n", fatfs.wflag);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "fsi_flag = %d\r\n", fatfs.fsi_flag);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "id = %u\r\n", fatfs.id);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "n_rootdir = %u\r\n", fatfs.n_rootdir);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "csize = %d\r\n", fatfs.csize);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "last_clust = %lu\r\n", fatfs.last_clst);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "free_clust = %lu\r\n", fatfs.free_clst);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "cdir = %lu\r\n", fatfs.cdir);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "n_fatent = %lu\r\n", fatfs.n_fatent);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "fsize = %lu\r\n", fatfs.fsize);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "volbase = %lu\r\n", fatfs.volbase);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "fatbase = %lu\r\n", fatfs.fatbase);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "dirbase = %lu\r\n", fatfs.dirbase);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "database = %lu\r\n", fatfs.database);
          serPutString(SER_CONSOLE, inputBuf);
          sprintf(inputBuf, "winsect = %lu\r\n", fatfs.winsect);
          serPutString(SER_CONSOLE, inputBuf);
          serPutString(SER_CONSOLE, "Type any key to continue");

          while (serGetByte(SER_CONSOLE, &cmdByte) == 0)
              ;
          break;
    }


}
