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

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import junit.framework.TestCase;

import org.eclipse.gemini.blueprint.TestUtils;
import org.eclipse.gemini.blueprint.context.support.BundleContextAwareProcessor;
import org.eclipse.gemini.blueprint.service.exporter.OsgiServiceRegistrationListener;
import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.eclipse.gemini.blueprint.mock.MockBundleContext;
import org.eclipse.gemini.blueprint.mock.MockServiceReference;
import org.eclipse.gemini.blueprint.mock.MockServiceRegistration;

/**
 * Integration test for osgi:service namespace handler.
 * 
 * @author Costin Leau
 * 
 */
public class OsgiServiceNamespaceHandlerTest extends TestCase {

	private GenericApplicationContext appContext;

	private BundleContext bundleContext;

	private final List services = new ArrayList();

	private ServiceRegistration registration;

	protected void setUp() throws Exception {

		services.clear();

		RegistrationListener.BIND_CALLS = 0;
		RegistrationListener.UNBIND_CALLS = 0;

		CustomRegistrationListener.REG_CALLS = 0;
		CustomRegistrationListener.UNREG_CALLS = 0;

		registration = new MockServiceRegistration();

		bundleContext = new MockBundleContext() {

			public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
				return new ServiceReference[] { new MockServiceReference(new String[] { Cloneable.class.getName() }) };
			}

			public ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties) {
				services.add(service);
				return registration;
			}
		};

		appContext = new GenericApplicationContext();
		appContext.getBeanFactory().addBeanPostProcessor(new BundleContextAwareProcessor(bundleContext));

		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
		// reader.setEventListener(this.listener);
		reader.loadBeanDefinitions(new ClassPathResource("osgiServiceNamespaceHandlerTests.xml", getClass()));
		appContext.refresh();
	}

	private Object getServiceAtIndex(int index) {
		Object sFactory = services.get(index);
		assertNotNull(sFactory);
		assertTrue(sFactory instanceof ServiceFactory);
		ServiceFactory fact = (ServiceFactory) sFactory;
		return fact.getService(null, null);
	}

	public void testSimpleService() throws Exception {
		Object bean = appContext.getBean("&inlineReference");
		assertSame(OsgiServiceFactoryBean.class, bean.getClass());
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) bean;

		assertTrue(Arrays.equals(new Class<?>[] { Serializable.class }, getInterfaces(exporter)));
		assertEquals("string", getTargetBeanName(exporter));
		//assertEquals(appContext.getBean("string"), getTarget(exporter));

		assertSame(appContext.getBean("string"), getServiceAtIndex(0));
	}

	public void testBiggerService() throws Exception {
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) appContext.getBean("&manyOptions");

		assertTrue(Arrays.equals(new Class<?>[] { Serializable.class, CharSequence.class }, getInterfaces(exporter)));
		Properties prop = new Properties();
		prop.setProperty("foo", "bar");
		prop.setProperty("white", "horse");
		assertEquals(prop, exporter.getServiceProperties());

		// Should be wrapped with a TCCL setting proxy
		System.out.println(getServiceAtIndex(1));
		assertNotSame(appContext.getBean("string"), getServiceAtIndex(1));

		assertEquals("string", getTargetBeanName(exporter));
		//assertEquals(appContext.getBean("string"), getTarget(exporter));
	}

	public void testNestedService() throws Exception {
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) appContext.getBean("&nestedService");
		assertTrue(Arrays.equals(new Class<?>[] { Object.class }, getInterfaces(exporter)));

		Object service = getServiceAtIndex(2);
		assertSame(HashMap.class, service.getClass());

		assertNull(getTargetBeanName(exporter));
		assertNotNull(getTarget(exporter));
	}

	public void testServiceExporterFactoryBean() throws Exception {
		Object bean = appContext.getBean("nestedService");
		assertTrue(bean instanceof ServiceRegistration);
		assertNotSame("registration not wrapped to provide exporting listener notification", registration, bean);
	}

	public void testServiceProperties() throws Exception {
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) appContext.getBean("&serviceProperties");
		Map properties = exporter.getServiceProperties();
		assertEquals(2, properties.size());
		assertTrue(properties.get("string") instanceof String);
		assertTrue(properties.get("int") instanceof Integer);

		assertNull(getTargetBeanName(exporter));
		assertNotNull(getTarget(exporter));

	}

	public void testListeners() throws Exception {
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) appContext.getBean("&exporterWithListener");
		OsgiServiceRegistrationListener[] listeners = getListeners(exporter);
		assertEquals(2, listeners.length);
	}

	public void testListenersInvoked() throws Exception {
		// registration should have been already called
		assertEquals(2, RegistrationListener.BIND_CALLS);

		Object target = appContext.getBean("exporterWithListener");
		assertTrue(target instanceof ServiceRegistration);

		assertEquals(0, RegistrationListener.UNBIND_CALLS);
		unregister((ServiceRegistration) target);
		assertEquals(2, RegistrationListener.UNBIND_CALLS);
		assertNotNull(RegistrationListener.SERVICE_REG);
		assertNotNull(RegistrationListener.SERVICE_UNREG);
		assertSame(RegistrationListener.SERVICE_REG, RegistrationListener.SERVICE_UNREG);
	}

	public void testFBWithCustomListeners() throws Exception {
		OsgiServiceFactoryBean exporter = (OsgiServiceFactoryBean) appContext.getBean("&exporterWithCustomListener");
		OsgiServiceRegistrationListener[] listeners = getListeners(exporter);
		assertEquals(1, listeners.length);
	}

	public void testCustomListenerInvoked() throws Exception {
		// registration should have been already called (service already
		// published)
		assertEquals(1, CustomRegistrationListener.REG_CALLS);

		Object target = appContext.getBean("exporterWithCustomListener");

		assertTrue(target instanceof ServiceRegistration);
		assertEquals(0, CustomRegistrationListener.UNREG_CALLS);
		unregister((ServiceRegistration) target);
		assertEquals(1, CustomRegistrationListener.UNREG_CALLS);
		// check service instance passed around
		assertSame(appContext.getBean("string"), CustomRegistrationListener.SERVICE_REG);
		assertSame(appContext.getBean("string"), CustomRegistrationListener.SERVICE_UNREG);
	}

	private OsgiServiceRegistrationListener[] getListeners(OsgiServiceFactoryBean exporter) {
		return (OsgiServiceRegistrationListener[]) TestUtils.getFieldValue(exporter, "listeners");
	}

	private Class<?>[] getInterfaces(OsgiServiceFactoryBean exporter) {
		return (Class[]) TestUtils.getFieldValue(exporter, "interfaces");
	}

	private String getTargetBeanName(OsgiServiceFactoryBean exporter) {
		return (String) TestUtils.getFieldValue(exporter, "targetBeanName");
	}

	private Object getTarget(OsgiServiceFactoryBean exporter) {
		return TestUtils.getFieldValue(exporter, "target");
	}

	private void unregister(ServiceRegistration target) throws Exception {
		Field fld = target.getClass().getDeclaredField("delegate");
		fld.setAccessible(true);
		ServiceRegistration reg = (ServiceRegistration) fld.get(target);
		reg.unregister();
	}
}