// Fichero de funciones javascript para gestión de campos numéricos.
// Autor: Fco. Javier Aranda Granda (Unidad Internet-Intranet/BdE)
// Versión: 070410
//
// SOLO FUNCIONALIDAD MADURA:
// SE RUEGA NO UTILIZAR PARA EXPERIMENTAR NUEVAS FUNCIONALIDADES.

// Estructura
// #Constantes
// #Funciones de formato
//- function checkStrNumero(strNumero:String, maxEnteros:int, maxDecimales:int, permiteCero:boolean, permiteNegativo:boolean):String
// * Verificar un string numérico buscando errores en su estructura.
//- function formatNumero(valor:double, maxEnteros:int, maxDecimales:int,
//		permiteCero:noolean, permiteNegativo:boolean, formateaMiles:boolean) {
// * Formatea un valor numérico según las especificaciones dadas.
// #Funciones de gestión de campos
//- function onKeypressNumero(campo:Text, evento:Event):boolean
// * Controla las teclas pulsadas en un campo numérico.
//- function onChangeNumero(campo:Text, evento:Event): boolean
// * Gestiona el evento al modificarse el valor de un campo/ dejar de editar un campo.
//- function getNumeroCampo(campo:Text):double {
// * Obtiene el valor numérico a partir de un String.
//- function setNumeroCampo(campo:Text, valor:double):void {
// * Establece un valor numérico en un campo.
//- function getComportamientoCampo(campo: Text):String {
// * Obtiene el identificador de comportamiento configurado para el campo.


// ######
// CONSTANTES
// ######
// Códigos de tecla relevantes.
var KEYCODE_0 = '0'.charCodeAt(0);
var KEYCODE_9 = '9'.charCodeAt(0);
var KEYCODE_PUNTO = '.'.charCodeAt(0);
var KEYCODE_COMA = ','.charCodeAt(0);
var KEYCODE_MENOS = '-'.charCodeAt(0);
var KEYCODE_BKSP = 8;
var KEYCODE_DEL = 0;

// Caracteres utilizados para formateo.
var SEP_MIL = '.';
var SEP_DEC = ',';

// Indicadores utilizados para explicitar los flags de comportamiento en las llamadas
// (No es lo mísmo invocar func(HACER_X) que func(true) )
var PERMITE_CERO = true;
var PERMITE_NEG = true;
var FORMATEA_MIL = true;

// Identificadores de los 'Comportamientos'(BeHaViouR) contemplados.
var BHVR_CURRENCY = 'CURRENCY';
var BHVR_RATE = 'RATE';
var BHVR_INT = 'INT';

// Identificadores para campos deshabilitados.
var BHVR_CURRENCY_D = 'CURRENCY_D';
var BHVR_RATE_D = 'RATE_D';
var BHVR_INT_D = 'INT_D';

// ######
// FUNCIONES DE FORMATO
// Se definen funciones para formatear y parsear números.
// ######
/**
 * Verificar un string numérico buscando errores en su estructura.
 * Ejemplos de uso:
 * var msgCheckCurrency = checkStrNumero(strNumero, 6, 2, PERMITE_CERO, PERMITE_NEG);
 * var msgCheckInt = checkStrNumero(strNumero, 6, 0, PERMITE_CERO, !PERMITE_NEG);
 * @param strNumero String a evaluar
 * @param maxEnteros Número máximo de dígitos enteros.
 * @param maxDecimales Número máximo de dígitos fraccionarios.
 *   Si se indica cero no se permitirá siquiera el separador decimal.
 * @param permiteCero Indicador de si el número permite el valor cero.
 *   Se ruega especificar a través de la constante PERMITE_CERO.
 * @param permiteNegativo Indicador de si el número permite valores negativos.
 *   Se ruega especificar a través de la constante PERMITE_NEG.
 * @return Una cadena indicando el error encontrado en la cadena.
 *   Una cadena vacía indica que el número se consideró correcto.
 *   Una cadena no vacía indica un mensaje de error.
 */
