/*
 * Created on Nov 23, 2004
 * 
 *  This file is part of susimail project, see http://susi.i2p/
 *  
 *  Copyright (C) 2004-2005  <susi23@mail.i2p>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *  
 *  $Revision: 1.2 $
 */
package i2p.susi.webmail;

import i2p.susi.debug.Debug;
import i2p.susi.util.Config;
import i2p.susi.util.ReadBuffer;
import i2p.susi.webmail.pop3.POP3MailBox;
import i2p.susi.webmail.pop3.POP3MailBox.FetchRequest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;

/**
 * @author user
 */
class MailCache {
	
	public enum FetchMode {
		HEADER, ALL, CACHE_ONLY
	}
	
	private final POP3MailBox mailbox;
	private final Hashtable<String, Mail> mails;
	private final PersistentMailCache disk;
	
	/** Includes header, headers are generally 1KB to 1.5 KB,
	 *  and bodies will compress well.
         */
	private static final int FETCH_ALL_SIZE = 8192;

	/**
	 * @param mailbox non-null
	 */
	MailCache(POP3MailBox mailbox, 
		  String host, int port, String user, String pass) {
		this.mailbox = mailbox;
		mails = new Hashtable<String, Mail>();
		PersistentMailCache pmc = null;
		try {
			pmc = new PersistentMailCache(host, port, user, pass);
		} catch (IOException ioe) {
			Debug.debug(Debug.ERROR, "Error creating disk cache: " + ioe);
		}
		disk = pmc;
		if (disk != null)
			loadFromDisk();
	}

	/**
	 * 
	 * @since 0.9.13
	 */
	private void loadFromDisk() {
		Collection<Mail> dmails = disk.getMails();
		for (Mail mail : dmails) {
			mails.put(mail.uidl, mail);
		}
	}

	/**
	 * The ones known locally, which will include any known on the server, if connected.
	 * Will not include any marked for deletion.
	 * 
	 * @since 0.9.13
	 */
	public String[] getUIDLs() {
		List<String> uidls = new ArrayList<String>(mails.size());
		synchronized(mails) {
			for (Mail mail : mails.values()) {
				if (!mail.markForDeletion)
					uidls.add(mail.uidl);
			}
		}
		return uidls.toArray(new String[uidls.size()]);
	}

	/**
	 * Fetch any needed data from pop3 server.
	 * 
	 * @param uidl message id to get
	 * @param mode CACHE_ONLY to not pull from pop server
	 * @return An e-mail or null
	 */
        @SuppressWarnings({"unchecked", "rawtypes"})
	public Mail getMail(String uidl, FetchMode mode) {
		
		Mail mail = null, newMail = null;

		/*
		 * synchronize update to hashtable
		 */
		synchronized(mails) {
			mail = mails.get( uidl );
			if( mail == null ) {
				newMail = new Mail(uidl);
				mails.put( uidl, newMail );
			}
		}
		if( mail == null ) {
			mail = newMail;
			mail.setSize(mailbox.getSize(uidl));
		}
		if (mail.markForDeletion)
			return null;
		int sz = mail.getSize();
		if (mode == FetchMode.HEADER && sz > 0 && sz <= FETCH_ALL_SIZE)
			mode = FetchMode.ALL;
			
		if (mode == FetchMode.HEADER) {
			if(!mail.hasHeader())
				mail.setHeader(mailbox.getHeader(uidl));
		} else if (mode == FetchMode.ALL) {
			if(!mail.hasBody()) {
				ReadBuffer rb = mailbox.getBody(uidl);
				if (rb != null) {
					mail.setBody(rb);
					if (disk != null && disk.saveMail(mail) &&
					    !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
						mailbox.queueForDeletion(mail.uidl);
					}
				}
			}
		} else {
			// else if it wasn't in cache, too bad
		}
		return mail;
	}

