var 	COUNT_DECIMAL_PLACES		= 30;
var	MAX_DIVISION_STEPS			= 20;

/* ********************************************************************************************************** */

function joNumCheck (checkval, numtype)
{
	var result 			= false;
	var regex 			= null;
	var no_zero			= false;
	
	var checkval = checkval.replace (/\s*/g, "");
	
	if (checkval != "")
	{
		switch (numtype)
		{
			case "boolean":
				checkval = checkval.toUpperCase();
				regex = /^(TRUE|1|YES|OK)$/;
				break;
			case "int":
				regex = /^[+-]?\d+$/;
				break;
			case "posint":
				no_zero = true;
				// no break;
			case "uint":
				regex = /^[+]?\d+$/;
				break;

			case "float":
				regex = /^[+-]?(\d+[\.]?\d*|\d*[\.]?\d+)([eEdD][+-]?\d+)?$/;
				break;
			case "posfloat":
				no_zero = true;
				// no break!
			case "ufloat":
				regex = /^[+]?(\d+[\.]?\d*|\d*[\.]?\d+)([eEdD][+-]?\d+)?$/;
				break;

			case "complex":
				regex = /^[+-]?(\d+[\.]?\d+|\d*[\.]?\d+)([eEdD][+-]?\d+)?$|^\(\s*[+-]?(\d+[\.]?\d*|\d*[\.]?\d+)([eEdD][+-]?\d+)?\s*,\s*[+]?(\d+[\.]?\d*|\d*[\.]?\d+)([eEdD][+-]?\d+)?\s*\)$/;
				break;
			case "ucomplex":
				regex = /^[+]?(\d+[\.]?\d+|\d*[\.]?\d+)([eEdD][+-]?\d+)?$|^\(\s*[+]?(\d+[\.]?\d*|\d*[\.]?\d+)([eEdD][+-]?\d+)?\s*,\s*[+]?(\d+[\.]?\d+|\d*[\.]?\d+)([eEdD][+-]?\d+)?\s*\)$/;
				break;
		}
	}
	if (regex)	result = (checkval.search (regex) == -1 ? false : true);
	if (result && no_zero)
	{
		if (!parseFloat(checkval)) result = false;
	}

	return result;
}

/* ********************************************************************************************************** */

function joGetRealPart (complex_number)
{
	var result = "0";
	var checkval = complex_number.replace (/\s*/g, "");
	if (checkval.indexOf ("(") != -1)
	{
		checkval = checkval.replace (/[()]/g, "");
		var checkval = checkval.split (",");
		result = checkval[0];
	}
	else
	{
		result = checkval;
	}
	return result;
}

/* ********************************************************************************************************** */

function joGetRealPartAsFloat (complex_number)
{
	return parseFloat (joGetRealPart (complex_number));
}

/* ********************************************************************************************************** */
/* ********************************************************************************************************** */
/* ********************************************************************************************************** */

function joVirtNumber (value)
{
	
	this.Normalize			= _joVirtNumber_Normalize;		// writes my value in decimal or scientific notation
	this.Clone				= _joVirtNumber_Clone;				// copy another VirtNumber Value into me
	this.Shift				= _joVirtNumber_Shift;				// shift number by pos/neg digits: multiply / divide by 10
	this.toStr				= _joVirtNumber_toStr;				// export my value as string (without exponential part)
	
	this.Add					= _joVirtNumber_Add;
	this.Subtract			= _joVirtNumber_Subtract;
	this.Multiply			= _joVirtNumber_Multiply;
	this.Devide				= _joVirtNumber_Devide;
	this.IsSmallerThan	= _joVirtNumber_IsSmallerThan;
	this.IsEqualTo			= _joVirtNumber_IsEqualTo;
	
	this.FlipSign			= _joVirtNumber_FlipSign;
	
	//...........................................................................................internal members
	this.value 				= "0";									// raw digits string without sign and comma
	this.sign				= "+";									// "+" / "-"
	this.dotpos				= 1;										// position of comma from start of string
	
	//.........................................................................................internal functions
	this._Init				= _joVirtNumber_init;
	this._cutZeros			= _joVirtNumber_cutZeros;			// cut leading zeros before and trailing zeros after comma
	this._SetSameLength	= _joVirtNumber_SetSameLength;	// bring both's VirtNumbers' digit strings to same length
	this._Calc				= _joVirtNumber_Calc;				// do calculations (add / subtract)
	
	return this._Init (value);
}

/* ********************************************************************************************************** */

function _joVirtNumber_FlipSign()
{
	this.sign	= this.sign == "+" ? "-" : "+";
}

/* ********************************************************************************************************** */

