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

import it.mice.voila.runtime.mail.beans.MailSelector;
import it.mice.voila.runtime.mail.beans.MailTemplate;
import it.mice.voila.runtime.mail.manager.MailManager;
import it.mice.voila.runtime.mail.senders.MimeMailSenderImpl;
import it.mice.voila.runtime.util.VelocityContextUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.mail.internet.MimeMessage;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.mail.javamail.MimeMessageHelper;

public class MailUtils {
	/**
	 * Action default logger
	 */
	private static Log logger = LogFactory.getLog(MailUtils.class);

	/**
	 * separator should multiple email addresses be specified in to,cc,bcc
	 */
	private static String EMAIL_ADDRESS_SEPARATOR =",";
	
	public static Map<String, Object> EMPTY_MAP = new HashMap<String, Object>();
	
	public static Object getStaticMethodValue(String fqName, String methodName) {
		try {
			Class utils = Class.forName(fqName);
			Method getMethod = utils.getMethod(methodName);
			Object output = getMethod.invoke(null);
			return output;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static Object getCurrentUserDetail() {
		return getStaticMethodValue("it.mice.voila.runtime.security.SecurityUtils", "getCurrentUserDetail");
	}
	
	
	public static MimeMessageHelper createNewMimeMessageHelper(MailTemplate mailTemplate, MimeMailSenderImpl mailSender, Map objectMap, Map allegati) {
		MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper messageHelper = null;
		try {
			messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
			if (!StringUtils.isBlank(mailTemplate.getMailFrom())) {
				messageHelper.setFrom(convertTemplate(mailTemplate.getMailFrom(), objectMap, mailSender.getVelocityEngine()));
			} else {
				messageHelper.setFrom(mailSender.getMailConfig().getMailFromAddress());
			}
			String[] to = extractEmails(mailTemplate.getMailTo(), mailTemplate.getMailToSelectors(), objectMap, mailSender.getVelocityEngine());
			if (to.length > 0) {
				messageHelper.setTo(to);
			}
			String[] cc = extractEmails(mailTemplate.getMailCc(), mailTemplate.getMailCcSelectors(), objectMap, mailSender.getVelocityEngine());
			if (cc.length > 0) {
				messageHelper.setCc(cc);
			}
			String[] bcc = extractEmails(mailTemplate.getMailBcc(), mailTemplate.getMailBccSelectors(), objectMap, mailSender.getVelocityEngine());
			if (bcc.length > 0) {
				messageHelper.setBcc(bcc);
			}
			if (!StringUtils.isBlank(mailTemplate.getMailReplyTo())) {
				messageHelper.setReplyTo(convertTemplate(mailTemplate.getMailReplyTo(), objectMap, mailSender.getVelocityEngine()));
			}
			String subject = convertTemplate(mailTemplate.getMailSubject(), objectMap, mailSender.getVelocityEngine());
			messageHelper.setSubject(subject);

			objectMap.put("mailSubject", subject);
			objectMap.put("skipTitle", mailTemplate.isSkipTitle());
			
			String htmlText = null;
			String plainText = null;
			if (!StringUtils.isBlank(mailTemplate.getMailBodyHtml())) {
				StringBuffer html = new StringBuffer();
				if (mailTemplate.getTheMailStyle() != null) {
					html.append(
							mailTemplate.getTheMailStyle().getMailStyleHeader())
							.append(mailTemplate.getMailBodyHtml())
							.append(mailTemplate.getTheMailStyle()
									.getMailStyleFooter());
				} else {
					html.append(mailTemplate.getMailBodyHtml());
				}
				htmlText = convertTemplate(html.toString(), objectMap, mailSender.getVelocityEngine());
			}
			if (!StringUtils.isBlank(mailTemplate.getMailBodyText())) {
				plainText = convertTemplate(mailTemplate.getMailBodyText(), objectMap, mailSender.getVelocityEngine());
			}
			if (htmlText != null && plainText != null) {
				messageHelper.setText(plainText, htmlText);
			}
			if (htmlText != null && plainText == null) {
				messageHelper.setText(htmlText, true);
			}
			if (htmlText == null && plainText != null) {
				messageHelper.setText(plainText, false);
			}

			Map attachments = new HashMap();
			if (allegati != null) {
				attachments.putAll(allegati);
			}
			resolveTemplateAttachments(mailTemplate, objectMap, attachments);
			
			if (attachments != null) {
				Iterator k = attachments.keySet().iterator();
				while (k.hasNext()) {
				    String filename = (String) k.next();
					if (attachments.get(filename) instanceof ByteArrayOutputStream) {
						ByteArrayOutputStream baos = (ByteArrayOutputStream) attachments.get(filename);
						messageHelper.addAttachment(filename, new ByteArrayResource(baos.toByteArray()));
					} else if (attachments.get(filename) instanceof InputStream) {
						InputStream is = (InputStream) attachments.get(filename);
						messageHelper.addAttachment(filename, new ByteArrayResource(IOUtils.toByteArray(is)));
					} else {
						throw new RuntimeException(
								"At the moment only ByteArrayOutputStream or any InputStream are allowed as attachments");
					}
				}
			}
			
		} catch (Exception e) {
	    	throw new RuntimeException(e);
		}

        return messageHelper;
	}

	public static String[] extractEmails(String mail, Collection<MailSelector> userSelector, Object dataObject, VelocityEngine velocityEngine) {
		Set<String> emails = new HashSet<String>();
		
		if (!StringUtils.isBlank(mail)) {
			String mailConverted = convertTemplate(mail, dataObject, velocityEngine);
			// gestione indirizzi separati da virgola
			for (String singleEmail : mailConverted.split(EMAIL_ADDRESS_SEPARATOR)) {
				if (singleEmail != null && !StringUtils.isEmpty(singleEmail.trim())) {
					emails.add(singleEmail);				
				}
			}
		}

		if (dataObject != null && velocityEngine != null) {
			Iterator<MailSelector> i = userSelector.iterator();
			while (i.hasNext()) {
				MailSelector mailSelector = i.next();
				Set<String> mails = mailSelector.getSelectedEmails(dataObject, velocityEngine);
				emails.addAll(mails);
			}
		}

		String[] emailsArray = (String[])emails.toArray(new String[emails.size()]);
		return emailsArray;
	}

	public static String convertTemplate(String input, Object dataObject, VelocityEngine velocityEngine) {
		if (dataObject == null || velocityEngine == null) {
			return input;
		}
		String messageText = null;
		try {
			Reader messageReader = null;
			if (input != null) {
				messageText = StringUtils.replace(input, "@{", "${");
				messageText = StringUtils.replace(messageText, "@!{", "$!{");
			}
			VelocityContext context = new VelocityContext(VelocityContextUtils.getVelocityContextToolsMap());
			try {
				context.put("currentUser", MailUtils.getCurrentUserDetail());
			} catch (Exception e) {
				//There is no current user details, probably an anonymous user...
			}
			context.put("dataObject", dataObject);
			prepareBaseUrl(context);
			
			if (dataObject instanceof Map) {
				for (Iterator iterator = ((Map) dataObject).entrySet().iterator(); iterator.hasNext();) {
					Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
					context.put(entry.getKey(), entry.getValue());
				}
			}
			String logString = "";
			StringWriter sw = new StringWriter();
			if (parseVelocityTemplate(context, sw, logString, messageText, messageReader, velocityEngine)) {
			    if (logger.isDebugEnabled())
			        logger.debug("Template text:\n" + messageText + "\nWas converted to:" + sw.toString());
			    return sw.toString();
			}
			String errMsg = "Failed to parse Velocity content for mail template text:" + input + ". Check content into applicationContext-Mail.xml file.." +
				"Error description:" + logString; 
		    if (logger.isWarnEnabled())
		        logger.warn("Failed to process mail template. Error template dump is:\n" + messageText + "\n");
			throw new RuntimeException(errMsg);
		} catch (Exception e) {
		    if (logger.isWarnEnabled())
		        logger.warn("Failed to process mail template. Error template dump is:\n" + messageText + "\n");
			throw new RuntimeException(e);
		}
	}

	private static void resolveTemplateAttachments(MailTemplate template, Map objectMap, Map attachments) {
		if (StringUtils.isBlank(template.getAttachments())) {
			return;
		}
		attachments.putAll(resolvePropertyScript(template.getAttachments(), objectMap));
	}

	public static Map resolvePropertyScript(String script, Map objectMap) {
		Map result = new HashMap();
		if (!StringUtils.isBlank(script)) {
			String[] lines = script.split("\n");
			for (int i = 0; i < lines.length; i++) {
				String[] lineParts = lines[i].split("=");
				String fileName = lineParts[0].trim();
				String propName = lineParts[1].trim();
				try {
					Object allegato = PropertyUtils.getNestedProperty(objectMap, propName);
					result.put(fileName, allegato);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
		return result;
	}

	/**
	 * @param mailSenderManager the mailSenderManager.
	 * @param mailConfigId the mailConfigId.
	 * @return the result.
	 */
	public static MimeMailSenderImpl fetchMailSender(MailManager mailSenderManager, String mailConfigId) {
		MimeMailSenderImpl mailSender = null;
		if (mailConfigId != null) {
			mailSender = mailSenderManager.getMailServers().get(mailConfigId);
		}
		if (mailSender == null) {
			mailSender = mailSenderManager.getMailServers().values().iterator().next();
		}
		if (mailSender == null) {
			throw new IllegalArgumentException("At least one mailConfig must be provided in order to use mail server.");
		}
		return mailSender;
	}

	/**
	 * The method try to get the base http url from the http request if the mail sender invocatiion was made from a web application.
	 * Else, try to resolve the base url from the "base.url" system property.
	 * If there is a base url defined in such places, put that url in the velocity context under name "baseUrl".
	 * @param context the velocity context to populate with the baseUrl attribute.
	 */
	private static void prepareBaseUrl(VelocityContext context) {
		String baseUrl = getBaseUrl();
		if (baseUrl != null) {
			context.put("baseUrl", baseUrl);
		}
	}

	protected static String getBaseUrl() {
		String baseUrl = null;
		try {
			Class utils = Class.forName("it.mice.voila.runtime.springmvc.util.SpringMvcUtil");
			Method getBaseUrlMethod = utils.getMethod("getBaseUrl");
			baseUrl = (String)getBaseUrlMethod.invoke(null);
		} catch (Exception e) {
			baseUrl = System.getProperty("base.url");
		}
		return baseUrl;
	}

	private static boolean parseVelocityTemplate(Context context, Writer out, String logTag, String stringTemplate, Reader streamTemplate, VelocityEngine velocityEngine) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException, IOException {
		if (stringTemplate != null) {
			return velocityEngine.evaluate(context, out, logTag, stringTemplate);
		}
		return velocityEngine.evaluate(context, out, logTag, streamTemplate);
	}

}
