
/** \file Coordinating reading and writing of SD cards.
*/

#include "sd.h"

#ifdef SD

#include "delay.h"
#include "pinio.h"
#include "serial.h"
#include "sersendf.h"
#include "gcode_parse.h"


static FATFS sdfile;
static FRESULT result;

/** Initialize SPI for SD card reading.
*/
void sd_init(void) {
  SET_OUTPUT(SD_CARD_SELECT_PIN);
  WRITE(SD_CARD_SELECT_PIN, 1);
}

/** Mount the SD card.
*/
void sd_mount(void) {
  result = pf_mount(&sdfile);
  if (result != FR_OK)
    sersendf_P(PSTR("E: SD init failed. (%su)\n"), result);
}

/** Unmount the SD card.

  This makes just sure subsequent reads to the card do nothing, instead of
  trying and failing. Not mandatory, just removing the card is fine, as well
  as inserting and mounting another one without previous unmounting.
*/
void sd_unmount(void) {
  pf_unmount(&sdfile);
}

/** List a given directory.

  \param path The path to list. Toplevel path is "/".

  A slash is added to directory names, to make it easier for users to
  recognize them.
*/
void sd_list(const char* path) {
  FILINFO fno;
  DIR dir;

  result = pf_opendir(&dir, path);
  if (result == FR_OK) {
    for (;;) {
      result = pf_readdir(&dir, &fno);
      if (result != FR_OK || fno.fname[0] == 0)
        break;
      serial_writestr((uint8_t *)fno.fname);
      if (fno.fattrib & AM_DIR)
        serial_writechar('/');
      serial_writechar('\n');
      delay_ms(2); // Time for sending the characters.
    }
  }
  else {
    sersendf_P(PSTR("E: failed to open dir. (%su)\n"), result);
  }
}

/** Open a file for reading.

  \param filename Name of the file to open and to read G-code from.

  Before too long this will cause the printer to read G-code from this file
  until done or until stopped by G-code coming in over the serial line.
*/
void sd_open(const char* filename) {
  result = pf_open(filename);
  if (result != FR_OK) {
    sersendf_P(PSTR("E: failed to open file. (%su)\n"), result);
  }
}

/** Read a line of G-code from a file.

  \param A pointer to the parser function. This function should accept an
         uint8_t with the character to parse and return an uint8_t whether
         end of line (EOL) was reached.

  \return Whether end of line (EOF) was reached or an error happened.

  Juggling with a buffer smaller than 512 bytes means that the underlying
  SD card handling code reads a full sector (512 bytes) in each operation,
  but throws away everything not fitting into this buffer. Next read operation
  reads the very same sector, but keeps a different part. That's ineffective.

  Much better is to parse straight as it comes from the card. This way there
  is no need for a buffer at all. Sectors are still read multiple times, but
  at least one line is read in one chunk (unless it crosses a sector boundary).
*/
uint8_t sd_read_gcode_line(void) {

  result = pf_parse_line(&gcode_parse_char);
  if (result == FR_END_OF_FILE) {
    return 1;
  }
  else if (result != FR_OK) {
    sersendf_P(PSTR("E: failed to parse from file. (%su)\n"), result);
    return 1;
  }

  return 0;
}

#endif /* SD */
