/**
 * Copyright (c) 2007-2012 EBM WebSourcing, 2012-2024 Linagora
 *
 * This program/library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * This program/library 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.bc.mail;

import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import org.ow2.petals.bc.mail.exception.MissingElementException;
import org.ow2.petals.bc.mail.service.consume.ConsumeDescriptor;
import org.ow2.petals.bc.mail.service.provide.ProvideDescriptor;
import org.ow2.petals.component.framework.api.configuration.SuConfigurationParameters;
import org.ow2.petals.component.framework.api.message.Exchange;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;

import com.ebmwebsourcing.easycommons.lang.StringHelper;
import com.ebmwebsourcing.easycommons.xml.XMLHelper;

/**
 * @author Mathieu CARROLLE - EBM WebSourcing
 */
public class MailUtil {

    private MailUtil() {
        // Private constructor because it's an utility class
    }

    /**
     * Extract mail session properties from the service unit for Provide node
     *
     * @param extensions
     *            extensions associated to the given message exchange
     * @return {@link ProvideDescriptor}
     * @throws MessagingException
     * @throws MissingElementException
     * @throws AddressException
     */
    public static ProvideDescriptor build(final SuConfigurationParameters extensions)
            throws MessagingException, MissingElementException, AddressException {
        final ProvideDescriptor provideDescriptor = new ProvideDescriptor();

        // Retrieve user and password
        provideDescriptor.setUsername(extensions.get(MailConstants.USER_PATHELEMENT));
        provideDescriptor.setPassword(extensions.get(MailConstants.PASSWORD_PATHELEMENT));

        // TLS support
        final String tlsEnabledStr = extensions.get(MailConstants.STARTTLS_PATHELEMENT);
        if (tlsEnabledStr != null && !tlsEnabledStr.trim().isEmpty()) {
            provideDescriptor.setStartTlsEnabled(Boolean.parseBoolean(tlsEnabledStr));
        }

        // Retrieve protocol (smtp,..)
        provideDescriptor.setScheme(extensions.get(MailConstants.SCHEME_PATHELEMENT));

        // Retrieve host and port
        provideDescriptor.setHostname(extensions.get(MailConstants.HOST_PATHELEMENT));
        provideDescriptor.setPort(extensions.get(MailConstants.PORT_PATHELEMENT));
        final String host;
        if ((host = extensions.get(MailConstants.HELO_HOST_PATHELEMENT)) != null) {
            provideDescriptor.setHeloHost(host);
        }

        // Retrieve "from" addresses
        provideDescriptor.setFromAddress(extensions.get(MailConstants.FROM_PATHELEMENT));

        // Retrieve "replyTo" addresses
        provideDescriptor.setReplyAddress(extensions.get(MailConstants.REPLY_PATHELEMENT));

        provideDescriptor.setToAddress(InternetAddress.parse(extensions.get(MailConstants.TO_PATHELEMENT)));
        final String ccAddresses = extensions.get(MailConstants.CC_PATHELEMENT);
        if (ccAddresses != null) {
            provideDescriptor.setCcAddress(InternetAddress.parse(ccAddresses.trim()));
        }
        final String bccAddresses = extensions.get(MailConstants.BCC_PATHELEMENT);
        if (bccAddresses != null) {
            provideDescriptor.setBccAddress(InternetAddress.parse(bccAddresses.trim()));
        }

        // Retrieve "subject"
        provideDescriptor.setSubject( fixMailSubject( extensions.get(MailConstants.SUBJECT_PATHELEMENT)));

        // retrieve send mode
        provideDescriptor.setSendMode(extensions.get(MailConstants.SEND_MODE_PATHELEMENT));

        // retrieve content type
        provideDescriptor.setContentType(extensions.get(MailConstants.CONTENTTYPE_PATHELEMENT));
        // Check obligatory properties
        checkProperties(provideDescriptor);

        return provideDescriptor;
    }

