content: /**
   * Copyright 2021 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 parser.MathCalcError;
  import simulator.coreelements.RunElementData;
  import simulator.runmodel.SimulationData;
  import simulator.simparser.ExpressionCalc;
  import simulator.simparser.ExpressionMultiEval;
  
  /**
   * Diese Klasse hlt die Thread-lokalen Laufzeitdaten zu einem
   * Ankunftsdatensatz {@link RunElementSourceRecord} vor.
   * @author Alexander Herzog
   * @see RunElementSourceRecord
   */
  public class RunElementSourceRecordData {
  	/** Zhlung der Anzahl an Anknften (knnen ganze Batche sein) an dieser Station */
  	public long arrivalCount;
  	/** Zhlung der Anzahl an eingetroffenen Kunden (ggf. Zhlung in den Batchen) an dieser Station */
  	public long arrivalClientCount;
  	/** Zeitpunkt der letzten Kundenankunft (in Millisekunden) */
  	public long arrivalTime;
  
  	/** Ausdruck zur Berechnung der Batch-Gren (kann <code>null</code> sein) */
  	public final ExpressionCalc batchSize;
  	/** Zwischenankunftszeiten-Ausdruck (kann <code>null</code> sein) */
  	public final ExpressionCalc expression;
  	/** Bedingung zur Kundenfreigabe  (kann <code>null</code> sein) */
  	public final ExpressionMultiEval condition;
  	/** Minimaler Abstand zwischen zwei Anknften (kann <code>null</code> sein) */
  	private final ExpressionCalc conditionMinDistance;
  	/** Aktueller gem {@link #conditionMinDistance} berechneter minimaler Abstand zwischen zwei Anknften (in MS) (ist -1, wenn momentan kein gltiger Wert vorliegt) */
  	private long conditionMinDistanceCalculatedMS;
  	/** Ist {@link #conditionMinDistance} ein konstanter Wert? Dann muss {@link #conditionMinDistanceCalculatedMS} nie verworfen und neu berechnet werden. */
  	private boolean conditionMinDistanceIsConst;
  
  	/** Ausdrcke fr die intervallabhngigen Anzahlen an Anknften */
  	public final ExpressionCalc[] intervalExpressions;
  	/** Ausdrcke fr die intervallabhngigen Zwischenankunftszeiten */
  	public final ExpressionCalc[] intervalDistributions;
  	/** Nchster zu verwendender Eintrag aus {@link RunElementSourceRecord#arrivalStream} */
  	public int arrivalTimeValueNext;
  
  	/** Rechenausdruck fr den Schwellenwert zur Erzeugung von Anknften */
  	private final ExpressionCalc threshold;
  	/** Vergleichswert fr den Schwellenwert-Ausdruck {@link #threshold} */
  	private final double thresholdValue;
  	/** Soll eine Ankunft beim ber- oder Unterschrreiten des Schwellenwerts ausgelst werden? */
  	private final boolean thresholdIsDirectionUp;
  	/** Letzter Wert des Schwellenwert-Ausdrucks */
  	private double thresholdLastValue;
  	/** Enthlt {@link #thresholdLastValue} einen gltigen Wert (<code>true</code>) oder wurde noch kein Wert aufgezeichnet (<code>false</code>)? */
  	private boolean thresholdIsLastValueAvailable;
  
  	/** Zustzliche Bedingung (Voraussetzung), damit eine Ankunft tatschlich ausgefhrt wird (kann <code>null</code> sein) */
  	public final ExpressionMultiEval arrivalCondition;
  
  	/** Ausdrcke fr Zuweisungen von Zahlen-Eigenschaften zu neuen Kunden */
  	public final RunElementSourceRecord.SourceSetExpressions setData;
  
  	/**
  	 * Konstruktor der Klasse <code>RunElementSourceData</code>
  	 * @param simData	Simulationsdatenobjekt
  	 * @param record	Modellweit gltiger (nicht Thread-lokaler) Ankunftsdatensatz
  	 * @param variableNames	Liste der global verfgbaren Variablennamen
  	 */
  	public RunElementSourceRecordData(final SimulationData simData, final RunElementSourceRecord record, final String[] variableNames) {
  		arrivalCount=0;
  		arrivalClientCount=0;
  		arrivalTime=-1;
  
  		if (record.batchSize==null) {
  			this.batchSize=null;
  		} else {
  			this.batchSize=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  			this.batchSize.parse(record.batchSize);
  		}
  
  		if (record.expression==null) {
  			this.expression=null;
  		} else {
  			this.expression=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  			this.expression.parse(record.expression);
  		}
  
  		if (record.condition==null) {
  			this.condition=null;
  			this.conditionMinDistance=null;
  			this.conditionMinDistanceCalculatedMS=-1;
  		} else {
  			this.condition=new ExpressionMultiEval(variableNames,simData.runModel.modelUserFunctions);
  			this.condition.parse(record.condition);
  			this.conditionMinDistance=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  			this.conditionMinDistance.parse(record.conditionMinDistance);
  			this.conditionMinDistanceIsConst=this.conditionMinDistance.isConstValue();
  			this.conditionMinDistanceCalculatedMS=-1;
  		}
  
  		if (record.intervalExpressions==null) {
  			this.intervalExpressions=null;
  		} else {
  			this.intervalExpressions=new ExpressionCalc[record.intervalExpressions.length];
  			for (int i=0;i<record.intervalExpressions.length;i++) {
  				this.intervalExpressions[i]=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  				this.intervalExpressions[i].parse(record.intervalExpressions[i]);
  			}
  		}
  
  		if (record.intervalDistributions==null) {
  			this.intervalDistributions=null;
  		} else {
  			this.intervalDistributions=new ExpressionCalc[record.intervalDistributions.length];
  			for (int i=0;i<record.intervalDistributions.length;i++) {
  				this.intervalDistributions[i]=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  				this.intervalDistributions[i].parse(record.intervalDistributions[i]);
  			}
  		}
  
  		if (record.thresholdExpression==null) {
  			this.threshold=null;
  		} else {
  			this.threshold=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  			this.threshold.parse(record.thresholdExpression);
  		}
  		this.thresholdValue=record.thresholdValue;
  		this.thresholdIsDirectionUp=record.thresholdDirectionUp;
  
  		if (record.arrivalCondition==null) {
  			this.arrivalCondition=null;
  		} else {
  			this.arrivalCondition=new ExpressionMultiEval(variableNames,simData.runModel.modelUserFunctions);
  			this.arrivalCondition.parse(record.arrivalCondition);
  		}
  
  		this.setData=record.getRuntimeExpressions(simData.runModel.variableNames,simData.runModel.modelUserFunctions);
  
  		arrivalTimeValueNext=0;
  	}
  
  	/**
  	 * Liefert die minimale zulssig Zwischenankunftszeit (bei der Bestimmung von Anknften gem einer Bedingung)
  	 * @param simData	Simulationsdatenobjekt
  	 * @param stationName	Name der Station
  	 * @return	Minimale zulssig Zwischenankunftszeit
  	 * @see #clearConditionMinDistanceCalculated()
  	 */
  	public long getConditionMinDistanceMS(final SimulationData simData, final String stationName) {
  		if (conditionMinDistanceCalculatedMS<0) {
  			try {
  				conditionMinDistanceCalculatedMS=Math.round(Math.max(0,conditionMinDistance.calc(simData.runData.variableValues,simData,null)*simData.runModel.scaleToSimTime));
  			} catch (MathCalcError e) {
  				simData.calculationErrorStation(conditionMinDistance,stationName);
  				conditionMinDistanceCalculatedMS=0;
  			}
  		}
  		return conditionMinDistanceCalculatedMS;
  	}
  
  	/**
  	 * Definiert die aktuell berechnete minimale zulssige Zwischenankunftszeit als verwendet.
  	 * Beim nchsten Aufruf von {@link #getConditionMinDistanceMS(SimulationData, String)} wird dann eine neue Zeitdauer berechnet.
  	 * @see #getConditionMinDistanceMS(SimulationData, String)
  	 */
  	public void clearConditionMinDistanceCalculated() {
  		if (conditionMinDistanceIsConst) return; /* Wenn die minimale Zwishchenankunftszeit fix ist, brauchen wir diese nicht verwerfen und neu berechnen. */
  		conditionMinDistanceCalculatedMS=-1;
  	}
  
  	/**
  	 * Prft, ob die Schwellenwert-Bedingung seit dem letzten Test erreicht wurde
  	 * @param simData	Simulationsdatenobjekt
  	 * @param data	Station, an der der Rechenausdruck ausgewertet werden sollte
  	 * @return	Gibt <code>true</code> zurck, wenn die Schwellenwert-Bedingung seit dem letzten Test erreicht wurde
  	 */
  	public boolean checkThreshold(final SimulationData simData, final RunElementData data) {
  		/* Nach Abbruch ist wirklich Schluss */
  		if (simData.runData.stopp) return false;
  
  		/* Schwellenwert gesetzt? */
  		if (threshold==null) return false;
  
  		/* Schwellenwert prfen */
  		simData.runData.setClientVariableValues(null);
  		double check=0;
  		try {
  			check=threshold.calc(simData.runData.variableValues,simData,null);
  		} catch (MathCalcError e) {
  			simData.calculationErrorStation(threshold,data);
  			return false;
  		}
  
  		boolean trigger=false;
  		if (thresholdIsLastValueAvailable) {
  			if (thresholdIsDirectionUp) {
  				if (thresholdLastValue<=thresholdValue && check>thresholdValue) trigger=true;
  			} else {
  				if (thresholdLastValue>=thresholdValue && check<thresholdValue) trigger=true;
  			}
  		}
  		thresholdLastValue=check;
  		thresholdIsLastValueAvailable=true;
  		return trigger;
  	}
  }
  
  