package gov.cms.grouper.snf.lego;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Comparator;
import java.util.List;
import java.util.function.Supplier;

public class SnfComparator {

  public static enum AcceptNull {
    FALSE_ON_NULL(() -> false, NULL_LOW),
    COMPUTE_NULL_AS_LOW(() -> true, NULL_LOW),
    COMPUTE_NULL_AS_HIGH(() -> true, NULL_HIGH),
    EXECEPTION_ON_NULL(() -> {
      throw new NullPointerException();
    }, NULL_LOW),;

    private final Supplier<Boolean> onNull;
    @SuppressWarnings("rawtypes")
    private final Comparator<Comparable> comparator;

    @SuppressWarnings("rawtypes")
    private AcceptNull(Supplier<Boolean> onNull, Comparator<Comparable> comparator) {
      this.onNull = onNull;
      this.comparator = comparator;
    }

    public <T extends Comparable<T>> boolean checkNull(List<T> values) {
      boolean hasNull = false;
      for (T value : values) {
        if (value == null) {
          hasNull = true;
          break;
        }
      }

      return hasNull == false || this.onNull.get();
    }

    public <T extends Comparable<T>> Comparator<T> getComparator() {
      return SnfUtils.cast(this.comparator);
    }

    public <T extends Comparable<T>> BetweenCompare<T> ofBetween() {
      return BetweenCompare.of(this, this);
    }

    public <T extends Comparable<T>> Compare<T> ofCompare() {
      return Compare.of(this);
    }
  }


  @SuppressWarnings({"rawtypes", "unchecked"})
  public static final Comparator<Comparable> NULL_HIGH = (o1, o2) -> {
    int result;
    if (o1 == o2) {
      result = 0;
    } else if (o1 == null) {
      result = 1;
    } else if (o2 == null) {
      result = -1;
    } else {
      result = o1.compareTo(o2);
    }
    return result;
  };

  @SuppressWarnings({"rawtypes", "unchecked"})
  public static final Comparator<Comparable> NULL_LOW = (o1, o2) -> {
    int result;
    if (o1 == o2) {
      result = 0;
    } else if (o1 == null) {
      result = -1;
    } else if (o2 == null) {
      result = 1;
    } else {
      result = o1.compareTo(o2);
    }
    return result;
  };


  public static <T extends Comparable<T>> boolean lte(T thisValue, T isLessThanThis) {
    Compare<T> com = Compare.of();
    boolean result = com.lte(thisValue, isLessThanThis);
    return result;
  }

  public static <T extends Comparable<T>> boolean lt(T thisValue, T isLessThanThis) {
    Compare<T> com = Compare.of();
    boolean result = com.lt(thisValue, isLessThanThis);
    return result;
  }

  public static <T extends Comparable<T>> boolean gte(T thisValue, T isGreaterThanThis) {
    Compare<T> com = Compare.of();
    boolean result = com.gte(thisValue, isGreaterThanThis);
    return result;
  }

  public static <T extends Comparable<T>> boolean gt(T thisValue, T isGreaterThanThis) {
    Compare<T> com = Compare.of();
    boolean result = com.gt(thisValue, isGreaterThanThis);
    return result;
  }

  public static <T extends Comparable<T>> boolean betweenInclusive(T fromInclusive, T check,
      T toInclusive) {
    BetweenCompare<T> com = BetweenCompare.snfOf();
    boolean result = com.betweenInclusive(fromInclusive, check, toInclusive);
    return result;
  }

  public static <T extends Comparable<T>> boolean between(T fromInclusive, T check, T toExclusive) {
    BetweenCompare<T> com = BetweenCompare.snfOf();
    boolean result = com.between(fromInclusive, check, toExclusive);
    return result;
  }

  public static <T extends Comparable<T>> boolean betweenNullLow(T fromInclusive, T check,
      T toInclusive) {
    BetweenCompare<T> com = BetweenCompare.snfOf();
    boolean result = com.betweenNullLow(fromInclusive, check, toInclusive);
    return result;
  }

  public static <T extends Number> BigDecimal sum(List<T> items) {
    BigDecimal sum = BigDecimal.ZERO;
    for (T item : items) {
      BigDecimal num = new BigDecimal(item.toString());
      sum = sum.add(num);
    }
    return sum;
  }

  public static <T extends Number> BigDecimal avg(List<T> items) {
    return SnfComparator.avg(items, RoundingMode.HALF_UP, 2);
  }

  public static <T extends Number> BigDecimal avg(List<T> items, RoundingMode mode, int scale) {
    if (items.isEmpty()) {
      return BigDecimal.ZERO;
    }
    BigDecimal sum = SnfComparator.sum(items);
    BigDecimal avg = sum.divide(new BigDecimal(items.size()), scale, mode);
    return avg;
  }

  public static <T extends Comparable<T>> Comparator<T> nullHigh() {
    return SnfUtils.cast(SnfComparator.NULL_HIGH);
  }

  public static <T extends Comparable<T>> Comparator<T> nullLow() {
    return SnfUtils.cast(SnfComparator.NULL_LOW);
  }

  public static class NullAs<T> {
    private final T nullValue;

    public NullAs(T nullValue) {
      super();
      this.nullValue = nullValue;
    }

    public T getNullValue() {
      return nullValue;
    }

    public T ck(T value) {
      T result = value;
      if (value == null) {
        result = this.nullValue;
      }
      return result;
    }

    public static <T> NullAs<T> of(T nullValue) {
      return new NullAs<>(nullValue);
    }
  }

}
