/**
 * 
 */
package com.gfi.utils;

import java.math.BigInteger;
import java.util.regex.Pattern;

import org.hibernate.validator.internal.constraintvalidators.EmailValidator;
import org.hibernate.validator.internal.constraintvalidators.URLValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ejie.ab39a.constantes.Constantes;

/**
 * Agrupa utilidades para la validacion de datos.
 * 
 *  
 * 
 */
public class UtilValidation {
	private static final Logger logger = LoggerFactory.getLogger(UtilValidation.class);

	private static final Pattern PATTERN_TLF = Pattern.compile("^(\\+)?[0-9]{8,15}$");
	private static final EmailValidator emailValidator = new EmailValidator();
	private static final URLValidator urlValidator = new URLValidator();

	/**
	 * Constructor privado
	 */
	private UtilValidation() {
	}

	/**
	 * Devuelve true en caso de que la cadena represente un número entero.
	 * 
	 * @param str
	 *            String str
	 * 
	 * @return Boolean Boolean
	 */
	public static Boolean isNumber(String str) {
		UtilValidation.logger.trace("isNumber {}", str);
		try {
			Long.parseLong(str);
		} catch (NumberFormatException nfe) {
			return false;
		}
		return true;
	}

	/**
	 * Devuelve true en caso de que la cadena represente un número entero o
	 * decimal.
	 * 
	 * @param str
	 *            String str
	 * @param decimalSeparator
	 *            String decimalSeparator
	 * @return Boolean Boolean
	 */
	public static Boolean isDecimal(String str, String decimalSeparator) {
		UtilValidation.logger.trace("isDecimal {}", str);
		Boolean resultado = false;
		if (!Utilities.isEmpty(str)) {
			final String ds = !Utilities.isEmpty(decimalSeparator) ? decimalSeparator : ".";
			String[] parts = str.split(ds.toString());
			resultado = parts.length >= 1 && parts.length <= Constantes.NUM_DOS && UtilValidation.isNumber(parts[0]);
			if (parts.length > 1) {
				resultado = resultado && (UtilValidation.isNumber(parts[1]));
			}
		}
		return resultado;
	}

	/**
	 * Devuelve true en caso de que la cadena represente una URL. Utiliza
	 * libreria apache-commons.
	 * 
	 * @param str
	 *            String str
	 * @return Boolean Boolean
	 */
	public static boolean isValidURL(String str) {
		UtilValidation.logger.trace("isValidURL {}", str);
		if (Utilities.isEmpty(str)) {
			return false;
		}
		return UtilValidation.urlValidator.isValid(str, null);
	}

	/**
	 * Devuelve true en caso de que la cadena represente un eMail
	 * 
	 * @param str
	 *            String str
	 * @return boolean boolean
	 */
	public static boolean isValidEmail(String str) {
		UtilValidation.logger.trace("isValidEmail {}", str);
		if (Utilities.isEmpty(str)) {
			return false;
		}
		return UtilValidation.emailValidator.isValid(str, null);
	}

	/**
	 * Devuelve true en caso de que la cadena represente un teléfono.<br/>
	 * <b>Formato</b>: +34666666666, 987666666, 600600600 ...
	 * 
	 * @param str
	 *            String str
	 * @return boolean boolean
	 */
	public static boolean isValidPhone(String str) {
		UtilValidation.logger.trace("isValidPhone {}", str);
		if (Utilities.isEmpty(str)) {
			return false;
		}
		return UtilValidation.PATTERN_TLF.matcher(str).find();
	}

	private static final int TAM_NIF = 9;
	private static final String LETRAS_NIF = "TRWAGMYFPDXBNJZSQVHLCKE";
	private static final int NUM_LETRAS_NIF = 23;
	private static final int POSICION_LETRA_NIF = 8;

