#pragma once

extern std::vector<uint16_t> melody_steps;

void PublishMqtt(const char * data);

class Player {
public:
	uint16_t id;						  					// The (internal) ID of the selected melody. Not specificly used anywhere atm. Might be used later.
	std::string name = "melody1";       // Name of the Melody saved. Will be used to read the file: /name.bin
	uint16_t speed = 500;               // Time to wait per beat. (In Miliseconds)
  uint32_t duration = 15000;          // Total Duration that program will run (In Miliseconds)
	uint32_t loop_duration = 0;					// Duration of the playback per segment
	uint32_t interval = 0;         			// Indicates the Duration of the Interval between finished segments, IF "inf" is true
  bool infinite_play = false;         // Infinite Loop Indicator (If True the melody will loop forever or until stoped, with pauses of "interval" in between loops)
	bool isPlaying = false;;						// Indicates if the Melody is actually Playing right now.
	bool isPaused = false;							// If playing, indicates if the Melody is Paused
	uint64_t startTime = 0;							// The time-point the Melody started Playing
	uint64_t loopStartTime = 0;					// The time-point the current segment started Playing
	bool hardStop = false;							// Flags a hardstop, immediately.
	uint64_t pauseTime = 0;							// The time-point the melody paused

	void play() {
		isPlaying = true;
		hardStop = false;
		startTime = loopStartTime = millis();
		Serial.println("Plbck: PLAY");
	}

	void forceStop() {
		hardStop = true;
		isPlaying = false;
		Serial.println("Plbck: FORCE STOP");
	}

	void stop() {
		hardStop = false;
		isPlaying = false;
		Serial.println("Plbck: STOP");
	}

	void pause() {
		isPaused = true;
		Serial.println("Plbck: PAUSE");
	}

	void unpause() {
		isPaused = false;
		loopStartTime = millis();
		Serial.println("Plbck: RESUME");
	}

  // Handles Incoming Commands to PLAY or STOP
  void command(char * command){
    Serial.print("INCOMING COMMAND: ");
    Serial.println(command);
    if (command[0] == '1') {
      play();
      PublishMqtt("OK - PLAY");
    } else if (command[0] == '0') {
      forceStop();
      PublishMqtt("OK - STOP");
    }
  }

  // Sets incoming Attributes for the Melody, into the class' variables.
  void setMelodyAttributes(JsonDocument doc){

  	if (doc.containsKey("name")) {
  			name = doc["name"].as<const char*>();
  	}
  	if (doc.containsKey("id")) {
  			id = doc["id"].as<uint16_t>();
  	}
  	if (doc.containsKey("duration")) {
  			duration = doc["duration"].as<uint32_t>();
  	}
  	if (doc.containsKey("infinite")) {
  			infinite_play = doc["infinite"].as<bool>();
  	}
  	if (doc.containsKey("interval")) {
  			interval = doc["interval"].as<uint32_t>();
  	}
  	if (doc.containsKey("speed")) {
  			speed = doc["speed"].as<uint16_t>();
  	}
  	if (doc.containsKey("loop_dur")) {
  			loop_duration = doc["loop_dur"].as<uint32_t>();
  	}
  	// Print Just for Debugging Purposes
  	Serial.printf("Name: %s, ID: %d, Total Duration: %lu, Loop Duration: %lu, Interval:  %d, Speed: %d, Inf: %s\n",
  		name.c_str(),
  		id,
  		duration,
  		loop_duration,
  		interval,
  		speed,
  		infinite_play ? "true" : "false"
  		);
  }

	// Loads the Selected melody from a .bin file, into RAM
	void loadMelodyInRAM(std::vector<uint16_t> &melody_steps) {

		std::string filePath = "/" + name + ".bin";
		Serial.println("New Melody Selected !!!");
		Serial.println("Reading data from file...");

		File bin_file = SPIFFS.open(filePath.c_str(), "r");
		if (!bin_file) {
			Serial.println("Failed to Open File");
			return;
		}

		size_t fileSize = bin_file.size();
		size_t steps = fileSize / 2;
		melody_steps.resize(steps);

		Serial.print("Opened File ! Size: ");
		Serial.print(fileSize);
		Serial.print(" Steps: ");
		Serial.println(steps);

		for (size_t i=0; i<steps; i++){
			melody_steps[i] = bin_file.read() << 8 | bin_file.read();
		}

		for (size_t i=0; i<steps; i++){
			Serial.print("Current Step: ");
			Serial.printf("%03d  // ", i);
			Serial.print("  HEX Value: ");
			Serial.printf("0x%04X\n", melody_steps[i]);
		}
		Serial.println("Closing File");
		bin_file.close();

		// closing the file

	}

};

class TimeKeeper {
private:

    // holds the daily scheduled melodies
    struct ScheduleEntry {
      std::string mel;
      uint8_t hour;
      uint8_t minute;
      uint16_t speed;
      uint16_t duration;
    };

    struct TimeNow {
      uint8_t hour;
      uint8_t minute;
      uint8_t second;
    };

    std::vector<ScheduleEntry> dailySchedule;
    TimeNow now;

    // update to current time
    void updateTime(){
      now.hour = getHour();
      now.minute = getMinute();
      now.second = getSecond();
    }

