/**
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.gateway.protocols.simple;

import java.io.ByteArrayInputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.util.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xmpp.packet.Presence;

/**
 * This class performs conversions between presence packets of XMPP and SIMPLE formats.
 * <br>
 * By now, SIP presence comforms with <a href="http://www.ietf.org/rfc/rfc4480.txt">RFC4480</a>.
 * @author Patrick Siu
 * @version 0.0.1
 */
public class SimplePresence {
	/**
	 * Tuple status of the presence packet.
	 * @author Patrick Siu
	 * @version 0.0.1
	 */
	public enum TupleStatus {
		OPEN  ("open"),
		CLOSED("closed");
		
		private String status;
		
		public static TupleStatus getTupleStatus(String tupleStatusString) throws IllegalArgumentException {
			for (TupleStatus t : values()) {
				if (t.toString().equalsIgnoreCase(tupleStatusString)) return t;
			}
			throw new IllegalArgumentException("There is no matching TupleStatus for this String.");
		}
		
		private TupleStatus(String status) {
			this.status = status;
		}
		
		public boolean isOpen() {
			return status.equals("open");
		}
		
		public String toString() {
			return status;
		}
	}
	
	/**
	 * Represents the rpid of the status packet.
	 * <br><br>
	 * Refer to <a href="http://www.ietf.org/rfc/rfc4480.txt">RFC4480</a> for details of these statuses.
	 * @author Patrick Siu
	 * @version 0.0.1
	 */
	public enum Rpid {
		APPOINTMENT      ("appointment"),
		AWAY             ("away"),
		BREAKFAST        ("breakfast"),
		BUSY             ("busy"),
		DINNER           ("dinner"),
		HOLIDAY          ("holiday"),
		IN_TRANSIT       ("in-transit"),
		LOOKING_FOR_WORK ("looking-for-work"),
		LUNCH            ("lunch"),
		MEAL             ("meal"),
		MEETING          ("meeting"),
		ON_THE_PHONE     ("on-the-phone"),
		OTHER            ("other"),
		PERFORMANCE      ("performance"),
		PERMANENT_ABSENCE("permanent-absence"),
		PLAYING          ("playing"),
		PRESENTATION     ("presentation"),
		SHOPPING         ("shopping"),
		SLEEPING         ("sleeping"),
		SPECTATOR        ("spectator"),
		STEERING         ("steering"),
		TRAVEL           ("travel"),
		TV               ("tv"),
		UNKNOWN          ("unknown"),
		VACATION         ("vacation"),
		WORKING          ("working"),
		WORSHIP          ("worship");
		
		private String desc;
		
		public static Rpid getRpid(String rpidString) throws IllegalArgumentException {
			for (Rpid r : values()) {
				if (r.toString().equalsIgnoreCase(rpidString)) return r;
			}
			throw new IllegalArgumentException("There is no matching Rpid for the String.");
		}
		
		private Rpid(String desc) {
			this.desc = desc;
		}
		
		/**
		 * Overridden to return the string description of the constant.
		 */
		public String toString() {
			return desc;
		}
	}
	
	private TupleStatus tupleStatus;
	private Rpid        rpid;
	private String      dmNote;
	private String      entity;
	
	/**
	 * Constructor.
	 */
	public SimplePresence() {
		this.tupleStatus = TupleStatus.OPEN;
		this.rpid        = Rpid.UNKNOWN;
		this.dmNote      = "";
		this.entity      = "";
	}
	
	public SimplePresence(TupleStatus tupleStatus) {
		this.tupleStatus = tupleStatus;
		this.rpid        = Rpid.UNKNOWN;
		this.dmNote      = "";
		this.entity      = "";
	}
	
	
	public void setRpid(Rpid rpid) {
		this.rpid = rpid;
	}
	
	public void setDmNote(String dmNote) {
		this.dmNote = dmNote;
	}
	
	public void setEntity(String entity) {
		this.entity = entity;
	}
	
	public void setTupleStatus(TupleStatus tupleStatus) {
		this.tupleStatus = tupleStatus;
	}
	
	
	public Rpid getRpid() {
		return this.rpid;
	}
	
	public String getDmNote() {
		return this.dmNote;
	}
	
	public TupleStatus getTupleStatus() {
		return this.tupleStatus;
	}
	
