/**
 * @file valveGroupControl.js
 *
 * Permission is hereby granted to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to use it for personal 
 * or non-commercial purposes, with the following restrictions:
 *
 * 1. **No Copying or Redistribution**: The Software or any of its parts may not 
 *    be copied, merged, distributed, sublicensed, or sold without explicit 
 *    prior written permission from the author.
 * 
 * 2. **Commercial Use**: Any use of the Software for commercial purposes requires 
 *    a valid license, obtainable only with the explicit consent of the author.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, 
 * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 *
 * Ownership of this code remains solely with the original author. Unauthorized 
 * use of this Software is strictly prohibited.
 *
 * Author:
 * - Rene De Ren
 * Email:
 * - r.de.ren@brabantsedelta.nl
 *
 * Future Improvements:
 * - Time-based stability checks
 * - Warmup handling
 * - Dynamic outlier detection thresholds
 * - Dynamic smoothing window and methods
 * - Alarm and threshold handling
 * - Maintenance mode
 * - Historical data and trend analysis
 */
/**
 * @file valveGroupControl.js
 *
 * Permission is hereby granted to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to use it for personal 
....
*/

//load local dependencies
const EventEmitter = require('events');
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');

class ValveGroupControl { 
    constructor(valveGroupControlConfig = {}) { 
        this.emitter = new EventEmitter(); // nodig voor ontvangen en uitvoeren van events emit() en on() --> Zien als internet berichten (niet bedraad in node-red)
        this.configManager = new configManager(); 
        this.defaultConfig = this.configManager.getConfig('valveGroupControl'); // Load default config for rotating machine ( use software type name ? )
        this.configUtils = new configUtils(this.defaultConfig);
        this.config = this.configUtils.initConfig(valveGroupControlConfig); // verify and set the config for the valve group

       // Init after config is set
        this.logger = new logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, this.config.general.name);

        // Initialize measurements
        this.measurements = new MeasurementContainer();
        this.child = {};
        this.valves = {}; // hold child object so we can get information from its child valves 

        

