/******************************************************************************
 * 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.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.NoSuchElementException;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;

/**
 * Bundle mock. Except resource/class loading operations (which are executed on its internal class loader), the rest of
 * the methods are dummies.
 * 
 * @author Costin Leau
 */
public class MockBundle implements Bundle {

	private String location;

	private final Dictionary headers;

	private static int GENERAL_BUNDLE_ID = 0;

	private long bundleId = (GENERAL_BUNDLE_ID++);

	// required for introspection by util classes (should be removed)
	private BundleContext bundleContext;

	private ClassLoader loader = getClass().getClassLoader();

	private final Dictionary defaultHeaders = new Hashtable(0);

	private final String SYMBOLIC_NAME = "Mock-Bundle_" + System.currentTimeMillis();

	private final String symName;

	private final Version version;

	private static class EmptyEnumeration implements Enumeration {

		public boolean hasMoreElements() {
			return false;
		}

		public Object nextElement() {
			throw new NoSuchElementException();
		}
	}

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

	/**
	 * Constructs a new <code>MockBundle</code> instance with the given bundle headers.
	 * 
	 * @param headers bundle headers
	 */
	public MockBundle(Dictionary headers) {
		this(null, headers, null);
	}

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

	/**
	 * Constructs a new <code>MockBundle</code> instance with the given symbolic name.
	 * 
	 * @param symName bundle symbolic name
	 */
	public MockBundle(String symName) {
		this(symName, null, null);
	}

	/**
	 * Constructs a new <code>MockBundle</code> instance using the given bundle symbolic name, properties and associated
	 * bundle context.
	 * 
	 * @param symName bundle symbolic name
	 * @param headers bundle headers
	 * @param context associated bundle context
	 */
	public MockBundle(String symName, Dictionary headers, BundleContext context) {
		this.symName = ((symName != null && symName.length() > 0) ? symName : SYMBOLIC_NAME);
		defaultHeaders.put("Bundle-SymbolicName", this.symName);

		this.location = "<default location>";
		this.headers = (headers == null ? defaultHeaders : headers);
		this.bundleContext = (context == null ? new MockBundleContext(this) : context);
		this.version = getVersion(headers);
	}

	/**
	 * Delegates to the classloader. Identical to classLoader.getResources(path + filePattern);
	 * 
	 * @see org.osgi.framework.Bundle#findEntries(java.lang.String, java.lang.String, boolean)
	 */
	public Enumeration findEntries(String path, String filePattern, boolean recurse) {
		Enumeration enm = null;

		try {
			enm = loader.getResources(path + "/" + filePattern);
		} catch (IOException ex) {
			// catch to allow nice behavior
			System.err.println("returning an empty enumeration as cannot load resource; exception " + ex);
		}
		return (enm == null ? new EmptyEnumeration() : enm);
	}

	public long getBundleId() {
		return this.bundleId;
	}

	public void setBundleId(long bundleId) {
		this.bundleId = bundleId;
	}

	public URL getEntry(String name) {
		return loader.getResource(name);
	}

	public Enumeration getEntryPaths(String path) {
		return new EmptyEnumeration();
	}

	public Dictionary getHeaders() {
		return headers;
	}

	public Dictionary getHeaders(String locale) {
		return getHeaders();
	}

	public long getLastModified() {
		return 0;
	}

	public String getLocation() {
		return location;
	}

	public ServiceReference[] getRegisteredServices() {
		return new ServiceReference[] {};
	}

	public URL getResource(String name) {
		return loader.getResource(name);
	}

	public Enumeration getResources(String name) throws IOException {
		return loader.getResources(name);
	}

	public ServiceReference[] getServicesInUse() {
		return new ServiceReference[] {};
	}

	public int getState() {
		return Bundle.ACTIVE;
	}

	public String getSymbolicName() {
		String name = (String) headers.get(Constants.BUNDLE_SYMBOLICNAME);
		return (name == null ? SYMBOLIC_NAME : name);
	}

	public boolean hasPermission(Object permission) {
		return true;
	}

	public Class<?> loadClass(String name) throws ClassNotFoundException {
		return loader.loadClass(name);
	}

	public void start() throws BundleException {
		start(0);
	}

	public void start(int options) throws BundleException {
	}

	public void stop() throws BundleException {
		stop(0);
	}

	public void stop(int options) throws BundleException {
	}

	public void uninstall() throws BundleException {
	}

	public void update() throws BundleException {
	}

	public void update(InputStream in) throws BundleException {
	}

	public Version getVersion() {
		return version;
	}

	private static Version getVersion(Dictionary headers) {
		if (headers != null) {
			Object header = headers.get(Constants.BUNDLE_VERSION);
			if (header instanceof String) {
				return Version.parseVersion((String) header);
			}
		}

		return Version.emptyVersion;
	}

	public Map getSignerCertificates(int signerType) {
		return Collections.emptyMap();
	}

	// chiefly here so that compilers/find-bugs don't complain about the
	// "unused" bundleContext field.
	// also enables OsgiResoureUtils.getBundleContext to find the context via
	// reflection
	public BundleContext getContext() {
		return this.bundleContext;
	}

	public BundleContext getBundleContext() {
		return getContext();
	}

	public String toString() {
		return symName;
	}

	/**
	 * Sets the location for this mock bundle.
	 * 
	 * @param location bundle location
	 */
	public void setLocation(String location) {
		this.location = location;
	}

	/**
	 * Sets the class loader internally used by the bundle to mock the loading operations. By default, the MockBundle
	 * uses its own class loader.
	 * 
	 * @param loader mock bundle class loader
	 */
	public void setClassLoader(ClassLoader loader) {
		if (loader == null) {
			throw new IllegalArgumentException("A non-null class loader expected");
		}

		this.loader = loader;
	}
}