/**
 * TrackManager class
 * 
 * Handles the management and visualization of TidalCycles tracks (d1-d16)
 * Each track has its own visual representation and effects
 */
class TrackManager {
  ArrayList<Track> tracks;
  ArrayList<SoundEvent> activeEvents;
  
  TrackManager() {
    tracks = new ArrayList<Track>();
    activeEvents = new ArrayList<SoundEvent>();
    
    // Initialize tracks for all possible orbits (0-15)
    for (int i = 0; i < 16; i++) {
      tracks.add(new Track(i));
    }
  }
  
  void update() {
    // Update all tracks
    for (Track track : tracks) {
      track.update();
    }
    
    // Update active events and remove completed ones
    for (int i = activeEvents.size() - 1; i >= 0; i--) {
      SoundEvent event = activeEvents.get(i);
      event.update();
      
      if (event.isDone()) {
        activeEvents.remove(i);
      }
    }
  }
  
  void display() {
    // Display all tracks
    for (Track track : tracks) {
      track.display();
    }
    
    // Display all active events
    for (SoundEvent event : activeEvents) {
      event.display();
    }
  }
  
  void addEvent(int orbit, String sound, float gain, float pan, float delta) {
    // Get appropriate track
    Track track = tracks.get(orbit);
    
    // Update track state
    track.onSound(sound, gain);
    
    // Create new event with appropriate visualization style based on orbit and sound
    SoundEvent event = createEvent(orbit, sound, gain, pan, delta);
    activeEvents.add(event);
  }
  
  SoundEvent createEvent(int orbit, String sound, float gain, float pan, float delta) {
    // Different visualization based on orbit (track number)
    switch(orbit) {
      case 0: // d1 - typically kick
        return new KickEvent(orbit, sound, gain, pan, delta);
      case 1: // d2 - typically snare
        return new SnareEvent(orbit, sound, gain, pan, delta);
      case 2: // d3 - typically hats or percussion
        return new HihatEvent(orbit, sound, gain, pan, delta);
      case 3: // d4 - typically bass
        return new BassEvent(orbit, sound, gain, pan, delta);
      default: // Other instruments
        if (sound.contains("suns") || sound.contains("key")) {
          return new MelodicEvent(orbit, sound, gain, pan, delta);
        } else if (sound.contains("break") || sound.contains("jungle")) {
          return new BreakEvent(orbit, sound, gain, pan, delta);
        } else if (sound.contains("voice") || sound.contains("voc")) {
          return new VoiceEvent(orbit, sound, gain, pan, delta);
        } else if (sound.contains("riser") || sound.contains("fx")) {
          return new FXEvent(orbit, sound, gain, pan, delta);
        } else {
          return new SoundEvent(orbit, sound, gain, pan, delta);
        }
    }
  }
  
  int getActiveTrackCount() {
    int count = 0;
    for (Track track : tracks) {
      if (track.isActive()) {
        count++;
      }
    }
    return count;
  }
  
  
  ArrayList<Integer> getActiveOrbits() {
    ArrayList<Integer> activeOrbits = new ArrayList<Integer>();
    
    // Add orbits of all active tracks
    for (int i = 0; i < tracks.size(); i++) {
      if (tracks.get(i).isActive()) {
        activeOrbits.add(i);
      }
    }
    
    return activeOrbits;
  }

  
  void reset() {
    activeEvents.clear();
    for (Track track : tracks) {
      track.reset();
    }
  }
}

/**
 * Track class
 * 
 * Represents a single TidalCycles track (d1-d16)
 * Maintains state and provides visual representation
 */
class Track {
  int orbit;
  color trackColor;
  boolean active;
  float activity;
  float lastTriggerTime;
  String lastSound;
  ArrayList<Float> historyGain;
  
  // Visual properties
  float baseHeight;
  float targetHeight;
  float currentHeight;
  
  Track(int orbit) {
    this.orbit = orbit;
    this.trackColor = orbitColors[orbit];
    this.active = false;
    this.activity = 0;
    this.lastTriggerTime = -1000;
    this.lastSound = "";
    
    this.historyGain = new ArrayList<Float>();
    
    // Visual initialization
    this.baseHeight = height / 32.0;
    this.targetHeight = baseHeight;
    this.currentHeight = baseHeight;
  }
  
