/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.mock;

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;

import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;

/**
 * ServiceReference mock.
 * 
 * <p/> This mock tries to adhere to the OSGi spec as much as possible by
 * providing the mandatory serviceId properties such as
 * {@link Constants#SERVICE_ID}, {@link Constants#OBJECTCLASS} and
 * {@link Constants#SERVICE_RANKING}.
 * 
 * @author Costin Leau
 * 
 */
public class MockServiceReference implements ServiceReference {

	private Bundle bundle;

	private static long GLOBAL_SERVICE_ID = System.currentTimeMillis();

	private long serviceId;

	// private ServiceRegistration registration;
	private Dictionary properties;

	private String[] objectClass = new String[] { Object.class.getName() };


	/**
	 * Constructs a new <code>MockServiceReference</code> instance using
	 * defaults.
	 */
	public MockServiceReference() {
		this(null, null, null);
	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance associated
	 * with the given bundle.
	 * 
	 * @param bundle associated reference bundle
	 */
	public MockServiceReference(Bundle bundle) {
		this(bundle, null, null);
	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance matching
	 * the given class namess.
	 * 
	 * @param classes associated class names
	 */
	public MockServiceReference(String[] classes) {
		this(null, null, null, classes);

	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance associated
	 * with the given bundle and matching the given class names.
	 * 
	 * @param bundle associated bundle
	 * @param classes matching class names
	 */
	public MockServiceReference(Bundle bundle, String[] classes) {
		this(bundle, null, null, classes);
	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance associated
	 * with the given service registration.
	 * 
	 * @param registration service registration
	 */
	public MockServiceReference(ServiceRegistration registration) {
		this(null, null, registration);
	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance associated
	 * with the given bundle, service registration and having the given service
	 * properties.
	 * 
	 * @param bundle associated bundle
	 * @param properties reference properties
	 * @param registration associated service registrations
	 */
	public MockServiceReference(Bundle bundle, Dictionary properties, ServiceRegistration registration) {
		this(bundle, properties, registration, null);
	}

	/**
	 * Constructs a new <code>MockServiceReference</code> instance. This
	 * constructor gives access to all the parameters of the mock service
	 * reference such as associated bundle, reference properties, service
	 * registration and reference class names.
	 * 
	 * @param bundle associated bundle
	 * @param properties reference properties
	 * @param registration service registration
	 * @param classes reference class names
	 */
	public MockServiceReference(Bundle bundle, Dictionary properties, ServiceRegistration registration, String[] classes) {
		this.bundle = (bundle == null ? new MockBundle() : bundle);
		// this.registration = (registration == null ? new
		// MockServiceRegistration() :
		// registration);
		this.properties = (properties == null ? new Hashtable() : properties);
		if (classes != null && classes.length > 0)
			this.objectClass = classes;
		addMandatoryProperties(this.properties);
	}

	private void addMandatoryProperties(Dictionary dict) {
		// add mandatory properties
		Object id = dict.get(Constants.SERVICE_ID);
		if (id == null || !(id instanceof Long))
			dict.put(Constants.SERVICE_ID, new Long(GLOBAL_SERVICE_ID++));

		if (dict.get(Constants.OBJECTCLASS) == null)
			dict.put(Constants.OBJECTCLASS, objectClass);

		Object ranking = dict.get(Constants.SERVICE_RANKING);
		if (ranking == null || !(ranking instanceof Integer))
			dict.put(Constants.SERVICE_RANKING, new Integer(0));

		serviceId = ((Long) dict.get(Constants.SERVICE_ID)).longValue();
	}

	public Bundle getBundle() {
		return bundle;
	}

	public Object getProperty(String key) {
		return properties.get(key);
	}

	public String[] getPropertyKeys() {
		String[] keys = new String[this.properties.size()];
		Enumeration ks = this.properties.keys();

		for (int i = 0; i < keys.length && ks.hasMoreElements(); i++) {
			keys[i] = (String) ks.nextElement();
		}

		return keys;
	}

	public Bundle[] getUsingBundles() {
		return new Bundle[] {};
	}

	public boolean isAssignableTo(Bundle bundle, String className) {
		return false;
	}

	/**
	 * Sets the properties associated with this reference.
	 * 
	 * @param properties
	 */
	public void setProperties(Dictionary properties) {
		if (properties != null) {
			// copy mandatory properties
			properties.put(Constants.SERVICE_ID, this.properties.get(Constants.SERVICE_ID));
			properties.put(Constants.OBJECTCLASS, this.properties.get(Constants.OBJECTCLASS));
			// optional property
			if (properties.get(Constants.SERVICE_RANKING) == null)
				properties.put(Constants.SERVICE_RANKING, this.properties.get(Constants.SERVICE_RANKING));

			this.properties = properties;
		}
	}

	/**
	 * Two mock service references are equal if they contain the same service
	 * id.
	 */
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj instanceof MockServiceReference) {
			return this.hashCode() == ((MockServiceReference) obj).hashCode();
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * Returns a hash code based on the class and service id.
	 */
	public int hashCode() {
		return MockServiceReference.class.hashCode() * 13 + (int) serviceId;
	}

	public String toString() {
		return "mock service reference [owning bundle id=" + bundle.hashCode() + "|props : " + properties + "]";
	}

	public int compareTo(Object reference) {
		ServiceReference other = (ServiceReference) reference;

		// compare based on service ranking

		Object ranking = this.getProperty(Constants.SERVICE_RANKING);
		// if the property is not supplied or of incorrect type, use the default
		int rank1 = ((ranking != null && ranking instanceof Integer) ? ((Integer) ranking).intValue() : 0);
		ranking = other.getProperty(Constants.SERVICE_RANKING);
		int rank2 = ((ranking != null && ranking instanceof Integer) ? ((Integer) ranking).intValue() : 0);

		int result = rank1 - rank2;

		if (result == 0) {
			long id1 = serviceId;
			long id2 = ((Long) other.getProperty(Constants.SERVICE_ID)).longValue();

			// when comparing IDs, make sure to return inverse results (i.e. lower
			// id, means higher service)
			return (int) (id2 - id1);
		}

		return result;
	}
}