#include "wled.h"

// SD connected via MMC / SPI
#if defined(WLED_USE_SD_MMC)
  #define USED_STORAGE_FILESYSTEMS "SD MMC, LittleFS"
  #define SD_ADAPTER SD_MMC
  #include "SD_MMC.h"
// SD connected via SPI (adjustable via usermod config)
#elif defined(WLED_USE_SD_SPI)
  #define SD_ADAPTER SD
  #define USED_STORAGE_FILESYSTEMS "SD SPI, LittleFS"
  #include "SD.h"
  #include "SPI.h"
#endif

#ifdef WLED_USE_SD_MMC
#elif defined(WLED_USE_SD_SPI)
  SPIClass spiPort = SPIClass(VSPI);
#endif

void listDir( const char * dirname, uint8_t levels);

class UsermodSdCard : public Usermod {
  private:
    bool sdInitDone = false;

    #ifdef WLED_USE_SD_SPI
      int8_t configPinSourceSelect = 16;
      int8_t configPinSourceClock = 14;
      int8_t configPinPoci = 36; // confusing names? Then have a look :)
      int8_t configPinPico = 15; // https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/

      //acquired and initialize the SPI port
      void init_SD_SPI()
      {
        if(!configSdEnabled) return;
        if(sdInitDone) return;

        PinManagerPinType pins[5] = {
        { configPinSourceSelect, true },
        { configPinSourceClock, true },
        { configPinPoci, false },
        { configPinPico, true }
        };

        if (!PinManager::allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) {
            DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name);
            sdInitDone = false;
            return;
        }

        bool returnOfInitSD = false;

        #if defined(WLED_USE_SD_SPI)
          spiPort.begin(configPinSourceClock, configPinPoci, configPinPico, configPinSourceSelect);
          returnOfInitSD = SD_ADAPTER.begin(configPinSourceSelect, spiPort);
        #endif

        if(!returnOfInitSD) {
          DEBUG_PRINTF("[%s] SPI begin failed!\n", _name);
          sdInitDone = false;
          return;
        }

