const EventEmitter = require('events');
const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop} = require('generalFunctions');

class Machine {

  /*-------------------               Construct and set vars               -------------------*/
  constructor(machineConfig = {}, stateConfig = {}, errorMetricsConfig = {}) {

    //basic setup
    this.emitter = new EventEmitter();  // Own EventEmitter
    
    this.logger = new logger(machineConfig.general.logging.enabled,machineConfig.general.logging.logLevel, machineConfig.general.name);
    this.configManager = new configManager(); 
    this.defaultConfig = this.configManager.getConfig('rotatingMachine'); // Load default config for rotating machine ( use software type name ? )
    this.configUtils = new configUtils(this.defaultConfig);

    // Load a specific curve
    this.model = machineConfig.asset.model; // Get the model from the machineConfig
    this.curve = this.model ? loadCurve(this.model) : null; // we need to convert the curve and add units to the curve information

    //Init config and check if it is valid
    this.config = this.configUtils.initConfig(machineConfig);
    
    //add unique name for this node.
    this.config = this.configUtils.updateConfig(this.config, {general:{name: this.config.functionality?.softwareType + "_" + machineConfig.general.id}}); // add unique name if not present

    if (!this.model || !this.curve) {
      this.logger.error(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`);
      // Set prediction objects to null to prevent method calls
      this.predictFlow = null;
      this.predictPower = null;
      this.predictCtrl = null;
      this.hasCurve = false;
    }
    else{
      this.hasCurve = true;
      this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } }); 
      //machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
      this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq });     // load nq (x : ctrl , y : flow relationship) 
      this.predictPower = new predict({ curve: this.config.asset.machineCurve.np });     // load np (x : ctrl , y : power relationship)
      this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
    }

    this.state = new state(stateConfig, this.logger); // Init State manager and pass logger
    this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);

    // Initialize measurements
    this.measurements = new MeasurementContainer({
      autoConvert: true,
      windowSize: 50,
      defaultUnits: {
        pressure: 'mbar',
        flow: this.config.general.unit,
        power: 'kW',
        temperature: 'C'
      }
    });

    this.interpolation = new interpolation();

    this.flowDrift = null;

    this.currentMode = this.config.mode.current;
    this.currentEfficiencyCurve = {};
    this.cog = 0;
    this.NCog = 0;
    this.cogIndex = 0;
    this.minEfficiency = 0;
    this.absDistFromPeak = 0;
    this.relDistFromPeak = 0;

    // When position state changes, update position
    this.state.emitter.on("positionChange", (data) => {
      this.logger.debug(`Position change detected: ${data}`);
      this.updatePosition();
    });

    //When state changes look if we need to do other updates
    this.state.emitter.on("stateChange", (newState) => {
      this.logger.debug(`State change detected: ${newState}`);
      this._updateState();
    });


    //perform init for certain values
    this._init();

    this.child = {}; // object to hold child information so we know on what to subscribe
    this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility


  }

  _init(){
    //assume standard temperature is 20degrees
    this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
    //assume standard atm pressure is at sea level
    this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
    //populate min and max
    const flowunit = this.config.general.unit;
    this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit) 
    this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
  }

  _updateState(){
    const isOperational = this._isOperationalState();
    if(!isOperational){
      //overrule the last prediction this should be 0 now
      this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
      this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
    }
  }

  /*-------------------               Register child events               -------------------*/
  registerChild(child, softwareType) {
    this.logger.debug('Setting up child event for softwaretype ' + softwareType);

    if(softwareType === "measurement"){
          const position = child.config.functionality.positionVsParent;
          const distance = child.config.functionality.distanceVsParent || 0;
          const measurementType = child.config.asset.type;
          const key = `${measurementType}_${position}`;
          //rebuild to measurementype.variant no position and then switch based on values not strings or names.
          const eventName = `${measurementType}.measured.${position}`;

          this.logger.debug(`Setting up listener for ${eventName} from child ${child.config.general.name}`);
          // Register event listener for measurement updates
          child.measurements.emitter.on(eventName, (eventData) => {
          this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
      

          this.logger.debug(` Emitting... ${eventName} with data:`);
          // Store directly in parent's measurement container
          this.measurements
            .type(measurementType)
            .variant("measured")
            .position(position)
            .value(eventData.value, eventData.timestamp, eventData.unit);
          
          // Call the appropriate handler
          this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
      });
    }
  }

// Centralized handler dispatcher
_callMeasurementHandler(measurementType, value, position, context) {
  switch (measurementType) {
    case 'pressure':
      this.updateMeasuredPressure(value, position, context);
      break;
      
    case 'flow':
      this.updateMeasuredFlow(value, position, context);
      break;
      
    case 'temperature':
      this.updateMeasuredTemperature(value, position, context);
      break;
      
    default:
      this.logger.warn(`No handler for measurement type: ${measurementType}`);
      // Generic handler - just update position
      this.updatePosition();
      break;
  }
}

//---------------- END child stuff -------------//

    // Method to assess drift using errorMetrics
    assessDrift(measurement, processMin, processMax) {
      this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
      const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
      const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;

      if (!predictedMeasurement || !measuredMeasurement) return null;
      
      return this.errorMetrics.assessDrift(
        predictedMeasurement,
        measuredMeasurement,
        processMin,
        processMax
      );
    }

  reverseCurve(curve) {
    const reversedCurve = {};
    for (const [pressure, values] of Object.entries(curve)) {
      reversedCurve[pressure] = {
        x: [...values.y], // Previous y becomes new x
        y: [...values.x]  // Previous x becomes new y
      };
    }
    return reversedCurve;
  }

  // -------- Config -------- //
  updateConfig(newConfig) {
    this.config = this.configUtils.updateConfig(this.config, newConfig);
  }

  // -------- Mode and Input Management -------- //
  isValidSourceForMode(source, mode) {
    const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
    const allowed = allowedSourcesSet.has(source);
    allowed? 
    this.logger.debug(`source is allowed proceeding with ${source} for mode ${mode}`) : 
    this.logger.warn(`${source} is not allowed in mode ${mode}`);

    return allowed;
  }

  isValidActionForMode(action, mode) {
    const allowedActionsSet = this.config.mode.allowedActions[mode] || [];
    const allowed = allowedActionsSet.has(action);
    allowed ? 
    this.logger.debug(`Action is allowed proceeding with ${action} for mode ${mode}`) : 
    this.logger.warn(`${action} is not allowed in mode ${mode}`);

    return allowed;
  }

  async handleInput(source, action, parameter) {

    //sanitize input
    if( typeof action !== 'string'){this.logger.error(`Action must be string`); return;}
    //convert to lower case to avoid to many mistakes in commands
    action = action.toLowerCase();

    // check for validity of the request
    if(!this.isValidActionForMode(action,this.currentMode)){return ;}
    if (!this.isValidSourceForMode(source, this.currentMode)) {return ;}
    
    this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
    
    try {
      switch (action) {

        case "execsequence":
          return await this.executeSequence(parameter);

        case "execmovement":
          return await this.setpoint(parameter);

        case "entermaintenance":
           
          return await this.executeSequence(parameter);

        
        case "exitmaintenance":
          return await this.executeSequence(parameter);

        case "flowmovement":
          // Calculate the control value for a desired flow
          const pos = this.calcCtrl(parameter);
          // Move to the desired setpoint
          return await this.setpoint(pos);

        case "emergencystop":
          this.logger.warn(`Emergency stop activated by '${source}'.`);
          return await this.executeSequence("emergencyStop");

        case "statuscheck":
          this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`);
          break;

        default:
          this.logger.warn(`Action '${action}' is not implemented.`);
          break;
      }
      this.logger.debug(`Action '${action}' successfully executed`);
      return {status : true , feedback: `Action '${action}' successfully executed.`};
    } catch (error) {
      this.logger.error(`Error handling input: ${error}`);
    }

  }

  abortMovement(reason = "group override") {
    if (this.state?.abortCurrentMovement) {
      this.state.abortCurrentMovement(reason);
    }
  }

  setMode(newMode) {
    const availableModes = this.defaultConfig.mode.current.rules.values.map(v => v.value);
    if (!availableModes.includes(newMode)) {
      this.logger.warn(`Invalid mode '${newMode}'. Allowed modes are: ${availableModes.join(', ')}`);
      return;
    }

    this.currentMode = newMode;
    this.logger.info(`Mode successfully changed to '${newMode}'.`);
  }

  // -------- Sequence Handlers -------- //
  async executeSequence(sequenceName) {

    const sequence = this.config.sequences[sequenceName];

    if (!sequence || sequence.size === 0) {
      this.logger.warn(`Sequence '${sequenceName}' not defined.`);
      return;
    }

    if (this.state.getCurrentState() == "operational" && sequenceName == "shutdown") {
      this.logger.info(`Machine will ramp down to position 0 before performing ${sequenceName} sequence`);
      await this.setpoint(0);
    }

    this.logger.info(` --------- Executing sequence: ${sequenceName} -------------`);

    for (const state of sequence) {
      try {
        await this.state.transitionToState(state);
        // Update measurements after state change

      } catch (error) {
        this.logger.error(`Error during sequence '${sequenceName}': ${error}`);
        break; // Exit sequence execution on error
      }
    }

    //recalc flow and power
    this.updatePosition();
  }

  async setpoint(setpoint) {

    try {
      // Validate setpoint
      if (typeof setpoint !== 'number' || setpoint < 0) {
        throw new Error("Invalid setpoint: Setpoint must be a non-negative number.");
      }

      this.logger.info(`Setting setpoint to ${setpoint}. Current position: ${this.state.getCurrentPosition()}`);

      // Move to the desired setpoint
      await this.state.moveTo(setpoint);

    } catch (error) {
      console.error(`Error setting setpoint: ${error}`);
    }
  }

  // Calculate flow based on current pressure and position
  calcFlow(x) {
    if(this.hasCurve) {
      if (!this._isOperationalState()) {
        this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
        this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
        this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`);
        return 0;
      }

      const cFlow = this.predictFlow.y(x);
      this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.config.general.unit);
      this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.config.general.unit);
      //this.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
      return cFlow;
    }

    // If no curve data is available, log a warning and return 0
    this.logger.warn(`No curve data available for flow calculation. Returning 0.`);
    this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.config.general.unit);
    this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.config.general.unit);
    return 0;
    
  }

  // Calculate power based on current pressure and position
  calcPower(x) {
    if(this.hasCurve) {
      if (!this._isOperationalState()) {
        this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
        this.logger.debug(`Machine is not operational. Setting predicted power to 0.`);
        return 0;
      }
    
      //this.predictPower.currentX = x; Decrepated
      const cPower = this.predictPower.y(x);
      this.measurements.type("power").variant("predicted").position('atEquipment').value(cPower);
      //this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
      return cPower;
    }
    // If no curve data is available, log a warning and return 0
    this.logger.warn(`No curve data available for power calculation. Returning 0.`);
    this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
    return 0;

  }

  // calculate the power consumption using only flow and pressure
  inputFlowCalcPower(flow) {
    if(this.hasCurve) {

      this.predictCtrl.currentX = flow;
      const cCtrl = this.predictCtrl.y(flow);
      this.predictPower.currentX = cCtrl;
      const cPower = this.predictPower.y(cCtrl);
      return cPower;
    }
    
    // If no curve data is available, log a warning and return 0
    this.logger.warn(`No curve data available for power calculation. Returning 0.`);
    this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
    return 0;

  }

  // Function to predict control value for a desired flow
  calcCtrl(x) {
    if(this.hasCurve) {
      this.predictCtrl.currentX = x;
      const cCtrl = this.predictCtrl.y(x);
      this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(cCtrl);
      //this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
      return cCtrl;
    }
    
    // If no curve data is available, log a warning and return 0
    this.logger.warn(`No curve data available for control calculation. Returning 0.`);
    this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(0);
    return 0;

  }

  // returns the best available pressure measurement to use in the prediction calculation
  // this will be either the differential pressure, downstream or upstream pressure
  getMeasuredPressure() {
    if(this.hasCurve === false){
      this.logger.error(`No valid curve available to calculate prediction using last known pressure`);
      return 0;
    }
    
    const pressureDiff = this.measurements.type('pressure').variant('measured').difference();

    // Both upstream & downstream => differential
    if (pressureDiff) {
      this.logger.debug(`Pressure differential: ${pressureDiff.value}`);
      this.predictFlow.fDimension = pressureDiff.value;
      this.predictPower.fDimension = pressureDiff.value;
      this.predictCtrl.fDimension = pressureDiff.value;
      //update the cog
      const { cog, minEfficiency } = this.calcCog();
      // calc efficiency
      const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
      //update the distance from peak
      this.calcDistanceBEP(efficiency,cog,minEfficiency);

      return pressureDiff.value;
    }

    // get downstream
    const downstreamPressure = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue();

    // Only downstream => use it, warn that it's partial
    if (downstreamPressure != null) {
      this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} This is less acurate!!`);
      this.predictFlow.fDimension = downstreamPressure;
      this.predictPower.fDimension = downstreamPressure;
      this.predictCtrl.fDimension = downstreamPressure;
            //update the cog
            const { cog, minEfficiency } = this.calcCog();
            // calc efficiency
            const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
            //update the distance from peak
            this.calcDistanceBEP(efficiency,cog,minEfficiency);
      return downstreamPressure;
    }

    this.logger.error(`No valid pressure measurements available to calculate prediction using last known pressure`);

    //set default at 0 => lowest pressure possible
    this.predictFlow.fDimension = 0;
    this.predictPower.fDimension = 0;
    this.predictCtrl.fDimension = 0;
          //update the cog
          const { cog, minEfficiency } = this.calcCog();
          // calc efficiency
          const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
          //update the distance from peak
          this.calcDistanceBEP(efficiency,cog,minEfficiency);
          //place min and max flow capabilities in containerthis.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin
          this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax).unit(this.config.general.unit);
          this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
    return 0;
  }

  handleMeasuredFlow() {
    const flowDiff = this.measurements.type('flow').variant('measured').difference();

    // If both are present
    if (flowDiff != null) {
      // In theory, mass flow in = mass flow out, so they should match or be close.
      if (flowDiff.value < 0.001) {
        // flows match within tolerance
        this.logger.debug(`Flow match: ${flowDiff.value}`);
        return flowDiff.value;
      } else {
        // Mismatch => decide how to handle. Maybe take the average?
        // Or bail out with an error. Example: we bail out here.
        this.logger.error(`Something wrong with down or upstream flow measurement. Bailing out!`);
        return null;
      }
    }

    // get
    const upstreamFlow = this.measurements.type('flow').variant('measured').position('upstream').getCurrentValue();

    // Only upstream => might still accept it, but warn
    if (upstreamFlow != null) {
      this.logger.warn(`Only upstream flow is present. Using it but results may be incomplete!`);
      return upstreamFlow;
    }

    // get
    const downstreamFlow = this.measurements.type('flow').variant('measured').position('downstream').getCurrentValue();

    // Only downstream => might still accept it, but warn
    if (downstreamFlow != null) {
      this.logger.warn(`Only downstream flow is present. Using it but results may be incomplete!`);
      return downstreamFlow;
    }

    // Neither => error
    this.logger.error(`No upstream or downstream flow measurement. Bailing out!`);
    return null;
  }

  handleMeasuredPower() {
    const power = this.measurements.type("power").variant("measured").position("atEquipment").getCurrentValue();
    // If your system calls it "upstream" or just a single "value", adjust accordingly

    if (power != null) {
      this.logger.debug(`Measured power: ${power}`);
      return power;
    } else {
      this.logger.error(`No measured power found. Bailing out!`);
      return null;
    }
  }

  updateMeasuredTemperature(value, position, context = {}) {
    this.logger.debug(`Temperature update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`);
  }
  
  // context handler for pressure updates
  updateMeasuredPressure(value, position, context = {}) {
   
    this.logger.debug(`Pressure update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`);
    
    //  Store in parent's measurement container 
    this.measurements.type("pressure").variant("measured").position(position).value(value, context.timestamp, context.unit);

    // Determine what kind of value to use as pressure (upstream , downstream or difference)
    const pressure = this.getMeasuredPressure();
    this.updatePosition();
    
    this.logger.debug(`Using pressure: ${pressure} for calculations`);
  }

  // NEW: Flow handler
  updateMeasuredFlow(value, position, context = {}) {
    if (!this._isOperationalState()) {
      this.logger.warn(`Machine not operational, skipping flow update from ${context.childName || 'unknown'}`);
      return;
    }

    this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
    
    // Store in parent's measurement container
    this.measurements.type("flow").variant("measured").position(position).value(value, context.timestamp, context.unit);
    
    // Update predicted flow if you have prediction capability
    if (this.predictFlow) {
      this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0);
      this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0);
    }
  }

  // Helper method for operational state check
  _isOperationalState() {
    const state = this.state.getCurrentState();
    this.logger.debug(`Checking operational state ${this.state.getCurrentState()} ? ${["operational", "accelerating", "decelerating"].includes(state)}`);
    return ["operational", "accelerating", "decelerating"].includes(state);
  }

  //what is the internal functions that need updating when something changes that has influence on this.
  updatePosition() {
    
    if (this._isOperationalState()) {

      const currentPosition = this.state.getCurrentPosition();

      // Update the predicted values based on the new position
      const { cPower, cFlow } = this.calcFlowPower(currentPosition);

      // Calc predicted efficiency
      const efficiency = this.calcEfficiency(cPower, cFlow, "predicted");

      //update the cog
      const { cog, minEfficiency } = this.calcCog();

      //update the distance from peak
      this.calcDistanceBEP(efficiency,cog,minEfficiency);

    }

  }

  calcDistanceFromPeak(currentEfficiency,peakEfficiency){
    return Math.abs(currentEfficiency - peakEfficiency);
  }

  calcRelativeDistanceFromPeak(currentEfficiency,maxEfficiency,minEfficiency){
    let distance = 1;
    if(currentEfficiency != null){
      distance = this.interpolation.interpolate_lin_single_point(currentEfficiency,maxEfficiency, minEfficiency, 0, 1);
    }
    return distance;
  }

  showWorkingCurves() {
    // Show the current curves for debugging
    const { powerCurve, flowCurve } = this.getCurrentCurves();
    return {
      powerCurve: powerCurve, 
      flowCurve: flowCurve,
      cog: this.cog,
      cogIndex: this.cogIndex,
      NCog: this.NCog,
      minEfficiency: this.minEfficiency,
      currentEfficiencyCurve: this.currentEfficiencyCurve,
      absDistFromPeak: this.absDistFromPeak,
      relDistFromPeak: this.relDistFromPeak
    };
  }

   // Calculate the center of gravity for current pressure
  calcCog() {

    //fetch current curve data for power and flow
    const { powerCurve, flowCurve }  = this.getCurrentCurves();

    const {efficiencyCurve, peak, peakIndex, minEfficiency } = this.calcEfficiencyCurve(powerCurve, flowCurve);

    // Calculate the normalized center of gravity
    const NCog = (flowCurve.y[peakIndex] - this.predictFlow.currentFxyYMin) / (this.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin); //

    //store in object for later retrieval
    this.currentEfficiencyCurve = efficiencyCurve;
    this.cog = peak;
    this.cogIndex = peakIndex;
    this.NCog = NCog;
    this.minEfficiency = minEfficiency;

    return { cog: peak, cogIndex: peakIndex, NCog: NCog, minEfficiency: minEfficiency };

  }

  calcEfficiencyCurve(powerCurve, flowCurve) {

    const efficiencyCurve = [];
    let peak = 0;
    let peakIndex = 0;
    let minEfficiency = 0;

    // Calculate efficiency curve based on power and flow curves
    powerCurve.y.forEach((power, index) => {

      // Get flow for the current power
      const flow = flowCurve.y[index];

      // higher efficiency is better
      efficiencyCurve.push( Math.round( ( flow / power ) * 100 ) / 100);

      // Keep track of peak efficiency
      peak = Math.max(peak, efficiencyCurve[index]);
      peakIndex = peak == efficiencyCurve[index] ? index : peakIndex;
      minEfficiency = Math.min(...efficiencyCurve);

    });

    return { efficiencyCurve, peak, peakIndex, minEfficiency };

  }

  //calc flow power based on pressure and current position
  calcFlowPower(x) {

    // Calculate flow and power
    const cFlow = this.calcFlow(x);
    const cPower = this.calcPower(x);

    return { cPower, cFlow };
  }

  calcEfficiency(power,flow,variant) {

    const pressureDiff = this.measurements.type('pressure').variant('measured').difference('Pa');
    const g = gravity.getStandardGravity();
    const temp = this.measurements.type('temperature').variant('measured').position('atEquipment').getCurrentValue('K');
    const atmPressure = this.measurements.type('atmPressure').variant('measured').position('atEquipment').getCurrentValue('Pa');  
    
    console.log(`--------------------calc efficiency : Pressure diff:${pressureDiff},${temp}, ${g} `);
    const rho = coolprop.PropsSI('D', 'T', temp, 'P', atmPressure, 'WasteWater');
    

    this.logger.debug(`temp: ${temp} atmPressure : ${atmPressure} rho : ${rho} pressureDiff: ${pressureDiff?.value || 0}`);
    const flowM3s = this.measurements.type('flow').variant('predicted').position('atEquipment').getCurrentValue('m3/s');
    const powerWatt = this.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue('W');
    this.logger.debug(`Flow : ${flowM3s} power: ${powerWatt}`);

    if (power != 0 && flow != 0) {
      const specificFlow = flow / power;
      const specificEnergyConsumption = power / flow;

      this.measurements.type("efficiency").variant(variant).position('atEquipment').value(specificFlow);
      this.measurements.type("specificEnergyConsumption").variant(variant).position('atEquipment').value(specificEnergyConsumption);
    
      if(pressureDiff?.value != null && flowM3s != null && powerWatt != null){
        const meterPerBar = pressureDiff.value / rho * g;
        const nHydraulicEfficiency = rho * g * flowM3s * (pressureDiff.value * meterPerBar ) / powerWatt;
        this.measurements.type("nHydraulicEfficiency").variant(variant).position('atEquipment').value(nHydraulicEfficiency);
      }
    
    } 

    //change this to nhydrefficiency ? 
    return this.measurements.type("efficiency").variant(variant).position('atEquipment').getCurrentValue();

  }

  updateCurve(newCurve) {
    this.logger.info(`Updating machine curve`);
    const newConfig = { asset: { machineCurve: newCurve } };

    //validate input of new curve fed to the machine
    this.config = this.configUtils.updateConfig(this.config, newConfig);

    //After we passed validation load the curves into their predictors
    this.predictFlow.updateCurve(this.config.asset.machineCurve.nq);
    this.predictPower.updateCurve(this.config.asset.machineCurve.np);
    this.predictCtrl.updateCurve(this.reverseCurve(this.config.asset.machineCurve.nq));
  }

  getCompleteCurve() {
    const powerCurve = this.predictPower.inputCurveData;
    const flowCurve = this.predictFlow.inputCurveData;
    return { powerCurve, flowCurve };
  }

  getCurrentCurves() {
    const powerCurve = this.predictPower.currentFxyCurve[this.predictPower.currentF];
    const flowCurve = this.predictFlow.currentFxyCurve[this.predictFlow.currentF];

    return { powerCurve, flowCurve };

  }

  calcDistanceBEP(efficiency,maxEfficiency,minEfficiency) {

    const absDistFromPeak = this.calcDistanceFromPeak(efficiency,maxEfficiency);
    const relDistFromPeak = this.calcRelativeDistanceFromPeak(efficiency,maxEfficiency,minEfficiency);

    //store internally
    this.absDistFromPeak = absDistFromPeak ;
    this.relDistFromPeak = relDistFromPeak;

    return { absDistFromPeak: absDistFromPeak, relDistFromPeak: relDistFromPeak };
  }

  getOutput() {

    // Improved output object generation

    const output = this.measurements.getFlattenedOutput();

    //fill in the rest of the output object
    output["state"] = this.state.getCurrentState();
    output["runtime"] = this.state.getRunTimeHours();
    output["ctrl"] = this.state.getCurrentPosition();
    output["moveTimeleft"] = this.state.getMoveTimeLeft();
    output["mode"] = this.currentMode;
    output["cog"] = this.cog; // flow / power efficiency
    output["NCog"] = this.NCog; // normalized cog
    output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ;
    output["maintenanceTime"] = this.state.getMaintenanceTimeHours();

    if(this.flowDrift != null){
      const flowDrift = this.flowDrift;
      output["flowNrmse"] = flowDrift.nrmse;
      output["flowLongterNRMSD"] = flowDrift.longTermNRMSD;
      output["flowImmediateLevel"] = flowDrift.immediateLevel;
      output["flowLongTermLevel"] = flowDrift.longTermLevel;
    }

    //should this all go in the container of measurements?
    output["effDistFromPeak"] = this.absDistFromPeak;
    output["effRelDistFromPeak"] = this.relDistFromPeak;
    //this.logger.debug(`Output: ${JSON.stringify(output)}`);

    return output;
  }


} // end of class

