/**
 * SampleAnalyzer class
 * 
 * Analyzes sample usage patterns to detect properties like:
 * - Tempo
 * - Rhythmic structure
 * - Energy
 * - Role in the musical composition
 */
class SampleAnalyzer {
  HashMap<Integer, TrackAnalysis> trackAnalysis;
  HashMap<String, SampleFeatures> sampleFeatures;
  
  SampleAnalyzer() {
    trackAnalysis = new HashMap<Integer, TrackAnalysis>();
    sampleFeatures = new HashMap<String, SampleFeatures>();
    
    // Initialize with some known samples
    initializeKnownSamples();
  }
  
  void initializeKnownSamples() {
    // Predefined features for common samples
    
    // Drums
    sampleFeatures.put("bd", new SampleFeatures(120, 0.9, "kick", 0.1));
    sampleFeatures.put("sn", new SampleFeatures(120, 0.7, "snare", 0.2));
    sampleFeatures.put("hh", new SampleFeatures(120, 0.5, "hihat", 0.05));
    sampleFeatures.put("cp", new SampleFeatures(120, 0.8, "clap", 0.15));
    
    // Breakbeats (longer durations)
    SampleFeatures amenFeatures = new SampleFeatures(165, 0.95, "break", 0.5);
    amenFeatures.complexity = 0.9;
    sampleFeatures.put("jungle_breaks", amenFeatures);
    
    // From ParVagues examples
    sampleFeatures.put("suns_keys", new SampleFeatures(120, 0.6, "melodic", 0.8));
    sampleFeatures.put("suns_guitar", new SampleFeatures(120, 0.7, "melodic", 0.5));
    sampleFeatures.put("suns_voice", new SampleFeatures(120, 0.4, "vocal", 1.0));
    sampleFeatures.put("bassWarsaw", new SampleFeatures(120, 0.85, "bass", 0.3));
    sampleFeatures.put("armora", new SampleFeatures(120, 0.6, "melodic", 0.4));
    sampleFeatures.put("FMRhodes1", new SampleFeatures(120, 0.5, "melodic", 0.7));
  }
  
  void processSample(int orbit, String sound, float gain, float delta) {
    // Get or create track analysis
    TrackAnalysis analysis = getTrackAnalysis(orbit);
    
    // Record hit time
    float hitTime = millis() / 1000.0;
    analysis.recordHit(hitTime, sound, gain, delta);
    
    // Extract sample name prefix (before ":" if present)
    String samplePrefix = sound;
    if (sound.contains(":")) {
      samplePrefix = sound.substring(0, sound.indexOf(":"));
    }
    
    // Check if we have features for this sample
    if (!sampleFeatures.containsKey(samplePrefix)) {
      // Try to extract features based on sample name and context
      SampleFeatures features = extractFeatures(sound, delta, gain, analysis);
      sampleFeatures.put(samplePrefix, features);
    }
  }
  
  SampleFeatures extractFeatures(String sound, float delta, float gain, TrackAnalysis analysis) {
    // Attempt to extract sample features based on name and context
    
    // Start with defaults
    float tempo = 120;
    float energy = 0.7;
    String role = "unknown";
    float duration = 0.2;
    
    // Estimate tempo from delta (time between events)
    if (delta > 0) {
      tempo = 60.0 / delta;
    } else if (analysis.getHitCount() > 1) {
      tempo = analysis.estimateTempo();
    }
    
    // Adjust tempo to a reasonable range
    tempo = constrain(tempo, 60, 200);
    
    // Estimate energy from gain and sample name
    energy = gain * 0.8; // Base energy on gain
    
    String lowerSound = sound.toLowerCase();
    
    // Estimate role from sample name
    if (lowerSound.contains("bd") || lowerSound.contains("kick") || lowerSound.contains("bass drum")) {
      role = "kick";
      energy *= 1.2; // Kicks are typically high energy
      duration = 0.1;
    } 
    else if (lowerSound.contains("sn") || lowerSound.contains("snare") || lowerSound.contains("cp") || lowerSound.contains("clap")) {
      role = "snare";
      energy *= 1.1;
      duration = 0.15;
    } 
    else if (lowerSound.contains("hh") || lowerSound.contains("hat")) {
      role = "hihat";
      energy *= 0.9;
      duration = 0.05;
    } 
    else if (lowerSound.contains("bass") || lowerSound.contains("sub") || lowerSound.contains("808")) {
      role = "bass";
      energy *= 1.1;
      duration = 0.4;
    } 
    else if (lowerSound.contains("break") || lowerSound.contains("jungle") || lowerSound.contains("amen")) {
      role = "break";
      energy *= 1.2;
      duration = 0.5;
    } 
    else if (lowerSound.contains("key") || lowerSound.contains("pad") || lowerSound.contains("chord") || lowerSound.contains("synth")) {
      role = "melodic";
      energy *= 0.8;
      duration = 0.7;
    } 
    else if (lowerSound.contains("fx") || lowerSound.contains("riser") || lowerSound.contains("sweep")) {
      role = "fx";
      energy *= 0.9;
      duration = 1.0;
    } 
    else if (lowerSound.contains("voc") || lowerSound.contains("voice")) {
      role = "vocal";
      energy *= 0.7;
      duration = 0.8;
    }
    
    // Constrain energy
    energy = constrain(energy, 0.1, 1.0);
    
    return new SampleFeatures(tempo, energy, role, duration);
  }
  
