package gov.cms.grouper.snf.lego;

import gov.cms.grouper.snf.lego.SnfComparator.AcceptNull;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;

/**
 * provide a null safe comparasion when you are force to deal with null
 */
public class BetweenCompare<T extends Comparable<T>> {

  public static final int FROM_INCLUSIVE = 1;
  public static final int FROM_EXCLUSIVE = 0;

  public static final int TO_INCLUSIVE = 0;
  public static final int TO_EXCLUSIVE = 1;

  private final AcceptNull fromAccept;
  private final AcceptNull toAccept;

  public BetweenCompare(AcceptNull fromAccept, AcceptNull toAccept) {
    super();
    this.fromAccept = fromAccept;
    this.toAccept = toAccept;
  }

  /**
   * compare from, check, to Any comparable type like integer, double, date
   *
   * @return check between from - to
   */
  public boolean betweenInclusive(T from, T check, T to) {
    boolean result = compute(this.fromAccept.getComparator(), from, FROM_INCLUSIVE, check,
        this.toAccept.getComparator(), to, TO_INCLUSIVE);
    return result;
  }

  public boolean compute(Comparator<T> fromComparator, T from, int fromInclusive, T check,
      Comparator<T> toComparator, T to, int toInclusive) {

    boolean fromNull = this.fromAccept.checkNull(Arrays.asList(from, check));
    boolean toNull = this.toAccept.checkNull(Arrays.asList(check, to));

    Supplier<Boolean> computedResult = () -> {
      boolean f = fromComparator.compare(from, check) < fromInclusive;
      boolean t = toComparator.compare(to, check) >= toInclusive;
      boolean result = f && t;
      return result;
    };

    boolean result = fromNull && toNull && computedResult.get();

    return result;
  }


  /**
   * a standard implementation of between compare from to Any comparable type like integer, double,
   * date
   *
   * @return compare from to
   */
  public boolean between(T fromInclusive, T check, T toExclusive) {
    boolean result = compute(this.fromAccept.getComparator(), fromInclusive, FROM_INCLUSIVE, check,
        this.toAccept.getComparator(), toExclusive, TO_EXCLUSIVE);

    return result;
  }

  public boolean betweenNullLow(T fromInclusive, T check, T toInclusive) {
    boolean result = compute(this.fromAccept.getComparator(), fromInclusive, FROM_INCLUSIVE, check,
        SnfComparator.nullLow(), toInclusive, TO_INCLUSIVE);
    return result;
  }


  public static <T extends Comparable<T>> BetweenCompare<T> of() {
    return new BetweenCompare<>(AcceptNull.FALSE_ON_NULL, AcceptNull.FALSE_ON_NULL);
  }

  public static <T extends Comparable<T>> BetweenCompare<T> snfOf() {
    return new BetweenCompare<>(AcceptNull.COMPUTE_NULL_AS_LOW, AcceptNull.COMPUTE_NULL_AS_HIGH);
  }

  public static <T extends Comparable<T>> BetweenCompare<T> of(AcceptNull from, AcceptNull to) {
    return new BetweenCompare<>(from, to);
  }
}
