package slite.lib_java;

import java.util.ArrayDeque;
import java.util.Date;

public class SliteThread implements Runnable
{
	Runnable subject = null;

	public static final ArrayDeque<SliteThread> threads = new ArrayDeque<SliteThread>();
	public static final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();

	public static int threadCounter = 0;
	public static long threadsKilled = 0;
	public static int maxThreadPoolSize = 300;
	static final Object queueLock = new Object();
	public static long threadMaxAge = 1800000;

	public long birth = 0;
	public boolean running = true;

	static
	{
		try { maxThreadPoolSize = Integer.parseInt(System.getProperty("slite.lib.java.SliteThread.maxThreadPoolSize"),500); } catch(Exception e){}
	}
	
	public static int getActiveThreadCount()
	{
		int result = 0;
		synchronized(queueLock)
		{
			result = threadCounter - SliteThread.threads.size();
		}
		return result;		
	}
	
	public static int getThreadQueueLength()
	{
		int result = 0;
		synchronized(queueLock)
		{
			result = SliteThread.threads.size();
		}
		return result;
	}
	
	public static int getTaskQueueLength()
	{
		int result = 0;
		synchronized(queueLock)
		{
			result = SliteThread.tasks.size();
		}
		return result;
	}

	public static void threadAvailable(SliteThread thread)
	{
		synchronized(queueLock)
		{
			Runnable task = SliteThread.tasks.pollFirst();
			if(task==null) SliteThread.threads.offerLast(thread);
			else thread.capture(task);
		}
	}

	public static void sleep(long time)
	{
		try
		{
			Thread.sleep(time);
		}
		catch(Exception e)
		{
			System.err.println(new Date());
			e.printStackTrace();
		}
	}

	public static void start(Runnable subject)
	{
		SliteThread thread = null;
		try
		{
			/*
			Three things can happen:
			1. "threads" queue contains at least 1 thread and is used to run task
			2. "threads" queue is empty but thread pool size has not been reached. Thus a new thread is constructed.
			3. "threads" queue is empty and thread pool size is reached. Thus the runnable is queued for the next available thread.
			*/
			synchronized(queueLock)
			{
				thread = SliteThread.threads.pollFirst(); // No. 1
				if(thread==null)
				{
					if(threadCounter < maxThreadPoolSize) // No. 2
					{
						thread = new SliteThread();
						threadCounter++;
					}
					else
						SliteThread.tasks.offerLast(subject); // No. 3
				}
			}
			if(thread!=null) thread.capture(subject);
		}
		catch(Exception e)
		{
			System.err.println(new Date());
			e.printStackTrace();		
		}
	}

	private SliteThread()
	{
		this.birth = System.currentTimeMillis();
		new Thread(this,"SliteThread-"+threadCounter).start();
	}

	@Override
	public synchronized void run()
	{
		while(running)
		{
			try
			{
				if(this.subject==null) this.wait();
				if(this.subject!=null) this.subject.run();
			}
			catch(Exception e)
			{
				System.err.println(new Date());
				e.printStackTrace();		
			}

			if(birth + threadMaxAge < System.currentTimeMillis()) running = false;

			if(this.subject!=null && running) // This detects a "spurious wakeup"
			{
				this.subject = null;
				SliteThread.threadAvailable(this);
			}
		}

		synchronized(queueLock)
		{
			threadCounter--;
			threadsKilled++;
		}

	}

	public synchronized void capture(Runnable subject)
	{
		this.subject = subject;
		this.notifyAll();
	}
}