// Add unit conversion support
const convertModule = require('../convert/index');

class Measurement {
  constructor(type, variant, position, windowSize, distance = null) {
    this.type = type;               // e.g. 'pressure', 'flow', etc.
    this.variant = variant;         // e.g. 'predicted' or 'measured', etc..
    this.position = position;       // Downstream or upstream of parent object
    this.windowSize = windowSize;   // Rolling window size
    this.distance = distance;       // Distance from parent, if applicable

    // Place all data inside an array
    this.values = [];               // Array to store all values
    this.timestamps = [];           // Array to store all timestamps
    
    // Unit tracking
    this.unit = null;                // Current unit of measurement

    // For tracking differences if this is a calculated difference measurement
    this.isDifference = false;
    this.sourcePositions = [];
  }

  // -- Updater methods --
  setType(type) {
    this.type = type;
    return this;
  }

  setVariant(variant) {
    this.variant = variant;
    return this;
  }

  setPosition(position) {
    this.position = position;
    return this;
  }

  setDistance(distance) {
    this.distance = distance;
    return this;
  }

  setValue(value, timestamp = Date.now()) {

    //shift the oldest value
    if(this.values.length >= this.windowSize){
      this.values.shift();
      this.timestamps.shift();
    }

    //push the new value
    this.values.push(value);
    this.timestamps.push(timestamp);

    return this;
  }

  setUnit(unit) {
    this.unit = unit;
    return this;
  }
  
  // -- Getter methods --
  getCurrentValue() {
    if (this.values.length === 0) return null;
    return this.values[this.values.length - 1];
  }

  getLaggedValue(lag){
    if(this.values.length <= lag) return null;
    return this.values[this.values.length - lag];
  }

  getLaggedSample(lag){
    if (lag < 0) throw new Error('lag must be >= 0');
      const index = this.values.length - 1 - lag;
    if (index < 0) return null;

    return {
      value: this.values[index],
      timestamp: this.timestamps[index],
      unit: this.unit,
    };
  }
  
  getAverage() {
    if (this.values.length === 0) return null;
    const sum = this.values.reduce((acc, val) => acc + val, 0);
    return sum / this.values.length;
  }
  
  getMin() {
    if (this.values.length === 0) return null;
    return Math.min(...this.values);
  }
  
  getMax() {
    if (this.values.length === 0) return null;
    return Math.max(...this.values);
  }
  
  getAllValues() {
    return {
      values: [...this.values],
      timestamps: [...this.timestamps],
      unit: this.unit
    };
  }
  
  // -- Position-based methods --
  
  // Create a new measurement that is the difference between two positions
  static createDifference(upstreamMeasurement, downstreamMeasurement) {

    if (upstreamMeasurement.type !== downstreamMeasurement.type || 
        upstreamMeasurement.variant !== downstreamMeasurement.variant) {
      throw new Error('Cannot calculate difference between different measurement types or variants');
    }
    
    // Ensure units match
    let downstream = downstreamMeasurement;
    if (upstreamMeasurement.unit && downstream.unit && 
        upstreamMeasurement.unit !== downstream.unit) {
      downstream = downstream.convertTo(upstreamMeasurement.unit);
    }
    
    // Create a new difference measurement
    const diffMeasurement = new Measurement(
      upstreamMeasurement.type,
      upstreamMeasurement.variant,
      'difference',
      Math.min(upstreamMeasurement.windowSize, downstream.windowSize)
    );
    
    // Mark as a difference measurement and keep track of sources
    diffMeasurement.isDifference = true;
    diffMeasurement.sourcePositions = ['upstream', 'downstream'];
    
    // Calculate all differences where timestamps match
    const upValues = upstreamMeasurement.getAllValues();
    const downValues = downstream.getAllValues();
    
    // Match timestamps and calculate differences
    for (let i = 0; i < upValues.timestamps.length; i++) {
      const upTimestamp = upValues.timestamps[i];
      const downIndex = downValues.timestamps.indexOf(upTimestamp);
      
      if (downIndex !== -1) {
        const diff = upValues.values[i] - downValues.values[downIndex];
        diffMeasurement.setValue(diff, upTimestamp);
      }
    }
    
    diffMeasurement.setUnit(upstreamMeasurement.unit);
    
    return diffMeasurement;
  }
  
  // -- Additional getter methods --
  getLatestTimestamp() {
    if (this.timestamps.length === 0) return null;
    return this.timestamps[this.timestamps.length - 1];
  }
  
  getValueAtTimestamp(timestamp) {
    const index = this.timestamps.indexOf(timestamp);
    if (index === -1) return null;
    return this.values[index];
  }
  
  // -- Unit conversion methods --
  convertTo(targetUnit) {
    if (!this.unit) {
      throw new Error('Current unit not set, cannot convert');
    }
    
    try {
      const convertedValues = this.values.map(value => 
        convertModule.convert(value).from(this.unit).to(targetUnit)
      );
      
      const newMeasurement = new Measurement(
        this.type, 
        this.variant, 
        this.position,
        this.windowSize,
        this.distance
      );
      
      // Copy values and timestamps
      newMeasurement.values = convertedValues;
      newMeasurement.timestamps = [...this.timestamps];
      newMeasurement.unit = targetUnit;
      
      return newMeasurement;
    } catch (error) {
      throw new Error(`Unit conversion failed: ${error.message}`);
    }
  }
}

module.exports = Measurement;
