/**
  ******************************************************************************
  * @file    TASDCard.c
  * @author  C. Otten
  * @version V0.0.0
  * @date    04/22/2011
  * @brief   RSTi Thermistor Array - SD Card access.
  ******************************************************************************
  *
  * COPYRIGHT 2011 Liquid Robotics, Inc.
  **/

#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "includes.h"
#include "mmc_uspi_conf.h"
#include "ff.h"

#include "base.h"
#include "uart.h"
#include "timer.h"
#include "fltPyldComms.h"
#include "FileTransfer.h"
#include "UserData.h"
#include "TADebug.h"
#include "RSTi_thermarray.h"

typedef struct
{
  // Define the size of this block to fit integrally into the SD Card file write block.
  // That block is currently set to 512 bytes. Start this at 32 bytes.
  uint32_t                          file_number;
  uint32_t                          sequence_num;
  RSTI_THERMARRAY_DATA_REPORT_BLOCK thermdata;  // This is 24 bytes.
} SDCARD_WRITE_BLOCK, *PSDCARD_WRITE_BLOCK;

extern char * FError2Str(int ferror);

static const char   cpathname[]       = "Data/";
static const char   cfilename[]       = "TA";
static const char   cextension[]      = ".log";

static FATFS        fatfs[NUM_SDCARDS]   __attribute__((section(".xdata")));  // File system parameters for data collection file.
static FIL          fobject[NUM_SDCARDS] __attribute__((section(".xdata")));  // File object data.

static BOOL Open_New_File(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t drv);


// ................................................................................................
// Initialize the Thermarray task.
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    drv                 - Select the SD Card to use.
// Returns:
//    uint8_t             - Effectively a boolean indicating initialization result.
//                            TRUE when initialization is completed successfully.
//                            FALSE if there was some kind of an issue.
//
void SDCard_Init(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t drv)
{
  PRSTI_SDCARD_TRACKING psocket = &pTaskInfo->pBuf->sdcard[drv];

  if (drv >= NUM_SDCARDS)
    return;
  if (f_chdrive(drv) != FR_OK)
    return;

  psocket->status.all       = 0xFF;             // Set all bits to "bad" status. Have code determine
                                                //  each individual status during initialization.
  psocket->status.s.therest = 0x00;             // Set all unused bits to "good" status, but only
                                                //  if the drive number is within limits.

  psocket->pfatfs           = &fatfs[drv];      // Locate the FAT and file tables.
  psocket->pfobject         = &fobject[drv];

  psocket->recsave_good_cnt = 0;                // Happens only at power up.
  psocket->recsave_fail_cnt = 0;

  psocket->fpath[0] = 0;                        // Empty string to start.

  psocket->file_op_type   = FOPTYPE_FMOUNT;
  psocket->file_op_result = f_mount(0, psocket->pfatfs);
                                                // This function must always pass, since all it does
                                                //  is set up the file tables. No SD Card access.
                                                // Must always use drive #0.
                                                // If it does fail, it is a firmware issue (non-zero drive.)
  // First time through. Create the initial data file.
  Open_New_File(pTaskInfo, drv);
  // If no file was able to be opened, so be it. Status will reflect that, and during data saves it
  //  will attempt to create a new file (and again, and again, ...).
} // SDCard_Init



