package org.nlab.smtp.transport.factory;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.nlab.smtp.transport.connection.ClosableSmtpConnection;
import org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection;
import org.nlab.smtp.transport.strategy.ConnectionStrategy;
import org.nlab.smtp.transport.strategy.TransportStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.event.TransportListener;

/**
 * A part of the code of this class is taken from the Spring <a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/mail/javamail/JavaMailSenderImpl.html">JavaMailSenderImpl class</a>.
 */
public class SmtpConnectionFactory implements PooledObjectFactory<ClosableSmtpConnection> {

  private static final Logger LOG = LoggerFactory.getLogger(SmtpConnectionFactory.class);

  protected final Session session;

  protected final TransportStrategy transportFactory;
  protected final ConnectionStrategy connectionStrategy;

  protected final boolean invalidateConnectionOnException;

  protected List<TransportListener> defaultTransportListeners;

  public SmtpConnectionFactory(Session session, TransportStrategy transportStrategy, ConnectionStrategy connectionStrategy, boolean invalidateConnectionOnException, Collection<TransportListener> defaultTransportListeners) {
    this.session = session;
    this.transportFactory = transportStrategy;
    this.connectionStrategy = connectionStrategy;
    this.invalidateConnectionOnException = invalidateConnectionOnException;
    this.defaultTransportListeners = new ArrayList<>(defaultTransportListeners);
  }

  public SmtpConnectionFactory(Session session, TransportStrategy transportFactory, ConnectionStrategy connectionStrategy, boolean invalidateConnectionOnException) {
    this(session, transportFactory, connectionStrategy, invalidateConnectionOnException, Collections.<TransportListener>emptyList());
  }


  @Override
  public PooledObject<ClosableSmtpConnection> makeObject() throws Exception {
    LOG.debug("makeObject");

    Transport transport = transportFactory.getTransport(session);
    connectionStrategy.connect(transport);

    DefaultClosableSmtpConnection closableSmtpTransport = new DefaultClosableSmtpConnection(transport, invalidateConnectionOnException);
    initDefaultListeners(closableSmtpTransport);

    return new DefaultPooledObject(closableSmtpTransport);
  }

  @Override
  public void destroyObject(PooledObject<ClosableSmtpConnection> pooledObject) {
    try {
      if (LOG.isDebugEnabled()) {
        LOG.debug("destroyObject [{}]", pooledObject.getObject().isConnected());
      }
      pooledObject.getObject().clearListeners();
      pooledObject.getObject().getDelegate().close();
    } catch (Exception e) {
      LOG.warn(e.getMessage(), e);
    }
  }

  @Override
  public boolean validateObject(PooledObject<ClosableSmtpConnection> pooledObject) {
    boolean connected = pooledObject.getObject().isConnected();
    LOG.debug("Is connected [{}]", connected);
    return connected;
  }


  @Override
  public void activateObject(PooledObject<ClosableSmtpConnection> pooledObject) throws Exception {
    initDefaultListeners(pooledObject.getObject());
  }

  @Override
  public void passivateObject(PooledObject<ClosableSmtpConnection> pooledObject) throws Exception {
    if (LOG.isDebugEnabled()) {
      LOG.debug("passivateObject [{}]", pooledObject.getObject().isConnected());
    }
    pooledObject.getObject().clearListeners();
  }


  public List<TransportListener> getDefaultListeners() {
    return Collections.unmodifiableList(defaultTransportListeners);
  }

  public boolean isInvalidateConnectionOnException() {
    return invalidateConnectionOnException;
  }

  public Session getSession() {
    return session;
  }

  public TransportStrategy getTransportFactory() {
    return transportFactory;
  }

  public ConnectionStrategy getConnectionStrategy() {
    return connectionStrategy;
  }

  private void initDefaultListeners(ClosableSmtpConnection smtpTransport) {
    for (TransportListener transportListener : defaultTransportListeners) {
      smtpTransport.addTransportListener(transportListener);
    }
  }
}