/*
 * PS3 Media Server, for streaming any medias to your PS3.
 * Copyright (C) 2011  G.Zsombor
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.util;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Background task executor and scheduler with a dynamic thread pool, where the threads are daemons.
 *
 * @author zsombor
 *
 */
public class TaskRunner {
	final static Logger LOGGER = LoggerFactory.getLogger(TaskRunner.class);
	
	private static TaskRunner instance;
	
	public static synchronized TaskRunner getInstance() {
		if (instance == null) {
			instance = new TaskRunner();
		}
		return instance;
	}
	
	private final ExecutorService executors = Executors.newCachedThreadPool(new ThreadFactory() {
		
		int counter = 0;
		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r, "background-task-" + (counter++));
			t.setDaemon(true);
			return t;
		}
	});
	
	private final Map<String, Integer> counters = new HashMap<String, Integer>();
	private final Map<String, Lock> uniquenessLock = new HashMap<String, Lock> ();
	
	public void submit(Runnable runnable) {
		executors.execute(runnable);
	}
	
	public <X> Future<X> submit(Callable<X> call) {
		return executors.submit(call);
	}
	
	/**
	 * Submit a named task for later execution.
	 *
	 * @param name
	 * @param runnable
	 */
	public void submitNamed(final String name, final Runnable runnable) {
		submitNamed(name, false, runnable);
	}
	
	/**
	 * Submit a named task for later execution. If singletonTask is set to true, checked that tasks with the same name is not concurrently running.
	 * @param name
	 * @param runnable
	 * @param singletonTask
	 */
	public void submitNamed(final String name, final boolean singletonTask, final Runnable runnable) {
		submit(new Runnable() {
			@Override
			public void run() {
				String prevName = Thread.currentThread().getName();
				boolean locked = false;
				try {
					if (singletonTask) {
						if (getLock(name).tryLock()) {
							locked = true;
							LOGGER.debug("singleton task " + name + " started");
						} else {
							locked = false;
							LOGGER.debug("singleton task '" + name + "' already running, exiting");
							return;
						}
					}
					Thread.currentThread().setName(prevName + '-' + name + '(' + getAndIncr(name) + ')');
					LOGGER.debug("task started");
					runnable.run();
					LOGGER.debug("task ended");
				} finally {
					if (locked) {
						getLock(name).unlock();
					}
					Thread.currentThread().setName(prevName);
				}
			}
		});
		
	}
	
	protected Lock getLock(String name) {
		synchronized(uniquenessLock) {
			Lock lk = uniquenessLock.get(name);
			if (lk == null) {
				lk = new ReentrantLock();
				uniquenessLock.put(name, lk);
			}
			return lk;
		}
	}
	
	protected int getAndIncr(String name) {
		synchronized(counters) {
			Integer val = counters.get(name);
			int newVal = (val == null) ? 0 : val.intValue() + 1;
			counters.put(name, newVal);
			return newVal;
		}
	}
	
	public void shutdown() {
		executors.shutdown();
	}

	/**
	 * @return True if all tasks have completed following shutdown.
	 * @see java.util.concurrent.ExecutorService#isTerminated()
	 */
	public boolean isTerminated() {
		return executors.isTerminated();
	}

	/**
	 * @param timeout
	 * @param unit
	 * @return true if this executor terminated and false if the timeout elapsed before termination.
	 * @throws InterruptedException
	 * @see java.util.concurrent.ExecutorService#awaitTermination(long, java.util.concurrent.TimeUnit)
	 */
	public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
		return executors.awaitTermination(timeout, unit);
	}
}
