/**
 * GigLez Statistical Fingerprint Extractor
 * JavaScript port of Python pattern_decoder.py fingerprinting
 *
 * Extracts statistical characteristics from pulse data
 * for device matching
 *
 * @module fingerprint
 */

/**
 * Calculate mean of array
 * @private
 */
function mean(arr) {
  if (arr.length === 0) return 0;
  return arr.reduce((sum, val) => sum + val, 0) / arr.length;
}

/**
 * Calculate standard deviation of array
 * @private
 */
function std(arr) {
  if (arr.length === 0) return 0;
  const m = mean(arr);
  const variance = arr.reduce((sum, val) => sum + Math.pow(val - m, 2), 0) / arr.length;
  return Math.sqrt(variance);
}

/**
 * Calculate sum of array
 * @private
 */
function sum(arr) {
  return arr.reduce((a, b) => a + b, 0);
}

/**
 * Pulse Fingerprint - Statistical characteristics of a signal
 */
export class PulseFingerprint {
  constructor({
    meanPulseWidth = 0,
    stdPulseWidth = 0,
    meanGapWidth = 0,
    stdGapWidth = 0,
    pulseGapRatio = 0,
    dutyCycle = 0,
    pulseCount = 0,
    minPulse = 0,
    maxPulse = 0,
    minGap = 0,
    maxGap = 0
  }) {
    this.meanPulseWidth = meanPulseWidth;
    this.stdPulseWidth = stdPulseWidth;
    this.meanGapWidth = meanGapWidth;
    this.stdGapWidth = stdGapWidth;
    this.pulseGapRatio = pulseGapRatio;
    this.dutyCycle = dutyCycle;
    this.pulseCount = pulseCount;
    this.minPulse = minPulse;
    this.maxPulse = maxPulse;
    this.minGap = minGap;
    this.maxGap = maxGap;
  }

  /**
   * Convert to plain object
   * @returns {Object}
   */
  toObject() {
    return {
      meanPulseWidth: this.meanPulseWidth,
      stdPulseWidth: this.stdPulseWidth,
      meanGapWidth: this.meanGapWidth,
      stdGapWidth: this.stdGapWidth,
      pulseGapRatio: this.pulseGapRatio,
      dutyCycle: this.dutyCycle,
      pulseCount: this.pulseCount,
      minPulse: this.minPulse,
      maxPulse: this.maxPulse,
      minGap: this.minGap,
      maxGap: this.maxGap
    };
  }

  /**
   * Get human-readable summary
   * @returns {string}
   */
  toString() {
    return `Fingerprint(pulses=${this.pulseCount}, ` +
           `mean=${this.meanPulseWidth.toFixed(1)}μs, ` +
           `duty=${(this.dutyCycle * 100).toFixed(1)}%)`;
  }
}

/**
 * Extract statistical fingerprint from pulse data
 *
 * Analyzes timing characteristics to create a unique
 * signature for the signal
 *
 * @param {number[]} pulses - Raw pulse data (positive = HIGH, negative = LOW)
 * @returns {PulseFingerprint}
 */
export function extractFingerprint(pulses) {
  if (!pulses || pulses.length === 0) {
    return new PulseFingerprint({});
  }

  // Separate HIGH pulses and LOW gaps
  const highPulses = pulses.filter(p => p > 0);
  const lowPulses = pulses.filter(p => p < 0).map(Math.abs);

  // Calculate pulse statistics
  const meanPulse = mean(highPulses);
  const stdPulse = std(highPulses);

  // Calculate gap statistics
  const meanGap = mean(lowPulses);
  const stdGap = std(lowPulses);

  // Calculate timing ratios
  const totalHigh = sum(highPulses);
  const totalLow = sum(lowPulses);
  const totalTime = totalHigh + totalLow;

  const pulseGapRatio = meanGap > 0 ? meanPulse / meanGap : 0;
  const dutyCycle = totalTime > 0 ? totalHigh / totalTime : 0;

  return new PulseFingerprint({
    meanPulseWidth: meanPulse,
    stdPulseWidth: stdPulse,
    meanGapWidth: meanGap,
    stdGapWidth: stdGap,
    pulseGapRatio,
    dutyCycle,
    pulseCount: pulses.length,
    minPulse: highPulses.length > 0 ? Math.min(...highPulses) : 0,
    maxPulse: highPulses.length > 0 ? Math.max(...highPulses) : 0,
    minGap: lowPulses.length > 0 ? Math.min(...lowPulses) : 0,
    maxGap: lowPulses.length > 0 ? Math.max(...lowPulses) : 0
  });
}

/**
 * Calculate similarity between two fingerprints
 * Returns a score from 0 (no match) to 1 (perfect match)
 *
 * @param {PulseFingerprint} fp1 - First fingerprint
 * @param {PulseFingerprint} fp2 - Second fingerprint
 * @returns {number} Similarity score (0-1)
 */
