package it.mice.voila.runtime.mail.manager;

import it.mice.voila.runtime.mail.beans.MailConfig;
import it.mice.voila.runtime.mail.beans.MailEvent;
import it.mice.voila.runtime.mail.beans.MailTemplate;
import it.mice.voila.runtime.mail.senders.MailAuthenticator;
import it.mice.voila.runtime.mail.senders.MimeMailSenderImpl;
import it.mice.voila.runtime.mail.utils.MailUtils;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.mail.Session;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.mail.javamail.JavaMailSenderImpl;

/**
 * Mail manager that is able to send email based on templates and events.
 * This abstract mail manager must be extended by concrete subclasses in order to provide a way to initialize the entire mail engine.
 * Two base concrete subclasses ae provided by default: <ol>
 * <li>XmlMailManager - used to load mail engine configuration using xml spring configuration files (generally applicationContext-Mail.xml)</li>
 * <li>DaoMailManager - used by custom generated client applicationto that load mail engine configuration using database tables.</li>
 */
public abstract class MailManager extends JavaMailSenderImpl implements InitializingBean, ApplicationContextAware {
	/**
	 * Action default logger
	 */
	private static Log logger = LogFactory.getLog(MailManager.class);

	/**
	 * Mail engine configuration list.
	 */
	private Map<String, MailConfig> mailConfigs = new HashMap<String, MailConfig>();
	/**
	 * Defined mail templates.
	 */
	private Map<String, MailTemplate> mailTemplates = new HashMap<String, MailTemplate>();
	/**
	 * Defined mail events.
	 */
	private Map<String, MailEvent> mailEvents = new HashMap<String, MailEvent>();
	/**
	 * Java mail servers initialized automatically based on provided mail configs.
	 */
	private Map<String, MimeMailSenderImpl> mailServers = new HashMap<String, MimeMailSenderImpl>();

	/**
	 * The velocity engine used to process mail info: subject, bod, etc...
	 */
	private VelocityEngine velocityEngine;
	/**
	 * If set to true means that mal will be printed to standard output console and not reall sent!
	 */
	private boolean testMode = false;
	/**
	 * If set to true means that mal will be printed to standard output console and not reall sent!
	 */
	private String baseUrl = null;
	/**
	 * Spring application context.
	 */
	private ApplicationContext applicationContext;
	/**
	 * Default constructor.
	 */
	public MailManager() {
		super();
	}

	/**
	 * @return the velocityEngine
	 */
	public VelocityEngine getVelocityEngine() {
		return velocityEngine;
	}

	/**
	 * @param velocityEngine the velocityEngine to set
	 */
	public void setVelocityEngine(VelocityEngine velocityEngine) {
		this.velocityEngine = velocityEngine;
	}
	
	public String getBaseUrl() {
		return baseUrl;
	}

	public void setBaseUrl(String baseUrl) {
		this.baseUrl = baseUrl;
	}

	/**
	 * @return the testMode
	 */
	public boolean isTestMode() {
		return testMode;
	}

	/**
	 * @param testMode the testMode to set
	 */
	public void setTestMode(boolean testMode) {
		this.testMode = testMode;
	}

	/**
	 * @return the applicationContext
	 */
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/* (non-Javadoc)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}
	
	/**
	 * @return the mailServers
	 */
	public Map<String, MimeMailSenderImpl> getMailServers() {
		return mailServers;
	}

	/**
	 * @param mailServers the mailServers to set
	 */
	public void setMailServers(Map<String, MimeMailSenderImpl> mailServers) {
		this.mailServers = mailServers;
	}

	/**
	 * @return the mailConfigs
	 */
	public Map<String, MailConfig> getMailConfigs() {
		return mailConfigs;
	}

	/**
	 * @param mailConfigs the mailConfigs to set
	 */
	public void setMailConfigs(Map<String, MailConfig> mailConfigs) {
		this.mailConfigs = mailConfigs;
	}

	/**
	 * @return the mailTemplates
	 */
	public Map<String, MailTemplate> getMailTemplates() {
		return mailTemplates;
	}

	/**
	 * @param mailTemplates the mailTemplates to set
	 */
	public void setMailTemplates(Map<String, MailTemplate> mailTemplates) {
		this.mailTemplates = mailTemplates;
	}

	/**
	 * @return the mailEvents
	 */
	public Map<String, MailEvent> getMailEvents() {
		return mailEvents;
	}

