package de.thomasjacob.improve.logic.issue;

import de.thomasjacob.improve.logic.Logs;
import de.thomasjacob.improve.logic.user.User;
import de.thomasjacob.webcore.TechnicalException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;

import org.apache.commons.el.ExpressionEvaluatorImpl;
import org.springframework.core.io.Resource;


/**
 * The notification mailer.
 * 
 * @author Thomas Jacob
 */
public class NotificationMailer
{
	/**
	 * The notification mailer singleton.
	 */
	private static NotificationMailer instance;

	private String bodyTemplate;

	private String from;

	private Session mailSession;

	private String mailSessionName;

	private String subjectTemplate;

	private Resource templatePath;

	/**
	 * Returns the notification mailer singleton.
	 * 
	 * @return The singleton.
	 */
	public static NotificationMailer getInstance()
	{
		return instance;
	}

	public void initialize() throws IOException, NamingException
	{
		instance = this;

		File templateFile = templatePath.getFile();

		byte[] buffer = new byte[(int) templateFile.length()];
		InputStream stream = templatePath.getInputStream();
		stream.read(buffer);

		bodyTemplate = new String(buffer, "ISO-8859-15");

		InitialContext context = new InitialContext();
		mailSession = (Session) context.lookup(mailSessionName);
	}

	public void sendNotification(User[] users, Issue issue, User editor, String serverUrl) throws TechnicalException
	{
		for (int userNo = 0; userNo < users.length; userNo++)
		{
			User user = users[userNo];
			if (user.getEmail().equals(""))
			{
				continue;
			}

			ExpressionEvaluatorImpl evaluator = new ExpressionEvaluatorImpl();
			VariableResolverImpl resolver = new VariableResolverImpl();
			resolver.setValue("issue", issue);
			resolver.setValue("serverUrl", serverUrl);
			resolver.setValue("user", user);

			String body;
			try
			{
				body = (String) evaluator.evaluate(bodyTemplate, String.class, resolver, new FunctionMapperImpl());
			}
			catch (ELException exception)
			{
				throw new TechnicalException("Cannot build mail body", exception);
			}

			String subject;
			try
			{
				subject = (String) evaluator.evaluate(subjectTemplate, String.class, resolver, new FunctionMapperImpl());
			}
			catch (ELException exception)
			{
				throw new TechnicalException("Cannot build mail subject", exception);
			}

			try
			{
				MimeMessage message = new MimeMessage(mailSession);
				message.setFrom(new InternetAddress(from));
				message.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail()));
				message.setSubject(subject);
				message.setText(body);

				if (!editor.getEmail().equals(""))
				{
					message.setReplyTo(new InternetAddress[]{new InternetAddress(editor.getEmail())});
				}

				Transport.send(message);
			}
			catch (Exception exception)
			{
				Logs.LOGIC.warn("Cannot send notification mail to '" + user.getName() + "'", exception);
			}
		}
	}

	public void setFrom(String from)
	{
		this.from = from;
	}

	public void setMailSession(String mailSessionName)
	{
		this.mailSessionName = mailSessionName;
	}

	public void setSubject(String subjectTemplate)
	{
		this.subjectTemplate = subjectTemplate;
	}

	public void setTemplatePath(Resource templatePath)
	{
		this.templatePath = templatePath;
	}

	public static class FunctionMapperImpl implements FunctionMapper
	{
		private Method emptyFunction;

		private Method sizeFunction;

		public FunctionMapperImpl()
		{
			try
			{
				emptyFunction = FunctionMapperImpl.class.getMethod("empty", new Class[]{Object.class});
				sizeFunction = FunctionMapperImpl.class.getMethod("size", new Class[]{Object.class});
			}
			catch (Exception exception)
			{
				throw new RuntimeException("Internal error: Cannot find EL function implementation", exception);
			}
		}

		public static boolean empty(Object value)
		{
			if (value == null)
			{
				return true;
			}
			else if (value instanceof Object[])
			{
				return ((Object[]) value).length == 0;
			}
			else if (value instanceof Collection)
			{
				return ((Collection) value).isEmpty();
			}
			else if (value instanceof Map)
			{
				return ((Map) value).isEmpty();
			}
			else if (value instanceof String)
			{
				return ((String) value).length() == 0;
			}
			else
			{
				return false;
			}
		}

		public static int size(Object value)
		{
			if (value == null)
			{
				return 0;
			}
			else if (value instanceof Object[])
			{
				return ((Object[]) value).length;
			}
			else if (value instanceof Collection)
			{
				return ((Collection) value).size();
			}
			else if (value instanceof Map)
			{
				return ((Map) value).size();
			}
			else
			{
				return 1;
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.jsp.el.FunctionMapper#resolveFunction(java.lang.String, java.lang.String)
		 */
		public Method resolveFunction(String prefix, String localName)
		{
			if (localName.equals("empty"))
			{
				return emptyFunction;
			}
			else if (localName.equals("size"))
			{
				return sizeFunction;
			}
			else
			{
				return null;
			}
		}
	}

	public static class VariableResolverImpl implements VariableResolver
	{
		private Map variables = new HashMap();

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.servlet.jsp.el.VariableResolver#resolveVariable(java.lang.String)
		 */
		public Object resolveVariable(String name)
		{
			return variables.get(name);
		}

		public void setValue(String name, Object value)
		{
			variables.put(name, value);
		}
	}
}