/**
 * MetadataSystem class
 * 
 * Manages metadata for tracks and provides an interface for:
 * 1. Auto-detection of track types based on patterns and samples
 * 2. Manual metadata setting through special OSC messages
 * 3. Persistent configuration through a metadata file
 */
class MetadataSystem {
  HashMap<Integer, TrackMetadata> trackMetadata;
  HashMap<String, ArrayList<String>> sampleCategories;
  boolean hasLoadedMetadata;
  
  MetadataSystem() {
    trackMetadata = new HashMap<Integer, TrackMetadata>();
    sampleCategories = new HashMap<String, ArrayList<String>>();
    hasLoadedMetadata = false;
    
    // Initialize with default categories
    initializeSampleCategories();
    
    // Try to load metadata file
    loadMetadataFile();
  }
  
  void initializeSampleCategories() {
    // Define categories of samples for auto-detection
    
    // Kick drums
    ArrayList<String> kicks = new ArrayList<String>();
    kicks.add("bd");
    kicks.add("kick");
    kicks.add("808bd");
    kicks.add("bass drum");
    kicks.add("jazz");
    sampleCategories.put("kick", kicks);
    
    // Snares
    ArrayList<String> snares = new ArrayList<String>();
    snares.add("sn");
    snares.add("snare");
    snares.add("cp");
    snares.add("clap");
    snares.add("rimshot");
    sampleCategories.put("snare", snares);
    
    // Hi-hats
    ArrayList<String> hats = new ArrayList<String>();
    hats.add("hh");
    hats.add("hat");
    hats.add("hihat");
    hats.add("openhat");
    hats.add("closehat");
    hats.add("ch");
    hats.add("oh");
    sampleCategories.put("hihat", hats);
    
    // Bass
    ArrayList<String> bass = new ArrayList<String>();
    bass.add("bass");
    bass.add("sub");
    bass.add("808");
    bass.add("bassWarsaw");
    sampleCategories.put("bass", bass);
    
    // Breaks
    ArrayList<String> breaks = new ArrayList<String>();
    breaks.add("break");
    breaks.add("jungle");
    breaks.add("amen");
    breaks.add("think");
    breaks.add("apache");
    breaks.add("funky");
    sampleCategories.put("breaks", breaks);
    
    // Melodic
    ArrayList<String> melodic = new ArrayList<String>();
    melodic.add("keys");
    melodic.add("pad");
    melodic.add("piano");
    melodic.add("rhodes");
    melodic.add("chord");
    melodic.add("synth");
    melodic.add("psin");
    melodic.add("superfork");
    sampleCategories.put("melodic", melodic);
    
    // FX
    ArrayList<String> fx = new ArrayList<String>();
    fx.add("fx");
    fx.add("riser");
    fx.add("sweep");
    fx.add("weird");
    fx.add("glitch");
    fx.add("noise");
    sampleCategories.put("fx", fx);
    
    // Vocals
    ArrayList<String> vocals = new ArrayList<String>();
    vocals.add("voc");
    vocals.add("voice");
    vocals.add("vocal");
    vocals.add("speech");
    vocals.add("talk");
    vocals.add("sing");
    sampleCategories.put("vocals", vocals);
    
    // ParVagues specific samples (from the code examples)
    ArrayList<String> parVaguesSpecific = new ArrayList<String>();
    parVaguesSpecific.add("suns_keys");
    parVaguesSpecific.add("suns_guitar");
    parVaguesSpecific.add("suns_voice");
    parVaguesSpecific.add("rampleM2");
    parVaguesSpecific.add("rampleD");
    parVaguesSpecific.add("armora");
    parVaguesSpecific.add("FMRhodes1");
    sampleCategories.put("parvagues", parVaguesSpecific);
  }
  
