/*
 * File    : TRTrackerServerPeerImpl.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.
 *
 * 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.tracker.server.impl;

import java.net.*;
import java.util.*;

import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.tracker.server.*;

import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPosition;

public class 
TRTrackerServerPeerImpl
	implements TRTrackerServerPeer, TRTrackerServerSimplePeer, HostNameToIPResolverListener, TRTrackerServerNatCheckerListener
{	
	private HashWrapper	peer_id;
	private int			key_hash_code;
	
	private byte[]		ip;
	private boolean		ip_override;
	private short		tcp_port;
	private short		udp_port;
	private short		http_port;
	private byte		crypto_level;
	private byte		az_ver;
	private String		ip_str;
	private byte[]		ip_bytes;
	private byte		NAT_status	= NAT_CHECK_UNKNOWN;
	
	private long		timeout;
	
	private long		uploaded;
	private long		downloaded;
	private long		amount_left;
	
	private long		last_contact_time;
	private boolean		download_completed;
	private boolean		biased;
	
	private short				up_speed;
	
		// fields above are serialised when exported
	
	private DHTNetworkPosition	network_position;
	private Object				user_data;
	
	protected
	TRTrackerServerPeerImpl(
		HashWrapper			_peer_id,
		int					_key_hash_code,
		byte[]				_ip,
		boolean				_ip_override,
		int					_tcp_port,
		int					_udp_port,
		int					_http_port,
		byte				_crypto_level,
		byte				_az_ver,
		long				_last_contact_time,
		boolean				_download_completed,
		byte				_last_nat_status,
		int					_up_speed,
		DHTNetworkPosition	_network_position )
	{
		peer_id				= _peer_id;
		key_hash_code		= _key_hash_code;
		ip					= _ip;
		ip_override			= _ip_override;
		tcp_port			= (short)_tcp_port;
		udp_port			= (short)_udp_port;
		http_port			= (short)_http_port;
		crypto_level		= _crypto_level;
		az_ver				= _az_ver;
		last_contact_time	= _last_contact_time;
		download_completed	= _download_completed;
		NAT_status			= _last_nat_status;	
		up_speed			= _up_speed>Short.MAX_VALUE?Short.MAX_VALUE:(short)_up_speed;
		network_position	= _network_position;
			
		resolveAndCheckNAT();
	}
	
		/**
		 * Import constructor
		 */

	protected
	TRTrackerServerPeerImpl(
		HashWrapper			_peer_id,
		int					_key_hash_code,
		byte[]				_ip,
		boolean				_ip_override,
		short				_tcp_port,
		short				_udp_port,
		short				_http_port,
		byte				_crypto_level,
		byte				_az_ver,
		String				_ip_str,
		byte[]				_ip_bytes,
		byte				_NAT_status,
		long				_timeout,
		long				_uploaded,
		long				_downloaded,
		long				_amount_left,
		long				_last_contact_time,
		boolean				_download_completed,
		boolean				_biased,
		short				_up_speed )
	{
		peer_id				= _peer_id;
		key_hash_code		= _key_hash_code;
		ip					= _ip;
		ip_override			= _ip_override;
		tcp_port			= _tcp_port;
		udp_port			= _udp_port;
		http_port			= _http_port;
		crypto_level		= _crypto_level;
		az_ver				= _az_ver;
		ip_str				= _ip_str;
		ip_bytes			= _ip_bytes;
		NAT_status			= _NAT_status;
		timeout				= _timeout;
		uploaded			= _uploaded;
		downloaded			= _downloaded;
		amount_left			= _amount_left;
		last_contact_time	= _last_contact_time;
		download_completed	= _download_completed;
		biased				= _biased;
		up_speed			= _up_speed;
	}
	
	protected boolean
	update(
		byte[]				_ip,
		int					_port,
		int					_udp_port,
		int					_http_port,
		byte				_crypto_level,
		byte				_az_ver,
		int					_up_speed,
		DHTNetworkPosition	_network_position )
	{
		udp_port			= (short)_udp_port;
		http_port			= (short)_http_port;
		crypto_level		= _crypto_level;
		az_ver				= _az_ver;
		up_speed			= _up_speed>Short.MAX_VALUE?Short.MAX_VALUE:(short)_up_speed;
		network_position	= _network_position;
		
		boolean	res	= false;
		
		if ( _port != getTCPPort() ){
			
			tcp_port	= (short)_port;
			
			res		= true;
		}
		
		if ( !Arrays.equals( _ip, ip )){
			
			ip			= _ip;
	
			res	= true;	
		}
		
		if ( res ){
			
			resolveAndCheckNAT();
		}
		
		return( res );
	}
	
	public void
	NATCheckComplete(
		boolean		ok )
	{
		if ( ok ){
			
			NAT_status = NAT_CHECK_OK;
			
		}else{
			
			NAT_status	= NAT_CHECK_FAILED;
		}
	}
	
	protected void
	setNATStatus(
		byte		status )
	{
		NAT_status	= status;
	}
	
	public byte
	getNATStatus()
	{
		return( NAT_status );
	}
	
	protected boolean
	isNATStatusBad()
	{
		return( NAT_status == NAT_CHECK_FAILED || NAT_status == NAT_CHECK_FAILED_AND_REPORTED );
	}
	
	protected void
	resolveAndCheckNAT()
	{
		// default values pending resolution
		
		ip_str 		= new String( ip );
		ip_bytes	= null;
		
		HostNameToIPResolver.addResolverRequest( ip_str, this );
		
			// a port of 0 is taken to mean that the client can't/won't receive incoming
			// connections - tr

		if ( tcp_port == 0 ){
			
			NAT_status = NAT_CHECK_FAILED_AND_REPORTED;
			
		}else{
			
				// only recheck if we haven't already ascertained the state
			
			if ( NAT_status == NAT_CHECK_UNKNOWN ){
				
				NAT_status	= NAT_CHECK_INITIATED;
				
				if ( !TRTrackerServerNATChecker.getSingleton().addNATCheckRequest( ip_str, getTCPPort(), this )){
					
					NAT_status = NAT_CHECK_DISABLED;
				}
			}	
		}
	}
	
	public void
	hostNameResolutionComplete(
		InetAddress	address )
	{
		if ( address != null ){
			
			ip_str 		= address.getHostAddress();
			
			ip_bytes	= address.getAddress();
		}
	}
	
	protected long
	getLastContactTime()
	{
		return( last_contact_time );
	}
	
	protected boolean
	getDownloadCompleted()
	{
		return( download_completed );
	}
	
	protected void
	setDownloadCompleted()
	{
		download_completed	= true;
	}
	
	public boolean
	isBiased()
	{
		return( biased );
	}
	
	public void
	setBiased(
		boolean	_biased )
	{
		biased	= _biased;
	}
	
	public HashWrapper
	getPeerId()
	{
		return( peer_id );
	}
	
	public byte[]
	getPeerID()
	{
		return( peer_id.getBytes());
	}
	
	protected int
	getKeyHashCode()
	{
		return( key_hash_code );
	}
	
	public byte[]
	getIPAsRead()
	{
		return( ip );
	}
	
	public String
	getIPRaw()
	{
		return( new String(ip));
	}
	
		/**
		 * If asynchronous resolution of the address is required, this will return
		 * the non-resolved address until the async process completes 
		 */
	
	public String
	getIP()
	{		
		return( ip_str );
	}
	
	protected boolean
	isIPOverride()
	{
		return( ip_override );
	}
	
		/**
		 * This will return in resolution of the address is not complete or fails
		 * @return
		 */
	
	public byte[]
	getIPAddressBytes()
	{
		return( ip_bytes );
	}
	
	public int
	getTCPPort()
	{
		return( tcp_port&0xffff );
	}
	
	public int
	getUDPPort()
	{
		return( udp_port&0xffff );
	}
	
	public int
	getHTTPPort()
	{
		return( http_port&0xffff );
	}
	
	public byte
	getCryptoLevel()
	{
		return( crypto_level );
	}
	
	public byte
	getAZVer()
	{
		return( az_ver );
	}
	
	public int
	getUpSpeed()
	{
		return( up_speed&0xffff );
	}
	
	public DHTNetworkPosition
	getNetworkPosition()
	{
		return( network_position );
	}
	
	protected void
	setTimeout(
		long		_now,
		long		_timeout )
	{
		last_contact_time	= _now;
		
		timeout				= _timeout;
	}
	
	protected long
	getTimeout()
	{
		return( timeout );
	}

	public int
	getSecsToLive()
	{
		return((int)(( timeout - SystemTime.getCurrentTime())/1000 ));
	}
	
	protected void
	setStats(
		long		_uploaded,
		long		_downloaded,
		long		_amount_left )
	{
		uploaded	= _uploaded;
		downloaded	= _downloaded;
		amount_left	= _amount_left;
	}
	
	public long
	getUploaded()
	{
		return( uploaded );
	}
	
	public long
	getDownloaded()
	{
		return( downloaded );
	}
	
	public long
	getAmountLeft()
	{
		return( amount_left );
	}
	
	public boolean
	isSeed()
	{
		return( amount_left == 0 );
	}
	
	public void
	setUserData(
		Object		key,
		Object		data )
	{
		if ( user_data == null ){
			
			user_data = new Object[]{ key, data };
			
		}else if ( user_data instanceof Object[]){
			
			Object[]	x = (Object[])user_data;
			
			if ( x[0] == key ){
				
				x[1] = data;
				
			}else{
				
				HashMap	map = new HashMap();
				
				user_data = map;
				
				map.put( x[0], x[1] );
				
				map.put( key, data );
			}
		}else{
			
			((Map)user_data).put( key, data );
		}
	}
	
	public Object
	getUserData(
		Object		key )
	{
		if ( user_data == null ){
			
			return( null );
			
		}else if( user_data instanceof Object[]){
			
			Object[]	x = (Object[])user_data;
			
			if ( x[0] == key ){
				
				return( x[1] );
				
			}else{
				
				return( null );
			}
		}else{
			
			return(((Map)user_data).get(key));
		}
	}
	
	public Map 
	export() 
	{
		Map map = new HashMap();
		
		map.put( "peer_id", 		peer_id.getBytes());
		map.put( "key_hash_code", 	new Long( key_hash_code ));
		map.put( "ip", 				ip );
		map.put( "ip_override",		new Long( ip_override?1:0 ));
		map.put( "tcp_port", 		new Long( tcp_port ));
		map.put( "udp_port", 		new Long( udp_port ));
		map.put( "http_port", 		new Long( http_port ));
		map.put( "crypto_level", 	new Long( crypto_level ));
		map.put( "az_ver", 			new Long( az_ver ));
		map.put( "ip_str", 			ip_str );
		if ( ip_bytes != null ){
			map.put( "ip_bytes", ip_bytes );
		}
		map.put( "NAT_status", 		new Long( NAT_status ));
		map.put( "timeout", 		new Long( timeout ));
		map.put( "uploaded", 		new Long( uploaded ));
		map.put( "downloaded", 		new Long( downloaded ));
		map.put( "amount_left", 	new Long( amount_left ));
		map.put( "last_contact_time", 	new Long( last_contact_time ));
		map.put( "download_completed", 	new Long( download_completed?1:0 ));
		map.put( "biased", 			new Long( biased?1:0 ));
		map.put( "up_speed", 		new Long( up_speed ));
		
		return( map );
	}
	
	public static TRTrackerServerPeerImpl
	importPeer(
		Map		map )
	{
		try{
			HashWrapper		peer_id			= new HashWrapper((byte[])map.get( "peer_id" ));
			int				key_hash_code	= ((Long)map.get( "key_hash_code" )).intValue();
			byte[]			ip				= (byte[])map.get( "ip" );
			boolean			ip_override		= ((Long)map.get( "ip_override" )).intValue()==1;
			short			tcp_port		= ((Long)map.get( "tcp_port" )).shortValue();
			short			udp_port		= ((Long)map.get( "udp_port" )).shortValue();
			short			http_port		= ((Long)map.get( "http_port" )).shortValue();
			byte			crypto_level	= ((Long)map.get( "crypto_level" )).byteValue();
			byte			az_ver			= ((Long)map.get( "az_ver" )).byteValue();
			String			ip_str			= new String( (byte[])map.get( "ip_str" ));
			byte[]			ip_bytes		= (byte[])map.get( "ip_bytes" );
			byte			NAT_status		= ((Long)map.get( "NAT_status" )).byteValue();
			long			timeout			= ((Long)map.get( "timeout" )).longValue();
			long			uploaded		= ((Long)map.get( "uploaded" )).longValue();
			long			downloaded		= ((Long)map.get( "downloaded" )).longValue();
			long			amount_left		= ((Long)map.get( "amount_left" )).longValue();
			long			last_contact_time	= ((Long)map.get( "last_contact_time" )).longValue();
			boolean			download_completed	= ((Long)map.get( "download_completed" )).intValue() == 1;
			boolean			biased			= ((Long)map.get( "biased" )).intValue() == 1;
			short			up_speed		= ((Long)map.get( "up_speed" )).shortValue();
			
			return( 
				new TRTrackerServerPeerImpl(
						peer_id,
						key_hash_code,
						ip,
						ip_override,
						tcp_port,
						udp_port,
						http_port,
						crypto_level,
						az_ver,
						ip_str,
						ip_bytes,
						NAT_status,
						timeout,
						uploaded,
						downloaded,
						amount_left,
						last_contact_time,
						download_completed,
						biased,
						up_speed ));
			
		}catch( Throwable e ){
			
			return( null );
		}
	}
	
	protected String
	getString()
	{
		return( new String(ip) + ":" + getTCPPort() + "(" + new String(peer_id.getHash()) + ")" );
	}
}