	/**
	 * Metodo que valida si es correcto el nif
	 * 
	 * @param str
	 *            String str
	 * @return boolean boolean
	 */
	public static boolean isValidNif(String str) {
		UtilValidation.logger.trace("isValidNif {}", str);
		boolean resultado = false;
		try {
			String nif = str.toUpperCase();
			if (nif.startsWith("T")) {
				resultado = (nif.length() <= Integer.valueOf(UtilValidation.TAM_NIF));
			} else {
				if (nif.length() == UtilValidation.TAM_NIF) {
					if (nif.startsWith("X")) {
						nif = Utilities.concat("0", nif.substring(1));
					} else if (nif.startsWith("Y")) {
						nif = Utilities.concat("1", nif.substring(1));
					} else if (nif.startsWith("Z")) {
						nif = Utilities.concat("2", nif.substring(1));
					}
					if (nif.matches("[0-9]{8}[" + UtilValidation.LETRAS_NIF + "]")) {
						int dni = Integer.parseInt(nif.substring(0, UtilValidation.POSICION_LETRA_NIF));
						char letraCalculada = UtilValidation.LETRAS_NIF.charAt(dni % UtilValidation.NUM_LETRAS_NIF);
						if (letraCalculada == nif.charAt(UtilValidation.POSICION_LETRA_NIF)) {
							resultado = true;
						}
					}
				} else {
					resultado = false;
				}
			}
		} catch (Exception e) {
			resultado = false;
		}

		return resultado;
	}

	private static final int TAM_CIF = 9;
	private static final Pattern cifPattern = Pattern.compile("[[A-H][J-N][P-S]UVW][0-9]{7}[0-9A-J]");
	private static final String CONTROL_SOLO_NUMEROS = "ABEH";
	private static final String CONTROL_SOLO_LETRAS = "KPQS";
	private static final String CONTROL_NUMERO_A_LETRA = "JABCDEFGHI";
	private static final int BASE_DIEZ = 10;

	/**
	 * Realiza la validacion si la cadena representa un CIF
	 * 
	 * @param str
	 *            la cadena a comprobar
	 * @return true si la cadena representa un CIF del tipo indicado
	 */
	public static boolean isValidCif(String str) {
		UtilValidation.logger.trace("isValidCif {}", str);
		String cif = str.toUpperCase();
		try {
			if (!UtilValidation.cifPattern.matcher(cif).matches()) {
				// No cumple el patrón
				return false;
			}
			// Sumamos los dígitos pares
			int parA = 0;
			for (int i = Constantes.NUM_DOS; i < (UtilValidation.TAM_CIF - 1); i += Constantes.NUM_DOS) {
				final int digito = Character.digit(cif.charAt(i), 10);
				if (digito < 0) {
					return false;
				}
				parA += digito;
			}
			// Para cada uno de los dígitos de la posiciones impares,
			// multiplicarlo por 2 y sumar los dígitos del resultado
			int nonB = 0;
			for (int i = 1; i < UtilValidation.TAM_CIF; i += Constantes.NUM_DOS) {
				final int digito = Character.digit(cif.charAt(i), 10);
				if (digito < 0) {
					return false;
				}
				int nn = Constantes.NUM_DOS * digito;
				if (nn >= UtilValidation.BASE_DIEZ) {
					nn = 1 + (nn - UtilValidation.BASE_DIEZ);
				}
				nonB += nn;
			}
			// Sumar pares mas impares
			final int parcialC = parA + nonB;
			// Obtener solo el último digito de parcialC, obteniendo digitoE
			final int digitoE = parcialC % UtilValidation.BASE_DIEZ;
			// Si digitoE es mayor que 0 restar el valor de digitoE a 10. Si
			// digitoE es = 0 nos quedamos con el 0
			final int digitoD = (digitoE > 0) ? (UtilValidation.BASE_DIEZ - digitoE) : 0;
			final char letraIni = cif.charAt(0);
			final char caracterFin = cif.charAt(8);
			// El último dígito (posición 9) es un código de control que puede
			// ser un número o una letra:
			// Será una LETRA si la clave de entidad es K, P, Q ó S.
			// Será un NUMERO si la entidad es A, B, E ó H.
			// Para otras claves de entidad: el dígito podrá ser tanto número
			// como letra.
			/*
			 * final boolean esControlValido = // ¿el caracter de control es
			 * válido como letra?
			 * (UtilValidation.CONTROL_SOLO_NUMEROS.indexOf(letraIni) < 0 &&
			 * UtilValidation.CONTROL_NUMERO_A_LETRA.charAt(digitoD) ==
			 * caracterFin) || // ¿el caracter de control es válido como dígito?
			 * (UtilValidation.CONTROL_SOLO_LETRAS.indexOf(letraIni) < 0 &&
			 * digitoD == Character.digit(caracterFin,
			 * UtilValidation.BASE_DIEZ));
			 */
			return // ¿el caracter de control es válido como letra?
			(UtilValidation.CONTROL_SOLO_NUMEROS.indexOf(letraIni) < 0
					&& UtilValidation.CONTROL_NUMERO_A_LETRA.charAt(digitoD) == caracterFin) ||
			// ¿el caracter de control es válido como dígito?
					(UtilValidation.CONTROL_SOLO_LETRAS.indexOf(letraIni) < 0
							&& digitoD == Character.digit(caracterFin, UtilValidation.BASE_DIEZ));

		} catch (Exception e) {
			return false;
		}
	}

