/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs.scheduler.tasks;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.openmrs.scheduler.Task;
import org.openmrs.scheduler.TaskDefinition;
import org.openmrs.util.OpenmrsThreadPoolHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class executes the Task.initialize method in a new thread. Extend this class if you want
 * your {@link #initialize(TaskDefinition)} method to run in a new thread (and hence not hold up the
 * "startup" processes)
 */
public class TaskThreadedInitializationWrapper implements Task {
	
	// Logger 
	private static final Logger log = LoggerFactory.getLogger(TaskThreadedInitializationWrapper.class);
	
	private final Task task;
	
	private boolean initialized = false;
	
	private final Lock lock = new ReentrantLock();
	
	private final Condition initializedCond = lock.newCondition();
	
	/**
	 * Default constructor to create this wrapper
	 * 
	 * @param task the Task to wrap around
	 */
	public TaskThreadedInitializationWrapper(Task task) {
		this.task = task;
	}
	
	/**
	 * @see org.openmrs.scheduler.Task#execute() Executes the task defined in the task definition
	 *      but waits until the initialize method has finished
	 */
	@Override
	public void execute() {
		lock.lock();
		try {
			while (!initialized) {
				initializedCond.await();
			}
		}
		catch (InterruptedException e) {
			log.error("Task could not be initialized hence not be executed.", e);
			return;
		}
		finally {
			lock.unlock();
		}

		try {
			task.execute();
		} catch (InterruptedException | ExecutionException e) {
			log.error("Exception occurred while executing task.", e);
		}
	}
	
	/**
	 * @see org.openmrs.scheduler.Task#initialize(org.openmrs.scheduler.TaskDefinition) Initializes
	 *      the task and sets the task definition. This method is non-blocking by executing in a new
	 *      thread.
	 */
	@Override
	public void initialize(final TaskDefinition config) {
		OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
			lock.lock();
			try {
				task.initialize(config);
				initialized = true;
				initializedCond.signalAll();
			}
			finally {
				lock.unlock();
			}
		});
	}
	
	/**
	 * @see org.openmrs.scheduler.Task#getTaskDefinition()
	 */
	@Override
	public TaskDefinition getTaskDefinition() {
		return task != null ? task.getTaskDefinition() : null;
	}
	
	/**
	 * @see org.openmrs.scheduler.Task#isExecuting()
	 */
	@Override
	public boolean isExecuting() {
		return task.isExecuting();
	}
	
	/**
	 * @see org.openmrs.scheduler.Task#shutdown()
	 */
	@Override
	public void shutdown() {
		task.shutdown();
	}
}
