/******************************************************************************
 * 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.Collection;
import java.util.List;

import org.eclipse.gemini.blueprint.service.exporter.support.DefaultInterfaceDetector;
import org.osgi.service.blueprint.reflect.BeanArgument;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.BeanProperty;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.blueprint.reflect.ReferenceListMetadata;
import org.osgi.service.blueprint.reflect.ReferenceMetadata;
import org.osgi.service.blueprint.reflect.ServiceMetadata;
import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
import org.osgi.service.blueprint.reflect.Target;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.util.StringUtils;

/**
 * Adapter factory that allows translating OSGi Blueprint metadata into Spring's {@link BeanDefinition}.
 * 
 * @author Costin Leau
 */
class BeanDefinitionFactory implements MetadataConstants {

	BeanDefinition buildBeanDefinitionFor(ComponentMetadata metadata) {
		// shortcut (to avoid re-rewrapping)
		if (metadata instanceof SimpleComponentMetadata) {
			return ((SimpleComponentMetadata) metadata).getBeanDefinition();
		}

		AbstractBeanDefinition definition = buildBeanDefinition(metadata);
		// add common properties
		definition.setAttribute(MetadataConstants.COMPONENT_METADATA_ATTRIBUTE, metadata);
		definition.setAttribute(MetadataConstants.COMPONENT_NAME, metadata.getId());

		// Set<String> dependencies = metadata.;
		// definition.setDependsOn(dependencies.toArray(new String[dependencies.size()]));
		throw new UnsupportedOperationException("move depends on for BeanMetadata");

		// return definition;
	}

	private AbstractBeanDefinition buildBeanDefinition(ComponentMetadata metadata) {

		if (metadata instanceof BeanMetadata) {
			return buildLocalComponent((BeanMetadata) metadata);
		}

		if (metadata instanceof ServiceMetadata) {
			return buildExporter((ServiceMetadata) metadata);
		}

		if (metadata instanceof ServiceReferenceMetadata) {
			if (metadata instanceof ReferenceListMetadata) {
				return buildReferenceCollection((ReferenceListMetadata) metadata);
			}
			if (metadata instanceof ReferenceMetadata) {
				return buildReferenceProxy((ReferenceMetadata) metadata);
			}
		}

		// unknown rich metadata type, unable to perform conversion
		throw new IllegalArgumentException("Unknown metadata type" + metadata.getClass());
	}

	@SuppressWarnings("unchecked")
	private AbstractBeanDefinition buildLocalComponent(BeanMetadata metadata) {
		// add basic definition properties
		BeanDefinitionBuilder builder =
				BeanDefinitionBuilder.genericBeanDefinition(metadata.getClassName()).setInitMethodName(
						metadata.getInitMethod()).setDestroyMethodName(metadata.getDestroyMethod()).setLazyInit(
						getLazy(metadata)).setScope(metadata.getScope());

		// add factory-method/factory-bean
		String factoryMethod = metadata.getFactoryMethod();
		if (StringUtils.hasText(factoryMethod)) {
			builder.setFactoryMethod(factoryMethod);
			Target factory = metadata.getFactoryComponent();
			if (factory != null) {
				builder.getRawBeanDefinition().setFactoryBeanName(((ComponentMetadata) factory).getId());
			}
		}

		// add constructor props
		List<BeanArgument> beanArguments = metadata.getArguments();
		ConstructorArgumentValues cargs = builder.getRawBeanDefinition().getConstructorArgumentValues();

		for (BeanArgument arg : beanArguments) {
			int index = arg.getIndex();
			Object val = BeanMetadataElementFactory.buildBeanMetadata(arg.getValue());
			if (index > -1) {
				cargs.addGenericArgumentValue(val, arg.getValueType());
			} else {
				cargs.addIndexedArgumentValue(index, val, arg.getValueType());
			}

		}

		// add property values
		Collection<BeanProperty> props = metadata.getProperties();
		MutablePropertyValues pvs = new MutablePropertyValues();

		for (BeanProperty injection : props) {
			pvs.addPropertyValue(injection.getName(), BeanMetadataElementFactory
					.buildBeanMetadata(injection.getValue()));
		}

		return builder.getBeanDefinition();
	}

	private boolean getLazy(ComponentMetadata metadata) {
		return (metadata.getActivation() == ComponentMetadata.ACTIVATION_LAZY ? true : false);
	}

	private AbstractBeanDefinition buildExporter(ServiceMetadata metadata) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(EXPORTER_CLASS);
		// add properties
		builder.addPropertyValue(EXPORTER_RANKING_PROP, metadata.getRanking());
		builder.addPropertyValue(EXPORTER_INTFS_PROP, metadata.getInterfaces());
		builder.addPropertyValue(EXPORTER_PROPS_PROP, metadata.getServiceProperties());
		builder.addPropertyValue(EXPORTER_AUTO_EXPORT_PROP,
				DefaultInterfaceDetector.values()[metadata.getAutoExport() - 1]);

		BeanMetadataElement beanMetadata = BeanMetadataElementFactory.buildBeanMetadata(metadata.getServiceComponent());
		if (beanMetadata instanceof RuntimeBeanReference) {
			builder.addPropertyValue(EXPORTER_TARGET_BEAN_NAME_PROP, beanMetadata);
		} else {
			builder.addPropertyValue(EXPORTER_TARGET_BEAN_PROP, beanMetadata);
		}

		// FIXME: add registration listeners

		return builder.getBeanDefinition();
	}

	private AbstractBeanDefinition buildReferenceCollection(ReferenceListMetadata metadata) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MULTI_SERVICE_IMPORTER_CLASS);
		addServiceReferenceProperties(metadata, builder);
		throw new UnsupportedOperationException("not implemented yet");
		// return builder.getBeanDefinition();
	}

	private AbstractBeanDefinition buildReferenceProxy(ReferenceMetadata metadata) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SINGLE_SERVICE_IMPORTER_CLASS);
		addServiceReferenceProperties(metadata, builder);
		throw new UnsupportedOperationException("not implemented yet");
		// return builder.getBeanDefinition();
	}

	private void addServiceReferenceProperties(ServiceReferenceMetadata referenceMetadata, BeanDefinitionBuilder builder) {
		builder.addPropertyValue(IMPORTER_FILTER_PROP, referenceMetadata.getFilter());
		builder.addPropertyValue(IMPORTER_INTFS_PROP, referenceMetadata.getInterface());
		// FIXME: add binding listeners
	}
}