package test;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.*;

import javax.inject.Inject;
import javax.inject.Provider;

import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations;

import bullet.ObjectGraph;
import dagger.Component;
import dagger.Module;
import dagger.Provides;

public class MembersInjectionTest {

  // XXX: Dagger‡ doesn't support @Inject on abstract methods, so we cannot make I and I2 interfaces
  static abstract class I {
    E e;
    @Inject void setE(E e) { this.e = e; }
  }
  static abstract class I2 extends I {
    C c;
    @Inject void setC(C c) { this.c = c; }
  }
  static final class A extends I {
    @Inject A() {}
  }
  static class B {
    @Inject A a;
  }
  static final class C extends B {
    @Inject Provider<I> iProvider;
    @Inject C() {}
  }
  static final class D extends I2 {
  }
  static final class E {
    @Inject E() {}
  }

  // XXX: interface must be public for Mockito ForwardsInvocations to work
  @Component(modules = SimpleModule.class)
  public interface SimpleComponent {
    void i(I i);
    void i2(I2 i1);
    void a(A a);
    void b(B b);
    void c(C c);
    void d(D d);
  }

  @Module
  static class SimpleModule {
    @Provides I provideI(A a) { return a; }
  }

  SimpleComponent component;
  ObjectGraph graph;

  @Before public void setUp() {
    // We cannot spy the Dagger‡ component as it's final, so we wrap it in a mock that delegates to it.
    // We want to test both that the method is called (mockito) and that everything actually works (dagger).
    SimpleComponent realComponent = DaggerMembersInjectionTest_SimpleComponent.create();
    this.component = mock(SimpleComponent.class, new ForwardsInvocations(realComponent));
    graph = new BulletMembersInjectionTest_SimpleComponent(component);
  }

  @Test public void testAnyI() {
    I i = new I() {};
    assertThat(graph.inject(i)).isSameAs(i);
    verify(component).i(i);
    assertThat(i.e).isNotNull();
    verifyNoMoreInteractions(component);
  }

  @Test public void testAny2() {
    I2 i2 = new I2() {};
    assertThat(graph.inject(i2)).isSameAs(i2);
    verify(component).i2(i2);
    assertThat(i2.c).isNotNull();
    assertThat(i2.e).isNotNull();
    verifyNoMoreInteractions(component);
  }

  @Test public void testA() {
    A a = new A();
    assertThat(graph.inject(a)).isSameAs(a);
    verify(component).a(a);
    assertThat(a.e).isNotNull();
    verifyNoMoreInteractions(component);
  }

  @Test public void testB() {
    B b = new B();
    assertThat(graph.inject(b)).isSameAs(b);
    verify(component).b(b);
    assertThat(b.a).isNotNull();
    verifyNoMoreInteractions(component);
  }

  @Test public void testC() {
    C c = new C();
    assertThat(graph.inject(c)).isSameAs(c);
    verify(component).c(c);
    assertThat(c.a).isNotNull();
    assertThat(c.iProvider).isNotNull();
    verifyNoMoreInteractions(component);
  }

  @Test public void testD() {
    D d = new D();
    assertThat(graph.inject(d)).isSameAs(d);
    verify(component).d(d);
    assertThat(d.c).isNotNull();
    assertThat(d.e).isNotNull();
    verifyNoMoreInteractions(component);
  }
}