  void update() {
    // Decay activity over time
    activity *= 0.95;
    
    // Update height with smooth animation
    currentHeight = lerp(currentHeight, targetHeight, 0.2);
    
    // Reset target height if activity is low
    if (activity < 0.1) {
      targetHeight = baseHeight;
      active = false;
    }
  }
  
  void display() {
    if (activity < 0.05) return; // Don't display inactive tracks
    
    float yPos = map(orbit, 0, 15, height * 0.1, height * 0.9);
    float trackWidth = width * 0.8;
    float xOffset = width * 0.1;
    
    // Draw track background with trail effect
    noStroke();
    fill(red(trackColor), green(trackColor), blue(trackColor), activity * 150);
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Draw glowing edge
    stroke(trackColor, activity * 255);
    strokeWeight(2);
    noFill();
    rect(xOffset, yPos - currentHeight/2, trackWidth, currentHeight, 5);
    
    // Add glow effect
    drawGlow(xOffset, yPos, trackWidth, currentHeight);
    
    // Draw activity meter
    float meterWidth = map(activity, 0, 1, 0, trackWidth);
    noStroke();
    fill(trackColor, activity * 200);
    rect(xOffset, yPos - currentHeight/3, meterWidth, currentHeight/3, 5);
  }
  
  void drawGlow(float x, float y, float w, float h) {
    // Create glow effect using multiple transparent strokes
    for (int i = 0; i < 5; i++) {
      float alpha = map(i, 0, 4, activity * 100, 0);
      stroke(red(trackColor), green(trackColor), blue(trackColor), alpha);
      strokeWeight(i * 2 + 2);
      noFill();
      rect(x, y - currentHeight/2, w, currentHeight, 5);
    }
  }
  
  void onSound(String sound, float gain) {
    // Update track state
    active = true;
    lastTriggerTime = millis();
    lastSound = sound;
    activity = 1.0;
    
    // Store gain history (for visual patterns)
    historyGain.add(gain);
    if (historyGain.size() > 16) {
      historyGain.remove(0);
    }
    
    // Update visual properties
    targetHeight = baseHeight + (gain * baseHeight * 2);
  }
  
  boolean isActive() {
    return active;
  }
  
  void reset() {
    activity = 0;
    historyGain.clear();
    currentHeight = baseHeight;
    targetHeight = baseHeight;
    active = false;
  }
}

/**
 * SoundEvent class
 * 
 * Base class for visualizing individual sound events
 */
class SoundEvent {
  int orbit;
  String sound;
  float gain;
  float pan;
  float delta;
  float birthTime;
  float lifespan;
  color eventColor;
  
  // Visual properties
  float size;
  float alpha;
  PVector position;
  
  SoundEvent(int orbit, String sound, float gain, float pan, float delta) {
    this.orbit = orbit;
    this.sound = sound;
    this.gain = gain;
    this.pan = pan;
    this.delta = delta;
    this.birthTime = millis();
    this.lifespan = 500 + (gain * 500); // Duration based on gain
    
    // Initialize visuals
    this.eventColor = orbitColors[orbit];
    this.size = 20 + (gain * 60);
    this.alpha = 255;
    
    // Position based on pan value
    float xPos = map(pan, 0, 1, width * 0.3, width * 0.7);
    float yPos = map(orbit, 0, 15, height * 0.2, height * 0.8);
    this.position = new PVector(xPos, yPos);
  }
  
  void update() {
    // Calculate age
    float age = millis() - birthTime;
    
    // Fade out as the event ages
    alpha = map(age, 0, lifespan, 255, 0);
    
    // Grow size slightly over time
    size = 20 + (gain * 60) * (1 + (age / lifespan) * 0.5);
  }
  
  void display() {
    if (alpha <= 0) return;
    
    // Draw the event
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    ellipse(position.x, position.y, size, size);
    
    // Add glow effect
    drawGlow(position.x, position.y, size);
  }
  
