/**
 * Copyright (c) 2006-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 java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

import org.ow2.petals.bc.mail.service.consume.ConsumeDescriptor;
import org.ow2.petals.bc.mail.service.provide.ProvideDescriptor;
import org.ow2.petals.commons.log.FlowAttributes;
import org.ow2.petals.commons.log.Level;
import org.ow2.petals.commons.log.PetalsExecutionContext;
import org.ow2.petals.component.framework.logger.StepLogHelper;

/**
 * This manager can be used to manage session with mail servers.
 * 
 * @author Olivier Fabre - EBM WebSourcing
 */
public class MailSessionManager {

    private final Logger log;

    /**
     *
     * @param log
     *            the component logger
     */
    public MailSessionManager(final Logger log) {
        this.log = log;
    }

    /**
     * Close previously opened folder and store. folder is expunged if needed
     *
     * @param folder
     *            the folder to close
     * @param store
     *            the store to close
     * @param expunge
     */
    public void closeFolderAndStore(final Folder folder, final Store store, final boolean expunge) {
        try {
            if ((folder != null) && (folder.isOpen())) {
                // Delete message from the mail folder, the purge is not
                // supported
                // by POP3 protocol.
                // With POP3, the marked delete messages are expunged at the
                // disconnection.
                folder.close(expunge);
            }
        } catch (Exception ex) {
            if (folder != null) {
                this.log.log(Level.WARNING, "Error closing mail Folder :" + folder.toString(), ex);
            }
        }
        try {

            if ((store != null) && (store.isConnected())) {
                store.close();
            }
        } catch (Exception ex) {
            if (store != null) {
                this.log.log(Level.WARNING, "Error closing mail Store : " + store.toString(), ex);
            }
        }
    }

    /**
     * Create a {@link Properties} object with given properties. Then create a new {@link Session} initialized with the
     * given properties
     *
     * @param provideDescriptor
     *            a {@link ProvideDescriptor} build from an address URI
     * @return the Mail Server {@link Session}
     */
    public Session createSessionPropertiesFromDescriptor(final ProvideDescriptor provideDescriptor) {
        Properties props = new Properties();

        if (MailConstants.MAIL_SCHEME_SMTP.equals(provideDescriptor.getScheme())) {
            props.put(MailConstants.MAIL_TRANSPORT_PROTOCOL_KEY, provideDescriptor.getScheme());
        } else {
            props.put(MailConstants.MAIL_STORE_PROTOCOL_KEY, provideDescriptor.getScheme());
        }
        props.put(MailConstants.MAIL_SMTP_HOST_KEY, provideDescriptor.getHostname());
        props.put(MailConstants.MAIL_SMTP_PORT_KEY, provideDescriptor.getPort());
        if (provideDescriptor.getFromAddress() != null) {
            props.put(MailConstants.MAIL_FROM_KEY, provideDescriptor.getFromAddress());
        }
        if (provideDescriptor.getHeloHost() != null) {
            props.put(MailConstants.MAIL_HELO_HOST, provideDescriptor.getHeloHost());
        }
        if (provideDescriptor.getHeloHost() != null) {
            props.put(MailConstants.MAIL_HELO_HOST, provideDescriptor.getHeloHost());
        }

        if (provideDescriptor.getUsername() != null) {
            props.put(MailConstants.MAIL_AUTH, Boolean.TRUE.toString());
            final Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(provideDescriptor.getUsername(), provideDescriptor.getPassword());
                }
            };

            if (provideDescriptor.isStartTlsEnabled()) {
                props.put(MailConstants.MAIL_SMTP_STARTTLS_ENABLE, Boolean.TRUE.toString());
            }