function checkStrNumero(strNumero, maxEnteros, maxDecimales, permiteCero, permiteNegativo) {
	if (strNumero.length == 0) {
		return '';
	}
	// Interpreta el punto y la coma como separador decimal.
	strNumero = strNumero.replace(/\./g, SEP_DEC);
	strNumero = strNumero.replace(/\,/g, SEP_DEC);
	
	// Si hay un signo menos, tiene que estar en la primera posición.
	if (strNumero.indexOf('-',1) != -1) {
		return 'Signo menos (-) en posición incorrecta';
	}
	if (strNumero.charAt(0) == -1 && !permiteNegativo) {
		return 'No se permite valor negativo';
	}
	// Haya la posición del separador decimal. Debe haber uno o ninguno.
	// Valida el número de dígitos enteros y decimales.
	var l_indexOfSepDec = strNumero.indexOf(SEP_DEC);
	if (l_indexOfSepDec != -1 &&
			strNumero.indexOf(SEP_DEC, l_indexOfSepDec + 1) != -1) {
		return 'Sólo se permite un separador decimal';
	}
	if ((l_indexOfSepDec == -1 && strNumero.length > maxEnteros) ||
			l_indexOfSepDec > maxEnteros) {
		return 'Sólo se permiten ' + maxEnteros + ' dígitos enteros';
	}
	if (l_indexOfSepDec != -1 &&
			strNumero.length - l_indexOfSepDec - 1 > maxDecimales) {
		return 'Sólo se permiten ' + maxDecimales + ' dígitos fraccionarios';
	}
	
	return '';
}

/**
 * Formatea un valor numérico según las especificaciones dadas.
 * Ejemplo llamada para importes:
 *   formatNumero(valor, 6, 2, PERMITE_CERO, PERMITE_NEG, FORMATEA_MIL)
 * @param valor Valor numérico a formatear.
 * @param maxEnteros Número máximo de dígitos enteros. Mostrará una advertencia
 *   si la magnitud excede el tamaño del campo, pero intentará su formato.
 * @param maxDecimales Número máximo de dígitos fraccionarios.
 *   Si se indica cero no se permitirá siquiera el separador decimal.
 * @param permiteCero Indicador de si el número permite el valor cero.
 *   Se ruega especificar a través de la constante PERMITE_CERO.
 * @param permiteNegativo Indicador de si el número permite valores negativos.
 *   Se ruega especificar a través de la constante PERMITE_NEG.
 * @param formateaMiles Indicador de si se formatean los separadores de millares.
 * @return Una cadena con el valor formateado.
 */
function formatNumero(valor, maxEnteros, maxDecimales, permiteCero, permiteNegativo,formateaMiles) {
	// Los valores no numéricos y los infinitos los devolvemos tal cual.
	if (isNaN(valor) || !isFinite(valor)) {
		return valor;
	}
	// TT_IMPROVE: Ver qué hacer con los números demasiado grandes.
	if (valor >= Math.pow(10.0, maxEnteros)) {
//		return valor;
	}
	// l_idxMagnitud indica el valor 10^l_idxMagnitud del dígito más significativo.
	var l_strValor = '';
	// Números con notación científica
	if (l_strValor.indexOf('e') >= 0) {
		return valor;
	}
	if (valor < -1E-8) { // Evito que el cero pueda aparecer como negativo.
		l_strValor = '-';
		valor *= -1.0;
	}

	// TT_IMPROVE: Controlar condiciones especiales con notación científica.
	valor = valor.toFixed(maxDecimales);
	var l_strValorAumentado = Math.round(valor * Math.pow(10.0, maxDecimales)) + '';
	// Añadimos los ceros a la izquierda que fueran necesarios para rellenar hasta la unidad.
	l_strValorAumentado = '0000000000'.substring(0,maxDecimales + 1 - l_strValorAumentado.length) +
			l_strValorAumentado;
	var l_idxDesdeSepDecimal = 0;
	var l_numEnteros = l_strValorAumentado.length - maxDecimales;
	if (formateaMiles) {
		l_idxDesdeSepDecimal = l_numEnteros % 3;
		if (l_idxDesdeSepDecimal == 0) { l_idxDesdeSepDecimal = 3; }
		l_strValor += l_strValorAumentado.substring(0, l_idxDesdeSepDecimal);
		for (;l_idxDesdeSepDecimal < l_numEnteros; l_idxDesdeSepDecimal += 3) {
			l_strValor += SEP_MIL;
			l_strValor += l_strValorAumentado.substr(l_idxDesdeSepDecimal, 3);
		}
	} else {
		l_strValor += l_strValorAumentado.substring(0, l_numEnteros);
	}
	if (maxDecimales > 0) {
		l_strValor += SEP_DEC;
		l_strValor += l_strValorAumentado.substring(l_numEnteros);
	}
	return l_strValor;
	
	/* Retorcido método anterior
	var l_idxMagnitud = 0;
	if (valor >= 10.0) {
		l_idxMagnitud = Math.floor(Math.log(valor) / Math.log(10.0));
	}
	var l_resto = valor;
	while(l_idxMagnitud > 0) {
		var l_unidadMagnitud = Math.pow(10.0,l_idxMagnitud);
		var l_digito = Math.floor(l_resto / l_unidadMagnitud);
		l_strValor += l_digito;
		if (formateaMiles && (l_idxMagnitud % 3 == 0)) {
			l_strValor += SEP_MIL;
		}
		
		l_resto = valor % l_unidadMagnitud;
		l_idxMagnitud--;
	}
	
	// Decimales
	if (maxDecimales > 0) {
	// Magnitud 0.
		l_strValor += Math.floor(l_resto);
		l_resto = valor % 1.0;
		l_strValor += SEP_DEC;
		// Escribir todos los decimales menos uno.
		for (var idxDecimal = 1; idxDecimal < maxDecimales; idxDecimal++) {
			l_resto *= 10.0;
			l_strValor += Math.floor(l_resto);
			l_resto %= 1.0;
		}
		// El último dígito se escribe redondeando.
		l_strValor += Math.round(l_resto * 10.0);
	} else {
		l_strValor += Math.round(l_resto);
	}
	return l_strValor;
*/
}



