/**
 * ParVaguesViz - Cyberpunk TidalCycles Visualizer (Improved)
 * 
 * A Processing-based visualizer that works with SuperDirt OSC messages
 * to create cyberpunk-style visualizations for TidalCycles performances.
 * 
 * Features:
 * - Automatic track detection and visualization
 * - Cyberpunk aesthetic with neon colors and grid effects
 * - Real-time audio-reactive visual elements
 * - Orbit-based color schemes
 * - Metadata system for track identification
 */

import oscP5.*;
import netP5.*;

// Processing 4.x compatibility check
boolean isProcessing4 = true;

// OSC connection settings
OscP5 oscP5;
NetAddress superdirtAddress;
int listenPort = 57120; // Default SuperDirt port

// Visualization components
TrackManager trackManager;
Grid grid;
Background background;
ParticleSystem particleSystem;
GlitchEffect glitchEffect;

// Metadata system
MetadataSystem metadataSystem;

// Sample analyzer
SampleAnalyzer sampleAnalyzer;

// UI settings
boolean debug = true;  // Start with debug ON to troubleshoot
boolean showHelp = false;
boolean showMetadata = true;  // Show metadata by default for better visibility
PFont debugFont;
PFont titleFont;
PFont metadataFont;

// Timing
float bpm = 120;
float cps = 0.5;
float currentCycle = 0;
float elapsedTime = 0;
float lastBeatTime = 0;

// Settings for improved visibility
float fadeAmount = 20;  // Lower = more motion blur, higher = clearer screen

// Colors (enhanced cyberpunk palette) - Increased brightness
color[] orbitColors = {
  #00FFFF, // Cyan (d1 - kick)
  #FF00FF, // Magenta (d2 - snare)
  #00FF99, // Neon green (d3 - drums)
  #FF5500, // Orange (d4 - bass)
  #9900FF, // Purple (d5)
  #FFFF00, // Yellow (d6)
  #FF0066, // Pink (d7)
  #0099FF, // Blue (d8 - breaks)
  #33FF33, // Green (d9)
  #FF3300, // Red (d10)
  #CC00FF, // Violet (d11)
  #00CCFF, // Light blue (d12)
  #FFFFFF, // White (d13-d16)
  #FFFFFF,
  #FFFFFF,
  #FFFFFF
};

// OSC message receive counter and timestamp for debugging
int oscMessageCount = 0;
float lastOscTime = 0;

void setup() {
  // Apply Processing 4.x compatibility fixes
  checkProcessingVersion();
  applyProcessing4Fixes();
  
  size(1280, 720, P3D);
  frameRate(60);
  smooth(8);
  
  // Initialize OSC with enhanced error handling
  try {
    oscP5 = new OscP5(this, listenPort);
    superdirtAddress = new NetAddress("127.0.0.1", listenPort);
    println("OSC initialized on port " + listenPort);
  } catch (Exception e) {
    println("ERROR initializing OSC: " + e.getMessage());
    println("Try restarting SuperDirt and this visualizer");
  }
  
  // Initialize components
  trackManager = new TrackManager();
  grid = new Grid();
  background = new Background();
  particleSystem = new ParticleSystem();
  glitchEffect = new GlitchEffect();
  
  // Initialize metadata system
  metadataSystem = new MetadataSystem();
  
  // Initialize sample analyzer
  sampleAnalyzer = new SampleAnalyzer();
  
  // Load fonts
  debugFont = createFont("Courier New Bold", 12);
  titleFont = createFont("Arial Bold", 24);
  metadataFont = createFont("Arial", 14);
  
  // Print startup message
  println("ParVaguesViz started");
  println("Listening for SuperDirt OSC messages on port " + listenPort);
  println("CONTROLS: D=Debug, H=Help, G=GridStyle, F=Fullscreen, R=Reset, M=Metadata");
  
  // Generate a test pattern to verify visualization is working
  generateTestPattern();
}