        sdInitDone = true;
      }

      //deinitialize the acquired SPI port
      void deinit_SD_SPI()
      {
        if(!sdInitDone) return;

        SD_ADAPTER.end();

        DEBUG_PRINTF("[%s] deallocate pins!\n", _name);
        PinManager::deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard);
        PinManager::deallocatePin(configPinSourceClock,  PinOwner::UM_SdCard);
        PinManager::deallocatePin(configPinPoci,         PinOwner::UM_SdCard);
        PinManager::deallocatePin(configPinPico,         PinOwner::UM_SdCard);

        sdInitDone = false;
      }

      // some SPI pin was changed, while SPI was initialized, reinit to new port
      void reinit_SD_SPI()
      {
          deinit_SD_SPI();
          init_SD_SPI();
      }
    #endif

    #ifdef WLED_USE_SD_MMC
      void init_SD_MMC() {
        if(sdInitDone) return;
        bool returnOfInitSD = false;
        returnOfInitSD = SD_ADAPTER.begin();
        DEBUG_PRINTF("[%s] MMC begin\n", _name);

        if(!returnOfInitSD) {
          DEBUG_PRINTF("[%s] MMC begin failed!\n", _name);
          sdInitDone = false;
          return;
        }

        sdInitDone = true;
      }
    #endif

  public:
    static bool configSdEnabled;
    static const char _name[];

    void setup() {
      DEBUG_PRINTF("[%s] usermod loaded \n", _name);
      #if defined(WLED_USE_SD_SPI)
        init_SD_SPI();
      #elif defined(WLED_USE_SD_MMC)
        init_SD_MMC();
      #endif
 
      #if defined(SD_ADAPTER) && defined(SD_PRINT_HOME_DIR)  
        listDir("/", 0);        
      #endif
    }

    void loop(){
      
    }

    uint16_t getId()
    {
      return USERMOD_ID_SD_CARD;
    }

    void addToConfig(JsonObject& root)
    {
      #ifdef WLED_USE_SD_SPI
      JsonObject top = root.createNestedObject(FPSTR(_name));
      top["pinSourceSelect"] = configPinSourceSelect;
      top["pinSourceClock"] = configPinSourceClock;
      top["pinPoci"] = configPinPoci;
      top["pinPico"] = configPinPico;
      top["sdEnabled"] = configSdEnabled;
      #endif
    }

    bool readFromConfig(JsonObject &root)
    {
      #ifdef WLED_USE_SD_SPI
        JsonObject top = root[FPSTR(_name)];
        if (top.isNull()) {
          DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name);
          return false;
        }

        uint8_t oldPinSourceSelect  = configPinSourceSelect;
        uint8_t oldPinSourceClock = configPinSourceClock;
        uint8_t oldPinPoci = configPinPoci;
        uint8_t oldPinPico = configPinPico;
        bool    oldSdEnabled = configSdEnabled;

        getJsonValue(top["pinSourceSelect"], configPinSourceSelect);
        getJsonValue(top["pinSourceClock"],  configPinSourceClock);
        getJsonValue(top["pinPoci"],         configPinPoci);
        getJsonValue(top["pinPico"],         configPinPico);
        getJsonValue(top["sdEnabled"],       configSdEnabled);

        if(configSdEnabled != oldSdEnabled) {
          configSdEnabled ? init_SD_SPI() : deinit_SD_SPI();
          DEBUG_PRINTF("[%s] SD card %s\n", _name, configSdEnabled ? "enabled" : "disabled");
        }

        if( configSdEnabled && (
            oldPinSourceSelect  != configPinSourceSelect ||
            oldPinSourceClock   != configPinSourceClock  ||
            oldPinPoci          != configPinPoci         ||
            oldPinPico          != configPinPico)
          )
        {
          DEBUG_PRINTF("[%s] Init SD card based of config\n", _name);
          DEBUG_PRINTF("[%s] Config changes \n - SS: %d -> %d\n - MI: %d -> %d\n - MO: %d -> %d\n - En: %d -> %d\n", _name, oldPinSourceSelect, configPinSourceSelect, oldPinSourceClock, configPinSourceClock, oldPinPoci, configPinPoci, oldPinPico, configPinPico);
          reinit_SD_SPI();
        }
      #endif

      return true;
    }
};

const char UsermodSdCard::_name[] PROGMEM = "SD Card";
bool UsermodSdCard::configSdEnabled = true;

#ifdef SD_ADAPTER
//checks if the file is available on SD card
bool file_onSD(const char *filepath)
{
  #ifdef WLED_USE_SD_SPI
    if(!UsermodSdCard::configSdEnabled) return false;
  #endif

  uint8_t cardType = SD_ADAPTER.cardType();
  if(cardType == CARD_NONE) {
    DEBUG_PRINTF("[%s] not attached / cardType none\n", UsermodSdCard::_name);
    return false; // no SD card attached
  }
  if(cardType == CARD_MMC || cardType == CARD_SD || cardType == CARD_SDHC)
  {
    return SD_ADAPTER.exists(filepath);
  }

  return false; // unknown card type
}

void listDir( const char * dirname, uint8_t levels){
    DEBUG_PRINTF("Listing directory: %s\n", dirname);

    File root = SD_ADAPTER.open(dirname);
    if(!root){
        DEBUG_PRINTF("Failed to open directory\n");
        return;
    }
    if(!root.isDirectory()){
        DEBUG_PRINTF("Not a directory\n");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            DEBUG_PRINTF("  DIR : %s\n",file.name());
            if(levels){
                listDir(file.name(), levels -1);
            }
        } else {
            DEBUG_PRINTF("  FILE: %s  SIZE: %d\n",file.name(), file.size());
        }
        file = root.openNextFile();
    }
}

#endif

static UsermodSdCard sd_card;
REGISTER_USERMOD(sd_card);