/*
 *
 *  JBoss, Home of Professional Open Source.
 *  Copyright 2013, Red Hat, Inc., and individual contributors
 *  as indicated by the @author tags. See the copyright.txt file in the
 *  distribution for a full listing of individual contributors.
 *
 *  This 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 software 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 software; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 *  02110-1301 USA, or see the FSF site: http://www.fsf.org.
 * /
 */

package org.jboss.as.mail.extension;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;

import org.jboss.as.network.NetworkUtils;
import org.jboss.as.network.OutboundSocketBinding;
import org.jboss.metadata.javaee.spec.MailSessionMetaData;
import org.jboss.metadata.javaee.spec.PropertyMetaData;
import org.jboss.msc.service.StartException;

/**
 * @author <a href="mailto:tomaz.cerar@redhat.com">Tomaz Cerar</a> (c) 2013 Red Hat Inc.
 */
class SessionProviderFactory {
    static SessionProvider create(MailSessionConfig config, final Map<String, OutboundSocketBinding> socketBindings) throws StartException {
        return new ManagedSession(config, socketBindings);
    }

    static SessionProvider create(MailSessionMetaData mailSessionMetaData) {
        return new DirectSessionProvider(mailSessionMetaData);
    }

    private static String getHostKey(final String protocol) {
        return new StringBuilder("mail.").append(protocol).append(".host").toString();
    }

    private static String getPortKey(final String protocol) {
        return new StringBuilder("mail.").append(protocol).append(".port").toString();
    }

    private static String getPropKey(final String protocol, final String name) {
        return new StringBuilder("mail.").append(protocol).append(".").append(name).toString();
    }

    private static class ManagedSession implements SessionProvider {
        private final Map<String, OutboundSocketBinding> socketBindings;
        private final MailSessionConfig sessionConfig;
        private final Properties properties = new Properties();

        private ManagedSession(MailSessionConfig sessionConfig, Map<String, OutboundSocketBinding> socketBindings) throws StartException {
            this.socketBindings = socketBindings;
            this.sessionConfig = sessionConfig;
            configure();
        }

        /**
         * Configures mail session properties
         *
         * @throws org.jboss.msc.service.StartException
         *          if socket binding could not be found
         * @see {http://javamail.kenai.com/nonav/javadocs/com/sun/mail/smtp/package-summary.html}
         * @see {http://javamail.kenai.com/nonav/javadocs/com/sun/mail/pop3/package-summary.html}
         * @see {http://javamail.kenai.com/nonav/javadocs/com/sun/mail/imap/package-summary.html}
         */
        private void configure() throws StartException {
            if (sessionConfig.getSmtpServer() != null) {
                properties.setProperty("mail.transport.protocol", "smtp");
                setServerProps(properties, sessionConfig.getSmtpServer(), "smtp");
            }
            if (sessionConfig.getImapServer() != null) {
                properties.setProperty("mail.store.protocol", "imap");
                setServerProps(properties, sessionConfig.getImapServer(), "imap");
            }
            if (sessionConfig.getPop3Server() != null) {
                properties.setProperty("mail.store.protocol", "pop3");
                setServerProps(properties, sessionConfig.getPop3Server(), "pop3");
            }
            if (sessionConfig.getCustomServers() != null) {
                configureCustomServers(properties, sessionConfig.getCustomServers());
            }
            if (sessionConfig.getFrom() != null) {
                properties.setProperty("mail.from", sessionConfig.getFrom());
            }
            properties.setProperty("mail.debug", String.valueOf(sessionConfig.isDebug()));
            MailLogger.ROOT_LOGGER.tracef("props: %s", properties);
        }

        private void configureCustomServers(final Properties props, final CustomServerConfig... serverConfigs) throws StartException {
            for (CustomServerConfig serverConfig : serverConfigs) {
                setServerProps(props, serverConfig, serverConfig.getProtocol());
            }
        }

