// MELODY PLAYBACK WILL BE HANDLED HERE
#include <vector>
extern Player player;

// Define a structure to track active solenoids
struct ActiveRelay {
    uint8_t relayIndex;      // Physical relay index (0-15)
    uint8_t bellIndex;       // Bell index for duration lookup
    uint64_t activationTime; // Activation start time
    uint16_t duration;       // Duration for which it should remain active
};

// Duration per BELL (not per relay output)
uint16_t bellDurations[16] = {90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90};

// Level 1: Bell to Physical Output mapping (bell index -> relay index)
// bellOutputs[0] = which relay controls Bell #0, etc.
uint16_t bellOutputs[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; // 0-based indexing

// Vector to track active solenoids
std::vector<ActiveRelay> activeRelays;

// Locks for Writing the Counters
portMUX_TYPE mySpinlock = portMUX_INITIALIZER_UNLOCKED;

void loop_playback(std::vector<uint16_t> &melody_steps);
void bellEngine(void *parameter);
void relayControlTask(void *param);
void itsHammerTime(uint16_t note);
void turnOffRelays(uint64_t now);

// Main Bell Engine. Activates Relays on the exact timing required.
void bellEngine(void *parameter) {
	// SETUP TASK
	for (;;) {
		// Playback until stopped (Completes AT LEAST 1 full loop)
		loop_playback(melody_steps);
 /*
		UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(NULL);
		Serial.print("Stack high water mark: ");
		Serial.println(highWaterMark);
 */
	}
}

// Task to deactivate relays dynamically after set timers
void relayControlTask(void *param) {
	while (true) {
      uint64_t now = millis();
      // Iterate through active relays and deactivate those whose duration has elapsed
      for (auto it = activeRelays.begin(); it != activeRelays.end();) {
          if (now - it->activationTime >= it->duration) {
              relays.digitalWrite(it->relayIndex, HIGH); // Deactivate the relay
              it = activeRelays.erase(it);              // Remove from the active list
          } else {
              ++it; // Move to the next relay
          }
      }
      vTaskDelay(pdMS_TO_TICKS(10)); // Check every 10ms
  }
}

// Function to wait for tempo, then loop to the next beat.
void loop_playback(std::vector<uint16_t> &melody_steps) {

  while(player.isPlaying && !player.isPaused){
    LOG_DEBUG("(BellEngine) Single Loop Starting.");

		// iterate through the beats and call the bell mechanism on each beat
		for (uint16_t note : melody_steps) {
			if (player.hardStop) return;
			itsHammerTime(note);
			int tempo = player.speed;
			vTaskDelay(pdMS_TO_TICKS(tempo));
		}

    player.segmentCmpltTime = millis();
		LOG_DEBUG("(BellEngine) Single Full Loop Complete");
	}

}

// Function to activate relays for a specific note
void itsHammerTime(uint16_t note) {
    uint64_t now = millis();

    // First, determine which bells should ring based on the note pattern
    for (uint8_t noteIndex = 0; noteIndex < 16; noteIndex++) {
        if (note & (1 << noteIndex)) { // This note should be played

            // Level 2: Map note to bell using noteAssignments
            uint8_t bellIndex = player.noteAssignments[noteIndex];

            // Skip if no bell assigned to this note (0 means no assignment)
            if (bellIndex == 0) continue;

            // Convert to 0-based indexing (noteAssignments uses 1-based)
            bellIndex = bellIndex - 1;

            // Level 1: Map bell to physical relay output
            uint8_t relayIndex = bellOutputs[bellIndex];

            // Activate the relay
            relays.digitalWrite(relayIndex, LOW);

            // Add to the activeRelays list with bell-specific duration
            activeRelays.push_back({
                relayIndex,
                bellIndex,
                now,
                bellDurations[bellIndex]
            });

            // Write ring to counter (count per bell, not per relay)
            portENTER_CRITICAL(&mySpinlock);
            strikeCounters[bellIndex]++;  // Count strikes per bell
            bellLoad[bellIndex]++;        // Load per bell
            coolingActive = true;
            portEXIT_CRITICAL(&mySpinlock);
        }
    }
}


// Helper function to update bell-to-output mapping via JSON
void updateBellOutputs(JsonVariant doc) {
    for (uint8_t i = 0; i < 16; i++)  {
        String key = String("b") + (i + 1);
        if (doc.containsKey(key)) {
            bellOutputs[i] =  doc[key].as<uint16_t>()-1;  // -1 to convert to 0 based indexing
            LOG_DEBUG("Bell %d Output set to Relay #%d", i + 1, bellOutputs[i]+1);
        } else {
            LOG_DEBUG("Bell %d not found in JSON payload. Keeping previous Output: Relay #%d", i + 1, bellOutputs[i]+1);
        }
    }
    LOG_INFO("Updated Relay Outputs.")
    StaticJsonDocument<128> response;
    response["status"] = "OK";
    response["type"] = "set_relay_outputs";
    char jsonOut[128];										      // Create Char Buffer
    serializeJson(response, jsonOut);			      // Serialize to Buffer
    replyOnWebSocket(activeClient, jsonOut);		// Reply on WebSocket
}

// Sets Incoming Relay Durations to RAM and then call funtion to save them to file.
void updateRelayTimings(JsonVariant doc) {
    // Iterate through the relays in the JSON payload
    for (uint8_t i = 0; i < 16; i++) {
        String key = String("b") + (i + 1); // Generate "b1", "b2", ...
        if (doc.containsKey(key)) {
            bellDurations[i] = doc[key].as<uint16_t>();
            LOG_DEBUG("Relay %d duration set to %d ms", i + 1, bellDurations[i]);
        } else {
            LOG_DEBUG("Relay %d not found in JSON payload. Keeping previous duration: %d ms", i + 1, bellDurations[i]);
        }
    }
		saveRelayTimings();
		LOG_INFO("Updated Relay Timings.")
    StaticJsonDocument<128> response;
    response["status"] = "OK";
    response["type"] = "set_relay_timings";
    char jsonOut[128];										      // Create Char Buffer
    serializeJson(response, jsonOut);			      // Serialize to Buffer
    replyOnWebSocket(activeClient, jsonOut);		// Reply on WebSocket
}
