/*
 * Created on 29-Jun-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 org.gudy.azureus2.core3.util;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author parg
 *
 */

public class 
HostNameToIPResolver 
{
	static protected AEThread2		resolver_thread;
	
	static protected List			request_queue		= new ArrayList();
	
	static protected AEMonitor		request_queue_mon	= new AEMonitor( "HostNameToIPResolver" );

	static protected AESemaphore	request_semaphore	= new AESemaphore("HostNameToIPResolver");
	
	public static boolean
	isDNSName(
		String	host )
	{
		return( AENetworkClassifier.categoriseAddress( host ) == AENetworkClassifier.AT_PUBLIC );
	}
	
	public static boolean
	isNonDNSName(
		String	host )
	{
		return( AENetworkClassifier.categoriseAddress( host ) != AENetworkClassifier.AT_PUBLIC );
	}
	
	public static InetAddress
	syncResolve(
		String	host )
	
		throws UnknownHostException
	{
		if ( isNonDNSName( host )){
			
			throw( new HostNameToIPResolverException( "non-DNS name '" + host + "'", true ));
		}
		
			// handle any raw addresses up front
		
		byte[]	bytes = textToNumericFormat( host );

		if ( bytes != null ){
			
			return( InetAddress.getByAddress( bytes ));
		}
		
			// filter out partially complete raw addresses by applying the basic rule in ftp://ftp.is.co.za/rfc/rfc3696.txt
			// at least one dot + not all numeric
		
		char[]	chars = host.toCharArray();
		
		boolean	resolve = false;
		
		for (int i=0;i<chars.length;i++){
			
			if ( !( chars[i] == '.' || Character.isDigit(chars[i]))){
				
				resolve	= true;
				
				break;
			}
		}
		
		if ( resolve ){
			
			return( InetAddress.getByName( host));
			
		}else{
						
			throw( new UnknownHostException( "Host '" + host + "' doesn't obey minimal validation rules"));
		}
	}
	
	public static void
	addResolverRequest(
		String							host,
		HostNameToIPResolverListener	l )
	{
		byte[]	bytes = textToNumericFormat( host );
				
		if ( bytes != null ){
		
			try{
				l.hostNameResolutionComplete( InetAddress.getByAddress( host, bytes ));
			
				return;
				
			}catch( UnknownHostException e ){
			}
		}
		
		try{
			request_queue_mon.enter();
			
			request_queue.add( new request( host, l ));
			
			request_semaphore.release();
			
			if ( resolver_thread == null ){
				
				resolver_thread = 
					new AEThread2("HostNameToIPResolver",true)
					{
						public void
						run()
						{
							while(true){
								
								try{
									request_semaphore.reserve(30000);
									
									request	req;
									
									try{
										request_queue_mon.enter();
										
										if ( request_queue.isEmpty()){
											
											resolver_thread = null;
											
											break;
										}
										
										req	= (request)request_queue.remove(0);
										
									}finally{
										
										request_queue_mon.exit();
									}
									
									try{
										InetAddress addr = syncResolve( req.getHost());
										
										req.getListener().hostNameResolutionComplete( addr );
											
									}catch( Throwable e ){
										
										req.getListener().hostNameResolutionComplete( null );
										
									}
								}catch( Throwable e ){
									
									Debug.printStackTrace( e );
								}
							}
						}
					};
										
				resolver_thread.start();
			}
		}finally{
			
			request_queue_mon.exit();
		}
	}
	
	public static InetAddress
	hostAddressToInetAddress(
		String		host )
	{
		byte[]	bytes = hostAddressToBytes( host );
		
		if ( bytes != null ){
			
			try{
				return( InetAddress.getByAddress( bytes ));
				
			}catch( Throwable e ){
				
				return( null );
			}
		}
		
		return( null );
	}
	
	public static byte[]
	hostAddressToBytes(
		String		host )
	{
		byte[] res = textToNumericFormat( host );
		
		return( res );
	}
		
	final static int INADDRSZ	= 4;
	
	static byte[] 
	textToNumericFormat(
		String src )
	{
		if (src.length() == 0) {
		    return null;
		}
	
		if ( src.indexOf(':') != -1 ){
		
				// v6
			try{
				return( InetAddress.getByName(src).getAddress());
				
			}catch( Throwable e ){
				
				return( null );
			}
		}
		
		int octets;
		char ch;
		byte[] dst = new byte[INADDRSZ];
	
		char[] srcb = src.toCharArray();
		boolean saw_digit = false;

		octets = 0;
		int i = 0;
		int cur = 0;
		while (i < srcb.length) {
		    ch = srcb[i++];
		    if (Character.isDigit(ch)) {
			// note that Java byte is signed, so need to convert to int
			int sum = (dst[cur] & 0xff)*10
			    + (Character.digit(ch, 10) & 0xff);
			
			if (sum > 255)
			    return null;

			dst[cur] = (byte)(sum & 0xff);
			if (! saw_digit) {
			    if (++octets > INADDRSZ)
				return null;
			    saw_digit = true;
			}
		    } else if (ch == '.' && saw_digit) {
			if (octets == INADDRSZ)
			    return null;
			cur++;
			dst[cur] = 0;
			saw_digit = false;
		    } else
			return null;
		}
		if (octets < INADDRSZ)
		    return null;
		return dst;
	}
	
	
	
	protected static class
	request
	{
		protected String						host;
		protected HostNameToIPResolverListener	listener;
		
		protected
		request(
			String							_host,
			HostNameToIPResolverListener	_listener )
		{
			host			= _host;
			listener		= _listener;
		}
		
		protected String
		getHost()
		{
			return( host );
		}
		
		protected HostNameToIPResolverListener
		getListener()
		{
			return( listener );
		}
	}
}
