/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved.                *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program is distributed in the hope   *
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 * For the text or an alternative of this public license, you may reach us    *
 * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *
 * or via info@compiere.org or http://www.compiere.org/license.html           *
 *****************************************************************************/
package org.compiere.util;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.URLDataSource;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.SendFailedException;
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 org.compiere.model.MClient;
import org.compiere.model.MSMTP;
import org.compiere.model.MSysConfig;
import com.sun.mail.smtp.SMTPMessage;
/**
 *	EMail delivery and receive support for iDempiere
 *  
http://java.sun.com/products/javamail/FAQ.html
 *  
 *  When I try to send a message, I get javax.mail.SendFailedException:
 * 		550 Unable to relay for my-address
 *  
 *  This is an error reply from your SMTP mail server. It indicates that
 *  your mail server is not configured to allow you to send mail through it.
 *
 *  @author Jorg Janke
 *  @version  $Id: EMail.java,v 1.4 2006/07/30 00:54:35 jjanke Exp $
 *	@author	Michael Judd BF [ 2736995 ] - toURL() in java.io.File has been depreciated
 */
public final class EMail implements Serializable
{
	/**
	 * generated serial id
	 */
	private static final long serialVersionUID = -8982983766981221312L;
	/** HTML mail marker */
	public final static String HTML_MAIL_MARKER = "ContentType=text/html;";
	
	/** log last email send error message in context */
	public final static String EMAIL_SEND_MSG = "EmailSendMsg";
	
	/**
	 *  @param client the client
	 *  @param from Sender's EMail address
	 *  @param to   Recipient EMail address
	 *  @param subject  Subject of message
	 *  @param message  The message
	 */
	public EMail (MClient client, String from, String to,
		String subject, String message)
	{
		this (client.getCtx(), client.getSMTPHost(), client.getSMTPPort(), client.isSecureSMTP(), from, to, subject, message, false);
	}	//	EMail
	/**
	 *  @param client the client
	 *  @param from Sender's EMail address
	 *  @param to   Recipient EMail address
	 *  @param subject  Subject of message
	 *  @param message  The message
	 *  @param html
	 */
	public EMail (MClient client, String from, String to,
		String subject, String message, boolean html)
	{
		this (client.getCtx(), client.getSMTPHost(), client.getSMTPPort(), client.isSecureSMTP(), from, to, subject, message, html);
	}	//	EMail
	/**
	 *	@param ctx context
	 *  @param smtpHost The mail server
	 *  @param from Sender's EMail address
	 *  @param to   Recipient EMail address
	 *  @param subject  Subject of message
	 *  @param message  The message
	 */
	public EMail (Properties ctx, String smtpHost, String from, String to,
		String subject, String message)
	{
		this(ctx, smtpHost, 0, false, from, to, subject, message, false);
	}
	/**
	 *	@param ctx context
	 *  @param smtpHost The mail server
	 *  @param from Sender's EMail address
	 *  @param to   Recipient EMail address
	 *  @param subject  Subject of message
	 *  @param message  The message
	 *  @param html html email
	 */
	public EMail (Properties ctx, String smtpHost, String from, String to,
		String subject, String message, boolean html)
	{
	   this(ctx, smtpHost, 0, false, from, to, subject, message, html);
	}
	/**
	 *	@param ctx context
	 *  @param smtpHost The mail server
	 *  @param smtpPort
	 *  @param isSecureSmtp
	 *  @param from Sender's EMail address
	 *  @param to   Recipient EMail address
	 *  @param subject  Subject of message
	 *  @param message  The message
	 *  @param html html email
	 */
	public EMail (Properties ctx, String smtpHost, int smtpPort, boolean isSecureSmtp, String from, String to,
		String subject, String message, boolean html)
	{
		
		setSmtpHost(smtpHost);
		setFrom(from);
		String bccAddressForAllMails = null;
		if (DB.isConnected())
			bccAddressForAllMails = MSysConfig.getValue(MSysConfig.MAIL_SEND_BCC_TO_ADDRESS, Env.getAD_Client_ID(Env.getCtx()));
		if (! Util.isEmpty(bccAddressForAllMails, true))
			addBcc(bccAddressForAllMails);
		addTo(to);
		m_ctx = ctx;
		if (subject == null || subject.length() == 0)
			setSubject(".");	//	pass validation
		else
			setSubject (subject);
		if (message != null && message.length() > 0)
		{
			if (html)
				setMessageHTML(subject, message);
			else
				setMessageText (message);
		}
		m_valid = isValid (true);
	    setSmtpPort(smtpPort);
		setSecureSmtp(isSecureSmtp);
	}	//	EMail
	
