/**
 * Copyright (C) 2009 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 static com.google.inject.Asserts.assertContains;

import com.google.inject.name.Names;
import com.google.inject.util.Providers;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @author jessewilson@google.com (Jesse Wilson)
 */
public class MembersInjectorTest extends TestCase {

  private static final A<C> uninjectableA = new A<C>() {
    @Override void doNothing() {
      throw new AssertionFailedError();
    }
  };

  private static final B uninjectableB = new B() {
    @Override void doNothing() {
      throw new AssertionFailedError();
    }
  };

  private static final C myFavouriteC = new C();

  public void testMembersInjectorFromBinder() {
    final AtomicReference<MembersInjector<A<C>>> aMembersInjectorReference
        = new AtomicReference<MembersInjector<A<C>>>();
    final AtomicReference<MembersInjector<B>> bMembersInjectorReference
        = new AtomicReference<MembersInjector<B>>();

    Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        MembersInjector<A<C>> aMembersInjector = getMembersInjector(new TypeLiteral<A<C>>() {});
        try {
          aMembersInjector.injectMembers(uninjectableA);
          fail();
        } catch (IllegalStateException expected) {
          assertContains(expected.getMessage(),
              "This MembersInjector cannot be used until the Injector has been created.");
        }

        MembersInjector<B> bMembersInjector = getMembersInjector(B.class);
        try {
          bMembersInjector.injectMembers(uninjectableB);
          fail();
        } catch (IllegalStateException expected) {
          assertContains(expected.getMessage(),
              "This MembersInjector cannot be used until the Injector has been created.");
        }

        aMembersInjectorReference.set(aMembersInjector);
        bMembersInjectorReference.set(bMembersInjector);

        assertEquals("MembersInjector<java.lang.String>",
            getMembersInjector(String.class).toString());

        bind(C.class).toInstance(myFavouriteC);
      }
    });

    A<C> injectableA = new A<C>();
    aMembersInjectorReference.get().injectMembers(injectableA);
    assertSame(myFavouriteC, injectableA.t);
    assertSame(myFavouriteC, injectableA.b.c);

    B injectableB = new B();
    bMembersInjectorReference.get().injectMembers(injectableB);
    assertSame(myFavouriteC, injectableB.c);

    B anotherInjectableB = new B();
    bMembersInjectorReference.get().injectMembers(anotherInjectableB);
    assertSame(myFavouriteC, anotherInjectableB.c);
  }

  public void testMembersInjectorFromInjector() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      protected void configure() {
        bind(C.class).toInstance(myFavouriteC);
      }
    });

    MembersInjector<A<C>> aMembersInjector
        = injector.getMembersInjector(new TypeLiteral<A<C>>() {});
    MembersInjector<B> bMembersInjector = injector.getMembersInjector(B.class);

    A<C> injectableA = new A<C>();
    aMembersInjector.injectMembers(injectableA);
    assertSame(myFavouriteC, injectableA.t);
    assertSame(myFavouriteC, injectableA.b.c);

    B injectableB = new B();
    bMembersInjector.injectMembers(injectableB);
    assertSame(myFavouriteC, injectableB.c);

    B anotherInjectableB = new B();
    bMembersInjector.injectMembers(anotherInjectableB);
    assertSame(myFavouriteC, anotherInjectableB.c);

    assertEquals("MembersInjector<java.lang.String>",
        injector.getMembersInjector(String.class).toString());
  }

  public void testMembersInjectorWithNonInjectedTypes() {
    Injector injector = Guice.createInjector();

    MembersInjector<NoInjectedMembers> membersInjector
        = injector.getMembersInjector(NoInjectedMembers.class);

    membersInjector.injectMembers(new NoInjectedMembers());
    membersInjector.injectMembers(new NoInjectedMembers());
  }

  public void testInjectionFailure() {
    Injector injector = Guice.createInjector();

    MembersInjector<InjectionFailure> membersInjector
        = injector.getMembersInjector(InjectionFailure.class);

    try {
      membersInjector.injectMembers(new InjectionFailure());
      fail();
    } catch (ProvisionException expected) {
      assertContains(expected.getMessage(),
          "1) Error injecting method, java.lang.ClassCastException: whoops, failure #1");
    }
  }

  public void testInjectionAppliesToSpecifiedType() {
    Injector injector = Guice.createInjector();

    MembersInjector<Object> membersInjector = injector.getMembersInjector(Object.class);
    membersInjector.injectMembers(new InjectionFailure());
  }

  public void testInjectingMembersInjector() {
    InjectsMembersInjector injectsMembersInjector = Guice.createInjector(new AbstractModule() {
      protected void configure() {
        bind(C.class).toInstance(myFavouriteC);
      }
    }).getInstance(InjectsMembersInjector.class);

    A<C> a = new A<C>();
    injectsMembersInjector.aMembersInjector.injectMembers(a);
    assertSame(myFavouriteC, a.t);
    assertSame(myFavouriteC, a.b.c);
  }

  public void testCannotBindMembersInjector() {
    try {
      Guice.createInjector(new AbstractModule() {
        protected void configure() {
          bind(MembersInjector.class).toProvider(Providers.<MembersInjector>of(null));
        }
      });
      fail();
    } catch (CreationException expected) {
      assertContains(expected.getMessage(),
          "1) Binding to core guice framework type is not allowed: MembersInjector.");
    }

    try {
      Guice.createInjector(new AbstractModule() {
        protected void configure() {
          bind(new TypeLiteral<MembersInjector<A<C>>>() {})
              .toProvider(Providers.<MembersInjector<A<C>>>of(null));
        }
      });
      fail();
    } catch (CreationException expected) {
      assertContains(expected.getMessage(),
          "1) Binding to core guice framework type is not allowed: MembersInjector.");
    }
  }

  public void testInjectingMembersInjectorWithErrorsInDependencies() {
    try {
      Guice.createInjector().getInstance(InjectsBrokenMembersInjector.class);
      fail();
    } catch (ConfigurationException expected) {
      assertContains(expected.getMessage(),
          "1) No implementation for " + Unimplemented.class.getName() + " was bound.",
          "while locating " + Unimplemented.class.getName(),
          "for field at " + A.class.getName() + ".t(MembersInjectorTest.java:",
          "while locating com.google.inject.MembersInjector<",
          "for field at " + InjectsBrokenMembersInjector.class.getName() + ".aMembersInjector(",
          "while locating " + InjectsBrokenMembersInjector.class.getName());
    }
  }

  public void testLookupMembersInjectorBinding() {
    Injector injector = Guice.createInjector(new AbstractModule() {
      protected void configure() {
        bind(C.class).toInstance(myFavouriteC);
      }
    });
    MembersInjector<A<C>> membersInjector =
        injector.getInstance(new Key<MembersInjector<A<C>>>() {});

    A<C> a = new A<C>();
    membersInjector.injectMembers(a);
    assertSame(myFavouriteC, a.t);
    assertSame(myFavouriteC, a.b.c);

    assertEquals("MembersInjector<java.lang.String>",
        injector.getInstance(new Key<MembersInjector<String>>() {}).toString());
  }

  public void testGettingRawMembersInjector() {
    Injector injector = Guice.createInjector();
    try {
      injector.getInstance(MembersInjector.class);
      fail();
    } catch (ConfigurationException expected) {
      assertContains(expected.getMessage(),
          "Cannot inject a MembersInjector that has no type parameter");
    }
  }

  public void testGettingAnnotatedMembersInjector() {
    Injector injector = Guice.createInjector();
    try {
      injector.getInstance(new Key<MembersInjector<String>>(Names.named("foo")) {});
      fail();
    } catch (ConfigurationException expected) {
      assertContains(expected.getMessage(),
          "1) No implementation for com.google.inject.MembersInjector<java.lang.String> "
              + "annotated with @com.google.inject.name.Named(value=foo) was bound.");
    }
  }

  static class A<T> {
    @Inject B b;
    @Inject T t;
    @Inject void doNothing() {}
  }

  static class B {
    @Inject C c;
    @Inject void doNothing() {}
  }

  static class C {}

  static class NoInjectedMembers {}

  static class InjectionFailure {
    int failures = 0;

    @Inject void fail() {
      throw new ClassCastException("whoops, failure #" + (++failures));
    }
  }

  static class InjectsMembersInjector {
    @Inject MembersInjector<A<C>> aMembersInjector;
    @Inject A<B> ab;
  }

  static class InjectsBrokenMembersInjector {
    @Inject MembersInjector<A<Unimplemented>> aMembersInjector;
  }

  static interface Unimplemented {}
}