	/**
	 * @param mailEvents the mailEvents to set
	 */
	public void setMailEvents(Map<String, MailEvent> mailEvents) {
		this.mailEvents = mailEvents;
	}
	/**
	 * Send an email by using the given templateId. The mail has no dynamic parts and no attachments.
	 * @param templateId mail template to process and send.
	 */
	public void sendEmailByTemplate(String templateId) {
		sendEmailByTemplate(templateId, MailUtils.EMPTY_MAP, null);
	}
	/**
	 * Send an email by using the given templateId. The mail can have dynamic parts but no attachments.
	 * @param templateId mail template to process and send.
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 */
	public void sendEmailByTemplate(String templateId, Map objectMap) {
		sendEmailByTemplate(templateId, objectMap, null);
	}
	/**
	 * Send an email by using the given templateId. The mail can have dynamic parts and attachments.
	 * @param templateId mail template to process and send.
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 * @param allegati map of attachments.
	 */
	public void sendEmailByTemplate(String templateId, Map objectMap, Map allegati) {
		MailTemplate mailTemplate = getMailTemplates().get(templateId);
		if (mailTemplate == null) {
			throw new IllegalArgumentException("Template " + templateId + " does not exist in the mail configuration.");
		}
		sendEmailByTemplate(mailTemplate, objectMap, allegati);
	}
	/**
	 * Send an email by using the given mail template object. The mail can have dynamic parts and attachments.
	 * @param template MailTemplate object to process and send
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 * @param allegati map of attachments.
	 */
	public void sendEmailByTemplate(MailTemplate template, Map objectMap, Map allegati) {
		objectMap.put("baseUrl", getBaseUrl());
		if (template.isEnabled()) {
			mailServers.get(template.getTheMailConfig().getMailConfigId()).sendEmails(template, objectMap, allegati);
		} else {
			logger.warn("Mail template " + template.getMailTemplateId() + " is not enabled. No mail will be sent!");
		}
	}

	/**
	 * Send one or more emails associated to the given eventId. Mails have no dynamic parts and no attachments.
	 * @param eventId the event to process in order to send related emails.
	 */
	public void sendEmailByEvent(String eventId) {
		sendEmailByEvent(eventId, MailUtils.EMPTY_MAP, null);
	}
	/**
	 * Send one or more emails associated to the given eventId. Mails can have dynamic parts but no attachments.
	 * @param eventId the event to process in order to send related emails.
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 */
	public void sendEmailByEvent(String eventId, Map objectMap) {
		sendEmailByEvent(eventId, objectMap, null);
	}
	/**
	 * Send one or more emails associated to the given eventId. Mails can have dynamic parts and attachments.
	 * @param eventId the event to process in order to send related emails.
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 * @param allegati map of attachments.
	 */
	public void sendEmailByEvent(String eventId, Map objectMap, Map allegati) {
		MailEvent mailEvent = getMailEvents().get(eventId);
		sendEmailByEvent(mailEvent, objectMap, allegati);
	}
	/**
	 * Send one or more emails associated to the given mail event object. Mails can have dynamic parts and attachments. 
	 * @param event the mail event object to process in order to send related emails.
	 * @param objectMap map of business objects to be used to resolve dynamic parts.
	 * @param allegati map of attachments.
	 */
	public void sendEmailByEvent(MailEvent event, Map objectMap, Map allegati) {
		if (event.isEnabled()) {
			Map copyMap = new HashMap();
			copyMap.putAll(objectMap);
			copyMap.putAll(MailUtils.resolvePropertyScript(event.getScript(), copyMap));
			for (Iterator<MailTemplate> iterator = event.getTheMailTemplate().iterator(); iterator.hasNext();) {
				MailTemplate template = iterator.next();
				sendEmailByTemplate(template, copyMap, allegati);
			}
		} else {
			logger.warn("Mail event " + event.getMailEventId() + " is not enabled. No mail will be sent!");
		}
	}
	