// ######
// FUNCIONES GESTION CAMPOS
// Se definen funciones para gestionar los campos input/text de los formularios
// ######
/**
 * Controla las teclas pulsadas en un campo numérico.
 * Trata de filtrar las teclas que no serían válidas.
 * @param campo Objeto Text utilizado como campo de entrada.
 * @param evento Evento onkeypress capturado.
 * @return true/false para indicar a la cadena de gestión de evento que continúe o
 *   cancele la cadena de procesamiento.
 */
function onKeypressNumero(campo, evento) {
	// # La funcionalidad que automáticamente sustituye los puntos por comas
	// no funciona correctamente en Firefox (propiedad which no modificable).
	// # No verificamos dinámicamente en función del contenido ya introducido
	// Hemos tenido anomalías cuando el cursor no está en la posición
	// final, o hay una selección dentro del campo.

	var l_comportamiento = getComportamientoCampo(campo);
	
	// Ninguno de los comportamientos definidos permite números negativos.
	var l_permiteNeg = false;
	// Sólo Currency y Rate permiten decimales.
	var l_permiteDecimales = (l_comportamiento == BHVR_CURRENCY) ||
			(l_comportamiento == BHVR_RATE);

	// Firefox reconoce which. IE reconoce keyCode.
	var l_keycode = evento.which != null ? evento.which : evento.keyCode;

	// Códigos admisibles en cualquier condición
	if ((l_keycode >= KEYCODE_0 && l_keycode <= KEYCODE_9) ||
			(l_keycode == KEYCODE_BKSP) ||
			(l_keycode == KEYCODE_DEL)) {
		return true;
	} else if (l_keycode == KEYCODE_PUNTO || l_keycode == KEYCODE_COMA) {
		return l_permiteDecimales;
	} else if (l_keycode == KEYCODE_MENOS) {
		return l_permiteNeg;
	} else {
		// Otros caracteres no contemplados.
		return false;
	}
}

/**
 * Gestiona el evento al modificarse el valor de un campo/ dejar de editar un campo.
 * Se pretende validar que el campo tenga un valor correcto.
 * En caso de encontrar alguna anomalía, se avisa y se retorna al campo.
 * @param campo Campo a validar.
 * @evento Evento onchange/blur
 * @return true/false para indicar a la cadena de gestión de evento que continúe o
 *   cancele la cadena de procesamiento.
 */
