package org.testng.asserts;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/** An assert class with various hooks allowing its behavior to be modified by subclasses. */
public class Assertion implements IAssertLifecycle {
  protected void doAssert(IAssert<?> assertCommand) {
    onBeforeAssert(assertCommand);
    try {
      executeAssert(assertCommand);
      onAssertSuccess(assertCommand);
    } catch (AssertionError ex) {
      onAssertFailure(assertCommand, ex);
      throw ex;
    } finally {
      onAfterAssert(assertCommand);
    }
  }

  /** Run the assert command in parameter. Meant to be overridden by subclasses. */
  @Override
  public void executeAssert(IAssert<?> assertCommand) {
    assertCommand.doAssert();
  }

  /** Invoked when an assert succeeds. Meant to be overridden by subclasses. */
  @Override
  public void onAssertSuccess(IAssert<?> assertCommand) {}

  @Override
  public void onAssertFailure(IAssert<?> assertCommand, AssertionError ex) {}

  /** Invoked before an assert is run. Meant to be overridden by subclasses. */
  @Override
  public void onBeforeAssert(IAssert<?> assertCommand) {}

  /** Invoked after an assert is run. Meant to be overridden by subclasses. */
  @Override
  public void onAfterAssert(IAssert<?> assertCommand) {}

  private abstract static class SimpleAssert<T> implements IAssert<T> {
    private final T actual;
    private final T expected;
    private final String m_message;

    public SimpleAssert(String message) {
      this(null, null, message);
    }

    public SimpleAssert(T actual, T expected) {
      this(actual, expected, null);
    }

    public SimpleAssert(T actual, T expected, String message) {
      this.actual = actual;
      this.expected = expected;
      m_message = message;
    }

    @Override
    public String getMessage() {
      return m_message;
    }

    @Override
    public T getActual() {
      return actual;
    }

    @Override
    public T getExpected() {
      return expected;
    }

    @Override
    public abstract void doAssert();
  }