void draw() {
  // Update timing
  elapsedTime = millis() / 1000.0;
  
  // Clear background with fade effect (lower alpha for more motion trails)
  background.update();
  background.display();
  
  // Update grid
  grid.update();
  grid.display();
  
  // Update and display tracks
  trackManager.update();
  trackManager.display();
  
  // Update and display particles
  particleSystem.update();
  particleSystem.display();
  
  // Apply glitch effects
  glitchEffect.apply();
  
  // Draw UI elements if debug mode is on
  if (debug) {
    drawDebugInfo();
  }
  
  // Draw metadata overlay if enabled - always check this variable
  if (showMetadata) {
    drawMetadataOverlay();
  }
  
  // Draw help if enabled
  if (showHelp) {
    drawHelp();
  }
  
  // Check for OSC timeout (no messages received in the last 5 seconds)
  if (oscMessageCount > 0 && millis() - lastOscTime > 5000) {
    // Draw OSC timeout warning
    fill(255, 0, 0, 200);
    textAlign(CENTER);
    textSize(18);
    text("WARNING: No OSC messages received in the last 5 seconds", width/2, 50);
    text("Check that SuperDirt is running and sending to port " + listenPort, width/2, 75);
  }
}

// Handle OSC messages
void oscEvent(OscMessage msg) {
  // Update OSC stats
  oscMessageCount++;
  lastOscTime = millis();
  
  // Detailed debug logging for OSC messages
  if (debug) {
    println("OSC message received: " + msg.addrPattern());
  }
  
  // Check for metadata messages
  if (msg.addrPattern().equals("/parvagues/metadata")) {
    metadataSystem.processMetadataMessage(msg);
    return;
  }
  
  // Forward the message to our handler
  if (msg.addrPattern().equals("/dirt/play")) {
    handleDirtMessage(msg);
  } else if (msg.addrPattern().equals("/cps")) {
    updateCPS(msg);
  }
}

// Process SuperDirt message
void handleDirtMessage(OscMessage msg) {
  // Extract basic information
  int orbit = -1;
  String sound = "";
  float cycle = 0;
  float delta = 0;
  float gain = 1.0;
  float pan = 0.5;
  
  // Debug output
  if (debug) {
    println("Processing dirt/play message: " + msg.typetag());
  }
  
  // Extract all parameters from the message
  try {
    for (int i = 0; i < msg.typetag().length(); i++) {
      String paramName = msg.get(i).stringValue();
      
      if (paramName.equals("orbit") && i+1 < msg.typetag().length()) {
        orbit = msg.get(i+1).intValue();
      } 
      else if (paramName.equals("s") && i+1 < msg.typetag().length()) {
        sound = msg.get(i+1).stringValue();
      } 
      else if (paramName.equals("cycle") && i+1 < msg.typetag().length()) {
        cycle = msg.get(i+1).floatValue();
      } 
      else if (paramName.equals("delta") && i+1 < msg.typetag().length()) {
        delta = msg.get(i+1).floatValue();
      } 
      else if (paramName.equals("gain") && i+1 < msg.typetag().length()) {
        gain = msg.get(i+1).floatValue();
      } 
      else if (paramName.equals("pan") && i+1 < msg.typetag().length()) {
        pan = msg.get(i+1).floatValue();
      }
    }
  } catch (Exception e) {
    println("Error processing OSC message: " + e.getMessage());
  }
  
  // Only process valid messages with an orbit
  if (orbit >= 0) {
    // Debug output
    if (debug) {
      println("Event: orbit=" + orbit + ", sound=" + sound + ", gain=" + gain + ", pan=" + pan);
    }
    
    // Update metadata system with sample information
    metadataSystem.updateFromSample(orbit, sound);
    
    // Update sample analyzer
    sampleAnalyzer.processSample(orbit, sound, gain, delta);
    
    // Beat detection logic
    currentCycle = cycle;
    float now = millis() / 1000.0;
    if (now - lastBeatTime > 0.1) { // Debounce
      lastBeatTime = now;
      grid.trigger(0.3); // Trigger grid effect on beats
      glitchEffect.trigger(0.1); // Small glitch on beats
    }
    
    // Create a new visual event for this sound
    trackManager.addEvent(orbit, sound, gain, pan, delta);
    
    // Add particles
    particleSystem.addParticles(orbit, pan, gain);
  }
}

