/*
 * Created on 13-Jul-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, 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.
 *
 */

package com.aelitis.azureus.core.impl;

import java.io.File;
import java.net.InetAddress;
import java.net.URL;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.config.impl.TransferSpeedValidator;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerAdapter;
import org.gudy.azureus2.core3.global.GlobalManagerFactory;
import org.gudy.azureus2.core3.global.GlobalManagerStats;
import org.gudy.azureus2.core3.global.GlobalMangerProgressListener;
import org.gudy.azureus2.core3.internat.*;
import org.gudy.azureus2.core3.ipfilter.IpFilterManager;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.ipfilter.*;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.host.*;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.platform.PlatformManager;
import org.gudy.azureus2.platform.PlatformManagerCapabilities;
import org.gudy.azureus2.platform.PlatformManagerFactory;
import org.gudy.azureus2.platform.PlatformManagerListener;
import org.gudy.azureus2.plugins.*;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentDownloader;
import org.gudy.azureus2.plugins.utils.DelayedTask;
import org.gudy.azureus2.plugins.utils.PowerManagementListener;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
import org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;

import com.aelitis.azureus.core.*;
import com.aelitis.azureus.core.backup.BackupManagerFactory;
import com.aelitis.azureus.core.custom.CustomizationManagerFactory;
import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.DHTListener;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTester;
import com.aelitis.azureus.core.instancemanager.AZInstanceManager;
import com.aelitis.azureus.core.instancemanager.AZInstanceManagerAdapter;
import com.aelitis.azureus.core.instancemanager.AZInstanceManagerFactory;
import com.aelitis.azureus.core.instancemanager.AZInstanceTracked;
import com.aelitis.azureus.core.nat.NATTraverser;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminNetworkInterface;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminNetworkInterfaceAddress;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminPropertyChangeListener;
import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPNetworkManager;
import com.aelitis.azureus.core.networkmanager.impl.udp.UDPNetworkManager;
import com.aelitis.azureus.core.pairing.PairingManagerFactory;
import com.aelitis.azureus.core.peermanager.PeerManager;
import com.aelitis.azureus.core.peermanager.nat.PeerNATTraverser;
import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
import com.aelitis.azureus.plugins.clientid.ClientIDPlugin;
import com.aelitis.azureus.core.security.CryptoManager;
import com.aelitis.azureus.core.security.CryptoManagerFactory;
import com.aelitis.azureus.core.speedmanager.SpeedLimitHandler;
import com.aelitis.azureus.core.speedmanager.SpeedManager;
import com.aelitis.azureus.core.speedmanager.SpeedManagerAdapter;
import com.aelitis.azureus.core.speedmanager.SpeedManagerFactory;
import com.aelitis.azureus.core.update.AzureusRestarterFactory;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.versioncheck.VersionCheckClient;
import com.aelitis.azureus.core.vuzefile.VuzeFile;
import com.aelitis.azureus.core.vuzefile.VuzeFileComponent;
import com.aelitis.azureus.core.vuzefile.VuzeFileHandler;
import com.aelitis.azureus.core.vuzefile.VuzeFileProcessor;
import com.aelitis.azureus.launcher.classloading.PrimaryClassloader;
import com.aelitis.azureus.plugins.dht.DHTPlugin;
import com.aelitis.azureus.plugins.startstoprules.defaultplugin.DefaultRankCalculator;
import com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin;
import com.aelitis.azureus.plugins.tracker.dht.DHTTrackerPlugin;
import com.aelitis.azureus.plugins.upnp.UPnPPlugin;
import com.aelitis.azureus.ui.UIFunctions;
import com.aelitis.azureus.ui.UIFunctionsManager;
import com.aelitis.azureus.util.MapUtils;

/**
 * @author parg
 *
 */