  void drawGlow(float x, float y, float s) {
    for (int i = 0; i < 5; i++) {
      float glowAlpha = map(i, 0, 4, alpha * 0.5, 0);
      fill(red(eventColor), green(eventColor), blue(eventColor), glowAlpha);
      noStroke();
      ellipse(x, y, s + (i * 10), s + (i * 10));
    }
  }
  
  boolean isDone() {
    return (millis() - birthTime) > lifespan;
  }
}

/**
 * Specialized sound event classes for different orbits/instruments
 */
class KickEvent extends SoundEvent {
  KickEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 400 + (gain * 200); // Shorter for kicks
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    // Specialized kick visualization (more impactful)
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw shock wave effect
    noFill();
    stroke(red(eventColor), green(eventColor), blue(eventColor), alpha * (1-progress));
    strokeWeight(3 * (1-progress));
    ellipse(position.x, position.y, size * (1 + progress * 3), size * (1 + progress * 3));
    
    // Draw core
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    ellipse(position.x, position.y, size * (1-progress*0.5), size * (1-progress*0.5));
  }
}

class SnareEvent extends SoundEvent {
  ArrayList<PVector> particles;
  
  SnareEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 600; // Longer for snares
    
    // Create particles for snare effect
    particles = new ArrayList<PVector>();
    int particleCount = int(10 + (gain * 20));
    
    for (int i = 0; i < particleCount; i++) {
      float angle = random(TWO_PI);
      float speed = random(1, 5);
      particles.add(new PVector(cos(angle) * speed, sin(angle) * speed));
    }
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw particles
    noStroke();
    for (PVector p : particles) {
      float x = position.x + (p.x * age * 0.1);
      float y = position.y + (p.y * age * 0.1);
      float particleSize = size * 0.2 * (1-progress);
      
      fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.7);
      ellipse(x, y, particleSize, particleSize);
    }
    
    // Draw core
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    ellipse(position.x, position.y, size * (1-progress*0.7), size * (1-progress*0.7));
  }
}

class HihatEvent extends SoundEvent {
  HihatEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 300; // Very short for hihats
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw star-like shape
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    
    pushMatrix();
    translate(position.x, position.y);
    rotate(progress * PI);
    
    beginShape();
    for (int i = 0; i < 6; i++) {
      float angle = i * TWO_PI / 6;
      float x1 = cos(angle) * size * 0.5 * (1-progress*0.5);
      float y1 = sin(angle) * size * 0.5 * (1-progress*0.5);
      vertex(x1, y1);
      
      angle += TWO_PI / 12;
      float x2 = cos(angle) * size * 0.2 * (1-progress*0.5);
      float y2 = sin(angle) * size * 0.2 * (1-progress*0.5);
      vertex(x2, y2);
    }
    endShape(CLOSE);
    
    popMatrix();
  }
}

class BassEvent extends SoundEvent {
  BassEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 800 + (gain * 400); // Longer for bass
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw ripple effect
    for (int i = 0; i < 3; i++) {
      float rippleProgress = (progress + (i * 0.2)) % 1.0;
      float rippleSize = size * (0.5 + rippleProgress * 2);
      float rippleAlpha = alpha * (1 - rippleProgress);
      
      noFill();
      stroke(red(eventColor), green(eventColor), blue(eventColor), rippleAlpha);
      strokeWeight(3 * (1-rippleProgress));
      ellipse(position.x, position.y, rippleSize, rippleSize * 0.5); // Oval for bass
    }
    
    // Draw core
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    ellipse(position.x, position.y, size * 0.8, size * 0.4);
  }
}

class MelodicEvent extends SoundEvent {
  float rotation;
  
  MelodicEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 1000 + (gain * 500);
    rotation = random(TWO_PI);
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    pushMatrix();
    translate(position.x, position.y);
    rotate(rotation + (progress * TWO_PI * 0.5));
    
    // Draw geometric shape
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    
    float sizeScale = 1 - (progress * 0.3);
    
