/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.felix.dm.itest.api;

import java.util.Map;
import java.util.Properties;

import org.apache.felix.dm.Component;
import org.apache.felix.dm.Component.ServiceScope;
import org.apache.felix.dm.ComponentState;
import org.apache.felix.dm.ComponentStateListener;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.itest.util.Ensure;
import org.apache.felix.dm.itest.util.TestBase;
import org.junit.Assert;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceRegistration;

/**
 * Validates a simple scoped service, which does not add some dynamic dependencies with a component init method.
 */
public class ScopedServiceTest extends TestBase implements ComponentStateListener {
	final static Ensure m_e = new Ensure();
	final Ensure.Steps m_listenerSteps = new Ensure.Steps(1, 4, 11);
	final static Ensure.Steps m_serviceImplStartSteps = new Ensure.Steps(2, 5, 12);
	final static Ensure.Steps m_serviceImplStopSteps = new Ensure.Steps(8, 10, 16);
    final Ensure.Steps m_serviceConsumerBindSteps = new Ensure.Steps(3, 6, 13);
    final Ensure.Steps m_serviceConsumerUnbindSteps = new Ensure.Steps(7, 9, 15);

    public void testPrototypeComponent() {
        DependencyManager m = getDM();     
        
        Component provider = m.createComponent()
        	.setScope(ServiceScope.PROTOTYPE)
        	.setAutoConfig(Bundle.class, false)
        	.setAutoConfig(ServiceRegistration.class, false)
        	.setImplementation(ServiceImpl.class)
            .setInterface(Service.class.getName(), null)
            .add(m.createServiceDependency().setRequired(true).setService(Service2.class).setCallbacks("bind", null))
        	.add(this);
        
        Component service2 = m.createComponent()
        		.setInterface(Service2.class.getName(), null)
        		.setImplementation(new Service2() {});
        
        Component consumer1 = m.createComponent()
            .setFactory(this, "createServiceConsumer")
            .add(m.createServiceDependency().setService(Service.class).setRequired(true).setCallbacks("bind", "change", "unbind"));
        
        Component consumer2 = m.createComponent()
            .setFactory(this, "createServiceConsumer")
            .add(m.createServiceDependency().setService(Service.class).setRequired(true).setCallbacks("bind", "unbind"));
                
        m.add(provider);          // add provider
        m.add(consumer1);         // add first consumer
        m.add(service2);          // add service2 (the provider depends on it)
        m_e.waitForStep(1, 5000); // our listener has seen the first clone starting
        m_e.waitForStep(2, 5000); // first clone started
        m_e.waitForStep(3, 5000); // first consumer bound to the first clone

        m.add(consumer2);         // add second consumer
        m_e.waitForStep(4, 5000); // our listener has seen the second clone starting
        m_e.waitForStep(5, 5000); // second clone started.
        m_e.waitForStep(6, 5000); // second consumer bound to the second clone

        // make sure both consumers have a different provider instances.
        ServiceConsumer consumer1Impl = (ServiceConsumer) consumer1.getInstance();
        Assert.assertNotNull(consumer1Impl.getService());
        ServiceConsumer consumer2Impl = (ServiceConsumer) consumer2.getInstance();
        Assert.assertNotNull(consumer2Impl.getService());
        Assert.assertNotEquals(consumer1Impl.getService(), consumer2Impl.getService());
        
        m.remove(consumer1); // remove consumer1
        m_e.waitForStep(7, 5000); // consumer1 unbound from first clone
        m_e.waitForStep(8, 5000); // first clone stopped
        
        m.remove(provider); // unregister the provider
        m_e.waitForStep(9, 5000); // consumer2 unbound from second clone
        m_e.waitForStep(10, 5000); // second clone stopped
        m.remove(consumer2);
        
        m.add(provider); // re-register the provider
        m.add(consumer1); // re-add the consumer1
        m_e.waitForStep(11, 5000); // our listener has seen the third clone starting
        m_e.waitForStep(12, 5000); // third clone started
        m_e.waitForStep(13, 5000); // consumer1 bound to the first clone
        
        Properties props = new Properties();
        props.put("foo", "bar");
        provider.setServiceProperties(props); // update provider service properties
        m_e.waitForStep(14, 5000); // consumer1 should be called in its change callback
        
        m.remove(service2); // remove the service2 (it will remove the provider
        m_e.waitForStep(15, 5000); // consumer1 stopped
        m_e.waitForStep(16, 5000); // third clone stopped

        m.clear();
    }
    
    @SuppressWarnings("unused")
    public ServiceConsumer createServiceConsumer() {
        return new ServiceConsumer();
    }
    
	@Override
	public void changed(Component c, ComponentState state) {
		if (state == ComponentState.STARTING) {
			Assert.assertEquals(ServiceImpl.class, c.getInstance().getClass());
			m_e.steps(m_listenerSteps); // will enter in step 1, 4, 11
		}		
	}
    
    public interface Service { 
    }
    
    public interface Service2 { 
    }
        
    public static class ServiceImpl implements Service {
        volatile Bundle m_bundle; // bundle requesting the service
        volatile ServiceRegistration<Service> m_registration; // registration of the requested service
		volatile Service2 m_service2;
		
		public ServiceImpl(Bundle bundle, ServiceRegistration<Service> registration) {
			m_bundle = bundle;
			m_registration = registration;
		}
		
        void bind(Service2 service2) {
        	m_service2 = service2;
        }

        void start() {
        	Assert.assertNotNull(m_bundle);
        	Assert.assertNotNull(m_registration);
        	Assert.assertNotNull(m_service2);
        	m_e.steps(m_serviceImplStartSteps); // 2, 5, 12
        }
        
        void stop() {
        	m_e.steps(m_serviceImplStopSteps); // 8, 10, 16
        }
    }
    
    public class ServiceConsumer {
        volatile Service m_myService;

        public void bind(Service service) {
            m_myService = service;
        	m_e.steps(m_serviceConsumerBindSteps); // 3, 6, 13
        }
        
        public void change(Service service, Map<String, Object> properties) {
        	Assert.assertEquals("bar", properties.get("foo"));
        	m_e.step(14);
        }
        
        public void unbind(Service service) {
        	Assert.assertEquals(m_myService, service);
        	m_e.steps(m_serviceConsumerUnbindSteps); // 7, 9, 15
        }
        
        public Service getService() {
            return m_myService;
        }
    }
}