    /**
     * Extract mail session properties from the service unit for Consume node
     *
     * @param extensions
     *            extensions associated to the given message exchange
     *
     * @return {@link ConsumeDescriptor}
     * @throws MessagingException
     * @throws MissingElementException
     */
    public static ConsumeDescriptor buildConsumeDescriptor(final SuConfigurationParameters extensions)
            throws MessagingException, MissingElementException {
        final ConsumeDescriptor sessionDescriptor = new ConsumeDescriptor();

        // Retrieve user and password
        sessionDescriptor.setUsername(extensions.get(MailConstants.USER_PATHELEMENT, null));
        sessionDescriptor.setPassword(extensions.get(MailConstants.PASSWORD_PATHELEMENT, null));

        // Retrieve protocol (smtp, imap, pop3)
        final String scheme = extensions.get(MailConstants.SCHEME_PATHELEMENT);
        sessionDescriptor.setScheme(scheme);

        // Retrieve host and port
        sessionDescriptor.setHostname(extensions.get(MailConstants.HOST_PATHELEMENT));

        final String portStr = extensions.get(MailConstants.PORT_PATHELEMENT);
        if (StringHelper.isNullOrEmpty(portStr)) {
            if (MailConstants.MAIL_SCHEME_POP3.equals(scheme)) {
                sessionDescriptor
                        .setPort(extensions.get(MailConstants.PORT_PATHELEMENT, MailConstants.MAIL_POP3_PORT_DEFAULT));
            } else if (MailConstants.MAIL_SCHEME_IMAP.equals(scheme)) {
                sessionDescriptor
                        .setPort(extensions.get(MailConstants.PORT_PATHELEMENT, MailConstants.MAIL_IMAP_PORT_DEFAULT));
            }
        } else {
            sessionDescriptor.setPort(portStr);
        }

        // Retrieve if STARTTLS is enabled
        final String startTlsEnabled = extensions.get(MailConstants.STARTTLS_PATHELEMENT);
        if (StringHelper.isNullOrEmpty(startTlsEnabled)) {
            sessionDescriptor.setStartTlsEnabled(false);
        } else {
            sessionDescriptor.setStartTlsEnabled(Boolean.parseBoolean(startTlsEnabled));
        }

        // Retrieve if SSL is enabled
        final String sslEnabled = extensions.get(MailConstants.SSL_ENABLED_PATHELEMENT);
        if (StringHelper.isNullOrEmpty(sslEnabled)) {
            sessionDescriptor.setSslEnabled(false);
        } else {
            sessionDescriptor.setSslEnabled(Boolean.parseBoolean(sslEnabled));
        }

        // Retrieve if SSL is enabled
        final String trustAllCertificatesEnabled = extensions.get(MailConstants.TRUST_ALL_CERTIFICATES_PATHELEMENT);
        if (StringHelper.isNullOrEmpty(trustAllCertificatesEnabled)) {
            sessionDescriptor.setTrustAllCertificatesEnabled(false);
        } else {
            sessionDescriptor.setTrustAllCertificatesEnabled(Boolean.parseBoolean(trustAllCertificatesEnabled));
        }

        // Retrieve period parameter ("checking for new mail" interval)
        sessionDescriptor.setPeriod(extensions.get(MailConstants.PERIOD_QUERYELEMENT,
                MailConstants.PERIOD_DEFAULT));

        // Retrieve folder parameter (the folder to search for new mails)
        sessionDescriptor.setFolder(extensions.get(MailConstants.FOLDER_QUERYELEMENT,
                MailConstants.FOLDER_DEFAULT));

        // Retrieve the Flag to set on read messages
        final String expunge = extensions.get(MailConstants.EXPUNGE_PATHELEMENT);
        if (StringHelper.isNullOrEmpty(expunge)) {
            sessionDescriptor.setDelete(true);
        } else {
            sessionDescriptor.setDelete(Boolean.getBoolean(expunge));
        }
        // Check obligatory properties
        checkProperties(sessionDescriptor);
        return sessionDescriptor;
    }