  void loadMetadataFile() {
    // Try to load metadata from a file
    try {
      String[] lines = loadStrings("parvagues_metadata.txt");
      if (lines != null && lines.length > 0) {
        for (String line : lines) {
          if (line.trim().startsWith("#") || line.trim().isEmpty()) continue; // Skip comments and empty lines
          
          String[] parts = line.split(":");
          if (parts.length >= 3) {
            int orbit = int(parts[0].trim());
            String trackName = parts[1].trim();
            String trackType = parts[2].trim();
            
            // Create metadata
            TrackMetadata metadata = new TrackMetadata(orbit, trackName, trackType);
            
            // Add additional properties if available
            if (parts.length > 3) {
              String[] properties = parts[3].split(",");
              for (String prop : properties) {
                String[] keyValue = prop.split("=");
                if (keyValue.length == 2) {
                  metadata.setProperty(keyValue[0].trim(), keyValue[1].trim());
                }
              }
            }
            
            // Store metadata
            trackMetadata.put(orbit, metadata);
          }
        }
        hasLoadedMetadata = true;
        println("Loaded metadata for " + trackMetadata.size() + " tracks");
      }
    } catch (Exception e) {
      println("Could not load metadata file: " + e.getMessage());
      // File might not exist yet, that's OK
    }
  }
  
  void saveMetadataFile() {
    // Save metadata to a file
    ArrayList<String> lines = new ArrayList<String>();
    lines.add("# ParVagues Visualization Metadata");
    lines.add("# Format: orbit:name:type:prop1=value1,prop2=value2,...");
    lines.add("");
    
    for (int orbit : trackMetadata.keySet()) {
      TrackMetadata metadata = trackMetadata.get(orbit);
      String line = orbit + ":" + metadata.name + ":" + metadata.type;
      
      // Add properties
      if (metadata.properties.size() > 0) {
        line += ":";
        boolean first = true;
        for (String key : metadata.properties.keySet()) {
          if (!first) line += ",";
          line += key + "=" + metadata.properties.get(key);
          first = false;
        }
      }
      
      lines.add(line);
    }
    
    saveStrings("data/parvagues_metadata.txt", lines.toArray(new String[0]));
    println("Saved metadata for " + trackMetadata.size() + " tracks");
  }
  
  TrackMetadata getMetadata(int orbit) {
    // Get metadata for a specific orbit
    if (trackMetadata.containsKey(orbit)) {
      return trackMetadata.get(orbit);
    }
    
    // Create default metadata if none exists
    TrackMetadata defaultMetadata = createDefaultMetadata(orbit);
    trackMetadata.put(orbit, defaultMetadata);
    return defaultMetadata;
  }
  
  TrackMetadata createDefaultMetadata(int orbit) {
    // Create default metadata based on orbit
    String name = "Track " + (orbit + 1);
    String type = "default";
    
    // Assign types based on common usage
    switch(orbit) {
      case 0: 
        type = "kick"; 
        name = "Kick";
        break;
      case 1: 
        type = "snare"; 
        name = "Snare";
        break;
      case 2: 
        type = "hihat"; 
        name = "Drums";
        break;
      case 3: 
        type = "bass"; 
        name = "Bass";
        break;
      case 7: // d8
        type = "breaks"; 
        name = "Breakbeats";
        break;
    }
    
    return new TrackMetadata(orbit, name, type);
  }
  
  void updateFromSample(int orbit, String sample) {
    // Update metadata based on sample name detection
    if (sample == null || sample.isEmpty()) return;
    
    TrackMetadata metadata = getMetadata(orbit);
    
    // If this is the first sample for this track, try to determine type
    if (!metadata.hasDetectedSample) {
      // Detect sample type
      String detectedType = detectSampleType(sample);
      if (detectedType != null) {
        metadata.type = detectedType;
        
        // Also update name if it's still default
        if (metadata.name.equals("Track " + (orbit + 1))) {
          metadata.name = detectedType.substring(0, 1).toUpperCase() + detectedType.substring(1);
        }
      }
      
      metadata.hasDetectedSample = true;
    }
    
    // Record this sample
    metadata.addRecentSample(sample);
    
    // Save metadata periodically (after acquiring some data)
    if (trackMetadata.size() >= 3 && !hasLoadedMetadata) {
      saveMetadataFile();
      hasLoadedMetadata = true;
    }
  }
  
