package com.ejie.ab39a.parse;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import java.io.StringWriter;

import javax.xml.bind.Marshaller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ejie.ab39a.utils.Utilidades;

/**
 * Clase que implementa el parser para los mensajes SOAP. Codifica desde UTF-8.
 * 
 * 
 *  
 * @param <O>
 *            o o
 */
public final class MessageParser<O> {

	private static final Logger logger = LoggerFactory.getLogger(MessageParser.class);

	private static final String DEFAULT_ENCODING = "UTF-8";

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

	/**
	 * Transforma un XML-String al objeto java especificado. Utilizando para
	 * ello el paquete al que pertenece la clase que se quiere obtener. Utiliza
	 * el encoding por defecto <B>UTF-8</B>.<br/>
	 * <b>Ejemplo: </b><br/>
	 * <code>BeanClass objBeanClass = MessageParser.parse(xmlValueStr, BeanClass.class);</code>
	 * 
	 * @param <O>
	 *            Tipo a devolver
	 * @param message
	 *            Cadena XML a convertir
	 * @param expectedType
	 *            Clase del tipo especificado
	 * @return <O> Instancia del objeto especificado
	 * @throws JAXBException
	 *             e e
	 * @throws UnsupportedEncodingException
	 *             e e
	 */
	public static <O> O parse(String message, Class<O> expectedType)
			throws JAXBException, UnsupportedEncodingException {
		return MessageParser.parse(message, expectedType, null, null);
	}

	/**
	 * Transforma un XML-String al objeto java especificado. Utilizando para
	 * ello, el paquete o la lista de clases aportadas. Si no se especifica
	 * ninguna de las dos, se tendrá en cuenta el paquete al que pertenece la
	 * clase que se quiere obtener.
	 * 
	 * @param <O>
	 *            Tipo a devolver
	 * @param message
	 *            Cadena XML a convertir
	 * @param expectedType
	 *            Clase del tipo especificado
	 * @param encoding
	 *            Encoding a utilizar
	 * @param packageClasses
	 *            Paquete que contiene las clases a utilizar.
	 * @param classes
	 *            Lista de las clases a utilizar.
	 * @return <O> Instancia del objeto especificado
	 * @throws JAXBException
	 *             e e
	 * @throws UnsupportedEncodingException
	 *             e e
	 */
	@SuppressWarnings(value = "unchecked")
	public static <O> O parse(String message, Class<O> expectedType, String encoding, Package packageClasses,
			Class<?>... classes) throws JAXBException, UnsupportedEncodingException {
		MessageParser.logger.trace("parse INI");
		O result = null;
		if (message == null) {
			return result;
		}
		/*
		 * Contemplar varias maneras de cargar las clases a utilizar en el
		 * parseo: Paquete de clases, o lista (las dos formas son excluyentes).
		 */
		JAXBContext jc;
		if (packageClasses != null) {
			jc = JAXBContext.newInstance(packageClasses.getName());
		} else if (classes != null && classes.length > 0) {
			jc = JAXBContext.newInstance(classes);
		} else {
			/*
			 * Si no se informa nada, se coge por defecto el paquete en el que
			 * se encuentra la clase a generar.
			 */
			jc = JAXBContext.newInstance(expectedType.getPackage().getName());
		}
		Unmarshaller unmarshaller = jc.createUnmarshaller();
		Object jaxbObject = unmarshaller.unmarshal(new ByteArrayInputStream(
				message.getBytes(Utilidades.isEmpty(encoding) ? MessageParser.DEFAULT_ENCODING : encoding)));
		if (jaxbObject instanceof JAXBElement<?>) {
			result = ((JAXBElement<O>) jaxbObject).getValue();
		} else {
			result = (O) jaxbObject;
		}
		if (MessageParser.logger.isTraceEnabled()) {
			MessageParser.logger.trace("parse END", result);
		}
		return result;
	}

	/**
	 * Transforma un objeto java a formato XML-String.
	 * 
	 * @param <O>
	 *            Tipo
	 * @param object
	 *            <O> object
	 * @return String String
	 * @throws JAXBException
	 *             e e
	 */
	public static <O> String unparse(O object) throws JAXBException {
		return MessageParser.unparse(object, null);
	}

	/**
	 * Transforma un objeto java a formato XML-String.
	 * 
	 * @param <O>
	 *            Tipo
	 * @param object
	 *            <O> object
	 * @param encoding
	 *            Encoding a utilizar
	 * @return String String
	 * @throws JAXBException
	 *             e e
	 */
	public static <O> String unparse(O object, String encoding) throws JAXBException {
		MessageParser.logger.trace("unparse INI ", new Object[] { object, encoding });
		if (object == null) {
			return null;
		}
		JAXBContext context = null;
		if (object instanceof JAXBElement) {
			context = JAXBContext.newInstance(((JAXBElement<?>) object).getValue().getClass());
		} else {
			context = JAXBContext.newInstance(object.getClass());
		}
		Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
		marshaller.setProperty(Marshaller.JAXB_ENCODING,
				Utilidades.isEmpty(encoding) ? MessageParser.DEFAULT_ENCODING : encoding);
		StringWriter stringWriter = new StringWriter();
		marshaller.marshal(object, stringWriter);

		if (MessageParser.logger.isTraceEnabled()) {
			MessageParser.logger.trace("unparse END ", stringWriter.getBuffer().toString());
		}
		return stringWriter.getBuffer().toString();
	}

	/**
	 * Transforma un objeto java a formato XML-String sin la cabecera XML.
	 * 
	 * @param <O>
	 *            Tipo
	 * @param object
	 *            <O> object
	 * @param encoding
	 *            Encoding a utilizar
	 * @return String String
	 * @throws JAXBException
	 *             e e
	 */
	public static <O> String unparseNoXmlVersion(O object, String encoding) throws JAXBException {
		MessageParser.logger.trace("unparse INI ", new Object[] { object, encoding });
		if (object == null) {
			return null;
		}
		JAXBContext context = null;
		if (object instanceof JAXBElement) {
			context = JAXBContext.newInstance(((JAXBElement<?>) object).getValue().getClass());
		} else {
			context = JAXBContext.newInstance(object.getClass());
		}
		Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
		marshaller.setProperty(Marshaller.JAXB_ENCODING,
				Utilidades.isEmpty(encoding) ? MessageParser.DEFAULT_ENCODING : encoding);
		StringWriter stringWriter = new StringWriter();
		marshaller.marshal(object, stringWriter);

		if (MessageParser.logger.isTraceEnabled()) {
			MessageParser.logger.trace("unparse END ", stringWriter.getBuffer().toString());
		}
		return stringWriter.getBuffer().toString();
	}
}