// ................................................................................................
// Initialize the Thermarray task.
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    drv                 - Select the SD Card to use.
// Returns:
//    uint8_t             - Effectivekly a boolean indicating initialization result.
//              TRUE when initialization is completed successfully.
//                          FALSE if there was some kind of an issue.
//
void SDCard_Save(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t drv)
{
  char                  *tmp_str;
  char                  *write_buf;
  PSDCARD_WRITE_BLOCK   pwrite_buf;
  uint16_t              num_written;
  uint8_t               idx;
  uint8_t               err;
  PRSTI_SDCARD_TRACKING psocket = &pTaskInfo->pBuf->sdcard[drv];

  if (drv >= NUM_SDCARDS)
    return;
  if (f_chdrive(drv) != FR_OK)
    return;

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);

  // Be a pessimist and presume that all operations will fail. When they succeed, each will change its
  //  state to success.
  psocket->status.all |= (SDCSTAT_nOPEN | SDCSTAT_nSEEK | SDCSTAT_nWRITE | SDCSTAT_nCLOSE);

  if ((psocket->status.all & (SDCSTAT_nPRESENT | SDCSTAT_nVALIDDIR | SDCSTAT_nCREATED)) != 0)
    // There is afile issue that needs to be resolved. Just create another file.
    if (!Open_New_File(pTaskInfo, drv))
      goto exit_SDCS;                           // Failed create.

  // Open the existing data file.
  if (psocket->file_op_type = FOPTYPE_FOPEN, (psocket->file_op_result = f_open(psocket->pfobject, psocket->fpath, FA_OPEN_EXISTING | FA_WRITE)) != FR_OK)
  {
    // There was a problem opening the file.
    // Don't attempt any further file transaction.
    psocket->status.s.notopen = TRUE;
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "Trouble opening file on SD Card #%u: <%s>: Error <%s>.\n",
                   drv,
                   psocket->fpath,
                   FError2Str(psocket->file_op_result));
    TADEBUG_STR(tmp_str, 100, &err);
    if (!Open_New_File(pTaskInfo, drv))
      goto exit_SDCS;                           // Failed open.
    else
      if (psocket->file_op_type = FOPTYPE_FOPEN, (psocket->file_op_result = f_open(psocket->pfobject, psocket->fpath, FA_OPEN_EXISTING | FA_WRITE)) != FR_OK)
        // Still having trouble. Exit without writing data.
        goto exit_SDCS;                         // Failed open.
  }
  psocket->status.s.notopen = FALSE;            // Successfully opened the file.

  // Seek to end of file so that data will be appended.
  if (psocket->file_op_type = FOPTYPE_FLSEEK, (psocket->file_op_result = f_lseek(psocket->pfobject, psocket->pfobject->fsize)) != FR_OK)
  {
    // There was a problem seeking within the file.
    // Try closing the file, but do not write data.
    f_close(psocket->pfobject);                 // Don't care about its result.
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "Trouble seeking file on SD Card #%u: <%s>: Error <%s>.\n",
                   drv,
                   psocket->fpath,
                   FError2Str(psocket->file_op_result));
    TADEBUG_STR(tmp_str, 100, &err);
    goto exit_SDCS;                             // Failed seek.
  }
  psocket->status.s.notseek = FALSE;            // Successfully moved pointer to end of file.

  // Write the data.
  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  write_buf = OSMemGet(mem_part_ptr[MEM_PART_100], &err);
  if (write_buf == NULL)
  {
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100, "No memory partition available.\n", 0);
    TADEBUG_STR(tmp_str, 100, &err);
    goto exit_SDCS;                             // Need that partition, but don't expect this to
                                                //  ever happen.
  }
  pwrite_buf = (PSDCARD_WRITE_BLOCK)write_buf;  // Not allowed to change 'write_buf'.
  // Copy filler data. This data helps if the SD Card FAT gets corrupted. It can be used to identify
  //  each block.
  pwrite_buf->file_number  = psocket->file_number;
  pwrite_buf->sequence_num = psocket->recsave_good_cnt;
  // Copy header data.
  pwrite_buf->thermdata.headerData.timestamp = pTaskInfo->pBuf->timestamp;
  pwrite_buf->thermdata.headerData.latitude  = pTaskInfo->pBuf->latitude;
  pwrite_buf->thermdata.headerData.longitude = pTaskInfo->pBuf->longitude;
  //  Copy sample data.
  for (idx = 0; idx < NUM_TA_NODES; idx++)
    // Node data already reflects inactive and invalid conditions.
    pwrite_buf->thermdata.data_sample.degreesx100[idx] = pTaskInfo->pBuf->node[idx].temp;

  psocket->file_op_type = FOPTYPE_FWRITE;
  psocket->file_op_result = f_write(psocket->pfobject,
                                    (void*)write_buf,
                                    sizeof(SDCARD_WRITE_BLOCK),
                                    &num_written);
  // Free up memory block.
  OSMemPut(mem_part_ptr[MEM_PART_100], write_buf);
  OSSemPost(mem_sem_ptr[MEM_PART_100]);

  if ((psocket->file_op_result != FR_OK) || (sizeof(SDCARD_WRITE_BLOCK) != num_written))
  {
    // Write failed. Try to close the file.
    f_close(psocket->pfobject);                 // Don't care about its result.
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "Trouble writing file on SD Card #%u: <%s>: Error <%s>.\n",
                   drv,
                   psocket->fpath,
                   FError2Str(psocket->file_op_result));
    TADEBUG_STR(tmp_str, 100, &err);
    goto exit_SDCS;                             // Failed write.
  }
  psocket->status.s.notwrite = FALSE;           // Successfully wrote data.

  // Close the file, which will flush the data to the SD Card.
  if (psocket->file_op_type = FOPTYPE_FCLOSE, (psocket->file_op_result = f_close(psocket->pfobject)) != FR_OK)
  {
    // There was a problem closing the file.
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "Trouble closing file on SD Card #%u: <%s>: Error <%s>.\n",
                   drv,
                   psocket->fpath,
                   FError2Str(psocket->file_op_result));
    TADEBUG_STR(tmp_str, 100, &err);
    goto exit_SDCS;                             // Failed close.
  }
  psocket->status.s.notclose = FALSE;           // Successfully closed file.

