var _NUMBER_FORMATTER_MAX_SIZE = 999;

/*
* Classe utilizada para validar e formatar campos de texto que receberão
* a entrada de dados do tipo numérico, independendo do número de casas
* decimais ou inteiras.
*
* @param tf Text field utilizado para entrada de dados.
*
* @version 1.0.9
* @author Masaru Hoshi <hoshi@snike.com.br>
*/
function NumberFormatter()  {
	//! Text field in use
	this.TextField = null;
	//! Decimal separator
	this.DecSep = '';
	//! Integer separator
	this.IntSep = '';
	//! Indicates whether or not decimal separator has been entered by user
	this.WaitDec = false;
	//! Size of integer part
	this.IntSize = _NUMBER_FORMATTER_MAX_SIZE;
	//! Size of decimal part
	this.DecSize = _NUMBER_FORMATTER_MAX_SIZE;

	/*
	* Class constructor
	* Receives as parameter the reference to text element that will be
	* used for typing control.
	* @param tf Input element to be mapped.
	* @param fmt Field display format.
	*/
	this.init = function(tf,fmt) {
		// Discovers format for being used
		if( this.prepareFormat(fmt) == false ) {
			return false;
		}

		// Define class mapped field
		this.TextField = tf;

		// Checks if TextField is empty and initializes it
		if( this.TextField.value == 'undefined' ||
			this.TextField.value == '' ) {
			this.TextField.value = '';
		}

		this.TextField['onkeydown'] = this.handleKeyboard;
/*
		this.TextField.onkeypress = 
			this.handleKeyboard;
*/

		this.TextField.formatter = this;
	};

	/*
	* Method used to finds which format should be used for number display
	* based on input mask.
	* @param fmt Mask used for input.
	*/
	this.prepareFormat = function(fmt) {
		var intPart = '';
		var decPart = '';

		// '.' as int or decimal separator
		if( fmt.match(/^[#0.]+$/) ) {
			// If there's more than one ocurrence of '.', it definitely
			// works as int separator
			if( fmt.replace(/[^.]/g,'').length > 1 ) {
				this.IntSep = '.';
				intPart = fmt.replace(/\./g,'');
			}
			// We don't know for sure if '.' is dec or int separator.
			// So, it's set to decimal anyway.
			else {
				this.IntSep = ',';
				this.DecSep = '.';

				intPart = fmt.replace(/\..*/g,'');
				decPart = fmt.replace(/.*\./g,'');
			}
		}
		// ',' as int or decimal separator
		else if( fmt.match(/^[#0,]+$/) ) {
			// If there's more than one ocurrence of '.', it definitely
			// works as int separator
			if( fmt.replace(/[^.]/g,'').length > 1 ) {
				this.IntSep = ',';
				intPart = fmt.replace(/,/g,'');
			}
			else {
				this.IntSep = '.';
				this.DecSep = ',';

				intPart = fmt.replace(/,.*/g,'');
				decPart = fmt.replace(/.*,/g,'');
			}

		}
		// Decimal, ',' int separator and '.' decimal separator
		else if( fmt.match(/^[#0,]+[#0.]+$/) ) {
			this.IntSep = ',';
			this.DecSep = '.';

			intPart = fmt.replace(/\..*/g,'').replace(/,/g,'');
			decPart = fmt.replace(/^.*\./g,'').replace(/\./g,'');
		}
		// Decimal, '.' int separator and ',' decimal separator
		else if( fmt.match(/^[#0.]+[#0,]+$/) ) {
			this.IntSep = '.';
			this.DecSep = ',';

			intPart = fmt.replace(/,.*/g,'').replace(/\./g,'');
			decPart = fmt.replace(/^.*,/g,'').replace(/,/g,'');
		}
		// Invalid format
		else {
			this.showHelp(fmt);
			return false;
		}

		this.DecSize = decPart.length;

		// Finally, it verifies if integer part has a limit
		if( fmt.match(/^0/) ) {
			this.IntSize = intPart.length;
		}

		return true;
	};

	/*
	* Maps data input by keyboard.
	* Defines what should and what should not be valid.
	* @param e Keyboard event.
	*/
	this.handleKeyboard = function(e) {
		e = e || window.event;

		var sender = Event.element(e);

		if( e.keyCode )
			code = e.keyCode;
		else if( e.which )
			code = e.which;

		switch(code) {
			// Navigation arrows
			case 37: case 38: case 39: case 40:
				return true;
			// TAB
 			case 9:
				return true;
			// Backspace
			case 8:
				sender.value = 
					sender.value.slice( 0, sender.value.length - 1 );
				sender.formatter.format();
				break;
			// Num pad numbers [0-9]
			case 96: case 97: case 98: case 99:
			case 100: case 101: case 102: case 103:
			case 104: case 105:
				code = parseInt(code) - 48;
			// Numbers inputable [0-9]
			case 48: case 49: case 50: case 51:
			case 52: case 53: case 54: case 55:
			case 56: case 57:
				sender.formatter.format( String.fromCharCode(code) );
				break;
			// '.' or ',' pressed as decimal separator
			case 46: case 44:
				// Should not allow using decimal separator more than once
				if( (sender.formatter.TextField.value.match(/,/g) && 
						this.DecSep == ',') || 
					(sender.formatter.TextField.value.match(/\./g) && 
						this.DecSep == '.') ) {
					return false;
				}
				sender.formatter.format( String.fromCharCode(code) );
				break;
			default: 
				break;
		}

		return false;
	};

	/*
	* Format TextField with criteria provided during object creation.
	* @param v Last pressed key.
	*/
	this.format = function(v) {
		// If no char has been specified, it must be blank
		if( arguments.length == 0 ) {
			v = '';
		}

		// Verify the consistence of TextField content
		if( this.TextField.value == 'undefined' ) {
			this.TextField.value = '';
		}

		// Decimal separator has been entered. So, it returns
		// until a number being input.
		if( v == this.DecSep ) {
			this.WaitSep = true;
			return;
		}
		// Integer separators must be ignored
		else if( v == this.IntSep ) {
			return;
		}

		var intSize = 0;
		var num = this.TextField.value.replace(/[^0-9]/g,'') + v;

		// There should be already a decimal separator and it should
		// not be empty delimiter.
		var rx = new RegExp( this.IntSep == '.' ? '\\\.' : this.IntSep, 'g' );
		var sep = this.TextField.value.replace(rx,'').indexOf( this.DecSep );

		if( sep >= 0 && this.DecSep != '' ) {
			intSize = sep;
		}
		// If the last input char was the separator, the integer part ends here
		else if( this.WaitSep ) {
			intSize = num.length - 1;
			this.WaitSep = false;
		}
		else {
			intSize = (num.length < this.IntSize) ? num.length : this.IntSize;
		}

		// There must always exists an integer part
		if( intSize == 0 ) {
			this.WaitSep = false;
			return;
		}

		var intPart = num.slice( 0, intSize );
		var decPart = num.slice( intSize );

		// Formatted string
		var fmtIntPart = '';

		// Formats integer part
		while( intPart.length > 0 ) {
			fmtIntPart = intPart.match(/[0-9]{1,3}$/) + 
				(fmtIntPart.length > 0 ? this.IntSep : '') + fmtIntPart;
			intPart = intPart.replace(/[0-9]{1,3}$/,'');
		}

		// Verifies decimal part
		if( decPart.length > 0 ) {
			if( decPart.length > this.DecSize ) {
				decPart = decPart.slice( 0, this.DecSize );
			}
			decPart = this.DecSep + decPart;
		}

		// Update field value
		this.TextField.value = fmtIntPart + decPart;
	};

	/*
	* Shows help alert about Class working mode.
	* @param fmt Format specified.
	*/
	this.showHelp = function(fmt) {
		var msg = 'Invalid format: '+fmt+'\n';
		msg += "- Don't mix '.' and ',' more than once.\n";
		msg += "- Only '#' and '0' are valid characters.";

		alert(msg);
	};

}

