package edu.vt.marian.common;

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


/**
 *	The identifier of an object in the MARIAN universe is composed
 *		of class id and instance id.  Class id can be regarded as database id and
 *		instance id can be regarded as a unique id in that database.
 *	<P>NOTE: classID is properly an unsigned 16-bit quantity; instanceID an unsigned
 *	    32-bit.  Unfortunately there are no unsigneds in Java, so we use an int for
 *	    classID and test the bounds.  Properly, we should use a long for instanceID,
 *	    but that's a lot of unused bits so we'll use an int and keep our fingers
 *	    crossed.
 *	@author	Robert France, Jianxin Zhao
 *	<P>designer(s): Robert France (france@vt.edu)
 *	<P>implementator(s): Jianxin Zhao (jxzhao@csgrad.cs.vt.edu), Robert France
 *	<P>finished time: 
 *	<P>known bugs: 
 *	<P>JDK version: 1.1.5
 *	<P>side effects:
 */
public class FullID
{
	/** just for debugging	*/
	protected Debug debug;

	protected int classID;
	protected int instanceID;

	protected final static int MAX_CLASS_ID = 65535;	// 2**16 - 1.
	protected final static int MAX_INSTANCE_ID = 2147483647; // 2**31 (see above).

	/** return values for methods of this class	*/
	public final static int OK = 0;
	public final static int BAD_PARAMS = -2;
	public final static int NULL_STREAM = 7;

	/**
		Create an empty full id object
			where both classID and instanceID have been set to 0 (ABSURD).
		@param	debug	used for debugging
	*/      
	public FullID(Debug debug)
	{
		// first initialize data members
		classID = 0;
		instanceID = 0;

		this.debug = debug;
	}


	/**
		Create a full id object
			with the specified class id and instance id values.
		@param	clID	class ID value;
		@param	instID	instance ID value;
		@param	debug	used for debugging
	*/
	public FullID(int clID, int instID, Debug debug)
	{
		// first initialize data members
		classID = clID;
		instanceID = instID;

		this.debug = debug;
	}


	/**
		Create a full id object
			from another one.  The created object will have
			the same class id and instance id as the parameter object.
		@param	id	the object to be copied
		@param	debug	used for debugging
	*/      
	public FullID(FullID id, Debug debug)
	{
		// first initialize data members
		this.debug = debug;
		if (id == null)
		{
			debug.dumpTrace("FullID.[constructor 2], parameter id is null");
			classID = 0;
			instanceID = 0;
		}
		else	// id is not null
		{
			classID = id.classID;
			instanceID = id.instanceID;
		}
	}


	/**
		Create a full id object from the specified stream.
		@param	br	the stream to read out a full id object;
		@param	debug	used for debugging
	*/  
	public FullID(BufferedReader br, Debug debug)
	{
		this.debug = debug;
		classID = 0;
		instanceID = 0;

		if (br == null)
		{
			debug.dumpTrace("FullID.[BR constructor]: parameter stream is null");
			return;
		}

		// br is not null, read class id
		int numLines = 0;
		try
		{
			numLines = Integer.parseInt(br.readLine());
		}
		catch (Exception e0)
		{
			debug.dumpTrace("FullID.[BR constructor]: error reading number of lines");
		}
		int i;
		String s = new String("");
		try
		{
			for (i = 0; i < numLines; i++)
			{
				if (i == 0)
				{
					// the beginning
					s += br.readLine();
				}
				else
				{
					// not the first line, so add "\n" between them
					s += "\n" + br.readLine();
				}
			}
		}
		catch (IOException e1)
		{
			debug.dumpTrace("FullID.[BR constructor]: error reading class id");
		}
		try
		{
			setClassID(Integer.parseInt(s));
		}
		catch (Exception e)
		{
			debug.dumpTrace("FullID.[BR constructor]: error parsing Class ID.");
			return;
		}
		// read object id
		try
		{
			numLines = Integer.parseInt(br.readLine());
		}
		catch (Exception e0)
		{
			debug.dumpTrace("FullID.[BR constructor]: error reading number of lines");
		}
		s = new String("");
		try
		{
			for (i = 0; i < numLines; i++)
			{
				if (i == 0)
				{
					// the beginning
					s += br.readLine();
				}
				else
				{
					// not the first line, so add "\n" between them
					s += "\n" + br.readLine();
				}
			}
		}
		catch (IOException e1)
		{
			debug.dumpTrace("FullID.[BR constructor]: error reading instance ID");
		}
		try
		{
			setInstanceID(Integer.parseInt(s));
		}
		catch (Exception e)
		{
			debug.dumpTrace("FullID.[BR constructor]: error parsing instance ID.");
			return;
		}
	}