exit_SDCS:
  if (tmp_str)
  {
    // Free up memory block.
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }
  // Some record keeping to allow for sending debug and/or status data.
  // Any one status bit high is cause to record a failure.
  if (psocket->status.all)
    psocket->recsave_fail_cnt++;
  else
    psocket->recsave_good_cnt++;
} // SDCard_Save


// ................................................................................................
// Search through the current directory for the next available file name and open a file using that name.
//  Keeps on trying until a file name has been found.
//  All file variables are public at this time. ('fobject', which is tied into 'fatfs'.)
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    drv                 - Select the SD Card to use.
// Returns:
//    BOOL                - If TRUE, successfully found a file that it was able to manipulate.
//                          If FALSE, there is an issue with getting a file ready for use. Probably
//                            will need to skip the write of data.
//
static BOOL Open_New_File(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t drv)
{
  uint8_t               err;
  uint8_t               fail_attempts;
  char                  *tmp_str;
  uint16_t              tmp_val;
  PRSTI_SDCARD_TRACKING psocket = &pTaskInfo->pBuf->sdcard[drv];

  if (drv >= NUM_SDCARDS)
    // The only place to return early without going through the exit stage.
    return FALSE;
  if (f_chdrive(drv) != FR_OK)
    return FALSE;

  psocket->file_op_result = FR_OK;
  tmp_val = 0;
  fail_attempts = 0;

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);

  // It this time we're keeping the SD Card powered on. Later we can use 'notpresent' to indicate
  //  its state.
  if (psocket->status.s.notpresent || psocket->status.s.notvaliddir)
  {
    USPI_Device_Init(drv);                      // Configure the hardware (possibly because it was
    USPI_Device_PowerOn(drv);                   //  turned off). Then turn it on.
    // SD Card has not (yet) been detected. Need to set up the FAT and the path to the data directory.
    psocket->status.all = 0xFF;                 // Set all status to read 'bad'.
    psocket->status.s.therest = 0x00;           // Set all unused bits to "good" status.
    // Continue only when it sees something's mounted.
    if (psocket->file_op_type = FOPTYPE_FCHDIR, (psocket->file_op_result = f_chdir("/Data")) != FR_OK)
    {
      if (psocket->file_op_result == FR_NO_PATH)
      {
        // No path found to where we'd want to store the data. Try to create it.
        //  This is usually due to an oversight when formatting the drive and forgetting to create
        //  the folder.
        if (psocket->file_op_type = FOPTYPE_FCHDIR, (psocket->file_op_result = f_chdir("/")) != FR_OK)
        {
          // Can't set it to the top level directory. Something's severely wrong.
          // Once I get some time, maybe I can get more creative in deciding what to do next.
          // For now exit without setting up the FAT. (Failing.)
          snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                         "Trouble changing directory to '\\' on SD Card #%u: Error <%s>.\n",
                         drv,
                         FError2Str(psocket->file_op_result));
          TADEBUG_STR(tmp_str, 100, &err);
          // Status already set to bad.
        }
        TADEBUG_STR("Attempt to create '\\Data' directory, since it is not found.\n", 100, &err);
        if (psocket->file_op_type = FOPTYPE_FMKDIR, (psocket->file_op_result = f_mkdir("data")) != FR_OK)
        {
          TADEBUG_STR("Can't create directory '\\Data'.\n", 100, &err);
          // Status already set to bad.
        }
        else
          TADEBUG_STR("Created directory '\\Data'.\n", 100, &err);
          psocket->status.s.notvaliddir = FALSE;
          psocket->status.s.notpresent = FALSE;
      }
      else
      {
        // Some other file error. Just report and abort file opening.
        snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                       "Trouble changing directory to '\\Data' on SD Card #%u: Error <%s>.\n",
                       drv,
                       FError2Str(psocket->file_op_result));
        TADEBUG_STR(tmp_str, 100, &err);
        // Status already set to bad.
      }
    }
    else
    {
      // It's happy with the change directory command. We know '\Data' exists.
      psocket->status.s.notvaliddir = FALSE;
      psocket->status.s.notpresent = FALSE;
    }

    if (!psocket->status.s.notpresent && !psocket->status.s.notvaliddir)
    {
      // Now that we know there is a \Data directory, park at the root, since the file name will
      //  include a complete path.
      if (psocket->file_op_type = FOPTYPE_FCHDIR, (psocket->file_op_result = f_chdir("/")) != FR_OK)
      {
        // Can't get back to root.
        psocket->status.s.notpresent = TRUE;
        snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                       "Trouble changing directory to 'root' on SD Card #%u: Error <%s>.\n",
                       drv,
                       FError2Str(psocket->file_op_result));
        TADEBUG_STR(tmp_str, 100, &err);
        psocket->status.s.notvaliddir = TRUE;
      }
    }
  }

  if (!psocket->status.s.notpresent && !psocket->status.s.notvaliddir)
  {
    // At this time the FAT is set to point to the root directory, and we know there is a '\Data'
    //  directory..
    // 'fresult' is set to  FR_OK.
    do
    {
      if (psocket->file_op_result != FR_OK && psocket->file_op_result != FR_EXIST)
      {
        // Open didn't work, but not because it exists.
        snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                       "File open failed for file % 8d, Path = <%s>: %s.\n",
                       tmp_val-1,
                       psocket->fpath,
                       FError2Str(psocket->file_op_result));
        TADEBUG_STR(tmp_str, 100, &err);
        if (++fail_attempts > 100)              // Don't know how long this takes. May need to adjust.
        {
          // Failed a lot. Continue without enabling file storage.
          psocket->status.s.notcreated = TRUE;
          psocket->status.s.notpresent = TRUE;  // Someone may have taken it out. Next time, start
                                                //  from scratch.
          goto exit_ONF;
        }
      }
      // File exists or it doesn't like the previous file name|path. Create next file name.
      if (snprintf(psocket->fpath, 30, "%s%s%06u%s", cpathname, cfilename, tmp_val++, cextension) >= 30)
      {
        // Just announce that the path was truncated. This shouldn't happen, since we keep a simple,
        //  one-level path and a 8.3 filename. If the truncated path works, so be it.
        snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                       "File path too long for file % 8d, Path = <%s>: %s.\n",
                       tmp_val-1,
                       psocket->fpath,
                       FError2Str(psocket->file_op_result));
        TADEBUG_STR(tmp_str, 100, &err);
      }
      // Keep at it until we either come to the next file name that is unique, or we have failed too
      //  many times.
    } while (psocket->file_op_type = FOPTYPE_FOPEN, (psocket->file_op_result = f_open(psocket->pfobject, psocket->fpath, FA_CREATE_NEW | FA_WRITE)) != FR_OK);
    // File has been created and is now available for use.
    psocket->status.s.notcreated = FALSE;
    // Report find.
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "Final open result for file #% 8d, Path = <%s>: %s.\n",
                   tmp_val-1,
                   psocket->fpath,
                   FError2Str(psocket->file_op_result));
    TADEBUG_STR(tmp_str, 100, &err);
    psocket->file_number = tmp_val-1;           // Hold on to file number for block tagging use.
    // Close file. Each data save will open it for its own use each time.
    if (psocket->file_op_type = FOPTYPE_FCLOSE, (psocket->file_op_result = f_close(psocket->pfobject)) != FR_OK)
    {
      snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                     "Trouble closing file % 8d, Path = <%s>: %s.\n",
                     tmp_val-1,
                     psocket->fpath,
                     FError2Str(psocket->file_op_result));
      TADEBUG_STR(tmp_str, 100, &err);
      psocket->status.s.notcreated = TRUE;      // That means something is wrong. Don't allow writing.
    }
  }

exit_ONF:
  if (psocket->status.s.notpresent || psocket->status.s.notvaliddir)
  {
    // Card was deemed not present or not a valid directory.
    USPI_Device_Disable(drv);                   // Turn off power in an attempt to restart from scratch.
    snprintf_PSTR(tmp_str, MEM_PART_SIZE_100,
                   "SD Card access issues. Powering down card slot #%u.\n", drv);
    TADEBUG_STR(tmp_str, 100, &err);
  }
  if (tmp_str)
  {
    // Free up memory block.
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }

  // Return TRUE only when SD Card is present, has a valid directory, and exists.
  return ((psocket->status.all & (SDCSTAT_nPRESENT | SDCSTAT_nVALIDDIR | SDCSTAT_nCREATED)) == 0);
} // Open_New_File
