////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2003-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.formatters
{

/**
 *  The NumberBase class is a utility class that contains
 *  general number formatting capabilities, including rounding,
 *  precision, thousands formatting, and negative sign formatting.
 *  The implementation of the formatter classes use this class.
 *
 *  @see mx.formatters.NumberFormatter
 *  @see mx.formatters.NumberBaseRoundType
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class NumberBase
{
    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 * 
	 *  @param decimalSeparatorFrom Decimal separator to use
	 *  when parsing an input String.
	 *
	 *  @param thousandsSeparatorFrom Character to use
	 *  as the thousands separator in the input String.
	 *
	 *  @param decimalSeparatorTo Decimal separator character to use
	 *  when outputting formatted decimal numbers.
	 *
	 *  @param thousandsSeparatorTo Character to use
	 *  as the thousands separator in the output String.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function NumberBase(decimalSeparatorFrom:String = ".",
							   thousandsSeparatorFrom:String = ",",
							   decimalSeparatorTo:String = ".",
							   thousandsSeparatorTo:String = ",")
	{
		super();

		this.decimalSeparatorFrom = decimalSeparatorFrom;
		this.thousandsSeparatorFrom = thousandsSeparatorFrom;
		this.decimalSeparatorTo = decimalSeparatorTo;
		this.thousandsSeparatorTo = thousandsSeparatorTo;

		isValid = true;
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  decimalSeparatorFrom
	//----------------------------------

	/**
     *  Decimal separator character to use
	 *  when parsing an input String.
	 *  
	 *  @default "."
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public var decimalSeparatorFrom:String;

	//----------------------------------
	//  decimalSeparatorTo
	//----------------------------------

	/**
     *  Decimal separator character to use
	 *  when outputting formatted decimal numbers.
	 *  
	 *  @default "."
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public var decimalSeparatorTo:String;

	//----------------------------------
	//  isValid
	//----------------------------------

	/**
	 *  If <code>true</code>, the format succeeded,
	 *  otherwise it is <code>false</code>.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public var isValid:Boolean = false;

	//----------------------------------
	//  thousandsSeparatorFrom
	//----------------------------------

	/**
     *  Character to use as the thousands separator
	 *  in the input String.
	 *  
	 *  @default ","
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public var thousandsSeparatorFrom:String;

	//----------------------------------
	//  thousandsSeparatorTo
	//----------------------------------

	/**
     *  Character to use as the thousands separator
	 *  in the output String.
	 *  
	 *  @default ","
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public var thousandsSeparatorTo:String;

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Formats a number by rounding it. 
	 *  The possible rounding types are defined by
	 *  mx.formatters.NumberBaseRoundType.
	 *
	 *  @param value Value to be rounded.
	 *
	 *  @param roundType The type of rounding to perform:
	 *  NumberBaseRoundType.NONE, NumberBaseRoundType.UP,
	 *  NumberBaseRoundType.DOWN, or NumberBaseRoundType.NEAREST.
	 *
	 *  @return Formatted number.
	 *
	 *  @see mx.formatters.NumberBaseRoundType
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatRounding(value:String, roundType:String):String
	{
		var v:Number = Number(value);
		
		if (roundType != NumberBaseRoundType.NONE)
		{
			if (roundType == NumberBaseRoundType.UP)
			{
				 v = Math.ceil(v);
			}
			else if (roundType == NumberBaseRoundType.DOWN)
			{
				v = Math.floor(v);
			}
			else if (roundType == NumberBaseRoundType.NEAREST)
			{
				v = Math.round(v);
			}
			else
			{
				isValid = false;
				return "";
			}
		}

		return v.toString();
	}

	/**
	 *  Formats a number by rounding it and setting the decimal precision.
	 *  The possible rounding types are defined by
	 *  mx.formatters.NumberBaseRoundType.
	 *
	 *  @param value Value to be rounded.
	 *
	 *  @param roundType The type of rounding to perform:
	 *  NumberBaseRoundType.NONE, NumberBaseRoundType.UP,
	 *  NumberBaseRoundType.DOWN, or NumberBaseRoundType.NEAREST.
	 *
	 *  @param precision int of decimal places to use.
	 *
	 *  @return Formatted number.
	 *
	 *  @see mx.formatters.NumberBaseRoundType
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatRoundingWithPrecision(value:String, roundType:String,
												precision:int):String
	{
		// precision works differently now. Its default value is -1
		// which means 'do not alter the number's precision'. If a precision
		// value is set, all numbers will contain that precision. Otherwise, there
		// precision will not be changed. 

		var v:Number = Number(value);
		
		// If rounding is not present and precision is NaN,
		// leave value untouched.
		if (roundType == NumberBaseRoundType.NONE) 
		{
			if (precision == -1)
				return v.toString();
		}
		else
		{
			// If rounding is present but precision is less than 0,
			// then do integer rounding.
			if (precision < 0) 
				precision = 0;
			
			// Shift decimal right as Math functions
			// perform only integer ceil/round/floor.
			v = v * Math.pow(10, precision);
			
			// Attempt to get rid of floating point errors
			v = Number(v.toString()); 
			if (roundType == NumberBaseRoundType.UP)
			{
				v = Math.ceil(v);
			}
			else if (roundType == NumberBaseRoundType.DOWN)
			{
				v = Math.floor(v);
			}
			else if (roundType == NumberBaseRoundType.NEAREST)
			{
				v = Math.round(v);
			}
			else
			{
				isValid = false;
				return "";
			}
			
			// Shift decimal left to get back decimal to original point.
			v = v / Math.pow(10, precision); 
		}

		return v.toString();
	}

	/**
	 *  Formats a number by replacing the default decimal separator, ".", 
	 *  with the decimal separator specified by <code>decimalSeparatorTo</code>. 
	 *
	 *  @param value The String value of the Number
	 *  (formatted American style ####.##).
	 *
	 *  @return String representation of the input where "." is replaced
	 *  with the decimal formatting character.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatDecimal(value:String):String
	{
	    var parts:Array = value.split(".");
	    return parts.join(decimalSeparatorTo);
	}

	/**
	 *  Formats a number by using 
	 *  the <code>thousandsSeparatorTo</code> property as the thousands separator 
	 *  and the <code>decimalSeparatorTo</code> property as the decimal separator.
	 *
	 *  @param value Value to be formatted.
	 *
	 *  @return Formatted number.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatThousands(value:String):String
	{
		var v:Number = Number(value);
		
		var isNegative:Boolean = (v < 0);
		
		var numStr:String = Math.abs(v).toString();
		
		numStr.toLowerCase();
        var e:int = numStr.indexOf("e")
        if (e != -1)  //deal with exponents
            numStr = expandExponents(numStr);
            
		var numArr:Array =
			numStr.split((numStr.indexOf(decimalSeparatorTo) != -1) ? decimalSeparatorTo : ".");
		var numLen:int = String(numArr[0]).length;

		if (numLen > 3)
		{
			var numSep:int = int(Math.floor(numLen / 3));

			if ((numLen % 3) == 0)
				numSep--;
			
			var b:int = numLen;
			var a:int = b - 3;
			
			var arr:Array = [];
			for (var i:int = 0; i <= numSep; i++)
			{
				arr[i] = numArr[0].slice(a, b);
				a = int(Math.max(a - 3, 0));
				b = int(Math.max(b - 3, 1));
			}
			
			arr.reverse();
			
			numArr[0] = arr.join(thousandsSeparatorTo);
		}
		
		numStr = numArr.join(decimalSeparatorTo);
		
		if (isNegative)
			numStr = "-" + numStr;
		
		return numStr.toString();
	}

	/**
	 *  Formats a number by setting its decimal precision by using 
	 *  the <code>decimalSeparatorTo</code> property as the decimal separator.
	 *
	 *  @param value Value to be formatted.
	 *
	 *  @param precision Number of decimal points to use.
	 *
	 *  @return Formatted number.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatPrecision(value:String, precision:int):String
	{
		// precision works differently now. Its default value is -1
		// which stands for 'do not alter the number's precision'. If a precision
		// value is set, all numbers will contain that precision. Otherwise, there
		// precision will not be changed.
		
		if (precision == -1)
			return value;
		
		var numArr:Array = value.split(decimalSeparatorTo);
		
		numArr[0] = numArr[0].length == 0 ? "0" : numArr[0];
		
		if (precision > 0)
		{
			var decimalVal:String = numArr[1] ? String(numArr[1]) : "";
			var fraction:String =
				decimalVal + "000000000000000000000000000000000";
			value = numArr[0] + decimalSeparatorTo + fraction.substr(0, precision);
		}
		else
		{
			value = String(numArr[0]);
		}
		
		return value.toString();
	}

	/**
	 *  Formats a negative number with either a minus sign (-)
	 *  or parentheses ().
	 *
	 *  @param value Value to be formatted.
	 *
	 *  @param useSign If <code>true</code>, use a minus sign (-).
	 *  If <code>false</code>, use parentheses ().
	 *
	 *  @return Formatted number.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function formatNegative(value:String, useSign:Boolean):String
	{
		if (useSign)
		{
			if (value.charAt(0) != "-")
				value = "-" + value;
		}
		else if (!useSign)
		{
			if (value.charAt(0) == "-")
				value = value.substr(1, value.length - 1);
			value = "(" + value + ")";
		}
		else
		{
			isValid = false;
			return "";
		}
		return value;
	}

	/**
	 *  Extracts a number from a formatted String.
	 *  Examines the String from left to right
	 *  and returns the first number sequence.
	 *  Ignores thousands separators and includes the
	 *  decimal and numbers trailing the decimal.
	 *
	 *  @param str String to parse for the numeric value.
	 *
	 *  @return Value, which can be a decimal.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function parseNumberString(str:String):String
	{
		// Check the decimal and thousands formatting for validity.
		var splitDec:Array = str.split(decimalSeparatorFrom);
		if (splitDec.length > 2)
			return null;

		// Attempt to extract the first number sequence from the string.
		var len:int = str.length;
		var count:int = 0;
		var letter:String;
		var num:String;
		var isNegative:Boolean = false;

		while (count < len)
		{
			letter = str.charAt(count);
			count++;

			if (("0" <= letter && letter <= "9") || (letter == decimalSeparatorFrom))
			{
				var lastLetter:String = str.charAt(count - 2);
				// We must handle cases where we have negative input like "-YTL5.000,00"
				// and currencySymbol is longer than one character.
				if ((lastLetter == "-") || (str.charAt(0) == "-"))
					isNegative = true;
				num = "";
				count--;
				
				for (var i:int = count; i < len; i++)
				{
					letter = str.charAt(count);
					count++;
					if ("0" <= letter && letter <= "9")
						num += letter;
					else if (letter == decimalSeparatorFrom)
						num += ".";
					else if (letter != thousandsSeparatorFrom || count >= len)
						break;
				}
			}
		}

		// there are all sorts of variations of zero, such as:
		// .0    0.   0.0    0000     0000.00000     -0
		// Here, we simply convert to a 'real' Number and see if
		// it's resolved to zero. if it does, just return a zero
		// string and be done with it.
		if ((num != null) && (str != ''))
		{
			var n:Number = Number(num);

			if (n == 0)
				return '0';
		}

		// if the last digit is the dot, whack it
		if (num)
		{
			if (num.charAt(num.length-1) == '.')
			{
				// we have something like 33.
				if (num.length >= 2)
				{
					num = num.substring(0, num.length-1);
				}
				// we have merely .
				else if (num.length == 1)
				{
					num = '';
					isNegative = false;
				}
			}
		}

		return isNegative ? "-" + num : num;
	}
	
    /**
     *  Formats a number in exponent notation, into 
     *  a number in decimal notation.
     *
     *  @param str String to process in exponent notation.
     *
     *  @return Formatted number.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */    
    public function expandExponents(value:String):String
    {
        //Break string into component parts
        var regExp:RegExp = /^([+-])?(\d+).?(\d*)[eE]([-+]?\d+)$/;
        var result:Array = regExp.exec(value);
        var sign:String = result[1];
        var first:String = result[2];
        var last:String =  result[3];
        var exp:int =  int(result[4]);
                
        //if we didn't get a good exponent to begin with, return what we can figure out
        if (!exp)
        {
            return (sign ? sign : "") + (first ? first : "0") + (last ? "." + last : "");
        }
                
        var r:String = first + last;
                
        var decimal:Boolean = exp < 0;
                
        if (decimal)
        {   
            var o:Number = -1 * (first.length + exp) + 1;
            return (sign ? sign : "") + "0." + new Array(o).join("0") + r;
        }
        else
        {
            var i:Number = exp + first.length;
            if (i >= r.length)
                return (sign ? sign : "") + r + new Array(i - r.length + 1).join("0");
            else
                return (sign ? sign : "") + r.substr(0,i) + "." + r.substr(i); 
        }
    }
}

}
