package com.windowsazure.messaging;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.ObjectCreationFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * Abstract class representing a registration.
 *
 */
public abstract class Registration implements Cloneable {
	protected String registrationId;
	protected Set<String> tags = new HashSet<String>();
	protected String etag;
	protected Date expirationTime;
	
	private static final ThreadLocal<Digester> singleRegParser;
	private static final ThreadLocal<Digester> multiRegParser;
	
	static {
		singleRegParser = new ThreadLocal<Digester>(){
			@Override protected Digester initialValue() {
				Digester instance = new Digester();
				addRegistrationRules(instance);
                return instance;
             }
		};
		
		multiRegParser = new ThreadLocal<Digester>(){
			@Override protected Digester initialValue() {
				Digester instance = new Digester();
				addRegistrationRules(instance);
				addCollectionRules(instance);
                return instance;
             }
		};
	}

	public Registration() {
	}
	
	public Registration clone(){  
	    try {
			return (Registration) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException(e);
		}  
	}

	public Registration(String registrationId) {
		super();
		this.registrationId = registrationId;
	}

	public Registration(Set<String> tags) {
		super();
		this.tags = tags;
	}

	public abstract String getXml();

	public String getRegistrationId() {
		return registrationId;
	}

	public void setRegistrationId(String registrationId) {
		this.registrationId = registrationId;
	}

	public Set<String> getTags() {
		return tags;
	}

	public void setTags(Set<String> tags) {
		this.tags = tags;
	}

	public String getEtag() {
		return etag;
	}

	public void setEtag(String etag) {
		this.etag = etag;
	}

	public void setTagsFromString(String string) {
		tags = new HashSet<String>();
		String[] tagsArray = string.split(",");
		for (int i = 0; i < tagsArray.length; i++) {
			tags.add(tagsArray[i].trim());
		}
	}

	protected String getTagsXml() {
		StringBuffer buf = new StringBuffer();
		if (!tags.isEmpty()) {
			buf.append("<Tags>");
			for (Iterator<String> i = tags.iterator(); i.hasNext();) {
				buf.append(i.next());
				if (i.hasNext())
					buf.append(",");
			}
			buf.append("</Tags>");
		}
		return buf.toString();
	}	

	public Date getExpirationTime() {
		return expirationTime;
	}

	public void setExpirationTime(Date expirationTime) {
		this.expirationTime = expirationTime;
	}

