/*
 * Copyright 2015 Patrick Jungermann
 *
 * Licensed 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 com.github.pjungermann.config.types;

import com.github.pjungermann.config.Config;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static org.junit.Assert.*;

/**
 * Tests for {@link DefaultConfigFactorySelector}.
 *
 * @author Patrick Jungermann
 */
public class DefaultConfigFactorySelectorTest {

    @Test
    public void beanCreation_withSpringContextAndNoFactory_noErrorAndEmptyListAsDefault() {
        StaticApplicationContext context = new StaticApplicationContext();
        context.registerSingleton(DefaultConfigFactorySelector.class.getName(), DefaultConfigFactorySelector.class);
        ConfigFactorySelector selector = context.getBean(ConfigFactorySelector.class);

        assertValidBean(selector);
    }

    @Test
    public void beanCreation_withGuiceAndNoFactory_noErrorAndEmptyListAsDefault() {
        Module module = new AbstractModule() {
            @Override
            protected void configure() {
                bind(ConfigFactorySelector.class)
                        .to(DefaultConfigFactorySelector.class);
                // multi binder without implementations
                Multibinder.newSetBinder(binder(), ConfigFactory.class);
            }
        };
        Provider<ConfigFactorySelector> provider =
                Guice.createInjector(module)
                        .getProvider(ConfigFactorySelector.class);
        ConfigFactorySelector selector = provider.get();

        assertValidBean(selector);
    }

    @Test
    public void getFactory_withoutFactories_returnNull() {
        DefaultConfigFactorySelector selector = new DefaultConfigFactorySelector();
        assertNull(selector.getFactory(new File("foo.bar")));
    }

    @Test
    public void getFactory_withFactories_returnFirstApplicableFactory() {
        ConfigFactory factory1 = new TestConfigFactory(false);
        ConfigFactory factory2 = new TestConfigFactory(true);
        ConfigFactory factory3 = new TestConfigFactory(true);
        Set<ConfigFactory> factories = new HashSet<>(3);
        factories.add(factory1);
        factories.add(factory2);
        factories.add(factory3);
        assertEquals(3, factories.size());

        DefaultConfigFactorySelector selector = new DefaultConfigFactorySelector();
        selector.setConfigFactories(factories);

        ConfigFactory result = selector.getFactory(new File("foo.bar"));

        assertSame(factory2, result);
    }

    @Test
    public void getFactory_withFactoriesButNoMatchingOne_returnNull() {
        ConfigFactory factory1 = new TestConfigFactory(false);
        ConfigFactory factory2 = new TestConfigFactory(false);
        ConfigFactory factory3 = new TestConfigFactory(false);
        Set<ConfigFactory> factories = new HashSet<>(3);
        factories.add(factory1);
        factories.add(factory2);
        factories.add(factory3);
        assertEquals(3, factories.size());

        DefaultConfigFactorySelector selector = new DefaultConfigFactorySelector();
        selector.setConfigFactories(factories);

        ConfigFactory result = selector.getFactory(new File("foo.bar"));

        assertNull(result);
    }

    @Test
    public void setFactories_null_useEmptySet() {
        // nullable annotation had to be used for optional binding in Guice
        DefaultConfigFactorySelector selector = new DefaultConfigFactorySelector();
        //noinspection ConstantConditions
        assertTrue(getConfigFactories(selector).isEmpty());

        selector.setConfigFactories(null);

        //noinspection ConstantConditions
        assertTrue(getConfigFactories(selector).isEmpty());
    }

    static void assertValidBean(ConfigFactorySelector selector) {
        assertTrue(selector instanceof DefaultConfigFactorySelector);
        assertNull(selector.getFactory(new File("foo.bar")));
        Collection<ConfigFactory> configFactories = getConfigFactories((DefaultConfigFactorySelector) selector);
        //noinspection ConstantConditions
        assertTrue(configFactories.isEmpty());
    }

    @SuppressWarnings("unchecked")
    static Collection<ConfigFactory> getConfigFactories(DefaultConfigFactorySelector selector) {
        try {
            Field listField = DefaultConfigFactorySelector.class.getDeclaredField("configFactories");
            listField.setAccessible(true);
            return (Collection<ConfigFactory>) listField.get(selector);

        } catch (NoSuchFieldException e) {
            fail("field not found: " + e.getMessage());

        } catch (IllegalAccessException e) {
            fail("field was not accessible: " + e.getMessage());
        }

        return null;
    }

    static class TestConfigFactory implements ConfigFactory {
        final boolean supported;

        TestConfigFactory(boolean supported) {
            this.supported = supported;
        }

        @Override
        public boolean supports(@NotNull File source) {
            return supported;
        }

        @NotNull
        @Override
        public Config create(@NotNull File source, @Nullable String profile, @NotNull Config context) throws IOException {
            return new Config();
        }
    }

}

@@ -1139,16 +1139,41 @@
 .Field;%0A
+import java.util.Arrays;%0A
 import j
@@ -1189,24 +1189,24 @@
 Collection;%0A
-
 import java.
@@ -2861,13 +2861,8 @@
 turn
-First
 Appl
@@ -3491,13 +3491,22 @@
 sert
-Same(
+OneOf(result, 
 fact
@@ -3511,22 +3511,24 @@
 ctory2, 
-result
+factory3
 );%0A    %7D
@@ -4244,32 +4244,76 @@
    %7D%0A%0A    @Test%0A
+    @SuppressWarnings(%22ConstantConditions%22)%0A
     public void 
@@ -4510,50 +4510,8 @@
 ();%0A
-        //noinspection ConstantConditions%0A
     
@@ -4615,50 +4615,8 @@
 );%0A%0A
-        //noinspection ConstantConditions%0A
     
@@ -4670,32 +4670,404 @@
 mpty());%0A    %7D%0A%0A
+    static void assertOneOf(Object result, Object... instances) %7B%0A        for (Object instance : instances) %7B%0A            try %7B%0A                assertSame(instance, result);%0A                return;%0A%0A            %7D catch (AssertionError ignore) %7B%0A            %7D%0A        %7D%0A%0A        fail(%22expected one of:%3C%22 + Arrays.toString(instances) + %22%3E but was:%3C%22 + result + %22%3E%22);%0A    %7D%0A%0A
     static void 

make test more stable, order is not guaranteed anymore due to being a set instead of the ArrayList and there is no order (yet)