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

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.eclipse.gemini.blueprint.config.internal.AbstractReferenceDefinitionParser;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.Mergeable;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReferenceFactoryBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;

/**
 * Internal class used for adapting Spring's bean definition to OSGi Blueprint metadata. Used by {@link MetadataFactory}
 * which acts as a facade.
 * 
 * @author Adrian Colyer
 * @author Costin Leau
 */
class ComponentMetadataFactory implements MetadataConstants {

	private static final String BEAN_REF_FB_CLASS_NAME = BeanReferenceFactoryBean.class.getName();
	private static final String GENERATED_REF = AbstractReferenceDefinitionParser.GENERATED_REF;
	private static final String PROMOTED_REF = AbstractReferenceDefinitionParser.PROMOTED_REF;
	private static final String REGEX =
			"\\.org\\.springframework\\.osgi\\.service\\.importer\\.support\\.OsgiService(?:Collection)*ProxyFactoryBean#\\d+#\\d+";
	private static final Pattern PATTERN = Pattern.compile(REGEX);
	private static final String GENERATED_END = "#generated";
	private static final String GENERATED_START = ".org.eclipse.gemini.blueprint.service.importer.support.OsgiService";
	private static final String GENERATED_MIDDLE = "ProxyFactoryBean#";

	/**
	 * Builds a component metadata from the given bean definition.
	 * 
	 * @param name bean name
	 * @param beanDefinition
	 * @return
	 */
	static ComponentMetadata buildMetadata(String name, BeanDefinition beanDefinition) {
		// shortcut (to avoid re-re-wrapping)
		Object metadata = beanDefinition.getAttribute(COMPONENT_METADATA_ATTRIBUTE);
		if (metadata instanceof ComponentMetadata)
			return (ComponentMetadata) metadata;

		// if no name has been given, look for one
		if (name == null) {
			name = (String) beanDefinition.getAttribute(COMPONENT_NAME);
		}

		if (isServiceExporter(beanDefinition)) {
			return new SimpleServiceExportComponentMetadata(name, beanDefinition);
		}

		if (isSingleServiceImporter(beanDefinition)) {
			return new SimpleReferenceMetadata(name, beanDefinition);
		}
		if (isCollectionImporter(beanDefinition)) {
			return new SimpleReferenceListMetadata(name, beanDefinition);
		}

		BeanDefinition original = unwrapImporterReference(beanDefinition);
		if (original != null) {
			return buildMetadata(null, original);
		}

		if (isEnvironmentManager(beanDefinition)) {
			return new EnvironmentManagerMetadata(name);
		}

		return new SimpleBeanMetadata(name, beanDefinition);
	}

	private static boolean isServiceExporter(BeanDefinition beanDefinition) {
		return checkBeanDefinitionClassCompatibility(beanDefinition, EXPORTER_CLASS);
	}

	private static boolean isSingleServiceImporter(BeanDefinition beanDefinition) {
		return checkBeanDefinitionClassCompatibility(beanDefinition, SINGLE_SERVICE_IMPORTER_CLASS);
	}

	private static boolean isCollectionImporter(BeanDefinition beanDefinition) {
		return checkBeanDefinitionClassCompatibility(beanDefinition, MULTI_SERVICE_IMPORTER_CLASS);
	}

	static BeanDefinition unwrapImporterReference(BeanDefinition beanDefinition) {
		if (BEAN_REF_FB_CLASS_NAME.equals(beanDefinition.getBeanClassName())) {
			// check special DM case of nested mandatory
			// references being promoted to top level beans
			if (beanDefinition instanceof AbstractBeanDefinition) {
				AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDefinition;
				if (abd.isSynthetic() && abd.hasAttribute(GENERATED_REF)) {
					BeanDefinition actual = abd.getOriginatingBeanDefinition();
					return actual;
				}
			}
		}

		return null;
	}

	private static boolean isEnvironmentManager(BeanDefinition beanDefinition) {
		return checkBeanDefinitionClassCompatibility(beanDefinition, ENV_FB_CLASS);
	}

