/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

package org.apache.datasketches.quantilescommon;

import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.find;
import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality.EQ;
import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality.GE;
import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality.GT;
import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality.LE;
import static org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality.LT;
import static org.testng.Assert.assertEquals;

import java.util.Comparator;
import java.util.Random;

import org.apache.datasketches.quantilescommon.GenericInequalitySearch.Inequality;
import org.testng.annotations.Test;

/**
 * @author Lee Rhodes
 */
public class GenericInequalitySearchTest {
  static Random rand = new Random(1);
  private static final String LS = System.getProperty("line.separator");
  private static int randDelta() { return rand.nextDouble() < 0.4 ? 0 : 1; }

  private final Comparator<Float> comparator = Comparator.naturalOrder();

  //CodeQL may complain about boxed values, but Java Generics require objects.
  private static Float[] buildRandFloatArr(final int len) {
    final Float[] arr = new Float[len];
    float v = 1.0f;
    for (int i = 0; i < len; i++) {
      arr[i] = v;
      v += randDelta();
    }
    return arr;
  }

//  @Test //visual testing only
//  //@SuppressWarnings("unused")
//  private static void viewBuildRandArr() {
//    final int len = 10;
//    for (int i = 0; i < 10; i++) {
//      final Float[] tarr = buildRandFloatArr(len);
//      for (int j = 0; j < len; j++) {
//        printf("%4.1f,", tarr[j]);
//      }
//      println("");
//    }
//  }

  @Test
  public void checkBinSearchFltLimits() {
    for (int len = 10; len <= 13; len++) {
      final Float[] tarr = buildRandFloatArr(len);
      final int low = 2;
      final int high = len - 2;
      println(listFltArray(tarr, low, high));
      checkBinarySearchFloatLimits(tarr, low, high);
    }
  }

  private static String listFltArray(final Float[] arr, final int low, final int high) {
    final StringBuilder sb = new StringBuilder();
    sb.append(LS);
    sb.append("The values in parentheses are the low and high values of the sub-array to search");
    sb.append(LS);
    sb.append("arr: ");
    for (int i = 0; i < arr.length; i++) {
      if (i == low || i == high) { sb.append(String.format("(%.0f) ", arr[i])); }
      else { sb.append(String.format("%.0f ", arr[i])); }
    }
    return sb.toString();
  }

  private void checkBinarySearchFloatLimits(final Float[] arr, final int low, final int high) {
    final Float lowV = arr[low];
    final Float highV = arr[high];
    Float v;
    int res;
    v = lowV - 1f;
    res = find(arr, low, high, v, LT, comparator);
    println(desc(arr, low, high, v, res, LT, comparator));
    assertEquals(res, -1);

    v = lowV;
    res = find(arr, low, high, v, LT, comparator);
    println(desc(arr, low, high, v, res, LT, comparator));
    assertEquals(res, -1);

    v = highV + 1;
    res = find(arr, low, high, v, LT, comparator);
    println(desc(arr, low, high, v, res, LT, comparator));
    assertEquals(res, high);

    v = lowV -1;
    res = find(arr, low, high, v, LE, comparator);
    println(desc(arr, low, high, v, res, LE, comparator));
    assertEquals(res, -1);

    v = highV;
    res = find(arr, low, high, v, LE, comparator);
    println(desc(arr, low, high, v, res, LE, comparator));
    assertEquals(res, high);

    v = highV + 1;
    res = find(arr, low, high, v, LE, comparator);
    println(desc(arr, low, high, v, res, LE, comparator));
    assertEquals(res, high);

    v = lowV - 1;
    res = find(arr, low, high, v, EQ, comparator);
    println(desc(arr, low, high, v, res, EQ, comparator));
    assertEquals(res, -1);

    v = highV;
    res = find(arr, low, high, v, EQ, comparator);
    println(desc(arr, low, high, v, res, EQ, comparator));
    assertEquals(arr[res], v);

    v = highV + 1;
    res = find(arr, low, high, v, EQ, comparator);
    println(desc(arr, low, high, v, res, EQ, comparator));
    assertEquals(res, -1);

    v = lowV - 1;
    res = find(arr, low, high, v, GT, comparator);
    println(desc(arr, low, high, v, res, GT, comparator));
    assertEquals(res, low);

    v = highV;
    res = find(arr, low, high, v, GT, comparator);
    println(desc(arr, low, high, v, res, GT, comparator));
    assertEquals(res, -1);

    v = highV + 1;
    res = find(arr, low, high, v, GT, comparator);
    println(desc(arr, low, high, v, res, GT, comparator));
    assertEquals(res, -1);

    v = lowV - 1;
    res = find(arr, low, high, v, GE, comparator);
    println(desc(arr, low, high, v, res, GE, comparator));
    assertEquals(res, low);

    v = lowV;
    res = find(arr, low, high, v, GE, comparator);
    println(desc(arr, low, high, v, res, GE, comparator));
    assertEquals(res, low);

    v = highV + 1;
    res = find(arr, low, high, v, GE, comparator);
    println(desc(arr, low, high, v, res, GE, comparator));
    assertEquals(res, -1);
  }

