package simulator.interfaces.timers;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Term;
import models.dataConstraintModel.JsonTerm;
import models.dataConstraintModel.MapTerm;
import models.dataConstraintModel.ResourcePath;
import models.dataFlowModel.DataTransferChannel;
import simulator.Event;
import simulator.Resource;
import simulator.Simulator;
import simulator.SystemState;
import simulator.interfaces.INativeReceiver;

public class TimerService implements INativeReceiver {
	public final String timersUpdatedChannelName = "TimersUpdated";
	public final String timerEventChannelName = "TimerEvent";

	protected final Simulator simulator;
	protected final Map<String, ScheduledThreadPoolExecutor> timers;	
	protected DataTransferChannel timersUpdatedChannel;
	protected DataTransferChannel timerEventChannel;
	
	public TimerService(Simulator simulator) {
		this.simulator = simulator;
		timers = new HashMap<>();
		timersUpdatedChannel = (DataTransferChannel) simulator.getModel().getChannel(timersUpdatedChannelName);
		timerEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(timerEventChannelName);
		simulator.addNativeReceiver(this, timersUpdatedChannel);
	}
	
	@Override
	public void onReceiveFromModel(Event event, SystemState nextSystemState) {
		Expression message = event.getMessage();
		if (message instanceof Term && ((Term) message).getChildren().size() >= 2) {
			Expression curTimersExp = ((Term) message).getChild(0);
			Expression nextTimersExp = ((Term) message).getChild(1);
			if (curTimersExp instanceof MapTerm && nextTimersExp instanceof MapTerm) {
				MapTerm curTimers = (MapTerm) curTimersExp;
				MapTerm nextTimers = (MapTerm) nextTimersExp;
				Set<String> oldTidSet = new HashSet<>(curTimers.keySet());
				Set<String> newTidSet = new HashSet<>(nextTimers.keySet());
				oldTidSet.removeAll(nextTimers.keySet());
				newTidSet.removeAll(curTimers.keySet());
				if (!oldTidSet.isEmpty() || !newTidSet.isEmpty()) {
					// If the set of timers is changed.
					
					// Remove old timers and their native receivers.
					for (String tid: oldTidSet) {
						ScheduledThreadPoolExecutor timer = timers.get(tid);
						timer.shutdown();
						timers.remove(tid);
					}
		
					// Add new timers.
					Resource timersResource = nextSystemState.getResource(event.getInputResource().getResourceIdentifier());
					for (String tid: newTidSet) {
						Expression value = nextTimers.get(tid);
						if (value instanceof JsonTerm) {
							JsonTerm timerValue = (JsonTerm) value;
							Resource timerResource = timersResource.getChildrenMap().get(tid);
							// Add a timer.
							Expression intervalExp = timerValue.get("interval");
							long interval = Long.parseLong(((Constant) intervalExp).toString());
							ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(0);
							timers.put(tid, timer);
							
							// Connect Java timer and model.
							ResourcePath resPath = timerEventChannel.getOutputResources().iterator().next();
							TimerEventSender sender = new TimerEventSender(simulator, timerEventChannel, resPath, timerResource);	// timer => timerResource
							timer.scheduleAtFixedRate(sender, 0, interval, TimeUnit.MILLISECONDS);
						}
					}
				}
			}
		}
	}
	
	public class TimerStart implements INativeReceiver {
		public final String timerStartChannelName = "TimerStart";
		protected DataTransferChannel timerStartChannel;
		protected DataTransferChannel timerEventChannel;
		
		public TimerStart() {
			timerStartChannel = (DataTransferChannel) simulator.getModel().getChannel(timerStartChannelName);
			timerEventChannel = (DataTransferChannel) simulator.getModel().getInputChannel(timerEventChannelName);
			simulator.addNativeReceiver(this, timerStartChannel);
		}

		@Override
		public void onReceiveFromModel(Event event, SystemState nextSystemState) {
			Expression message = event.getMessage();
			if (message instanceof Term && ((Term) message).getChildren().size() >= 1) {
				Expression tidExp = event.getInputResource().getResourceIdentifier().getLastParam();
				Expression intervalExp = ((Term) message).getChild(0);
				if (tidExp instanceof Constant && intervalExp instanceof Constant) {
					String tid = ((Constant) tidExp).toString();
					long interval = Long.parseLong(((Constant) intervalExp).toString());
					ScheduledThreadPoolExecutor timer = timers.get(tid);
					TimerEventSender sender = new TimerEventSender(simulator, timerEventChannel, event.getInputResourcePath(), event.getInputResource());
					timer.scheduleAtFixedRate(sender, 0, interval, TimeUnit.MILLISECONDS);
				}
			}
		}
	}

	public class TimerClear implements INativeReceiver {
		public final String timerClearChannelName = "TimerClear";
		protected DataTransferChannel timerClearChannel;
		
		public TimerClear() {
			timerClearChannel = (DataTransferChannel) simulator.getModel().getChannel(timerClearChannelName);
			simulator.addNativeReceiver(this, timerClearChannel);
		}
		
		@Override
		public void onReceiveFromModel(Event event, SystemState nextSystemState) {
			Expression message = event.getMessage();
			if (message instanceof Term && ((Term) message).getChildren().size() >= 1) {
				Expression tidExp = event.getInputResource().getResourceIdentifier().getLastParam();
				if (tidExp instanceof Constant) {
					String tid = ((Constant) tidExp).toString();
					ScheduledThreadPoolExecutor timer = timers.get(tid);
					timer.shutdown();
					timers.remove(tid);
				}
			}
		}
		
	}
}
