package org.hamcrest.number;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.hamcrest.collection.ArrayMatching;

import java.math.BigDecimal;
import java.math.MathContext;

/**
 * A matcher that checks a {@link BigDecimal} is close to an expected value.
 */
public class BigDecimalCloseTo extends TypeSafeMatcher<BigDecimal> {

  private final BigDecimal delta;
  private final BigDecimal value;

  /**
   * Constructor, best called from {@link #closeTo(BigDecimal, BigDecimal)}.
   * @param value the expected value
   * @param error the acceptable difference from the expected value
   * @see #closeTo(BigDecimal, BigDecimal)
   */
  public BigDecimalCloseTo(BigDecimal value, BigDecimal error) {
      this.delta = error;
      this.value = value;
  }

  @Override
  public boolean matchesSafely(BigDecimal item) {
      return actualDelta(item).compareTo(BigDecimal.ZERO) <= 0;
  }

  @Override
  public void describeMismatchSafely(BigDecimal item, Description mismatchDescription) {
      mismatchDescription.appendValue(item)
              .appendText(" differed by ")
              .appendValue(actualDelta(item))
              .appendText(" more than delta ")
              .appendValue(delta);
  }

  @Override
  public void describeTo(Description description) {
      description.appendText("a numeric value within ")
              .appendValue(delta)
              .appendText(" of ")
              .appendValue(value);
  }

  private BigDecimal actualDelta(BigDecimal item) {
      return item.subtract(value, MathContext.DECIMAL128).abs().subtract(delta, MathContext.DECIMAL128).stripTrailingZeros();
  }

  /**
   * Creates a matcher of {@link java.math.BigDecimal}s that matches when an examined BigDecimal is equal
   * to the specified <code>operand</code>, within a range of +/- <code>error</code>. The comparison for equality
   * is done by BigDecimals {@link java.math.BigDecimal#compareTo(java.math.BigDecimal)} method.
   * For example:
   * <pre>assertThat(new BigDecimal("1.03"), is(closeTo(new BigDecimal("1.0"), new BigDecimal("0.03"))))</pre>
   *
   * @param operand
   *     the expected value of matching BigDecimals
   * @param error
   *     the delta (+/-) within which matches will be allowed
   * @return The matcher.
   */
  public static Matcher<BigDecimal> closeTo(BigDecimal operand, BigDecimal error) {
      return new BigDecimalCloseTo(operand, error);
  }

}
