/**
 * ParVaguesViz - Cyberpunk TidalCycles Visualizer
 * 
 * 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 = false;
boolean showHelp = false;
boolean showMetadata = false;
PFont debugFont;
PFont titleFont;
PFont metadataFont;

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

// Colors (cyberpunk palette)
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
};

void setup() {
  // Apply Processing 4.x compatibility fixes
  checkProcessingVersion();
  applyProcessing4Fixes();
  
  size(1280, 720, P3D);
  frameRate(60);
  smooth(8);
  
  // Initialize OSC
  oscP5 = new OscP5(this, listenPort);
  superdirtAddress = new NetAddress("127.0.0.1", listenPort);
  
  // 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);
}

void draw() {
  // Update timing
  elapsedTime = millis() / 1000.0;
  
  // Clear background with fade effect
  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
  if (showMetadata) {
    drawMetadataOverlay();
  }
  
  // Draw help if enabled
  if (showHelp) {
    drawHelp();
  }
}

// Handle OSC messages
void oscEvent(OscMessage msg) {
  // 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;
  
  // Extract all parameters from the message
  for (int i = 0; i < msg.typetag().length(); i++) {
    String paramName = msg.get(i).stringValue();
    
    if (paramName.equals("orbit")) {
      orbit = msg.get(i+1).intValue();
    } 
    else if (paramName.equals("s")) {
      sound = msg.get(i+1).stringValue();
    } 
    else if (paramName.equals("cycle")) {
      cycle = msg.get(i+1).floatValue();
    } 
    else if (paramName.equals("delta")) {
      delta = msg.get(i+1).floatValue();
    } 
    else if (paramName.equals("gain")) {
      gain = msg.get(i+1).floatValue();
    } 
    else if (paramName.equals("pan")) {
      pan = msg.get(i+1).floatValue();
    }
  }
  
  // Only process valid messages with an orbit
  if (orbit >= 0) {
    // 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);
  }
}

// Handle keyboard inputs
void keyPressed() {
  if (key == 'd' || key == 'D') {
    debug = !debug;
  } else if (key == 'h' || key == 'H') {
    showHelp = !showHelp;
  } else if (key == 'g' || key == 'G') {
    grid.toggleStyle();
  } else if (key == 'f' || key == 'F') {
    // Use the compatible fullscreen toggle
    handleFullscreenToggle();
  } else if (key == 'r' || key == 'R') {
    // Reset all visuals
    trackManager.reset();
    particleSystem.reset();
  } else if (key == 'm' || key == 'M') {
    // Toggle metadata display
    showMetadata = !showMetadata;
  }
}

// Debug information display
void drawDebugInfo() {
  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("Tracked Samples: " + sampleAnalyzer.getSampleCount(), 10, 95);
  
  // Add hint about metadata
  fill(200);
  text("Press 'M' to toggle metadata display", 10, 125);
}

// 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);
  } 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() {
  fill(0, 180);
  noStroke();
  rect(width/2 - 200, height/2 - 150, 400, 300);
  
  fill(255);
  textFont(titleFont);
  textAlign(CENTER);
  
  text("ParVaguesViz Controls", width/2, height/2 - 120);
  
  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",
    "",
    "Automatically visualizes tracks d1-d16",
    "Special visualization for d8 breakbeats",
    "No TidalCycles configuration needed"
  };
  
  for (int i = 0; i < helpText.length; i++) {
    text(helpText[i], width/2 - 180, height/2 - 80 + i * 20);
  }
}

// Check if currently fullscreen
boolean sketchFullScreen() {
  if (isProcessing4) {
    return width == displayWidth && height == displayHeight;
  } else {
    return width == displayWidth && height == displayHeight;
  }
}
