/*
 * Copyright (c) 2012, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name "TwelveMonkeys" nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.util.service;

import com.twelvemonkeys.util.CollectionUtil;
import org.junit.Test;

import java.util.*;

import static org.junit.Assert.*;

/**
 * ServiceRegistryTest
 *
 * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
 * @author last modified by $Author: haraldk$
 * @version $Id: ServiceRegistryTest.java,v 1.0 25.01.12 16:16 haraldk Exp$
 */
public class ServiceRegistryTest {

    private final TestRegistry registry = new TestRegistry();

    @Test(expected = IllegalArgumentException.class)
    public void testCreateNull() {
        new ServiceRegistry(null);
    }

    @Test
    public void testCreateEmptyIterator() {
        // A completely useless registry...
        ServiceRegistry registry = new ServiceRegistry(Collections.<Class<?>>emptyList().iterator());
        registry.registerApplicationClasspathSPIs();

        while (registry.categories().hasNext()) {
            fail("No categories");
        }
    }

    @Test(expected = ServiceConfigurationError.class)
    public void testCreateBadConfig() {
        @SuppressWarnings("unchecked") 
        ServiceRegistry registry = new ServiceRegistry(Arrays.asList(BadSPI.class).iterator());
        registry.registerApplicationClasspathSPIs();

        // DONE: Test non-class

        // TODO: Test class not implementing SPI category
        // TODO: Test class that throws exception in constructor
        // TODO: Test class that has no public no-args constructor
        // TODO: Test IOException
        // Some of these can be tested using stubs, via the package protected registerSPIs method
    }

    @Test
    public void testCategories() {
        // Categories
        Iterator<Class<?>> categories = registry.categories();
        assertTrue(categories.hasNext());
        Class<?> category = categories.next();
        assertEquals(DummySPI.class, category);
        assertFalse(categories.hasNext());
    }

    @Test
    public void testProviders() {
        // Providers
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        List<DummySPI> providerList = new ArrayList<DummySPI>();
        CollectionUtil.addAll(providerList, providers);

        assertEquals(2, providerList.size());

        // Order should be as in configuration file
        assertNotNull(providerList.get(0));
        assertEquals(DummySPIImpl.class, providerList.get(0).getClass());
        assertNotNull(providerList.get(1));
        assertEquals(DummySPIToo.class, providerList.get(1).getClass());
    }

    @Test
    public void testCompatibleCategoriesNull() {
        // Compatible categories
        Iterator<Class<?>> categories = registry.compatibleCategories(null);
        assertFalse(categories.hasNext());
    }

    @Test
    public void testCompatibleCategoriesImpl() {
        Iterator<Class<?>> categories = registry.compatibleCategories(new DummySPIImpl());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testCompatibleCategoriesToo() {
        Iterator<Class<?>> categories = registry.compatibleCategories(new DummySPIToo());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testCompatibleCategoriesNonRegistered() {
        Iterator<Class<?>> categories = registry.compatibleCategories(new DummySPI() {});
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testCompatibleCategoriesUnknownType() {
        Iterator<Class<?>> categories = registry.compatibleCategories(new Object());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesNull() {
        // Containing categories
        Iterator<Class<?>> categories = registry.containingCategories(null);
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesKnownInstanceImpl() {
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        assertTrue(providers.hasNext()); // Sanity check

        Iterator<Class<?>> categories = registry.containingCategories(providers.next());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesKnownInstanceToo() {
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        providers.next();
        assertTrue(providers.hasNext()); // Sanity check

        Iterator<Class<?>> categories = registry.containingCategories(providers.next());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesNewInstanceRegisteredImpl() {
        // NOTE: Currently we match based on type, rather than instance, but it does make sense...
        Iterator<Class<?>> categories = registry.containingCategories(new DummySPIImpl());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesNewInstanceRegisteredToo() {
        // NOTE: Currently we match based on type, rather than instance, but it does make sense...
        Iterator<Class<?>> categories = registry.containingCategories(new DummySPIToo());
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesCompatibleNonRegisteredType() {
        Iterator<Class<?>> categories = registry.containingCategories(new DummySPI() {});
        assertFalse(categories.hasNext());
    }

    @Test
    public void testContainingCategoriesUnknownType() {
        Iterator<Class<?>> categories = registry.containingCategories(new Object());
        assertFalse(categories.hasNext());
    }

    @Test
    public void testRegister() {
        // Register
        DummySPI dummy = new DummySPI() {};
        assertTrue(registry.register(dummy));

        // Should now have category
        Iterator<Class<?>> categories = registry.containingCategories(dummy);
        assertTrue(categories.hasNext());
        assertEquals(DummySPI.class, categories.next());
        assertFalse(categories.hasNext());

        // Should now be in providers
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        List<DummySPI> providerList = new ArrayList<DummySPI>();
        CollectionUtil.addAll(providerList, providers);

        assertEquals(3, providerList.size());

        assertNotNull(providerList.get(1));
        assertSame(dummy, providerList.get(2));
    }

    @Test
    public void testRegisterAlreadyRegistered() {
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        assertTrue(providers.hasNext()); // Sanity check

        assertFalse(registry.register(providers.next()));
    }

    @Test
    public void testRegisterNull() {
        assertFalse(registry.register(null));
    }

    @Test
    public void testRegisterIncompatible() {
        assertFalse(registry.register(new Object()));
    }
    
    @Test
    public void testDeregisterNull() {
        assertFalse(registry.deregister(null));
    }

    @Test
    public void testDeregisterIncompatible() {
        assertFalse(registry.deregister(new Object()));
    }

    @Test
    public void testDeregisterCompatibleNonRegistered() {
        DummySPI dummy = new DummySPI() {};
        assertFalse(registry.deregister(dummy));
    }

    @Test
    public void testDeregister() {
        Iterator<DummySPI> providers = registry.providers(DummySPI.class);
        assertTrue(providers.hasNext()); // Sanity check
        DummySPI instance = providers.next();
        assertTrue(registry.deregister(instance));

        // Test no longer in registry
        providers = registry.providers(DummySPI.class);
        int count = 0;
        while (providers.hasNext()) {
            DummySPI next = providers.next();
            assertNotSame(instance, next);
            count++;
        }

        assertEquals(1, count);
    }

    // TODO: Test register with category
    // TODO: Test register with unknown category
    // TODO: Test register with null category

    // TODO: Test de-register with category
    // TODO: Test de-register with unknown category
    // TODO: Test de-register with null category


    private static class TestRegistry extends ServiceRegistry {
        @SuppressWarnings("unchecked")
        public TestRegistry() {
            super(Arrays.asList(DummySPI.class).iterator());
            registerApplicationClasspathSPIs();
        }
    }
    
    public static class BadSPI {}
}