// Update timing information from CPS messages
void updateCPS(OscMessage msg) {
  if (msg.checkTypetag("f")) {
    cps = msg.get(0).floatValue();
    bpm = cps * 60 * 4; // Convert to BPM
    
    // Update components with new timing
    grid.setCPS(cps);
    background.setCPS(cps);
    
    if (debug) {
      println("CPS updated: " + cps + " (BPM: " + bpm + ")");
    }
  }
}


// Handle keyboard inputs

// Replace the keyPressed function to properly handle all keys
void keyPressed() {
  if (key == 'd' || key == 'D') {
    debug = !debug;
    println("Debug mode: " + (debug ? "ON" : "OFF"));
  } else if (key == 'h' || key == 'H') {
    showHelp = !showHelp;
    println("Help: " + (showHelp ? "ON" : "OFF"));
  } else if (key == 'g' || key == 'G') {
    grid.toggleStyle();
    println("Grid style changed to: " + grid.gridStyle);
  } else if (key == 'f' || key == 'F') {
    toggleFullScreen();
    println("Toggled fullscreen");
  } else if (key == 'r' || key == 'R') {
    resetVisualizer();
    println("Visualization reset");
  } else if (key == 'm' || key == 'M') {
    showMetadata = !showMetadata;
    println("Metadata display: " + (showMetadata ? "ON" : "OFF"));
  } else if (key == 't' || key == 'T') {
    generateTestPattern();
    println("Test pattern generated");
  }
}

// Improved fullscreen toggle that works properly
void toggleFullScreen() {
  if (surface.isVisible()) {
    if (width == displayWidth && height == displayHeight) {
      // Currently fullscreen, switch to windowed
      surface.setSize(1280, 720);
      surface.setLocation(displayWidth/2 - 640, displayHeight/2 - 360);
    } else {
      // Currently windowed, switch to fullscreen
      surface.setSize(displayWidth, displayHeight);
      surface.setLocation(0, 0);
    }
  }
}

// Completely reset the visualizer
void resetVisualizer() {
  // Clear all active tracks and events
  trackManager = new TrackManager();
  particleSystem = new ParticleSystem();
  
  // Reset other components
  grid = new Grid();
  glitchEffect = new GlitchEffect();
  
  // Clear the screen
  background(0);
}

// Generate a visible test pattern
void generateTestPattern() {
  // First clear existing events
  resetVisualizer();
  
  // Create events for different tracks with clear visibility
  for (int orbit = 0; orbit < 12; orbit++) {
    String sound = "";
    float gain = 1.0;  // Increased from 0.8
    float pan = random(0.3, 0.7);  // Random position
    float delta = 0.25;
    
    // Create appropriate sample names for each orbit
    switch(orbit) {
      case 0: 
        sound = "kick"; 
        break;
      case 1: 
        sound = "snare"; 
        break;
      case 2: 
        sound = "hihat"; 
        break;
      case 3: 
        sound = "bass"; 
        break;
      case 4:
        sound = "keys";
        break;
      case 5:
        sound = "synth";
        break;
      case 6:
        sound = "voice";
        break;
      case 7: 
        sound = "jungle_breaks"; 
        break;
      case 8:
        sound = "weird";
        break;
      case 9:
        sound = "riser";
        break;
      case 10:
        sound = "fx";
        break;
      case 11:
        sound = "808";
        break;
      default:
        sound = "sample" + orbit;
    }
    
    // Create test event - add events with delay to make them more visible
    Thread eventThread = new Thread(new Runnable() {
      public void run() {
        // Get local copies of the variables
        int localOrbit = orbit;
        String localSound = sound;
        float localGain = gain;
        float localPan = pan;
        float localDelta = delta;
        
        try {
          // Add delay based on orbit to spread out events
          Thread.sleep(localOrbit * 200);
          
          // Create the event on the main thread
          trackManager.addEvent(localOrbit, localSound, localGain, localPan, localDelta);
          metadataSystem.updateFromSample(localOrbit, localSound);
          
          // Add particles too
          particleSystem.addParticles(localOrbit, localPan, localGain);
        } catch (Exception e) {
          println("Error in test pattern thread: " + e.getMessage());
        }
      }
    });
    eventThread.start();
  }
  
  // Trigger effects
  grid.trigger(0.6);
  glitchEffect.trigger(0.3);
}