            return Session.getInstance(props, authenticator);
        } else {
            return Session.getInstance(props);
        }
    }

    /**
     * Create a {@link Properties} object with given properties. Then create a new {@link Session} initialized with the
     * given properties
     *
     * @param consumeDescriptor
     *            a {@link ConsumeDescriptor} build from an address URI
     * @return the Mail Server {@link Session}
     */
    public Session createSessionPropertiesFromDescriptor(final ConsumeDescriptor consumeDescriptor) {
        final Properties props = new Properties();

        props.put(MailConstants.MAIL_STORE_PROTOCOL_KEY, consumeDescriptor.getScheme());
        props.put(MailConstants.MAIL_HOST_KEY, consumeDescriptor.getHostname());
        if (consumeDescriptor.getUsername() != null) {
            props.put(MailConstants.MAIL_USER_KEY, consumeDescriptor.getUsername());
        }
        if (consumeDescriptor.getScheme().equals("imap")) {
            if (consumeDescriptor.isStartTlsEnabled()) {
                props.put(MailConstants.MAIL_IMAP_STARTTLS_ENABLE, "true");
            }
            if (consumeDescriptor.isSslEnabled()) {
                props.put(MailConstants.MAIL_IMAP_SSL_ENABLE, "true");

                if (consumeDescriptor.isTrustAllCertificatesEnabled()) {
                    props.put(MailConstants.MAIL_IMAP_TRUST_ALL_CERTIFICATES_ENABLE, "*");
                }
            }
        }

        return Session.getInstance(props);
    }

    /**
     * Retrieve a specified {@link Folder} from the given {@link Store} and open it.
     *
     * @param store
     *            the mail {@link Store}
     * @param consumeDescriptor
     *            the mail {@link ConsumeDescriptor}
     * @return the opened {@link Folder}
     * @throws MessagingException
     */
    public Folder getFolderAndOpen(Store store, ConsumeDescriptor consumeDescriptor) throws MessagingException {
        String msg = "";
        // Search in the specified folder of this Store
        Folder folder = null;
        folder = store.getDefaultFolder();
        if (folder == null) {
            msg = "No default folder for this store : " + store.toString();
            throw new MessagingException(msg);
        }
        folder = folder.getFolder(consumeDescriptor.getFolder());
        if (folder == null) {
            msg = "Invalid folder : " + consumeDescriptor.getFolder();
            throw new MessagingException(msg);
        }
        folder.open(Folder.READ_WRITE);
        return folder;
    }

    /**
     * Retrieve new mails into the given folder. Just unread mail are considered as new mail.
     *
     * @param folder
     *            the folder used to search for new mail
     * @return the new mail Array
     * @throws MessagingException
     */
    public List<Message> getNewMails(final Folder folder) throws MessagingException {
        final List<Message> messages = new ArrayList<Message>();
        final Message[] lstMessage = folder.getMessages();
        // Check for new mail to process(Flag not supported with pop3 protocol)
        for (Message message : lstMessage) {
            if (!message.isSet(Flag.DELETED)) {
                messages.add(message);
            }
        }
        return messages;
    }

    /**
     * Retrieve a {@link Store} from the given Session. Then connect to it.
     *
     * @param session
     *            the mail {@link Session}
     * @param consumeDescriptor
     *            the {@link ConsumeDescriptor} associated to this session
     * @return the mail {@link Store}
     * @throws MessagingException
     *             if error occurs during mail {@link Store} retrieval
     */
    public Store getStoreAndConnect(final Session session, final ConsumeDescriptor consumeDescriptor)
            throws MessagingException {
        Store store = null;
        // Get a Store object
        store = session.getStore(consumeDescriptor.getScheme());
        final String hostname = consumeDescriptor.getHostname();
        final int port = Integer.parseInt(consumeDescriptor.getPort());
        final String username = consumeDescriptor.getUsername();
        try {
            store.connect(hostname, port, username, consumeDescriptor.getPassword());
        } catch (final MessagingException e) {
            this.log.log(Level.WARNING, String.format("Error connecting to %s:%d with %s", hostname, port, username),
                    e);
            throw e;
        }

        return store;
    }

    /**
     * Send an email based on the given MimeMessage
     *
     * @param mimeMessage
     *            the {@link MimeMessage} to send. The resulting email will conform to the RFC822 and MIME standards
     * @param sessionDescriptor
     *            a {@link ProvideDescriptor} build from an address URI
     * @param session
     *            the mail {@link Session}
     * @throws MessagingException
     *             if error occurs during message sending
     *
     * @see Transport#send(javax.mail.Message)
     */
    public void sendMail(MimeMessage mimeMessage, ProvideDescriptor sessionDescriptor, Session session)
            throws MessagingException {

        final FlowAttributes providerStep = PetalsExecutionContext.getFlowAttributes();
        final FlowAttributes provideExtStep = PetalsExecutionContext.nextFlowStepId();

        final String emailAddresses;
        final Address[] toAddresses = mimeMessage.getRecipients(RecipientType.TO);
        if (toAddresses != null && toAddresses.length > 0) {
            final StringBuilder builder = new StringBuilder('(');
            for (int i = 0; i < toAddresses.length; i++) {
                builder.append(toAddresses[i].toString());
                if (i != toAddresses.length - 1) {
                    builder.append(", ");
                }
            }
            emailAddresses = builder.toString();
        } else {
            emailAddresses = "";
        }

        this.log.log(Level.MONIT, "",
                new MailProvideExtFlowStepBeginLogData(provideExtStep, providerStep, emailAddresses));
        try {
            Transport.send(mimeMessage, mimeMessage.getAllRecipients());
            StepLogHelper.addMonitExtEndTrace(this.log, provideExtStep, false);
        } catch (final Exception e) {
            StepLogHelper.addMonitExtFailureTrace(this.log, provideExtStep, e, false);
            throw e;
        }
    }
}