  String detectSampleType(String sample) {
    // Detect the type of a sample based on its name
    String lowerSample = sample.toLowerCase();
    
    // Check each category
    for (String category : sampleCategories.keySet()) {
      ArrayList<String> keywords = sampleCategories.get(category);
      for (String keyword : keywords) {
        if (lowerSample.contains(keyword.toLowerCase())) {
          return category;
        }
      }
    }
    
    // No match found
    return null;
  }
  
  void processMetadataMessage(OscMessage msg) {
    // Process a special metadata OSC message
    // Format: /parvagues/metadata orbit "name" "type" ["prop1=value1,prop2=value2"]
    
    try {
      // Extract data
      int orbit = msg.get(0).intValue();
      String name = msg.get(1).stringValue();
      String type = msg.get(2).stringValue();
      
      // Create or update metadata
      TrackMetadata metadata = getMetadata(orbit);
      metadata.name = name;
      metadata.type = type;
      
      // Process properties if present
      if (msg.arguments().length > 3) {
        String props = msg.get(3).stringValue();
        String[] properties = props.split(",");
        for (String prop : properties) {
          String[] keyValue = prop.split("=");
          if (keyValue.length == 2) {
            metadata.setProperty(keyValue[0].trim(), keyValue[1].trim());
          }
        }
      }
      
      // Track that this is explicit metadata
      metadata.isExplicit = true;
      
      // Save metadata file
      saveMetadataFile();
      
      println("Updated metadata for orbit " + orbit + ": " + name + " (" + type + ")");
      
    } catch (Exception e) {
      println("Error processing metadata message: " + e.getMessage());
    }
  }
  
  String getDebugInfo() {
    // Generate debug info about current metadata
    StringBuilder info = new StringBuilder();
    info.append("Track Metadata:\n");
    
    for (int orbit : trackMetadata.keySet()) {
      TrackMetadata metadata = trackMetadata.get(orbit);
      info.append("d" + (orbit + 1) + ": " + metadata.name + " (" + metadata.type + ")");
      
      // Add sample info
      if (metadata.recentSamples.size() > 0) {
        info.append(" - Samples: ");
        for (int i = 0; i < Math.min(3, metadata.recentSamples.size()); i++) {
          if (i > 0) info.append(", ");
          info.append(metadata.recentSamples.get(i));
        }
        if (metadata.recentSamples.size() > 3) {
          info.append(", ...");
        }
      }
      
      info.append("\n");
    }
    
    return info.toString();
  }
}

/**
 * TrackMetadata class
 * 
 * Stores metadata about a track
 */
class TrackMetadata {
  int orbit;
  String name;
  String type;
  HashMap<String, String> properties;
  ArrayList<String> recentSamples;
  boolean isExplicit;
  boolean hasDetectedSample;
  
  TrackMetadata(int orbit, String name, String type) {
    this.orbit = orbit;
    this.name = name;
    this.type = type;
    this.properties = new HashMap<String, String>();
    this.recentSamples = new ArrayList<String>();
    this.isExplicit = false;
    this.hasDetectedSample = false;
  }
  
  void setProperty(String key, String value) {
    properties.put(key, value);
  }
  
  String getProperty(String key, String defaultValue) {
    if (properties.containsKey(key)) {
      return properties.get(key);
    }
    return defaultValue;
  }
  
  void addRecentSample(String sample) {
    // Add to recent samples, avoiding duplicates
    if (!recentSamples.contains(sample)) {
      recentSamples.add(sample);
      
      // Keep only the 10 most recent samples
      if (recentSamples.size() > 10) {
        recentSamples.remove(0);
      }
    }
  }
  
  color getTrackColor() {
    // Get the color for this track based on type or explicit setting
    
    // Check if color is explicitly set in properties
    if (properties.containsKey("color")) {
      String hexColor = properties.get("color");
      if (hexColor.startsWith("#")) {
        hexColor = hexColor.substring(1);
      }
      return unhex("FF" + hexColor);
    }
    
    // Otherwise, use default color based on orbit
    return orbitColors[orbit];
  }
}