// Debug information display
void drawDebugInfo() {
  fill(0, 180);
  noStroke();
  rect(5, 5, 300, 150, 5);
  
  fill(255);
  textFont(debugFont);
  textAlign(LEFT);
  
  text("FPS: " + int(frameRate), 10, 20);
  text("CPS: " + nf(cps, 0, 2) + " (BPM: " + int(bpm) + ")", 10, 35);
  text("Cycle: " + nf(currentCycle, 0, 2), 10, 50);
  text("Active Tracks: " + trackManager.getActiveTrackCount(), 10, 65);
  text("Particles: " + particleSystem.getParticleCount(), 10, 80);
  text("OSC Messages: " + oscMessageCount, 10, 95);
  text("Last OSC: " + nf((millis() - lastOscTime)/1000, 0, 1) + "s ago", 10, 110);
  
  // Add hints
  fill(200);
  text("T = Test Pattern, M = Metadata, H = Help", 10, 140);
}

// Draw metadata overlay
void drawMetadataOverlay() {
  // Semi-transparent background
  fill(0, 200);
  noStroke();
  rect(width - 320, 10, 310, height - 20, 10);
  
  // Title
  fill(255);
  textFont(titleFont);
  textAlign(CENTER);
  text("Track Metadata", width - 165, 40);
  
  // Track information
  textFont(metadataFont);
  textAlign(LEFT);
  float y = 70;
  
  // Draw metadata for each active track
  ArrayList<Integer> activeOrbits = trackManager.getActiveOrbits();
  
  if (activeOrbits.size() == 0) {
    text("No active tracks", width - 300, y);
    text("Try pressing T to generate a test pattern", width - 300, y + 25);
  } else {
    for (Integer orbit : activeOrbits) {
      TrackMetadata metadata = metadataSystem.getMetadata(orbit);
      
      // Draw track name and type with track color
      fill(metadata.getTrackColor());
      text("d" + (orbit + 1) + ": " + metadata.name, width - 300, y);
      text("Type: " + metadata.type, width - 300, y + 20);
      
      // Draw most recent sample
      fill(200);
      if (metadata.recentSamples.size() > 0) {
        text("Sample: " + metadata.recentSamples.get(metadata.recentSamples.size() - 1), 
             width - 300, y + 40);
      }
      
      // Add analyzed features if available
      SampleFeatures features = sampleAnalyzer.getFeaturesForOrbit(orbit);
      if (features != null) {
        fill(180);
        text("Tempo: " + nf(features.tempo, 0, 1) + " BPM", width - 300, y + 60);
        text("Energy: " + nf(features.energy, 0, 2), width - 150, y + 60);
      }
      
      // Draw separator
      stroke(100);
      line(width - 300, y + 75, width - 30, y + 75);
      noStroke();
      
      // Move to next track position
      y += 90;
      
      // Avoid drawing outside screen
      if (y > height - 50) break;
    }
  }
}

// Help information display
void drawHelp() {
  // Semi-transparent background
  fill(0, 220);
  noStroke();
  rect(width/2 - 200, height/2 - 170, 400, 340, 10);
  
  fill(255);
  textFont(titleFont);
  textAlign(CENTER);
  
  text("ParVaguesViz Controls", width/2, height/2 - 140);
  
  textFont(debugFont);
  textAlign(LEFT);
  
  String[] helpText = {
    "D - Toggle debug info",
    "H - Toggle help",
    "G - Change grid style",
    "F - Toggle fullscreen",
    "R - Reset visuals",
    "M - Toggle metadata display",
    "T - Generate test pattern",
    "",
    "Automatically visualizes tracks d1-d16",
    "Special visualization for d8 breakbeats",
    "No TidalCycles configuration needed",
    "",
    "If no events are visible, check that:",
    "1. SuperDirt is running on port 57120",
    "2. Your TidalCycles pattern is active",
    "3. Press T to generate a test pattern"
  };
  
  for (int i = 0; i < helpText.length; i++) {
    text(helpText[i], width/2 - 180, height/2 - 100 + i * 20);
  }
}
