/**
  Copyright (c) 2012 committers of YAKINDU and others.
  All rights reserved. This program and the accompanying materials
  are made available under the terms of the Eclipse Public License v1.0
  which accompanies this distribution, and is available at
  http://www.eclipse.org/legal/epl-v10.html
  Contributors:
  	Markus Muehlbrandt - Initial contribution and API
 */
package org.yakindu.sct.generator.java

import org.yakindu.sct.model.sexec.ExecutionFlow
import org.yakindu.sct.model.sgen.GeneratorEntry
import org.eclipse.xtext.generator.IFileSystemAccess
import com.google.inject.Inject

class RuntimeService {
	
	@Inject
	extension Naming
	
	@Inject
	extension GenmodelEntries
	
	def generateRuntimeService(ExecutionFlow flow, GeneratorEntry entry, IFileSystemAccess fsa) {
		fsa.generateFile(entry.basePackagePath + '/' + runtimeServiceClass.java, content(entry))
	}
	
	def private content(GeneratorEntry entry) {
		'''
		«entry.licenseText»
		package «entry.getBasePackageName()»;
		
		import java.util.HashMap;
		import java.util.LinkedList;
		import java.util.List;
		import java.util.Map;
		import java.util.Timer;
		import java.util.TimerTask;
		import java.util.concurrent.locks.ReentrantReadWriteLock;
		
		/**
		 * Runtime service for state machines to execute a run to completion step
		 * periodically.
		 * 
		 */
		public class RuntimeService {

			private static RuntimeService runtimeService;

			private Timer timer = null;

			private Map<Long, StatemachineTimerTask> timerTasks = new HashMap<Long, StatemachineTimerTask>();

			private class StatemachineTimerTask extends TimerTask {

				private List<IStatemachine> statemachineList = new LinkedList<IStatemachine>();

				private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

				private boolean isPaused = false;

				@Override
				public void run() {
					lock.readLock().lock();
					if (!isPaused) {
						for (IStatemachine statemachine : statemachineList) {
							statemachine.runCycle();
						}
					}
					lock.readLock().unlock();
				}

				/**
				 * Adds the given state machine to the TimerTask.
				 *
				 * @param statemachine
				 * @return {@code true} if state machine is added properly.
				 */
				public boolean addStatemachine(IStatemachine statemachine) {
					lock.writeLock().lock();
					boolean ret = statemachineList.add(statemachine);
					lock.writeLock().unlock();
					return ret;
				}

				/**
				 * Removes the given state machine from the TimerTask.
				 *
				 * @param statemachine
				 * @return {@code true} if state machine is removed properly.
				 */
				public boolean removeStatemachine(IStatemachine statemachine) {
					lock.writeLock().lock();
					boolean ret = statemachineList.remove(statemachine);
					lock.writeLock().unlock();
					return ret;
				}

				public void pause() {
					isPaused = true;
				}

				public void resume() {
					isPaused = false;
				}
			}

			private RuntimeService() {
				// Not intended to be instantiated.
			}

			/**
			 * Returns the {@code RuntimeService} instance as singleton.
			 *
			 * @return The singleton {@code RuntimeService} instance
			 */
			public static RuntimeService getInstance() {
				if (runtimeService == null) {
					runtimeService = new RuntimeService();
				}
				return runtimeService;
			}

			/**
			 * Registers an {@link IStatemachine} for scheduled fixed rate execution
			 *
			 * @param statemachine
			 *            - The state machine to execute
			 * @param cyclePeriod
			 *            - the fixed rate cycle period for scheduling
			 * @return {@code true} if state machine is added properly.
			 */
			public boolean registerStatemachine(IStatemachine statemachine,
					long cyclePeriod) {
		
				if (timerTasks.containsKey(cyclePeriod)) {
					// TimerTask for cycle time already existing -> add state machine
					return timerTasks.get(cyclePeriod).addStatemachine(statemachine);
				} else {
					// Create new TimerTask for cycle period and add state machine
					StatemachineTimerTask timerTask = new StatemachineTimerTask();
					timerTasks.put(cyclePeriod, timerTask);
					boolean ret = timerTask.addStatemachine(statemachine);
					// Create a new Timer instance if runtime service was cancelled
					// before
					if (timer == null) {
						timer = new Timer();
					}
					timer.scheduleAtFixedRate(timerTask, 0, cyclePeriod);
					return ret;
				}
			}

			/**
			 * Removes the given state machine from runtime service.
			 *
			 * @param statemachine
			 *            - the state machine to be removed
			 * @param cyclePeriod
			 *            - the scheduling cycle period of the state machine
			 * @return {@code true} if state machine is removed properly.
			 */
			public boolean unregisterStatemachine(IStatemachine statemachine,
					long cyclePeriod) {
				if (timerTasks.containsKey(cyclePeriod)) {
					boolean ret = timerTasks.get(cyclePeriod).removeStatemachine(
							statemachine);
		
					return ret;
				}
				return false;
			}

			/**
			 * Cancels the execution of state machines for the given cycle period. This
			 * stops the execution of state machines which are registered for the given
			 * cycle period and cancels the executing {@link TimerTask}.
			 *
			 * @return {@code true} if poperly cancelled
			 */
			public boolean cancelAll(long cyclePeriod) {
				if (timer != null && timerTasks.containsKey(cyclePeriod)) {
					TimerTask task = timerTasks.get(cyclePeriod);
					task.cancel();
					timer.purge();
					timerTasks.remove(cyclePeriod);
					return true;
				}
				return false;
			}

			/**
			 * Pauses the execution of all state machines which are registered for the
			 * given cyclePeriod.
			 *
			 * @param cyclePeriod
			 * @return {@code true} if properly paused
			 *
			 */
			public boolean pauseAll(long cyclePeriod) {
				if (timerTasks.containsKey(cyclePeriod)) {
					timerTasks.get(cyclePeriod).pause();
					return true;
				}
				return false;
			}

			/**
			 * Resumes the execution of all state machines which are registered for the
			 * given cyclePeriod.
			 *
			 * @param cyclePeriod
			 * @return {@code true} if properly resumed
			 *
			 */
			public boolean resumeAll(long cyclePeriod) {
				if (timerTasks.containsKey(cyclePeriod)) {
					timerTasks.get(cyclePeriod).resume();
					return true;
				}
				return false;
			}

			/**
			 * Cancels the execution of all registered state machines. This cancels the
			 * executing {@link Timer} freeing all allocated resources and terminates
			 * all existing execution threads.
			 */
			public void cancelTimer() {
				if (timer != null) {
					timer.cancel();
					timer.purge();
					timerTasks.clear();
					timer = null;
				}
			}
		}
		'''
	}
}