  public void assertTrue(final boolean condition, final String message) {
    doAssert(
        new SimpleAssert<Boolean>(condition, Boolean.TRUE, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertTrue(condition, message);
          }
        });
  }

  public void assertTrue(final boolean condition) {
    doAssert(
        new SimpleAssert<Boolean>(condition, Boolean.TRUE) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertTrue(condition);
          }
        });
  }

  public void assertFalse(final boolean condition, final String message) {
    doAssert(
        new SimpleAssert<Boolean>(condition, Boolean.FALSE, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertFalse(condition, message);
          }
        });
  }

  public void assertFalse(final boolean condition) {
    doAssert(
        new SimpleAssert<Boolean>(condition, Boolean.FALSE) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertFalse(condition);
          }
        });
  }

  public void fail(final String message, final Throwable realCause) {
    doAssert(
        new SimpleAssert<Object>(message) {
          @Override
          public void doAssert() {
            org.testng.Assert.fail(message, realCause);
          }
        });
  }

  public void fail(final String message) {
    doAssert(
        new SimpleAssert<Object>(message) {
          @Override
          public void doAssert() {
            org.testng.Assert.fail(message);
          }
        });
  }

  public void fail() {
    doAssert(
        new SimpleAssert<Object>(null) {
          @Override
          public void doAssert() {
            org.testng.Assert.fail();
          }
        });
  }

  public <T> void assertEquals(final T actual, final T expected, final String message) {
    doAssert(
        new SimpleAssert<T>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public <T> void assertEquals(final T actual, final T expected) {
    doAssert(
        new SimpleAssert<T>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final String actual, final String expected, final String message) {
    doAssert(
        new SimpleAssert<String>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final String actual, final String expected) {
    doAssert(
        new SimpleAssert<String>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(
      final double actual, final double expected, final double delta, final String message) {
    doAssert(
        new SimpleAssert<Double>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, delta, message);
          }
        });
  }

  public void assertEquals(final double actual, final double expected, final double delta) {
    doAssert(
        new SimpleAssert<Double>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, delta);
          }
        });
  }

  public void assertEquals(
      final float actual, final float expected, final float delta, final String message) {
    doAssert(
        new SimpleAssert<Float>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, delta, message);
          }
        });
  }

  public void assertEquals(final float actual, final float expected, final float delta) {
    doAssert(
        new SimpleAssert<Float>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, delta);
          }
        });
  }

  public void assertEquals(final long actual, final long expected, final String message) {
    doAssert(
        new SimpleAssert<Long>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final long actual, final long expected) {
    doAssert(
        new SimpleAssert<Long>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final boolean actual, final boolean expected, final String message) {
    doAssert(
        new SimpleAssert<Boolean>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final boolean actual, final boolean expected) {
    doAssert(
        new SimpleAssert<Boolean>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final byte actual, final byte expected, final String message) {
    doAssert(
        new SimpleAssert<Byte>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final byte actual, final byte expected) {
    doAssert(
        new SimpleAssert<Byte>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final char actual, final char expected, final String message) {
    doAssert(
        new SimpleAssert<Character>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final char actual, final char expected) {
    doAssert(
        new SimpleAssert<Character>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final short actual, final short expected, final String message) {
    doAssert(
        new SimpleAssert<Short>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final short actual, final short expected) {
    doAssert(
        new SimpleAssert<Short>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final int actual, final int expected, final String message) {
    doAssert(
        new SimpleAssert<Integer>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final int actual, final int expected) {
    doAssert(
        new SimpleAssert<Integer>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertNotNull(final Object object) {
    doAssert(
        new SimpleAssert<Object>(object, null) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotNull(object);
          }
        });
  }

  public void assertNotNull(final Object object, final String message) {
    doAssert(
        new SimpleAssert<Object>(object, null, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotNull(object, message);
          }
        });
  }

  public void assertNull(final Object object) {
    doAssert(
        new SimpleAssert<Object>(object, null) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNull(object);
          }
        });
  }

  public void assertNull(final Object object, final String message) {
    doAssert(
        new SimpleAssert<Object>(object, null, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNull(object, message);
          }
        });
  }

  public void assertSame(final Object actual, final Object expected, final String message) {
    doAssert(
        new SimpleAssert<Object>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertSame(actual, expected, message);
          }
        });
  }

  public void assertSame(final Object actual, final Object expected) {
    doAssert(
        new SimpleAssert<Object>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertSame(actual, expected);
          }
        });
  }

  public void assertNotSame(final Object actual, final Object expected, final String message) {
    doAssert(
        new SimpleAssert<Object>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotSame(actual, expected, message);
          }
        });
  }

  public void assertNotSame(final Object actual, final Object expected) {
    doAssert(
        new SimpleAssert<Object>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotSame(actual, expected);
          }
        });
  }

  public void assertEquals(final Collection<?> actual, final Collection<?> expected) {
    doAssert(
        new SimpleAssert<Collection<?>>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(
      final Collection<?> actual, final Collection<?> expected, final String message) {
    doAssert(
        new SimpleAssert<Collection<?>>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final Object[] actual, final Object[] expected, final String message) {
    doAssert(
        new SimpleAssert<Object[]>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEqualsNoOrder(
      final Object[] actual, final Object[] expected, final String message) {
    doAssert(
        new SimpleAssert<Object[]>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEqualsNoOrder(actual, expected, message);
          }
        });
  }

  public void assertEquals(final Object[] actual, final Object[] expected) {
    doAssert(
        new SimpleAssert<Object[]>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEqualsNoOrder(final Object[] actual, final Object[] expected) {
    doAssert(
        new SimpleAssert<Object[]>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEqualsNoOrder(actual, expected);
          }
        });
  }

  public void assertEquals(final byte[] actual, final byte[] expected) {
    doAssert(
        new SimpleAssert<byte[]>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final byte[] actual, final byte[] expected, final String message) {
    doAssert(
        new SimpleAssert<byte[]>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final Set<?> actual, final Set<?> expected) {
    doAssert(
        new SimpleAssert<Set<?>>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertEquals(final Set<?> actual, final Set<?> expected, final String message) {
    doAssert(
        new SimpleAssert<Set<?>>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected, message);
          }
        });
  }

  public void assertEquals(final Map<?, ?> actual, final Map<?, ?> expected) {
    doAssert(
        new SimpleAssert<Map<?, ?>>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final Object actual, final Object expected, final String message) {
    doAssert(
        new SimpleAssert<Object>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final Object actual, final Object expected) {
    doAssert(
        new SimpleAssert<Object>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final String actual, final String expected, final String message) {
    doAssert(
        new SimpleAssert<String>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final String actual, final String expected) {
    doAssert(
        new SimpleAssert<String>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final long actual, final long expected, final String message) {
    doAssert(
        new SimpleAssert<Long>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final long actual, final long expected) {
    doAssert(
        new SimpleAssert<Long>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final boolean actual, final boolean expected, final String message) {
    doAssert(
        new SimpleAssert<Boolean>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final boolean actual, final boolean expected) {
    doAssert(
        new SimpleAssert<Boolean>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final byte actual, final byte expected, final String message) {
    doAssert(
        new SimpleAssert<Byte>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final byte actual, final byte expected) {
    doAssert(
        new SimpleAssert<Byte>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final char actual, final char expected, final String message) {
    doAssert(
        new SimpleAssert<Character>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final char actual, final char expected) {
    doAssert(
        new SimpleAssert<Character>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final short actual, final short expected, final String message) {
    doAssert(
        new SimpleAssert<Short>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final short actual, final short expected) {
    doAssert(
        new SimpleAssert<Short>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(final int actual, final int expected, final String message) {
    doAssert(
        new SimpleAssert<Integer>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, message);
          }
        });
  }

  public void assertNotEquals(final int actual, final int expected) {
    doAssert(
        new SimpleAssert<Integer>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected);
          }
        });
  }

  public void assertNotEquals(
      final float actual, final float expected, final float delta, final String message) {
    doAssert(
        new SimpleAssert<Float>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, delta, message);
          }
        });
  }

  public void assertNotEquals(final float actual, final float expected, final float delta) {
    doAssert(
        new SimpleAssert<Float>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, delta);
          }
        });
  }

  public void assertNotEquals(
      final double actual, final double expected, final double delta, final String message) {
    doAssert(
        new SimpleAssert<Double>(actual, expected, message) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, delta, message);
          }
        });
  }

  public void assertNotEquals(final double actual, final double expected, final double delta) {
    doAssert(
        new SimpleAssert<Double>(actual, expected) {
          @Override
          public void doAssert() {
            org.testng.Assert.assertNotEquals(actual, expected, delta);
          }
        });
  }

  /***
   * Override this method should you want to change
   * the default way Throwable objects are logged.
   * @param error Throwable of the Assertion
   * @return default throwable formatted message for TestNG
   */
  protected String getErrorDetails(Throwable error) {
    StringBuilder sb = new StringBuilder();
    sb.append(error.getMessage());
    Throwable cause = error.getCause();
    while (cause != null) {
      sb.append(" ").append(cause.getMessage());
      cause = cause.getCause();
    }
    return sb.toString();
  }
}
