/**
   @file micro_sd.cpp

   @brief library for communication with micro-SD card

   @author Miroslav Pivovarsky
   Contact: miroslav.pivovarsky@gmail.com

   @bug: no know bug
*/

#include "micro_sd.h"

/**
   @brief Constructor
   @param none
   @return none
*/
MicroSd::MicroSd() {
  CardDetected = false;
  CardSize = 0;
  DetectAfterBoot = false;
}

/**
   @brief Reinit micro SD card
   @param none
   @return none
*/
void MicroSd::ReinitCard() {
  Serial.println("Reinit micro SD card!");
  Serial.println("Deinit micro SD card");
  SD_MMC.end();
  delay(50);
  Serial.println("Init micro SD card");
  InitSdCard();
}

/**
   @brief Init SD card. And check, if is SD card inserted
   @param none
   @return none
*/
void MicroSd::InitSdCard() {
  /* Start INIT Micro SD card */
  Serial.println("Start init micro-SD Card");

  /* set SD card to 1-line/1-bit mode. GPIO 4 is used for LED and for microSD card. But communication is slower. */
  /* https://github.com/espressif/arduino-esp32/blob/master/libraries/SD_MMC/src/SD_MMC.h */
  if (!SD_MMC.begin("/sdcard", true)) {
    Serial.println("SD Card Mount Failed");
    CardDetected = false;
    CardSize = 0;
    //DetectAfterBoot = false;
    return;
  }

  /* check microSD card and card type */
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD_MMC card attached");
    CardDetected = false;
    CardSize = 0;
    //DetectAfterBoot = false;
    return;
  }

  /* print card type */
  Serial.print("Found card. Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.print("MMC");
  } else if (cardType == CARD_SD) {
    Serial.print("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.print("SDHC");
  } else {
    Serial.print("UNKNOWN");
  }

  /* calculation card size */
  CardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf(", Card Size: %d MB\n", CardSize);
  CardDetected = true;
  DetectAfterBoot = true;
}

/**
   @brief List directory on the micro SD card
   @param fs::FS - card
   @param String - Directory name
   @param uint8_t - levels
   @return none
*/
void MicroSd::ListDir(fs::FS &fs, String DirName, uint8_t levels) {
  if (true == CardDetected) {
    Serial.printf("Listing directory: %s\n", DirName.c_str());

    File root = fs.open(DirName.c_str());
    if (!root) {
      Serial.println("Failed to open directory");
      return;
    }
    if (!root.isDirectory()) {
      Serial.println("Not a directory");
      return;
    }

    File file = root.openNextFile();
    while (file) {
      if (file.isDirectory()) {
        Serial.print("  DIR : ");
        Serial.println(file.name());
        if (levels) {
          ListDir(fs, file.path(), levels - 1);
        }
      } else {
        Serial.print("  FILE: ");
        Serial.print(file.name());
        Serial.print("  SIZE: ");
        Serial.println(file.size());
      }
      file = root.openNextFile();
    }
  }
}

/**
   @brief List directory on the micro SD card
   @param fs::FS - card
   @param String - dir name
   @return bool - status
*/
bool MicroSd::CreateDir(fs::FS &fs, String path) {
  bool status = false;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Creating Dir: %s... ", path.c_str());
#endif

    if (fs.mkdir(path.c_str())) {
      status = true;
    }

#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.println((status == true) ? "Created" : "Failed");
#endif
  }
  return status;
}

/**
   @brief remove directory on the micro SD card
   @param fs::FS - card
   @param String - dir name
   @return bool - status
*/
bool MicroSd::RemoveDir(fs::FS &fs, String path) {
  bool status = false;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Removing Dir: %s... ", path.c_str());
#endif

    if (fs.rmdir(path.c_str())) {
      status = true;
    }

#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.println((status == true) ? "Removed" : "Failed");
#endif
  }
  return status;
}

/**
   @brief Read file and print data to console
   @param fs::FS - card
   @param String - file name
   @return none
*/
void MicroSd::ReadFileConsole(fs::FS &fs, String path) {
  if (true == CardDetected) {
    Serial.printf("Reading file: %s\n", path.c_str());

    File file = fs.open(path.c_str());
    if (!file) {
      Serial.println("Failed to open file for reading");
      return;
    }

    Serial.print("Read from file: ");
    while (file.available()) {
      Serial.write(file.read());
    }
  }
}

