/******************************************************************************
 * 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.activator;

import java.util.Collection;

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.service.blueprint.container.BlueprintContainer;
import org.osgi.service.blueprint.container.BlueprintEvent;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.eclipse.gemini.blueprint.blueprint.container.SpringBlueprintContainer;
import org.eclipse.gemini.blueprint.blueprint.container.SpringBlueprintConverter;
import org.eclipse.gemini.blueprint.blueprint.container.SpringBlueprintConverterService;
import org.eclipse.gemini.blueprint.blueprint.container.support.BlueprintContainerServicePublisher;
import org.eclipse.gemini.blueprint.context.BundleContextAware;
import org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEvent;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextListener;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextFailedEvent;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextRefreshedEvent;
import org.eclipse.gemini.blueprint.extender.event.BootstrappingDependenciesEvent;
import org.eclipse.gemini.blueprint.extender.event.BootstrappingDependenciesFailedEvent;
import org.eclipse.gemini.blueprint.extender.internal.activator.OsgiContextProcessor;
import org.eclipse.gemini.blueprint.extender.internal.blueprint.event.EventAdminDispatcher;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitStartingEvent;
import org.springframework.util.ClassUtils;

/**
 * Blueprint specific context processor.
 * 
 * @author Costin Leau
 */
public class BlueprintContainerProcessor implements
		OsgiBundleApplicationContextListener<OsgiBundleApplicationContextEvent>, OsgiContextProcessor {

	/** logger */
	private static final Log log = LogFactory.getLog(BlueprintContainerProcessor.class);
	private static final Class<?> ENV_FB_CLASS;

	static {
		String className = "org.eclipse.gemini.blueprint.blueprint.reflect.internal.metadata.EnvironmentManagerFactoryBean";
		ClassLoader loader = OsgiBundleApplicationContextEvent.class.getClassLoader();
		ENV_FB_CLASS = ClassUtils.resolveClassName(className, loader);
	}

	private final EventAdminDispatcher dispatcher;
	private final BlueprintListenerManager listenerManager;
	private final Bundle extenderBundle;
	private final BeanFactoryPostProcessor cycleBreaker;

	class BlueprintWaitingEventDispatcher implements ApplicationListener<ApplicationEvent> {
		private final BundleContext bundleContext;
		private volatile boolean enabled = true;
		private volatile boolean initialized = false;

		BlueprintWaitingEventDispatcher(BundleContext context) {
			this.bundleContext = context;
		}

		// WAITING event
		public void onApplicationEvent(ApplicationEvent event) {
			if (event instanceof ContextClosedEvent) {
				enabled = false;
				return;
			}

			if (event instanceof ContextRefreshedEvent) {
				initialized = true;
				return;
			}

			if (event instanceof OsgiServiceDependencyWaitStartingEvent) {
				if (enabled) {
					OsgiServiceDependencyWaitStartingEvent evt = (OsgiServiceDependencyWaitStartingEvent) event;
					String[] filter = new String[] { evt.getServiceDependency().getServiceFilter().toString() };
					BlueprintEvent waitingEvent =
							new BlueprintEvent(BlueprintEvent.WAITING, bundleContext.getBundle(), extenderBundle,
									filter);

					listenerManager.blueprintEvent(waitingEvent);
					dispatcher.waiting(waitingEvent);
				}
				return;
			}
		}
	};

	public BlueprintContainerProcessor(EventAdminDispatcher dispatcher, BlueprintListenerManager listenerManager,
			Bundle extenderBundle) {
		this.dispatcher = dispatcher;
		this.listenerManager = listenerManager;
		this.extenderBundle = extenderBundle;

		Class<?> processorClass =
				ClassUtils.resolveClassName(
						"org.eclipse.gemini.blueprint.blueprint.container.support.internal.config.CycleOrderingProcessor",
						BundleContextAware.class.getClassLoader());

		cycleBreaker = (BeanFactoryPostProcessor) BeanUtils.instantiate(processorClass);
	}

	public void postProcessClose(ConfigurableOsgiBundleApplicationContext context) {
		BlueprintEvent destroyedEvent =
				new BlueprintEvent(BlueprintEvent.DESTROYED, context.getBundle(), extenderBundle);

		listenerManager.blueprintEvent(destroyedEvent);
		dispatcher.afterClose(destroyedEvent);
	}

	public void postProcessRefresh(ConfigurableOsgiBundleApplicationContext context) {
		BlueprintEvent createdEvent = new BlueprintEvent(BlueprintEvent.CREATED, context.getBundle(), extenderBundle);

		listenerManager.blueprintEvent(createdEvent);
		dispatcher.afterRefresh(createdEvent);
	}

	public void postProcessRefreshFailure(ConfigurableOsgiBundleApplicationContext context, Throwable th) {
		BlueprintEvent failureEvent =
				new BlueprintEvent(BlueprintEvent.FAILURE, context.getBundle(), extenderBundle, th);

		listenerManager.blueprintEvent(failureEvent);
		dispatcher.refreshFailure(failureEvent);
	}

	public void preProcessClose(ConfigurableOsgiBundleApplicationContext context) {
		BlueprintEvent destroyingEvent =
				new BlueprintEvent(BlueprintEvent.DESTROYING, context.getBundle(), extenderBundle);

		listenerManager.blueprintEvent(destroyingEvent);
		dispatcher.beforeClose(destroyingEvent);
	}

	public void preProcessRefresh(final ConfigurableOsgiBundleApplicationContext context) {
		final BundleContext bundleContext = context.getBundleContext();
		// create the ModuleContext adapter
		final BlueprintContainer blueprintContainer = createBlueprintContainer(context);

		// 1. add event listeners
		// add service publisher
		context.addApplicationListener(new BlueprintContainerServicePublisher(blueprintContainer, bundleContext));
		// add waiting event broadcaster
		context.addApplicationListener(new BlueprintWaitingEventDispatcher(context.getBundleContext()));

		// 2. add environmental managers
		context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {

			private static final String BLUEPRINT_BUNDLE = "blueprintBundle";
			private static final String BLUEPRINT_BUNDLE_CONTEXT = "blueprintBundleContext";
			private static final String BLUEPRINT_CONTAINER = "blueprintContainer";
			private static final String BLUEPRINT_EXTENDER = "blueprintExtenderBundle";
			private static final String BLUEPRINT_CONVERTER = "blueprintConverter";

			public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
				// lazy logger evaluation
				Log logger = LogFactory.getLog(context.getClass());

				if (!(beanFactory instanceof BeanDefinitionRegistry)) {
					logger.warn("Environmental beans will be registered as singletons instead "
							+ "of usual bean definitions since beanFactory " + beanFactory
							+ " is not a BeanDefinitionRegistry");
				}

				// add blueprint container bean
				addPredefinedBlueprintBean(beanFactory, BLUEPRINT_BUNDLE, bundleContext.getBundle(), logger);
				addPredefinedBlueprintBean(beanFactory, BLUEPRINT_BUNDLE_CONTEXT, bundleContext, logger);
				addPredefinedBlueprintBean(beanFactory, BLUEPRINT_CONTAINER, blueprintContainer, logger);
				// addPredefinedBlueprintBean(beanFactory, BLUEPRINT_EXTENDER, extenderBundle, logger);
				addPredefinedBlueprintBean(beanFactory, BLUEPRINT_CONVERTER, new SpringBlueprintConverter(beanFactory),
						logger);

				// add Blueprint conversion service
				// String[] beans = beanFactory.getBeanNamesForType(BlueprintConverterConfigurer.class, false, false);
				// if (ObjectUtils.isEmpty(beans)) {
				// beanFactory.addPropertyEditorRegistrar(new BlueprintEditorRegistrar());
				// }
				beanFactory.setConversionService(new SpringBlueprintConverterService(
						beanFactory.getConversionService(), beanFactory));
			}

			private void addPredefinedBlueprintBean(ConfigurableListableBeanFactory beanFactory, String beanName,
					Object value, Log logger) {
				if (!beanFactory.containsLocalBean(beanName)) {
					logger.debug("Registering pre-defined bean named " + beanName);
					if (beanFactory instanceof BeanDefinitionRegistry) {
						BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

						GenericBeanDefinition def = new GenericBeanDefinition();
						def.setBeanClass(ENV_FB_CLASS);
						ConstructorArgumentValues cav = new ConstructorArgumentValues();
						cav.addIndexedArgumentValue(0, value);
						def.setConstructorArgumentValues(cav);
						def.setLazyInit(false);
						def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
						registry.registerBeanDefinition(beanName, def);

					} else {
						beanFactory.registerSingleton(beanName, value);
					}

				} else {
					logger.warn("A bean named " + beanName
							+ " already exists; aborting registration of the predefined value...");
				}
			}
		});

		// 3. add cycle breaker
		context.addBeanFactoryPostProcessor(cycleBreaker);

		BlueprintEvent creatingEvent = new BlueprintEvent(BlueprintEvent.CREATING, context.getBundle(), extenderBundle);
		listenerManager.blueprintEvent(creatingEvent);
		dispatcher.beforeRefresh(creatingEvent);
	}

	private BlueprintContainer createBlueprintContainer(ConfigurableOsgiBundleApplicationContext context) {
		// return new ExceptionHandlingBlueprintContainer(context, bundleContext);
		return new SpringBlueprintContainer(context);
	}

	public void onOsgiApplicationEvent(OsgiBundleApplicationContextEvent evt) {

		// grace event
		if (evt instanceof BootstrappingDependenciesEvent) {
			BootstrappingDependenciesEvent event = (BootstrappingDependenciesEvent) evt;
			Collection<String> flts = event.getDependencyFilters();
			if (flts.isEmpty()) {
				if (log.isDebugEnabled()) {
					log.debug("All dependencies satisfied, not sending Blueprint GRACE event "
							+ "with emtpy dependencies from " + event);
				}
			} else {
				String[] filters = flts.toArray(new String[flts.size()]);
				BlueprintEvent graceEvent =
						new BlueprintEvent(BlueprintEvent.GRACE_PERIOD, evt.getBundle(), extenderBundle, filters);
				listenerManager.blueprintEvent(graceEvent);
				dispatcher.grace(graceEvent);
			}

			return;
		}

		// bootstrapping failure
		if (evt instanceof BootstrappingDependenciesFailedEvent) {
			BootstrappingDependenciesFailedEvent event = (BootstrappingDependenciesFailedEvent) evt;
			Collection<String> flts = event.getDependencyFilters();
			String[] filters = flts.toArray(new String[flts.size()]);
			BlueprintEvent failureEvent =
					new BlueprintEvent(BlueprintEvent.FAILURE, evt.getBundle(), extenderBundle, filters, event
							.getFailureCause());
			listenerManager.blueprintEvent(failureEvent);
			dispatcher.refreshFailure(failureEvent);
			return;
		}

		// created
		if (evt instanceof OsgiBundleContextRefreshedEvent) {
			postProcessRefresh((ConfigurableOsgiBundleApplicationContext) evt.getApplicationContext());
			return;
		}

		// failure
		if (evt instanceof OsgiBundleContextFailedEvent) {
			OsgiBundleContextFailedEvent failureEvent = (OsgiBundleContextFailedEvent) evt;
			postProcessRefreshFailure(
					((ConfigurableOsgiBundleApplicationContext) failureEvent.getApplicationContext()), failureEvent
							.getFailureCause());
			return;
		}
	}
}