package edu.vt.marian.common;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;

import edu.vt.marian.common.Sortable;


/**
 * The weight of an object in some approximate context (e.g., match to
 * a query).
 * @author Jianxin Zhao
 * @author Robert France
 * @author Paul Mather
 * @version $Id: Weight.java,v 1.3 2000/09/21 18:40:07 france Exp $
 */

public class Weight implements Sortable
{
    /** the underlying value of this object.	*/
    private int value = -1;		// Always created INVALID.
    
    /** the bounds of reasonable weight values.	*/
    private final static int upper_limit = 65536;	// 2**16
    private final static int lower_limit = 0;

    /** Public versions of the bounds for reasonable values.	*/
    public final static Weight topWt = new Weight(upper_limit, null);
    public final static Weight bottomWt = new Weight(lower_limit, null);
	
    /** the methods' return values.	*/
    public final static int EQUAL = Sortable.EQUAL;
    public final static int HIGHER = Sortable.GREATER;
    public final static int LOWER = Sortable.LESS;
    public final static int NOT_APPLY = 6;
    public final static int NULL_STREAM = 7;

    /** this is just used for debugging */
    Debug debug;


    /**
     * Create an invalid Weight object.
     * @param debug used for debugging
     */      
    public Weight(Debug debug)
    {
	this.debug = debug;
    }


    /**
     * Create a Weight object based on a specified value.
     * @param i the underlying value for this Weight object
     * @param debug used for debugging
     */      
    public Weight(int i, Debug debug)
    {
	this.debug = debug;
	value = i;
    }


    /**
     * Create a Weight object based on a specified value.
     * @param f the value of this Weight object;
     * @param debug used for debugging
     */      
    public Weight(double f, Debug debug)
    {
	this.debug = debug;
	value = (int) (f * upper_limit);
    }


    /**
     * Create a Weight object based on another Weight object.
     * @param	w	the Weight object to copy.
     */      
    public Weight(Weight w)
    {
	this.debug = w.debug;
	this.value = w.value;
    }


    /**
     * Create a Weight object from an input stream.
     * @param br the stream to read out this object
     * @param debug used for debugging
     */      
    public Weight(BufferedReader br, Debug debug)
    {
	this.debug = debug;

	if (br == null)
	{
	    debug.dumpTrace("Weight.[constructor 2]: stream is null");
	}

	// br is not null, read out value
	try
	{
	    value = Integer.parseInt(br.readLine());
	}
	catch (Exception e)
	{
	    debug.dumpTrace("Weight:[constructor 2]: error (" + e.getMessage() +
			    ") while reading value from stream.");
	    return;
	}
    }


    /**
     * Tell whether or not this Weight object is valid.
     * @return true if this weight has a valid value set; false otherwise.
     */      
    public boolean isValid()
    {
	return( (value <= upper_limit) && (value >= lower_limit) );
    }


    /**
     * Return the result of the comparision between this Weight object
     * and the parameter Weight object.
     * @param w the Weight object used to compared with this object
     * @return EQUAL -- their values are equal;
     * HIGHER -- the value of this object is higher than the parameter object;
     * LOWER -- the value of this object is lower than the parameter object.
     */
    public int compare(Weight w)
    {
	if (w == null)
	{
	    debug.dumpTrace("Weight.compare(): parameter w is null");
	    return NOT_APPLY;
	}

	// w is not null
	if (value > w.getValue())
	{
	    return HIGHER;
	}

	if (value < w.getValue())
	{
	    return LOWER;
	}

	// they are equal
	return EQUAL;
    }

    /*
     * Sortable interface comparison method.
     * @param obj Object being compared to this weight
     * @return integer indicating how we compare to given value
     * @see edu.vt.marian.common.Sortable
     */
    public int compare(Object obj)
    {
	if (obj instanceof Weight)
	    return this.compare((Weight) obj);
	else
	    return Sortable.INCOMPARABLE;
    }

    /**
     * Scale one Weight by another.
     * @param	w the Weight object used to scale this object.
     * <P>NOTE:  We are using the integer values lower_limit..upper_limit
     * to mimic the real number interval 0..1.  As long as
     * lower_limit == 0) we need only adjust to keep values
     * proportionally under upper_limit, which we treat both
     * as the top value and as multiplicative identity.  We
     * achieve this by multiplying the two "value" fields,
     * then shifting the result right by 16 bits (effectively
     * dividing by 2**16).
     */
    public void scale(Weight w)
    {
	value = (value * w.value) >> 16;
    }

    /**
     * Scale one Weight by a real constant.
     * @param f	the double precision value used to scale this object.
     */      
    public void scale(double f)
    {
	value = (int) (f * value);
    }


    /**
     * "Add" one Weight to another.
     * @param w the Weight object to be accumulated into this object.
     */      
    public void accum(Weight w)
    {
	value = value + w.value;
    }

    /**
     * "Add" a float between 0..1 to this object.
     * @param f the double precision value to be accumulated into
     * this object.
     */
    public void accum(double f)
    {
	value = value + (int) (f * upper_limit);
    }


    /**
     * Return the value of this Weight object.
     * @return	the value of this Weight object as a double.
     */      
    public double getValue()
    {
	return (double) value / (double) upper_limit;
    }

    public int getUnderlyingValue()
    {
	return value;
    }

    public void set(Weight w)
    {
	if (w == null)
	{
	    debug.dumpTrace("Weight.set(): parameter w is null");
	    value = -1;
	}
	value = w.getUnderlyingValue();
    }

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

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


    /**
     * Print the content of this object to a specified stream.
     * @param	pw the stream to write this object
     * @return	OK -- the object has been written to the stream correctly;
     * NULL_STREAM -- the parameter stream is null 
     */      
    public int toStream(PrintWriter pw)
    {
	if (pw == null)
	{
	    debug.dumpTrace("Weight.toStream(): stream is null");
	    return ReturnCodes.NULL_STREAM;
	}

	// pw is not null
	pw.println(value);
	pw.flush();
	return ReturnCodes.OK;
    }
}
