package com.ejie.ab39a.utils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.security.crypto.codec.Base64;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.ejie.ab39a.constantes.Constantes;
import com.ejie.ab39a.exceptions.ParametroKey;
import com.ejie.ab39a.model.EjePe;
import com.ejie.ab39a.model.ElementosArbol;
import com.ejie.ab39a.model.ElementosPe;
import com.gfi.constants.Constants;

import eu.medsea.mimeutil.MimeUtil;

/**
 * Utilidades
 * 
 *  
 * 
 */
public final class Utilidades {

	private static final Logger LOGGER = LoggerFactory.getLogger(Utilidades.class);

	private static final int STREAM_ALLOCATE_BUFFER_SIZE = 1024;

	/**
	 * Constructor de Utilidades
	 */
	private Utilidades() {
	}

	/**
	 * Obtiene un String con el stackTrace de un Throwable
	 * 
	 * @param aThrowable
	 *            Throwable aThrowable
	 * @return String String
	 */
	public static String getStackTrace(Throwable aThrowable) {
		final Writer result = new StringWriter();
		final PrintWriter printWriter = new PrintWriter(result);
		aThrowable.printStackTrace(printWriter);
		return result.toString();
	}

	/**
	 * Devuelve el Content-Type de una extension. Si esa extension tuviera
	 * varias, devuelve siempre la primera
	 * 
	 * @param fichero
	 *            Fichero fichero
	 * @return String con el ContentType
	 */
	public static String getMimeTypeExtension(File fichero) {
		MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
		Collection<?> mimeTypes = MimeUtil.getMimeTypes(fichero);
		String[] arrMimeTypes = mimeTypes.toString().split(Constantes.SEPARADOR_COMA);
		return arrMimeTypes.length > 0 ? arrMimeTypes[0] : "";
	}

	/**
	 * Traspasa informacion de un stream de entrada a uno de salida, de forma
	 * 'no bloqueante' (usando la libreria nio).
	 * 
	 * @param input
	 *            InputStream input
	 * @param output
	 *            OutputStream output
	 * @return long long
	 * @throws IOException
	 *             e e
	 */
	public static long stream(InputStream input, OutputStream output) throws IOException {

		Utilidades.LOGGER.trace("stream INI");
		ReadableByteChannel inputChannel = null;
		WritableByteChannel outputChannel = null;

		try {
			inputChannel = Channels.newChannel(input);
			outputChannel = Channels.newChannel(output);
			ByteBuffer buffer = ByteBuffer.allocate(Utilidades.STREAM_ALLOCATE_BUFFER_SIZE);
			long size = 0;

			while (inputChannel.read(buffer) != -1) {
				buffer.flip();
				size += outputChannel.write(buffer);
				buffer.clear();
			}

			Utilidades.LOGGER.trace("stream FIN", size);
			return size;
		} finally {
			if (outputChannel != null) {
				try {
					outputChannel.close();
				} catch (IOException e) {
					Utilidades.LOGGER.error("Error al cerrar canal de salida", e);
				}
			}
			if (inputChannel != null) {
				try {
					inputChannel.close();
				} catch (IOException e) {
					Utilidades.LOGGER.error("Error al cerrar canal de entrada", e);
				}
			}
		}
	}

	/**
	 * Metodo que genera un path de un fichero temporal
	 * 
	 * 
	 * @param seed
	 *            String seed
	 * @param ext
	 *            String ext
	 * @return String String
	 */
	public static String generarPathFicheroTemp(String seed, String ext) {

		StringBuilder builder = new StringBuilder();
		builder.append(UtilProperties.Props.temp_path.getValue());
		builder.append(seed).append(Constantes.UNDERSCORE).append(System.currentTimeMillis());
		builder.append(Constantes.SEPARADOR_EXTENSION).append(ext);

		return builder.toString();
	}

	/**
	 * Consigue el mensaje "code" del messageSource indicado, en el idioma
	 * actual, utilizando los parametros indicados
	 * 
	 * *Es posible que si se cambian los literales a BBDD, haga falta cambiar*
	 * 
	 * @param messageSource
	 *            ReloadableResourceBundleMessageSource messageSource
	 * @param code
	 *            String code
	 * @param params
	 *            Object[] params
	 * @return String String
	 */
	public static String getLiteralMessageWithParams(ReloadableResourceBundleMessageSource messageSource, String code,
			Object[] params) {
		Locale locale = LocaleContextHolder.getLocale();
		return messageSource.getMessage(code, params, locale);
	}