module.exports = Machine;

/*-------------------               Testing               -------------------*/

/*
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');

//import a child
const Child = require('../../measurement/src/specificClass');

console.log(`Creating child...`);
const PT1 = new Child(config={
  general:{
    name:"PT1",
    logging:{
      enabled:true,
      logLevel:"debug",
    },
  },
  functionality:{
    softwareType:"measurement",
    positionVsParent:"upstream",
  },
  asset:{
    supplier:"Vega",
    category:"sensor",
    type:"pressure",
    model:"Vegabar 82",
    unit: "mbar"
  },

});

const PT2 = new Child(config={
  general:{
    name:"PT2",
    logging:{
      enabled:true,
      logLevel:"debug",
    },
  },
  functionality:{
    softwareType:"measurement",
    positionVsParent:"upstream",
  },
  asset:{
    supplier:"Vega",
    category:"sensor",
    type:"pressure",
    model:"Vegabar 82",
    unit: "mbar"
  },
});

//create a machine
console.log(`Creating machine...`);

const machineConfig = {
  general: {
      name: "Hydrostal",
      logging: {
          enabled: true,
          logLevel: "debug",
      }
  },
  asset: {
      supplier: "Hydrostal",
      type: "pump",
      category: "centrifugal",
      model: "H05K-S03R+HGM1X-X280KO", // Ensure this field is present.
      machineCurve: curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"],
  }
}

const stateConfig = {
  general: {
    logging: {
      enabled: true,
      logLevel: "debug",
    },
  },
  // Your custom config here (or leave empty for defaults)
  movement: {
    speed: 1,
  },
  time: {
    starting: 2,
    warmingup: 3,
    stopping: 2,
    coolingdown: 3,
  },
};

const machine = new Machine(machineConfig, stateConfig);

//machine.logger.info(JSON.stringify(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]));
machine.logger.info(`Registering child...`);
machine.childRegistrationUtils.registerChild(PT1, "upstream");
machine.childRegistrationUtils.registerChild(PT2, "downstream");

//feed curve to the machine class
//machine.updateCurve(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]);

PT1.logger.info(`Enable sim...`);
PT1.toggleSimulation();
PT2.logger.info(`Enable sim...`);
PT2.toggleSimulation();
machine.getOutput();
//manual test
//machine.handleInput("parent", "execSequence", "startup");

machine.measurements.type("pressure").variant("measured").position('upstream').value(-200);
machine.measurements.type("pressure").variant("measured").position('downstream').value(1000);

testingSequences();

const tickLoop = setInterval(changeInput,1000);

function changeInput(){
  PT1.logger.info(`tick...`);
  PT1.tick();
  PT2.tick();
}

async function testingSequences(){
  try{
    console.log(` ********** Testing sequence startup... **********`);
    await machine.handleInput("parent", "execSequence", "startup");
    console.log(` ********** Testing movement to 15... **********`);
    await machine.handleInput("parent", "execMovement", 15);
    machine.getOutput();
    console.log(` ********** Testing sequence shutdown... **********`);
    await machine.handleInput("parent", "execSequence", "shutdown");
    console.log(`********** Testing moving to setpoint 10... while in idle **********`);
    await machine.handleInput("parent", "execMovement", 10);
    console.log(` ********** Testing sequence emergencyStop... **********`);
    await machine.handleInput("parent", "execSequence", "emergencystop");
    console.log(`********** Testing sequence boot... **********`);
    await machine.handleInput("parent", "execSequence", "boot");
  }catch(error){
    console.error(`Error: ${error}`);
  }
}


//*/



