package tc.oc.commons.core.inject;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import javax.inject.Named;

import com.google.common.collect.Lists;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import org.junit.Test;
import tc.oc.test.InjectedTestCase;

import static org.junit.Assert.*;
import static tc.oc.test.Assert.*;

public class InjectableMethodTest extends InjectedTestCase {

    @Test
    public void constant() throws Exception {
        class Woot {
            String foo() {
                return "hi";
            }
        }

        InjectableMethod<?> method = InjectableMethod.forDeclaredMethod(new Woot(), "foo");
        String hi = Guice.createInjector(method.bindingModule()).getInstance(String.class);

        assertEquals("hi", hi);
    }

    @Test
    public void voidReturnType() throws Exception {
        class Woot {
            void foo() {}
        }

        InjectableMethod<?> method = InjectableMethod.forDeclaredMethod(new Woot(), "foo");
        assertThrows(CreationException.class, () ->
            Guice.createInjector(method.bindingModule())
        );
    }

    @Test
    public void dependencies() throws Exception {
        class Woot {
            String foo(int i, short s) {
                return i + " " + s;
            }
        }

        InjectableMethod<?> method = InjectableMethod.forDeclaredMethod(new Woot(), "foo", int.class, short.class);
        String hi = Guice.createInjector(
            method.bindingModule(),
            binder -> {
                binder.bind(int.class).toInstance(123);
                binder.bind(short.class).toInstance((short) 456);
            }
        ).getInstance(String.class);

        assertEquals("123 456", hi);
    }

    @Test
    public void qualifiedReturnType() throws Exception {
        class Woot {
            @Named("q") String foo() {
                return "hi";
            }
        }

        InjectableMethod<?> method = InjectableMethod.forDeclaredMethod(new Woot(), "foo");
        String hi = Guice.createInjector(method.bindingModule()).getInstance(Key.get(String.class, Names.named("q")));

        assertEquals("hi", hi);
    }

    @Test
    public void qualifiedDependency() throws Exception {
        class Woot {
            String foo(@Named("q") int i) {
                return String.valueOf(i);
            }
        }

        InjectableMethod<?> method = InjectableMethod.forDeclaredMethod(new Woot(), "foo", int.class);
        String hi = Guice.createInjector(
            method.bindingModule(),
            binder -> binder.bind(int.class)
                            .annotatedWith(Names.named("q"))
                            .toInstance(123)
        ).getInstance(String.class);


        assertEquals("123", hi);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface Yay {}

    @Test
    public void scanForMethods() throws Exception {
        class Woot {
            @Yay String foo() {
                return "foo";
            }

            String bar() {
                return "bar";
            }
        }

        List<InjectableMethod<?>> methods = InjectableMethod.forAnnotatedMethods(TypeLiteral.get(Woot.class), new Woot(), Yay.class);
        Injector injector = Guice.createInjector(Lists.transform(methods, InjectableMethod::bindingModule));

        assertEquals("foo", injector.getInstance(String.class));
    }
}
