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.List;
  
  import language.Language;
  import mathtools.NumberTools;
  import parser.MathCalcError;
  import simulator.coreelements.RunElement;
  import simulator.coreelements.RunElementData;
  import simulator.events.ConveyorSystemChangeEvent;
  import simulator.runmodel.RunDataClient;
  import simulator.runmodel.SimulationData;
  import simulator.simparser.ExpressionCalc;
  
  /**
   * Laufzeitdaten eines <code>RunElementConveyor</code>-Laufzeit-Objekts
   * @author Alexander Herzog
   * @see RunElementConveyor
   * @see RunElementData
   */
  public class RunElementConveyorData extends RunElementData implements RunElementDataWithWaitingClients {
  	/**
  	 * Initiale Gre fr {@link #waitingClients}
  	 */
  	private static final int DEFAULT_QUEUE_SIZE=256;
  
  	/**
  	 * Verfgbare Kapazitt
  	 */
  	public double freeCapacity;
  
  	/**
  	 * Wartende Kunden
  	 */
  	public final List<RunDataClient> waitingClients;
  
  	/**
  	 * Verzgerungsausdrcke
  	 */
  	public ExpressionCalc[] capacityNeeded;
  
  	/**
  	 * Findet gerade eine Verarbeitung statt, so dass keine Kunden entnommen werden knnen?
  	 */
  	public boolean queueLockedForPickUp;
  
  	/**
  	 * Notwendige Zeit, um einen Kunden ber das Flieband zu bewegen
  	 */
  	private long transportTimeMS;
  
  	/**
  	 * Existiert ein von {@link #triggerUpdateEvent(SimulationData)}
  	 * generiertes, noch nicht abgearbeitetes Event?
  	 * @see #triggerUpdateEvent(SimulationData)
  	 * @see ConveyorSystemChangeEvent
  	 */
  	private boolean notifyTriggered;
  
  	/**
  	 * Konstruktor der Klasse
  	 * @param station	Station zu diesem Datenelement
  	 * @param capacityNeededStrings	Kundentyp-abhngige pro Kunde bentigte Kapazitten auf dem Flieband
  	 * @param variableNames	Liste der globalen Variablen (zur Initialisierung der Rechenausdrcke)
  	 * @param freeCapacity	Verfgbare Kapazitt
  	 * @param transportTimeMS	Notwendige Zeit, um einen Kunden ber das Flieband zu bewegen
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public RunElementConveyorData(final RunElement station, final String[] capacityNeededStrings, final String[] variableNames, final double freeCapacity, final long transportTimeMS, final SimulationData simData) {
  		super(station,simData);
  		queueLockedForPickUp=false;
  		waitingClients=new ArrayList<>(DEFAULT_QUEUE_SIZE);
  
  		capacityNeeded=new ExpressionCalc[capacityNeededStrings.length];
  		for (int i=0;i<capacityNeededStrings.length;i++) if (capacityNeededStrings[i]!=null) {
  			capacityNeeded[i]=new ExpressionCalc(variableNames,simData.runModel.modelUserFunctions);
  			capacityNeeded[i].parse(capacityNeededStrings[i]);
  		}
  
  		this.freeCapacity=freeCapacity;
  		this.transportTimeMS=transportTimeMS;
  		notifyTriggered=false;
  	}
  
  	/**
  	 * Fgt einen Kunden zu der Liste der wartenden Kunden hinzu
  	 * @param client	Hinzuzufgender Kunde
  	 * @param time	Zeitpunkt an dem der Kunde an der <code>RunElementProcess</code>-Station eingetroffen ist (zur spteren Berechnung der Wartezeit der Kunden)
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public void addClientToQueue(final RunDataClient client, final long time, final SimulationData simData) {
  		/* Kunden an Warteschlange anstellen */
  		client.stationInformationLong=-1;
  		waitingClients.add(client);
  		client.lastWaitingStart=time;
  		client.stationInformationDouble=getNeededCapacity(simData,client);
  
  		/* Logging */
  		if (simData.loggingActive) station.log(simData,Language.tr("Simulation.Log.ConveyorArrival"),String.format(Language.tr("Simulation.Log.ConveyorArrival.Info"),client.logInfo(simData),station.name,NumberTools.formatNumber(client.stationInformationDouble)));
  
  		/* Statistik */
  		simData.runData.logClientEntersStationQueue(simData,station,this,client);
  
  		/* Bei Animationen Zwischenevent einfgen, um Darstellung flssig zu gestalten */
  		if (simData.runModel.isAnimation) triggerUpdateEvent(simData);
  	}
  
  	/**
  	 * Entfernt einen Kunden aus der Warteschlange
  	 * @param client	Zu entfernender Kunde
  	 * @param indexOfClientInQueue	Index des Kunden in der <code>waitingClients</code>-Liste. Kann -1 sein, dann wird der Index gem dem <code>client</code>-Objekt selbst ermittelt
  	 * @param time	Zeitpunkt, an dem der Kunde entfernt werden soll
  	 * @param simData	Simulationsdaten
  	 * @return	Gibt die Wartezeit des Kunden zurck
  	 */
  	public long removeClientFromQueue(final RunDataClient client, final int indexOfClientInQueue, final long time, final SimulationData simData) {
  		final int index=(indexOfClientInQueue>=0)?indexOfClientInQueue:waitingClients.indexOf(client);
  		if (index<0) return 0;
  		final long timeInQueue=time-client.lastWaitingStart;
  
  		/* Kunde aus Warteschlange austragen */
  		waitingClients.remove(index);
  
  		/* Statistik */
  		simData.runData.logClientLeavesStationQueue(simData,station,this,client);
  
  		/* Wartezeit zurckliefern */
  		return timeInQueue;
  	}
  
  	/**
  	 * Liefert notwendige Kapazitt fr einen Kunden
  	 * @param simData	Simulationsdaten
  	 * @param client	Kunde, fr den die Kapazitt bestimmt werden soll
  	 * @return	Bedienzeit in Sekunden
  	 */
  	public double getNeededCapacity(final SimulationData simData, final RunDataClient client) {
  		final int type=client.type;
  
  		final double additionalWaitingTime=(simData.currentTime-client.lastWaitingStart)*simData.runModel.scaleToSeconds;
  		simData.runData.setClientVariableValues(client,additionalWaitingTime);
  		try {
  			return capacityNeeded[type].calc(simData.runData.variableValues,simData,client);
  		} catch (MathCalcError e) {
  			simData.calculationErrorStation(capacityNeeded[type],this);
  			return 0;
  		}
  	}
  
  	/**
  	 * Wird durch {@link ConveyorSystemChangeEvent} ausgelst und benachrichtigt
  	 * das Flieband, dass sich der Systemzustand in Bezug auf die Bewegung
  	 * eines Kunden auf dem Flieband verndert.
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	public void updateEvent(final SimulationData simData) {
  		notifyTriggered=false;
  		if (clientsAtStation>0) triggerUpdateEvent(simData);
  		simData.runData.fireStateChangeNotify(simData);
  	}
  
  	/**
  	 * Generiert ein Event zur Aktualisierung der Fliebanddaten
  	 * @param simData	Simulationsdatenobjekt
  	 */
  	private void triggerUpdateEvent(final SimulationData simData) {
  		if (notifyTriggered && transportTimeMS>simData.runModel.scaleToSimTime) return;
  		if (simData.runData.stopp) return;
  		final ConveyorSystemChangeEvent event=(ConveyorSystemChangeEvent)simData.getEvent(ConveyorSystemChangeEvent.class);
  		event.init(simData.currentTime+transportTimeMS/10);
  		event.conveyorData=this;
  		simData.eventManager.addEvent(event);
  		notifyTriggered=true;
  	}
  
  	@Override
  	public List<RunDataClient> getWaitingClients() {
  		return waitingClients;
  	}
  }
  