	private static final int IBANNUMBER_MIN_SIZE = 15;
	private static final int IBANNUMBER_MAX_SIZE = 34;
	private static final BigInteger IBANNUMBER_MAGIC_NUMBER = new BigInteger("97");

	/**
	 * 
	 * Realiza la validacion si la cadena representa un IBAN valido
	 * 
	 * @param str
	 *            la cadena a comprobar
	 * @return true si la cadena representa un IBAN valido
	 */
	public static boolean isValidIBAN(String str) {
		final int longCadena = 4;
		String newAccountNumber = str.trim();
		// Check that the total IBAN length is correct as per the country. If
		// not, the IBAN is invalid. We could also check
		// for specific length according to country, but for now we won't
		if (newAccountNumber.length() < UtilValidation.IBANNUMBER_MIN_SIZE
				|| newAccountNumber.length() > UtilValidation.IBANNUMBER_MAX_SIZE) {
			return false;
		}
		// Move the four initial characters to the end of the string.
		newAccountNumber = newAccountNumber.substring(longCadena) + newAccountNumber.substring(0, longCadena);
		// Replace each letter in the string with two digits, thereby expanding
		// the string, where A = 10, B = 11, ..., Z = 35.
		StringBuilder numericAccountNumber = new StringBuilder();
		for (int i = 0; i < newAccountNumber.length(); i++) {
			numericAccountNumber.append(Character.getNumericValue(newAccountNumber.charAt(i)));
		}
		// Interpret the string as a decimal integer and compute the remainder
		// of that number on division by 97.
		BigInteger ibanNumber = new BigInteger(numericAccountNumber.toString());
		return ibanNumber.mod(UtilValidation.IBANNUMBER_MAGIC_NUMBER).intValue() == 1;
	}

	/**
	 * Valida el digito de control de una cuenta bancaria
	 * 
	 * @param str
	 *            numcuenta de 24 digitos
	 * @return true si el dc es correcto
	 */
	public static boolean isValidDC(String str) {
		final int magicNumberDC = 11, // Numero primo del algoritmo
				base10 = 10, // numeros en base10
				cuenta_iban = 24, // longitud iban
				p_iban = 4, p_banco = 4, p_sucursal = 4, p_dc = 2, p_cuenta = 10;
		final int[] factores = { 1, 2, 4, 8, 5, 10, 9, 7, 3, 6 };// 2n % 11

		boolean resul = false;
		try {
			String strbanco, strsucursal, strdcControl, strcuenta;
			int pos_ini = str.length() == cuenta_iban ? p_iban : 0;
			strbanco = str.substring(pos_ini, pos_ini += p_banco);
			strsucursal = str.substring(pos_ini, pos_ini += p_sucursal);
			strdcControl = str.substring(pos_ini, pos_ini += p_dc);
			strcuenta = str.substring(pos_ini, pos_ini += p_cuenta);
			// digito para banco y sucursal (00BBBBSSSS):
			int dc1 = 0;
			char[] arrayB_S = new StringBuilder().append("00").append(strbanco).append(strsucursal).toString()
					.toCharArray();
			for (int i = 0; i < arrayB_S.length; i++) {
				dc1 += Character.getNumericValue(arrayB_S[i]) * factores[i];
			}
			dc1 = magicNumberDC - (dc1 % magicNumberDC);
			dc1 = dc1 == base10 ? 1 : dc1 == base10 + 1 ? 0 : dc1;
			// digito para el num. de cuenta (CCCCCCCCCC)
			int dc2 = 0;
			char[] arrayCta = strcuenta.toCharArray();
			for (int i = 0; i < arrayCta.length; i++) {
				dc2 += Character.getNumericValue(arrayCta[i]) * factores[i];
			}
			dc2 = magicNumberDC - (dc2 % magicNumberDC);
			dc2 = dc2 == base10 ? 1 : dc2 == base10 + 1 ? 0 : dc2;
			String dc = String.valueOf(dc1) + String.valueOf(dc2);
			resul = (Integer.parseInt(strdcControl) == Integer.parseInt(dc));
		} catch (Exception e) {
			UtilValidation.logger.trace("isValidDC {}", e);
		}
		return resul;
	}

}