        private void setServerProps(final Properties props, final ServerConfig server, final String protocol) throws StartException {
            if (server.isSslEnabled()) {
                props.setProperty(getPropKey(protocol, "ssl.enable"), "true");
            } else if (server.isTlsEnabled()) {
                props.setProperty(getPropKey(protocol, "starttls.enable"), "true");
            }
            if (server.getCredentials() != null) {
                props.setProperty(getPropKey(protocol, "auth"), "true");
                props.setProperty(getPropKey(protocol, "user"), server.getCredentials().getUsername());
            }
            props.setProperty(getPropKey(protocol, "debug"), String.valueOf(sessionConfig.isDebug()));


            Map<String, String> customProps = server.getProperties();
            if (server.getOutgoingSocketBinding() != null) {
                InetSocketAddress socketAddress = getServerSocketAddress(server);
                if (socketAddress.getAddress() == null) {
                    MailLogger.ROOT_LOGGER.hostUnknown(socketAddress.getHostName());
                    props.setProperty(getHostKey(protocol), NetworkUtils.canonize(socketAddress.getHostName()));
                } else {
                    props.setProperty(getHostKey(protocol), NetworkUtils.canonize(socketAddress.getAddress().getHostName()));
                }
                props.setProperty(getPortKey(protocol), String.valueOf(socketAddress.getPort()));
            } else {
                String host = customProps.get("host");
                if (host != null && !"".equals(host.trim())) {
                    props.setProperty(getHostKey(protocol), host);
                }
                String port = customProps.get("port");
                if (port != null && !"".equals(port.trim())) {
                    props.setProperty(getPortKey(protocol), port);
                }
            }
            if (customProps != null && !customProps.isEmpty()) {
                for (Map.Entry<String, String> prop : customProps.entrySet()) {
                    if (!props.contains(prop.getKey())) {
                        if (prop.getKey().contains(".")){
                            props.put(prop.getKey(), prop.getValue());
                        }else{
                            props.put(getPropKey(protocol, prop.getKey()), prop.getValue());
                        }
                    }
                }
            }
        }

        private InetSocketAddress getServerSocketAddress(ServerConfig server) throws StartException {
            final String ref = server.getOutgoingSocketBinding();
            final OutboundSocketBinding binding = socketBindings.get(ref);
            if (binding == null) {
                throw MailLogger.ROOT_LOGGER.outboundSocketBindingNotAvailable(ref);
            }
            return new InetSocketAddress(binding.getUnresolvedDestinationAddress(), binding.getDestinationPort());
        }

        @Override
        public Session getSession() {
            final Session session;
            final ClassLoader current = Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(Session.class.getClassLoader());
                session = Session.getInstance(properties, new ManagedPasswordAuthenticator(sessionConfig));
            } finally {
                Thread.currentThread().setContextClassLoader(current);
            }
            return session;
        }
    }

    protected static class ManagedPasswordAuthenticator extends Authenticator {
        private MailSessionConfig config;

        public ManagedPasswordAuthenticator(MailSessionConfig sessionConfig) {
            this.config = sessionConfig;
        }

        @Override
        protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
            String protocol = getRequestingProtocol();
            Credentials c = null;
            if (MailSubsystemModel.SMTP.equals(protocol) && config.getSmtpServer() != null) {
                c = config.getSmtpServer().getCredentials();
            } else if (MailSubsystemModel.POP3.equals(protocol) && config.getPop3Server() != null) {
                c = config.getPop3Server().getCredentials();
            } else if (MailSubsystemModel.IMAP.equals(protocol) && config.getImapServer() != null) {
                c = config.getImapServer().getCredentials();
            }
            if (c == null) {
                for (CustomServerConfig ssc : config.getCustomServers()) {
                    if (ssc.getProtocol().equals(protocol)) {
                        c = ssc.getCredentials();
                        break;
                    }
                }
            }

            if (c != null && c.getPassword() != null) {
                return new javax.mail.PasswordAuthentication(c.getUsername(), c.getPassword());
            }
            return null;
        }
    }

    private static class DirectSessionProvider implements SessionProvider {

        private final MailSessionMetaData metaData;
        private final Properties properties = new Properties();
        private PasswordAuthentication authenticator = null;

        private DirectSessionProvider(MailSessionMetaData metaData) {
            this.metaData = metaData;
            configure();
        }

        private void configure() {
            String protocol = metaData.getTransportProtocol();
            if (protocol == null) {
                protocol = metaData.getStoreProtocol();
            }
            if (protocol == null) {
                protocol = "smtp";
            }
            properties.put(getHostKey(protocol), metaData.getHost());
            if (metaData.getFrom() != null) {
                properties.put(getPropKey(protocol, "from"), metaData.getFrom());
            }
            if (metaData.getProperties() != null) {
                for (PropertyMetaData prop : metaData.getProperties()) {
                    properties.put(prop.getKey(), prop.getValue());
                }
            }

            if (metaData.getUser() != null) {
                authenticator = new PasswordAuthentication(metaData.getUser(), metaData.getPassword());
            }
        }

        @Override
        public Session getSession() {
            return Session.getInstance(properties, new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return authenticator;
                }
            });
        }
    }
}
