//load local dependencies
const EventEmitter = require('events');

//load all config modules
const defaultConfig = require('./nrmseConfig.json');
const ConfigUtils = require('../helper/configUtils');

class ErrorMetrics {
  constructor(config = {}, logger) {

    this.emitter = new EventEmitter();  // Own EventEmitter
    this.configUtils = new ConfigUtils(defaultConfig);
    this.config = this.configUtils.initConfig(config);
  
    // Init after config is set
    this.logger = logger;

    // For long-term NRMSD accumulation
    this.cumNRMSD = 0;
    this.cumCount = 0;
  }

  //INCLUDE timestamps in the next update OLIFANT
  meanSquaredError(predicted, measured) {
    if (predicted.length !== measured.length) {
      this.logger.error("Comparing MSE Arrays must have the same length.");
      return 0;
    }


    let sumSqError = 0;
    for (let i = 0; i < predicted.length; i++) {
      const err = predicted[i] - measured[i];
      sumSqError += err * err;
    }
    return sumSqError / predicted.length;
  }

  rootMeanSquaredError(predicted, measured) {
    return Math.sqrt(this.meanSquaredError(predicted, measured));
  }

  normalizedRootMeanSquaredError(predicted, measured, processMin, processMax) {
    const range = processMax - processMin;
    if (range <= 0) {
      this.logger.error("Invalid process range: processMax must be greater than processMin.");
    }
    const rmse = this.rootMeanSquaredError(predicted, measured);
    return rmse / range;
  }

  longTermNRMSD(input) {

    const storedNRMSD = this.cumNRMSD;
    const storedCount = this.cumCount;
    const newCount = storedCount + 1;
    
    // Update cumulative values
    this.cumCount = newCount;
    
    // Calculate new running average
    if (storedCount === 0) {
      this.cumNRMSD = input; // First value
    } else {
      // Running average formula: newAvg = oldAvg + (newValue - oldAvg) / newCount
      this.cumNRMSD = storedNRMSD + (input - storedNRMSD) / newCount;
    }

    if(newCount >= 100) {
      // Return the current NRMSD value, not just the contribution from this sample
      return this.cumNRMSD;
    }
    return 0;
  }

  normalizeUsingRealtime(predicted, measured) {
    const realtimeMin = Math.min(Math.min(...predicted), Math.min(...measured));
    const realtimeMax = Math.max(Math.max(...predicted), Math.max(...measured));
    const range = realtimeMax - realtimeMin;
    if (range <= 0) {
      throw new Error("Invalid process range: processMax must be greater than processMin.");
    }
    const rmse = this.rootMeanSquaredError(predicted, measured);
    return rmse / range;
  }

  detectImmediateDrift(nrmse) {
    let ImmDrift = {};
    this.logger.debug(`checking immediate drift with thresholds : ${this.config.thresholds.NRMSE_HIGH} ${this.config.thresholds.NRMSE_MEDIUM} ${this.config.thresholds.NRMSE_LOW}`);
    switch (true) {
      case( nrmse > this.config.thresholds.NRMSE_HIGH ) :
        ImmDrift = {level : 3 , feedback : "High immediate drift detected"};
        break;
      case( nrmse > this.config.thresholds.NRMSE_MEDIUM ) :
        ImmDrift = {level : 2 , feedback : "Medium immediate drift detected"};
        break;
      case(nrmse > this.config.thresholds.NRMSE_LOW ):
        ImmDrift = {level : 1 , feedback : "Low immediate drift detected"};
        break;
      default:
        ImmDrift = {level : 0 , feedback : "No drift detected"};
    }
    return ImmDrift;
  }

  detectLongTermDrift(longTermNRMSD) {
    let LongTermDrift = {};
    this.logger.debug(`checking longterm drift with thresholds : ${this.config.thresholds.LONG_TERM_HIGH} ${this.config.thresholds.LONG_TERM_MEDIUM} ${this.config.thresholds.LONG_TERM_LOW}`);
    switch (true) {
      case(Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_HIGH) :
        LongTermDrift = {level : 3 , feedback : "High long-term drift detected"};
        break;
      case (Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_MEDIUM) :
        LongTermDrift = {level : 2 , feedback : "Medium long-term drift detected"};
        break;
      case ( Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_LOW ) :
        LongTermDrift = {level : 1 , feedback : "Low long-term drift detected"};
        break;
      default:
        LongTermDrift = {level : 0 , feedback : "No drift detected"}; 
    }
    return LongTermDrift;
  }

  detectDrift(nrmse, longTermNRMSD) {
    const ImmDrift = this.detectImmediateDrift(nrmse);
    const LongTermDrift = this.detectLongTermDrift(longTermNRMSD);
    return { ImmDrift, LongTermDrift };
  }

  // asses the drift
  assessDrift(predicted, measured, processMin, processMax) {
    // Compute NRMSE and check for immediate drift
    const nrmse = this.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
    this.logger.debug(`NRMSE: ${nrmse}`);
    // cmopute long-term NRMSD and add result to cumalitve NRMSD
    const longTermNRMSD = this.longTermNRMSD(nrmse);
    // return the drift
    // Return the drift assessment object
    const driftAssessment = this.detectDrift(nrmse, longTermNRMSD);
    return {
      nrmse,
      longTermNRMSD,
      immediateLevel: driftAssessment.ImmDrift.level,
      immediateFeedback: driftAssessment.ImmDrift.feedback,
      longTermLevel: driftAssessment.LongTermDrift.level,
      longTermFeedback: driftAssessment.LongTermDrift.feedback
    };
  }


}

module.exports = ErrorMetrics;