  TrackAnalysis getTrackAnalysis(int orbit) {
    if (!trackAnalysis.containsKey(orbit)) {
      trackAnalysis.put(orbit, new TrackAnalysis(orbit));
    }
    return trackAnalysis.get(orbit);
  }
  
  SampleFeatures getFeaturesForOrbit(int orbit) {
    TrackAnalysis analysis = getTrackAnalysis(orbit);
    String lastSample = analysis.getLastSample();
    
    if (lastSample != null) {
      // Extract sample name prefix
      String samplePrefix = lastSample;
      if (lastSample.contains(":")) {
        samplePrefix = lastSample.substring(0, lastSample.indexOf(":"));
      }
      
      // Get features
      if (sampleFeatures.containsKey(samplePrefix)) {
        return sampleFeatures.get(samplePrefix);
      }
    }
    
    return null;
  }
  
  int getSampleCount() {
    return sampleFeatures.size();
  }
}

/**
 * TrackAnalysis class
 * 
 * Analyzes the hit patterns for a specific track
 */
class TrackAnalysis {
  int orbit;
  ArrayList<HitInfo> hits;
  String lastSample;
  
  // Pattern analysis
  float[] intervalHistogram; // For tempo detection
  float lastHitTime;
  
  TrackAnalysis(int orbit) {
    this.orbit = orbit;
    this.hits = new ArrayList<HitInfo>();
    this.lastSample = null;
    
    // Initialize histogram (for intervals between 50ms and 2000ms)
    intervalHistogram = new float[100]; // 20ms bins
    lastHitTime = -1;
  }
  
  void recordHit(float time, String sound, float gain, float delta) {
    // Add hit info
    hits.add(new HitInfo(time, sound, gain, delta));
    
    // Keep only recent hits (last 10 seconds)
    while (hits.size() > 0 && time - hits.get(0).time > 10) {
      hits.remove(0);
    }
    
    // Update last sample
    lastSample = sound;
    
    // Update interval histogram
    if (lastHitTime > 0) {
      float interval = time - lastHitTime;
      if (interval >= 0.05 && interval <= 2.0) {
        int bin = constrain(floor((interval - 0.05) * 50), 0, 99);
        intervalHistogram[bin] += 1;
      }
    }
    lastHitTime = time;
  }
  
  float estimateTempo() {
    // Find the most common interval
    int maxBin = 0;
    float maxValue = 0;
    
    for (int i = 0; i < intervalHistogram.length; i++) {
      if (intervalHistogram[i] > maxValue) {
        maxValue = intervalHistogram[i];
        maxBin = i;
      }
    }
    
    // Convert bin to tempo
    float interval = 0.05 + (maxBin / 50.0);
    float tempo = 60.0 / interval;
    
    return tempo;
  }
  
  int getHitCount() {
    return hits.size();
  }
  
  String getLastSample() {
    return lastSample;
  }
  
  // Inner class to store hit information
  class HitInfo {
    float time;
    String sound;
    float gain;
    float delta;
    
    HitInfo(float time, String sound, float gain, float delta) {
      this.time = time;
      this.sound = sound;
      this.gain = gain;
      this.delta = delta;
    }
  }
}

/**
 * SampleFeatures class
 * 
 * Stores detected features of a sample
 */
class SampleFeatures {
  float tempo;      // Estimated tempo in BPM
  float energy;     // 0-1 energy level
  String role;      // Role of the sample (kick, snare, etc.)
  float duration;   // Typical duration in seconds
  float complexity; // 0-1 complexity level
  
  SampleFeatures(float tempo, float energy, String role, float duration) {
    this.tempo = tempo;
    this.energy = energy;
    this.role = role;
    this.duration = duration;
    this.complexity = 0.5; // Default complexity
  }
}
