/*
 *  Copyright (C) 2007  Takashi Kasuya <kasuya@sfc.keio.ac.jp>
 *
 * This library 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 library 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 library; if not, write to the Free Software
 *　Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jp.ac.naka.ec.plugin.mail;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import javax.imageio.ImageIO;
import javax.mail.BodyPart;
import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;

import jp.ac.naka.ec.EventSource;
import jp.ac.naka.ec.Main;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityEvent;
import jp.ac.naka.ec.entity.EntityListener;

import org.apache.log4j.Logger;

import com.drew.imaging.jpeg.JpegProcessingException;
import com.drew.lang.Rational;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifDirectory;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.exif.GpsDirectory;
import com.sun.mail.pop3.POP3SSLStore;

/**
 * メールからイベントを生成、配信
 * 
 * @author Takashi Kasuya
 * 
 */
public class GMailReceiver extends EventSource {

	private static Logger logger = Logger.getLogger(GMailReceiver.class);
	private String user, pass;
	private String pop3 = "";

	EntityContainer source;
	
	private Session session;
	private Store store;
	private Folder folder;
	private MailBodyParser parser = null;
	
	private EntityListener target;
	
	public static String resource_folder = "photo";
	public static int INTERVAL = 1000 * 10 * 3;

	/**
	 * 
	 * 
	 */
	public GMailReceiver() {
	}

