#include <Arduino.h>
#include <M5ez.h>
#include <WiFi.h>
#include <Artnet.h>
#include <ESPDMX.h>

// WiFi AP credentials
const char* ssid     = "AtlasAP";
const char* password = "feuilles";

Artnet artnet;
DMXESPSerial dmx;

byte dmx_buffer[512] ;

enum  { IDLE,
        PLAYBACK,
        ARTNET,
        RECORD
        } dmx_state ;


File dmx_data;
const String dmx_file = "/data";


//////////////////////////////////    WIFI    //////////////////////////////////

void ClientConnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("New client connected to AP");
}

void ClientDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("Client disconnected from AP");
}

void ClientIP(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("Client has IP");
}

//////////////////////////////////    HELPERS    //////////////////////////////////

void print_artnet(){
  Serial.print("universe number = ");
  Serial.print(artnet.getUniverse());
  Serial.print("\tdata length = ");
  Serial.print(artnet.getLength());
  Serial.print("\tsequence n0. = ");
  Serial.println(artnet.getSequence());
  Serial.print("DMX data: ");
  for (int i = 0 ; i < artnet.getLength() ; i++)
  {
    Serial.print(artnet.getDmxFrame()[i]);
    Serial.print("  ");
  }
  Serial.println();
  Serial.println();
}

//////////////////////////////////     SD      //////////////////////////////////

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    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.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\n", path);

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

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

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("File renamed");
    } else {
        Serial.println("Rename failed");
    }
}

void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}


//////////////////////////////////    TASKS    //////////////////////////////////

uint16_t artnet_rate = 20 ;
uint16_t dmx_rate = 40;

uint16_t dmx_loop(){

  for (int i = 0; i < 512; i++)
  {
    dmx.write(i, dmx_buffer[i]);
  }
  dmx.update(); 

  return dmx_rate ;  
}

uint16_t readFile_dmx(){

  
  if(dmx_data.available()){
    //read 512 bytes
    dmx_data.read(dmx_buffer, 512);
  }
  else{ //open the file
    dmx_data = SD.open(dmx_file, FILE_READ);
    if(!dmx_data){
      Serial.println("No data file found, going to idle mode");
      dmx_state = IDLE ;
      return 0; //cancel this event
    } 
  }
  
  return dmx_rate ;

}

uint16_t writeFile_dmx(){
  dmx_data.write(dmx_buffer, 512);
  return dmx_rate ;
}

uint16_t artnet_loop(){
 if (artnet.read() == ART_DMX)  {
    // Serial.print("got universe ");
    // Serial.println(artnet.getUniverse());
    // print out our data
    // print_artnet();
    
    //Check channel 512 for commands : 0 to playback dmx file, 255 to start recording, any value in between for realtime mode (and stop recording)
    int dmx_command ;
    if(artnet.getUniverse()==0){dmx_command = artnet.getDmxFrame()[511]; }
    //first cancel previous tasks if command changed (reading or recording the file)
    if( (dmx_state==RECORD) && (dmx_command!=255) ){
      dmx_data.close() ;
      ez.removeEvent(writeFile_dmx) ;
    }
    if( (dmx_state==PLAYBACK) && (dmx_command!=0) ){
      dmx_data.close() ;
      ez.removeEvent(readFile_dmx) ;
    }

    //then apply  command if needed
    if (dmx_command == 0 ) {
      if (dmx_state != PLAYBACK){
        dmx_state = PLAYBACK ;
        ez.addEvent(readFile_dmx) ;
        ez.msgBox("Artnet2dmx", "PLAYBACK", "", false);
      }
    }
    else if (dmx_command == 255) {
      if(dmx_state!=RECORD){
        dmx_state = RECORD ;
        dmx_data=SD.open(dmx_file, FILE_WRITE);
        ez.addEvent(writeFile_dmx) ;
        ez.msgBox("Artnet2dmx", "RECORD", "", false);
      }      
    }
    else if ((dmx_command!=0) && (dmx_command!=255)){
      if(dmx_state != ARTNET){
        dmx_state = ARTNET ; 
        ez.msgBox("Artnet2dmx", "ARTNET", "", false);
      }
    }
    

    //copy artnet data in dmx buffer if in ARTNET or RECORD mode
    if ((dmx_state == RECORD) || (dmx_state == ARTNET)){
      if(artnet.getUniverse()==0){
        for (int i = 0 ; i < 512 ; i++){
          dmx_buffer[i] = byte(artnet.getDmxFrame()[i]);
        }
      }
    }
  } 
  return artnet_rate ;
}




//////////////////////////////////    SETUP   //////////////////////////////////



void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("start");

  #include <themes/dark.h>
  ez.begin();
  ez.msgBox("Artnet2dmx", "STARTING", "", false);
  String printString;
  
  Serial.println("starting Wifi AP \"Atlas-AP\" with pw \"feuille\"");
  //Register events
  WiFi.onEvent(ClientConnected, SYSTEM_EVENT_AP_STACONNECTED);
  WiFi.onEvent(ClientDisconnected, SYSTEM_EVENT_AP_STADISCONNECTED);
  WiFi.onEvent(ClientIP, SYSTEM_EVENT_AP_STAIPASSIGNED);
  //start AP
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  printString+=("AP IP address: ");
  printString+=IP.toString();

  Serial.println("Opening artnet port");
  artnet.begin();

  Serial.println("Starting DMX and sending blackout");

  dmx.init(512, 17);           // initialization for complete bus
  delay(10);
  for (int i = 0; i < 512; i++)
  {
    dmx.write(i, 0);
  }

  //Starting SD and checking if a file exists already
  if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }

    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);
    listDir(SD, "/", 0);
    // readFile(SD, "/DATA1");


  Serial.println("initialized...");
  


  delay(200);               // wait a while (not necessary)

  ez.addEvent(artnet_loop);
  ez.addEvent(dmx_loop);

ez.msgBox("Artnet2dmx", printString, "", false);

}


//////////////////////////////////    LOOP   //////////////////////////////////


void loop() {

 ez.yield();
 delay(1);

  // for (int i = 0; i < 512; i++)
  // {
  //   dmx.write(i, 255);
  // }
  // Serial.print("turning on...");
  // dmx.update();           // update the DMX bus
  // Serial.println("updated!");
  // delay(1000);            // wait for 1s

  // for (int i = 0; i < 512; i++)
  // {
  //   dmx.write(i, 0);
  // }
  // Serial.print("turning off...");
  // dmx.update();           // update the DMX bus
  // Serial.println("updated!");
  // delay(1000);

}