/*
 *    FavouriteBasket.java
 *    Copyright (C) 2006 New Zealand Digital Library, http://www.nzdl.org
 *
 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.greenstone.gsdl3.service;

import java.sql.Statement;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import org.greenstone.util.GlobalProperties;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.GSPath;
import org.greenstone.gsdl3.util.UserContext;
import org.greenstone.gsdl3.util.XMLConverter;

import java.io.Serializable;
import java.net.InetAddress;
import java.util.Properties;
import java.util.Date;

import javax.mail.*;
import javax.mail.internet.*;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

import org.apache.log4j.*;

public class FavouriteBasket extends ServiceRack
{

	static Logger logger = Logger.getLogger(org.greenstone.gsdl3.service.FavouriteBasket.class.getName());

	// the services on offer
	// these strings must match what is found in the properties file
	protected static final String ADD_ITEM_SERVICE = "AddFavourite";
	protected static final String DISPLAY_ITEMS_SERVICE = "GetFavouritesList";
	protected static final String ITEM_NUM_SERVICE = "GetNumFavourites";
	protected static final String DELETE_ITEMS_SERVICE = "DeleteFavourites";
	protected static final String SEND_MAIL_SERVICE = "SendFavouritesMail";
    //protected static final String DELETE_ITEM_SERVICE = "DeleteItem";

  // for AddFavourite
	protected static final String ITEM_PARAM = "item";
  // for DeleteFavouritess
  protected static final String ITEMS_PARAM = "items";
  // for SendMail
  protected static final String ADDRESS_PARAM = "address";
  protected static final String SUBJECT_PARAM = "subject";
  protected static final String CONTENT_PARAM = "content";
  protected static final String CC_PARAM = "cc";
  protected static final String BCC_PARAM = "bcc";

  
	protected static final String delimiter = "|";
	protected static final int delay = 1800000;

	protected Hashtable<String, Hashtable<String, Hashtable<String, Item>>> userMap = null;
	protected Hashtable<String, UserTimer> timerMap = null;
	protected String username = "";
	protected String password = "";

        HashSet<String> meta_names = null;

        /** constructor */
	public FavouriteBasket()
	{
		userMap = new Hashtable<String, Hashtable<String, Hashtable<String, Item>>>();
		timerMap = new Hashtable<String, UserTimer>();
	}

	private Hashtable<String, Hashtable<String, Item>> updateDocMap(Element request)
	{

	  UserContext userContext = new UserContext(request);
	  String id = null;
	  String user_name =userContext.getUsername();
	  if (user_name == null || user_name.equals("")) {
	    id = userContext.getUserID();
	  } else {
	    id = user_name;
	  }
		if (userMap.containsKey(id))
		{
			if (timerMap.containsKey(id))
			{
				UserTimer timer = timerMap.get(id);
				timer.restart();
			}
			return userMap.get(id);
		}
		else
		{
			UserTimer timer = new UserTimer(delay, id);
			timerMap.put(id, timer);
			timer.start();
			Hashtable<String, Hashtable<String, Item>> newDocs = new Hashtable<String, Hashtable<String, Item>>();
			userMap.put(id, newDocs);
			return newDocs;
		}
	}

	/** configure this service */
	public boolean configure(Element info, Element extra_info)
	{
		logger.info("Configuring FavouriteBasket...");
		this.config_info = info;

		// set up short_service_info_ - for now just has name and type
		Element add_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		add_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		add_service.setAttribute(GSXML.NAME_ATT, ADD_ITEM_SERVICE);
		this.short_service_info.appendChild(add_service);

		// set up short_service_info_ - for now just has name and type
		Element disp_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		disp_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		disp_service.setAttribute(GSXML.NAME_ATT, DISPLAY_ITEMS_SERVICE);
		this.short_service_info.appendChild(disp_service);

		// set up short_service_info_ - for now just has name and type
		Element num_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		num_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		num_service.setAttribute(GSXML.NAME_ATT, ITEM_NUM_SERVICE);
		this.short_service_info.appendChild(num_service);

		// set up short_service_info_ - for now just has name and type
		Element delete_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		delete_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		delete_service.setAttribute(GSXML.NAME_ATT, DELETE_ITEMS_SERVICE);
		this.short_service_info.appendChild(delete_service);

		// set up short_service_info_ - for now just has name and type
		// Element deleteone_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		// deleteone_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		// deleteone_service.setAttribute(GSXML.NAME_ATT, DELETE_ITEM_SERVICE);
		// this.short_service_info.appendChild(deleteone_service);

		// set up short_service_info_ - for now just has name and type
		Element mail_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
		mail_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
		mail_service.setAttribute(GSXML.NAME_ATT, SEND_MAIL_SERVICE);
		this.short_service_info.appendChild(mail_service);

                Element format_info = (Element) GSXML.getChildByTagName(info, GSXML.FORMAT_ELEM);
		if (format_info != null)
		{
                  this.format_info_map.put(DISPLAY_ITEMS_SERVICE, this.desc_doc.importNode(format_info, true));
		}

		return true;

	}

	/** returns a specific service description */
  protected Element getServiceDescription(Document doc, String service_id, String lang, String subset)
	{

		if (service_id.equals(ADD_ITEM_SERVICE))
		{
			Element add_service = doc.createElement(GSXML.SERVICE_ELEM);
			add_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
			add_service.setAttribute(GSXML.NAME_ATT, ADD_ITEM_SERVICE);
			return add_service;
		}
		if (service_id.equals(DISPLAY_ITEMS_SERVICE))
		{

			Element disp_service = doc.createElement(GSXML.SERVICE_ELEM);
			disp_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
			disp_service.setAttribute(GSXML.NAME_ATT, DISPLAY_ITEMS_SERVICE);
			return disp_service;
		}

		if (service_id.equals(ITEM_NUM_SERVICE))
		{

			Element num_service = doc.createElement(GSXML.SERVICE_ELEM);
			num_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
			num_service.setAttribute(GSXML.NAME_ATT, ITEM_NUM_SERVICE);
			return num_service;
		}

		if (service_id.equals(DELETE_ITEMS_SERVICE))
		{

			Element del_service = doc.createElement(GSXML.SERVICE_ELEM);
			del_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
			del_service.setAttribute(GSXML.NAME_ATT, DELETE_ITEMS_SERVICE);
			return del_service;
		}

		if (service_id.equals(SEND_MAIL_SERVICE))
		{

			Element mail_service = doc.createElement(GSXML.SERVICE_ELEM);
			mail_service.setAttribute(GSXML.TYPE_ATT, "gather"); // what??
			mail_service.setAttribute(GSXML.NAME_ATT, SEND_MAIL_SERVICE);
			return mail_service;
		}

		return null;
	}

	protected Element processAddFavourite(Element request)
	{

		Hashtable<String, Hashtable<String, Item>> docsMap = updateDocMap(request);

		// Create a new (empty) result message
		Document result_doc = XMLConverter.newDOM();
		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);

		// Get the parameters of the request
		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
		if (param_list == null)
		{
			logger.error("FavouriteBasket Error: AddFavourite request had no paramList.");
			return result; // Return the empty result
		}

		HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);

		String item = (String) params.get(ITEM_PARAM);
		String collection = "";
		int pos = item.indexOf(":");
		if (pos != -1)
		{
			collection = item.substring(0, pos);
			item = item.substring(pos + 1);
		}

		if (docsMap.containsKey(collection))
		{
			Hashtable<String, Item> items = docsMap.get(collection);
			if (!items.containsKey(item))
			{
				Item newItem = new Item(collection, item);
				items.put(item, newItem);
				result.appendChild(newItem.wrapIntoElement(result_doc));
			}
		}
		else
		{
			Hashtable<String, Item> items = new Hashtable<String, Item>();
			Item newItem = new Item(collection, item);
			items.put(item, newItem);
			docsMap.put(collection, items);
			result.appendChild(newItem.wrapIntoElement(result_doc));
		}

		return result;
	}


	protected Element processDeleteFavourites(Element request)
	{

		Hashtable<String, Hashtable<String, Item>> docsMap = updateDocMap(request);

		// Create a new (empty) result message
		Document result_doc = XMLConverter.newDOM();
		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);

		// Get the parameters of the request
		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);

		if (param_list == null)
		{
			logger.error("FavouriteBasket Error: DeleteItem request had no paramList.");
			return result; // Return the empty result
		}

		HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);

		String param = (String) params.get(ITEMS_PARAM);

		if (param == null)
			return result;

		String[] items = param.split("\\|");

		for (int i = 0; i < items.length; i++)
		{
			String item = items[i];
			if (item.trim().length() == 0)
				continue;
			String collection = "";
			int pos = item.indexOf(":");
			if (pos != -1)
			{
				collection = item.substring(0, pos);
				item = item.substring(pos + 1);
			}

			if (docsMap.containsKey(collection))
			{
				Hashtable itemMap = docsMap.get(collection);
				if (itemMap.containsKey(item))
				{
					itemMap.remove(item);
				}
				if (itemMap.size() == 0)
				{
					docsMap.remove(collection);
				}
			}

		}

		return result;
	}


	protected Element processGetNumFavourites(Element request)
	{
		Hashtable<String, Hashtable<String, Item>> docsMap = updateDocMap(request);

		// Create a new (empty) result message
		Document result_doc = XMLConverter.newDOM();
		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);

		int size = 0;
		String ids = "";
		Iterator<String> keys = docsMap.keySet().iterator();

		while (keys.hasNext())
		{
			Hashtable items = docsMap.get(keys.next());
			size += items.size();
			Iterator values = items.values().iterator();
			while (values.hasNext())
			{
				Item item = (Item) values.next();
				result.appendChild(item.wrapIntoElement(result_doc));
			}
		}

		Element selement = result_doc.createElement("size");
		selement.setAttribute("value", size + "");
		result.appendChild(selement);

		return result;
	}


	protected Element processGetFavouritesList(Element request)
	{
		Hashtable<String, Hashtable<String, Item>> docsMap = updateDocMap(request);

		// Create a new (empty) result message
		Document result_doc = XMLConverter.newDOM();
		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);

		Iterator<String> keys = docsMap.keySet().iterator();

		while (keys.hasNext())
		{
			String collection = keys.next();
			Hashtable items = docsMap.get(collection);
			Iterator itemItr = items.values().iterator();

			Element collectionNode = result_doc.createElement("favouriteList");
			collectionNode.setAttribute("name", collection);
			result.appendChild(collectionNode);

			while (itemItr.hasNext())
			{
				Item item = (Item) itemItr.next();
				collectionNode.appendChild(item.wrapIntoElement(result_doc));
			}
		}

		return result;
	}




	public Element processSendFavouritesMail(Element request)
	{
		// Create a new (empty) result message
	  Document result_doc = XMLConverter.newDOM();
		Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);

		// Get the parameters of the request
		Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);

		if (param_list == null)
		{
			logger.error("FavouriteBasket Error: SendMail request had no paramList.");
			return result; // Return the empty result
		}

		HashMap<String, Serializable> params = GSXML.extractParams(param_list, false);

		String to = (String) params.get(ADDRESS_PARAM);
		String subject = (String) params.get(SUBJECT_PARAM);
		String content = (String) params.get(CONTENT_PARAM);
		String cc = (String) params.get(CC_PARAM);
		String bcc = (String) params.get(BCC_PARAM);

		String mailhost = GlobalProperties.getProperty("mail.smtp.host");
		username = GlobalProperties.getProperty("mail.smtp.username");
		password = GlobalProperties.getProperty("mail.smtp.password");
		String from = GlobalProperties.getProperty("mail.from");
		String port = GlobalProperties.getProperty("mail.smtp.port");
		String mail_security = GlobalProperties.getProperty("mail.security");
		String mailer = "msgsend";

		try
		{

			Properties props = System.getProperties();

			//Setup smtp host and from address
			// XXX - could use Session.getTransport() and Transport.connect()
			// XXX - assume we're using SMTP
			if (mailhost != null && !mailhost.trim().equals(""))
			{
				props.put("mail.smtp.host", mailhost);
			}
			else
			{
				props.put("mail.smtp.host", "localhost");
			}
			if (from != null && !from.trim().equals(""))
			{
				props.put("mail.from", from);
			}


			if (port != null && !port.trim().equals("")) {
			    props.put("mail.smtp.port", port);
			}
			
			if (mail_security != null) {
			    mail_security = mail_security.trim();
			    if (mail_security.equals("ssl")) {
				props.put("mail.smtp.ssl.enable", "true"); 
			    } else if (mail_security.equals("tls")) {
				props.put("mail.smtp.starttls.enable", "true");
			    }
			    else if (mail_security.equals("")) {
				logger.error("unknown security protocol "+mail_security +", should be ssl or tls");
			    }
			}

			// this doesn't seem to matter having this when
			// username and password are empty
			props.put("mail.smtp.auth", "true");

			//setup username and password to the smtp server
			
			if (username == null || username.trim().equals(""))
				username = "";
			if (password == null || password.trim().equals(""))
				password = "";
			
			Authenticator auth = new Authenticator()
			{
				protected PasswordAuthentication getPasswordAuthentication()
				{
					return new PasswordAuthentication(username, password);
				}
			};

			Session session = Session.getInstance(props, auth);

			Message msg = new MimeMessage(session);
			msg.setFrom();
			msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));
			if (cc != null)
			{
				msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false));
			}
			if (bcc != null)
			{
				msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(bcc, false));
			}
			msg.setSubject(subject);
			msg.setText(content.replaceAll("-------", "&"));
			msg.setHeader("X-Mailer", mailer);
			msg.setSentDate(new Date());

			// send the thing off
			Transport.send(msg);

			logger.info("\nMail was sent successfully.");
			result.appendChild(result_doc.createTextNode("Mail was sent successfully."));
		}
		catch (Exception e)
		{
		    logger.error("Error sending mail!");
		    e.printStackTrace();
		    result.appendChild(result_doc.createTextNode(e.getMessage()));
		}

		return result;
	}

	protected class Item
	{
		public String collection;
		public String docid;

	        public Item(String coll, String id)
		{
			this.collection = coll;
			this.docid = id;
		}

		public boolean equals(Object o)
		{
			if (!(o instanceof Item))
			{
				return false;
			}
			Item item = (Item) o;
			String id = collection + ":" + docid;
			String idin = item.collection + ":" + item.docid;
			return id.equals(idin);

		}

		public Element wrapIntoElement(Document doc)
		{
			Element itemElement = doc.createElement("documentNode");
			itemElement.setAttribute("nodeID", docid);
			itemElement.setAttribute("collection", collection);

			return itemElement;
		}
	}

	private class UserTimer extends Timer implements ActionListener
	{
		String id = "";

		public UserTimer(int delay, String id)
		{
			super(delay, (ActionListener) null);
			addActionListener(this);
			this.id = id;
		}

		public void actionPerformed(ActionEvent e)
		{
			userMap.remove(id);
			timerMap.remove(id);
			stop();
		}

	}

}
