/* Copyright Dassault Systemes, 1999, 2009 */



package examples.development.datatypes;

import com.engineous.sdk.vars.AbstractValue;
import com.engineous.sdk.vars.Value;
import com.engineous.sdk.vars.VarInvalidValueException;
import com.engineous.sdk.vars.VarUnsupportedOperationException;
import com.engineous.sdk.vars.VariableException;
import com.engineous.sdk.version.Version;


/**
 * Example of a simple user-defined data type.
 * This example implements a basic Complex data type with Real and Imaginary parts.
 * This example is complete and ready to run except for Internationalization.
 * Error handling is complete, though not all exceptions have informative messages.
 * <p>
 * Like most data types, this extends the class com.engineous.sdk.vars.AbstractValue
 * in order to pick up the default definitions of most methods.
 */
public class ComplexValue
		extends AbstractValue {

	/** Data members for real and imaginary parts. */
	private double realPart, imaginaryPart;

	/**
	 * Constructor.  Does nothing but set the default value for this object.
	 * @throws VariableException
	 */
	public ComplexValue()
			throws VariableException {

		// Always call super-class constructor.  Java does this automatically for no-args constructors,
		// but it is better to be explicit.
		super();

		// Initialize to a known state - the default value of (0.0, 0.0)
		setDefaultValue();

	}

	//--------------------------------------------------------------------------------------------
	//
	// Methods unique to this class.  These are difficult to call due to class loading issues
	// with the MetaModelManager unless this class is added to datatypes.jar.
	//
	//--------------------------------------------------------------------------------------------

	/**
	 * Get the real part of the complex number.
	 * @return double
	 */
	public double getRe() {
		return realPart;
	}

	/**
	 * Get the imaginary part of the real number.
	 * @return double
	 */
	public double getIm() {
		return imaginaryPart;
	}

	/**
	 * Get the absolute value - the distance from the origin.
	 * (this may not be the proper name for this, but it's been a long time since I did complex math).
	 * @return Absolute value of this Complex number as a real number.
	 */
	public double getAbs() {
		return Math.sqrt(realPart * realPart + imaginaryPart * imaginaryPart);
	}

	/**
	 * Set the real part, leave the imaginary part unchanged.
	 * @param re New real part
	 * @throws VariableException
	 */
	public void setRe(double re)
			throws VariableException {
		// delegate to setValue(double, double)
		setValue(re, imaginaryPart);
	}

	/**
	 * Set the Imaginary part, leave the real part unchanged.
	 * @param im New value of the Imaginary part.
	 * @throws VariableException
	 */
	public void setIm(double im)
			throws VariableException {
		// delegate to setValue(double, double)
		setValue(realPart, im);
	}

	/**
	 * Set the new value as a real,imaginary pair of doubles
	 * @param re New Real part
	 * @param im New Imaginary part
	 * @throws VariableException
	 */
	public void setValue(double re, double im)
			throws VariableException {

		if ((realPart == re) && (imaginaryPart == im)) {
			return;
		}
		
		ComplexValue oldValue = new ComplexValue();
		oldValue.imaginaryPart= this.imaginaryPart;
		oldValue.realPart = this.realPart;
		
		this.realPart = re;
		this.imaginaryPart = im;
				
		fireValueChangeEvent(oldValue);

	}

	// Could add many other utility methods here to perform operations on a complex number
	// and return a new Complex value that is the result of applying the operation to This
	// or to This plus some argument.  Some possibilities:
	//     ComplexValue negate()
	//     ComplexValue add(ComplexValue rhs)
	//     ComplexValue log()
	// Or make these static:
	//     static ComplexValue log(ComplexValue)
	//     static ComplexValue add(ComplexValue left, ComplexValue right)

	//--------------------------------------------------------------------------------------------
	//
	// Implementation of Value interface
	//
	//--------------------------------------------------------------------------------------------

	/**
	 * All Value objects must implement clone.
	 * In this case, the default clone operation is sufficient, so delegate to that.
	 * If there were complex objects as local data, they would have to be cloned in addition.
	 * @return A clone of this object with the same value, but no listeners.
	 * @throws CloneNotSupportedException
	 * @see com.engineous.sdk.vars.AbstractValue#clone()
	 */
	public ComplexValue clone()
			 {
		return (ComplexValue)super.clone();
	}

	/**
	 * Private method to test if the current value is representable as a real number.
	 * This returns true if the imaginary part is exactly 0.0.
	 * This could be made less sensitive to rounding error by instead checking for an imaginary part
	 * less than some value likely to be produced by rounding, such as 1e-15.
	 * @return true if this Complex number can be represented as a Real number.
	 */
	private boolean isRealNumber() {
		return imaginaryPart == 0.0;
	}

	/**
	 * Return this value as an integer.  This is permissive - if the imaginary part is 0, treat this the
	 * same way a Real value does and return the truncated part of the real part.
	 * @return Truncated value of the real part, provided the imaginary part is zero.
	 * @throws VarUnsupportedOperationException
	 * @throws VariableException if the imaginary part is non-zero.
	 * @see com.engineous.sdk.vars.AbstractValue#getAsInt()
	 */
	public long getAsInt()
			throws VarUnsupportedOperationException, VariableException {

		if (isRealNumber()) {
			return (long)realPart;
		}
		else {
			throw new VarInvalidValueException();
		}

	}

	/**
	 * Return this value as an Real number.  This is permissive - if the imaginary part is 0, return the
	 * real part.
	 * @return The real part, provided the imaginary part is zero.
	 * @throws VarUnsupportedOperationException
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#getAsObject()
	 */
	public double getAsReal()
			throws VarUnsupportedOperationException, VariableException {

		if (isRealNumber()) {
			return realPart;
		}
		else {
			throw new VarInvalidValueException();
		}

	}

	/**
	 * Return the value as a String.  The 'store' format is used - real part, comma, imaginary part.
	 * @return A String representing the value of the Complex number.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#getAsString()
	 */
	public String getAsString()
			throws VariableException {
		return store();
	}

	/**
	 * Returns the value of this Complex number as an Object.
	 * The returned value of this method should meet the following requirements:
	 * <ul>
	 *   <li>It is NOT tied to the Value object - changing the returned object must not change this.
	 *       OR the returned object is immutable and cannot be changed at all.
	 *   <li>The value returned by getAsObject() must be valid to pass to setValue(Object)
	 *   <li>Where possible, it is a Java type (as opposed to user-defined type)
	 *   <li>Returning this.clone() is allowed but is a last resort.
	 * </ul>
	 * Other than the above requirements, the returned value is arbitrary.  In this case, an array of two
	 * doubles is returned, with the first element the real part and the second the imaginary part.
	 * @return Array of two doubles.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#getAsReal()
	 */
	public Object getAsObject()
			throws VariableException {
		return new double[]{ realPart, imaginaryPart };
	}

	/**
	 * Set the value from a Real number.  The imaginary part is set to 0.0.
	 * @param value Real value to assign.
	 * @throws VarUnsupportedOperationException
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#setValue(double)
	 */
	public void setValue(double value)
			throws VarUnsupportedOperationException, VariableException {
		setValue(value, 0.0);
	}

	/**
	 * Set the value from an Integer.  Treat the Integer as a real number.
	 * @param value New Real part.
	 * @throws VarUnsupportedOperationException
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#setValue(long)
	 */
	public void setValue(long value)
			throws VarUnsupportedOperationException, VariableException {
		// Delegate to setValue(double).
		setValue((double)value);
	}

	/**
	 * Set the value from a String.
	 * The string must be a valid Number, or two Numbers separated by a comma.
	 * A single number is treated as the real part, with 0.0 for the imaginary part.
	 * @param value New value as a Real number or two Numbers separated by a comma.
	 * @throws VarUnsupportedOperationException
	 * @throws VariableException if the String cannot be converted into a complex number.
	 * @see com.engineous.sdk.vars.AbstractValue#setValue(java.lang.String)
	 */
	public void setValue(String value)
			throws VarUnsupportedOperationException, VariableException {

		int i = value.indexOf(',');
		if (i >= 0) {
			// probably 2 numbers, try to use them.
			String reString = value.substring(0, i);
			String imString = value.substring(i + 1);
			setValue(stringToDouble(reString), stringToDouble(imString));
		}
		else {
			// assume it is one number, use it.
			setValue(stringToDouble(value), 0.0);
		}

	}

	/**
	 * Utility to convert a string of (hopefully) integers into a double.
	 * Throws VariableException if the conversion isn't possible.
	 * @param s String to convert.  Should consist of only a real number possibly surrounded by white space.
	 * @return Value of string as a double
	 * @throws VariableException if the string is not a valid double
	 */
	private double stringToDouble(String s)
			throws VariableException {

		try {
			return Double.parseDouble(s.trim());
		}
		catch (NumberFormatException ex) {
			throw new VarInvalidValueException(ex);
		}

	}

	/**
	 * Set the value of this from an Object.
	 * The object should be either a double[2] containing real and imaginary parts, or a Number
	 * (including Double and Long) containing the Real part.
	 * @param value Object to assign from.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#setValue(java.lang.Object)
	 */
	public void setValue(Object value)
			throws VariableException {

		if (value.getClass() == (new double[2]).getClass()) {
			// Argument is double[], so assign directly.
			double[] dvalue = (double[])value;
			if (dvalue.length != 2) {
				throw new VarInvalidValueException();
			}
			setValue(dvalue[0], dvalue[1]);
		}
		else if (value instanceof Number) {
			// Single number (real or integer).  Use it as real part.
			setValue(((Number)value).doubleValue(), 0.0);
		}
		else {
			super.setValue(value);
		}

	}

	// NOTE: did not override getAsBoolean() or setValue(boolean).  The default implementation in 
	// AbstractValue just throws VarUnsupportedOperationException which is correct for a Complex number.

	/**
	 * Allow this object to be assigned from some other Value object.
	 * Like all of the setValue() methods, this is permissive - it converts the From value to a complex
	 * number if there is any reasonable way to do so.
	 * Also like those methods, it fires a ValueChanged event if the assignment actually modifies the value.
	 * @param from Value to extract the value from.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#assignFrom(com.engineous.sdk.vars.Value)
	 */
	public void assignFrom(Value from)
			throws VariableException {

		if (from instanceof ComplexValue) {
			// Assignment from another ComplexValue.  Directly assign the internal values.
			ComplexValue fromComplex = (ComplexValue)from;
		
			setValue(fromComplex.getRe(), fromComplex.getIm());			
		}
	}

	/**
	 * Is this value representable as a single number.
	 * This is almost never true, except for data types Real and Integer.
	 * @return false as a Complex number cannot be uniquely represented by a unique Number.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#isNumericallyRepresentable()
	 */
	public boolean isNumericallyRepresentable()
			throws VariableException {
		return false;
	}

	/**
	 * Set the value of this to the default value for the type.
	 * In this case, the default value is 0.0 + 0.0j.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#setDefaultValue()
	 */
	public void setDefaultValue()
			throws VariableException {
		this.realPart = 0.0;
		this.imaginaryPart = 0.0;
	}

	/**
	 * Convert the value into a String for the specific purpose of storing it in the Model XML (.zmf file).
	 * The value does not have to be human-readable, but it is nice if it is.
	 * In this case, it is the real part and imaginary part, converted to the Java format for real constants,
	 * separated by a comma.
	 * @return String containing the real part, a comma, and the imaginary part.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#store()
	 */
	public String store()
			throws VariableException {
		return Double.toString(realPart) + "," + Double.toString(imaginaryPart);
	}

	/**
	 * Restore the value from a string in the format returned by 'store'.
	 * Used to read values from Model files.
	 * This method can NOT delegate to setValue(String) because that method fires a change event and this method
	 * must not.
	 * @param versionWritten
	 * @param momento String form of this value obtained from the Model file
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#restore(com.engineous.sdk.version.Version, java.lang.String)
	 */
	public void restore(Version versionWritten, String momento)
			throws VariableException {

		if ((momento == null) || (momento.trim().length() == 0)) {
			// No momento means use default value.
			setDefaultValue();
			return;
		}

		int i = momento.indexOf(',');
		if (i >= 0) {
			// probably 2 numbers, try to use them.
			realPart = stringToDouble(momento.substring(0, i));
			imaginaryPart = stringToDouble(momento.substring(i + 1));
		}
		else {
			// Be permissive, assume it is one number, use it.
			realPart = stringToDouble(momento);
		}
	}


	/**
	 * Return the value in a format to be stored in the Isight results database.
	 * The returned value is an array of Objects.  The same object types, in the same order must be
	 * returned from EVERY call to this method, and must match the objects returned by method storeBinTypes()
	 * The elements in the returned array must be of the following types:
	 * <ul>
	 *   <li>Boolean
	 *   <li>Integer
	 *   <li>Long
	 *   <li>Float
	 *   <li>Double
	 *   <li>String
	 *   <li>char[]
	 *   <li>byte[]
	 * </ul>
	 * @return Array of two Double objects
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#storeBin()
	 */
	public Object[] storeBin()
			throws VariableException {
		return new Object[]{ new Double(realPart), new Double(imaginaryPart) };
	}

	/**
	 * Restore the state of this given an Object array that came from storeBin.
	 * Be permissive in case the data is missing.
	 * @param versionWritten
	 * @param data an array of two Double objects containing the Real and Imaginary parts.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.AbstractValue#restoreBin(com.engineous.sdk.version.Version, java.lang.Object[])
	 */
	public void restoreBin(Version versionWritten, Object[] data)
			throws VariableException {

		if ((data == null) || (data.length == 0)) {
			// No data, use default value
			setDefaultValue();
		}
		else {
			// Just assume the correct format and convert any ArrayIndexException or ClassCastException into a VariableException.
			try {
				realPart = ((Double)data[0]).doubleValue();
				imaginaryPart = ((Double)data[1]).doubleValue();
			}
			catch (Exception ex) {
				throw new VarInvalidValueException(ex);
			}
		}

	}

	/**
	 * Return a prototype that matches all returned values from storeBin().
	 * This is used to create database tables for results.
	 * @return A static prototype value.
	 * @throws VariableException
	 * @see com.engineous.sdk.vars.Value#storeBinTypes()
	 */
	public Object[] storeBinTypes()
			throws VariableException {
		return STORE_BIN_TYPES;
	}

	private static final Object[] STORE_BIN_TYPES = { new Double(0.0), new Double(0.0) };

	/**
	 * Implement the Comparable interface.  All Value types must implement this so sorting by the Value column
	 * in the Variable Bean works.  The order does NOT have to make sense (there is no consistent ordering of
	 * Complex numbers), it just has to look reasonable and be consistent if the same set is sorted repeatedly.
	 * Only comparison to other ComplexValue types needs to be implemented - the Model infrastructure sorts different
	 * Value types into an arbitrary order relative to each other, and then sorts each type within its Value type.
	 * <p>
	 * This method implements a lexicographic order - order by the real part.  If the real parts are equal,
	 * order by the imaginary parts.
	 * @param o Other object, must be a ComplexValue.
	 * @return -1, 0, or 1 depending on whether this is <, ==, or > o.
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Object o) {

		ComplexValue other = (ComplexValue)o;
		int result = Double.compare(realPart, other.realPart);
		if (result == 0) {
			result = Double.compare(imaginaryPart, other.imaginaryPart);
		}
		return result;

	}
}