	/**
		Test whether or not the fullID is valid.
		<P>Note:  The null object of any class (#classID:0#) is always valid.
			However, objects with a null classID are <I>not</I>.  This is
			because the null class (CLASS_ABSURD) has no members.
		@return	true / false
	*/      
	public boolean isValid()
	{
		return( (classID > 0) && (classID <= MAX_CLASS_ID) &&
		        (instanceID >= 0) && (instanceID <= MAX_INSTANCE_ID) );
	}


	/**
		Set the class id of this object.
		<P>uses the services of class(es): none
		@param	clID	the class id of this object will be set to this value.
		@return	OK == the value has been set;
		<BR>	BAD_PARAMS == invalid value for clID.
	*/      
	public int setClassID(int clID)
	{
		if ( (clID < 0) || (clID > MAX_CLASS_ID) )
			return BAD_PARAMS;
		this.classID = clID;
		return OK;
	}


	/**
		Return the class id of this object.
		@return	the class id of this object as an integer
	*/      
	public int getClassID()
	{
		return classID;
	}


	/**
		Set the instance id of this object.
		@param	instID	the instance id of this object will be set to this
				value
		@return	OK == the value has been set;
		<BR>	BAD_PARAMS == invalid value for instID.
	*/      
	public int setInstanceID(int instID)
	{
		if ( (instID < 0) || (instID > MAX_INSTANCE_ID) )
			return BAD_PARAMS;
		this.instanceID = instID; 
		return OK;
	}


	/**
		Return the instance id of this object.
		@return	the instance id of this object as an integer
	*/      
	public int getInstanceID()
	{
		return instanceID;
	}


	/**
		Tell whether this object has the 
			same class id and instance id as the parameter object.
		@param	id	the object used to compare with this object
		@return	true == this object has the same class id and instance id as the
				parameter object.
		<BR>	false == this object doesn't have the same class id and instance id
				as the parameter object.
	*/      
	public boolean equals(Object o)
	{
		if (o == null)
		{
			debug.dumpTrace("FullID.equals():  null parameter.");
			return false;
		}

		if (o instanceof FullID)
		{
			return( (classID == ((FullID) o).classID) &&
			        (instanceID == ((FullID) o).instanceID) );
		}

		debug.dumpTrace("FullID.equals(): parameter not of this class.");
		return false;
	}


	/**
	 *	Generate a hash value suitable for java.util.HashTable.
	 *	@return	a positive integer.
	 */
	public int hashCode()
	{
		return ( (classID << 16) | instanceID & 0x7FFFFFFF );
			// Bit AND replaces symbolic "if (ii<0) ii = -ii;".
	}


	/**
		Pack this into a byte array for compact storage.
		@param	bos	a ByteArrayOutputStream into which to pack this.
		@return	OK == all jake.
		<BR>	NULL_STREAM == could not write.
	*/      
	public int writePacked(ByteArrayOutputStream bos)
	{
		DataOutputStream dos = new DataOutputStream(bos);
		try
		{
			dos.writeShort(classID);
			dos.writeInt(instanceID);
		} catch (Exception e)
		  {
			debug.dumpTrace("Weight.writePacked():  cannot write: " +
			                e.getMessage());
			return NULL_STREAM;
		   }
		return(OK);
	}

	/**
		Read this out of compact storage in a byte array.
		@param	bis	a ByteArrayInputStream from which to unpack this.
		@return	OK == all jake.
		<BR>	NULL_STREAM == could not read.
	*/      
	public int readPacked(ByteArrayInputStream bis)
	{
		DataInputStream dis = new DataInputStream(bis);
		try
		{
			classID = dis.readUnsignedShort();
			instanceID = dis.readInt();
		} catch (Exception e)
		  {
			debug.dumpTrace("Weight.readPacked():  cannot read: " +
			                e.getMessage());
			return NULL_STREAM;
		   }
		return(OK);
	}


	/**
		Convert this to a human-readable string.
		@return	"@null@" if this is not a valid fullID;
			"#classID:instanceID#" if it is.
	*/      
	public String toString()
	{
		if (! isValid() )
			return( new String("@null@") );
		else
			return( new String ("#" + classID + ":" + instanceID + "#") );
	}
	

	/**
		Print the content of this object to a specified stream.
		@param	pw	the stream to write this object
		@return	OK == this object has been write to the stream correctly.
		<BR>	NULL_STREAM == could not read.
	*/      
	public int toStream(PrintWriter pw)
	{
		if (pw == null)
		{
			debug.dumpTrace("FullID.toStream(): parameter stream is null");
			return NULL_STREAM;
		}
		// pw is not null

		// write class id
		String s = Integer.toString(getClassID());
		pw.println(1);		// ClassID (and InstanceID below) will always be
		pw.println(s);		//   a simple value, thus using only one line.

		// write instance id
		s = Integer.toString(getInstanceID());
		pw.println(1);
		pw.println(s);
		pw.flush();

		return OK;
	}

}