/**
   @brief Write message to file
   @param fs::FS - card
   @param String - file name
   @param String - message
   @return bool - status
*/
bool MicroSd::WriteFile(fs::FS &fs, String path, String message) {
  bool status = false;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Writing file: %s... ", path.c_str());
#endif

    File file = fs.open(path.c_str(), FILE_WRITE);
    if (!file) {
#if (true == CONSOLE_VERBOSE_DEBUG)
      Serial.printf("Failed to open file for writing");
#endif
    } else {
      if (file.print(message.c_str())) {
        status = true;
      }

#if (true == CONSOLE_VERBOSE_DEBUG)
      Serial.println((status == true) ? "File written" : "Write Failed");
#endif
    }
  }
  return status;
}

/**
   @brief Added text to end of file
   @param fs::FS - card
   @param String - file name
   @param String - message
   @return bool - status
*/
bool MicroSd::AppendFile(fs::FS &fs, String path, String message) {
  bool status = false;

  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Appending to file: %s... ", path.c_str());
#endif

    File file = fs.open(path.c_str(), FILE_APPEND);
    if (!file) {
#if (true == CONSOLE_VERBOSE_DEBUG)
      Serial.println("Failed to open file for appending");
#endif
      CardDetected = false;
    } else {
      if (file.print(message.c_str())) {
        status = true;
      } 

#if (true == CONSOLE_VERBOSE_DEBUG)
      Serial.println((status == true) ? "Message appended" : "Append Failed");
#endif
    }
  }
  return status;
}

/**
   @brief Rename file on the SD card
   @param fs::FS - card
   @param String - origin file name
   @param String - new file name
   @return bool - status
*/
bool MicroSd::RenameFile(fs::FS &fs, String path1, String path2) {
  bool status = false;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Renaming file %s to %s... ", path1.c_str(), path2.c_str());
#endif
    if (fs.rename(path1.c_str(), path2.c_str())) {
      status = true;
    }

#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.println((status == true) ? "File renamed" : "Rename Failed");
#endif
  }

  return status;
}

/**
   @brief Delete file on the SD card
   @param fs::FS - card
   @param String - file name
   @return bool - status
*/
bool MicroSd::DeleteFile(fs::FS &fs, String path) {
  bool status = false;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Deleting file: %s... ", path.c_str());
#endif
    if (fs.remove(path.c_str())) {
      status = true;
    }

#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.println((status == true) ? "File deleted" : "Delete Failed");
#endif
  }

  return status;
}

/**
   @brief Get file size in the kb
   @param fs::FS - card
   @param String - file name
   @return uint32_t - size
*/
uint32_t MicroSd::GetFileSize(fs::FS &fs, String path) {
  uint32_t ret = 0;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("Getting file size: %s... ", path.c_str());
#endif
    File file = fs.open(path.c_str(), FILE_APPEND);
    if (!file) {
#if (true == CONSOLE_VERBOSE_DEBUG)
      Serial.println("Failed to open file for appending");
#endif
      return 0;
    }

    ret = file.size() / 1024; /* convert from bytes to kb */
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf(" File size: %d\n ", ret);
#endif
  }

  return ret; /* kb*/
}

/**
   @brief Check file count with partial match
   @param fs::FS - card
   @param String - dir name
   @param String - file name
   @return int16_t - count
*/
uint16_t MicroSd::FileCount(fs::FS &fs, String DirName, String FileName) {
  uint16_t FileCount = 0;
  if (true == CardDetected) {
#if (true == CONSOLE_VERBOSE_DEBUG)
    Serial.printf("File name count: %s\n", DirName.c_str());
#endif

    File root = fs.open(DirName.c_str());
    if (!root) {
      Serial.println("Failed to open directory");
      return 0;
    }
    if (!root.isDirectory()) {
      Serial.println("Not a directory");
      return 0;
    }

    File file = root.openNextFile();
    while (file) {
      if (!file.isDirectory()) {
#if (true == CONSOLE_VERBOSE_DEBUG)
        Serial.print("  FILE: ");
        Serial.print(file.name());
        Serial.print("  SIZE: ");
        Serial.print(file.size());
#endif
        if (String(file.name()).indexOf(FileName) != -1) {
          FileCount++;
#if (true == CONSOLE_VERBOSE_DEBUG)
          Serial.print(" - MATCH");
#endif
        }
#if (true == CONSOLE_VERBOSE_DEBUG)
        Serial.println("");
#endif
      }
      file = root.openNextFile();
    }
  }

  return FileCount;
}

/**
   @brief Get card detected status
   @param none
   @return bool - status
*/
bool MicroSd::GetCardDetectedStatus() {
  return CardDetected;
}

/**
   @brief Get card size
   @param none
   @return uint16_t - size
*/
uint16_t MicroSd::GetCardSize() {
  return CardSize;
}

/**
   @brief Get card detect after boot
   @param none
   @return bool - status
*/
bool MicroSd::GetCardDetectAfterBoot() {
  return DetectAfterBoot;
}

/* EOF */