package com.sharkysoft.math;

import java.math.BigDecimal;

/**
 * Real value in scientific notation.
 *
 * <p><b>Details:</b> A <code>MantissaExponent</code> represents the base 10
 * "scientific notation" of a real value.  Instances of this class are returned
 * exclusively by <code>MathToolbox.toScientificNotation</code>.</p>
 *
 * @author Sharky
 */
public final class MantissaExponent
{

	/**
	 * Exponent value for NaN.
	 * 
	 * <p><b>Details:</b> If {@link #getMantissa()} for an instance of 
	 * <code>MantissaExponent</code> returns <code>null</code> and 
	 * {@link #getExponent()} for the same instance returns <code>NAN</code>, 
	 * then the instance represents the NaN value.</p>
	 */
	public static final int NAN = 0;

	/**
	 * Exponent value for NaN.
	 * 
	 * <p><b>Details:</b> If {@link #getMantissa()} for an instance of 
	 * <code>MantissaExponent</code> returns <code>null</code> and 
	 * {@link #getExponent()} for the same instance returns <code>NEG_INF</code>, 
	 * then the instance represents negative infinity.</p>
	 */
	public static final int NEG_INF = -1;

	/**
	 * Exponent value for NaN.
	 * 
	 * <p><b>Details:</b> If {@link #getMantissa()} for an instance of 
	 * <code>MantissaExponent</code> returns <code>null</code> and 
	 * {@link #getExponent()} for the same instance returns 
	 * <code>POS_INF</code>, then the instance represents positive infinity.</p>
	 */
	public static final int POS_INF = +1;

	private static final BigDecimal gpBdNan = new BigDecimal(NAN);

	private static final BigDecimal gpBdNegInf = new BigDecimal(NEG_INF);

	private static final BigDecimal gpBdPosInf = new BigDecimal(POS_INF);

	private static final BigDecimal gpBdPos10 = BigDecimal.valueOf(+10);

	private static final BigDecimal gpBdNeg10 = BigDecimal.valueOf(-10);

	private static final BigDecimal gpBdPos1 = BigDecimal.valueOf(+1);

	private static final BigDecimal gpBdNeg1 = BigDecimal.valueOf(-1);

	////////////////
	//            //
	//  Mantissa  //
	//            //
	////////////////

	/**
	 * Mantissa.
	 *
	 * <p><b>Details:</b> Property <code>Mantissa</code> is the mantissa part of
	 * the scientific notation.</p>
	 *
	 * @see #getMantissa()
	 */
	protected final BigDecimal mpMantissa;

	/**
	 * Retrieves Mantissa property.
	 *
	 * @return current value
	 *
	 * @see #mpMantissa
	 */
	public BigDecimal getMantissa()
	{
		return mpMantissa;
	}

	////////////////
	//            //
	//  Exponent  //
	//            //
	////////////////

	/**
	 * Exponent.
	 *
	 * <p><b>Details:</b> Property <code>Exponent</code> is the exponent part of
	 * the scientific notation.</p>
	 *
	 * @see #getExponent()
	 */
	protected final int mnExponent;

	/**
	 * Retrieves Exponent property.
	 *
	 * @return current value
	 *
	 * @see #mnExponent
	 */
	public int getExponent()
	{
		return mnExponent;
	}

	////////////////////
	//                //
	//  constructors  //
	//                //
	////////////////////

	/**
	 * Computes mantissa and exponent.
	 *
	 * <p><b>Details:</b> This constructor computes the scientific notation
	 * mantissa and exponent of the supplied real value.</p>
	 *
	 * @param ipBig the real value
	 */
	MantissaExponent(BigDecimal ipBig)
	{
		// Recognize special signals for special values.
		if (ipBig == gpBdNan || ipBig == gpBdNegInf || ipBig == gpBdPosInf)
		{
			mpMantissa = null;
			mnExponent = ipBig.intValue();
			return;
		}
		int vnExponent = 0;
		switch (ipBig.signum())
		{
		case 1 :
			if (ipBig.compareTo(gpBdPos10) >= 0)
			{
				do
				{
					ipBig = ipBig.movePointLeft(1);
					++ vnExponent;
				}
				while (ipBig.compareTo(gpBdPos10) >= 0);
			}
			else if (ipBig.compareTo(gpBdPos1) < 0)
			{
				do
				{
					ipBig = ipBig.movePointRight(1);
					-- vnExponent;
				}
				while (ipBig.compareTo(gpBdPos1) < 0);
			}
			break;
		case -1 :
			if (ipBig.compareTo(gpBdNeg10) <= 0)
			{
				do
				{
					ipBig = ipBig.movePointLeft(1);
					++ vnExponent;
				}
				while (ipBig.compareTo(gpBdNeg10) <= 0);
			}
			else if (ipBig.compareTo(gpBdNeg1) > 0)
			{
				do
				{
					ipBig = ipBig.movePointRight(1);
					-- vnExponent;
				}
				while (ipBig.compareTo(gpBdNeg1) > 0);
			}
			break;
		}
		mpMantissa = ipBig;
		mnExponent = vnExponent;
	}

	private static BigDecimal getBig(final float ifValue)
	{
		if (ifValue == Float.POSITIVE_INFINITY)
			return gpBdPosInf;
		if (ifValue == Float.NEGATIVE_INFINITY)
			return gpBdNegInf;
		if (Float.isNaN(ifValue))
			return gpBdNan;
		return new BigDecimal(ifValue);
	}

	/**
	 * Computes mantissa and exponent.
	 *
	 * <p><b>Details:</b> This constructor computes the scientific notation
	 * mantissa and exponent of the supplied real value.</p>
	 *
	 * @param ifVal the real value
	 */
	MantissaExponent(final float ifVal)
	{
		this(getBig(ifVal));
	}

	private static BigDecimal getBig(final double idValue)
	{
		if (idValue == Double.POSITIVE_INFINITY)
			return gpBdPosInf;
		if (idValue == Double.NEGATIVE_INFINITY)
			return gpBdNegInf;
		if (Double.isNaN(idValue))
			return gpBdNan;
		return new BigDecimal(idValue);
	}

	/**
	 * Computes mantissa and exponent.
	 *
	 * <p><b>Details:</b> This constructor computes the scientific notation
	 * mantissa and exponent of the supplied real value.</p>
	 *
	 * @param idVal the real value
	 */
	MantissaExponent(double idVal)
	{
		this(getBig(idVal));
	}

	/**
	 * Generates scientific notation string.
	 *
	 * <p><b>Details:</b> <code>toString</code> renders this real value to a
	 * string in scientific notation, using the format "Mantissa*10^Exponent".
	 * If this value is not rational (i.e., NaN or Infinite), then this method
	 * returns the same string as <code>Float.toString(float)</code> would for 
	 * these special values.</p>
	 *
	 * @return the string
	 */
	public String toString()
	{
		if (mpMantissa == gpBdNan)
			return Float.toString(Float.NaN);
		if (mpMantissa == gpBdNegInf)
			return Float.toString(Float.NEGATIVE_INFINITY);
		if (mpMantissa == gpBdPosInf)
			return Float.toString(Float.POSITIVE_INFINITY);
		return mpMantissa + "*10^" + mnExponent;
	}

}