    /**
     * Build a descriptor by extracting all information from the associated JBI message payload.
     * <p>
     * <b>NOTE:</b><br>
     * Used when the component supplies its own service
     * </p>
     *
     * @param node
     *            the root node of the messsage payload
     * @throws MissingElementException
     * @throws MessagingException
     * @throws AddressException
     * @throws DOMException
     *
     */
    public static ProvideDescriptor buildFromPayload(final Node node) throws MissingElementException,
            MessagingException, AddressException, DOMException {
        final ProvideDescriptor sessionDescriptor = new ProvideDescriptor();

        // find the send/mail xml element
        if (node == null || !node.getLocalName().equalsIgnoreCase("mail")) {
            throw new MissingElementException("mail");
        }

        // Set the host address
        final Node hostNode = XMLHelper.findChild(node, null, MailConstants.HOST_PATHELEMENT, false);
        if (hostNode != null) {
            sessionDescriptor.setHostname(hostNode.getTextContent());
        }

        // set the scheme
        final Node schemeNode = XMLHelper.findChild(node, null, MailConstants.SCHEME_PATHELEMENT,
                false);
        if (schemeNode != null) {
            sessionDescriptor.setScheme(schemeNode.getTextContent());
        }
        sessionDescriptor.setScheme(MailConstants.MAIL_SCHEME_SMTP);

        // set smtp port
        final Node portNode = XMLHelper.findChild(node, null, MailConstants.PORT_PATHELEMENT, false);
        final String port;
        if (portNode != null && (port = portNode.getTextContent()) != null) {
            sessionDescriptor.setPort(port);
        } else {
            sessionDescriptor.setPort(null);
        }

        // set smtp user name
        final Node userNode = XMLHelper.findChild(node, null, MailConstants.USER_PATHELEMENT, false);
        if (userNode != null) {
            sessionDescriptor.setUsername(userNode.getTextContent());
        }

        // set smtp password
        final Node pwdNode = XMLHelper.findChild(node, null, MailConstants.PASSWORD_PATHELEMENT,
                false);
        if (pwdNode != null) {
            sessionDescriptor.setPassword(pwdNode.getTextContent());
        }

        // TLS support
        final Node tlsEnabledNode = XMLHelper.findChild(node, null, MailConstants.STARTTLS_PATHELEMENT, false);
        if (tlsEnabledNode != null) {
            final String tlsEnabledStr = tlsEnabledNode.getTextContent();
            if (tlsEnabledStr != null && !tlsEnabledStr.trim().isEmpty()) {
                sessionDescriptor.setStartTlsEnabled(Boolean.parseBoolean(tlsEnabledStr));
            }
        }

        // set smtp SEND MODE
        final Node sendModeNode = XMLHelper.findChild(node, null, MailConstants.SEND_MODE_PATHELEMENT, false);
        final String sendMode;
        if (sendModeNode != null && (sendMode = sendModeNode.getTextContent()) != null) {
            sessionDescriptor.setSendMode(sendMode);
        } else {
            sessionDescriptor.setSendMode(null);
        }


        // Set the from address
        final Node fromNode = XMLHelper.findChild(node, null, MailConstants.FROM_PATHELEMENT, false);
        if (fromNode != null) {
            sessionDescriptor.setFromAddress(fromNode.getTextContent());
        }

        // Set the reply address
        final Node replyNode = XMLHelper
                .findChild(node, null, MailConstants.REPLY_PATHELEMENT, false);
        if (replyNode != null) {
            sessionDescriptor.setReplyAddress(replyNode.getTextContent());
        }

        // Build the recipient address "To"
        final Node toNode = XMLHelper.findChild(node, null, MailConstants.TO_PATHELEMENT, false);
        if (toNode != null) {
            sessionDescriptor.setToAddress(InternetAddress.parse(toNode.getTextContent()));
        }

        // Build the recipient address "Cc"
        final Node ccNode = XMLHelper.findChild(node, null, MailConstants.CC_PATHELEMENT, false);
        if (ccNode != null) {
            sessionDescriptor.setCcAddress(InternetAddress.parse(ccNode.getTextContent()));
        }

        // Build the recipient address "Bcc"
        final Node bccNode = XMLHelper.findChild(node, null, MailConstants.BCC_PATHELEMENT, false);
        if (bccNode != null) {
            sessionDescriptor.setBccAddress(InternetAddress.parse(bccNode.getTextContent()));
        }

        // set the content type
        final Node contentTypeNode = XMLHelper.findChild(node, null, MailConstants.CONTENTTYPE_PATHELEMENT, false);
        final String contentType;
        if (contentTypeNode != null && (contentType = contentTypeNode.getTextContent()) != null) {
            sessionDescriptor.setContentType(contentType);
        } else {
            sessionDescriptor.setContentType(null);
        }

        // Set the subject
        final Node subjectNode = XMLHelper.findChild(node, null, MailConstants.SUBJECT_PATHELEMENT,
                false);
        if (subjectNode != null) {
            sessionDescriptor.setSubject( fixMailSubject( subjectNode.getTextContent()));
        }

        final Node heloHostNode = XMLHelper.findChild(node, null, MailConstants.HELO_HOST_PATHELEMENT,
                false);
        if (heloHostNode != null) {
            sessionDescriptor.setHeloHost(heloHostNode.getTextContent());
        }
        checkProperties(sessionDescriptor);
        return sessionDescriptor;
    }

