package es.ejie.frmk.infraestructura.mail;

import java.util.ArrayList;
import java.util.Date;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.control.excepciones.Q70FrameworkException;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.infraestructura.utilidades.concurrencia.Q70Lock;

/**
 * El <b>Gestor de Mail</b> es un componente que permite interactuar con el
 * servidor corporativo de correo electrónico de EJIE para enviar mensajes de
 * correo electrónico y gestionar la Autentificación de los mismos.El servidor
 * de correo de salida y el indicador que permite enviar los emails al resto de
 * direcciones de email en caso de que falle una, se configura en el fichero de
 * configuración XML, y estará por defecto en tanto en cuanto no se haya
 * especificado uno al enviar el correo. El <b>Gestor de Mail<b> permite
 * enviar:
 * <p>
 * <ul>
 * <li> Texto Plano</li>
 * <li> Texto en HTML</li>
 * <li> Ficheros Adjuntos (n ficheros)</li>
 * <li> Ficheros Incrustados en HTML (n ficheros)</li>
 * </ul>
 * </p>
 * Se enviará como cuerpo principal del email el texto plano o el texto en HTML.
 * En caso de que en el mismo email haya texto plano y HTML, la parte
 * correspondiente a HTML se enviará como fichero adjunto y la de texto plano en
 * el cuerpo principal.
 * @version 2.0 Fecha: 22-mar-06
 */
public abstract class Q70GestorMailBase implements Q70GestorMailServidorInterface {

	private String smtpFichero;
	private String sendPartial;

	private Q70Lock lock = null;
	
	/**
	 * @return Returns the lock.
	 */
	protected Q70Lock getLock() {
		return lock;
	}
	
	/**
	 * @param lock The lock to set.
	 */
	protected void setLock(Q70Lock lock) {
		this.lock = lock;
	}
	
