/*
 * Created on Nov 9, 2007
 * Created by Paul Gardner
 * Copyright (C) 2007 Aelitis, All Rights Reserved.
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * AELITIS, SAS au capital de 63.529,40 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */


package org.gudy.azureus2.core3.util;

import java.util.LinkedList;

import com.aelitis.azureus.core.util.Java15Utils;


public abstract class 
AEThread2 
{
	public static final boolean TRACE_TIMES = false;
	
	private static final int MIN_RETAINED	= Math.max(Runtime.getRuntime().availableProcessors(),2);
	private static final int MAX_RETAINED	= Math.max(MIN_RETAINED*4, 16);
	
	private static final int THREAD_TIMEOUT_CHECK_PERIOD	= 10*1000;
	private static final int THREAD_TIMEOUT					= 60*1000;
	
	private static final LinkedList	daemon_threads = new LinkedList();
	
	private static final class JoinLock {
		volatile boolean released = false;
	}
	
	private static long	last_timeout_check;
	
	private static long	total_starts;
	private static long	total_creates;
	
	
	private threadWrapper	wrapper;
	
	private String				name;
	private boolean				daemon;
	private int					priority	= Thread.NORM_PRIORITY;
	private volatile JoinLock	lock		= new JoinLock();
	
	public
	AEThread2(
		String		_name )
	{
		this( _name, true );
	}
	
	public
	AEThread2(
		String		_name,
		boolean		_daemon )
	{
		name		= _name;
		daemon		= _daemon;
	}
	
	/**
	 * multiple invocations of start() are possible, but discouraged if combined
	 * with other thread operations such as interrupt() or join()
	 */
	public void
	start()
	{
		JoinLock currentLock = lock;
		JoinLock newLock;
		
		synchronized (currentLock)
		{
			// create new lock in case this is a restart, all old .join()s will be locked on the old thread and thus released by the old thread
			if(currentLock.released)
				newLock = lock = new JoinLock();
			else
				newLock = currentLock;
		}
		
		if ( daemon ){
			
			synchronized( daemon_threads ){

				total_starts++;
				
				if ( daemon_threads.isEmpty()){
				
					total_creates++;
					
					wrapper = new threadWrapper( name, true );
					
				}else{
					
					wrapper = (threadWrapper)daemon_threads.removeLast();
					
					wrapper.setName( name );
				}
			}
		}else{
		
			wrapper = new threadWrapper( name, false );
		}
		
		if ( priority != wrapper.getPriority() ){
			
			wrapper.setPriority( priority );
		}
		
		wrapper.currentLock = newLock;
		
		wrapper.start( this, name );
	}
	
	public void
	setPriority(
		int		_priority )
	{
		priority	= _priority;
			
		if ( wrapper != null ){
			wrapper.setPriority( priority );
		}
	}
	
	public void
	setName(
		String	s )
	{
		name	= s;
		
		if ( wrapper != null ){
			
			wrapper.setName( name );
		}
	}
	
	public String
	getName()
	{
		return( name );
	}
	
	public void
	interrupt()
	{
		if ( wrapper == null ){
			
			throw new IllegalStateException( "Interrupted before started!" );
			
		}else{
			
			wrapper.interrupt();
		}
	}
	
	public boolean
	isAlive() {
		return wrapper == null ? false : wrapper.isAlive();
	}
	
	public boolean
	isCurrentThread()
	{
		return( wrapper == Thread.currentThread());
	}
	
	public String
	toString()
	{
		if ( wrapper == null ){
			
			return( name + " [daemon=" + daemon + ",priority=" + priority + "]" );
			
		}else{
				
			return( wrapper.toString());
		}
	}
	
	public abstract void
	run();
	
	public static boolean
	isOurThread(
		Thread	thread )
	{
		return( AEThread.isOurThread( thread ));
	}
	
	public static void
	setOurThread()
	{
		AEThread.setOurThread();
	}
	
	public static void
	setOurThread(
		Thread	thread )
	{
		AEThread.setOurThread( thread );
	}
	
	public static void
	setDebug(
		Object		debug )
	{
		Thread current = Thread.currentThread();
		
		if ( current instanceof threadWrapper ){
			
			((threadWrapper)current).setDebug( debug );
		}
	}
	
		/**
		 * entry 0 is debug object, 1 is Long mono-time it was set
		 * @param t
		 * @return
		 */
	
	public static Object[]
	getDebug(
		Thread		t )
	{
		if ( t instanceof threadWrapper ){
			
			return(((threadWrapper)t).getDebug());
		}
		
		return( null );
	}
	
	protected static class
	threadWrapper
		extends Thread
	{
		private AESemaphore2	sem;
		private AEThread2		target;
		private JoinLock		currentLock;
		
		private long		last_active_time;
		
		private Object[]		debug;
		
		protected
		threadWrapper(
			String		name,
			boolean		daemon )
		{
			super( name );
			
			setDaemon( daemon );
		}
		
		public void
		run()
		{
			while( true ){
				
				synchronized( currentLock ){
					try{
						if ( TRACE_TIMES ){

							long 	start_time 	= SystemTime.getHighPrecisionCounter();
							long	start_cpu 	= Java15Utils.getThreadCPUTime();

							try{

								target.run();

							}finally{
								
								long	time_diff 	= ( SystemTime.getHighPrecisionCounter() - start_time )/1000000;
								long	cpu_diff	= ( Java15Utils.getThreadCPUTime() - start_cpu ) / 1000000;
								
								if ( cpu_diff > 10 || time_diff > 10 ){
								
									System.out.println( TimeFormatter.milliStamp() + ": Thread: " + target.getName() + ": " + cpu_diff + "/" + time_diff );
								}
							}
						}else{

							target.run();
						}
												
					}catch( Throwable e ){
						
						DebugLight.printStackTrace(e);
						
					}finally{
						
						target = null;

						debug	= null;
						
						currentLock.released = true;
						
						currentLock.notifyAll();						
					}
				}
								
				if ( isInterrupted() || !Thread.currentThread().isDaemon()){
					
					break;
					
				}else{
					
					synchronized( daemon_threads ){
						
						last_active_time	= SystemTime.getCurrentTime();
						
						if ( 	last_active_time < last_timeout_check ||
								last_active_time - last_timeout_check > THREAD_TIMEOUT_CHECK_PERIOD ){
							
							last_timeout_check	= last_active_time;
							
							while( daemon_threads.size() > 0 && daemon_threads.size() > MIN_RETAINED ){
								
								threadWrapper thread = (threadWrapper)daemon_threads.getFirst();
								
								long	thread_time = thread.last_active_time;
								
								if ( 	last_active_time < thread_time ||
										last_active_time - thread_time > THREAD_TIMEOUT ){
									
									daemon_threads.removeFirst();
									
									thread.retire();
									
								}else{
									
									break;
								}
							}
						}
						
						if ( daemon_threads.size() >= MAX_RETAINED ){
							
							return;
						}

						daemon_threads.addLast( this );

						setName( "AEThread2:parked[" + daemon_threads.size() + "]" );
						
						// System.out.println( "AEThread2: queue=" + daemon_threads.size() + ",creates=" + total_creates + ",starts=" + total_starts );
					}
					
					sem.reserve();
					
					if ( target == null ){
						
						break;
					}
				}
			}
		}
		
		protected void
		start(
			AEThread2	_target,
			String		_name )
		{
			target	= _target;
			
			setName( _name );
			
			if ( sem == null ){
				
				 sem = new AESemaphore2( "AEThread2" );
				 
				 super.start();
				 
			}else{
				
				sem.release();
			}
		}
		
		protected void
		retire()
		{			
			sem.release();
		}
		
		protected void
		setDebug(
			Object	d )
		{
			debug	= new Object[]{ d, SystemTime.getMonotonousTime() };
		}
		
		protected Object[]
		getDebug()
		{
			return( debug );
		}
	}
	
	public void 
	join()
	{
		JoinLock currentLock = lock;

			// sync lock will be blocked by the thread
		
		synchronized( currentLock ){
			
				// wait in case the thread is not running yet
			
			while (!currentLock.released ){
				
				try{
					currentLock.wait();
					
				}catch( InterruptedException e ){ 
				}
			}
		}
	}
}