    // Read the current Month's JSON file and add the daily entries to the dailySchedule
    void loadDailySchedule() {
    std::string filePath = "/" + getMonth() + ".json";
    File file = SPIFFS.open(filePath.c_str(), "r");
    if (!file) {
        Serial.printf("Failed to open file: %s\n", filePath.c_str());
        return;
    }

    Serial.println("Opened daily schedule file");
    StaticJsonDocument<8192> doc; // Adjust size based on expected JSON complexity
    DeserializationError error = deserializeJson(doc, file);
    file.close();

    if (error) {
        Serial.printf("Failed to parse JSON: %s\n", error.c_str());
        return;
    }

    Serial.println("- - - SERIALIZE JSON - - -");
    serializeJsonPretty(doc, Serial);
    Serial.println("- - - -  END JSON  - - - -");

    // Extract entries for the current day
    std::string currentDay = getDay();
    dailySchedule.clear(); // Clear previous day's schedule

    Serial.printf("Current Day: %s\n", currentDay.c_str());

    if (doc.containsKey(currentDay.c_str())) {
        Serial.println("doc contains key!");

        // Check if the current day contains a single object or an array
        JsonVariant dayVariant = doc[currentDay.c_str()];

        if (dayVariant.is<JsonArray>()) {
            // Handle case where the day contains an array of entries
            JsonArray dayEntries = dayVariant.as<JsonArray>();
            for (JsonObject entry : dayEntries) {
                ScheduleEntry schedule;
                schedule.mel = entry["name"].as<std::string>();
                schedule.hour = entry["hour"];
                schedule.minute = entry["minute"];
                schedule.speed = entry["speed"];
                schedule.duration = entry["duration"];
                dailySchedule.push_back(schedule);

                Serial.printf("Added Entry - Melody: %s, Hour: %d, Minute: %d, Speed: %d, Duration: %d\n",
                               schedule.mel.c_str(), schedule.hour, schedule.minute, schedule.speed, schedule.duration);
            }
        } else if (dayVariant.is<JsonObject>()) {
            // Handle case where the day contains a single object
            JsonObject entry = dayVariant.as<JsonObject>();
            ScheduleEntry schedule;
            schedule.mel = entry["name"].as<std::string>();
            schedule.hour = entry["hour"];
            schedule.minute = entry["minute"];
            schedule.speed = entry["speed"];
            schedule.duration = entry["duration"];
            dailySchedule.push_back(schedule);

            Serial.printf("Added Single Entry - Melody: %s, Hour: %d, Minute: %d, Speed: %d, Duration: %d\n",
                           schedule.mel.c_str(), schedule.hour, schedule.minute, schedule.speed, schedule.duration);
        } else {
            Serial.println("Invalid data format for the current day.");
        }
    } else {
        Serial.println("No schedule found for today.");
    }
}

    // Calls "loadDailySchedule" and returns true ONCE per new day (00:00)
    bool newDayCheck() {

      // Static variable to store whether we've already detected the new day
      static bool alreadyTriggered = false;
      // Check if it's midnight
      if (now.hour == 0 && now.minute == 0) {
          if (!alreadyTriggered) {
              // First time at midnight, trigger the event
              alreadyTriggered = true;
              Serial.println("New day detected, returning true.");
              loadDailySchedule();
              return true;
          } else {
              // It's still midnight, but we've already triggered
              Serial.println("Already triggered for today, returning false.");
              return false;
          }
      } else {
          // Reset the trigger after midnight has passed
          alreadyTriggered = false;
          return false;
      }
    }

public:

    // get date and time data from the RTC module
    uint16_t getYear() { return rtc.now().year(); }
    std::string getMonth() { return monthName(rtc.now().month()); }
    std::string getDay() { return std::to_string(rtc.now().day()); }
    uint8_t getHour() { return rtc.now().hour(); }
    uint8_t getMinute() { return rtc.now().minute(); }
    uint8_t getSecond() { return rtc.now().second(); }

    // turn months from decimal to char*
    std::string monthName(uint8_t monthNumber) {
      const char* monthNames[] = {
          "jan", "feb", "mar", "apr", "may", "jun",
          "jul", "aug", "sep", "oct", "nov", "dec"
      };
      return monthNumber >= 1 && monthNumber <= 12 ? monthNames[monthNumber - 1] : "unknown";
    }

    // Loads and updates the daily schedule
    void refreshDailySchedule() { loadDailySchedule(); }

    // Returns the daily schedule in "ScheduleEntry" format
    const std::vector<ScheduleEntry>& getDailySchedule() const { return dailySchedule; }

    // Prints the time, NOW.
    void printTimeNow(){
      Serial.printf("Current Time: %s %s, %u - %02u:%02u:%02u\n",
              getMonth().c_str(),  // Month as a string
              getDay().c_str(),    // Day as a string
              getYear(),           // Year as an integer
              getHour(),           // Hour (24-hour format)
              getMinute(),         // Minute
              getSecond());        // Second
    }

    void tick(){
      updateTime();
      newDayCheck();
    }

    void checkAndRunSchedule(Player & player) {

        for (auto it = dailySchedule.begin(); it != dailySchedule.end(); ) {
            Serial.println("Running daily schedule check");
            if (now.hour == it->hour && now.minute == it->minute) {
                Serial.printf("Entry Exists, returning True!");
                StaticJsonDocument<200> jsonDoc;
                jsonDoc["name"] = it->mel.c_str();
                jsonDoc["speed"] = it->speed;
                jsonDoc["duration"] = it->duration;
                jsonDoc["loop_dur"] = 0;
                jsonDoc["internal"] = 0;
                Serial.printf("name: %s speed: %d duration: %d",it->mel.c_str(), it->speed, it->duration);
                if (!player.isPlaying){
                    player.setMelodyAttributes(jsonDoc);
                    player.loadMelodyInRAM(melody_steps);
                    player.play();
                    it = dailySchedule.erase(it);
                }
            }
            else {
              Serial.printf("No Entry, returning False!");
              ++it;
            }
        }
    }

};