  @Test // visual only
  public void exerciseFltBinSearch() {
    checkFindFloat(LT);
    checkFindFloat(LE);
    checkFindFloat(GE);
    checkFindFloat(GT);
  }

  private void checkFindFloat(final GenericInequalitySearch.Inequality inequality) {
    final String ie = inequality.name();
    println("Inequality: " + ie);
    //                   0  1  2  3  4  5  6  7  8  9
    final Float[] arr = {5f,5f,5f,6f,6f,6f,7f,8f,8f,8f};
    final int len = arr.length;
    print("Index:    ");
    for (int i = 0; i < len; i++) { printf("%d,   ", i); }
    print(LS + "Value:  ");
    for (int i = 0; i < len; i++) { printf("%.1f, ", arr[i]); }
    println("");
    for (float v = 0.5f; v <= arr[len - 1] + 0.5f; v += .5f) {
      final int low = 0;
      final int high = len - 1;
      final int idx = find(arr, low, high, v, inequality, comparator);
      if (idx == -1) {
        println(ie +": " + v + " Not resolved, return -1.");
      }
      else {
        println(desc(arr, low, high, v, idx, inequality, comparator));
      }
    }
    println("");
  }

  /**
   * Optional call that describes the details of the results of the search.
   * Used primarily for debugging.
   * @param arr The underlying sorted array of generic values
   * @param low the low index of the range
   * @param high the high index of the range
   * @param v the generic value to search for
   * @param idx the resolved index from the search
   * @param inequality one of LT, LE, EQ, GE, GT.
   * @param comparator for the type T
   * @param <T> The generic type of value to be used in the search process.
   * @return the descriptive string.
   */
  public static <T> String desc(final T[] arr, final int low, final int high, final T v, final int idx,
      final Inequality inequality, final Comparator<T> comparator) {
    switch (inequality) {
      case LT: {
        if (idx == -1) {
          return "LT: " + v + " <= arr[" + low + "]=" + arr[low] + "; return -1";
        }
        if (idx == high) {
          return "LT: " + v + " > arr[" + high + "]=" + arr[high]
            + "; return arr[" + high + "]=" + arr[high];
        } //idx < high
        return "LT: " + v
          + ": arr[" + idx + "]=" + arr[idx] + " < " + v
          + " <= arr[" + (idx + 1) + "]=" + arr[idx + 1]
          + "; return arr[" + idx + "]=" + arr[idx];
      }
      case LE: {
        if (idx == -1) {
          return "LE: " + v + " < arr[" + low + "]=" + arr[low] + "; return -1";
        }
        if (idx == high) {
          return "LE: " + v + " >= arr[" + high + "]=" + arr[high]
            + "; return arr[" + high + "]=" + arr[high];
        }
        return "LE: " + v
          + ": arr[" + idx + "]=" + arr[idx] + " <= " + v
          + " < arr[" + (idx + 1) + "]=" + arr[idx + 1]
          + "; return arr[" + idx + "]=" + arr[idx];
      }
      case EQ: {
        if (idx == -1) {
          if (comparator.compare(v, arr[high]) > 0) {
            return "EQ: " + v + " > arr[" + high + "]; return -1";
          }
          if (comparator.compare(v, arr[low]) < 0) {
            return "EQ: " + v + " < arr[" + low + "]; return -1";
          }
          return "EQ: " + v + " Cannot be found within arr[" + low + "], arr[" + high + "]; return -1";
        }
        return "EQ: " + v + " == arr[" + idx + "]; return arr[" + idx + "]=" + arr[idx];
      }
      case GE: {
        if (idx == -1) {
          return "GE: " + v + " > arr[" + high + "]=" + arr[high] + "; return -1";
        }
        if (idx == low) {
          return "GE: " + v + " <= arr[" + low + "]=" + arr[low]
            + "; return arr[" + low + "]=" + arr[low];
        } //idx > low
        return "GE: " + v
          + ": arr[" + (idx - 1) + "]=" + arr[idx - 1] + " < " + v + " <= arr[" + idx + "]=" + arr[idx]
          + "; return arr[" + idx + "]=" + arr[idx];
      }
      case GT: {
        if (idx == -1) {
          return "GT: " + v + " >= arr[" + high + "]=" + arr[high] + "; return -1";
        }
        if (idx == low) {
          return "GT: " + v + " < arr[" + low + "]=" + arr[low]
            + "; return arr[" + low + "]=" + arr[low];
        } //idx > low
        return "GT: " + v
          + ": arr[" + (idx - 1) + "]=" + arr[idx - 1] + " <= " + v + " < arr[" + idx + "]=" + arr[idx]
          + "; return arr[" + idx + "]=" + arr[idx];
      }
    }
    return "";
  }

  private final static boolean enablePrinting = false;

  /**
   * @param format the format
   * @param args the args
   */
  static final void printf(final String format, final Object ...args) {
    if (enablePrinting) { System.out.printf(format, args); }
  }

  /**
   * @param o the Object to println
   */
  static final void println(final Object o) {
    if (enablePrinting) { System.out.println(o.toString()); }
  }

  /**
   * @param o the Object to print
   */
  static final void print(final Object o) {
    if (enablePrinting) { System.out.print(o.toString()); }
  }

}
