content: /**
   * Copyright 2020 Alexander Herzog
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package simulator.elements;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.concurrent.Semaphore;
  
  import org.apache.commons.math3.util.FastMath;
  
  import language.Language;
  import simulator.coreelements.RunElementAnalogProcessing;
  import simulator.coreelements.RunElementAnalogProcessingData;
  import simulator.runmodel.SimulationData;
  import statistics.StatisticsTimeAnalogPerformanceIndicator;
  import ui.modeleditor.elements.ModelElementTankFlowData;
  
  /**
   * Laufzeitdaten eines <code>RunElementTank</code>-Laufzeit-Objekts
   * @author Alexander Herzog
   * @see RunElementTank
   * @see RunElementAnalogProcessingData
   */
  public class RunElementTankData extends RunElementAnalogProcessingData {
  	/**
  	 * Kapazitt
  	 */
  	private final double capacity;
  
  	/**
  	 * Initialer maximalem Durchfluss (pro Sekunde) pro Ventil
  	 */
  	private final double[] initialValveValues;
  
  	/**
  	 * Maximaler Durchfluss (pro Sekunde) pro Ventil
  	 */
  	private double[] valveValues;
  
  	/**
  	 * Bis wann ist das Ventil bereits belegt?
  	 */
  	private long[] valveTimes;
  
  	/**
  	 * Sichert parallele Zugriffe auf {@link #outgoing}
  	 * und {@link #incoming} ab.
  	 */
  	private final Semaphore lock;
  
  	/**
  	 * Auslaufende Flsse
  	 */
  	private final List<RunElementTankFlow> outgoing;
  
  	/**
  	 * Einlaufende Flsse
  	 */
  	private final List<RunElementTankFlow> incoming;
  
  	/**
  	 * Konstruktor der Klasse
  	 * @param station	Zugehriges RunElement
  	 * @param capacity	Kapazitt des Tanks
  	 * @param initialValveValues	Initialer maximalem Durchfluss (pro Sekunde) pro Ventil
  	 * @param statistics	Statistikobjekt, in dem die Gre des analogen Wertes erfasst wird
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public RunElementTankData(final RunElementAnalogProcessing station, final double capacity, final double[] initialValveValues, final StatisticsTimeAnalogPerformanceIndicator statistics, final SimulationData simData) {
  		super(station,statistics,simData);
  
  		this.capacity=capacity;
  		this.initialValveValues=initialValveValues;
  		valveValues=Arrays.copyOf(initialValveValues,initialValveValues.length);
  		valveTimes=new long[valveValues.length];
  		Arrays.fill(valveTimes,0);
  
  		outgoing=new ArrayList<>();
  		incoming=new ArrayList<>();
  		lock=new Semaphore(1);
  	}
  
  	@Override
  	protected void init(final SimulationData simData) {
  		valveValues=Arrays.copyOf(initialValveValues,initialValveValues.length);
  		valveTimes=new long[valveValues.length];
  		Arrays.fill(valveTimes,0);
  	}
  
  	@Override
  	protected void updateValue(SimulationData simData) {
  		lock.acquireUninterruptibly();
  		try {
  
  			/* Flsse verarbeiten */
  			for (RunElementTankFlow flow: outgoing) flow.processFlow(simData);
  			for (RunElementTankFlow flow: incoming) flow.processFlow(simData);
  
  			int index;
  
  			/* Prfen, ob auslaufende Flsse abgeschlossen werden knnen */
  			index=0;
  			while (index<outgoing.size()) if (outgoing.get(index).flowDone) {
  				endOutgoingFlow(index,simData);
  			} else {
  				index++;
  			}
  
  			/* Prfen, ob einlaufende Flsse abgeschlossen werden knnen */
  			index=0;
  			while (index<incoming.size()) if (incoming.get(index).flowDone) {
  				endIncomingFlow(index,simData);
  			} else {
  				index++;
  			}
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Liefert den aktuellen maximalen Durchfluss an den Ventilen
  	 * @return	Aktueller maximaler Durchfluss an den Ventilen
  	 */
  	public double[] getValveValues() {
  		return valveValues;
  	}
  
  	/**
  	 * Stellt den aktuellen Wert ein.
  	 * @param simData	Simulationsdatenobjekt
  	 * @param value	Neuer Wert
  	 */
  	@Override
  	public void setValue(final SimulationData simData, double value) {
  		if (value<0) value=0;
  		if (value>capacity) value=capacity;
  		super.setValue(simData,value);
  	}
  
  	/**
  	 * Stellt den maximalen Durchfluss an einem Ventil ein
  	 * @param nr	0-basierte Nummer des Ventils
  	 * @param maxFlow	Maximaler Fluss (Wert &ge; 0)
  	 */
  	public void setValveMaxFlow(final int nr, final double maxFlow) {
  		if (nr<0 || nr>=valveValues.length) return;
  		valveValues[nr]=FastMath.max(0,maxFlow);
  	}
  
  	@Override
  	protected boolean isRateZero() {
  		lock.acquireUninterruptibly();
  		try {
  			return incoming.size()==0 && outgoing.size()==0;
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Fgt einen Fluss zu der Liste der einlaufenden Flsse hinzu
  	 * @param flow	Neuer Fluss
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public void addIncomingFlow(final RunElementTankFlow flow, final SimulationData simData) {
  		lock.acquireUninterruptibly();
  		try {
  			incoming.add(flow);
  			rateActivated(simData);
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Fgt einen Fluss zu der Liste der auslaufenden Flsse hinzu
  	 * @param flow	Neuer Fluss
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public void addOutgoingFlow(final RunElementTankFlow flow, final SimulationData simData) {
  		lock.acquireUninterruptibly();
  		try {
  			outgoing.add(flow);
  			rateActivated(simData);
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Entfernt einen ausgehenden Fluss aus der Liste der ausgehenden Flsse {@link #outgoing}.
  	 * @param flow	Zu entfernender Fluss
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	private void removeOutgoingFlowNotify(final RunElementTankFlow flow, final SimulationData simData) {
  		lock.acquireUninterruptibly();
  		try {
  			outgoing.remove(flow);
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Entfernt einen eingehenden Fluss aus der Liste der eingehenden Flsse {@link #outgoing}.
  	 * @param flow	Zu entfernender Fluss
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	private void removeIncomingFlowNotify(final RunElementTankFlow flow, final SimulationData simData) {
  		lock.acquireUninterruptibly();
  		try {
  			incoming.remove(flow);
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Beendet einen einlaufenden Fluss.
  	 * @param index	Index des Flusses in {@link #incoming}
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	private void endIncomingFlow(final int index, final SimulationData simData) {
  		/* Aus eigener Liste austragen */
  		final RunElementTankFlow flow=incoming.remove(index);
  
  		/* Aus anderer Liste austragen */
  		if (flow.source!=null) flow.source.getData(simData).removeOutgoingFlowNotify(flow,simData);
  
  		/* Logging */
  		if (simData.loggingActive) station.log(simData,Language.tr("Simulation.Log.Tank"),String.format(Language.tr("Simulation.Log.Tank.StopInfo"),flow.logInfo(),station.name));
  	}
  
  	/**
  	 * Beendet einen auslaufenden Fluss.
  	 * @param index	Index des Flusses in {@link #incoming}
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	private void endOutgoingFlow(final int index, final SimulationData simData) {
  		/* Aus eigener Liste austragen */
  		final RunElementTankFlow flow=outgoing.remove(index);
  
  		/* Aus anderer Liste austragen */
  		if (flow.destination!=null) flow.destination.getData(simData).removeIncomingFlowNotify(flow,simData);
  
  		/* Logging */
  		if (simData.loggingActive) station.log(simData,Language.tr("Simulation.Log.Tank"),String.format(Language.tr("Simulation.Log.Tank.StopInfo"),flow.logInfo(),station.name));
  	}
  
  	/**
  	 * Das Stations-Element muss {@link SignalListener} implementieren und bei einer
  	 * Nachricht darber diese Methode aufrufen.
  	 * @param simData	Simulationsdatenobjekt
  	 * @param signalName	Name des ausgelsten Signals
  	 */
  	public void signalNotify(final SimulationData simData, final String signalName) {
  		lock.acquireUninterruptibly();
  		try {
  
  			int index;
  
  			index=0;
  			while (index<incoming.size()) {
  				final RunElementTankFlow flow=incoming.get(index);
  				if (flow.stopCondition==ModelElementTankFlowData.FlowStopCondition.STOP_BY_SIGNAL && flow.stopSignal.equals(signalName)) {
  					endIncomingFlow(index,simData);
  				} else {
  					index++;
  				}
  			}
  
  			index=0;
  			while (index<outgoing.size()) {
  				final RunElementTankFlow flow=outgoing.get(index);
  				if (flow.stopCondition==ModelElementTankFlowData.FlowStopCondition.STOP_BY_SIGNAL && flow.stopSignal.equals(signalName)) {
  					endOutgoingFlow(index,simData);
  				} else {
  					index++;
  				}
  			}
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Bestimmt den maximalen Ausfluss ber ein Ventil innerhalb einer bestimmten Zeit
  	 * @param valveNr	0-basierende Ventilnummer
  	 * @param timeDeltaMS	Zeitdauer
  	 * @param simData	Simulationsdatenobjekt
  	 * @return	Maximaler Ausfluss durch das Ventil in der angegebenen Zeit
  	 */
  	public double getMaxFlowOut(final int valveNr, final long timeDeltaMS, final SimulationData simData) {
  		if (valveTimes[valveNr]>=simData.currentTime) return 0; /* Ventil ist belegt */
  
  		final double maxFlow=valveValues[valveNr]*(timeDeltaMS*simData.runModel.scaleToSeconds);
  
  		valveTimes[valveNr]=simData.currentTime;
  		return FastMath.min(value,maxFlow);
  	}
  
  	/**
  	 * Bestimmt den maximalen Zufluss ber ein Ventil innerhalb einer bestimmten Zeit
  	 * @param valveNr	0-basierende Ventilnummer
  	 * @param timeDeltaMS	Zeitdauer
  	 * @param simData	Simulationsdatenobjekt
  	 * @return	Maximaler Zufluss durch das Ventil in der angegebenen Zeit
  	 */
  	public double getMaxFlowIn(final int valveNr, final long timeDeltaMS, final SimulationData simData) {
  		if (valveTimes[valveNr]>=simData.currentTime) return 0; /* Ventil ist belegt */
  
  		final double maxFlow=valveValues[valveNr]*(timeDeltaMS*simData.runModel.scaleToSeconds);
  
  		valveTimes[valveNr]=simData.currentTime;
  		return FastMath.min(capacity-value,maxFlow);
  	}
  
  	/**
  	 * ndert den Fllstand des Tanks um einen bestimmten Wert
  	 * @param delta	nderung des Fllstands
  	 */
  	public void changeValueByFlow(final double delta) {
  		value+=delta;
  		if (value<0) value=0;
  		if (value>capacity) value=capacity;
  	}
  
  	/**
  	 * Liefert eine Liste mit den aktuellen auslaufenden Flssen
  	 * @return	Liste mit den aktuellen auslaufenden Flssen
  	 */
  	public RunElementTankFlow[] getOutgoingFlows() {
  		lock.acquireUninterruptibly();
  		try {
  			return outgoing.toArray(RunElementTankFlow[]::new);
  		} finally {
  			lock.release();
  		}
  	}
  
  	/**
  	 * Liefert eine Liste mit den aktuellen einlaufenden Flssen
  	 * @return	Liste mit den aktuellen einlaufenden Flssen
  	 */
  	public RunElementTankFlow[] getIncomingFlows() {
  		lock.acquireUninterruptibly();
  		try {
  			return incoming.toArray(RunElementTankFlow[]::new);
  		} finally {
  			lock.release();
  		}
  	}
  }