	/**
	 * Metodo que parsea los parametros a objects
	 * 
	 * @param messageSource
	 *            ReloadableResourceBundleMessageSource messageSource
	 * @param listaParametroKey
	 *            List<ParametroKey> listaParametroKey
	 * @return Object[] Object
	 */
	public static Object[] parseParametroKeyToObject(ReloadableResourceBundleMessageSource messageSource,
			List<ParametroKey> listaParametroKey) {
		Object[] objetos = null;
		if (!Utilidades.isEmptyList(listaParametroKey)) {
			objetos = new Object[listaParametroKey.size()];
			int i = 0;
			for (ParametroKey parametroKey : listaParametroKey) {
				objetos[i] = parametroKey.isEsKey()
						? Utilidades.getLiteralMessage(messageSource, parametroKey.getValorParametro())
						: parametroKey.getValorParametro();
				i++;
			}
		}
		return objetos;
	}

	/**
	 * Devuelve true en caso de que la cadena sea:
	 * <li><b>null</b></li>
	 * <li><b>length == 0</b></li>.<br>
	 * 
	 * @param cadena
	 *            String cadena
	 * @return Boolean Boolean
	 */
	public static Boolean isEmpty(String cadena) {
		return cadena == null || cadena.trim().length() == 0;
	}

	/**
	 * Devuelve true en caso de que la lista sea:
	 * <li><b>null</b></li>
	 * <li><b>length == 0</b></li>.<br>
	 * 
	 * @param lista
	 *            List<?> lista
	 * @return Boolean Boolean
	 */
	public static Boolean isEmptyList(List<?> lista) {
		return lista == null || lista.isEmpty();
	}

	/**
	 * Consigue el mensaje "code" del messageSource indicado, en el idioma
	 * actual
	 * 
	 * *Es posible que si se cambian los literales a BBDD, haga falta cambiar*
	 * 
	 * @param messageSource
	 *            ReloadableResourceBundleMessageSource messageSource
	 * @param code
	 *            String code
	 * @return String String
	 */
	public static String getLiteralMessage(ReloadableResourceBundleMessageSource messageSource, String code) {
		Locale locale = LocaleContextHolder.getLocale();
		return messageSource.getMessage(code, null, locale);
	}

	/**
	 * Caso de contener errores, estos serán devueltos en la response en formato
	 * String para ser tratados por el feedback de errores. No formato Json
	 * 
	 * @param error
	 *            String error
	 * @param response
	 *            HttpServletResponse response
	 * @return boolean boolean
	 */
	public static boolean sendError(String error, HttpServletResponse response) {
		boolean hayErrores = false;
		if (error != null) {
			try {
				response.setContentType("application/json;charset=UTF-8");
				response.setCharacterEncoding(Constants.UTF8);
				byte[] errorAux = error.getBytes(Constants.UTF8);
				response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
				response.setContentLength(errorAux.length);
				response.getOutputStream().write(errorAux);
				response.flushBuffer();
			} catch (IOException e) {
				Utilidades.LOGGER.error("Error al enviar un error por la response", e);
			}
			hayErrores = true;
		}
		return hayErrores;
	}

	/**
	 * Metodo que obtiene una cadena con los parámetros recibidos
	 * 
	 * @param cadena
	 *            String cadena
	 * @return String String
	 */
	public static String obtenerCadena(String... cadena) {
		StringBuilder stb = new StringBuilder(Constants.STRING_BUILDER_INIT);
		for (int i = 0; i < cadena.length; i++) {
			stb.append(cadena[i]);
		}
		return stb.toString();
	}

	/**
	 * Parsea un String que contiene un xml a un Document
	 * 
	 * @param strXml
	 *            String strXml
	 * @return Document Document
	 * @throws IOException
	 *             e e
	 * @throws SAXException
	 *             e e
	 * @throws ParserConfigurationException
	 *             e e
	 * @throws UnsupportedEncodingException
	 *             e e
	 */
	public static Document parseStringToDocument(String strXml)
			throws UnsupportedEncodingException, ParserConfigurationException, SAXException, IOException {

		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		return builder.parse(new ByteArrayInputStream(strXml.getBytes("utf-8")));
	}

