/******************************************************************************
 * 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.extender.internal.blueprint.event;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.blueprint.container.BlueprintEvent;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.eclipse.gemini.blueprint.util.OsgiBundleUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.springframework.util.ObjectUtils;

/**
 * Actual {@link EventAdmin} dispatcher. Implemented as a package-protected generic class that can be further configured
 * by the user-facing classes. The additional benefit is that being a separate class, this class can be used only when
 * the org.osgi.service.event package is available, w/o triggering runtime class loading.
 * 
 * @author Costin Leau
 */
class OsgiEventDispatcher implements EventDispatcher, BlueprintConstants {

	/** logger */
	private static final Log log = LogFactory.getLog(OsgiEventDispatcher.class);

	private static final String EVENT_ADMIN = "org.osgi.service.event.EventAdmin";

	private final BundleContext bundleContext;
	private final PublishType publisher;

	public OsgiEventDispatcher(BundleContext bundleContext, PublishType publisher) {
		this.bundleContext = bundleContext;
		this.publisher = publisher;
	}

	public void afterClose(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		sendEvent(new Event(TOPIC_DESTROYED, props));
	}

	public void afterRefresh(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		sendEvent(new Event(TOPIC_CREATED, props));
	}

	public void beforeClose(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		sendEvent(new Event(TOPIC_DESTROYING, props));
	}

	public void beforeRefresh(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		sendEvent(new Event(TOPIC_CREATING, props));
	}

	public void refreshFailure(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);

		Throwable th = event.getCause();
		props.put(EXCEPTION, th);
		props.put(CAUSE, th);
		props.put(EXCEPTION_CLASS, th.getClass().getName());
		String msg = th.getMessage();
		props.put(EXCEPTION_MESSAGE, (msg != null ? msg : ""));
		initDependencies(props, event);
		sendEvent(new Event(TOPIC_FAILURE, props));
	}

	public void grace(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		initDependencies(props, event);
		sendEvent(new Event(TOPIC_GRACE, props));
	}

	public void waiting(BlueprintEvent event) {
		Dictionary<String, Object> props = init(event);
		initDependencies(props, event);
		sendEvent(new Event(TOPIC_WAITING, props));
	}

	private void initDependencies(Dictionary<String, Object> props, BlueprintEvent event) {
		String[] deps = event.getDependencies();
		if (!ObjectUtils.isEmpty(deps)) {
			props.put(DEPENDENCIES, deps);
			// props.put(SERVICE_FILTER, deps[0]);
			// props.put(SERVICE_FILTER_2, deps[0]);
			// props.put(SERVICE_OBJECTCLASS, extractObjectClassFromFilter(deps[0]));
			props.put(ALL_DEPENDENCIES, deps);
		}
	}

	private Dictionary<String, Object> init(BlueprintEvent event) {
		Dictionary<String, Object> props = new Hashtable<String, Object>();

		Bundle bundle = event.getBundle();

		// common properties
		props.put(TIMESTAMP, System.currentTimeMillis());
		props.put(EVENT, event);
		props.put(TYPE, Integer.valueOf(event.getType()));

		props.put(BUNDLE, event.getBundle());
		props.put(BUNDLE_ID, bundle.getBundleId());

		// name (under two keys)
		String name = OsgiStringUtils.nullSafeName(bundle);
		props.put(BUNDLE_NAME, name);
		props.put(Constants.BUNDLE_NAME, name);

		// sym name (under two keys)
		String symName = OsgiStringUtils.nullSafeSymbolicName(bundle);
		props.put(BUNDLE_SYM_NAME, symName);
		props.put(Constants.BUNDLE_SYMBOLICNAME, symName);

		// version (as well under two keys)
		Version version = OsgiBundleUtils.getBundleVersion(bundle);
		props.put(BUNDLE_VERSION, version);
		props.put(Constants.BUNDLE_VERSION, version);

		// extender bundle info
		Bundle extenderBundle = event.getExtenderBundle();

		props.put(EXTENDER_BUNDLE, extenderBundle);
		props.put(EXTENDER_BUNDLE_ID, extenderBundle.getBundleId());
		props.put(EXTENDER_BUNDLE_SYM_NAME, extenderBundle.getSymbolicName());
		Version extenderVersion = OsgiBundleUtils.getBundleVersion(extenderBundle);
		props.put(EXTENDER_BUNDLE_VERSION, extenderVersion);

		return props;
	}

	private void sendEvent(Event osgiEvent) {
		boolean trace = log.isTraceEnabled();
		ServiceReference ref = bundleContext.getServiceReference(EVENT_ADMIN);
		if (ref != null) {
			EventAdmin eventAdmin = (EventAdmin) bundleContext.getService(ref);
			if (eventAdmin != null) {
				if (trace) {
					StringBuilder sb = new StringBuilder();
					String[] names = osgiEvent.getPropertyNames();
					sb.append("{");
					for (int i = 0; i < names.length; i++) {
						String name = names[i];
						sb.append(name);
						sb.append("=");
						Object value = osgiEvent.getProperty(name);
						sb.append(ObjectUtils.getDisplayString(value));
						if (i < names.length - 1)
							sb.append(",");
					}
					sb.append("}");

					log.trace("Broadcasting OSGi event " + osgiEvent + " w/ props " + sb.toString());
				}
				publisher.publish(eventAdmin, osgiEvent);
			}
		} else {
			log.trace("No event admin found for broadcasting event " + osgiEvent);
		}
	}
}