	/**
	 * Fetch any needed data from pop3 server.
	 * Mail objects are inserted into the requests.
	 * After this, call getUIDLs() to get all known mail UIDLs.
	 * MUST already be connected, otherwise returns false.
	 * 
	 * @param mode HEADER or ALL only
	 * @return true if any were fetched
	 * @since 0.9.13
	 */
        @SuppressWarnings({"unchecked", "rawtypes"})
	public boolean getMail(FetchMode mode) {
		if (mode == FetchMode.CACHE_ONLY)
			throw new IllegalArgumentException();
		boolean hOnly = mode == FetchMode.HEADER;
		
		Collection<String> popKnown = mailbox.getUIDLs();
		if (popKnown == null)
			return false;
		List<POP3Request> fetches = new ArrayList<POP3Request>();
		//  Fill in the answers from the cache and make a list of
		//  requests.to send off
		for (String uidl : popKnown) {
			Mail mail = null, newMail = null;
			boolean headerOnly = hOnly;

			/*
			 * synchronize update to hashtable
			 */
			synchronized(mails) {
				mail = mails.get( uidl );
				if( mail == null ) {
					newMail = new Mail(uidl);
					mails.put( uidl, newMail );
				}
			}
			if( mail == null ) {
				mail = newMail;
				mail.setSize(mailbox.getSize(uidl));
			}
			if (mail.markForDeletion)
				continue;
			int sz = mail.getSize();
			if (sz > 0 && sz <= FETCH_ALL_SIZE)
				headerOnly = false;
			if( headerOnly ) {
				if(!mail.hasHeader()) {
					if (disk != null) {
						if (disk.getMail(mail, true)) {
							Debug.debug(Debug.DEBUG, "Loaded header from disk cache: " + uidl);
							// note that disk loaded the full body if it had it
							if (mail.hasBody() &&
								!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
								// we already have it, send delete
								mailbox.queueForDeletion(mail.uidl);
							}
							continue;  // found on disk, woo
						}
					}
					POP3Request pr = new POP3Request(mail, true);
					fetches.add(pr);
				} else {
					if (mail.hasBody() &&
						!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
						// we already have it, send delete
						mailbox.queueForDeletion(mail.uidl);
					}
				}
			} else {
				if(!mail.hasBody()) {
					if (disk != null) {
						if (disk.getMail(mail, false)) {
							Debug.debug(Debug.DEBUG, "Loaded body from disk cache: " + uidl);
							// note that disk loaded the full body if it had it
							if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
								// we already have it, send delete
								mailbox.queueForDeletion(mail.uidl);
							}
							continue;  // found on disk, woo
						}
					}
					POP3Request pr = new POP3Request(mail, false);
					fetches.add(pr);
				} else {
					if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
						// we already have it, send delete
						mailbox.queueForDeletion(mail.uidl);
					}
				}
			}
		}

		boolean rv = false;
		if (!fetches.isEmpty()) {
			//  Send off the fetches
			// gaah compiler
			List foo = fetches;
			List<FetchRequest> bar = foo;
			mailbox.getBodies(bar);
			//  Process results
			for (POP3Request pr : fetches) {
				ReadBuffer rb = pr.buf;
				if (rb != null) {
					Mail mail = pr.mail;
					if (!mail.hasHeader())
						mail.setNew(true);
					if (pr.getHeaderOnly()) {
						mail.setHeader(rb);
					} else {
						mail.setBody(rb);
					}
					rv = true;
					if (disk != null) {
						if (disk.saveMail(mail) && mail.hasBody() &&
						    !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
							mailbox.queueForDeletion(mail.uidl);
						}
					}
				}
			}
		}
		return rv;
	}

	/**
	 * Mark mail for deletion locally.
	 * Send delete requests to POP3 then quit and reconnect.
	 * No success/failure indication is returned.
	 * 
	 * @since 0.9.13
	 */
	public void delete(String uidl) {
		delete(Collections.singleton(uidl));
	}

	/**
	 * Mark mail for deletion locally.
	 * Send delete requests to POP3 then quit and reconnect.
	 * No success/failure indication is returned.
	 * 
	 * @since 0.9.13
	 */
	public void delete(Collection<String> uidls) {
		List<String> toDelete = new ArrayList<String>(uidls.size());
		for (String uidl : uidls) {
			if (disk != null)
				disk.deleteMail(uidl);
			synchronized(mails) {
				Mail mail = mails.get(uidl);
				if (mail == null)
					continue;
				mail.markForDeletion = true;
				// now replace it with an empty one to save memory
				mail = new Mail(uidl);
				mail.markForDeletion = true;
				mails.put(uidl, mail);
			}
			toDelete.add(uidl);
		}
		if (toDelete.isEmpty())
			return;
		mailbox.queueForDeletion(toDelete);
	}

	/**
	 *  Outgoing to POP3
	 */
	private static class POP3Request implements FetchRequest {
		public final Mail mail;
		private final boolean headerOnly;
		public ReadBuffer buf;

		public POP3Request(Mail m, boolean hOnly) {
			mail = m;
			headerOnly = hOnly;
		}

		public String getUIDL() {
			return mail.uidl;
		}

		public boolean getHeaderOnly() {
			return headerOnly;
		}

		public void setBuffer(ReadBuffer buffer) {
			buf = buffer;
		}
	}
}