	/**
	 * parse Document To String
	 * 
	 * @param node
	 *            Document node
	 * @return String String
	 */
	public static String parseDocumentToString(Document node) {
		Utilidades.LOGGER.debug("INI parseDocumen2String");
		try {
			Source source = new DOMSource(node);
			StringWriter stringWriter = new StringWriter();
			Result result = new StreamResult(stringWriter);
			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer transformer = factory.newTransformer();
			transformer.transform(source, result);
			String resultado = stringWriter.getBuffer().toString();
			Utilidades.LOGGER.debug("FIN parseDocument2String");
			return resultado;
		} catch (TransformerConfigurationException e) {
			Utilidades.LOGGER.error("Se ha producido un error en el metodo parseDocumen2String: " + e.getMessage());
		} catch (TransformerException e) {
			Utilidades.LOGGER.error("Se ha producido un error en el metodo parseDocumen2String: " + e.getMessage());
		}
		return null;
	}

	/**
	 * Dada la ruta completa al fichero, devuelve el String con el contenido del
	 * mismo, quitados los espacios innecesarios (haciendo un trim)
	 * 
	 * @param rutaCompletaFichero
	 *            String rutaCompletaFichero
	 * @return String String
	 * @throws IOException
	 *             e e
	 */
	public static String getContenidoFichero(String rutaCompletaFichero) throws IOException {
		File file = new File(rutaCompletaFichero);
		FileInputStream inputStream = new FileInputStream(file);
		String resul = null;
		try {
			resul = IOUtils.toString(inputStream, Constantes.UTF_8);
		} finally {
			inputStream.close();
		}

		return resul;
	}

	/**
	 * Compara el plan de la version seleccionada con la version anterior
	 * 
	 * @param listaArbolVersion
	 *            List<ElementosArbol> lista con el arbol de la version
	 *            seleccionada
	 * @param listaArbolVersionAnterior
	 *            Map<Long, ElementosPe> mapa con los elementos de la version
	 *            anterior
	 * @param listaEjesVersionAnterior
	 *            Map<Long, EjePe> mapa con los ejes de la version anterior
	 * @return List<ElementosArbol> lista
	 */
	@SuppressWarnings(value = "rawtypes")
	public static Map<String, Object> compararPlanes(List<ElementosArbol> listaArbolVersion,
			Map<Long, ElementosPe> listaArbolVersionAnterior, Map<Long, EjePe> listaEjesVersionAnterior) {
		Utilidades.LOGGER.debug("Inicio compararPlanes");

		List<ElementosArbol> elementoArbol = listaArbolVersion.get(0).getHijosArbol();
		EjePe eje = new EjePe();
		for (int i = 0; i < elementoArbol.size(); i++) {
			eje = (EjePe) listaEjesVersionAnterior.get(elementoArbol.get(i).getIdElementoA());
			if (!(elementoArbol.get(i).getHelburucasA() == null ? Constantes.VACIO
					: elementoArbol.get(i).getHelburucasA())
							.equals(eje.getHelburucas() == null ? Constantes.VACIO : eje.getHelburucas())) {
				elementoArbol.get(i).setElemDistinto(1);
				elementoArbol.get(i).setElemAntiguo(eje);
			} else if (!(elementoArbol.get(i).getHelburuA() == null ? Constantes.VACIO
					: elementoArbol.get(i).getHelburuA())
							.equals(eje.getHelburu() == null ? Constantes.VACIO : eje.getHelburu())) {
				elementoArbol.get(i).setElemDistinto(1);
				elementoArbol.get(i).setElemAntiguo(eje);
			}
			comparaHijos(elementoArbol.get(i).getHijosArbol(), listaArbolVersionAnterior);

		}

		Map<String, Object> mapaDatos = new HashMap<String, Object>();
		mapaDatos.put("listaArbol", listaArbolVersion);
		List<ElementosArbol> listaElemBorrados = new ArrayList<ElementosArbol>();
		Iterator it = listaArbolVersionAnterior.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry e = (Map.Entry) it.next();
			listaElemBorrados.add((ElementosArbol) e.getValue());
		}
		mapaDatos.put("listaElemBorrados", listaElemBorrados);

