/**
 * Copyright (C) 2006 Google Inc.
 *
 * 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.google.inject;

import com.google.common.collect.Iterables;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.Message;

import junit.framework.TestCase;

import java.util.List;

/**
 * @author crazybob@google.com (Bob Lee)
 */
public class ImplicitBindingTest extends TestCase {

  public void testCircularDependency() throws CreationException {
    Injector injector = Guice.createInjector();
    Foo foo = injector.getInstance(Foo.class);
    assertSame(foo, foo.bar.foo);
  }

  static class Foo {
    @Inject Bar bar;
  }

  static class Bar {
    final Foo foo;
    @Inject
    public Bar(Foo foo) {
      this.foo = foo;
    }
  }

  public void testDefaultImplementation() {
    Injector injector = Guice.createInjector();
    I i = injector.getInstance(I.class);
    i.go();
  }

  @ImplementedBy(IImpl.class)
  interface I {
    void go();
  }

  static class IImpl implements I {
    public void go() {}
  }

  static class AlternateImpl implements I {
    public void go() {}
  }

  public void testDefaultProvider() {
    Injector injector = Guice.createInjector();
    Provided provided = injector.getInstance(Provided.class);
    provided.go();
  }

  public void testBindingOverridesImplementedBy() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(I.class).to(AlternateImpl.class);
      }
    });
    assertEquals(AlternateImpl.class, injector.getInstance(I.class).getClass());
  }

  @ProvidedBy(ProvidedProvider.class)
  interface Provided {
    void go();
  }

  public void testNoImplicitBindingIsCreatedForAnnotatedKeys() {
    try {
      Guice.createInjector().getInstance(Key.get(I.class, Names.named("i")));
      fail();
    } catch (ConfigurationException expected) {
      Asserts.assertContains(expected.getMessage(),
          "1) No implementation for " + I.class.getName(),
          "annotated with @" + Named.class.getName() + "(value=i) was bound.",
          "while locating " + I.class.getName(),
          " annotated with @" + Named.class.getName() + "(value=i)");
    }
  }

  static class ProvidedProvider implements Provider<Provided> {
    public Provided get() {
      return new Provided() {
        public void go() {}
      };
    }
  }

  /**
   * When we're building the binding for A, we temporarily insert that binding to support circular
   * dependencies. And so we can successfully create a binding for B. But later, when the binding
   * for A ultimately fails, we need to clean up the dependent binding for B.
   * 
   * The test loops through linked bindings & bindings with constructor & member injections,
   * to make sure that all are cleaned up and traversed.  It also makes sure we don't touch
   * explicit bindings.
   */
  public void testCircularJitBindingsLeaveNoResidue() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(Valid.class);
        bind(Valid2.class);
      }
    });
    
    // Capture good bindings.
    Binding v1 = injector.getBinding(Valid.class);
    Binding v2 = injector.getBinding(Valid2.class);
    Binding jv1 = injector.getBinding(JitValid.class);
    Binding jv2 = injector.getBinding(JitValid2.class);

    // Then validate that a whole series of invalid bindings are erased.
    assertFailure(injector, Invalid.class);
    assertFailure(injector, InvalidLinked.class);
    assertFailure(injector, InvalidLinkedImpl.class);
    assertFailure(injector, InvalidLinked2.class);
    assertFailure(injector, InvalidLinked2Impl.class);
    assertFailure(injector, InvalidProvidedBy.class);
    assertFailure(injector, InvalidProvidedByProvider.class);
    assertFailure(injector, InvalidProvidedBy2.class);
    assertFailure(injector, InvalidProvidedBy2Provider.class);
    assertFailure(injector, Invalid2.class);
    
    // Validate we didn't do anything to the valid explicit bindings.
    assertSame(v1, injector.getBinding(Valid.class));
    assertSame(v2, injector.getBinding(Valid2.class));
    
    // Validate that we didn't erase the valid JIT bindings
    assertSame(jv1, injector.getBinding(JitValid.class));
    assertSame(jv2, injector.getBinding(JitValid2.class));
  }
  
  @SuppressWarnings("unchecked")
  private void assertFailure(Injector injector, Class clazz) {
    try {
      injector.getBinding(clazz);
      fail("Shouldn't have been able to get binding of: " + clazz);
    } catch(ConfigurationException expected) {
      Message msg = Iterables.getOnlyElement(expected.getErrorMessages());
      assertEquals("No implementation for " + InvalidInterface.class.getName() + " was bound.",
          msg.getMessage());
      List<Object> sources = msg.getSources();
      // Assert that the first item in the sources if the key for the class we're looking up,
      // ensuring that each lookup is "new".
      assertEquals(Key.get(clazz).toString(), sources.get(0).toString());
      // Assert that the last item in each lookup contains the InvalidInterface class
      Asserts.assertContains(sources.get(sources.size()-1).toString(),
          Key.get(InvalidInterface.class).toString());
    }
  }

  static class Invalid {
    @Inject Valid a;
    @Inject JitValid b;    
    @Inject InvalidProvidedBy c; 
    @Inject Invalid(InvalidLinked a) {}    
    @Inject void foo(InvalidInterface a) {}
    
  }

  @ImplementedBy(InvalidLinkedImpl.class)
  static interface InvalidLinked {}
  static class InvalidLinkedImpl implements InvalidLinked {
    @Inject InvalidLinked2 a;
  }
  
  @ImplementedBy(InvalidLinked2Impl.class)
  static interface InvalidLinked2 {}
  static class InvalidLinked2Impl implements InvalidLinked2 {
    @Inject InvalidLinked2Impl(Invalid2 a) {}
  }
  
  @ProvidedBy(InvalidProvidedByProvider.class)
  static interface InvalidProvidedBy {}
  static class InvalidProvidedByProvider implements Provider<InvalidProvidedBy> {
    @Inject InvalidProvidedBy2 a;
    public InvalidProvidedBy get() {
      return null;
    }
  }
  
  @ProvidedBy(InvalidProvidedBy2Provider.class)
  static interface InvalidProvidedBy2 {}
  static class InvalidProvidedBy2Provider implements Provider<InvalidProvidedBy2> {
    @Inject Invalid2 a;
    public InvalidProvidedBy2 get() {
      return null;
    }
  }  
  
  static class Invalid2 {
    @Inject Invalid a;
  }

  interface InvalidInterface {}
  
  static class Valid { @Inject Valid2 a; }
  static class Valid2 {}
  
  static class JitValid { @Inject JitValid2 a; }
  static class JitValid2 {}
  
  /**
   * Regression test for https://github.com/google/guice/issues/319
   * 
   * The bug is that a class that asks for a provider for itself during injection time, 
   * where any one of the other types required to fulfill the object creation was bound 
   * in a child constructor, explodes when the injected Provider is called.
   * 
   * It works just fine when the other types are bound in a main injector.
   */  
  public void testInstancesRequestingProvidersForThemselvesWithChildInjectors() {       
    final Module testModule = new AbstractModule() {
      @Override
      protected void configure() {
        bind(String.class)
          .toProvider(TestStringProvider.class);
      }            
    };    
    
    // Verify it works when the type is setup in the parent.
    Injector parentSetupRootInjector = Guice.createInjector(testModule);
    Injector parentSetupChildInjector = parentSetupRootInjector.createChildInjector();
    assertEquals(TestStringProvider.TEST_VALUE, 
        parentSetupChildInjector.getInstance(
            RequiresProviderForSelfWithOtherType.class).getValue());
        
    // Verify it works when the type is setup in the child, not the parent.
    // If it still occurs, the bug will explode here.
    Injector childSetupRootInjector = Guice.createInjector();
    Injector childSetupChildInjector = childSetupRootInjector.createChildInjector(testModule);      
    assertEquals(TestStringProvider.TEST_VALUE, 
        childSetupChildInjector.getInstance(
            RequiresProviderForSelfWithOtherType.class).getValue());
  }
  
  static class TestStringProvider implements Provider<String> {
    static final String TEST_VALUE = "This is to verify it all works";
    
    public String get() {
      return TEST_VALUE;
    }    
  }    
  
  static class RequiresProviderForSelfWithOtherType {
    private final Provider<RequiresProviderForSelfWithOtherType> selfProvider;
    private final String providedStringValue;
    
    @Inject    
    RequiresProviderForSelfWithOtherType(
        String providedStringValue,
        Provider<RequiresProviderForSelfWithOtherType> selfProvider
        ) {
      this.providedStringValue = providedStringValue;
      this.selfProvider = selfProvider;      
    }
    
    public String getValue() {
      // Attempt to get another instance of ourself. This pattern
      // is possible for recursive processing. 
      selfProvider.get();
      
      return providedStringValue;
    }
  }

  /**
   * Ensure that when we cleanup failed JIT bindings, we don't break.
   * The test here requires a sequence of JIT bindings:
   *   A-> B 
   *   B -> C, A
   *   C -> A, D
   *   D not JITable
   * The problem was that C cleaned up A's binding and then handed control back to B,
   * which tried to continue processing A.. but A was removed from the jitBindings Map,
   * so it attempts to create a new JIT binding for A, but we haven't yet finished
   * constructing the first JIT binding for A, so we get a recursive
   * computation exception from ComputingConcurrentHashMap.
   * 
   * We also throw in a valid JIT binding, E, to guarantee that if
   * something fails in this flow, it can be recreated later if it's
   * not from a failed sequence.
   */
  public void testRecursiveJitBindingsCleanupCorrectly() throws Exception {
    Injector injector = Guice.createInjector();
    try {
      injector.getInstance(A.class);
      fail("Expected failure");
    } catch(ConfigurationException expected) {
      Message msg = Iterables.getOnlyElement(expected.getErrorMessages());
      Asserts.assertContains(msg.getMessage(),
          "Could not find a suitable constructor in " + D.class.getName());
    }
    // Assert that we've removed all the bindings.
    assertNull(injector.getExistingBinding(Key.get(A.class)));
    assertNull(injector.getExistingBinding(Key.get(B.class)));
    assertNull(injector.getExistingBinding(Key.get(C.class)));
    assertNull(injector.getExistingBinding(Key.get(D.class)));
    
    // Confirm that we didn't prevent 'E' from working.
    assertNotNull(injector.getBinding(Key.get(E.class)));
  }

  static class A {
    @Inject public A(B b) {}
  }

  static class B {
    @Inject public B(C c, A a) {}
  }

  static class C {
    @Inject public C(A a, D d, E e) {}
  }

  static class D {
    public D(int i) {}
  }
  
  // Valid JITable binding
  static class E { }

  public void testProvidedByNonEmptyEnum() {
    NonEmptyEnum cardSuit = Guice.createInjector().getInstance(NonEmptyEnum.class);

    assertEquals(NonEmptyEnum.HEARTS, cardSuit);
  }

  public void testProvidedByEmptyEnum() {
    EmptyEnum emptyEnumValue = Guice.createInjector().getInstance(EmptyEnum.class);
    assertNull(emptyEnumValue);
  }

  @ProvidedBy(NonEmptyEnumProvider.class)
  enum NonEmptyEnum { HEARTS, DIAMONDS, CLUBS, SPADES }

  static final class NonEmptyEnumProvider implements Provider<NonEmptyEnum> {
    @Override
    public NonEmptyEnum get() {
      return NonEmptyEnum.HEARTS;
    }
  }

  @ProvidedBy(EmptyEnumProvider.class)
  enum EmptyEnum {}

  static final class EmptyEnumProvider implements Provider<EmptyEnum> {
    @Override
    public EmptyEnum get() {
      return null;
    }
  }

  // An enum cannot be implemented by anything, so it should not be possible to have a successful
  // binding when the enum is annotated with @ImplementedBy.
  public void testImplementedByEnum() {
    Injector injector = Guice.createInjector();
    try {
      injector.getInstance(EnumWithImplementedBy.class);
      fail("Expected failure");
    } catch(ConfigurationException expected) {
      Message msg = Iterables.getOnlyElement(expected.getErrorMessages());
      Asserts.assertContains(msg.getMessage(),
          "No implementation for " + EnumWithImplementedBy.class.getName() + " was bound.");
    }
  }

  @ImplementedBy(EnumWithImplementedByEnum.class)
  enum EnumWithImplementedBy {}
  private static class EnumWithImplementedByEnum {}
}
