/*-------------------------------------------------------------------------
*
*	Copyright (C) 2005, PostgreSQL Global Development Group
*
*--------------------------------------------------------------------------
*/
package org.postgresql.net;

import java.io.Serializable;
import java.sql.SQLException;
import org.postgresql.util.GT;
import org.postgresql.util.PGobject;
import org.postgresql.util.PGtokenizer;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

/**
 *	This represents org.postgresql's cidr datatype, which is
 *	used to hold network addresses.
 *
 *	<p>
 *	This class wraps the postgresql specific CIDR datatype. It
 *	supports IPV4 network addresses in the following format.
 *	<p>
 *	a[.b[.c[.d]]][/e]
 *
 *	@author Russell Francis &lt; rfrancis@ev.net &gt;
 */
public class PGcidr extends PGobject implements Serializable, Cloneable
{
	protected int hashValue;

	/**
	 *	This constructor takes a string in the
	 *	cidr format a[.b[.c[.d]]][/e] and creates
	 *	a PGcidr to represent it.
	 *
	 *	@param s The representation of the cidr as a string.
	 *	@exception SQLException if the string is not in the proper format.
	 */
	public PGcidr( String s )
	throws SQLException
	{
		this();
		setValue( s );
	}

	/**
	 *	A simple constructor.
	 */
	public PGcidr()
	{
		setType( "cidr" );
	}

	/**
	 *	Set the value of this CIDR.
	 *
	 * 	<p>This accepts strings in the a[.b[.c[.d]]][/netmask] format.
	 *
 	 *	@param v The string representation of this network address.
	 *	@exception SQLException If it is not in a valid cidr format.
	 */
	public void setValue( String v )
	throws SQLException
	{
		int a = 0;
		int b = 0;
		int c = 0;
		int d = 0;
		int netmask = -1;

		if( v == null )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) );
		}

		PGtokenizer t = new PGtokenizer( v, '/' );

		int size = t.getSize();
		if( ( size != 1 ) && ( size != 2 ) )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) ); 
		}

		try
		{
			if( size == 2 )
			{
				// we have a netmask to read
				netmask = Integer.valueOf( t.getToken( 1 ) ).intValue();
				if( ( netmask < 0 ) || ( netmask > 32 ) )
				{
					throw( new PSQLException( 
						GT.tr( "Conversion to type {0} failed: {1}.", new Object[]{ type, v } ),
						PSQLState.DATA_TYPE_MISMATCH ) );
				}
			}

			// read the body a.b.c.d
			t = new PGtokenizer( t.getToken( 0 ), '.' );
			size = t.getSize();
			if( ( size < 1 ) || ( size > 4 ) )
			{
				throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
					new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) ); 
			}

			
			a = Integer.valueOf( t.getToken( 0 ) ).intValue();

			if( size >= 2 )
			{
				b = Integer.valueOf( t.getToken( 1 ) ).intValue();
			}

			if( size >= 3 )
			{
				c = Integer.valueOf( t.getToken( 2 ) ).intValue();
			}

			if( size >= 4 )
			{
				d = Integer.valueOf( t.getToken( 3 ) ).intValue();
			}
		}
		catch( NumberFormatException e )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v }), PSQLState.DATA_TYPE_MISMATCH, e ) );
		}

		if( ( a < 0 ) || ( a > 255 ) ||
			( b < 0 ) || ( b > 255 ) ||
			( c < 0 ) || ( c > 255 ) ||
			( d < 0 ) || ( d > 255 ) )
		{
			throw( new PSQLException( GT.tr( "Conversion to type {0} failed: {1}.",
				new Object[]{ type, v } ), PSQLState.DATA_TYPE_MISMATCH ) ); 
		}

		// If the netmask is not set in the parameter,	
		// we will take a guess like PG does.
		//
		//	1.0.0.0 - 127.0.0.0		-	class A, netmask 8
		//	128.0.0.0 - 191.0.0.0	- 	class B, netmask 16
		// 	192.0.0.0 - 223.0.0.0	-	class C, netmask 24
		if( netmask == -1 )
		{
			// start with a safe default, we will try to trim
			// this down depending on the values of a,b,c & d.
			netmask = 32;
			if( a >= 1 || a <= 127 )
			{
				if( b == 0 && c == 0 && d == 0 )
				{
					netmask = 8;
				}
				else if( c == 0 && d == 0 )
				{
					netmask = 16;
				}
				else if( d == 0 )
				{
					netmask = 24;
				}
			}
			else if( a >= 128 && a <= 191 )
			{
				if( c == 0 && d == 0 )
				{
					netmask = 16;
				}
				else if( d == 0 )
				{
					netmask = 24;
				}
			}
			else if( a >= 192 && a <= 223 )
			{
				if( d == 0 )
				{
					netmask = 24;
				}
			}
		}

		// verify that there are no bits to the right of the netmask
		if( netmask < 32 )
		{
			int address = ( a << 24 ) | ( b << 16 ) | ( c << 8 ) | d;
			address <<= netmask;
		
			if( address != 0 )
			{
				// There are bits to the right of the netmask
				throw( new PSQLException(  GT.tr( "Conversion to type {0} failed: {1}.",
					new Object[]{ type, v }), PSQLState.DATA_TYPE_MISMATCH ) );
			}
		}

		// Seems like it will generate a decent hash?
		this.hashValue = ((a ^ b) << 24) | ((b ^ c) << 8) | ((c ^ d) << 16) | (d ^ netmask);

		// ok, the parameter cleared all of out tests, 
		// a.b.c.d/netmask should contain out new CIDR value.
		this.value = "" + a + "." + b + "." + c + "." + d + "/" + netmask;
	}

	/**
 	 *	Get the hash code for this network address.
	 *
	 *	@return The hash value for this object.
	 */
	public int hashCode()
	{
		return( this.hashValue );
	}

	/**
	 *	Compare two PGcidr objects for equality.
	 *
	 *	<p>This will return true if the parameter obj is of type PGcidr
	 *	and represents the same network as this.
	 *
	 *	@param obj The object which we wish to compare.
	 *	@return true if it represents the same network as this, false otherwise.
	 */
	public boolean equals( Object obj )
	{
		if( obj instanceof PGcidr )
		{
			PGcidr cidr = (PGcidr)obj;
			if( this.getValue().equals( cidr.getValue() ) )
			{
				return( true );
			}
		}
		return( false );
	}

	/**
	 *	This will make a duplicate of the current PGcidr object.
	 *
	 *	@return null on failure, or a new PGcidr object which
	 *		represents the same network address as the invoking
	 *		object.
	 */
	public Object clone()
	{
		try
		{
			return( new PGcidr( this.getValue() ) );
		}
		catch( SQLException e )
		{
			return( null );
		}
	}
}
