/*
 * File    : COConfigurationManager.java
 * Created : 5 Oct. 2003
 * By      : Parg 
 * 
 * Azureus - a Java Bittorrent client
 *
 * 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 ( see the LICENSE file ).
 *
 * 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 org.gudy.azureus2.core3.config;

import java.security.AccessControlException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;

import org.gudy.azureus2.core3.config.impl.*;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.IndentWriter;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.gudy.azureus2.core3.util.protocol.AzURLStreamHandlerFactory;

public class 
COConfigurationManager 
{
	public static final int CONFIG_DEFAULT_MIN_MAX_UPLOAD_SPEED			= 5;
	public static final int CONFIG_DEFAULT_MAX_DOWNLOAD_SPEED			= 0;
	public static final int	CONFIG_DEFAULT_MAX_CONNECTIONS_PER_TORRENT	= 50;
	public static final int	CONFIG_DEFAULT_MAX_CONNECTIONS_GLOBAL		= 250;
	
	public static final int CONFIG_CACHE_SIZE_MAX_MB;
	
	static{
		long max_mem_bytes 	= Runtime.getRuntime().maxMemory();
	    long mb_1			= 1*1024*1024;
	    long mb_32			= 32*mb_1;
	    int size = (int)(( max_mem_bytes - mb_32 )/mb_1);	    
	    if( size > 2000 )  size = 2000;  //safety check
      if( size < 1 )  size = 1;
	    CONFIG_CACHE_SIZE_MAX_MB = size;
	}
	
	public static final boolean	ENABLE_MULTIPLE_UDP_PORTS	= false;
	
	private static boolean	pre_initialised;
	
	public static synchronized void
	preInitialise()
	{	  	
		if ( !pre_initialised ){
			
			pre_initialised	= true;
		
			try{
				if ( System.getProperty( "azureus.portable.enable", "false" ).equalsIgnoreCase( "true" )){
				
					try{
						if ( File.separatorChar != '\\' ){
								
							throw( new Exception( "Portable only supported on Windows" ));
						}
						
						File portable_root;
							
						try{
							portable_root = new File( "." ).getCanonicalFile();
	
						}catch( Throwable e ){
	
							portable_root = new File( "." ).getAbsoluteFile();
						}	
						
						if ( !portable_root.canWrite()){
							
							throw( new Exception( "can't write to " + portable_root ));
						}
						
						File	root_file = new File( portable_root, "portable.dat" );
						
						String	str = portable_root.getAbsolutePath();

						if ( str.length() < 2 || str.charAt(1) != ':' ){
							
							throw( new Exception( "drive letter missing in '" + str + "'" ));
						}														
						
						String	root_relative = str.substring( 2 );
							
						boolean	write_file = true;
						
						if ( root_file.exists()){
							
							LineNumberReader lnr = new LineNumberReader( new InputStreamReader( new FileInputStream( root_file ), "UTF-8" ));
							
							try{
								String	 line = lnr.readLine();
								
								if ( line != null ){
									
									line = line.trim();
									
									if ( line.equalsIgnoreCase( root_relative )){
										
										write_file = false;
										
									}else{
										
										throw( new Exception( "root changed - old='" + line + "', new='" + root_relative ));
									}
								}
							}finally{
								
								lnr.close();
							}
						}
						
						if ( write_file ){
							
							PrintWriter pw = new PrintWriter( new OutputStreamWriter( new FileOutputStream( root_file ), "UTF-8" ));
							
							try{
								pw.println( root_relative );
								
							}finally{
								
								pw.close();
							}
						}
						
						System.setProperty( "azureus.install.path", str );
						System.setProperty( "azureus.config.path", str );
						
						System.setProperty( "azureus.portable.root", str );
						
						System.out.println( "Portable setup OK - root=" + root_relative + " (current=" + str + ")" );
						
					}catch( Throwable e ){
						
						System.err.println( "Portable setup failed: " + e.getMessage());
						
						System.setProperty( "azureus.portable.enable", "false" );
					
						System.setProperty( "azureus.portable.root", "" );
					}
				}else{
					
					System.setProperty( "azureus.portable.root", "" );	
				}
				
				/*
			  	String	handlers = System.getProperty( "java.protocol.handler.pkgs" );
			  	
			  	if ( handlers == null ){
			  		
			  		handlers = "org.gudy.azureus2.core3.util.protocol";
			  		
			  	}else{
			  		
			  		handlers += "|org.gudy.azureus2.core3.util.protocol";
			  	}
			  	
			  	System.setProperty( "java.protocol.handler.pkgs", handlers );
			  	*/
				
					/* for the moment disable this as it is causing some users to get an SSL exception on
					 * trackers with Java 7 due to the tracker hostname setup
					 * See http://stackoverflow.com/questions/7615645/ssl-handshake-alert-unrecognized-name-error-since-upgrade-to-java-1-7-0
					 */
				
				System.setProperty( "jsse.enableSNIExtension", "false" );
				
				System.setProperty( "sun.net.maxDatagramSockets", "4096" );
				
				URL.setURLStreamHandlerFactory(new AzURLStreamHandlerFactory());
			  	 	
			  		// DNS cache timeouts
			  	
			  	System.setProperty("sun.net.inetaddr.ttl", "60");
			  	System.setProperty("networkaddress.cache.ttl", "60");
			  	System.setProperty("sun.net.inetaddr.negative.ttl", "300" );
			  	System.setProperty("networkaddress.cache.negative.ttl", "300" );
			  	
			  	// flick AWT into headless mode, which is supposed to make it more lightweight
			  	// don't do this as it borks (for example) swing based plugins, java webstart installer,
			  	// swing webui plugin, ....
			  	
			  	// System.setProperty("java.awt.headless", "true");
		      
		  		// defaults, overridden later if needed
		      
			  	System.setProperty( "sun.net.client.defaultConnectTimeout", "120000" );	
			  	System.setProperty(	"sun.net.client.defaultReadTimeout", "60000" ); 
			  	
			  		// allows us to set HOST headers which is needed when working with Tor+nginx...
			  	
			  	System.setProperty( "sun.net.http.allowRestrictedHeaders", "true" );
			  	
			      //see http://developer.apple.com/releasenotes/Java/Java142RN/ResolvedIssues/chapter_3_section_7.html
			      //fixes the osx kernel panic bug caused by Apple's faulty kqueue implementation (as of 10.3.6)
			  	
			    if( Constants.isOSX ) {
			    	
			    		// things seem good in 10.6
			    	
			    	//if ( !Constants.isOSX_10_6_OrHigher ){
			        
			    		System.setProperty( "java.nio.preferSelect", "true" );
			    	//}
			    }
			    
			    //if ( Constants.IS_CVS_VERSION && ( Constants.isOSX || Constants.isWindows )){
			    // everyone gets this as we use it to force prevent resolution when running socks
			    
			    System.setProperty("sun.net.spi.nameservice.provider.1","dns,aednsproxy");
			    
			    //}
			    
			    SystemProperties.determineApplicationName();
			    
			}catch( Throwable e ){
				
					// can happen in applet
				
				if ( e instanceof AccessControlException ){
					
				}else{
				
					e.printStackTrace();
				}
			}
		}
	}
	
	public static ConfigurationManager
	initialise()
	{
		preInitialise();
		
		return ConfigurationManager.getInstance();
	}
	
	public static ConfigurationManager
	initialiseFromMap(
		Map		data )
	{
		preInitialise();
		
		return ConfigurationManager.getInstance(data);
	}

	public static final boolean
	isNewInstall()
	{
		return( ConfigurationManager.getInstance().isNewInstall());
	}
	
	public static String
	getStringParameter(
		String		_name )
	{
		return( ConfigurationManager.getInstance().getStringParameter( _name ));
	}
	
	public static String
	getStringParameter(
		String		_name,
		String		_default )
	{
		return( ConfigurationManager.getInstance().getStringParameter( _name, _default ));
	}
	
	public static boolean
	setParameter(String parameter, String value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}

	public static boolean
	verifyParameter(String parameter, String value) 
	{
		return ConfigurationManager.getInstance().verifyParameter( parameter, value );
	}
	
	public static boolean
	getBooleanParameter(
		String		_name )
	{
		return( ConfigurationManager.getInstance().getBooleanParameter( _name ));
	}
	
	/**
	 * @deprecated You should set ConfigurationDefaults, and use {@link #getBooleanParameter(String)} 
	 */
	public static boolean
	getBooleanParameter(
		String		_name,
		boolean		_default )
	{
		return( ConfigurationManager.getInstance().getBooleanParameter( _name, _default ));
	}
	
	public static boolean  
	setParameter(String parameter, boolean value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static int
	getIntParameter(
		String		_name )
	{
		return( ConfigurationManager.getInstance().getIntParameter( _name ));
	}
	
	
	/**
	 * Only use this for internal values, NOT for ones that the user can sensibly change. In this
	 * case add the key to the configuration defaults and use the above method
	 * @param _name
	 * @param _def
	 * @return
	 */
	
	public static int
	getIntParameter(
		String		_name,
		int		_default )
	{
		return( ConfigurationManager.getInstance().getIntParameter( _name, _default ));
	}
	
	public static boolean 
	setParameter(String parameter, int value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static boolean 
	setParameter(String parameter, long value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static long
	getLongParameter(
		String		_name )
	{
		return( ConfigurationManager.getInstance().getLongParameter( _name ));
	}
	
	/**
	 * Only use this for internal values, NOT for ones that the user can sensibly change. In this
	 * case add the key to the configuration defaults and use the above method
	 * @param _name
	 * @param _def
	 * @return
	 */
	
	public static long
	getLongParameter(
		String		_name,
		long		_def )
	{
		return( ConfigurationManager.getInstance().getLongParameter( _name, _def ));
	}
	
	public static byte[] getByteParameter(String _name) {
		return( ConfigurationManager.getInstance().getByteParameter(_name));
	}

	public static byte[]
	getByteParameter(
		String		_name,
		byte[]		_default )
	{
		return( ConfigurationManager.getInstance().getByteParameter( _name, _default ));
	}
	
	public static boolean
	setParameter(String parameter, byte[] value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static String
	getDirectoryParameter(
		String		_name )		
		throws IOException
	{
		return( ConfigurationManager.getInstance().getDirectoryParameter( _name ));
	}
	
	
	
	/*
	public static boolean
	setParameter(String parameter, Color value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}

	public static boolean
	setParameter(String parameter, RGB value) 
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	*/
	
	public static boolean
	setRGBParameter(String parameter, int red, int green, int blue) 
	{
		return ConfigurationManager.getInstance().setRGBParameter( parameter, red, green, blue);
	}
	
	public static boolean setRGBParameter(String parameter, int[] rgb, boolean override) {
		return ConfigurationManager.getInstance().setRGBParameter(parameter, rgb, override);
	}

	public static float
	getFloatParameter(
		String		_name)
	{
		return( ConfigurationManager.getInstance().getFloatParameter( _name ));
	}
	
	public static float
	getFloatParameter(
		String		_name,
		float		_def )
	{
		return( ConfigurationManager.getInstance().getFloatParameter( _name, _def ));
	}
	public static boolean 
	setParameter(String parameter, float value)
	{
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static boolean
	setParameter(String parameter,StringList value) {
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static StringList
	getStringListParameter(String parameter)
	{
		return( ConfigurationManager.getInstance().getStringListParameter( parameter ));
	}

	public static boolean
	setParameter(String parameter,List value) {
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static List
	getListParameter(String parameter, List def)
	{
		return( ConfigurationManager.getInstance().getListParameter( parameter, def ));
	}
	
	public static boolean
	setParameter(String parameter,Map value) {
		return ConfigurationManager.getInstance().setParameter( parameter, value );
	}
	
	public static Map
	getMapParameter(String parameter, Map def)
	{
		return( ConfigurationManager.getInstance().getMapParameter( parameter, def ));
	}
	
	  /**
	   * Returns true if a parameter with the given name exists.
	   * @param key The name of the parameter to check.
	   * @param explicit If <tt>true</tt>, we only check for a value which is
	   *     definitely stored explicitly, <tt>false</tt> means that we'll also
	   *     check against configuration defaults too.  
	   */
	public static boolean hasParameter(String parameter, boolean explicit) {
		return ConfigurationManager.getInstance().hasParameter(parameter, explicit);
	}
	
	public static void
	save()
	{
		ConfigurationManager.getInstance().save();
	}
	
		/**
		 * Mark as needing a save but not immediately - use when potentially needing a large number of saves that aren't
		 * absolutely required to be immediately persisted
		 */
	
	public static void
	setDirty()
	{
		ConfigurationManager.getInstance().setDirty();
	}
	
	public static void
	addListener(
		COConfigurationListener		listener )
	{
		ConfigurationManager.getInstance().addListener( listener );
	}

	public static void
	addAndFireListener(
		COConfigurationListener		listener )
	{
		ConfigurationManager.getInstance().addAndFireListener( listener );
	}
	
	public static void
	addParameterListener(String parameter, ParameterListener listener)
	{
	  ConfigurationManager.getInstance().addParameterListener(parameter, listener);
	}

	/**
	 * @param strings
	 * @param parameterListener
	 *
	 * @since 3.0.1.5
	 */
	public static void addParameterListener(String[] ids,
			ParameterListener listener) {
		ConfigurationManager instance = ConfigurationManager.getInstance();
		for (int i = 0; i < ids.length; i++) {
		  instance.addParameterListener(ids[i], listener);
		}
	}

	public static void
	addAndFireParameterListener(String parameter, ParameterListener listener)
	{
	  ConfigurationManager.getInstance().addParameterListener(parameter, listener);
	  
	  listener.parameterChanged( parameter );
	}
	
	public static void
	addAndFireParameterListeners(String[] parameters, ParameterListener listener)
	{
		for (int i=0;i<parameters.length;i++){
			ConfigurationManager.getInstance().addParameterListener(parameters[i], listener);
		}
	  
		listener.parameterChanged( null );
	}
	public static void
	removeParameterListener(String parameter, ParameterListener listener)
	{
		ConfigurationManager.getInstance().removeParameterListener(parameter, listener);
	}

	public static void
	removeListener(
		COConfigurationListener		listener )
	{
		ConfigurationManager.getInstance().removeListener( listener );
	}
  
  public static Set<String>
  getAllowedParameters()
  {
  	return ConfigurationDefaults.getInstance().getAllowedParameters();
  }
  
  public static Set<String>
  getDefinedParameters()
  {
  	return ConfigurationManager.getInstance().getDefinedParameters();
  }
  
  	/**
  	 * raw parameter access
  	 * @param name
  	 * @return
  	 */
  public static Object
  getParameter(
	String	name )
  {
  	return ConfigurationManager.getInstance().getParameter(name);
  }
		 
  	/**
  	 * checks if a default is defined for the named parameter
  	 * @param parameter
  	 * @return
  	 */
  
  public static boolean
  doesParameterDefaultExist(
	 String     parameter)
  {
       return ConfigurationDefaults.getInstance().doesParameterDefaultExist(parameter);
  }
  
  	/**
  	 * checks if the user has explicitly set a value for the named parameter
  	 * @param parameter
  	 * @return
  	 */
  
  public static boolean
  doesParameterNonDefaultExist(
	 String     parameter)
  {
       return ConfigurationManager.getInstance().doesParameterNonDefaultExist(parameter);
  }
  public static void
  registerExternalDefaults(
  	Map							addmap)
  {
  	ConfigurationDefaults.getInstance().registerExternalDefaults(addmap);
  }
  
  public static void
  setBooleanDefault(
  	String	parameter,
	boolean	_default )
  {
  	ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }
  
  public static void setFloatDefault(String parameter, float _default) {
	  ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }
  
  public static void
  setIntDefault(
  	String	parameter,
	int	_default )
  {
  	ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }

  public static void
  setLongDefault(
  	String	parameter,
	long	_default )
  {
  	ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }
  
  public static void
  setStringDefault(
  	String	parameter,
	String	_default )
  {
  	ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }
  
  public static void
  setByteDefault(
  	String	parameter,
	byte[]	_default )
  {
  	ConfigurationDefaults.getInstance().addParameter( parameter, _default );
  }
  
  public static Object
  getDefault(
	 String parameter )
  {
	return( ConfigurationDefaults.getInstance().getParameter( parameter ));
  }
  
  public static boolean removeParameter(String parameter) {
		return ConfigurationManager.getInstance().removeParameter(parameter);
  }
  
  public static boolean removeRGBParameter(String parameter) {
		return ConfigurationManager.getInstance().removeRGBParameter(parameter);
  }
  
  public static void
  registerExportedParameter(
	 String		name,
	 String		key )
  {
	  ConfigurationManager.getInstance().registerExportedParameter( name, key );
  }
  
  public static void
  resetToDefaults()
  {
	  ConfigurationManager.getInstance().resetToDefaults();
  }
  
  public static void
  addResetToDefaultsListener(
	  ResetToDefaultsListener		l )
  {
	  ConfigurationManager.getInstance().addResetToDefaultsListener( l );
  }
  
  public static void
  dumpConfigChanges(
	IndentWriter	writer )
  {
	 ConfigurationManager.getInstance().dumpConfigChanges( writer );
  }
  
  public interface
  ParameterVerifier
  {
	  public boolean
	  verify(
		String	parameter,
		Object	value );
  }
  
  public interface
  ResetToDefaultsListener
  {
	public void
	reset();
  }
}
