/*
 * Copyright (c) 2014 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.common.truth;

import javax.annotation.Nullable;

/**
 * Propositions for {@link Throwable} subjects.
 *
 * @author Kurt Alfred Kluever
 */
public final class ThrowableSubject extends Subject<ThrowableSubject, Throwable> {
  static ThrowableSubject create(FailureStrategy failureStrategy, @Nullable Throwable throwable) {
    return new ThrowableSubject(causeInsertingStrategy(failureStrategy, throwable), throwable);
  }

  private ThrowableSubject(FailureStrategy failureStrategy, @Nullable Throwable throwable) {
    super(failureStrategy, throwable);
  }

  /*
   * TODO(cpovirk): consider a special case for isEqualTo and isSameAs that adds |expected| as a
   * suppressed exception
   */

  /**
   * Fails if the subject does not have the given message.
   *
   * @deprecated Use {@code hasMessageThat().isEqualTo(expected)} instead. You may also consider
   *     using inexact matching of the message (e.g. {@code hasMessageThat().contains(substring)})
   *     for less brittle tests.
   */
  @Deprecated
  public void hasMessage(@Nullable String expected) {
    hasMessageThat().isEqualTo(expected);
  }

  /** Returns a {@code StringSubject} to make assertions about the throwable's message. */
  public StringSubject hasMessageThat() {
    return new StringSubject(badMessageStrategy(failureStrategy), actual().getMessage());
  }

  /**
   * Returns a new {@code ThrowableSubject} that supports assertions on this throwable's direct
   * cause. This method can be invoked repeatedly (e.g. {@code
   * assertThat(e).hasCauseThat().hasCauseThat()....} to assert on a particular indirect cause.
   */
  public ThrowableSubject hasCauseThat() {
    // provides a more helpful error message if hasCauseThat() methods are chained too deep
    // e.g. assertThat(new Exception()).hCT().hCT()....
    if (actual() == null) {
      failWithRawMessage("Causal chain is not deep enough - add a .isNotNull() check?");
    }
    return new ThrowableSubject(badCauseStrategy(failureStrategy), actual().getCause());
  }

  private static FailureStrategy causeInsertingStrategy(
      final FailureStrategy delegate, final Throwable defaultCause) {
    return new FailureStrategy() {
      @Override
      public void fail(String message) {
        delegate.fail(message, defaultCause);
      }

      @Override
      public void fail(String message, Throwable cause) {
        delegate.fail(message, cause);
        // TODO(cpovirk): add defaultCause as a suppressed exception? If fail() throws...
      }

      @Override
      public void failComparing(String message, CharSequence expected, CharSequence actual) {
        delegate.failComparing(message, expected, actual, defaultCause);
      }

      @Override
      public void failComparing(
          String message, CharSequence expected, CharSequence actual, Throwable cause) {
        delegate.failComparing(message, expected, actual, cause);
        // TODO(cpovirk): add defaultCause as a suppressed exception? If failComparing() throws...
      }
    };
  }

  private FailureStrategy badMessageStrategy(final FailureStrategy delegate) {
    return new FailureStrategy() {
      private String prependMessage(String message) {
        String name = actual().getClass().getName();
        if (internalCustomName() != null) {
          name = internalCustomName() + "(" + name + ")";
        }
        return "Unexpected message for " + name + ":" + (message.isEmpty() ? "" : " " + message);
      }

      @Override
      public void fail(String message) {
        delegate.fail(prependMessage(message));
      }

      @Override
      public void fail(String message, Throwable cause) {
        delegate.fail(prependMessage(message), cause);
      }

      @Override
      public void failComparing(String message, CharSequence expected, CharSequence actual) {
        delegate.failComparing(prependMessage(message), expected, actual);
      }

      @Override
      public void failComparing(
          String message, CharSequence expected, CharSequence actual, Throwable cause) {
        delegate.failComparing(prependMessage(message), expected, actual, cause);
      }
    };
  }

  private FailureStrategy badCauseStrategy(final FailureStrategy delegate) {
    return new FailureStrategy() {
      private String prependMessage(String message) {
        String name = actual().getClass().getName();
        if (internalCustomName() != null) {
          name = internalCustomName() + "(" + name + ")";
        }
        return "Unexpected cause for " + name + ":" + (message.isEmpty() ? "" : " " + message);
      }

      @Override
      public void fail(String message) {
        delegate.fail(prependMessage(message));
      }

      @Override
      public void fail(String message, Throwable cause) {
        delegate.fail(prependMessage(message), cause);
      }

      @Override
      public void failComparing(String message, CharSequence expected, CharSequence actual) {
        delegate.failComparing(prependMessage(message), expected, actual);
      }

      @Override
      public void failComparing(
          String message, CharSequence expected, CharSequence actual, Throwable cause) {
        delegate.failComparing(prependMessage(message), expected, actual, cause);
      }
    };
  }
}

@@ -2405,24 +2405,203 @@
 ).hCT()....%0A
+    // TODO(diamondm) in keeping with other subjects' behavior this should still NPE if the subject%0A    // *itself* is null, since there's no context to lose. See also b/37645583%0A
     if (actu

Add TODO to ThrowableSubject's .hasCauseThat() method.