		Utilidades.LOGGER.debug("Fin compararPlanes");
		return mapaDatos;
	}

	/**
	 * Compara los hijos del arbol
	 * 
	 * @param elementoArbol
	 *            List<ElementosArbol> elementoArbol
	 * @param listaArbolVersionAnterior
	 *            Map<Long, ElementosPe> listaArbolVersionAnterior
	 */
	public static void comparaHijos(List<ElementosArbol> elementoArbol,
			Map<Long, ElementosPe> listaArbolVersionAnterior) {

		ElementosPe elementoAnt = new ElementosPe();
		for (int x = 0; x < elementoArbol.size(); x++) {

			ElementosPe elemPe = (ElementosPe) elementoArbol.get(x);
			elementoAnt = (ElementosPe) listaArbolVersionAnterior.get(elemPe.getIdElementoBase());

			if (elementoAnt != null) {

				listaArbolVersionAnterior.remove(elemPe.getIdElementoBase());

				if (!(elemPe.getNombreA() == null ? Constantes.VACIO : elemPe.getNombreA())
						.equals(elementoAnt.getNombreA() == null ? Constantes.VACIO : elementoAnt.getNombreA())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!(elemPe.getNombrecasA() == null ? Constantes.VACIO : elemPe.getNombrecasA())
						.equals(elementoAnt.getNombrecasA() == null ? Constantes.VACIO : elementoAnt.getNombrecasA())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!(elemPe.getHelburucasA() == null ? Constantes.VACIO : elemPe.getHelburucasA()).equals(
						elementoAnt.getHelburucasA() == null ? Constantes.VACIO : elementoAnt.getHelburucasA())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!(elemPe.getHelburuA() == null ? Constantes.VACIO : elemPe.getHelburuA())
						.equals(elementoAnt.getHelburuA() == null ? Constantes.VACIO : elementoAnt.getHelburuA())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getHasEus().equals(elementoAnt.getHasEus())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getHasElebi().equals(elementoAnt.getHasElebi())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAurreEus().equals(elementoAnt.getAurreEus())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAurreElebi().equals(elementoAnt.getAurreElebi())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAmaiEus().equals(elementoAnt.getAmaiEus())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAmaiElebi().equals(elementoAnt.getAmaiElebi())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAmaiValor().equals(elementoAnt.getAmaiValor())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getAurreValor().equals(elementoAnt.getAurreValor())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				} else if (!elemPe.getHasValor().equals(elementoAnt.getHasValor())) {
					elementoArbol.get(x).setElemDistinto(1);
					elementoArbol.get(x).setElemAntiguo(elementoAnt);
				}
			} else {
				elementoArbol.get(x).setElemDistinto(1);
				elementoArbol.get(x).setElemAntiguo(new ElementosPe());
				elementoArbol.get(x).setIsElemNuevo(1);
			}

			comparaHijos(elemPe.getHijosArbol(), listaArbolVersionAnterior);
		}
	}

	/**
	 * Devuelve un String decodificado
	 * 
	 * @param buffer
	 *            String en base64
	 * @return String decodificado
	 * @throws UnsupportedEncodingException
	 *             e e
	 */
	public static String decodeBase64(String buffer) throws UnsupportedEncodingException {
		return Utilidades.decodeBase64(buffer, Constantes.UTF_8);
	}

	/**
	 * Convierte el array de bytes en base 64, con el encoding especificado
	 * 
	 * @param encoding
	 *            String a decodificar
	 * @param buffer
	 *            String buffer
	 * @return String array
	 * @throws UnsupportedEncodingException
	 *             e e
	 * 
	 * 
	 */
	public static String decodeBase64(String buffer, String encoding) throws UnsupportedEncodingException {

		return new String(Base64.decode(buffer.getBytes(encoding)), encoding);

	}

	/**
	 * Convierte el array de bytes en base 64
	 * 
	 * @param buffer
	 *            byte[] a decodificar
	 * @return byte[] array
	 * @throws UnsupportedEncodingException
	 *             e e
	 */
	public static byte[] decodeBase64(byte[] buffer) throws UnsupportedEncodingException {

		return Base64.decode(buffer);
	}
}