	public void setExpirationTimeFromString(String expirationTimeString) throws ParseException {		
		this.expirationTime = javax.xml.bind.DatatypeConverter.parseDateTime(expirationTimeString).getTime();
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((etag == null) ? 0 : etag.hashCode());
		result = prime * result
				+ ((expirationTime == null) ? 0 : expirationTime.hashCode());
		result = prime * result
				+ ((registrationId == null) ? 0 : registrationId.hashCode());
		result = prime * result + ((tags == null) ? 0 : tags.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Registration other = (Registration) obj;
		if (etag == null) {
			if (other.etag != null)
				return false;
		} else if (!etag.equals(other.etag))
			return false;
		if (expirationTime == null) {
			if (other.expirationTime != null)
				return false;
		} else if (!expirationTime.equals(other.expirationTime))
			return false;
		if (registrationId == null) {
			if (other.registrationId != null)
				return false;
		} else if (!registrationId.equals(other.registrationId))
			return false;
		if (tags == null) {
			if (other.tags != null)
				return false;
		} else if (!tags.equals(other.tags))
			return false;
		return true;
	}

	public static Registration parse(InputStream content) throws IOException,
			SAXException {
		return singleRegParser.get().parse(content);
	}

	private static void addRegistrationRules(Digester digester) {
		digester.addFactoryCreate("*/RegistrationDescription",
				new Registration.RegistrationCreationFactory());
		digester.addObjectCreate("*/WindowsRegistrationDescription",
				WindowsRegistration.class);
		digester.addObjectCreate("*/WindowsTemplateRegistrationDescription",
				WindowsTemplateRegistration.class);
		digester.addObjectCreate("*/AppleRegistrationDescription",
				AppleRegistration.class);
		digester.addObjectCreate("*/AppleTemplateRegistrationDescription",
				AppleTemplateRegistration.class);
		digester.addObjectCreate("*/GcmRegistrationDescription",
				GcmRegistration.class);
		digester.addObjectCreate("*/GcmTemplateRegistrationDescription",
				GcmTemplateRegistration.class);
		digester.addObjectCreate("*/FcmRegistrationDescription",
				FcmRegistration.class);
		digester.addObjectCreate("*/FcmTemplateRegistrationDescription",
				FcmTemplateRegistration.class);
		digester.addObjectCreate("*/MpnsRegistrationDescription",
				MpnsRegistration.class);
		digester.addObjectCreate("*/MpnsTemplateRegistrationDescription",
				MpnsTemplateRegistration.class);
		digester.addObjectCreate("*/AdmRegistrationDescription",
				AdmRegistration.class);
		digester.addObjectCreate("*/AdmTemplateRegistrationDescription",
				AdmTemplateRegistration.class);
		digester.addObjectCreate("*/BaiduRegistrationDescription",
				BaiduRegistration.class);
		digester.addObjectCreate("*/BaiduTemplateRegistrationDescription",
				BaiduTemplateRegistration.class);
		digester.addCallMethod("*/RegistrationId", "setRegistrationId", 1);
		digester.addCallParam("*/RegistrationId", 0);
		digester.addCallMethod("*/ETag", "setEtag", 1);
		digester.addCallParam("*/ETag", 0);
		digester.addCallMethod("*/ChannelUri", "setChannelUri", 1);
		digester.addCallParam("*/ChannelUri", 0);
		digester.addCallMethod("*/DeviceToken", "setDeviceToken", 1);
		digester.addCallParam("*/DeviceToken", 0);
		digester.addCallMethod("*/GcmRegistrationId", "setGcmRegistrationId", 1);
		digester.addCallParam("*/GcmRegistrationId", 0);
		digester.addCallMethod("*/FcmRegistrationId", "setFcmRegistrationId", 1);
		digester.addCallParam("*/FcmRegistrationId", 0);
		digester.addCallMethod("*/Tags", "setTagsFromString", 1);
		digester.addCallParam("*/Tags", 0);
		digester.addCallMethod("*/BodyTemplate", "setBodyTemplate", 1);
		digester.addCallParam("*/BodyTemplate", 0);
		digester.addCallMethod("*/WnsHeader", "addHeader", 2);
		digester.addCallParam("*/WnsHeader/Header", 0);
		digester.addCallParam("*/WnsHeader/Value", 1);
		digester.addCallMethod("*/MpnsHeader", "addHeader", 2);
		digester.addCallParam("*/MpnsHeader/Header", 0);
		digester.addCallParam("*/MpnsHeader/Value", 1);
		digester.addCallMethod("*/Expiry", "setExpiry", 1);
		digester.addCallParam("*/Expiry", 0);
		digester.addCallMethod("*/ExpirationTime", "setExpirationTimeFromString", 1);
		digester.addCallParam("*/ExpirationTime", 0);
		digester.addCallMethod("*/AdmRegistrationId", "setAdmRegistrationId", 1);
		digester.addCallParam("*/AdmRegistrationId", 0);
		digester.addCallMethod("*/BaiduUserId", "setBaiduUserId", 1);
		digester.addCallParam("*/BaiduUserId", 0);
		digester.addCallMethod("*/BaiduChannelId", "setBaiduChannelId", 1);
		digester.addCallParam("*/BaiduChannelId", 0);
	}

	public static CollectionResult parseRegistrations(InputStream content)
			throws IOException, SAXException {
		return multiRegParser.get().parse(content);
	}

	private static void addCollectionRules(Digester digester) {
		digester.addObjectCreate("feed", CollectionResult.class);
		digester.addSetNext("*/RegistrationDescription", "addRegistration");
		digester.addSetNext("*/WindowsRegistrationDescription", "addRegistration");
		digester.addSetNext("*/WindowsTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/AppleRegistrationDescription", "addRegistration");
		digester.addSetNext("*/AppleTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/GcmRegistrationDescription", "addRegistration");
		digester.addSetNext("*/GcmTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/FcmRegistrationDescription", "addRegistration");
		digester.addSetNext("*/FcmTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/MpnsRegistrationDescription", "addRegistration");
		digester.addSetNext("*/MpnsTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/AdmRegistrationDescription", "addRegistration");
		digester.addSetNext("*/AdmTemplateRegistrationDescription", "addRegistration");
		digester.addSetNext("*/BaiduRegistrationDescription", "addRegistration");
		digester.addSetNext("*/BaiduTemplateRegistrationDescription", "addRegistration");
	}

	public static class RegistrationCreationFactory implements
			ObjectCreationFactory<Object> {
		private Digester digester;

		@Override
		public Object createObject(Attributes attributes) throws Exception {
			if ("WindowsRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new WindowsRegistration();
			}
			if ("WindowsTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new WindowsTemplateRegistration();
			}
			if ("AppleRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new AppleRegistration();
			}
			if ("AppleTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new AppleTemplateRegistration();
			}
			if ("GcmRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new GcmRegistration();
			}
			if ("GcmTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new GcmTemplateRegistration();
			}
			if ("FcmRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new FcmRegistration();
			}
			if ("FcmTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new FcmTemplateRegistration();
			}
			if ("MpnsRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new MpnsRegistration();
			}
			if ("MpnsTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new MpnsTemplateRegistration();
			}
			if ("AdmRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new AdmRegistration();
			}
			if ("AdmTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new AdmTemplateRegistration();
			}
			if ("BaiduRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new BaiduRegistration();
			}
			if ("BaiduTemplateRegistrationDescription".equals(attributes
					.getValue("i:type"))) {
				return new BaiduTemplateRegistration();
			}
			throw new UnsupportedOperationException("unknown type: "
					+ attributes.getValue("i:type"));
		}

		@Override
		public Digester getDigester() {
			return digester;
		}

		@Override
		public void setDigester(Digester digester) {
			this.digester = digester;
		}
	}
}