	private void setSecureSmtp(boolean isSecureSmtp) {
		m_secureSmtp = isSecureSmtp;
	}
	private void setSmtpPort(int smtpPort) {
		m_smtpPort = smtpPort;
	}
	/**
	 * Set acknowledgement for receipt (Disposition-Notification-To)
	 * @param ar
	 */
	public void setAcknoledgmentReceipt(boolean ar) {
		m_acknowledgementReceipt = ar;
	}
	/**	From Address				*/
	private InternetAddress     m_from;
	/** To Address					*/
	private ArrayList	m_to;
	/** CC Addresses				*/
	private ArrayList	m_cc;
	/** BCC Addresses				*/
	private ArrayList	m_bcc;
	/**	Reply To Address			*/
	private InternetAddress		m_replyTo;
	/**	Mail Subject				*/
	private String  			m_subject;
	/** Mail Plain Message			*/
	private String  			m_messageText;
	/** Mail HTML Message			*/
	private String  			m_messageHTML;
	/**	Mail SMTP Server			*/
	private String  			m_smtpHost;
	private int					m_smtpPort;
	private boolean				m_secureSmtp;
	private boolean				m_acknowledgementReceipt;
	
	/**	Attachments					*/
	private ArrayList	m_attachments;
	/**	UserName and Password		*/
	private transient EMailAuthenticator	m_auth = null;
	/**	Message						*/
	private SMTPMessage 		m_msg = null;
	/** Context - may be null		*/
	private Properties			m_ctx;
	/**	Info Valid					*/
	private boolean 	m_valid = false;
	/** Send result Message			*/
	private String		m_sentMsg = null;
	private List additionalHeaders = new ArrayList();
	/**	Mail Sent OK Status				*/
	public static final String      SENT_OK = "OK";
	/**	Logger							*/
	protected transient static CLogger		log = CLogger.getCLogger (EMail.class);
	/** Set it to true if you need to use the SMTP defined at tenant level - otherwise will try to use a SMTP from AD_SMTP table */
	private boolean m_forceUseTenantSmtp = false; 
	/**
	 *	Send Mail
	 *	@return OK or error message
	 */
	public String send()
	{
		String msg;
		try {
			msg = send(false);
		} catch (Exception e) {
			msg = e.getLocalizedMessage();
		}
		return msg;
	}
	/**
	 *	Send Mail
	 *	@return OK or error message
	 */
	public String sendEx() throws Exception
	{
		return send(true);
	}
	/**
	 *	Send Mail
	 *	@return OK or error message
	 * @throws Exception 
	 */
	public String send(boolean throwException) throws Exception
	{
		if (!m_forceUseTenantSmtp && getFrom() != null) {
			MSMTP smtp = MSMTP.get(m_ctx, Env.getAD_Client_ID(m_ctx), getFrom().getAddress());
			if (smtp != null) {
				setSmtpHost(smtp.getSMTPHost());
				setSmtpPort(smtp.getSMTPPort());
				setSecureSmtp(smtp.isSecureSMTP());
				createAuthenticator(smtp.getRequestUser(), smtp.getRequestUserPW());
				if (log.isLoggable(Level.FINE)) log.fine("sending email using from " + getFrom().getAddress() + " using " + smtp.toString());
			}
		}
		if (log.isLoggable(Level.INFO)){
			log.info("(" + m_smtpHost + ") " + m_from + " -> " + m_to);
			log.info("(m_auth) " + m_auth);
		}
		
		m_sentMsg = null;
		Env.getCtx().remove(EMAIL_SEND_MSG);
		
		//
		if (!isValid(true))
		{
			m_sentMsg = "Invalid Data";
			Env.getCtx().put(EMAIL_SEND_MSG, m_sentMsg);
			return m_sentMsg;
		}
		//
		Properties props = new Properties();
		props.putAll(System.getProperties());
		props.put("mail.store.protocol", "smtp");
		props.put("mail.transport.protocol", "smtp");
		props.put("mail.host", m_smtpHost);
		//Timeout for sending the email defaulted to 20 seconds if not defined in a SysConfig Key
		props.put("mail.smtp.timeout", MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_TIMEOUT, 20000, Env.getAD_Client_ID(m_ctx)));
		int mail_smtp_connectiontimeout = MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_CONNECTIONTIMEOUT, -1, Env.getAD_Client_ID(m_ctx));
		if (mail_smtp_connectiontimeout >= 0)
			props.put("mail.smtp.connectiontimeout", mail_smtp_connectiontimeout);
		int mail_smtp_writetimeout = MSysConfig.getIntValue(MSysConfig.MAIL_SMTP_WRITETIMEOUT, -1, Env.getAD_Client_ID(m_ctx));
		if (mail_smtp_writetimeout >= 0)
			props.put("mail.smtp.writetimeout", mail_smtp_writetimeout);
		if (CLogMgt.isLevelFinest())
			props.put("mail.debug", "true");
		//
		boolean isOAuth2 = false;
		if (m_auth != null)
			isOAuth2 = m_auth.isOAuth2();
		Session session = null;
		try
		{
			boolean isGmail = m_smtpHost.equalsIgnoreCase("smtp.gmail.com");
			if (m_auth != null)		//	createAuthenticator was called
				props.put("mail.smtp.auth", "true");
			if (m_smtpPort > 0)
			{
				props.put("mail.smtp.port", String.valueOf(m_smtpPort));
			} else if (isGmail)
			{
				props.put("mail.smtp.port", "587");
			}
			if (m_secureSmtp || isGmail)
			{
				props.put("mail.smtp.starttls.enable", "true");
			}
			if (isOAuth2) {
				props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
			    props.put("mail.smtp.starttls.required", "true");
			    props.put("mail.smtp.auth.login.disable","true");
			    props.put("mail.smtp.auth.plain.disable","true");
			    props.put("mail.debug.auth", "true");
				m_auth = new EMailAuthenticator (m_auth.getPasswordAuthentication().getUserName(), m_auth.getPasswordAuthentication().getPassword());
			}
			session = Session.getInstance(props);
			session.setDebug(CLogMgt.isLevelFinest());
		}
		catch (SecurityException se)
		{
			if (throwException)
				throw se;
			log.log(Level.WARNING, "Auth=" + m_auth + " - " + se.toString());
			m_sentMsg = se.toString();
			Env.getCtx().put(EMAIL_SEND_MSG, m_sentMsg);
			return se.toString();
		}
		catch (Exception e)
		{
			if (throwException)
				throw e;
			log.log(Level.SEVERE, "Auth=" + m_auth, e);
			m_sentMsg = e.toString();
			Env.getCtx().put(EMAIL_SEND_MSG, m_sentMsg);
			return e.toString();
		}
		Transport t = null;
		try
		{
			m_msg = new SMTPMessage(session);
			//	Addresses
			m_msg.setFrom(m_from);
			// IDEMPIERE-2104 - intended for test or dev systems to not send undesired emails
			boolean isDontSendToAddress = false;
			if (DB.isConnected())
				isDontSendToAddress = MSysConfig.getBooleanValue(MSysConfig.MAIL_DONT_SEND_TO_ADDRESS, false, Env.getAD_Client_ID(Env.getCtx()));
			if (! isDontSendToAddress) {
				InternetAddress[] rec = getTos();
				if (rec.length == 1)
					m_msg.setRecipient (Message.RecipientType.TO, rec[0]);
				else
					m_msg.setRecipients (Message.RecipientType.TO, rec);
				rec = getCcs();
				if (rec != null && rec.length > 0)
					m_msg.setRecipients (Message.RecipientType.CC, rec);
				rec = getBccs();
				if (rec != null && rec.length > 0)
					m_msg.setRecipients (Message.RecipientType.BCC, rec);
				if (m_replyTo != null)
					m_msg.setReplyTo(new Address[] {m_replyTo});
			} else {
				String bccAddressForAllMails = null;
				if (DB.isConnected())
					bccAddressForAllMails = MSysConfig.getValue(MSysConfig.MAIL_SEND_BCC_TO_ADDRESS, Env.getAD_Client_ID(Env.getCtx()));
				if (! Util.isEmpty(bccAddressForAllMails, true)) {
					m_msg.setRecipients (Message.RecipientType.TO, bccAddressForAllMails);
				}
				List replyToList=new ArrayList();
				if(m_replyTo!=null)
					replyToList.add(m_replyTo);
				InternetAddress[] rec = getTos();
				if (rec != null && rec.length > 0){
					m_msg.setHeader("OriginalTo", getCommaSeparatedString(rec));
					replyToList.addAll(Arrays.asList(rec));
				}
				if(replyToList.size()>0)
					m_msg.setReplyTo(replyToList.toArray(new InternetAddress[]{}));
				rec = getCcs();
				if (rec != null && rec.length > 0)
					m_msg.setHeader("OriginalCC", getCommaSeparatedString(rec));
				rec = getBccs();
				if (rec != null && rec.length > 0)
					m_msg.setHeader("OriginalBCC", getCommaSeparatedString(rec));
			}
			//
			m_msg.setSentDate(new java.util.Date());
			m_msg.setHeader("Comments", "iDempiereMail");
			if (m_acknowledgementReceipt)
				m_msg.setHeader("Disposition-Notification-To", m_from.getAddress());
			//	Bounce only header
			m_msg.setReturnOption (SMTPMessage.RETURN_HDRS);
			if (additionalHeaders.size() > 0) {
				for (ValueNamePair vnp : additionalHeaders) {
					m_msg.setHeader(vnp.getName(), vnp.getValue());
				}
			}
			//
			setContent();
			m_msg.saveChanges();
			t = session.getTransport("smtp");
			if (m_auth != null) {
				t.connect(m_smtpHost, m_smtpPort, m_auth.getPasswordAuthentication().getUserName(), m_auth.getPasswordAuthentication().getPassword());
			} else {
				t.connect();
			}
			ClassLoader tcl = Thread.currentThread().getContextClassLoader();
			try {
				Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader());
				t.sendMessage(m_msg, m_msg.getAllRecipients());
			} finally {
				Thread.currentThread().setContextClassLoader(tcl);
			}
			if (log.isLoggable(Level.FINE)) log.fine("Success - MessageID=" + m_msg.getMessageID());
		}
		catch (MessagingException me)
		{
			if (throwException)
				throw me;
			me.printStackTrace();
			Exception ex = me;
			StringBuilder sb = new StringBuilder("(ME)");
			boolean printed = false;
			do
			{
				if (ex instanceof SendFailedException)
				{
					SendFailedException sfex = (SendFailedException)ex;
					Address[] invalid = sfex.getInvalidAddresses();
					if (!printed)
					{
						if (invalid != null && invalid.length > 0)
						{
							sb.append (" - Invalid:");
							for (int i = 0; i < invalid.length; i++)
								sb.append (" ").append (invalid[i]);
						}
						Address[] validUnsent = sfex.getValidUnsentAddresses ();
						if (validUnsent != null && validUnsent.length > 0)
						{
							sb.append (" - ValidUnsent:");
							for (int i = 0; i < validUnsent.length; i++)
								sb.append (" ").append (validUnsent[i]);
						}
						Address[] validSent = sfex.getValidSentAddresses ();
						if (validSent != null && validSent.length > 0)
						{
							sb.append (" - ValidSent:");
							for (int i = 0; i < validSent.length; i++)
								sb.append (" ").append (validSent[i]);
						}
						printed = true;
					}
					if (sfex.getNextException() == null)
						sb.append(" ").append(sfex.getLocalizedMessage());
				}
				else if (ex instanceof AuthenticationFailedException)
				{
					sb.append(" - Invalid Username/Password - " + m_auth);
				}
				else	//	other MessagingException
				{
					String msg = ex.getLocalizedMessage();
					if (msg == null)
						sb.append(": ").append(ex.toString());
					else
					{
						if (msg.indexOf("Could not connect to SMTP host:") != -1)
						{
							int index = msg.indexOf('\n');
							if (index != -1)
								msg = msg.substring(0, index);
							String cc = "??";
							if (m_ctx != null)
								cc = m_ctx.getProperty(Env.AD_CLIENT_ID);
							msg += " - AD_Client_ID=" + cc;
						}
						String className = ex.getClass().getName();
						if (className.indexOf("MessagingException") != -1)
							sb.append(": ").append(msg);
						else
							sb.append(" ").append(className).append(": ").append(msg);
					}
				}
				//	Next Exception
				if (ex instanceof MessagingException)
					ex = ((MessagingException)ex).getNextException();
				else
					ex = null;
			}
			while (ex != null);	//	error loop
			//
			if (CLogMgt.isLevelFinest())
				log.log(Level.WARNING, sb.toString(), me);
			else
				log.log(Level.WARNING, sb.toString());
			m_sentMsg = sb.toString();
			Env.getCtx().put(EMAIL_SEND_MSG, m_sentMsg);
			return sb.toString();
		}
		catch (Exception e)
		{
			if (throwException)
				throw e;
			log.log(Level.SEVERE, "", e);
			m_sentMsg = e.getLocalizedMessage();
			Env.getCtx().put(EMAIL_SEND_MSG, m_sentMsg);
			return e.getLocalizedMessage();
		}
		finally
		{
			if(t != null) 
			{
				try {
					t.close();					
				} catch (Exception e) {
				}
				t = null;
			}
		}
		//
		if (CLogMgt.isLevelFinest())
			dumpMessage();
		m_sentMsg = SENT_OK;
		return m_sentMsg;
	}	//	send
	private String getCommaSeparatedString(InternetAddress[] recs) {
		StringBuilder retValue = new StringBuilder();
		for (InternetAddress rec : recs) {
			if (retValue.length() > 0)
				retValue.append(",");
			retValue.append(rec.getAddress());
		}
		return retValue.toString();
	}
	/**
	 * 	Get Send Result Msg
	 *	@return msg
	 */
	public String getSentMsg()
	{
		return m_sentMsg;
	}	//	getSentMsg
	/**
	 * 	Is send success
	 *	@return msg == SENT_OK
	 */
	public boolean isSentOK()
	{
		return m_sentMsg != null && SENT_OK.equals(m_sentMsg);
	}	//	isSentOK
	/**
	 * 	Dump Message Info
	 */
	private void dumpMessage()
	{
		if (m_msg == null || !log.isLoggable(Level.FINEST))
			return;
		try
		{
			Enumeration> e = m_msg.getAllHeaderLines ();
			while (e.hasMoreElements ())
				log.finest("- " + e.nextElement ());
		}
		catch (MessagingException ex)
		{
			log.log(Level.WARNING, m_msg.toString(), ex);
		}
	}	//	dumpMessage
	/**
	 * 	Get the message 
	 * 	@return mail message
	 */
	protected MimeMessage getMimeMessage()
	{
		return m_msg;
	}	//	getMessage
	/**
	 * 	Get Message ID or null
	 * 	@return Message ID e.g. <20030130004739.15377.qmail@web13506.mail.yahoo.com>
	 *  <25699763.1043887247538.JavaMail.jjanke@main>
	 */
	public String getMessageID()
	{
		try
		{
			if (m_msg != null)
				return m_msg.getMessageID ();
		}
		catch (MessagingException ex)
		{
			log.log(Level.SEVERE, "", ex);
		}
		return null;
	}	//	getMessageID
	/**
	 * 	Create Authenticator for User
	 * 	@param username user name
	 * 	@param password user password
	 * 	@return Authenticator or null
	 */
	public EMailAuthenticator createAuthenticator (String username, String password)
	{
		if (username == null || password == null)
		{
			log.fine("Ignored - " +  username + "/" + password);
			m_auth = null;
		}
		else
		{
			m_auth = new EMailAuthenticator (username, password);
		}
		return m_auth;
	}	//	createAuthenticator
	/**
	 *  Get Sender
	 *  @return Sender's internet address
	 */
	public InternetAddress getFrom()
	{
		return m_from;
	}   //  getFrom
	/**
	 *  Set Sender
	 *  @param newFrom Sender's email address
	 */
	public void setFrom(String newFrom)
	{
		if (newFrom == null)
		{
			m_valid = false;
			return;
		}
		try
		{
			m_from = createInternetAddress(newFrom);
			if (DB.isConnected() && MSysConfig.getBooleanValue(MSysConfig.MAIL_SEND_BCC_TO_FROM, false, Env.getAD_Client_ID(Env.getCtx())))
				addBcc(newFrom);
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, newFrom + ": " + e.toString());
			m_valid = false;
		}
	}   //  setFrom
	/**
	 *  Add To Recipient
	 *  @param newTo Recipient's email address
	 * 	@return true if valid
	 */
	public boolean addTo (String newTo)
	{
		if (newTo == null || newTo.length() == 0)
		{
			m_valid = false;
			return false;
		}
		InternetAddress ia = null;
		try
		{
			ia = createInternetAddress(newTo);
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, newTo + ": " + e.toString());
			m_valid = false;
			return false;
		}
		if (m_to == null)
			m_to = new ArrayList();
		m_to.add(ia);
		return true;
	}   //  addTo
	/**
	 *  Get Recipient
	 *  @return Recipient's internet address
	 */
	public InternetAddress getTo()
	{
		if (m_to == null || m_to.size() == 0)
			return null;
		InternetAddress ia = (InternetAddress)m_to.get(0);
		return ia;
	}   //  getTo
	/**
	 *  Get TO Recipients
	 *  @return Recipient's internet address
	 */
	public InternetAddress[] getTos()
	{
		if (m_to == null || m_to.size() == 0)
			return null;
		InternetAddress[] ias = new InternetAddress[m_to.size()];
		m_to.toArray(ias);
		return ias;
	}   //  getTos
	/**
	 * 	Add CC Recipient
	 * 	@param newCc EMail cc Recipient
	 * 	@return true if valid
	 */
	public boolean addCc (String newCc)
	{
		if (newCc == null || newCc.length() == 0)
			return false;
		InternetAddress ia = null;
		try
		{
			ia = createInternetAddress(newCc);
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, newCc + ": " + e.toString());
			return false;
		}
		if (m_cc == null)
			m_cc = new ArrayList();
		m_cc.add (ia);
		return true;
	}	//	addCc
	/**
	 *  Get CC Recipients
	 *  @return Recipient's internet address
	 */
	public InternetAddress[] getCcs()
	{
		if (m_cc == null || m_cc.size() == 0)
			return null;
		InternetAddress[] ias = new InternetAddress[m_cc.size()];
		m_cc.toArray(ias);
		return ias;
	}   //  getCcs
	/**
	 * 	Add BCC Recipient
	 * 	@param newBcc EMail cc Recipient
	 * 	@return true if valid
	 */
	public boolean addBcc (String newBcc)
	{
		if (newBcc == null || newBcc.length() == 0)
			return false;
		String[] addresses = newBcc.split(", *");
		for (String bccAddress : addresses) {
			InternetAddress ia = null;
			try
			{
				ia = createInternetAddress(bccAddress);
			}
			catch (Exception e)
			{
				log.log(Level.WARNING, bccAddress + ": " + e.getMessage());
				return false;
			}
			if (m_bcc == null)
				m_bcc = new ArrayList();
			m_bcc.add (ia);
		}
		return true;
	}	//	addBcc
	/**
	 *  Get BCC Recipients
	 *  @return Recipient's internet address
	 */
	public InternetAddress[] getBccs()
	{
		if (m_bcc == null || m_bcc.size() == 0)
			return null;
		InternetAddress[] ias = new InternetAddress[m_bcc.size()];
		m_bcc.toArray(ias);
		return ias;
	}   //  getBccs
	/**
	 *  Set Reply to Address
	 *  @param newTo email address
	 * 	@return true if valid
	 */
	public boolean setReplyTo (String newTo)
	{
		if (newTo == null || newTo.length() == 0)
			return false;
		InternetAddress ia = null;
		try
		{
			ia = createInternetAddress(newTo);
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, newTo + ": " + e.toString());
			return false;
		}
		m_replyTo = ia;
		return true;
	}   //  setReplyTo
	/**
	 *  Get Reply To
	 *  @return Reply To internet address
	 */
	public InternetAddress getReplyTo()
	{
		return m_replyTo;
	}   //  getReplyTo
	/**
	 *  Set Subject
	 *  @param newSubject Subject
	 */
	public void setSubject(String newSubject)
	{
		if (newSubject == null || newSubject.length() == 0)
			m_valid = false;
		else
			m_subject = newSubject;
	}   //  setSubject
	/**
	 *  Get Subject
	 *  @return subject
	 */
	public String getSubject()
	{
		return m_subject;
	}   //  getSubject
	/**
	 *  Set Message Body
	 *  @param newMessage message
	 */
	public void setMessageText (String newMessage)
	{
		if (newMessage == null || newMessage.length() == 0)
			m_valid = false;
		else
		{
			m_messageText = newMessage;
			if (!m_messageText.endsWith("\n"))
				m_messageText += "\n";
		}
	}   //  setMessage
	/**
	 *  Get MIME String Message - line ending with CRLF.
	 *  @return message
	 */
	public String getMessageCRLF()
	{
		if (m_messageText == null)
			return "";
		char[] chars = m_messageText.toCharArray();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < chars.length; i++)
		{
			char c = chars[i];
			if (c == '\n')
			{
				int previous = i-1;
				if (previous >= 0 && chars[previous] == '\r')
					sb.append(c);
				else
					sb.append("\r\n");
			}
			else
				sb.append(c);
		}
		return sb.toString();
	}   //  getMessageCRLF
	/**
	 *  Set HTML Message
	 *  @param html message
	 */
	public void setMessageHTML (String html)
	{
		if (html == null || html.length() == 0)
			m_valid = false;
		else
		{
			m_messageHTML = html;
			if (!m_messageHTML.endsWith("\n"))
				m_messageHTML += "\n";
		}
	}   //  setMessageHTML
	/**
	 *  Set HTML Message
	 *  @param subject subject repeated in message as H2
	 * 	@param message message
	 */
	public void setMessageHTML (String subject, String message)
	{
		m_subject = subject;
		StringBuilder sb = new StringBuilder("\n")
				.append("\n")
				.append("\n")
				.append(subject + "\n")
				.append("\n")
				.append("\n");
			sb.append("\n")
				.append(message)
				.append("\n")
				.append("\n");
		sb.append("\n");
		m_messageHTML = sb.toString();
	}   //  setMessageHTML
	/**
	 *  Get HTML Message
	 *  @return message
	 */
	public String getMessageHTML()
	{
		return m_messageHTML;
	}   //  getMessageHTML
	/**
	 *	Add file Attachment
	 * 	@param file file to attach
	 */
	public void addAttachment (File file)
	{
		if (file == null)
			return;
		if (m_attachments == null)
			m_attachments = new ArrayList();
		m_attachments.add(new FileDataSource(file));
	}	//	addAttachment
	/**
	 * Add a collection of attachments
	 * @param files collection of files
	 */
	public void addAttachments(Collection files)
	{
		if (files == null || files.size() == 0)
			return;
		for (File f : files) {
			addAttachment(f);
		}
	}
	/**
	 *	Add url based file Attachment
	 * 	@param url url content to attach
	 */
	public void addAttachment (URI url)
	{
		if (url == null)
			return;
		if (m_attachments == null)
			m_attachments = new ArrayList();
		try {
			m_attachments.add(new URLDataSource(url.toURL()));
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}	//	addAttachment
	/**
	 *	Add attachment.
	 *  (converted to ByteArrayDataSource)
	 * 	@param data data
	 * 	@param type MIME type
	 * 	@param name name of attachment
	 */
	public void addAttachment (byte[] data, String type, String name)
	{
		ByteArrayDataSource byteArray = new ByteArrayDataSource (data, type).setName(name);
		addAttachment (byteArray);
	}	//	addAttachment
	/**
	 *	Add arbitrary Attachment
	 * 	@param dataSource content to attach
	 */
	public void addAttachment (DataSource dataSource)
	{
		if (dataSource == null)
			return;
		if (m_attachments == null)
			m_attachments = new ArrayList();
		m_attachments.add(dataSource);
	}	//	addAttachment
	/**
	 *	Set the message content
	 * 	@throws MessagingException
	 * 	@throws IOException
	 */
	private void setContent ()
		throws MessagingException, IOException
	{
		//	Local Character Set
		String charSetName = Ini.getCharset().name();
		if (charSetName == null || charSetName.length() == 0)
			charSetName = "iso-8859-1";	// WebEnv.ENCODING - alternative iso-8859-1
		//
		m_msg.setSubject (getSubject(), charSetName);
		//	Simple Message
		if (m_attachments == null || m_attachments.size() == 0)
		{
			if (m_messageHTML == null || m_messageHTML.length () == 0)
				m_msg.setText (getMessageCRLF(), charSetName);
			else
				m_msg.setDataHandler (new DataHandler
					(new ByteArrayDataSource (m_messageHTML, charSetName, "text/html")));
			//
			if (log.isLoggable(Level.FINE)) log.fine("(simple) " + getSubject());
		}
		else	//	Multi part message	***************************************
		{
			//	First Part - Message
			MimeBodyPart htmlPart= null;
			MimeBodyPart planPart = new MimeBodyPart();
			//TODO: get plan text form html, i see getMessageCRLF just return empty string
			planPart.setText (getMessageCRLF(), charSetName);
			htmlPart = new MimeBodyPart();
			if (m_messageHTML == null){
				m_messageHTML = "" + getMessageCRLF() + "";
			}
			htmlPart.setDataHandler (new DataHandler
					(new ByteArrayDataSource (m_messageHTML, charSetName, "text/html")));
			
			// Create Multipart and its parts to it
			Multipart mainPart = new MimeMultipart("mixed");
			
			Multipart viewContentPart = new MimeMultipart("related");
			MimeBodyPart viewBodyPart = new MimeBodyPart();
			viewBodyPart.setContent(viewContentPart);
			mainPart.addBodyPart(viewBodyPart);
			
			Multipart textAlternativePart = new MimeMultipart("alternative");
			//textAlternativePart.addBodyPart(planPart);
			textAlternativePart.addBodyPart(htmlPart);
			MimeBodyPart alternativeBodyPart = new MimeBodyPart();
			alternativeBodyPart.setContent(textAlternativePart);
			viewContentPart.addBodyPart(alternativeBodyPart);
			
			if (log.isLoggable(Level.FINE)) log.fine("(multi) " + getSubject() + " - " + htmlPart);
			//	for all attachments
			for (int i = 0; i < m_attachments.size(); i++)
			{
				Object attachment = m_attachments.get(i);
				DataSource ds = null;
				if (attachment instanceof File)
				{
					File file = (File)attachment;
					if (file.exists())
						ds = new FileDataSource (file);
					else
					{
						log.log(Level.WARNING, "File does not exist: " + file);
						continue;
					}
				}
				else if (attachment instanceof URI)
				{
					URI url = (URI)attachment;
					ds = new URLDataSource (url.toURL());
				}
				else if (attachment instanceof DataSource)
					ds = (DataSource)attachment;
				else
				{
					log.log(Level.WARNING, "Attachement type unknown: " + attachment);
					continue;
				}
				//	Attachment Part
				MimeBodyPart mbp_2 = new MimeBodyPart();
				mbp_2.setDataHandler(new DataHandler(ds));
				mbp_2.setFileName(ds.getName());
				if (log.isLoggable(Level.FINE)) log.fine("Added Attachment " + ds.getName() + " - " + mbp_2);
				if (m_messageHTML != null && m_messageHTML.contains("cid:"+ds.getName())) {
					mbp_2.setContentID("<" + ds.getName() + ">");
					mbp_2.setDisposition(MimeBodyPart.INLINE);
					viewContentPart.addBodyPart(mbp_2);
				}else{
					mainPart.addBodyPart(mbp_2);
				}
			}
			//	Add to Message
			m_msg.setContent(mainPart);
		}	//	multi=part
	}	//	setContent
	/**
	 *  Set SMTP Host or address
	 *  @param newSmtpHost Mail server
	 */
	public void setSmtpHost(String newSmtpHost)
	{
		if (newSmtpHost == null || newSmtpHost.length() == 0)
			m_valid = false;
		else
			m_smtpHost = newSmtpHost;
	}   //  setSMTPHost
	/**
	 *  Get Mail Server name or address
	 *  @return mail server
	 */
	public String getSmtpHost()
	{
		return m_smtpHost;
	}   //  getSmtpHosr
	/**
	 *  Is Info valid to send EMail
	 *  @return true if email is valid and can be sent
	 */
	public boolean isValid()
	{
		return m_valid;
	}   //  isValid
	/**
	 *  Re-Check Info is valid to send EMail
	 * 	@param recheck if true check main variables
	 *  @return true if email is valid and can be sent
	 */
	public boolean isValid (boolean recheck)
	{
		if (!recheck)
			return m_valid;
		//  From
		if (m_from == null
			|| m_from.getAddress().length() == 0
			|| m_from.getAddress().indexOf(' ') != -1)
		{
			log.warning("From is invalid=" + m_from);
			return false;
		}
		//	To
		InternetAddress[] ias = getTos();
		if (ias == null)
		{
			log.warning("No To");
			return false;
		}
		for (int i = 0; i < ias.length; i++)
		{
			if (ias[i] == null
				|| ias[i].getAddress().length() == 0
				|| ias[i].getAddress().indexOf(' ') != -1)
			{
				log.warning("To(" + i + ") is invalid=" + ias[i]);
				return false;
			}
		}
		//	Host
		if (m_smtpHost == null || m_smtpHost.length() == 0)
		{
			log.warning("SMTP Host is invalid" + m_smtpHost);
			return false;
		}
		//	Subject
		if (m_subject == null || m_subject.length() == 0)
		{
			log.warning("Subject is invalid=" + m_subject);
			return false;
		}
		return true;
	}   //  isValid
	/**
	 * Get attachments
	 * @return attachments array or empty array. This method will never return null.
	 */
	public Object[] getAttachments()
	{
		if (m_attachments == null)
			return new Object[]{};
		return m_attachments.toArray();
	}
	/**
	 * 	String Representation
	 *	@return info
	 */
	@Override
	public String toString ()
	{
		StringBuilder sb = new StringBuilder ("EMail[");
		sb.append("From:").append(m_from)
			.append(",To:").append(getTo())
			.append(",Subject=").append(getSubject())
			.append ("]");
		return sb.toString ();
	}	//	toString
	/**
	 * 	Validate format of an email address
	 *  IDEMPIERE-1409
	 *	@return true if email has proper format
	 */
	public static boolean validate(final String email) {
		try
		{
			new InternetAddress (email, true);
		}
		catch (Exception e)
		{
			log.log(Level.WARNING, email + ": " + e.toString());
			return false;
		}
		return true;
	}
	/**
	 * Set additional headers
	 * @param name
	 * @param value
	 */
	public void setHeader(String name, String value) {
		additionalHeaders.add(new ValueNamePair(value, name));
	}
	/**
	 * Create an internet address with personal if the email address is formatted as "Personal "
	 * @param email
	 * @return internet address with personal if defined
	 * @throws Exception
	 */
	public static InternetAddress createInternetAddress(String email) throws Exception {
		InternetAddress ia = new InternetAddress (email, true);
		if (email.contains("<") && email.contains(">")) {
			int idx = email.lastIndexOf("<");
			String personal = email.substring(0, idx).trim();
			if (! personal.isEmpty())
				ia.setPersonal(personal);
		}
		return ia;
	}
	/**
	 * Set is force the user of tenant SMTP configuration
	 * @param forceTenantSmtp
	 */
	public void setForTenantSmtp(boolean forceTenantSmtp) {
		m_forceUseTenantSmtp = forceTenantSmtp;	
	}
}	//	EMail