	/** {@inheritDoc}
	 * Initialize all mail engine configurations and prepare java mail servers.
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		logger.info("Start loading mail configuration...");
		
		initMailConfigs();
		initMailTemplates();
		initMailEvents();
		
		configMailServers();

		logger.info("Mail configuration success!");
	}
	/**
	 * Configure java mail servers.
	 */
	private void configMailServers() {
		Iterator<MailConfig> mailConfigs = getMailConfigs().values().iterator();
		Map<String, MimeMailSenderImpl> mailServersTmp = new HashMap<String, MimeMailSenderImpl>();
		while (mailConfigs.hasNext()) {
			MailConfig mailConfig = mailConfigs.next();
			
			MimeMailSenderImpl sender = getApplicationContext().getBean("mimeMailSenderImpl", MimeMailSenderImpl.class);
			
			sender.setDefaultEncoding(getDefaultEncoding());
			sender.setDefaultFileTypeMap(getDefaultFileTypeMap());
			sender.setJavaMailProperties(((Properties)getJavaMailProperties().clone()));
			sender.setTestMode(isTestMode());
			sender.setVelocityEngine(getVelocityEngine());
			sender.setProtocol(getProtocol());
			sender.setHost(getHost());
			sender.setPassword(getPassword());
			sender.setPort(getPort());
			sender.setUsername(getUsername());
			sender.setMailConfig(mailConfig);
			
			if (mailConfig.getMailNomeHost() != null) {
				sender.setHost(mailConfig.getMailNomeHost());
				if (mailConfig.getMailPort() != null) {
					sender.setPort(mailConfig.getMailPort());
				} else {
					sender.setPort(25);
				}
			}

			if (mailConfig.getMailUsername() != null && mailConfig.getMailPassword() != null) {
				sender.getJavaMailProperties().put("mail.smtp.auth", "true");
				sender.getJavaMailProperties().put("mail.smtps.auth", "true");
			} else {
				sender.getJavaMailProperties().put("mail.smtp.auth", "false");
				sender.getJavaMailProperties().put("mail.smtps.auth", "false");
			}
			
			if ("SSL".equals(mailConfig.getMailCrypto())) {
				sender.setProtocol("smtps");
				sender.getJavaMailProperties().put("mail.smtp.starttls.enable", "false");
			} else if ("TLS".equals(mailConfig.getMailCrypto())) {
				sender.setProtocol("smtp");
				sender.getJavaMailProperties().put("mail.smtp.starttls.enable", "true");
			} else if ("TLS/SSL".equals(mailConfig.getMailCrypto())) {
				sender.setProtocol("smtps");
				sender.getJavaMailProperties().put("mail.smtp.starttls.enable", "true");
				sender.getJavaMailProperties().put("mail." + sender.getProtocol() + ".socketFactory.port", sender.getPort());
				sender.getJavaMailProperties().put("mail." + sender.getProtocol() + ".socketFactory.class",	"javax.net.ssl.SSLSocketFactory");
			} else {
				sender.setProtocol("smtp");
				sender.getJavaMailProperties().put("mail.smtp.starttls.enable", "false");
			}

			sender.getJavaMailProperties().put("mail.smtp.connectiontimeout", 20000);
			sender.getJavaMailProperties().put("mail.smtp.timeout", 20000);
			sender.getJavaMailProperties().put("mail.smtps.connectiontimeout", 20000);
			sender.getJavaMailProperties().put("mail.smtps.timeout", 20000);

			if (mailConfig.getMailUsername() != null) {
				sender.setUsername(mailConfig.getMailUsername());
			}
			if (mailConfig.getMailPassword() != null) {
				sender.setPassword(mailConfig.getMailPassword());
			}
			
			if ("S".equals(mailConfig.getMailSPA())) {
				sender.setUseSPA(true);
				
//				sender.getJavaMailProperties().put("mail." + sender.getProtocol() + ".host", sender.getHost());
//				sender.getJavaMailProperties().put("mail." + sender.getProtocol() + ".auth", "true");
//				sender.getJavaMailProperties().put("mail." + sender.getProtocol() + ".port", sender.getPort());
		 
				Session session = Session.getInstance(sender.getJavaMailProperties(),
						new MailAuthenticator(sender.getUsername(), sender.getPassword()));
				sender.setSession(session);
			} else {
				sender.setSession(Session.getInstance(sender.getJavaMailProperties()));
			}
			
			mailServersTmp.put(mailConfig.getMailConfigId(), sender);
		}
		
		mailServers = mailServersTmp;
	}
	/**
	 * Superclass must provide a way to initialize the given (initially empty) mail engine configuration map.
	 * @param mailConfigs (initially empty) mail engine configuration map.
	 */
	protected abstract void initMailConfigs();
	/**
	 * Superclass must provide a way to initialize the given (initially empty) mail templates map.
	 * @param mailTemplates (initially empty) mail templates map.
	 */
	protected abstract void initMailTemplates();
	/**
	 * Superclass must provide a way to initialize the given (initially empty) mail events map.
	 * @param mailEvents (initially empty) mail events map.
	 */
	protected abstract void initMailEvents();
}