public class 
AzureusCoreImpl 
	implements 	AzureusCore
{
	private final static LogIDs LOGID = LogIDs.CORE;
	protected static AzureusCore		singleton;
	protected static AEMonitor			class_mon	= new AEMonitor( "AzureusCore:class" );
	
	private static final String DM_ANNOUNCE_KEY	= "AzureusCore:announce_key";
	private static final boolean LOAD_PLUGINS_IN_OTHER_THREAD = true;
	
	/** 
	 * Listeners that will be fired after core has completed initialization
	 */
	static List<AzureusCoreRunningListener> coreRunningListeners = new ArrayList<AzureusCoreRunningListener>(1);
	
	static AEMonitor mon_coreRunningListeners = new AEMonitor("CoreCreationListeners");
	
	public static AzureusCore
	create()
	
		throws AzureusCoreException
	{
		try{
			class_mon.enter();
			
			if ( singleton != null ){
		
				throw( new AzureusCoreException( "Azureus core already instantiated" ));
			}
			
			singleton	= new AzureusCoreImpl();
			
			return( singleton );
			
		}finally{
			
			class_mon.exit();
		}
	}
	
	public static boolean
	isCoreAvailable()
	{
		return( singleton != null );
	}

	public static boolean
	isCoreRunning()
	{
		return( singleton != null && singleton.isStarted() );
	}

	public static AzureusCore
	getSingleton()
	
		throws AzureusCoreException
	{
		if ( singleton == null ){
			
			throw( new AzureusCoreException( "core not instantiated"));
		}
		
		return( singleton );
	}	

	private PluginInitializer 	pi;
	private GlobalManager		global_manager;
	private AZInstanceManager	instance_manager;
	private SpeedManager		speed_manager;
	private CryptoManager		crypto_manager;
	private NATTraverser		nat_traverser;
	
	private final long create_time;


	private volatile boolean				started;
	private volatile boolean				stopped;
	private volatile boolean				restarting;
	
	private CopyOnWriteList		listeners				= new CopyOnWriteList();
	private CopyOnWriteList		lifecycle_listeners		= new CopyOnWriteList();
	private List				operation_listeners		= new ArrayList();
	
	private CopyOnWriteList<PowerManagementListener>	power_listeners = new CopyOnWriteList<PowerManagementListener>();
	
	private AESemaphore			stopping_sem	= new AESemaphore( "AzureusCore::stopping" );
	
	private AEMonitor			this_mon		= new AEMonitor( "AzureusCore" );

	private AzureusCoreOperation	initialisation_op = createOperation( AzureusCoreOperation.OP_INITIALISATION );
	
	public static boolean SUPPRESS_CLASSLOADER_ERRORS = false;
	
	private boolean ca_shutdown_computer_after_stop	= false;
	private long	ca_last_time_downloading 		= -1;
	private long	ca_last_time_seeding 			= -1;
	
	private boolean prevent_sleep_remove_trigger	= false;
	
	protected
	AzureusCoreImpl()
	{
		create_time = SystemTime.getCurrentTime();
		
		if(!SUPPRESS_CLASSLOADER_ERRORS && !(this.getClass().getClassLoader() instanceof PrimaryClassloader))
			System.out.println("###\nWarning: Core not instantiated through a PrimaryClassloader, this can lead to restricted functionality or bugs in future versions\n###");
		
		COConfigurationManager.initialise();
		
		MessageText.loadBundle();
		
		AEDiagnostics.startup( COConfigurationManager.getBooleanParameter( "diags.enable.pending.writes", false ));
		
		COConfigurationManager.setParameter( "diags.enable.pending.writes", false );
				
		AEDiagnostics.markDirty();
		
		AETemporaryFileHandler.startup();
    
		AEThread2.setOurThread();
		
			// set up a backwards pointer from config -> app dir so we can derive one from the other. It'll get saved on closedown, no need to do so now
				
		COConfigurationManager.setParameter( "azureus.application.directory", new File( SystemProperties.getApplicationPath()).getAbsolutePath());
		COConfigurationManager.setParameter( "azureus.user.directory", new File( SystemProperties.getUserPath()).getAbsolutePath());
		
		crypto_manager = CryptoManagerFactory.getSingleton();
		
		PlatformManagerFactory.getPlatformManager().addListener(
			new PlatformManagerListener()
			{
				public int
				eventOccurred(
					int		type )
				{
					if ( type == ET_SHUTDOWN ){
						
						if (Logger.isEnabled()){
							Logger.log(new LogEvent(LOGID, "Platform manager requested shutdown"));
						}
						
						COConfigurationManager.save();
						
						requestStop();
						
						return( 0 );
						
					}else if ( type == ET_SUSPEND ){
						
						if (Logger.isEnabled()){
							Logger.log(new LogEvent(LOGID, "Platform manager requested suspend"));
						}
						
						COConfigurationManager.save();
						
					}else if ( type == ET_RESUME ){
		
						if (Logger.isEnabled()){
							Logger.log(new LogEvent(LOGID, "Platform manager requested resume"));
						}
						
						announceAll( true );
					}
					
					return( -1 );
				}
			});
		
			//ensure early initialization
				
		CustomizationManagerFactory.getSingleton().initialize();
		
		AEProxySelectorFactory.getSelector();
		
		NetworkManager.getSingleton();
		
		PeerManager.getSingleton();
		
			// Used to be a plugin, but not any more...
		
		ClientIDPlugin.initialize();
		
		pi = PluginInitializer.getSingleton( this, initialisation_op );
		
		
		instance_manager = 
			AZInstanceManagerFactory.getSingleton( 
				new AZInstanceManagerAdapter()
				{
					public String
					getID()
					{
						return( COConfigurationManager.getStringParameter( "ID", "" ));
					}
					
					public InetAddress 
					getPublicAddress() 
					{	
						return( PluginInitializer.getDefaultInterface().getUtilities().getPublicAddress());
					}
					
					public int[]
					getPorts()
					{
						return( new int[]{
							TCPNetworkManager.getSingleton().getTCPListeningPortNumber(),
							UDPNetworkManager.getSingleton().getUDPListeningPortNumber(),
							UDPNetworkManager.getSingleton().getUDPNonDataListeningPortNumber()});

					}
					public VCPublicAddress
					getVCPublicAddress()
					{
						return(
							new VCPublicAddress()
							{
								private VersionCheckClient vcc = VersionCheckClient.getSingleton();
								
								public String
								getAddress()
								{
									return( vcc.getExternalIpAddress( true, false ));
								}
								
								public long
								getCacheTime()
								{
									return( vcc.getSingleton().getCacheTime( false ));
								}
							});
					}

					public AZInstanceTracked.TrackTarget
					track(
						byte[]		hash )
					{
						List	dms = getGlobalManager().getDownloadManagers();
						
						Iterator	it = dms.iterator();
						
						DownloadManager	matching_dm = null;
						
						try{
							while( it.hasNext()){
								
								DownloadManager	dm = (DownloadManager)it.next();
								
								TOTorrent	torrent = dm.getTorrent();
								
								if ( torrent == null ){
									
									continue;
								}
								
								byte[]	sha1_hash = (byte[])dm.getData( "AZInstanceManager::sha1_hash" );
								
								if ( sha1_hash == null ){			

									sha1_hash	= new SHA1Simple().calculateHash( torrent.getHash());
									
									dm.setData( "AZInstanceManager::sha1_hash", sha1_hash );
								}
								
								if ( Arrays.equals( hash, sha1_hash )){
									
									matching_dm	= dm;
									
									break;
								}
							}
						}catch( Throwable e ){
							
							Debug.printStackTrace(e);
						}
						
						if ( matching_dm == null ){
							
							return( null );
						}
						
						if ( !matching_dm.getDownloadState().isPeerSourceEnabled( PEPeerSource.PS_PLUGIN )){
							
							return( null );
						}
						
						int	dm_state = matching_dm.getState();
						
						if ( dm_state == DownloadManager.STATE_ERROR || dm_state == DownloadManager.STATE_STOPPED ){
							
							return( null );
						}
						
						try{
						
							final Object target = DownloadManagerImpl.getDownloadStatic( matching_dm );
						
							final boolean	is_seed = matching_dm.isDownloadComplete(true);
							
							return(
								new AZInstanceTracked.TrackTarget()
								{
									public Object
									getTarget()
									{
										return( target );
									}
									
									public boolean
									isSeed()
									{
										return( is_seed );
									}
								});
							
						}catch( Throwable e ){
							
							return( null );
						}
					}
					
					public DHTPlugin 
					getDHTPlugin()
					{
						PluginInterface pi = getPluginManager().getPluginInterfaceByClass( DHTPlugin.class );
						
						if ( pi != null ){
							
							return( (DHTPlugin)pi.getPlugin());
						}
						
						return( null );
					}
					
					public UPnPPlugin 
					getUPnPPlugin() 
					{
						PluginInterface pi = getPluginManager().getPluginInterfaceByClass( UPnPPlugin.class );
						
						if ( pi != null ){
							
							return((UPnPPlugin)pi.getPlugin());
						}
						
						return( null );
					}
					
					public void 
					addListener(
						final StateListener listener) 
					{
						AzureusCoreImpl.this.addLifecycleListener(
							new AzureusCoreLifecycleAdapter()
							{
								public void 
								started(
									AzureusCore core) 
								{
									listener.started();
								}
								
								public void
								stopping(
									AzureusCore		core )
								{
									listener.stopped();
								}
							});
					}
				});
		
		if ( COConfigurationManager.getBooleanParameter( "speedmanager.enable", true )){
			
			speed_manager	= 
				SpeedManagerFactory.createSpeedManager( 
						this,
						new SpeedManagerAdapter()
						{
							private static final int UPLOAD_SPEED_ADJUST_MIN_KB_SEC		= 10;
							private static final int DOWNLOAD_SPEED_ADJUST_MIN_KB_SEC	= 300;
							
							private boolean setting_limits;
							
							public int
							getCurrentProtocolUploadSpeed(
								int	average_period )
							{
								if ( global_manager != null ){
									
									GlobalManagerStats stats = global_manager.getStats();
									
									return( stats.getProtocolSendRateNoLAN( average_period ));
									
								}else{
									
									return(0);
								}
							}
							
							public int
							getCurrentDataUploadSpeed(
								int	average_period )
							{
								if ( global_manager != null ){
									
									GlobalManagerStats stats = global_manager.getStats();
									
									return( stats.getDataSendRateNoLAN( average_period ));
									
								}else{
									
									return(0);
								}
							}
	
	                        public int
	                        getCurrentProtocolDownloadSpeed(
	                        	int	average_period )
	                        {
	                            if( global_manager != null ){
	                                GlobalManagerStats stats = global_manager.getStats();
	                                return (stats.getProtocolReceiveRateNoLAN( average_period ) );
	                            }else{
	                                return(0);
	                            }
	                        }
	
	                        public int
	                        getCurrentDataDownloadSpeed(
	                        	int	average_period )
	                        {
	                            if( global_manager != null ){
	                                GlobalManagerStats stats = global_manager.getStats();
	                                return (stats.getDataReceiveRateNoLAN( average_period ) );
	                            }else{
	                                return(0);
	                            }
	                        }
	                        
	                        public int
							getCurrentUploadLimit()
							{
								String key = TransferSpeedValidator.getActiveUploadParameter( global_manager );
								
								int	k_per_second = COConfigurationManager.getIntParameter( key );
								
								int	bytes_per_second;
								
								if ( k_per_second == 0 ){
									
									bytes_per_second = Integer.MAX_VALUE;
									
								}else{
									
									bytes_per_second = k_per_second*1024;
								}
								
								return( bytes_per_second );
							}
							
							public void
							setCurrentUploadLimit(
								int		bytes_per_second )
							{
								if ( bytes_per_second != getCurrentUploadLimit()){
									
									String key = TransferSpeedValidator.getActiveUploadParameter( global_manager );
										
									int	k_per_second;
									
									if ( bytes_per_second == Integer.MAX_VALUE ){
										
										k_per_second	= 0;
										
									}else{
									
										k_per_second = (bytes_per_second+1023)/1024;
									}
									
									if ( k_per_second > 0 ){
									
										k_per_second = Math.max( k_per_second, UPLOAD_SPEED_ADJUST_MIN_KB_SEC );
									}
									
									COConfigurationManager.setParameter( key, k_per_second );
								}
							}
							
							public int
							getCurrentDownloadLimit()
							{
								return( TransferSpeedValidator.getGlobalDownloadRateLimitBytesPerSecond());
							}
							
							public void
							setCurrentDownloadLimit(
								int		bytes_per_second )
							{
								if ( bytes_per_second == Integer.MAX_VALUE ){
									
									bytes_per_second = 0;
								}
								
								if ( bytes_per_second > 0 ){
									
									bytes_per_second = Math.max( bytes_per_second, DOWNLOAD_SPEED_ADJUST_MIN_KB_SEC*1024 );
								}
								
								TransferSpeedValidator.setGlobalDownloadRateLimitBytesPerSecond( bytes_per_second );
							}
							
							public Object
							getLimits()
							{
								String up_key 	= TransferSpeedValidator.getActiveUploadParameter( global_manager );
								String down_key	= TransferSpeedValidator.getDownloadParameter();
								
								return( 
									new Object[]{
										up_key,
										new Integer( COConfigurationManager.getIntParameter( up_key )),
										down_key,
										new Integer( COConfigurationManager.getIntParameter( down_key )),
									});
							}
							
							public void
							setLimits(
								Object		limits,
								boolean		do_up,
								boolean		do_down )
							{
								if ( limits == null ){
									
									return;
								}
								try{
									if ( setting_limits ){
										
										return;
									}
								
									setting_limits	= true;
								
									Object[]	bits = (Object[])limits;
									
									if ( do_up ){
										
										COConfigurationManager.setParameter((String)bits[0], ((Integer)bits[1]).intValue());
									}
									
									if ( do_down ){
										
										COConfigurationManager.setParameter((String)bits[2], ((Integer)bits[3]).intValue());
									}
									
								}finally{
									
									setting_limits	= false;
									
								}
							}
						});
		}
		
		nat_traverser = new NATTraverser( this );
		
		PeerNATTraverser.initialise( this );
		
		BackupManagerFactory.getManager( this );
		
			// one off explicit GC to clear up initialisation mem
		
		SimpleTimer.addEvent(
				"AzureusCore:gc",
				SystemTime.getOffsetTime(60*1000),
				new TimerEventPerformer()
				{
					public void 
					perform(
						TimerEvent event) 
					{
						System.gc();
					}
				});
	}
	
	public long
	getCreateTime()
	{
		return( create_time );
	}
	
	protected void
	announceAll(
		boolean	force )
	{
		Logger.log(	new LogEvent(LOGID, "Updating trackers" ));

		GlobalManager gm = getGlobalManager();
		
		if ( gm != null ){
			
			List	downloads = gm.getDownloadManagers();
			
			long now	= SystemTime.getCurrentTime();

			for (int i=0;i<downloads.size();i++){
				
				DownloadManager	dm = (DownloadManager)downloads.get(i);
				
				Long	last_announce_l = (Long)dm.getUserData( DM_ANNOUNCE_KEY );
				
				long	last_announce	= last_announce_l==null?create_time:last_announce_l.longValue();
				
				TRTrackerAnnouncer an = dm.getTrackerClient();
				
				if ( an != null ){
					
					TRTrackerAnnouncerResponse last_announce_response = an.getLastResponse();
					
					if ( 	now - last_announce > 15*60*1000 ||
							last_announce_response == null ||
							last_announce_response.getStatus() == TRTrackerAnnouncerResponse.ST_OFFLINE ||
							force ){
	
						dm.setUserData( DM_ANNOUNCE_KEY, new Long( now ));
						
						Logger.log(	new LogEvent(LOGID, "    updating tracker for " + dm.getDisplayName()));
	
						dm.requestTrackerAnnounce( true );
					}
				}
			}
		}
		
	    PluginInterface dht_tracker_pi = getPluginManager().getPluginInterfaceByClass( DHTTrackerPlugin.class );

	    if ( dht_tracker_pi != null ){
	    	
	    	((DHTTrackerPlugin)dht_tracker_pi.getPlugin()).announceAll();
	    }
	}
	
	public LocaleUtil
	getLocaleUtil()
	{
		return( LocaleUtil.getSingleton());
	}
	
	public void
	start()
	
		throws AzureusCoreException
	{
		AEThread2.setOurThread();
		
		try{
			this_mon.enter();
		
			if ( started ){
				
				throw( new AzureusCoreException( "Core: already started" ));
			}
			
			if ( stopped ){
				
				throw( new AzureusCoreException( "Core: already stopped" ));
			}
			
			started	= true;
			
		}finally{
			
			this_mon.exit();
		}
		
		// If a user sets this property, it is an alias for the following settings.
		if ("1".equals(System.getProperty("azureus.safemode"))) {
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Safe mode enabled"));
			
			Constants.isSafeMode = true;
			System.setProperty("azureus.loadplugins", "0");
			System.setProperty("azureus.disabledownloads", "1");
			System.setProperty("azureus.skipSWTcheck", "1");
			
			// Not using localised text - not sure it's safe to this early.
			Logger.log(new LogAlert(LogAlert.UNREPEATABLE, LogEvent.LT_WARNING,
				"You are running " + Constants.APP_NAME + " in safe mode - you " +
					"can change your configuration, but any downloads added will " +
					"not be remembered when you close " + Constants.APP_NAME + "."
			));
		}
		
	   /**
	    * test to see if UI plays nicely with a really slow initialization
	    */
	   String sDelayCore = System.getProperty("delay.core", null);
	   if (sDelayCore != null) {
	  	 try {
	  		 long delayCore = Long.parseLong(sDelayCore);
	  		 Thread.sleep(delayCore);
	  	 } catch (Exception e) {
	  		 e.printStackTrace();
	  	 }
	   }


		// run plugin loading in parallel to the global manager loading
		AEThread2 pluginload = new AEThread2("PluginLoader",true)
		{
			public void run() {
				if (Logger.isEnabled())
					Logger.log(new LogEvent(LOGID, "Loading of Plugins starts"));
				pi.loadPlugins(AzureusCoreImpl.this, false, !"0".equals(System.getProperty("azureus.loadplugins")), true, true);
				if (Logger.isEnabled())
					Logger.log(new LogEvent(LOGID, "Loading of Plugins complete"));
			}
		};
		
		if (LOAD_PLUGINS_IN_OTHER_THREAD) {
			pluginload.start();
		}
		else {
			pluginload.run();
		}


		


		// Disable async loading of existing torrents, because there are many things
		// (like hosting) that require all the torrents to be loaded.  While we
		// can write code for each of these cases to wait until the torrents are
		// loaded, it's a pretty big job to find them all and fix all their quirks.
		// Too big of a job for this late in the release stage.
		// Other example is the tracker plugin that is coded in a way where it must 
		// always publish a complete rss feed
		
		global_manager = GlobalManagerFactory.create(
				this,
				new GlobalMangerProgressListener()
				{
					public void 
					reportCurrentTask(
						String currentTask )
					{
						initialisation_op.reportCurrentTask( currentTask );
					}
					  
					public void 
					reportPercent(
						int percent )
					{
						initialisation_op.reportPercent( percent );
					}
				}, 0);
		
		if (stopped) {
			System.err.println("Core stopped while starting");
			return;
		}

		// wait until plugin loading is done
		if (LOAD_PLUGINS_IN_OTHER_THREAD) {
			pluginload.join();
		}

		if (stopped) {
			System.err.println("Core stopped while starting");
			return;
		}
		
		VuzeFileHandler.getSingleton().addProcessor(
				new VuzeFileProcessor()
				{
					public void
					process(
						VuzeFile[]		files,
						int				expected_types )
					{
						for (int i=0;i<files.length;i++){
							
							VuzeFile	vf = files[i];
							
							VuzeFileComponent[] comps = vf.getComponents();
							
							for (int j=0;j<comps.length;j++){
								
								VuzeFileComponent comp = comps[j];
								
								int	comp_type = comp.getType();
								
								if ( comp_type == VuzeFileComponent.COMP_TYPE_ADD_TORRENT ){
									
									PluginInterface default_pi = getPluginManager().getDefaultPluginInterface();
									
									Map map = comp.getContent();
									
									try{
										Torrent torrent;

										String url = MapUtils.getMapString(map, "torrent_url", null );
																				
										if ( url != null ){
											
											TorrentDownloader dl = default_pi.getTorrentManager().getURLDownloader( new URL( url ));
											
											torrent = dl.download();
																						
										}else{
											
											String tf = MapUtils.getMapString(map, "torrent_file", null );

											if ( tf != null ){
												
												File file = new File( tf );
												
												if ( !file.canRead() || file.isDirectory()){
													
													throw( new Exception( "torrent_file '" + tf + "' is invalid" ));
												}
												
												torrent = default_pi.getTorrentManager().createFromBEncodedFile(file);
																								
											}else{
												
												throw( new Exception( "torrent_url or torrent_file must be specified" ));
											}
										}
										
										File	dest = null;
										
										String save_folder = MapUtils.getMapString(map, "save_folder", null );
										
										if ( save_folder != null ){
											
											dest = new File( save_folder, torrent.getName());
											
										}else{
											
											String save_file = MapUtils.getMapString(map, "save_file", null );

											if ( save_file != null ){
												
												dest = new File( save_file );
											}
										}

										if ( dest != null ){
											
											dest.getParentFile().mkdirs();
										}
										
										default_pi.getDownloadManager().addDownload( torrent, null, dest );

									}catch( Throwable e ){
										
										Debug.out( e );
									}
									
									comp.setProcessed();
								}
							}
						}
					}
				});
		
		
		
		triggerLifeCycleComponentCreated(global_manager);

		pi.initialisePlugins();

		if (stopped) {
			System.err.println("Core stopped while starting");
			return;
		}
		
		if (Logger.isEnabled())
			Logger.log(new LogEvent(LOGID, "Initializing Plugins complete"));

		try{
			PluginInterface dht_pi 	= getPluginManager().getPluginInterfaceByClass( DHTPlugin.class );

			if ( dht_pi != null ){
				
				dht_pi.addEventListener(
					new PluginEventListener()
					{
						private boolean	first_dht = true;
						
						public void
						handleEvent(
							PluginEvent	ev )
						{
							if ( ev.getType() == DHTPlugin.EVENT_DHT_AVAILABLE ){
								
								if ( first_dht ){
									
									first_dht	= false;
								
									DHT 	dht = (DHT)ev.getValue();
									
									dht.addListener(
										new DHTListener()
										{
											public void 
											speedTesterAvailable(
												DHTSpeedTester tester ) 
											{
												if ( speed_manager != null ){
												
													speed_manager.setSpeedTester( tester );
												}
											}
										});
																
									global_manager.addListener(
											new GlobalManagerAdapter()
											{
												public void 
												seedingStatusChanged( 
													boolean seeding_only_mode,
													boolean	b )
												{
													checkConfig();
												}
											});
									
									COConfigurationManager.addAndFireParameterListeners(
										new String[]{	TransferSpeedValidator.AUTO_UPLOAD_ENABLED_CONFIGKEY,
														TransferSpeedValidator.AUTO_UPLOAD_SEEDING_ENABLED_CONFIGKEY },
										new ParameterListener()
										{
											public void 
											parameterChanged(
												String parameterName )
											{
												checkConfig();
											}
										});
										
								}
							}
						}
						
						protected void
						checkConfig()
						{
							if ( speed_manager != null ){
							
								speed_manager.setEnabled( TransferSpeedValidator.isAutoSpeedActive(global_manager) );
							}
						}
						
					});
			}
		}catch( Throwable e ){
		}
		
	   if ( COConfigurationManager.getBooleanParameter( "Resume Downloads On Start" )){
	   
		   global_manager.resumeDownloads();
	   }
	    
	   VersionCheckClient.getSingleton().initialise();

	   instance_manager.initialize();

	   NetworkManager.getSingleton().initialize(this); 
         
	   SpeedLimitHandler.getSingleton( this );
	   
	   Runtime.getRuntime().addShutdownHook( new AEThread("Shutdown Hook") {
	     public void runSupport() {
			Logger.log(new LogEvent(LOGID, "Shutdown hook triggered" ));
			AzureusCoreImpl.this.stop();
	     }
	   });	
	   	  

	   DelayedTask delayed_task = 
	   		UtilitiesImpl.addDelayedTask(
	   			"Core", 
	   			new Runnable()
	   			{
	   				public void
	   				run()
	   				{
	   					new AEThread2( "core:delayTask", true )
	   					{
	   						public void
	   						run()
	   						{				
			   					AEDiagnostics.checkDumpsAndNatives();
		
			   					COConfigurationManager.setParameter( "diags.enable.pending.writes", true );
			   					
			   					AEDiagnostics.flushPendingLogs();
			   					
			   					NetworkAdmin na = NetworkAdmin.getSingleton();
		
			   					na.runInitialChecks(AzureusCoreImpl.this);
		
			   					na.addPropertyChangeListener(
			   							new NetworkAdminPropertyChangeListener()
			   							{
			   								private String	last_as;
		
			   								public void
			   								propertyChanged(
			   										String		property )
			   								{
			   									NetworkAdmin na = NetworkAdmin.getSingleton();
		
			   									if ( property.equals( NetworkAdmin.PR_NETWORK_INTERFACES )){
		
			   										boolean	found_usable = false;
		
			   										NetworkAdminNetworkInterface[] intf = na.getInterfaces();
		
			   										for (int i=0;i<intf.length;i++){
		
			   											NetworkAdminNetworkInterfaceAddress[] addresses = intf[i].getAddresses();
		
			   											for (int j=0;j<addresses.length;j++){
		
			   												if ( !addresses[j].isLoopback()){
		
			   													found_usable = true;
			   												}
			   											}
			   										}
		
			   										// ignore event if nothing usable
		
			   										if ( !found_usable ){
		
			   											return;
			   										}
		
			   										Logger.log(	new LogEvent(LOGID, "Network interfaces have changed (new=" + na.getNetworkInterfacesAsString() + ")"));
		
			   										announceAll( false );
		
			   									}else if ( property.equals( NetworkAdmin.PR_AS )){
		
			   										String	as = na.getCurrentASN().getAS();
		
			   										if ( last_as == null ){
		
			   											last_as = as;
		
			   										}else if ( !as.equals( last_as )){
		
			   											Logger.log(	new LogEvent(LOGID, "AS has changed (new=" + as + ")" ));
		
			   											last_as = as;
		
			   											announceAll( false );
			   										}
			   									}
			   								}
			   							});
			   					
			   					setupSleepAndCloseActions();
	   						}
	   					}.start();
	   				}
	   			});

	   delayed_task.queue();

			if (stopped) {
				System.err.println("Core stopped while starting");
				return;
			}

	   PairingManagerFactory.getSingleton();
	   
	   AzureusCoreRunningListener[] runningListeners;
	   mon_coreRunningListeners.enter();
	   try {
	  	 if (coreRunningListeners == null) {
	  		 runningListeners = new AzureusCoreRunningListener[0];
	  	 } else {
	  		 runningListeners = coreRunningListeners.toArray(new AzureusCoreRunningListener[0]);
	  		 coreRunningListeners = null;
	  	 }
	  	 
	   } finally {
	  	 mon_coreRunningListeners.exit();
	   }

		// Trigger Listeners now that core is started
		new AEThread2("Plugin Init Complete", false )
		{
			public void
			run()
			{
				try{
					PlatformManagerFactory.getPlatformManager().startup( AzureusCoreImpl.this );
					
				}catch( Throwable e ){
					
					Debug.out( "PlatformManager: init failed", e );
				}
				
				Iterator	it = lifecycle_listeners.iterator();
				
				while( it.hasNext()){
					
					try{
						AzureusCoreLifecycleListener listener = (AzureusCoreLifecycleListener)it.next();
						
						if ( !listener.requiresPluginInitCompleteBeforeStartedEvent()){
						
							listener.started( AzureusCoreImpl.this );
						}
					}catch( Throwable e ){
						
						Debug.printStackTrace(e);
					}
				}
				
				pi.initialisationComplete();
				
				it = lifecycle_listeners.iterator();
				
				while( it.hasNext()){
					
					try{
						AzureusCoreLifecycleListener listener = (AzureusCoreLifecycleListener)it.next();
						
						if ( listener.requiresPluginInitCompleteBeforeStartedEvent()){
						
							listener.started( AzureusCoreImpl.this );
						}				
					}catch( Throwable e ){
						
						Debug.printStackTrace(e);
					}
				}
			}
		}.start();

		// Typicially there are many runningListeners, most with quick execution, and 
		// a few longer ones.  Let 3 run at a time, queue the rest.  Without
		// a ThreadPool, the slow ones would delay the startup processes that run
		// after this start() method
		ThreadPool tp = new ThreadPool("Trigger AzureusCoreRunning Listeners", 3);
		for (final AzureusCoreRunningListener l : runningListeners) {
			try {
				tp.run(new AERunnable() {
					public void runSupport() {
						l.azureusCoreRunning(AzureusCoreImpl.this);
					}
				});
			} catch (Throwable t) {
				Debug.out(t);
			}
		}
		
		// Debug.out("Core Start Complete");
	}

	public boolean 
	isInitThread() 
	{
		return( AEThread2.isOurThread( Thread.currentThread()));
	}
	
	public boolean
	isStarted()
	{
	   mon_coreRunningListeners.enter();
	   try {
	  	 return( started && coreRunningListeners == null );
	   } finally {
	  	 mon_coreRunningListeners.exit();
	   }
	}
	
	public void 
	triggerLifeCycleComponentCreated(
		AzureusCoreComponent component )
	{
		Iterator it = lifecycle_listeners.iterator();
		
		while( it.hasNext()){

			try{
				((AzureusCoreLifecycleListener)it.next()).componentCreated(this, component);
				
			}catch( Throwable e ){
				
				Debug.printStackTrace(e);
			}
		}
	}
	
	private void
	runNonDaemon(
		final Runnable	r )
	
		throws AzureusCoreException
	{
		if ( !Thread.currentThread().isDaemon()){
			
			r.run();
			
		}else{
			
			final AESemaphore	sem = new AESemaphore( "AzureusCore:runNonDaemon" );
			
			final Throwable[]	error = {null};
			
			new AEThread2( "AzureusCore:runNonDaemon", false )
			{
				public void
				run()
				{
					try{
			
						r.run();
						
					}catch( Throwable e ){
						
						error[0]	= e;
						
					}finally{
						
						sem.release();
					}
				}
			}.start();
			
			sem.reserve();
			
			if ( error[0] != null ){
	
				if ( error[0] instanceof AzureusCoreException ){
					
					throw((AzureusCoreException)error[0]);
					
				}else{
					
					throw( new AzureusCoreException( "Operation failed", error[0] ));
				}			
			}
		}
	}
	
	public void
	stop()
	
		throws AzureusCoreException
	{
		runNonDaemon(new AERunnable() {
			public void runSupport() {
				if (Logger.isEnabled())
					Logger.log(new LogEvent(LOGID, "Stop operation starts"));

				stopSupport(true);
			}
		});
	}
	
	private void
	stopSupport(
		boolean		apply_updates )
	
		throws AzureusCoreException
	{
		AEDiagnostics.flushPendingLogs();
		
		boolean	wait_and_return = false;
		
		try{
			this_mon.enter();
		
			if ( stopped ){
				
					// ensure config is saved as there may be pending changes to persist and we've got here
					// via a shutdown hook
									
				COConfigurationManager.save();
				
				wait_and_return = true;
				
			}else{
			
				stopped	= true;
				
				if ( !started ){
					
					Logger.log(new LogEvent(LOGID, "Core not started"));
					
						// might have been marked dirty due to core being created to allow functions to be used but never started...
					
					if ( AEDiagnostics.isDirty()){
						
						AEDiagnostics.markClean();
					}
					
					stopping_sem.releaseForever();
					
					return;
				}		
			}
		}finally{
			
			this_mon.exit();
		}
		
		if ( wait_and_return ){
			
			Logger.log(new LogEvent(LOGID, "Waiting for stop to complete"));
			
			stopping_sem.reserve();
			
			return;
		}
		
		SimpleTimer.addEvent(
			"ShutFail",
			SystemTime.getOffsetTime( 30*1000 ),
			new TimerEventPerformer()
			{
				boolean	die_die_die;
				
				public void 
				perform(
					TimerEvent event )
				{
					AEDiagnostics.dumpThreads();
					
					if ( die_die_die ){
					
						Debug.out( "Shutdown blocked, force exiting" );
						
						stopping_sem.releaseForever();
						
						SESecurityManager.exitVM(0);
					}
					
					die_die_die = true;
					
					SimpleTimer.addEvent( "ShutFail", SystemTime.getOffsetTime( 30*1000 ), this );
				}
			});
				
		List	sync_listeners 	= new ArrayList();
		List	async_listeners	= new ArrayList();
		
		Iterator it = lifecycle_listeners.iterator();
			
		while( it.hasNext()){
			AzureusCoreLifecycleListener	l = (AzureusCoreLifecycleListener)it.next();
			
			if ( l.syncInvokeRequired()){
				sync_listeners.add( l );
			}else{
				async_listeners.add( l );
			}
		}
		
		try{
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Invoking synchronous 'stopping' listeners"));

			for (int i=0;i<sync_listeners.size();i++){		
				try{
					((AzureusCoreLifecycleListener)sync_listeners.get(i)).stopping( this );
					
				}catch( Throwable e ){
					
					Debug.printStackTrace(e);
				}
			}
			
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Invoking asynchronous 'stopping' listeners"));

				// in case something hangs during listener notification (e.g. version check server is down
				// and the instance manager tries to obtain external address) we limit overall dispatch
				// time to 10 seconds
			
			ListenerManager.dispatchWithTimeout(
					async_listeners,
					new ListenerManagerDispatcher()
					{
						public void
						dispatch(
							Object		listener,
							int			type,
							Object		value )
						{
							((AzureusCoreLifecycleListener)listener).stopping( AzureusCoreImpl.this );
						}
					},
					10*1000 );
	
			
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Stopping global manager"));

			if (global_manager != null) {
				global_manager.stopGlobalManager();
			}
			
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Invoking synchronous 'stopped' listeners"));

			for (int i=0;i<sync_listeners.size();i++){		
				try{
					((AzureusCoreLifecycleListener)sync_listeners.get(i)).stopped( this );
					
				}catch( Throwable e ){
					
					Debug.printStackTrace(e);
				}
			}
			
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Invoking asynchronous 'stopped' listeners"));

			ListenerManager.dispatchWithTimeout(
					async_listeners,
					new ListenerManagerDispatcher()
					{
						public void
						dispatch(
							Object		listener,
							int			type,
							Object		value )
						{
							((AzureusCoreLifecycleListener)listener).stopped( AzureusCoreImpl.this );
						}
					},
					10*1000 );
			
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Waiting for quiescence"));

			NonDaemonTaskRunner.waitUntilIdle();
			
				// shut down diags - this marks the shutdown as tidy and saves the config
			
			AEDiagnostics.markClean();
	
			if (Logger.isEnabled())
				Logger.log(new LogEvent(LOGID, "Stop operation completes"));
	
				// if any installers exist then we need to closedown via the updater
			
			if ( 	apply_updates && 
					getPluginManager().getDefaultPluginInterface().getUpdateManager().getInstallers().length > 0 ){
				
				AzureusRestarterFactory.create( this ).restart( true );
			}
			
			try {
				Class c = Class.forName( "sun.awt.AWTAutoShutdown" );
	      
				if (c != null) {
					c.getMethod( "notifyToolkitThreadFree", new Class[]{} ).invoke( null, new Object[]{} );
				}
			} catch (Throwable t) {
			}
			
			if ( ca_shutdown_computer_after_stop ){
				
				if ( apply_updates ){
					
						// best we can do here is wait a while for updates to be applied
					try{
						Thread.sleep( 10*1000 );
						
					}catch( Throwable e ){
						
					}
				}
				
				try{
					PlatformManagerFactory.getPlatformManager().shutdown( PlatformManager.SD_SHUTDOWN );
					
				}catch( Throwable e ){
					
					Debug.out( "PlatformManager: shutdown failed", e );
				}
			}
		
			try{
				ThreadGroup	tg = Thread.currentThread().getThreadGroup();
				
				while( tg.getParent() != null ){
					
					tg = tg.getParent();
				}
				
				Thread[]	threads = new Thread[tg.activeCount()+1024];
				
				tg.enumerate( threads, true );
				
				boolean	bad_found = false;
				
				for (int i=0;i<threads.length;i++){
					
					Thread	t = threads[i];
										
					if ( t != null && t.isAlive() && t != Thread.currentThread() && !t.isDaemon() && !AEThread2.isOurThread( t )){
					
						bad_found = true;
						
						break;
					}
				}
				
				if ( bad_found ){
										
					new AEThread2( "VMKiller", true )
					{
						public void
						run()
						{
							try{
								Thread.sleep(10*1000);
								
								ThreadGroup	tg = Thread.currentThread().getThreadGroup();
								
								Thread[]	threads = new Thread[tg.activeCount()+1024];
								
								tg.enumerate( threads, true );
								
								String	bad_found = "";
								
								for (int i=0;i<threads.length;i++){
									
									Thread	t = threads[i];
																		
									if ( t != null && t.isAlive() && !t.isDaemon() && !AEThread2.isOurThread( t )){
									
										String	details = t.getName();
										
										StackTraceElement[] trace = t.getStackTrace();
										
										if ( trace.length > 0 ){
											
											details += "[";
											
											for ( int j=0;j<trace.length;j++ ){
											
												details += (j==0?"":",") + trace[j];
											}
											
											details += "]";
										}
										
										bad_found += (bad_found.length()==0?"":", ") + details;
									}
								}
								
								Debug.out( "Non-daemon thread(s) found: '" + bad_found + "' - force closing VM" );
								
								SESecurityManager.exitVM(0);
								
							}catch( Throwable e ){
								
							}
						}
					}.start();
	
				}
			}catch( Throwable e ){
			}
			
	
		}finally{
			
			stopping_sem.releaseForever();
		}
	}
	
	
	public void
	requestStop()
	
		throws AzureusCoreException
	{
		if (stopped)
			return;

		runNonDaemon(new AERunnable() {
			public void runSupport() {

				Iterator it = lifecycle_listeners.iterator();
				
				while( it.hasNext()){

					if (!((AzureusCoreLifecycleListener)it.next())
							.stopRequested(AzureusCoreImpl.this)) {
						if (Logger.isEnabled())
							Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
									"Request to stop the core has been denied"));

						return;
					}
				}

				stop();
			}
		});
	}
	
	public void
	restart()
	
		throws AzureusCoreException
	{
		runNonDaemon(new AERunnable() {
			public void runSupport() {
				if (Logger.isEnabled())
					Logger.log(new LogEvent(LOGID, "Restart operation starts"));

				checkRestartSupported();

				restarting = true;
				
				stopSupport(false);

				if (Logger.isEnabled())
					Logger.log(new LogEvent(LOGID, "Restart operation: stop complete,"
							+ "restart initiated"));

				AzureusRestarterFactory.create(AzureusCoreImpl.this).restart(false);
			}
		});
	}
	
	public void
	requestRestart()
	
		throws AzureusCoreException
	{
		runNonDaemon(new AERunnable() {
            public void runSupport() {
                checkRestartSupported();

                Iterator it = lifecycle_listeners.iterator();
                
                while( it.hasNext()){
                    AzureusCoreLifecycleListener l = (AzureusCoreLifecycleListener)it.next();

                    if (!l.restartRequested(AzureusCoreImpl.this)) {

                        if (Logger.isEnabled())
                            Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING,
                                    "Request to restart the core"
                                            + " has been denied"));

                        return;
                    }
                }

                restart();
            }
        });
	}
	
	public boolean
	isRestarting()
	{
		return( restarting );
	}
	
	public void
	checkRestartSupported()
	
		throws AzureusCoreException
	{
		if ( getPluginManager().getPluginInterfaceByClass( "org.gudy.azureus2.update.UpdaterPatcher") == null ){
			Logger.log(new LogAlert(LogAlert.REPEATABLE, LogAlert.AT_ERROR,
					"Can't restart without the 'azupdater' plugin installed"));
			
			throw( new  AzureusCoreException("Can't restart without the 'azupdater' plugin installed"));
		}
	}
	
	public void
	saveState()
	{
		GlobalManager	gm = global_manager;
		
		if ( gm != null ){
			
			gm.saveState();
		}
		
		COConfigurationManager.save();
	}
	
	public GlobalManager
	getGlobalManager()
	
		throws AzureusCoreException
	{
		if ( global_manager == null ){
			
			throw( new AzureusCoreException( "Core not running" ));
		}
		
		return( global_manager );
	}
	
	public TRHost
	getTrackerHost()
	
		throws AzureusCoreException
	{	
		return( TRHostFactory.getSingleton());
	}
	
	public PluginManagerDefaults
	getPluginManagerDefaults()
	
		throws AzureusCoreException
	{
		return( PluginManager.getDefaults());
	}
	
	public PluginManager
	getPluginManager()
	
		throws AzureusCoreException
	{
			// don't test for running here, the restart process calls this after terminating the core...
		
		return( PluginInitializer.getDefaultInterface().getPluginManager());
	}
	
	public IpFilterManager
	getIpFilterManager()
	
		throws AzureusCoreException
	{
		return( IpFilterManagerFactory.getSingleton());
	}
	
	public AZInstanceManager
	getInstanceManager()
	{
		return( instance_manager );
	}
	
	public SpeedManager
	getSpeedManager()
	{
		return( speed_manager );
	}
	
	public CryptoManager
	getCryptoManager()
	{
		return( crypto_manager );
	}
	
	public NATTraverser
	getNATTraverser()
	{
		return( nat_traverser );
	}
	
	private void
	setupSleepAndCloseActions()
	{
		if ( PlatformManagerFactory.getPlatformManager().hasCapability( PlatformManagerCapabilities.PreventComputerSleep )){
			
			COConfigurationManager.addAndFireParameterListeners(
				new String[]{
					"Prevent Sleep Downloading",
					"Prevent Sleep FP Seeding",
				},
				new ParameterListener()
				{
					private TimerEventPeriodic timer_event;

					public void 
					parameterChanged(
						String parameterName )
					{
						synchronized( this ){
					
							boolean dl = COConfigurationManager.getBooleanParameter( "Prevent Sleep Downloading" );
							boolean se = COConfigurationManager.getBooleanParameter( "Prevent Sleep FP Seeding" );
							
							boolean	active = dl || se;
								
							try{
								setPreventComputerSleep( PlatformManagerFactory.getPlatformManager(), active, "config change" );
								
							}catch( Throwable e ){
								
								Debug.out( e );
							}
							
							
							if ( !active ){
								
								if ( timer_event != null ){
									
									timer_event.cancel();
									
									timer_event = null;
								}
							}else{
							
								if ( timer_event == null ){
									
									timer_event = 
										SimpleTimer.addPeriodicEvent(
												"core:sleepAct",
												2*60*1000,
												new TimerEventPerformer()
												{
													public void 
													perform(
														TimerEvent event )
													{
														if ( !stopped ){
														
															checkSleepActions();
														}
													}
												});
								}
							}
						}
					}
				});
		}
		
		COConfigurationManager.addAndFireParameterListeners(
			new String[]{
					"On Downloading Complete Do",
					"On Seeding Complete Do"
			},
			new ParameterListener()
			{
				private TimerEventPeriodic timer_event;
				
				public void 
				parameterChanged(
					String parameterName )
				{
					String	dl_act = COConfigurationManager.getStringParameter( "On Downloading Complete Do" );
					String	se_act = COConfigurationManager.getStringParameter( "On Seeding Complete Do" );
					
					synchronized( this ){
						
						boolean	dl_nothing 	= dl_act.equals( "Nothing" );
						boolean se_nothing	= se_act.equals( "Nothing" );
						
						if ( dl_nothing ){
							
							ca_last_time_downloading	= -1;
						}
						
						if ( se_nothing ){
							
							ca_last_time_seeding	= -1;
						}
				
						if ( dl_nothing && se_nothing ){
							
							if ( timer_event != null ){
								
								timer_event.cancel();
								
								timer_event = null;
							}
						}else{
						
							if ( timer_event == null ){
								
								timer_event = 
									SimpleTimer.addPeriodicEvent(
											"core:closeAct",
											30*1000,
											new TimerEventPerformer()
											{
												public void 
												perform(
													TimerEvent event )
												{
													if ( !stopped ){
													
														checkCloseActions();
													}
												}
											});
							}
							
							checkCloseActions();
						}
					}
				}
			});
	}

	protected void
	checkSleepActions()
	{
		boolean ps_downloading 	= COConfigurationManager.getBooleanParameter( "Prevent Sleep Downloading" );
		boolean ps_fp_seed	 	= COConfigurationManager.getBooleanParameter( "Prevent Sleep FP Seeding" );

		String	declining_subsystems = "";
		
		for ( PowerManagementListener l: power_listeners ){
			
			try{
				if ( !l.requestPowerStateChange( PowerManagementListener.ST_SLEEP, null )){
					
					declining_subsystems += (declining_subsystems.length()==0?"":",") + l.getPowerName();
				}
				
			}catch( Throwable e ){
				
				Debug.out( e );
			}
		}
		
		if ( declining_subsystems.length() == 0 && !( ps_downloading || ps_fp_seed )){

			return;
		}

		PlatformManager platform = PlatformManagerFactory.getPlatformManager();

		boolean	prevent_sleep 	= false;
		String	prevent_reason	= null;

		if ( declining_subsystems.length() > 0 ){
			
			prevent_sleep 	= true;
			prevent_reason 	= "subsystems declined sleep: " +  declining_subsystems;
			
		}else{
			
			List<DownloadManager> managers = getGlobalManager().getDownloadManagers();

			for ( DownloadManager manager: managers ){
	
				int state = manager.getState();
				
				if ( 	state == DownloadManager.STATE_FINISHING ||
						manager.getDownloadState().getFlag( DownloadManagerState.FLAG_METADATA_DOWNLOAD )){
	
					if ( ps_downloading ){
	
						prevent_sleep 	= true;
						prevent_reason	= "active downloads";
						
						break;
					}
	
				}else{
	
					if ( state == DownloadManager.STATE_DOWNLOADING ){
	
						PEPeerManager pm = manager.getPeerManager();
	
						if ( pm != null ){
	
							if ( pm.hasDownloadablePiece()){
	
								if ( ps_downloading ){
	
									prevent_sleep 	= true;
									prevent_reason	= "active downloads";
	
									break;
								}
							}else{
	
									// its effectively seeding, change so logic about recheck obeyed below
	
								state = DownloadManager.STATE_SEEDING;
							}
						}
					}
	
					if ( state == DownloadManager.STATE_SEEDING && ps_fp_seed ){
	
						DiskManager disk_manager = manager.getDiskManager();
	
						if ( disk_manager != null && disk_manager.getCompleteRecheckStatus() != -1 ){
	
								// wait until recheck is complete before we mark as downloading-complete
	
							if ( ps_downloading ){
	
								prevent_sleep 	= true;
								prevent_reason	= "active downloads";
	
								break;
							}
	
						}else{
	
							try{
								DefaultRankCalculator calc = StartStopRulesDefaultPlugin.getRankCalculator( PluginCoreUtils.wrap( manager ));
	
								if ( calc.getCachedIsFP()){
									
									prevent_sleep 	= true;
									prevent_reason	= "first-priority seeding";
	
									break;
								}
							}catch( Throwable e ){
	
							}
						}
					}
				}
			}
		}
		
		if ( prevent_sleep != platform.getPreventComputerSleep()){
			
			if ( prevent_sleep ){
				
				prevent_sleep_remove_trigger = false;
				
			}else{
				
				if ( !prevent_sleep_remove_trigger ){
					
					prevent_sleep_remove_trigger = true;
					
					return;
				}
			}
			
			if ( prevent_reason == null ){
				
				if ( ps_downloading && ps_fp_seed ){
					
					prevent_reason = "no active downloads or first-priority seeding";
					
				}else if ( ps_downloading ){
					
					prevent_reason = "no active downloads";
					
				}else{
					
					prevent_reason = "no active first-priority seeding";
				}
			}

			setPreventComputerSleep( platform, prevent_sleep, prevent_reason );
		}
	}
	
	private void
	setPreventComputerSleep(
		PlatformManager		platform,
		boolean				prevent_sleep,
		String				prevent_reason )
	{
		for ( PowerManagementListener l: power_listeners ){
			
			try{
				l.informPowerStateChange( PowerManagementListener.ST_SLEEP, new Object[]{ prevent_sleep, prevent_reason });
				
			}catch( Throwable e ){
				
				Debug.out( e );
			}
		}
		
		Logger.log( new LogEvent(LOGID, "Computer sleep prevented state changed to '" + prevent_sleep + "' due to " + prevent_reason ));	

		try{
			platform.setPreventComputerSleep( prevent_sleep );
			
		}catch( Throwable e ){
			
			Debug.out( e );
		}
	}
	
	protected void
	checkCloseActions()
	{
		List<DownloadManager> managers = getGlobalManager().getDownloadManagers();
		
		boolean	is_downloading 	= false;
		boolean is_seeding		= false;
		
		for ( DownloadManager manager: managers ){
			
			if ( manager.isPaused()){
			
					// if anything's paused we don't want to trigger any actions as something transient (e.g. speed test) is going on
				
				return;
			}
			
			if ( manager.getDownloadState().getFlag( DownloadManagerState.FLAG_METADATA_DOWNLOAD )){
				
					// we want this to complete before considering actions
				
				return;
			}
			
			if ( manager.getDownloadState().getFlag( DownloadManagerState.FLAG_LOW_NOISE )){
				
				continue;	// don't count these as interesting as the user isn't directly interested in them
			}
			
			int state = manager.getState();
			
			if ( state == DownloadManager.STATE_FINISHING ){

				is_downloading = true;
				
			}else{
			
				if ( state == DownloadManager.STATE_DOWNLOADING ){
					
					PEPeerManager pm = manager.getPeerManager();
					
					if ( pm != null ){
						
						if ( pm.hasDownloadablePiece()){
							
							is_downloading = true;
							
						}else{
				
								// its effectively seeding, change so logic about recheck obeyed below
							
							state = DownloadManager.STATE_SEEDING;
						}
					}
				}
				
				if ( state == DownloadManager.STATE_SEEDING ){
				
					DiskManager disk_manager = manager.getDiskManager();
	
					if ( disk_manager != null && disk_manager.getCompleteRecheckStatus() != -1 ){
					
							// wait until recheck is complete before we mark as downloading-complete
						
						is_downloading	= true;
						
					}else{
						
						is_seeding		= true;
					}
				}
			}
		}
		
		long	now = SystemTime.getMonotonousTime();
		
		if ( is_downloading ){
			
			ca_last_time_downloading 	= now;
			ca_last_time_seeding		= -1;
			
		}else if ( is_seeding ){
			
			ca_last_time_seeding = now;
		}
		
		String	dl_act = COConfigurationManager.getStringParameter( "On Downloading Complete Do" );
		
		if ( !dl_act.equals( "Nothing" )){
		
			if ( ca_last_time_downloading >= 0 && !is_downloading && now - ca_last_time_downloading >= 30*1000 ){
				
				executeCloseAction( true, true, dl_act, null );
			}
		}
		
		String	se_act = COConfigurationManager.getStringParameter( "On Seeding Complete Do" );

		if ( !se_act.equals( "Nothing" )){
			
			if ( ca_last_time_seeding >= 0 && !is_seeding && now - ca_last_time_seeding >= 30*1000 ){
				
				executeCloseAction( true, false, se_act, null );
			}
		}
	}
	
	public void
	executeCloseAction(
		String		action,
		String		reason )
	{
		executeCloseAction( false, false, action, reason );
	}
	
	private void
	executeCloseAction(
		final boolean	obey_reset,
		final boolean	download_trigger,
		final String	action,
		final String	reason )
	{
			// prevent retriggering on resume from standby
		
		ca_last_time_downloading	= -1;
		ca_last_time_seeding		= -1;
		
		boolean reset = obey_reset && COConfigurationManager.getBooleanParameter( "Stop Triggers Auto Reset" );
		
		if ( reset ){
			
			if ( download_trigger ){
			
				COConfigurationManager.setParameter( "On Downloading Complete Do", "Nothing" );
				
			}else{
				
				COConfigurationManager.setParameter( "On Seeding Complete Do", "Nothing" );
			}
		}
		
		String type_str		= reason==null?MessageText.getString( "core.shutdown." + (download_trigger?"dl":"se")):reason;
		String action_str 	= MessageText.getString( "ConfigView.label.stop." + action );
				
		String message = 
			MessageText.getString( 
				"core.shutdown.alert",
				new String[]{
					action_str,
					type_str,
				});
		
		UIFunctions ui_functions = UIFunctionsManager.getUIFunctions();
		
		if ( ui_functions != null ){
		
			ui_functions.forceNotify( UIFunctions.STATUSICON_NONE, null, message, null, new Object[0], -1 );
		}
		
		Logger.log( 
			new LogAlert( 
				LogAlert.UNREPEATABLE, 
				LogEvent.LT_INFORMATION,
				message ));

		new DelayedEvent(
			"CoreShutdown",
			10*1000,
			new AERunnable()
			{
				public void
				runSupport()
				{
					Logger.log( new LogEvent(LOGID, "Executing close action '" + action + "' due to " + (download_trigger?"downloading":"seeding") + " completion" ));					
					
						// quit vuze -> quit
						// shutdown computer -> quit vuze + shutdown
						// sleep/hibernate = announceAll and then sleep/hibernate with Vuze still running
					
					if ( action.equals( CA_QUIT_VUZE )){
						
						requestStop();
					
					}else if ( action.equals( CA_SLEEP ) || action.equals( CA_HIBERNATE )){

						announceAll( true );
						
						try{
							PlatformManagerFactory.getPlatformManager().shutdown( 
									action.equals( CA_SLEEP )?PlatformManager.SD_SLEEP:PlatformManager.SD_HIBERNATE );
							
						}catch( Throwable e ){
							
							Debug.out( "PlatformManager: shutdown failed", e );
						}
						
					}else if ( action.equals( CA_SHUTDOWN )){

						ca_shutdown_computer_after_stop = true;
						
						requestStop();
						
					}else if ( action.startsWith( "RunScript" )){
						
						String script;
						
						if ( download_trigger ){
							
							script = COConfigurationManager.getStringParameter( "On Downloading Complete Script", "" );
							
						}else{
							
							script = COConfigurationManager.getStringParameter( "On Seeding Complete Script", "" );
						}
						
						File script_file = new File( script.trim());
						
						if ( !script_file.isFile()){
							
							Logger.log( new LogEvent(LOGID, "Script failed to run - '" + script_file + "' isn't a valid script file" ));
							
							Debug.out( "Invalid script: " + script_file );
							
						}else{
							
							try{
								boolean	close_vuze = action.equals( "RunScriptAndClose" );
								
								if ( !close_vuze ){
									
										// assume script might implement a sleep
									
									announceAll( true );
								}
								
								getPluginManager().getDefaultPluginInterface().getUtilities().createProcess( script_file.getAbsolutePath());
								
								if ( close_vuze ){
									
									requestStop();
								}
								
							}catch( Throwable e ){
								
								Debug.out( e );
							}
						}
						
					}else{
						
						Debug.out( "Unknown close action '" + action + "'" );
					}
				}
			});
	}
	
	public AzureusCoreOperation
	createOperation(
		final int		type )
	{
		AzureusCoreOperation	op =
			new AzureusCoreOperation()
			{
				public int
				getOperationType()
				{
					return( type );
				}
				
				public AzureusCoreOperationTask 
				getTask() 
				{
					return null;
				}
				
				public void 
				reportCurrentTask(
					String task )
				{
					AzureusCoreImpl.this.reportCurrentTask( this, task );
				}
				  
				public void 
				reportPercent(
					int percent )
				{
					AzureusCoreImpl.this.reportPercent( this, percent );
				}
			};
			
		for (int i=0;i<operation_listeners.size();i++){
			
			try{
				((AzureusCoreOperationListener)operation_listeners.get(i)).operationCreated( op );
				
			}catch( Throwable e ){
				
				Debug.printStackTrace(e);
			}
		}
		
		return( op );
	}
	
	public void
	createOperation(
		final int					type,
		AzureusCoreOperationTask	task )
	{
		final AzureusCoreOperationTask[] f_task = { task };
		
		AzureusCoreOperation	op =
				new AzureusCoreOperation()
				{
					public int
					getOperationType()
					{
						return( type );
					}
					
					public AzureusCoreOperationTask 
					getTask() 
					{
						return( f_task[0] );
					}
					
					public void 
					reportCurrentTask(
						String task )
					{
						AzureusCoreImpl.this.reportCurrentTask( this, task );
					}
					  
					public void 
					reportPercent(
						int percent )
					{
						AzureusCoreImpl.this.reportPercent( this, percent );
					}
				};
				

		for (int i=0;i<operation_listeners.size();i++){
			
				// don't catch exceptions here as we want errors from task execution to propagate
				// back to the invoker
			
			if (((AzureusCoreOperationListener)operation_listeners.get(i)).operationCreated( op )){
				
				f_task[0] = null;
			}
		}
		
			// nobody volunteeered to run it for us, we'd better do it
		
		if ( f_task[0] != null ){
			
			task.run( op );
		}
	}
	
	protected void 
	reportCurrentTask(
		AzureusCoreOperation		op,			
		String 						currentTask )
	{
		if ( op.getOperationType() == AzureusCoreOperation.OP_INITIALISATION ){
			
			PluginInitializer.fireEvent( PluginEvent.PEV_INITIALISATION_PROGRESS_TASK, currentTask );
		}
		
		Iterator it = listeners.iterator();
		
		while( it.hasNext()){
			
			try{
				((AzureusCoreListener)it.next()).reportCurrentTask( op, currentTask );
				
			}catch( Throwable e ){
				
				Debug.printStackTrace(e);
			}
		}
	}
	  
	protected void 
	reportPercent(
		AzureusCoreOperation		op,	
		int 						percent )
	{
		if ( op.getOperationType() == AzureusCoreOperation.OP_INITIALISATION ){

			PluginInitializer.fireEvent( PluginEvent.PEV_INITIALISATION_PROGRESS_PERCENT, new Integer( percent ));
		}

		Iterator it = listeners.iterator();
		
		while( it.hasNext()){
			
			try{
				((AzureusCoreListener)it.next()).reportPercent( op, percent );
				
			}catch( Throwable e ){
				
				Debug.printStackTrace(e);
			}
		}
	}
	
	public void
	addLifecycleListener(
		AzureusCoreLifecycleListener	l )
	{
		lifecycle_listeners.add(l);
	}
	
	public void
	removeLifecycleListener(
		AzureusCoreLifecycleListener	l )
	{
		lifecycle_listeners.remove(l);
	}
	
	public void
	addListener(
		AzureusCoreListener	l )
	{
		listeners.add( l );
	}
	
	public void
	removeListener(
		AzureusCoreListener	l )
	{
		listeners.remove( l );
	}
	
	public void
	addOperationListener(
		AzureusCoreOperationListener	l )
	{
		operation_listeners.add(l);
	}
	
	public void
	removeOperationListener(
		AzureusCoreOperationListener	l )
	{
		operation_listeners.remove(l);
	}

	public static void addCoreRunningListener(AzureusCoreRunningListener l) {
	   mon_coreRunningListeners.enter();
	   try {
    		if (AzureusCoreImpl.coreRunningListeners != null) {
    			coreRunningListeners.add(l);
    			
    			return;
    		}
	   } finally {
	  	 mon_coreRunningListeners.exit();
	   }
	   
	   l.azureusCoreRunning(AzureusCoreImpl.getSingleton());
	}
	
	public void
	addPowerManagementListener(
		PowerManagementListener	listener )
	{
		power_listeners.add( listener );
	}
		
	public void
	removePowerManagementListener(
		PowerManagementListener	listener )
	{
		power_listeners.remove( listener );
	}	
}