	/* (non-Javadoc)
	 * @see jp.ac.naka.ec.Plugin#init(jp.ac.naka.ec.entity.EntityContainer)
	 */
	public void init(EntityContainer source) throws Exception {
		this.source = source;
		pop3 = "pop.gmail.com";
		user = "fugameister@gmail.com";
		pass = "vitaminwater";
		
		connect();

		Timer timer = new Timer(true);
		TimerTask task = new GetMailTask();
		timer.schedule(task, 0, INTERVAL);
		//Thread.sleep(100000);
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.Plugin#init(jp.ac.naka.ec.entity.EntityContainer, java.util.Properties)
	 */
	public void init(EntityContainer source, Properties prop) throws Exception {
		this.source = source;
		pop3 = prop.getProperty("pop");
		user = prop.getProperty("user");
		pass = prop.getProperty("password");
		
		String temp = prop.getProperty("interval");
		if (temp != null && !temp.equals(""))
			INTERVAL = Integer.valueOf(temp);
		temp = prop.getProperty("parser");
		if (temp != null && !temp.equals("")){
			Class clazz = Class.forName(temp);
			parser = (MailBodyParser)clazz.newInstance();
		}
		connect();

		Timer timer = new Timer(true);
		TimerTask task = new GetMailTask();
		timer.schedule(task, 0, INTERVAL);
		//Thread.sleep(100000);
	}

	private void connect() throws MessagingException {
		Properties pop3Props = new Properties();
		pop3Props.setProperty("mail.pop3.user", user);
		pop3Props.setProperty("mail.pop3.passwd", pass);
		pop3Props.setProperty("mail.pop3.ssl", "true");
		pop3Props.setProperty("mail.pop3.host", pop3);

		String user = ((pop3Props.getProperty("mail.pop3.user") != null) ? pop3Props
				.getProperty("mail.pop3.user")
				: pop3Props.getProperty("mail.user"));
		String passwd = ((pop3Props.getProperty("mail.pop3.passwd") != null) ? pop3Props
				.getProperty("mail.pop3.passwd")
				: pop3Props.getProperty("mail.passwd"));
		String host = ((pop3Props.getProperty("mail.pop3.host") != null) ? pop3Props
				.getProperty("mail.pop3.host")
				: pop3Props.getProperty("mail.host"));

		if ((pop3Props.getProperty("mail.pop3.ssl") != null)
				&& (pop3Props.getProperty("mail.pop3.ssl")
						.equalsIgnoreCase("true"))) {
			String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
			pop3Props.setProperty("mail.pop3.socketFactory.class", SSL_FACTORY);
			pop3Props.setProperty("mail.pop3.socketFactory.fallback", "false");
			String portStr = ((pop3Props.getProperty("mail.pop3.port") != null) ? pop3Props
					.getProperty("mail.pop3.port")
					: "995");
			pop3Props.setProperty("mail.pop3.port", portStr);
			pop3Props.setProperty("mail.pop3.socketFactory.port", portStr);

			URLName url = new URLName("pop3://" + user + ":" + passwd + "@"
					+ host + ":" + pop3Props.getProperty("mail.pop3.port"));

			session = Session.getInstance(pop3Props, null);
			store = new POP3SSLStore(session, url);
		} else {
			session = Session.getInstance(pop3Props, null);
			store = session.getStore("pop3");
		}
		// session.setDebug(true);

		store.connect(host, user, passwd);

	}

	public void openFolder(String folderName) throws MessagingException {

		folder = store.getDefaultFolder();
		folder = folder.getFolder(folderName);

		if (folder == null)
			throw new NullPointerException("Invalid folder");
		folder.open(Folder.READ_ONLY);
		/*
		 * try { folder.open(Folder.READ_WRITE); } catch (MessagingException ex) {
		 * folder.open(Folder.READ_ONLY); }
		 */
	}

	public void closeFolder() throws Exception {
		folder.close(false);
	}

	public void disconnect() throws Exception {
		store.close();
	}

	public void processMessage(Message p) throws Exception {

		String message = null;
		String filename = "";
		GPSInfo info = null;
		if (parser == null) {
			parser = new MailBodyParserImpl();
		}
		message = parser.parseMailBody(p);
		String subject = p.getSubject();
		StringBuilder sb = new StringBuilder(message);

		if (p.isMimeType("text/plain")) {
			sb.append("<content><![CDATA[");
			sb.append((String) p.getContent());
			sb.append("]]></content>");
		} else if (p.isMimeType("multipart/alternative")) {
			
			MimeMultipart mmp = (MimeMultipart) p.getContent();
			for (int i = 0; i < mmp.getCount(); i++) {
				BodyPart part = mmp.getBodyPart(i);
				if (part.isMimeType("text/plain")) {
					sb.append("<content><![CDATA[");
					sb.append((String) part.getContent());
					sb.append("]]></content>");
				}
			}

		} else if (p.isMimeType("Multipart/Mixed")) {
			Multipart multipart = (Multipart) p.getContent();

			if (multipart == null) {
				throw new NullPointerException("Constent is null");
			}
			for (int i = 0; i < multipart.getCount(); i++) {
				Part part = multipart.getBodyPart(i);
				String disp = part.getDisposition();
				if (disp == null) {
					MimeBodyPart mbp = (MimeBodyPart) part;
					if (part.isMimeType("text/plain")) {
						sb.append("<content><![CDATA[");
						sb.append((String) mbp.getContent());
						sb.append("]]></content>");
					} else if (part.isMimeType("multipart/alternative")) {
						sb.append("<content><![CDATA[");
						/*
						BufferedReader reader = new BufferedReader(new InputStreamReader(mbp.getInputStream()));
						String temp;
						while ((temp = reader.readLine()) != null) {
							sb.append(temp);
						}*/
						sb.append("UNREAD");
						sb.append("]]></content>");
					}
				} else if (disp.equalsIgnoreCase(Part.ATTACHMENT)
						|| disp.equalsIgnoreCase(Part.INLINE)) {
					MimeBodyPart mbp = (MimeBodyPart) part;
					if (mbp.isMimeType("image/jpeg")) {
						filename = System.currentTimeMillis() + ".jpg";
						try {
							BufferedImage image = ImageIO.read(part
									.getInputStream());
							filename = saveImageFile(filename, image);
							filename = filename.replace("\\", "/");
							info = getGPSInfoFromExif(part.getInputStream());
						} catch (Exception e) {
							// NOT
							logger.warn(
									"Error while processing attachment file.",
									e);
						}
					}
				}
			}
		}
		message = sb.toString();
		sb = new StringBuilder();
		sb.append("<mail>");
		sb.append(message);
		sb.append("<subject>");
		sb.append(subject);
		sb.append("</subject>");
		if (info != null) {
			sb.append("<latitude>");
			sb.append(info.getLatitude());
			sb.append("</latitude>");
			sb.append("<longitude>");
			sb.append(info.getLongitude());
			sb.append("</longitude>");
			sb.append("<path>");
			sb.append(filename);
			sb.append("</path>");
		}

		sb.append("</mail>");
		EntityEvent evt = new EntityEvent(sb.toString(), target, source);
		getEventDispatcher().dispatchEvent(evt);
	}

	GPSInfo getGPSInfoFromExif(InputStream input) throws MetadataException {
		Metadata metadata = null;
		try {
			metadata = new ExifReader(input).extract();
		} catch (JpegProcessingException e) {
			e.printStackTrace();
		}
		Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
		Date date = null;
		if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL))
			date = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL);

		Directory gpsDirectory = metadata.getDirectory(GpsDirectory.class);
		String datum = gpsDirectory.getString(GpsDirectory.TAG_GPS_MAP_DATUM);
		float lat = 0f, lon = 0f;
		if (datum == null) {
			return null;
		}
		else if (datum.equals("WGS-84")) {
			lat = toGeocode(gpsDirectory
					.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE));
			lon = toGeocode(gpsDirectory
					.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE));
		} else {
			// 日本測地系
			throw new UnsupportedOperationException("日本測地系は未対応");
		}
		return new GPSInfo(date, lat, lon);
	}

	static float toGeocode(Rational[] rationals) {
		float ret = 0;
		int i = 0;
		for (Rational r : rationals) {
			switch (i++) {
			case 0:
				ret += r.floatValue();
				break;
			case 1:
				ret += r.floatValue() / 60;
				break;
			case 2:
				ret += r.floatValue() / 3600;
				break;
			}
		}
		return ret;
	}

	private String saveImageFile(String filename, BufferedImage image)
			throws IOException {
		File dir = new File(resource_folder);
		if (!dir.exists()) {
			dir.mkdir();
		}
		// 保存処理
		File f = new File(dir.getAbsoluteFile() + "/" + filename);
		ImageIO.write(image, "jpg", f);
		return f.getAbsolutePath();
	}

	/**
	 * a = hour + 1/60* min + 1/3600 * sec
	 * 
	 * @param src
	 * @return Geocode
	 */
	float toGeocode(String src) {
		float ret = 0f;
		if (src == null) {
			return 0;
		} else {

			String temp = src.replace('"', '.');
			temp = temp.replace('\'', '.');
			String[] values = temp.split("\\.");
			ret = Float.valueOf(values[0]);
			ret += 1f / 60f * Float.valueOf(values[1]);
			ret += 1f / 3600f * Float.valueOf(values[2] + "." + values[3]);
			return ret;
		}
	}

	private static class GPSInfo {
		private float latitude, longitude;

		private Date date;

		public GPSInfo(Date date, float lat, float lon) {
			this.date = date;
			this.latitude = lat;
			this.longitude = lon;
		}

		/**
		 * @return the latitude
		 */
		public float getLatitude() {
			return latitude;
		}

		/**
		 * @return the longitude
		 */
		public float getLongitude() {
			return longitude;
		}
	}

	private class GetMailTask extends TimerTask {

		@Override
		public void run() {
			try {
				openFolder("INBOX");
				Message[] msgs = folder.getMessages();

				// Use a suitable FetchProfile
				FetchProfile fp = new FetchProfile();
				fp.add(FetchProfile.Item.ENVELOPE);
				folder.fetch(msgs, fp);
				//folder.setFlags(msgs, new Flags(Flags.Flag.DELETED), true);
				for (int i = 0; i < msgs.length; i++) {
					logger.info("Get message from [" + msgs[i].getFrom()[0]+"]");
					processMessage(msgs[i]);
				}
				if (msgs.length == 0) {
					logger.info("No mail");
				}
				closeFolder();
			} catch (Exception e) {
				logger.error("Error occure while getting Email", e);
				try {
					closeFolder();
					disconnect();
				} catch (Exception e1) {
				}
			}
		}

	}

	public static void main(String args[]) throws Exception {
		/*
		PropertyConfigurator.configure(Main.log4j_properties);
		Properties prop = new Properties();
		prop.setProperty("pop", "pop.gmail.com");
		prop.setProperty("user", "fugameister@gmail.com");
		prop.setProperty("password", "vitaminwater");
		MailReceiver r = new MailReceiver();
		r.init(null, prop);
		*/new Main();
	}

	public void setTarget(EntityListener target) {
		this.target = target;
	}
}