function _joVirtNumber_Clone (virtnum)
{
	this.value 		= virtnum.value;
	this.sign		= virtnum.sign;
	this.dotpos		= virtnum.dotpos;
}

/* ********************************************************************************************************** */

function _joVirtNumber_init (init_value)
{
	this.value 		= "0";
	this.sign		= "+";
	this.dotpos		= 1;
	
	if (init_value)
	{
		init_value = init_value.replace (/\s*/g, "");
	
		if (init_value != "")
		{
			var is_int 		= joNumCheck (init_value, "int");
			var is_float	= joNumCheck (init_value, "float");
			if (is_int || is_float)
			{
				var inval 		= init_value.split (/[eEdD]/);
				
				//............................................................................................real part
				var p1 			= inval[0];
				this.sign 		= (p1.indexOf ("-") == -1 ? "+" : "-");
				
				p1					= p1.replace (/[+-]/, "");
				this.dotpos		= p1.indexOf (".");
				
				p1					= p1.replace (".", "");			
				this.value 		= p1;
	
				if (this.dotpos == -1) this.dotpos = this.value.length;
	
				//..........................................................................................exponential
				if (inval.length == 2)
				{
					var p2		= inval[1];
					var digits	= parseInt (p2.replace (/[+-]/, ""));
					var neg		= (p2.indexOf ("-") == -1 ? false : true);
	
					if (neg)
					{
						for (var i = 0; i < digits; i++) 
						{
							this.dotpos--;
							if (this.dotpos < 0)
							{
								this.value 		= "0" + this.value;
								this.dotpos 	= 0;
							}
						}
					}
					else 
					{
						for (var i = 0; i < digits; i++) 
						{
							this.value += "0";
							this.dotpos++;
						}
						
					}
				}
	
				//...................................................................................omit leading zeros
				this._cutZeros();
				if (this.value == "") 
				{
					this.value = "0";
					this.dotpos = 1;
				}
			}
		}
	}
	return this.toStr();
}

/* ********************************************************************************************************** */

function _joVirtNumber_cutZeros()
{
	var digit = this.value.charAt (0);
	while (digit == "0" && this.dotpos > 0)
	{
		this.value = this.value.substr (1);
		this.dotpos--;
		digit = this.value.charAt (0);
	}
	
	digit = this.value.charAt (this.value.length - 1);
	while (digit == "0" && this.value.length > this.dotpos)
	{
		this.value = this.value.substr (0, this.value.length - 1);
		digit = this.value.charAt (this.value.length - 1);
	}
}

/* ********************************************************************************************************** */

function _joVirtNumber_toStr()
{
	var result = "";
	
	if (this.dotpos)
	{
		if (this.dotpos < this.value.length)
		{
			result += this.value.substr (0, this.dotpos);
			if (parseInt (this.value.substr (this.dotpos)))
				result += "." + this.value.substr (this.dotpos);
		}
		else
		{
			result += this.value;
		}
	}
	else
	{
		result = "0." + this.value;
	}
	if (result != "0" && this.sign == "-") result = this.sign + result;
	
	return result;
}

/* ********************************************************************************************************** */

function _joVirtNumber_Add (number2)
{
	if (this.sign == number2.sign)
		return this._Calc (number2, "add");
	else
		return this._Calc (number2, "subtract");
}

/* ********************************************************************************************************** */

function _joVirtNumber_Subtract (number2)
{
	if (this.sign != number2.sign)
		return this._Calc (number2, "add");
	else
		return this._Calc (number2, "subtract");
}

/* ********************************************************************************************************** */

function _joVirtNumber_Multiply (number2)
{
	if (this.value == "0" || number2.value == "0")
	{
		this._Init();
	}
	else
	{
		var auxnum = new joVirtNumber();
		auxnum.Clone (this);
		auxnum.Shift (number2.dotpos - 1);
		this._Init ("0");
	
		for (var i = 0; i < number2.value.length; i++)
		{
			var digit = number2.value.charAt(i);
			for (var j = 0; j < digit; j++)
			{
				this.Add (auxnum);
			}
			auxnum.Shift (-1);
		}	
		
		this.sign = (number2.sign == this.sign ? "+" : "-");
	}

	return this.toStr();
}

/* ********************************************************************************************************** */