        // Initialize variables
        this.maxDeltaP = 0; // max deltaP is 0 als er geen child valves zijn
        this.currentMode = this.config.mode.current;
        this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility

    }

    registerOnChildEvents() {}

    registerChild(child, positionVsParent) {
    }

    isValidSourceForMode(source, mode) {
        const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
        this.logger.info(`Allowed sources for mode '${mode}': ${allowedSourcesSet}`);
        return allowedSourcesSet.has(source);
    }

    async handleInput(source, action, parameter) {
        if (!this.isValidSourceForMode(source, this.currentMode)) {
          let warningTxt = `Source '${source}' is not valid for mode '${this.currentMode}'.`;
          this.logger.warn(warningTxt);
          return {status : false , feedback: warningTxt};
        }
    
        this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
        try {
          switch (action) {
            case "execSequence":
              await this.executeSequence(parameter);
              break;
            case "totalFlowChange":
              await this.updateFlow(parameter);
              break;
            case "emergencyStop":
              this.logger.warn(`Emergency stop activated by '${source}'.`);
              await this.executeSequence("emergencyStop");
              break;
            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}`);
        }
        
    }

    setMode(newMode) {
        const availableModes = defaultConfig.mode.current.rules.values.map(vgc => vgc.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;
    }

    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
      }
    }
  }

updateFlow(variant,value,position) {

    switch (variant) {
      case ("measured"):
        // put value in measurements container
        this.logger.debug(`Updating measured flow for position ${position} with value ${value}`);
        this.measurements.type("flow").variant("measured").position(position).value(value);
        this.calcValveFlows(); 
        break;

      case ("predicted"):
        this.logger.debug(`Updating predicted flow for position ${position} with value ${value}`);
        this.measurements.type("flow").variant("predicted").position(position).value(value);
        this.calcValveFlows(); // Pass the value to calculate valve flows
        break;

      default:
        this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
        break;
    }
  }

  updateMeasurement(variant, subType, value, position) {
    this.logger.debug(`---------------------- updating ${subType} ------------------ `);
    switch (subType) {
      case "pressure":
        // Update pressure measurement
        //this.updatePressure(variant,value,position);
        break;
      case "flow":
        this.updateFlow(variant,value,position);
        break;
      case "power":
        // Update power measurement
        break;
      default:
        this.logger.error(`Type '${subType}' not recognized for measured update.`);
        return;
    }
  }

  calcValveFlows() {   
    const totalFlow = this.measurements.type("flow").variant("measured").position("atEquipment").getCurrentValue(); // get the total flow from the measurement container
    let totalKv = 0; 

    this.logger.debug(`Calculating valve flows... ${totalFlow}`); //Checkpoint
    
    for (const key in this.valves){ //bereken sum kv values om verdeling total flow te maken
        this.logger.info('kv: ' + this.valves[key].kv); //CHECKPOINT
        if (this.valves[key].state.getCurrentPosition() != null) {
            totalKv += this.valves[key].kv;
            this.logger.info('Total Kv = ' + totalKv); //CHECKPOINT
        }
        if(totalKv === 0) {
            this.logger.warn('Total Kv is 0, cannot calculate flow distribution.');
            return; // Avoid division by zero
        }
    }

    for (const key in this.valves){ 
        const valve = this.valves[key]; 
        this.logger.debug(`Calculating ratio for valve total: ${totalKv} valve.kv: ${valve.kv} ratio : ${valve.kv / totalKv}`); //Checkpoint
        const ratio = valve.kv / totalKv; 
        const flow = ratio * totalFlow; // bereken flow per valve 

        //update flow per valve in de object zelf wat daar vervolgens weer de nieuwe deltaP berekent
        valve.updateFlow("predicted", flow, "downstream"); 
        this.logger.info(`--> Sending updated flow to valves --> ${flow} `); //Checkpoint 

    }
  }

  calcMaxDeltaP() { // bereken de max deltaP van alle child valves
    let maxDeltaP = 0; //max deltaP is 0 als er geen child valves zijn
    this.logger.info('Calculating new max deltaP...'); 
    for (const key in this.valves) {
      const valve = this.valves[key]; //haal de child valve object op
      const deltaP = valve.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue(); //get delta P
      this.logger.info(`Delta P for valve ${key}: ${deltaP}`); 
      if (deltaP > maxDeltaP) { //als de deltaP van de child valve groter is dan de huidige maxDeltaP, dan update deze
        maxDeltaP = deltaP;
      }
    }
    this.logger.info('Max Delta P updated to: ' + maxDeltaP); 

    this.maxDeltaP = maxDeltaP; //update de max deltaP in de measurement container van de valveGroupControl class

}


  getOutput() { 

    // Improved output object generation
    const output = {};
    //build the output object
    this.measurements.getTypes().forEach(type => {
      this.measurements.getVariants().forEach(variant => {
        this.measurements.getPositions().forEach(position => {

        const value = this.measurements.type(type).variant(variant).position(position).getCurrentValue(); //get the current value of the measurement
        
        if (value != null) {
          output[`${position}_${variant}_${type}`] = value;
        }
      });
      });
    });

    //fill in the rest of the output object
    output["mode"] = this.currentMode;
    output["maxDeltaP"] = this.maxDeltaP; 

    //this.logger.debug(`Output: ${JSON.stringify(output)}`);

    return output;
  }

}

module.exports = ValveGroupControl;


const valve = require('../../valve/src/specificClass.js');
const valveConfig = {
      general: {
        name: "valve",
        logging: {
          enabled: true,
          logLevel: "debug"
        }
      },
      asset: {
        supplier: "binder",
        category: "valve",
        type: "control",
        model: "ECDV",
        unit: "m3/h"
      },
      functionality: {
        positionVsParent: 'atEquipment', // Default to 'atEquipment' if not specified
      }
    };

const stateConfig = {
        general: {
          logging: {
            enabled: true,
            logLevel: "debug"
          }
        },
        movement: {
          speed: 1
        },
        time: {
          starting: 1,
          warmingup: 1,
          stopping: 1,
          coolingdown: 1
        }
      };

const valve1 = new valve(valveConfig, stateConfig);
//const valve2 = new valve(valveConfig, stateConfig);
//const valve3 = new valve(valveConfig, stateConfig);

valve1.kv = 10; // Set Kv value for valve1
//valve2.kv = 20; // Set Kv value for valve2  
//valve3.kv = 30; // Set Kv value for valve3

valve1.updateMeasurement("measured", "pressure" , 500, "downstream"); 
//valve2.updateMeasurement("measured" , "pressure" , 500, "downstream"); 
//valve3.updateMeasurement("measured" , "pressure" , 500, "downstream"); 

const vgc = new ValveGroupControl(); 

vgc.childRegistrationUtils.registerChild(valve1, "atEquipment");
//vgc.childRegistrationUtils.registerChild(valve2, "atEquipment");
//vgc.childRegistrationUtils.registerChild(valve3, "atEquipment");

vgc.updateFlow("measured", 1000, "atEquipment"); // Update total flow to 100 m3/h