function onChangeNumero(campo, evento) {
	var l_comportamiento = getComportamientoCampo(campo);
	var l_avisos = '';
	if (l_comportamiento==BHVR_CURRENCY) {
		l_avisos = checkStrNumero(campo.value, 8, 2, PERMITE_CERO, !PERMITE_NEG);
	} else if (l_comportamiento==BHVR_RATE) {
		l_avisos = checkStrNumero(campo.value, 2, 4, PERMITE_CERO, !PERMITE_NEG);
	} else if (l_comportamiento==BHVR_INT) {
		l_avisos = checkStrNumero(campo.value, 8, 0, !PERMITE_CERO, !PERMITE_NEG);
	} else {
		l_avisos = 'Comportamiento no controlado: ' + l_comportamiento;
	}
	if (l_avisos != '') {
		alert(l_avisos);
		campo.focus();
		return false;
	}
}

/**
 * Obtiene el valor numérico a partir de un String.
 * @param campo Campo de donde obtener el valor.
 * @return valor numérico obtenido.
 */
function getNumeroCampo(campo) {
	var l_comportamiento = getComportamientoCampo(campo);
	// Ignoramos los puntos en los casos en los que serían válidos como miles.
	var l_puntosSonSepMil = (l_comportamiento == BHVR_CURRENCY_D) ||
			(l_comportamiento == BHVR_INT_D);
	var l_puntosSonSepDec = (l_comportamiento == BHVR_CURRENCY) ||
			(l_comportamiento == BHVR_RATE);
	var l_comasSonSepDec = !((l_comportamiento == BHVR_INT) ||
			(l_comportamiento == BHVR_INT_D));
	var strValor = campo.value;
	// Eliminar los separadores de millares.
	// Sustituir los separadores de decimales por el separador aceptado por parseFloat(el punto).
	if (l_puntosSonSepMil) {
		strValor = strValor.replace(/\./g, '');
	} else if (l_puntosSonSepDec) {
		// Igual a no hacer nada.
		//strValor = strValor.replace(/\./g, '.');
	}
	if (l_comasSonSepDec) {
		strValor = strValor.replace(/\,/g, '.');
	}
	
	var l_parseAsInt = (l_comportamiento == BHVR_INT) ||
			(l_comportamiento == BHVR_INT_D);
	if (l_parseAsInt) {
		return parseInt(strValor, 10);
	} else {
		return parseFloat(strValor);
	}
}

/**
 * Establece un valor numérico en un campo.
 * @param campo Objeto Text donde establecer el valor.
 * @param valor Valor numérico a establecer.
 */
function setNumeroCampo(campo, valor) {
	var l_comportamiento = getComportamientoCampo(campo);
	var l_strValor = '';
	if (l_comportamiento==BHVR_CURRENCY) {
		l_strValor = formatNumero(valor, 8, 2, PERMITE_CERO, !PERMITE_NEG, !FORMATEA_MIL);
	} else if (l_comportamiento==BHVR_RATE) {
		l_strValor = formatNumero(valor, 2, 4, PERMITE_CERO, !PERMITE_NEG, !FORMATEA_MIL);
	} else if (l_comportamiento==BHVR_INT) {
		l_strValor = formatNumero(valor, 8, 0, !PERMITE_CERO, !PERMITE_NEG, !FORMATEA_MIL);
	} else if (l_comportamiento==BHVR_CURRENCY_D) {
		l_strValor = formatNumero(valor, 8, 2, PERMITE_CERO, !PERMITE_NEG, FORMATEA_MIL);
	} else if (l_comportamiento==BHVR_RATE_D) {
		l_strValor = formatNumero(valor, 2, 4, PERMITE_CERO, !PERMITE_NEG, !FORMATEA_MIL);
	} else if (l_comportamiento==BHVR_INT_D) {
		l_strValor = formatNumero(valor, 8, 0, !PERMITE_CERO, !PERMITE_NEG, FORMATEA_MIL);
	} else {
		alert('Comportamiento no controlado: ' + l_comportamiento);
		l_strValor = valor.toString();
	}
	campo.value = l_strValor;
}

/**
 * Obtiene el identificador de comportamiento configurado para el campo.
 * @param campo El campo para el cual averiguamos el comportamiento.
 * @return el identificador (String) de comportamiento.
 */
function getComportamientoCampo(campo) {
	return campo.getAttribute('numbhvr').toUpperCase();
}