function _joVirtNumber_Devide (number2, count_dec_places)
{
	if (!count_dec_places) count_dec_places = COUNT_DECIMAL_PLACES;
	var result;
	if (number2.value != "0")
	{
		var auxnum 	= new joVirtNumber();
		var onenum	= new joVirtNumber ("1");
		var auxdiv	= new joVirtNumber();
		var sign		= (number2.sign == this.sign ? "+" : "-");
		
		var rounds	= 0;
		auxnum.Clone (this);
		auxdiv.Clone (number2);
		auxnum.sign = "+";
		auxdiv.sign = "+";
			
		this._Init ("0");
		auxnum._SetSameLength (auxdiv);	
		
		while (auxnum.value.replace (/0*/, "") != "" && count_dec_places > (this.value.length - this.dotpos) && rounds < MAX_DIVISION_STEPS)
		{
			while (auxnum.value >= auxdiv.value)
			{
				auxnum.Subtract (auxdiv);
				auxnum._SetSameLength (auxdiv);	
				this.Add (onenum);
			}
			rounds++;
			onenum.Shift (-1);
			auxdiv.Shift (-1);
			auxnum._SetSameLength (auxdiv);	
		}
		this._cutZeros();
		this.sign = sign;
	}
	else
	{
		this._Init();
	}
	

	return this.toStr();
}

/* ********************************************************************************************************** */

function _joVirtNumber_Shift (digits)
{
	if (parseInt (digits))
	{
		var i;
		if (digits > 0)
		{
			for (i = 0; i < digits; i++)
			{
				this.dotpos++;
				if (this.dotpos >= this.value.length)
				{
					this.value += "0";
				}
			}
		}
		else
		{
			for (i = 0; i < digits * -1; i++)
			{
				this.dotpos--;
				if (this.dotpos < 0)
				{
					this.dotpos = 0;
					this.value  = "0" + this.value;
				}
			}
		}
	}
	return this.toStr();
}

/* ********************************************************************************************************** */

function _joVirtNumber_SetSameLength (number2)
{
	var max_dotpos = Math.max (this.dotpos, number2.dotpos);
	var i;
	for (; this.dotpos < max_dotpos; this.dotpos++) this.value = "0" + this.value;
	for (; number2.dotpos < max_dotpos; number2.dotpos++) number2.value = "0" + number2.value;
	
	var max_length = Math.max (this.value.length, number2.value.length);
	
	for (i = this.value.length; i < max_length; i++) this.value += "0";
	for (i = number2.value.length; i < max_length; i++) number2.value += "0";
}

/* ********************************************************************************************************** */

function _joVirtNumber_Normalize()
{
	var result 		= "0";
	var firstpos	= 0;
	if (this.value != "" && this.value != "0")
	{
		while (!parseInt (this.value.charAt (firstpos))) firstpos++;
		
		var shiftit 	= this.dotpos - 1 - firstpos;
		
		if (shiftit > 3 || shiftit < -3)
		{
			result = this.value.charAt(firstpos) + "." + this.value.substr (firstpos + 1) + "e" + shiftit;
			if (this.sign == "-") result = this.sign + result;
		}
		else
		{
			result = this.toStr();
		}
	}
	return result;
}

/* ********************************************************************************************************** */

function _joVirtNumber_Calc (number2, calc_type)
{
	this._SetSameLength (number2);

	var length 		= this.value.length;
	var newval 		= "";
	var digit;
	var shift 	= 0;
	var i;
	
	switch (calc_type)
	{
		case "add":
		{
			for (i = length - 1; i >= 0; i--)
			{
				digit 	= parseInt (this.value.charAt(i)) + parseInt (number2.value.charAt(i)) + shift;
				shift		= Math.floor (digit / 10);
				digit		= digit % 10;
				newval	= digit + newval;
			}
			if (shift)	
			{
				newval = "1" + newval;
				this.dotpos++;
			}
			break;
		}
		case "subtract":
		{
			var flip		= (number2.value > this.value);
			var num1		= flip ? number2.value : this.value;
			var num2		= flip ? this.value : number2.value;
			
			for (i = length - 1; i >= 0; i--)
			{
				digit 	= parseInt (num1.charAt(i)) - parseInt (num2.charAt(i)) - shift;
				shift		= (digit < 0 ? 1 : 0);
				digit		= (digit < 0 ? digit + 10 : digit);
				newval	= digit + newval;
			}
			
			if (flip)
			{
				this.FlipSign();
			}
			
			break;
		}
	}
	
	this.value = newval;
	this._cutZeros();
	number2._cutZeros();
	
	return this.toStr();
}

/* ********************************************************************************************************** */

function _joVirtNumber_IsSmallerThan (number2)
{
	var result = false;
	var auxnum = new joVirtNumber();
	auxnum.Clone (this);

	auxnum.Subtract (number2);
	if (auxnum.sign == "-") result = true;
	return result;
}

/* ********************************************************************************************************** */

function _joVirtNumber_IsEqualTo (number2)
{
	var result = false;
	
	if (this.value == number2.value && this.sign == number2.sign && this.dotpos == number2.dotpos) result = true;
	
	return result;
}

