/**
 * 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.
 *
 * Copyright 2012-2017 the original author or authors.
 */
package org.assertj.core.test;

import static java.lang.String.format;

import java.util.ArrayList;
import java.util.List;

import org.assertj.core.error.ErrorMessageFactory;
import org.assertj.core.util.introspection.IntrospectionError;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.hamcrest.core.AllOf;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsSame;
import org.hamcrest.core.StringContains;
import org.hamcrest.core.StringEndsWith;
import org.hamcrest.core.StringStartsWith;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * Allows in-test specification of expected exception types and messages.
 *
 * @author Alex Ruiz
 */
public class ExpectedException implements TestRule {
  private final org.junit.rules.ExpectedException delegate = org.junit.rules.ExpectedException.none();

  public static ExpectedException none() {
    return new ExpectedException();
  }

  private ExpectedException() {}

  @Override
  public Statement apply(Statement base, Description description) {
    return delegate.apply(base, description);
  }

  public void expectAssertionError(String message) {
    expect(AssertionError.class, message);
  }

  public void expectAssertionError(ErrorMessageFactory errorMessageFactory) {
    delegate.expect(new ThrowableMatcher<>(AssertionError.class, errorMessageFactory.create()));
  }

  public void expectAssertionErrorWithMessageContaining(String... parts) {
    expectWithMessageContaining(AssertionError.class, parts);
  }

  public void expectNullPointerException(String message) {
    expect(NullPointerException.class, message);
  }

  public void expectIllegalArgumentException(String message) {
    expect(IllegalArgumentException.class, message);
  }

  public void expectIndexOutOfBoundsException(String message) {
    expect(IndexOutOfBoundsException.class, message);
  }

  public void expectUnsupportedOperationException(String message) {
    expect(UnsupportedOperationException.class, message);
  }

  public void expectIntrospectionErrorWithMessageContaining(String... parts) {
    expectWithMessageContaining(IntrospectionError.class, parts);
  }

  public void expect(Class<? extends Throwable> type, String message) {
    expect(type);
    expectMessage(message);
  }

  public void expectWithMessageContaining(Class<? extends Throwable> type, String... parts) {
    expect(type);
    expectMessageContaining(parts);
  }

  public void expectWithMessageStartingWith(Class<? extends Throwable> type, String start) {
    expect(type);
    expectMessageStartingWith(start);
  }

  public void expectWithMessageEndingWith(Class<? extends Throwable> type, String end) {
    expect(type);
    expectMessageEndingWith(end);
  }

  public void expect(Class<? extends Throwable> type) {
    delegate.expect(type);
  }

  public void expectWithCause(Class<? extends Throwable> type, Throwable cause) {
    expect(type);
    expectCause(cause);
  }

  public void expectWithCause(Class<? extends Throwable> type, String message, Throwable cause) {
    expect(type);
    expectMessage(message);
    expectCause(cause);
  }

  public void expectMessage(String message) {
    delegate.expectMessage(IsEqual.equalTo(format(message)));
  }

  private void expectMessageContaining(String... parts) {
    List<Matcher<? super String>> matchers = new ArrayList<>();
    for (String part : parts) {
      matchers.add(StringContains.containsString(format(part)));
    }
    delegate.expectMessage(AllOf.allOf(matchers));
  }

  private void expectMessageStartingWith(String start) {
    delegate.expectMessage(StringStartsWith.startsWith(format(start)));
  }

  private void expectMessageEndingWith(String end) {
    delegate.expectMessage(StringEndsWith.endsWith(format(end)));
  }

  private void expectCause(Throwable cause) {
    delegate.expectCause(IsSame.sameInstance(cause));
  }

  private static class ThrowableMatcher<T extends Throwable> extends TypeSafeMatcher<T> {

    private String expectedStringRepresentation;

    public ThrowableMatcher(Class<?> expectedType, String expectedMessage) {
      this.expectedStringRepresentation = buildStringRepresentation(expectedType, expectedMessage);
    }

    @Override
    public void describeTo(org.hamcrest.Description description) {
      description.appendText(expectedStringRepresentation);
    }

    @Override
    protected boolean matchesSafely(T actual) {
      return expectedStringRepresentation.equals(buildStringRepresentation(actual.getClass(), actual.getMessage()));
    }

    private String buildStringRepresentation(Class<?> clazz, String message) {
      return format("<%s: %s>", clazz.getName(), message);
    }
  }

}

@@ -755,35 +755,149 @@
 e.error.
-ErrorMessageFactory
+AssertionErrorFactory;%0Aimport org.assertj.core.error.ErrorMessageFactory;%0Aimport org.assertj.core.presentation.StandardRepresentation
 ;%0Aimport
@@ -2141,32 +2141,346 @@
 reate()));%0A  %7D%0A%0A
+  public void expectAssertionError(AssertionErrorFactory assertionErrorFactory) %7B%0A    AssertionError assertionError = assertionErrorFactory.newAssertionError(null, StandardRepresentation.STANDARD_REPRESENTATION);%0A    delegate.expect(new ThrowableMatcher%3C%3E(AssertionError.class, assertionError.getMessage()));%0A  %7D%0A%0A
   public void ex
@@ -5073,43 +5073,77 @@
 ate 
-String expectedStringRepresentation
+final Class%3C?%3E expectedType;%0A    private final String expectedMessage
 ;%0A%0A 
@@ -5241,70 +5241,57 @@
 cted
-StringRepresentation = buildStringRepresentation(expectedType,
+Type = expectedType;%0A      this.expectedMessage =
  exp
@@ -5298,25 +5298,24 @@
 ectedMessage
-)
 ;%0A    %7D%0A%0A   
@@ -5421,23 +5421,20 @@
 endText(
-expecte
+buil
 dStringR
@@ -5442,24 +5442,55 @@
 presentation
+(expectedType, expectedMessage)
 );%0A    %7D%0A%0A  
@@ -5574,81 +5574,58 @@
 cted
-StringRepresentation.equals(buildStringRepresentation(actual.getClass(), 
+Type.isInstance(actual) && expectedMessage.equals(
 actu
@@ -5636,25 +5636,24 @@
 etMessage())
-)
 ;%0A    %7D%0A%0A   

add ExpectedException#AssertionErrorFactory(assertionErrorFactory) to make ExpectedException usable in more tests