    // Create polygon shape
    beginShape();
    int sides = 5;
    for (int i = 0; i < sides; i++) {
      float angle = i * TWO_PI / sides;
      float x = cos(angle) * size * 0.5 * sizeScale;
      float y = sin(angle) * size * 0.5 * sizeScale;
      vertex(x, y);
    }
    endShape(CLOSE);
    
    // Add inner detail
    fill(0, alpha * 0.5);
    beginShape();
    for (int i = 0; i < sides; i++) {
      float angle = i * TWO_PI / sides;
      float x = cos(angle) * size * 0.3 * sizeScale;
      float y = sin(angle) * size * 0.3 * sizeScale;
      vertex(x, y);
    }
    endShape(CLOSE);
    
    popMatrix();
    
    // Add glow
    drawGlow(position.x, position.y, size * sizeScale);
  }
}

class BreakEvent extends SoundEvent {
  ArrayList<PVector> chunks;
  
  BreakEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 700;
    
    // Create chunks for break visualization
    chunks = new ArrayList<PVector>();
    int chunkCount = int(5 + (gain * 10));
    
    for (int i = 0; i < chunkCount; i++) {
      float angle = random(TWO_PI);
      float distance = random(size * 0.2, size * 0.6);
      float chunkSize = random(size * 0.1, size * 0.3);
      chunks.add(new PVector(cos(angle) * distance, sin(angle) * distance, chunkSize));
    }
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Draw chunks
    rectMode(CENTER);
    for (PVector chunk : chunks) {
      float x = position.x + (chunk.x * (0.5 + progress));
      float y = position.y + (chunk.y * (0.5 + progress));
      float chunkSize = chunk.z * (1 - progress * 0.5);
      
      fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.8);
      noStroke();
      rect(x, y, chunkSize, chunkSize, 2);
    }
    rectMode(CORNER);
    
    // Draw center
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    ellipse(position.x, position.y, size * 0.4 * (1-progress), size * 0.4 * (1-progress));
  }
}

class VoiceEvent extends SoundEvent {
  VoiceEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 1200;
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Waveform visualization
    stroke(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noFill();
    strokeWeight(2);
    
    beginShape();
    for (int i = 0; i < 20; i++) {
      float x = position.x - (size/2) + (i * (size/20));
      float waveHeight = sin(i * 0.5 + (millis() * 0.005)) * size * 0.2 * (1-progress*0.7);
      float y = position.y + waveHeight;
      vertex(x, y);
    }
    endShape();
    
    // Draw central point
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha);
    noStroke();
    ellipse(position.x, position.y, size * 0.2, size * 0.2);
  }
}

class FXEvent extends SoundEvent {
  FXEvent(int orbit, String sound, float gain, float pan, float delta) {
    super(orbit, sound, gain, pan, delta);
    lifespan = 1500;
    size *= 1.5; // Larger for FX
  }
  
  @Override
  void display() {
    if (alpha <= 0) return;
    
    float age = millis() - birthTime;
    float progress = age / lifespan;
    
    // Lightning effect
    stroke(red(eventColor), green(eventColor), blue(eventColor), alpha * (1-progress*0.5));
    
    // Draw multiple lightning bolts
    for (int j = 0; j < 3; j++) {
      float offsetX = random(-size/4, size/4);
      float offsetY = random(-size/4, size/4);
      
      strokeWeight(3 * (1-progress));
      noFill();
      
      beginShape();
      vertex(position.x + offsetX, position.y - size/2);
      
      // Create jagged lines
      int segments = 5;
      for (int i = 1; i < segments; i++) {
        float segmentY = position.y - size/2 + (i * size/segments);
        float segmentX = position.x + offsetX + random(-size/4, size/4);
        vertex(segmentX, segmentY);
      }
      
      vertex(position.x + offsetX, position.y + size/2);
      endShape();
    }
    
    // Draw core
    fill(red(eventColor), green(eventColor), blue(eventColor), alpha * 0.7);
    noStroke();
    ellipse(position.x, position.y, size * 0.3 * (1-progress*0.5), size * 0.3 * (1-progress*0.5));
  }
}