export function calculateSimilarity(fp1, fp2) {
  // Pulse width similarity (40% weight)
  const pulseWidthError = Math.abs(fp1.meanPulseWidth - fp2.meanPulseWidth) /
                          Math.max(fp1.meanPulseWidth, fp2.meanPulseWidth);
  const pulseWidthSimilarity = Math.max(0, 1 - pulseWidthError);

  // Pulse count similarity (30% weight)
  const countError = Math.abs(fp1.pulseCount - fp2.pulseCount) /
                     Math.max(fp1.pulseCount, fp2.pulseCount);
  const countSimilarity = Math.max(0, 1 - countError);

  // Duty cycle similarity (20% weight)
  const dutyError = Math.abs(fp1.dutyCycle - fp2.dutyCycle);
  const dutySimilarity = Math.max(0, 1 - dutyError);

  // Pulse/gap ratio similarity (10% weight)
  const ratioError = Math.abs(fp1.pulseGapRatio - fp2.pulseGapRatio) /
                     Math.max(fp1.pulseGapRatio, fp2.pulseGapRatio);
  const ratioSimilarity = Math.max(0, 1 - ratioError);

  // Weighted average
  return (
    pulseWidthSimilarity * 0.4 +
    countSimilarity * 0.3 +
    dutySimilarity * 0.2 +
    ratioSimilarity * 0.1
  );
}

/**
 * Compare fingerprint against expected protocol characteristics
 * Used for protocol library matching
 *
 * @param {PulseFingerprint} fingerprint - Observed fingerprint
 * @param {Object} expected - Expected characteristics from protocol
 * @param {number} expected.shortPulseUs - Expected short pulse width
 * @param {number} expected.longPulseUs - Expected long pulse width
 * @param {number} expected.typicalPulseCount - Expected pulse count
 * @returns {Object} { pulseSimilarity, countSimilarity, overall }
 */
export function compareToProtocol(fingerprint, expected) {
  // Expected mean pulse is average of short and long
  const expectedMeanPulse = (expected.shortPulseUs + expected.longPulseUs) / 2;

  // Pulse width comparison
  const pulseError = Math.abs(expectedMeanPulse - fingerprint.meanPulseWidth) / expectedMeanPulse;
  const pulseSimilarity = Math.max(0, 1 - pulseError);

  // Pulse count comparison
  const countError = Math.abs(expected.typicalPulseCount - fingerprint.pulseCount) /
                     expected.typicalPulseCount;
  const countSimilarity = Math.max(0, 1 - countError);

  // Overall score (weighted)
  const overall = (pulseSimilarity * 0.6 + countSimilarity * 0.4) * 0.8; // Scale down for lower confidence

  return {
    pulseSimilarity,
    countSimilarity,
    overall,
    pulseError: `${(pulseError * 100).toFixed(2)}%`,
    countError: `${(countError * 100).toFixed(2)}%`
  };
}

/**
 * Classify signal type based on fingerprint
 * Uses heuristics to guess signal type
 *
 * @param {PulseFingerprint} fingerprint
 * @returns {Object} { type, confidence, reason }
 */
export function classifySignal(fingerprint) {
  const { meanPulseWidth, dutyCycle, pulseCount, pulseGapRatio } = fingerprint;

  // Very short pulses (< 100μs) - likely TPMS or high-speed protocol
  if (meanPulseWidth < 100) {
    return {
      type: 'TPMS or High-Speed',
      confidence: 0.7,
      reason: `Very short pulses (${meanPulseWidth.toFixed(0)}μs)`
    };
  }

  // Short pulses (100-300μs) - likely Acurite or similar
  if (meanPulseWidth >= 100 && meanPulseWidth < 300) {
    return {
      type: 'Acurite-like',
      confidence: 0.6,
      reason: `Short pulses (${meanPulseWidth.toFixed(0)}μs)`
    };
  }

  // Medium pulses (300-700μs) - likely garage door openers or remotes
  if (meanPulseWidth >= 300 && meanPulseWidth < 700) {
    return {
      type: 'Remote Control',
      confidence: 0.6,
      reason: `Medium pulses (${meanPulseWidth.toFixed(0)}μs)`
    };
  }

  // Long pulses (700+μs) - likely Oregon Scientific or weather sensors
  if (meanPulseWidth >= 700) {
    return {
      type: 'Weather Sensor',
      confidence: 0.7,
      reason: `Long pulses (${meanPulseWidth.toFixed(0)}μs)`
    };
  }

  return {
    type: 'Unknown',
    confidence: 0.3,
    reason: 'Pulse characteristics do not match known patterns'
  };
}

/**
 * Generate detailed fingerprint report
 * For debugging and analysis
 *
 * @param {PulseFingerprint} fingerprint
 * @returns {string} Multi-line report
 */
export function generateReport(fingerprint) {
  const classification = classifySignal(fingerprint);

  return `
Pulse Fingerprint Report
========================

Basic Statistics:
  Total Pulses:     ${fingerprint.pulseCount}
  Mean Pulse Width: ${fingerprint.meanPulseWidth.toFixed(2)} μs
  Std Dev:          ${fingerprint.stdPulseWidth.toFixed(2)} μs
  Mean Gap Width:   ${fingerprint.meanGapWidth.toFixed(2)} μs
  Std Dev:          ${fingerprint.stdGapWidth.toFixed(2)} μs

Pulse Range:
  Min Pulse:        ${fingerprint.minPulse} μs
  Max Pulse:        ${fingerprint.maxPulse} μs
  Min Gap:          ${fingerprint.minGap} μs
  Max Gap:          ${fingerprint.maxGap} μs

Timing Characteristics:
  Pulse/Gap Ratio:  ${fingerprint.pulseGapRatio.toFixed(3)}
  Duty Cycle:       ${(fingerprint.dutyCycle * 100).toFixed(2)}%

Classification:
  Likely Type:      ${classification.type}
  Confidence:       ${(classification.confidence * 100).toFixed(0)}%
  Reason:           ${classification.reason}
  `.trim();
}