	/**
	 * Método que al pasarle un objeto Q70MailEnvelope es capaz< de enviar un mail.
	 * @param Q70MailEnvelope Estructura de datos que representa el email a enviar.
	 * @see es.ejie.frmk.infraestructura.mail.Q70GestorMailInterface#dispatch(es.ejie.frmk.infraestructura.mail.Q70MailMessage)
	 */
	public void dispatch(Q70MailEnvelope mailEnvelope) throws Q70MailException {
		Properties props;

		getLock().entradaZonaCritica();
		Q70TrazaInterface traza = Q70Factoria.getTraza();
		try {
			Q70MailMessage mailMessage = mailEnvelope.getMessage();

			props = new Properties();

			// Asignamos el host, puerto de salida del e-mail

			props.put("mail.smtp.host", valoresDefectos(mailEnvelope.getHost(), this.getSMTP()));
			props.put("mail.smtp.port", valoresDefectos(mailEnvelope.getPuerto(), Q70Constantes.PORT_SMTP));
			// En caso de error de envio de un correo, que envie el resto
			if (null != mailEnvelope.getSendPartial())
				props.put("mail.smtp.sendpartial", mailEnvelope.getSendPartial().toString());
			else
				props.put("mail.smtp.sendpartial", this.getSendPartial());

			// Si hay autorización la configuramos
			if (null != mailEnvelope.getAuthentication()) // getMailAuthentication())
				props.put("mail.smtp.auth", "true");

			// Instanciamos la session de envio del email.
			Session session = Session.getInstance(props, mailEnvelope.getAuthentication());

			MimeMessage message = new MimeMessage(session);
			// Tipo del contenido de la cabecera sea form-data
			Multipart partesMail = new MimeMultipart("form-data");

			try {
				message.setSentDate(new Date());
				// Asignamos el asunto. Se codifica el texto del asunto por los
				// acentos.
				message.setSubject(MimeUtility.encodeText(mailMessage.getAsunto(), Q70Constantes.ISO88591, Q70Constantes.ENCODING));
				// Asignamos el remitente del email
				message.setFrom(new InternetAddress(mailMessage.getRemitente().getDireccionEmail(), mailMessage.getRemitente().getNombrePersonal()));
				// Asignamos los destinatarios del e-mail. Es una lista de
				// Q70MailUser

				if (!mailMessage.getAllDestinatarios().isEmpty())
					message.addRecipients(Message.RecipientType.TO, getAddress(mailMessage.getAllDestinatarios()));
				// Asignamos los destinatarios con copia del e-mail. Es una lista de
				// Q70MailUser
				if (!mailMessage.getAllDestinatariosCC().isEmpty())
					message.addRecipients(Message.RecipientType.CC, getAddress(mailMessage.getAllDestinatariosCC()));
				// Asignamos los destinatarios con copia oculta del e-mail. Es una
				// lista de Q70MailUser
				if (!mailMessage.getAllDestinatariosCCO().isEmpty())
					message.addRecipients(Message.RecipientType.BCC, getAddress(mailMessage.getAllDestinatariosCCO()));

				// Gestión del Cuerpo del Mensaje Principal. Si hay HTML y texto se
				// adjunta HTML como fichero,
				// sino se coloca como cuerpo del mensaje principal.
				partesMail = getCuerpoPrincipal(mailMessage.getTextoPlano(), mailMessage.getTextoHTML(), partesMail);

				if (crearAdjuntoHTML(mailMessage.getTextoPlano(), mailMessage.getTextoHTML()))
					mailMessage.addFicheroAdjunto(new Q70MailFile(null, mailMessage.getTextoHTML().getBytes(Q70Constantes.ISO88591), "text/html"));

				// Adjuntamos los ficheros adjuntos en el email. Es una colección de
				// Q70MailFile
				if (!mailMessage.getAllFicherosAdjuntos().isEmpty())
					partesMail = getPartesAdjuntas(mailMessage.getAllFicherosAdjuntos(), false, partesMail);
				// Adjuntamos los ficheros incrustados en el email. Es una colección
				// de Q70MailFile
				if (!mailMessage.getAllFicherosIncrustados().isEmpty())
					partesMail = getPartesAdjuntas(mailMessage.getAllFicherosIncrustados(), true, partesMail);

				// Cada porción del email (cuerpo del email, ficheros adjuntos,
				// ficheros incrustados) es un conjunto de
				// partes que las unimos al mensaje.

				message.setContent(partesMail);
				// Enviamos el mensaje
				Transport.send(message);

				// Miramos si asignamos traza
				if (traza.isTraceActive()) {
					traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.DEBUG, "Correo Enviado");
				}

			} catch (Exception e) {
				throw new Q70MailException(this.getClass().getName(), "dispatch", "Se ha recibido una excepcion en el componente de email", e);
			}		
		} catch (RuntimeException e) {
			throw e;
		} finally {
			getLock().salidaZonaCritica();
		}

	}

	/**
	 * Si no existe valor puesto por el usuario, retorna el valor por defecto
	 * @param valor
	 * @param defecto
	 * @return String
	 */
	private String valoresDefectos(String valor, String defecto) {
		return (null != valor && valor.trim().length() > 0) ? valor : defecto;
	}

	/**
	 * verdadero si en el mismo email hay texto plano y html, falso en caso
	 * contrario
	 * @param plano
	 * @param html
	 * @return boolean
	 */

	private boolean crearAdjuntoHTML(String plano, String html) {
		return (null != plano && null != html && plano.trim().length() > 0 && html.trim().length() > 0);
	}

	/**
	 * Se asigna el cuerpo principal del email y se añade a la estructura del
	 * email.
	 * @param plano
	 * @param html
	 * @param partesMail
	 * @return Multipart
	 * @throws MessagingException
	 * @throws Exception
	 */
	private Multipart getCuerpoPrincipal(String plano, String html, Multipart partesMail) throws MessagingException, Exception {
		MimeBodyPart cuerpoPrincipal = new MimeBodyPart();

		if (null != html && html.trim().length() > 0)
			if ((null != plano) && (plano.trim().length() > 0))
				cuerpoPrincipal.setContent(plano, "text/plain;charset=" + Q70Constantes.ISO88591);
			else
				cuerpoPrincipal.setContent(html, "text/html;charset=" + Q70Constantes.ISO88591);
		else
			cuerpoPrincipal.setContent(plano, "text/plain;charset=" + Q70Constantes.ISO88591);

		partesMail.addBodyPart(cuerpoPrincipal);

		return partesMail;
	}

	/**
	 * Se añaden los ficheros adjuntos e incrustados en la estructura del
	 * e-mail. Se pasa un ArrayList del tipo Q70MailFile.
	 * @param lista
	 * @param partesMail
	 * @return Multipart
	 * @throws Exception
	 */
	private Multipart getPartesAdjuntas(ArrayList lista, boolean incrustado, Multipart partesMail) throws MessagingException {
		MimeBodyPart cuerpoAttach = null;
		for (int i = 0; i < lista.size(); i++) {
			cuerpoAttach = new MimeBodyPart();
			Q70MailFile mailFile = (Q70MailFile) lista.get(i);
			cuerpoAttach.setDataHandler(new DataHandler(mailFile));
			if (incrustado)
				cuerpoAttach.setHeader("Content-ID", getCIDFichero(mailFile.getName()));
			if (null != mailFile.getName() && mailFile.getName().trim().length() > 0)
				cuerpoAttach.setFileName(mailFile.getName());
			partesMail.addBodyPart(cuerpoAttach);

		}
		return partesMail;
	}

	/**
	 * @param cadena
	 * @return String
	 */
	private String getCIDFichero(String cadena) {
		return "<" + cadena + ">";
	}

	/**
	 * Retorna un vector de Address con las direcciones de email. Se pasa un
	 * ArrayList del tipo Q70MailUser
	 * @param lista
	 * @return lista Address
	 * @throws Exception
	 */
	private Address[] getAddress(ArrayList lista) throws Exception {
		Q70MailUser mailUser;
		Address address[] = new Address[lista.size()];

		for (int i = 0; i < lista.size(); i++) {
			mailUser = (Q70MailUser) lista.get(i);
			address[i] = new InternetAddress(mailUser.getDireccionEmail(), mailUser.getNombrePersonal());
		}
		return address;
	}

	/**
	 * Asigna el servidor de correo desde el fichero de configuración.
	 * @param smtpFichero
	 */
	public void setSMTP(String smtpFichero) throws Q70FrameworkException{
		if (!getLock().isBloqueo()) {
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"setSMTP","Se ha entrado por un método que necesita que el bloqueo del componente esté activo.",null);
		}
		this.smtpFichero = smtpFichero;			
	}

	/**
	 * Retorna el servidor de correo por defecto.
	 * @param smtpFichero
	 * @return String
	 */
	public String getSMTP() {
		getLock().entradaZonaCritica();		
		try {
			return smtpFichero;			
		} catch (RuntimeException e) {
			throw e;
		} finally {
			getLock().salidaZonaCritica();
		}
	}

	/**
	 * Asigna si envia el correo al resto de direcciones de email en caso de que
	 * falle una.
	 * @param sendPartial
	 */
	public void setSendPartial(String sendPartial) throws Q70FrameworkException{
		if (!getLock().isBloqueo()) {
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"setSendPartial","Se ha entrado por un método que necesita que el bloqueo del componente esté activo.",null);
		}
		this.sendPartial = sendPartial;			
	}

	/**
	 * Retorna el indicador de envio de direcciones de email.
	 * @param smtpFichero
	 * @return String
	 */
	public String getSendPartial() {
		getLock().entradaZonaCritica();		
		try {
			return sendPartial;			
		} catch (RuntimeException e) {
			throw e;
		} finally {
			getLock().salidaZonaCritica();
		}
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#inicializar()
	 */
	public void inicializar() throws Q70FrameworkException{

	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#liberar()
	 */
	public void liberar() throws Q70FrameworkException{

	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#activar()
	 */
	public void activar() {
		getLock().activar();
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#suspender()
	 */
	public void suspender() throws Q70FrameworkException {
		try {
			getLock().suspender();
		} catch (Exception e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método suspender de la clase Q70GestorMailBase");
			}
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"suspender","Se ha producido un error en el método suspender.",e);
		}
	}

	protected abstract void doDestruir();
	
	public void destruir() throws Q70FrameworkException{
		try {
			liberar();
			doDestruir();
			activar();
		} catch (Throwable e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método destruir de la clase Q70GestorMailBase");
			}
			throw new Q70FrameworkException(Q70Constantes.ID_COMPONENTE,this.getClass().toString(),"destruir","Se ha producido un error en el método destruir.",e);
		}
	}
}
