/*
 * Created on 12-Jan-2005
 * 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.dht.impl;

import java.io.*;
import java.util.Properties;

import org.gudy.azureus2.core3.util.AERunStateHandler;
import org.gudy.azureus2.core3.util.Debug;

import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.DHTListener;
import com.aelitis.azureus.core.dht.DHTLogger;
import com.aelitis.azureus.core.dht.DHTOperationListener;
import com.aelitis.azureus.core.dht.DHTStorageAdapter;
import com.aelitis.azureus.core.dht.control.*;
import com.aelitis.azureus.core.dht.db.DHTDB;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncher;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncherAdapter;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncherFactory;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionManager;
import com.aelitis.azureus.core.dht.router.DHTRouter;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTester;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTesterFactory;
import com.aelitis.azureus.core.dht.transport.*;
import com.aelitis.azureus.core.util.CopyOnWriteList;

/**
 * @author parg
 *
 */

public class 
DHTImpl 
	implements DHT, AERunStateHandler.RunStateChangeListener
{
	private DHTStorageAdapter		storage_adapter;
	private DHTNATPuncherAdapter	nat_adapter;
	private DHTControl				control;
	private DHTNATPuncher			nat_puncher;
	private DHTSpeedTester			speed_tester;
	private	Properties				properties;
	private DHTLogger				logger;
	
	private CopyOnWriteList<DHTListener>	listeners = new CopyOnWriteList<DHTListener>();
	
	private boolean	runstate_startup 	= true;
	private boolean	sleeping			= false;
	
	public 
	DHTImpl(
		DHTTransport			_transport,
		Properties				_properties,
		DHTStorageAdapter		_storage_adapter,
		DHTNATPuncherAdapter	_nat_adapter,
		DHTLogger				_logger )
	{		
		properties		= _properties;
		storage_adapter	= _storage_adapter;
		nat_adapter		= _nat_adapter;
		logger			= _logger;
		
		DHTNetworkPositionManager.initialise( storage_adapter );
		
		DHTLog.setLogger( logger );
		
		int		K 		= getProp( PR_CONTACTS_PER_NODE, 			DHTControl.K_DEFAULT );
		int		B 		= getProp( PR_NODE_SPLIT_FACTOR, 			DHTControl.B_DEFAULT );
		int		max_r	= getProp( PR_MAX_REPLACEMENTS_PER_NODE, 	DHTControl.MAX_REP_PER_NODE_DEFAULT );
		int		s_conc 	= getProp( PR_SEARCH_CONCURRENCY, 			DHTControl.SEARCH_CONCURRENCY_DEFAULT );
		int		l_conc 	= getProp( PR_LOOKUP_CONCURRENCY, 			DHTControl.LOOKUP_CONCURRENCY_DEFAULT );
		int		o_rep 	= getProp( PR_ORIGINAL_REPUBLISH_INTERVAL, 	DHTControl.ORIGINAL_REPUBLISH_INTERVAL_DEFAULT );
		int		c_rep 	= getProp( PR_CACHE_REPUBLISH_INTERVAL, 	DHTControl.CACHE_REPUBLISH_INTERVAL_DEFAULT );
		int		c_n 	= getProp( PR_CACHE_AT_CLOSEST_N, 			DHTControl.CACHE_AT_CLOSEST_N_DEFAULT );
		boolean	e_c 	= getProp( PR_ENCODE_KEYS, 					DHTControl.ENCODE_KEYS_DEFAULT ) == 1;
		boolean	r_p 	= getProp( PR_ENABLE_RANDOM_LOOKUP, 		DHTControl.ENABLE_RANDOM_DEFAULT ) == 1;
		
		control = DHTControlFactory.create( 
				new DHTControlAdapter()
				{
					public DHTStorageAdapter
					getStorageAdapter()
					{
						return( storage_adapter );
					}
					
					public boolean
					isDiversified(
						byte[]		key )
					{
						if ( storage_adapter == null ){
							
							return( false );
						}
						
						return( storage_adapter.isDiversified( key ));
					}
					
					public byte[][]
					diversify(
						String				description,
						DHTTransportContact	cause,
						boolean				put_operation,
						boolean				existing,
						byte[]				key,
						byte				type,
						boolean				exhaustive,
						int					max_depth )
					{
						boolean	valid;
						
						if ( existing ){
							
							valid =	 	type == DHT.DT_FREQUENCY ||
										type == DHT.DT_SIZE ||
										type == DHT.DT_NONE;
						}else{
							
							valid = 	type == DHT.DT_FREQUENCY ||
										type == DHT.DT_SIZE;
						}
						
						if ( storage_adapter != null && valid ){
							
							if ( existing ){
								
								return( storage_adapter.getExistingDiversification( key, put_operation, exhaustive, max_depth ));
								
							}else{
								
								return( storage_adapter.createNewDiversification( description, cause, key, put_operation, type, exhaustive, max_depth ));
							}
						}else{
							
							if ( !valid ){
								
								Debug.out( "Invalid diversification received: type = " + type );
							}
							
							if ( existing ){
								
								return( new byte[][]{ key });
								
							}else{
								
								return( new byte[0][] );
							}
						}
					}
				},
				_transport, 
				K, B, max_r,
				s_conc, l_conc, 
				o_rep, c_rep, c_n, e_c, r_p,
				logger );
		
		if ( nat_adapter != null ){
			
			nat_puncher	= DHTNATPuncherFactory.create( nat_adapter, this );
		}
				
		AERunStateHandler.addListener( this, true );
	}
	
	public 
	DHTImpl(
		DHTTransport			_transport,
		DHTRouter				_router,
		DHTDB					_database,
		Properties				_properties,
		DHTStorageAdapter		_storage_adapter,
		DHTLogger				_logger )
	{		
		properties		= _properties;
		storage_adapter	= _storage_adapter;
		logger			= _logger;
		
		DHTNetworkPositionManager.initialise( storage_adapter );
		
		DHTLog.setLogger( logger );
		
		int		K 		= getProp( PR_CONTACTS_PER_NODE, 			DHTControl.K_DEFAULT );
		int		B 		= getProp( PR_NODE_SPLIT_FACTOR, 			DHTControl.B_DEFAULT );
		int		max_r	= getProp( PR_MAX_REPLACEMENTS_PER_NODE, 	DHTControl.MAX_REP_PER_NODE_DEFAULT );
		int		s_conc 	= getProp( PR_SEARCH_CONCURRENCY, 			DHTControl.SEARCH_CONCURRENCY_DEFAULT );
		int		l_conc 	= getProp( PR_LOOKUP_CONCURRENCY, 			DHTControl.LOOKUP_CONCURRENCY_DEFAULT );
		int		o_rep 	= getProp( PR_ORIGINAL_REPUBLISH_INTERVAL, 	DHTControl.ORIGINAL_REPUBLISH_INTERVAL_DEFAULT );
		int		c_rep 	= getProp( PR_CACHE_REPUBLISH_INTERVAL, 	DHTControl.CACHE_REPUBLISH_INTERVAL_DEFAULT );
		int		c_n 	= getProp( PR_CACHE_AT_CLOSEST_N, 			DHTControl.CACHE_AT_CLOSEST_N_DEFAULT );
		boolean	e_c 	= getProp( PR_ENCODE_KEYS, 					DHTControl.ENCODE_KEYS_DEFAULT ) == 1;
		boolean	r_p 	= getProp( PR_ENABLE_RANDOM_LOOKUP, 		DHTControl.ENABLE_RANDOM_DEFAULT ) == 1;
		
		control = DHTControlFactory.create( 
				new DHTControlAdapter()
				{
					public DHTStorageAdapter
					getStorageAdapter()
					{
						return( storage_adapter );
					}
					
					public boolean
					isDiversified(
						byte[]		key )
					{
						if ( storage_adapter == null ){
							
							return( false );
						}
						
						return( storage_adapter.isDiversified( key ));
					}
					
					public byte[][]
					diversify(
						String				description,
						DHTTransportContact	cause,
						boolean				put_operation,
						boolean				existing,
						byte[]				key,
						byte				type,
						boolean				exhaustive,
						int					max_depth )
					{
						boolean	valid;
						
						if ( existing ){
							
							valid =	 	type == DHT.DT_FREQUENCY ||
										type == DHT.DT_SIZE ||
										type == DHT.DT_NONE;
						}else{
							
							valid = 	type == DHT.DT_FREQUENCY ||
										type == DHT.DT_SIZE;
						}
						
						if ( storage_adapter != null && valid ){
							
							if ( existing ){
								
								return( storage_adapter.getExistingDiversification( key, put_operation, exhaustive, max_depth ));
								
							}else{
								
								return( storage_adapter.createNewDiversification( description, cause, key, put_operation, type, exhaustive, max_depth ));
							}
						}else{
							
							if ( !valid ){
								
								Debug.out( "Invalid diversification received: type = " + type );
							}
							
							if ( existing ){
								
								return( new byte[][]{ key });
								
							}else{
								
								return( new byte[0][] );
							}
						}
					}
				},
				_transport, 
				_router,
				_database,
				K, B, max_r,
				s_conc, l_conc, 
				o_rep, c_rep, c_n, e_c, r_p,
				logger );
	}
	
	public void 
	runStateChanged(
		long run_state ) 
	{
		try{
			boolean	is_sleeping = AERunStateHandler.isDHTSleeping();
			
			if ( sleeping != is_sleeping ){
			
				sleeping = is_sleeping;
				
				if ( !runstate_startup ){
				
					System.out.println( "DHT sleeping=" + sleeping );
				}
			}
			
			control.setSleeping( sleeping );
			
			DHTSpeedTester old_tester = null;
			DHTSpeedTester new_tester = null;
			
			synchronized( this ){
				
				if ( sleeping ){
					
					if ( speed_tester != null ){
						
						old_tester = speed_tester;
						
						speed_tester = null;
					}
				}else{
					
					new_tester = speed_tester = DHTSpeedTesterFactory.create( this );
				}
			}
			
			if ( old_tester != null ){
		
				if ( !runstate_startup ){
					
					System.out.println( "    destroying speed tester" );
				}
				
				old_tester.destroy();
			}
			
			if ( new_tester != null ){
				
				if ( !runstate_startup ){
					
					System.out.println( "    creating speed tester" );
				}
				
				for ( DHTListener l: listeners ){
					
					try{
						l.speedTesterAvailable( new_tester );
						
					}catch( Throwable e ){
						
						Debug.out( e );
					}
				}
			}
		}finally{
			
			runstate_startup = false;
		}
	}
	
	public boolean
	isSleeping()
	{
		return( sleeping );
	}
	
	public void
	setSuspended(
		boolean	susp )
	{
		if ( susp ){
			
			if ( nat_puncher != null ){
				
				nat_puncher.setSuspended( true );
			}
		
			control.setSuspended( true );
			
		}else{
			
			control.setSuspended( false );
			
			if ( nat_puncher != null ){
				
				nat_puncher.setSuspended( false );
			}
		}
	}
	
	protected int
	getProp(
		String		name,
		int			def )
	{
		Integer	x = (Integer)properties.get(name);
		
		if ( x == null ){
			
			properties.put( name, new Integer( def ));
			
			return( def );
		}
		
		return( x.intValue());
	}
	
	public int
	getIntProperty(
		String		name )
	{
		return(((Integer)properties.get(name)).intValue());
	}
	
	public boolean
	isDiversified(
		byte[]		key )
	{
		return( control.isDiversified( key ));
	}
	
	public void
	put(
		byte[]					key,
		String					description,
		byte[]					value,
		short					flags,
		DHTOperationListener	listener )
	{
		control.put( key, description, value, flags, (byte)0, DHT.REP_FACT_DEFAULT, true, listener );
	}
	
	public void
	put(
		byte[]					key,
		String					description,
		byte[]					value,
		short					flags,
		boolean					high_priority,
		DHTOperationListener	listener )
	{
		control.put( key, description, value, flags, (byte)0, DHT.REP_FACT_DEFAULT, high_priority, listener );
	}
	
	public void
	put(
		byte[]					key,
		String					description,
		byte[]					value,
		short					flags,
		byte					life_hours,
		boolean					high_priority,
		DHTOperationListener	listener )
	{
		control.put( key, description, value, flags, life_hours, DHT.REP_FACT_DEFAULT, high_priority, listener );
	}
	
	public void
	put(
		byte[]					key,
		String					description,
		byte[]					value,
		short					flags,
		byte					life_hours,
		byte					replication_control,
		boolean					high_priority,
		DHTOperationListener	listener )
	{
		control.put( key, description, value, flags, life_hours, replication_control, high_priority, listener );
	}
	
	public DHTTransportValue
	getLocalValue(
		byte[]		key )
	{
		return( control.getLocalValue( key ));
	}
		
	public void
	get(
		byte[]					key,
		String					description,
		short					flags,
		int						max_values,
		long					timeout,
		boolean					exhaustive,
		boolean					high_priority,
		DHTOperationListener	listener )
	{
		control.get( key, description, flags, max_values, timeout, exhaustive, high_priority, listener );
	}
		
	public byte[]
	remove(
		byte[]					key,
		String					description,
		DHTOperationListener	listener )
	{
		return( control.remove( key, description, listener ));
	}
	
	public byte[]
	remove(
		DHTTransportContact[]	contacts,
		byte[]					key,
		String					description,
		DHTOperationListener	listener )
	{
		return( control.remove( contacts, key, description, listener ));
	}
	
	public DHTTransport
	getTransport()
	{
		return( control.getTransport());
	}
	
	public DHTRouter
	getRouter()
	{
		return( control.getRouter());
	}
	
	public DHTControl
	getControl()
	{
		return( control );
	}
	
	public DHTDB
	getDataBase()
	{
		return( control.getDataBase());
	}
	
	public DHTNATPuncher
	getNATPuncher()
	{
		return( nat_puncher );
	}
	
	public DHTSpeedTester
	getSpeedTester()
	{
		return( speed_tester );
	}
	
	public DHTStorageAdapter
	getStorageAdapter()
	{
		return( storage_adapter );
	}
	
	public void
	integrate(
		boolean		full_wait )
	{
		control.seed( full_wait );	
		
		if ( nat_puncher!= null ){
			
			nat_puncher.start();
		}
	}
	
	public void
	destroy()
	{
		if ( nat_puncher != null ){
			
			nat_puncher.destroy();
		}
		
		DHTNetworkPositionManager.destroy( storage_adapter );
		
		AERunStateHandler.removeListener( this );
		
		if ( control != null ){
			
			control.destroy();
		}
		
		if ( speed_tester != null ){
			
			speed_tester.destroy();
		}
	}
	
	public void
	exportState(
		DataOutputStream	os,
		int					max )
	
		throws IOException
	{	
		control.exportState( os, max );
	}
	
	public void
	importState(
		DataInputStream		is )
	
		throws IOException
	{	
		control.importState( is );
	}
	
	public void
	setLogging(
		boolean	on )
	{
		DHTLog.setLogging( on );
	}
	
	public DHTLogger
	getLogger()
	{
		return( logger );
	}
	
	public void
	print(
		boolean	full )
	{
		control.print( full );
	}
	
	public void
	addListener(
		DHTListener		listener )
	{
		listeners.add( listener );
		
		DHTSpeedTester st = speed_tester;
		
		if ( st != null ){
			
			listener.speedTesterAvailable( st );
		}
	}
	
	public void
	removeListener(
		DHTListener		listener )
	{
		listeners.remove( listener );
	}
}
