package com.google.inject.testing.throwingproviders;

import static com.google.common.truth.Truth.assertAbout;

import com.google.common.truth.FailureStrategy;
import com.google.common.truth.Subject;
import com.google.common.truth.SubjectFactory;
import com.google.common.truth.ThrowableSubject;
import com.google.inject.throwingproviders.CheckedProvider;
import javax.annotation.Nullable;

/**
 * Truth {@link Subject} for use with {@link CheckedProvider} classes.
 *
 * @author eatnumber1@google.com (Russ Harmon)
 */
public final class CheckedProviderSubject<T, P extends CheckedProvider<T>>
    extends Subject<CheckedProviderSubject<T, P>, P> {

  private static final class CheckedProviderSubjectFactory<T, P extends CheckedProvider<T>>
      extends SubjectFactory<CheckedProviderSubject<T, P>, P> {
    @Override
    public CheckedProviderSubject<T, P> getSubject(
        FailureStrategy failureStrategy, @Nullable P target) {
      return new CheckedProviderSubject<T, P>(failureStrategy, target);
    }
  }

  public static <T, P extends CheckedProvider<T>>
      SubjectFactory<CheckedProviderSubject<T, P>, P> checkedProviders() {
    return new CheckedProviderSubjectFactory<>();
  }

  public static <T, P extends CheckedProvider<T>> CheckedProviderSubject<T, P> assertThat(
      @Nullable P provider) {
    return assertAbout(CheckedProviderSubject.<T, P>checkedProviders()).that(provider);
  }

  private CheckedProviderSubject(FailureStrategy failureStrategy, @Nullable P subject) {
    super(failureStrategy, subject);
  }

  /**
   * Allows for assertions on the value provided by this provider.
   *
   * <p>The value provided by a checked provider is the object returned by a call to {@link
   * CheckedProvider#get}
   *
   * @return a {@link Subject} for asserting against the return value of {@link CheckedProvider#get}
   */
  public Subject<?, Object> providedValue() {
    P provider = actual();
    T got;
    try {
      got = provider.get();
    } catch (Exception e) {
      failWithRawMessageAndCause(
          String.format("checked provider <%s> threw an exception", provider), e);
      return ignoreCheck().that(new Object());
    }
    return check().withMessage("value provided by <%s>", provider).that(got);
  }

  /**
   * Allows for assertions on the exception thrown by this provider.
   *
   * <p>The exception thrown by a checked provider is the {@link Throwable} thrown by a call to
   * {@link CheckedProvider#get}
   *
   * @return a {@link ThrowableSubject} for asserting against the {@link Throwable} thrown by {@link
   *     CheckedProvider#get}
   */
  public ThrowableSubject thrownException() {
    P provider = actual();
    T got;
    try {
      got = provider.get();
    } catch (Throwable e) {
      return check().withMessage("exception thrown by <%s>", provider).that(e);
    }
    failWithBadResults("threw", "an exception", "provided", got);
    return ignoreCheck().that(new Throwable());
  }
}

@@ -143,24 +143,24 @@
 .Failure
-Strategy
+Metadata
 ;%0Aimport
@@ -197,55 +197,8 @@
 ct;%0A
-import com.google.common.truth.SubjectFactory;%0A
 impo
@@ -691,30 +691,33 @@
 r%3CT%3E%3E%0A      
-extend
+implement
 s SubjectFac
@@ -705,32 +705,33 @@
 plements Subject
+.
 Factory%3CCheckedP
@@ -811,19 +811,22 @@
 t%3CT, P%3E 
-get
+create
 Subject(
@@ -845,32 +845,32 @@
 lure
-Strategy failureStrategy
+Metadata failureMetadata
 , @N
@@ -942,24 +942,24 @@
 (failure
-Strategy
+Metadata
 , target
@@ -1031,24 +1031,25 @@
      Subject
+.
 Factory%3CChec
@@ -1411,32 +1411,32 @@
 lure
-Strategy failureStrategy
+Metadata failureMetadata
 , @N
@@ -1477,16 +1477,16 @@
 lure
-Strategy
+Metadata
 , su

Replace truth.FailureStrategy with truth.FailureMetadata in custom Subjects.