	private String getEightLength(int hash) {
		StringBuffer buffer = new StringBuffer(Integer.toHexString(hash));
		
		while (buffer.length() < 8) {
			buffer.insert(0, "0");
		}
		
		return new String(buffer);
	}
	
	public String toXML() {
		String result =
				"<?xml version='1.0' encoding='UTF-8'?>" +
				"<presence xmlns='urn:ietf:params:xml:ns:pidf'" +
				         " xmlns:dm='urn:ietf:params:xml:ns:pidf:data-model'" +
				         " xmlns:rpid='urn:ietf:params:xml:ns:pidf:rpid'" +
				         " xmlns:c='urn:ietf:params:xml:ns:pidf:cipid'" +
				         " entity='" + entity + "'>" +
				"<tuple id='t" + getEightLength(tupleStatus.hashCode()) + "'><status><basic>" + tupleStatus.toString() + "</basic></status></tuple>" +
				"<dm:person id='p" + getEightLength(this.hashCode()) + "'><rpid:activities><rpid:" + rpid.toString() + "/></rpid:activities>" +
//				"<tuple><status><basic>" + tupleStatus.toString() + "</basic></status></tuple>" +
//				"<dm:person><rpid:activities><rpid:" + rpid.toString() + "/></rpid:activities>" +
				((dmNote != null && !dmNote.equals("")) ? "<dm:note>" + dmNote + "</dm:note>" : "") + 
				"</dm:person>" +
				"</presence>";
		
//		DocumentFactory docFactory = DocumentFactory.getInstance();
//		
//		Document xmlDocument = docFactory.createDocument();
//		Element  rootElement = docFactory.createDocument().addElement("presence");
//
//		rootElement.addAttribute("xmlns",      "urn:ietf:params:xml:ns:pidf");
//		rootElement.addAttribute(new QName("xmlns", "dm"),   "urn:ietf:params:xml:ns:pidf:data-model");
//		rootElement.addAttribute(new QName("xmlns", "rpid"), "urn:ietf:params:xml:ns:pidf:rpid");
//		rootElement.addAttribute(new QName("xmlns", "c"),    "urn:ietf:params:xml:ns:pidf:cipid");
//		rootElement.addAttribute("entity",     entity);
//		
//		Element tupleElement = rootElement.addElement("tuple");
//		tupleElement.addAttribute("id", "t" + Integer.toHexString(this.hashCode()));
//		tupleElement.addElement("status").addElement("basic").setText(this.tupleStatus.toString());
//		
//		Element personElement = rootElement.addElement(new QName("dm", "person"));
//		personElement.addAttribute("id", "p" + Integer.toHexString(this.hashCode()));
//		personElement.addElement(new QName("rpid", "activities")).addElement("rpid:" + this.rpid.toString());
//		
//		if (this.dmNote != null && !this.dmNote.trim().equals("")) {
//			personElement.addElement("dm:note").setText(this.dmNote);
//		}
//		
//		return xmlDocument.asXML();
		
		return result;
	}
	
//	public Presence convertSIPPresenceToXMPP(String sipPresence) {
//		Presence  xmppPresence = new Presence();
//		
//		SAXParser saxParser;
//		try {
//			SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
//			saxParser = saxParserFactory.newSAXParser();
//			
//		} catch (Exception e) {
//			Log.debug("Unable to load parser to parse SIP presence to XMPP Presence.", e);
//			return xmppPresence;
//		}
//		
//		return xmppPresence;
//	}
	
//	public String convertXMPPPresenceToSIP(Presence xmppPresence) {
//		String sipPresence = "";
//		String basic       = "open";
//		String rpid        = "unknown";
//		String dmNote      = "";
//		
//		if (!xmppPresence.isAvailable()) {
//			// Prepare "closed" basic presence.
//			basic = "closed";
//		} else {
//			Presence.Show xmppPresenceShow = xmppPresence.getShow();
//			if (xmppPresenceShow.equals(Presence.Show.away)) {
//				rpid = "away";
//			} else if (xmppPresenceShow.equals(Presence.Show.chat)) {
//				rpid = "away";
//			} else if (xmppPresenceShow.equals(Presence.Show.dnd)) {
//				rpid = "busy";
//			} else if (xmppPresenceShow.equals(Presence.Show.xa)) {
//				rpid = "away";
//			} else {
//				rpid = "";
//			}
//		}
//		
//		sipPresence = "<?xml version='1.0' encoding='UTF-8'?>"
//				+ "<presence xmlns='urn:ietf:params:xml:ns:pidf' xmlns:dm='urn:ietf:params:xml:ns:pidf:data-model' "
//				+ "xmlns:rpid='urn:ietf:params:xml:ns:pidf:rpid' xmlns:c='urn:ietf:params:xml:ns:pidf:cipid' "
//				+ "entity='pres:sip:sipdemo1@192.168.1.199'>"
//				+ "<tuple><status><basic>" + basic + "</basic></status></tuple>"
//				+ "<dm:person id='p3e32d940'><rpid:activities><rpid:" + rpid + "/></rpid:activities></dm:person></presence>";
//		
//		return sipPresence;
//	}
	