    public static String extractMailBodyFromPayload(final Node node) throws MissingElementException,
            MessagingException {
        // Set the body
        final Node bodyNode = XMLHelper.findChild(node, null, MailConstants.BODY_PATHELEMENT, false);
        if (bodyNode == null) {
            throw new MissingElementException(MailConstants.BODY_PATHELEMENT);
        } else {
            return XMLHelper.toString(bodyNode.getChildNodes());
        }
    }

    /**
     * Retrieve mail session properties from Normalized message properties.
     * <p>
     * <b>NOTE:</b><br>
     * Used when the component supplies its own service
     * </p>
     *
     * @param exchange
     *            the message exchange used to extract properties
     * @param node
     *            the root node of the message payload to extract mail's body
     * @return {@link ProvideDescriptor}
     * @throws MissingElementException
     * @throws MessagingException
     * @throws AddressException
     */
    public static ProvideDescriptor buildFromProperties(final Exchange exchange, final Node node)
            throws MissingElementException, MessagingException, AddressException {
        final ProvideDescriptor sessionDescriptor = new ProvideDescriptor();
        final NormalizedMessage inNormalizedMessage = exchange.getInMessage();
        if (node == null || !node.getLocalName().equalsIgnoreCase("mail")) {
            throw new MissingElementException("mail");
        }
        // retrieve smtp host
        final String host = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.HOST_QNAME.toString());
        sessionDescriptor.setHostname(host);