	private static boolean checkBeanDefinitionClassCompatibility(BeanDefinition definition, Class<?> clazz) {
		if (definition instanceof AbstractBeanDefinition) {
			AbstractBeanDefinition abstractDefinition = (AbstractBeanDefinition) definition;
			if (abstractDefinition.hasBeanClass()) {
				Class<?> beanClass = abstractDefinition.getBeanClass();
				return clazz.isAssignableFrom(beanClass);
			}
		}
		return (clazz.getName().equals(definition.getBeanClassName()));
	}

	static Collection<ComponentMetadata> buildNestedMetadata(BeanDefinition beanDefinition) {
		List<ComponentMetadata> col = new ArrayList<ComponentMetadata>(4);
		processBeanDefinition(beanDefinition, col);
		// remove the first definition
		col.remove(0);
		return col;
	}

	private static void processBeanMetadata(BeanMetadataElement metadata, Collection<ComponentMetadata> to) {
		if (metadata instanceof BeanDefinition) {
			processBeanDefinition((BeanDefinition) metadata, to);
		}

		else if (metadata instanceof BeanDefinitionHolder) {
			BeanDefinitionHolder bh = (BeanDefinitionHolder) metadata;
			processBeanDefinition(bh.getBeanDefinition(), to);
		}

		else if (metadata instanceof Mergeable && metadata instanceof Iterable) {
			processIterable((Iterable) metadata, to);
		}
	}

	private static void processBeanDefinition(BeanDefinition definition, Collection<ComponentMetadata> to) {
		to.add(buildMetadata(null, definition));

		// start with constructors
		ConstructorArgumentValues cavs = definition.getConstructorArgumentValues();
		// generic values
		List<ValueHolder> genericValues = cavs.getGenericArgumentValues();
		for (ValueHolder valueHolder : genericValues) {
			Object value = MetadataUtils.getValue(valueHolder);
			if (value instanceof BeanMetadataElement) {
				processBeanMetadata((BeanMetadataElement) value, to);
			}
		}
		// indexed ones
		Map<Integer, ValueHolder> indexedValues = cavs.getIndexedArgumentValues();
		for (ValueHolder valueHolder : indexedValues.values()) {
			Object value = MetadataUtils.getValue(valueHolder);
			if (value instanceof BeanMetadataElement) {
				processBeanMetadata((BeanMetadataElement) value, to);
			}
		}

		// now property values
		PropertyValues pvs = definition.getPropertyValues();
		for (PropertyValue pv : pvs.getPropertyValues()) {
			Object value = MetadataUtils.getValue(pv);
			if (value instanceof BeanMetadataElement) {
				processBeanMetadata((BeanMetadataElement) value, to);
			}
		}
	}

	private static void processIterable(Iterable iterableMetadata, Collection<ComponentMetadata> to) {
		for (Object value : iterableMetadata) {
			if (value instanceof BeanMetadataElement) {
				processBeanMetadata((BeanMetadataElement) value, to);
			}
		}
	}

	public static List<ComponentMetadata> buildComponentMetadataFor(ConfigurableListableBeanFactory factory) {
		List<ComponentMetadata> metadata = new ArrayList<ComponentMetadata>();
		String[] components = factory.getBeanDefinitionNames();

		for (String beanName : components) {
			BeanDefinition definition = factory.getBeanDefinition(beanName);

			// filter generated definitions
			if (!definition.hasAttribute(PROMOTED_REF)) {
				// add metadata for top-level definitions
				metadata.add(MetadataFactory.buildComponentMetadataFor(beanName, definition));
				// look for nested ones
				metadata.addAll(MetadataFactory.buildNestedComponentMetadataFor(definition));
			}
		}

		return metadata;
	}

	// eliminate the names of promoted importers
	public static Set<String> filterIds(Set<String> components) {
		// search for pattern "
		// .org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean#N#N and
		// .org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceCollectionProxyFactoryBean#N#N

		Set<String> filtered = new LinkedHashSet<String>(components.size());

		for (String string : components) {
			if (!(string.startsWith(GENERATED_START) && string.endsWith(GENERATED_END) && string
					.contains(GENERATED_MIDDLE))) {
				filtered.add(string);
			}
		}

		return filtered;
	}
}