	public void parse(String simplePresenceXml) throws Exception {
		SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
		SAXParser        saxParser        = saxParserFactory.newSAXParser();
		
		ByteArrayInputStream bais = new ByteArrayInputStream(simplePresenceXml.getBytes());
		
		saxParser.parse(bais, new SimplePresenceParser());
		bais.close();
	}
	
	
	public static SimplePresence parseSimplePresence(String simplePresenceXml) throws Exception {
		SimplePresence simplePresenceObject = new SimplePresence();
		simplePresenceObject.parse(simplePresenceXml);
		
		return simplePresenceObject;
	}
	
	class SimplePresenceParser extends DefaultHandler {
		private boolean isStartTag   = false;
		private boolean isPresence   = false;
		private boolean isStatus     = false;
		private boolean isStatusType = false;
		private boolean isStatusName = false;
		
		String elementName = null;
		String paramName   = null;
		String userName    = null;
		String statusType  = null;
		String statusName  = null;
		
		public SimplePresenceParser() {
		}
		
		public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) throws SAXException {
			isStartTag  = true;
			elementName = (!sName.equals(""))? sName : qName;
			
			if(elementName.equals("presence")) {
				isPresence = true;
				if (attrs.getIndex("entity") >= 0) {
					entity = attrs.getValue("entity");
				}
			}
			else if(elementName.equals("basic")) {
				isStatus = true;
			}
			else if(elementName.equals("rpid:user-input")) {
				isStatusType = true;
			}
			else if(elementName.startsWith("rpid:")) {
				try {
					String temp = elementName.substring(elementName.indexOf("rpid:") + 5);
					
					if(!temp.equals("activities")) {
						try {
							rpid = Rpid.getRpid(temp);
						}
						catch (IllegalArgumentException ex) {
							Log.debug(ex);
							// Ignore the exception.  Leave it as "unknown".
						}
					}
				}
				catch (Exception ex) {
					Log.debug(ex);
				}
			}
			else if(elementName.equals("dm:note")) {
				isStatusName = true;
			}
			
			if (isPresence) {
//				for(int i = 0; i < attrs.getLength(); i++) {
//					if(attrs.getQName(i).equals("entity")) {
//						userName = attrs.getValue(i).substring(attrs.getValue(i).indexOf("sip:"));
//					}
//				}
			}
		}
		
		public void characters(char buf[], int offset, int len) throws SAXException {
			String data = new String(buf, offset, len);
			
			if (isStatus) {
				try {
					tupleStatus = TupleStatus.getTupleStatus(data);
				}
				catch (IllegalArgumentException ex) {
				}
			}
			else if (isStatusType) {
//				statusType = data;
			}
			else if (isStatusName) {
				dmNote = data;
				
//				if (rpid.compareTo(Rpid.UNKNOWN) == 0) {
//					try {
//						rpid = Rpid.getRpid(data);
//					}
//					catch (IllegalArgumentException ex) {
//						// Ignore the exception.  Leave it as "unknown".
//					}
//				}
//				statusName = data;
			}
		}
		
		public void endElement(String namespaceURI, String sName, String qName) throws SAXException {
			isStartTag = false;
			elementName = (!sName.equals(""))? sName : qName;
			
			if(elementName.equals("presence")) {
				isPresence   = false;
			}
			else if(elementName.equals("basic")) {
				isStatus     = false;
			}
			else if(elementName.equals("rpid:user-input")) {
				isStatusType = false;
			}
			else if(elementName.equals("dm:note")) {
				isStatusName = false;
			}
		}
		
		public void endDocument() throws SAXException {
//			obj.setUser(userName);
//			obj.setType(statusType);
//			obj.setStatus(statusName);
		}
	}
}