        // retrieve smtp port
        String port = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.PORT_QNAME.toString());
        sessionDescriptor.setPort(port);

        // retrieve smtp username
        final String username = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.USER_QNAME.toString());
        sessionDescriptor.setUsername(username);

        // retrieve smtp password
        final String password = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.PASSWORD_QNAME.toString());
        sessionDescriptor.setPassword(password);

        // TLS support
        final String tlsEnabledStr = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.STARTTLS_QNAME.toString());
        if (tlsEnabledStr != null && !tlsEnabledStr.trim().isEmpty()) {
            sessionDescriptor.setStartTlsEnabled(Boolean.parseBoolean(tlsEnabledStr));
        }

        // retrieve smtp send mode
        final String sendMode = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.SENDMODE_QNAME.toString());
        sessionDescriptor.setSendMode(sendMode);

        // set scheme mode
        final String scheme = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.SCHEME_QNAME.toString());
        sessionDescriptor.setScheme(scheme);

        // set smtp helo
        final String heloHost = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.HELOHOST_QNAME.toString());
        sessionDescriptor.setHeloHost(heloHost);

        // Retrieve "from" address
        final String fromAddress = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.FROM_QNAME.toString());
        sessionDescriptor.setFromAddress(fromAddress);
        
        // Retrieve "to" Address
        final String toAddress = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.TO_QNAME.toString());
        if (toAddress != null) {
            sessionDescriptor.setToAddress(InternetAddress.parse(toAddress));
        }

        // Retrieve "cc" Address
        final String ccAddress = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.CC_QNAME.toString());
        if (ccAddress != null) {
            sessionDescriptor.setCcAddress(InternetAddress.parse(ccAddress));
        }

        // Retrieve "bcc" Address
        final String bccAddress = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.BCC_QNAME.toString());
        if (bccAddress != null) {
            sessionDescriptor.setBccAddress(InternetAddress.parse(bccAddress));
        }

        // Retrieve "replyTo" address
        final String replyAddress = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.REPLY_TO_QNAME.toString());
        sessionDescriptor.setReplyAddress(replyAddress);

        // retrieve content-type
        final String contentType = (String) inNormalizedMessage
                .getProperty(MailConstants.MailWSAddressing.CONTENTTYPE_QNAME.toString());
        sessionDescriptor.setContentType(contentType);

        // Retrieve "subject"
        String subject = (String) inNormalizedMessage.getProperty(MailConstants.MailWSAddressing.ACTION_QNAME.toString());
        subject = fixMailSubject( subject );
        sessionDescriptor.setSubject(subject);

        // Check mandatory properties
        checkProperties(sessionDescriptor);
        return sessionDescriptor;
    }

    /**
     * Check if the ws addressing properties are present
     *
     * @param exchange
     * @return
     */
    public static boolean checkWsaProperties(final Exchange exchange) {
        // Retrieve "from" address
        final String fromAddress = (String) exchange.getInMessage().getProperty(
                MailConstants.MailWSAddressing.FROM_QNAME.toString());
        // Retrieve "to" Address
        final String toAddress = (String) exchange.getInMessage().getProperty(
                MailConstants.MailWSAddressing.TO_QNAME.toString());
        if (!StringHelper.isNullOrEmpty(fromAddress) || !StringHelper.isNullOrEmpty(toAddress)) {
            return true;
        }
        return false;
    }

    /**
     * Check the properties extracted from the address URI. Only hostname is
     * obligatory for all schemes. Only SMTP, POP3 and IMAP schemes are
     * supported. "toAddress" is need for SMTP scheme.
     *
     * @param provideDescriptor
     * @throws MessagingException
     * @throws MissingElementException
     */
    public static void checkProperties(final ProvideDescriptor provideDescriptor)
            throws MessagingException, MissingElementException {
        final String msg;
        if (StringHelper.isNullOrEmpty(provideDescriptor.getHostname())) {
            throw new MissingElementException(MailConstants.HOST_PATHELEMENT);
        }
        try {
            new InternetAddress(provideDescriptor.getFromAddress()).validate();
        } catch (final Exception e) {
            throw new MissingElementException(MailConstants.FROM_PATHELEMENT);
        }
        try {
            for (final InternetAddress mailAddress : provideDescriptor.getToAddress()) {
                mailAddress.validate();
            }
        } catch (final Exception e) {
            throw new MissingElementException(MailConstants.TO_PATHELEMENT);
        }
        if (!MailConstants.MAIL_SCHEME_SMTP.equalsIgnoreCase(provideDescriptor.getScheme())) {
            msg = "The specified scheme isn't supported : " + provideDescriptor.getScheme();
            throw new MessagingException(msg);
        }
    }

    /**
     * Check the properties extracted from the address URI. Only hostname is
     * obligatory for all schemes. Only SMTP, POP3 and IMAP schemes are
     * supported. "toAddress" is need for SMTP scheme.
     *
     * @param consumeDescriptor
     * @throws MissingElementException
     * @throws MessagingException
     *             if a checking step failed
     */
    protected static void checkProperties(final ConsumeDescriptor consumeDescriptor)
            throws MessagingException, MissingElementException {
        if (StringHelper.isNullOrEmpty(consumeDescriptor.getHostname())) {
            throw new MissingElementException(MailConstants.HOST_PATHELEMENT);
        }
        if (!(MailConstants.MAIL_SCHEME_IMAP.equalsIgnoreCase(consumeDescriptor.getScheme()) || MailConstants.MAIL_SCHEME_POP3
                .equalsIgnoreCase(consumeDescriptor.getScheme()))) {
            throw new MessagingException("The specified scheme isn't supported : "
                    + consumeDescriptor.getScheme());
        }
    }

    /**
     * Makes sure a mail subject does not contain line breaks.
     * @param string a string (can be null)
     * @return the fixed string (null if <code>string</code> was null)
     */
    public static String fixMailSubject(final String string) {
        String result = string;
        if( result != null )
            result = result.replace( "\r\n", "" ).replace( "\n", "" );

        return result;
    }
}
