/*
 * 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;

import static java.lang.Math.pow;
import static org.apache.datasketches.Util.bytesToInt;
import static org.apache.datasketches.Util.bytesToLong;
import static org.apache.datasketches.Util.bytesToString;
import static org.apache.datasketches.Util.ceilingPowerOf2;
import static org.apache.datasketches.Util.ceilingPowerOfBdouble;
import static org.apache.datasketches.Util.characterPad;
import static org.apache.datasketches.Util.checkIfMultipleOf8AndGT0;
import static org.apache.datasketches.Util.checkIfPowerOf2;
import static org.apache.datasketches.Util.checkProbability;
import static org.apache.datasketches.Util.evenlyLogSpaced;
import static org.apache.datasketches.Util.evenlySpaced;
import static org.apache.datasketches.Util.evenlySpacedFloats;
import static org.apache.datasketches.Util.floorPowerOf2;
import static org.apache.datasketches.Util.floorPowerOfBdouble;
import static org.apache.datasketches.Util.getResourceBytes;
import static org.apache.datasketches.Util.getResourceFile;
import static org.apache.datasketches.Util.intToBytes;
import static org.apache.datasketches.Util.isLessThanUnsigned;
import static org.apache.datasketches.Util.isMultipleOf8AndGT0;
import static org.apache.datasketches.Util.isPowerOf2;
import static org.apache.datasketches.Util.milliSecToString;
import static org.apache.datasketches.Util.nanoSecToString;
import static org.apache.datasketches.Util.numberOfLeadingOnes;
import static org.apache.datasketches.Util.numberOfTrailingOnes;
import static org.apache.datasketches.Util.pwr2LawNext;
import static org.apache.datasketches.Util.pwr2LawPrev;
import static org.apache.datasketches.Util.pwrLawNextDouble;
import static org.apache.datasketches.Util.simpleLog2OfLong;
import static org.apache.datasketches.Util.zeroPad;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.io.File;

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author Lee Rhodes
 */
@SuppressWarnings("javadoc")
public class UtilTest {
  private static final String LS = System.getProperty("line.separator");

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkPowerOf2() {
    checkIfPowerOf2(31, "31");
  }

  @Test
  public void numTrailingOnes() {
    long mask = 1L;
    for (int i = 0; i <= 64; i++) {
      final long v = ~mask & -1L;
      mask <<= 1;
      final int numT1s = numberOfTrailingOnes(v);
      final int numL1s = numberOfLeadingOnes(v);
      assertEquals(Long.numberOfTrailingZeros(~v), numT1s);
      assertEquals(Long.numberOfLeadingZeros(~v), numL1s);
      //println(zeroPad(Long.toBinaryString(v),64) + ", " + numL1s + ", " + numT1s);
      continue;
    }
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkBoundsTest() {
    Util.checkBounds(999, 2, 1000);
  }
  
  @Test
  public void checkIsPowerOf2() {
    Assert.assertEquals(isPowerOf2(0), false);
    Assert.assertEquals(isPowerOf2(1), true);
    Assert.assertEquals(isPowerOf2(2), true);
    Assert.assertEquals(isPowerOf2(4), true);
    Assert.assertEquals(isPowerOf2(8), true);
    Assert.assertEquals(isPowerOf2(1 << 30), true);
    Assert.assertEquals(isPowerOf2(3), false);
    Assert.assertEquals(isPowerOf2(5), false);
    Assert.assertEquals(isPowerOf2( -1), false);
  }

  @Test
  public void checkCheckIfPowerOf2() {
    checkIfPowerOf2(8, "Test 8");
    try {
      checkIfPowerOf2(7, "Test 7");
      Assert.fail("Expected SketchesArgumentException");
    }
    catch (final SketchesArgumentException e) {
      //pass
    }
  }

  @Test
  public void checkCeilingPowerOf2() {
    Assert.assertEquals(ceilingPowerOf2(Integer.MAX_VALUE), 1 << 30);
    Assert.assertEquals(ceilingPowerOf2(1 << 30), 1 << 30);
    Assert.assertEquals(ceilingPowerOf2(64), 64);
    Assert.assertEquals(ceilingPowerOf2(65), 128);
    Assert.assertEquals(ceilingPowerOf2(0), 1);
    Assert.assertEquals(ceilingPowerOf2( -1), 1);
  }

  @Test
  public void checkCeilingPowerOf2double() {
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, Integer.MAX_VALUE), pow(2.0, 31));
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, 1 << 30), pow(2.0, 30));
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, 64.0), 64.0);
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, 65.0), 128.0);
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, 0.0), 1.0);
    Assert.assertEquals(ceilingPowerOfBdouble(2.0, -1.0), 1.0);
  }

  @Test
  public void checkFloorPowerOf2Int() {
    Assert.assertEquals(floorPowerOf2( -1), 1);
    Assert.assertEquals(floorPowerOf2(0), 1);
    Assert.assertEquals(floorPowerOf2(1), 1);
    Assert.assertEquals(floorPowerOf2(2), 2);
    Assert.assertEquals(floorPowerOf2(3), 2);
    Assert.assertEquals(floorPowerOf2(4), 4);

    Assert.assertEquals(floorPowerOf2((1 << 30) - 1), 1 << 29);
    Assert.assertEquals(floorPowerOf2(1 << 30), 1 << 30);
    Assert.assertEquals(floorPowerOf2((1 << 30) + 1), 1 << 30);
  }
  @Test
  public void checkFloorPowerOf2Long() {
    Assert.assertEquals(floorPowerOf2( -1L), 1L);
    Assert.assertEquals(floorPowerOf2(0L), 1L);
    Assert.assertEquals(floorPowerOf2(1L), 1L);
    Assert.assertEquals(floorPowerOf2(2L), 2L);
    Assert.assertEquals(floorPowerOf2(3L), 2L);
    Assert.assertEquals(floorPowerOf2(4L), 4L);

    Assert.assertEquals(floorPowerOf2((1L << 63) - 1L), 1L << 62);
    Assert.assertEquals(floorPowerOf2(1L << 62), 1L << 62);
    Assert.assertEquals(floorPowerOf2((1L << 62) + 1L), 1L << 62);
  }
  @Test
  public void checkFloorPowerOf2double() {
    Assert.assertEquals(floorPowerOfBdouble(2.0, -1.0), 1.0);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 0.0), 1.0);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 1.0), 1.0);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 2.0), 2.0);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 3.0), 2.0);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 4.0), 4.0);

    Assert.assertEquals(floorPowerOfBdouble(2.0, (1 << 30) - 1.0), 1 << 29);
    Assert.assertEquals(floorPowerOfBdouble(2.0, 1 << 30), 1 << 30);
    Assert.assertEquals(floorPowerOfBdouble(2.0, (1 << 30) + 1.0), 1L << 30);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkIfMultipleOf8AndGTzero() {
    checkIfMultipleOf8AndGT0(8, "test");
    checkIfMultipleOf8AndGT0(7, "test");
  }

  @Test
  public void checkIsMultipleOf8AndGT0() {
    Assert.assertTrue(isMultipleOf8AndGT0(8));
    Assert.assertFalse(isMultipleOf8AndGT0(7));
    Assert.assertFalse(isMultipleOf8AndGT0(-1));
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkIfMultipleOf8AndGTzero2() {
    checkIfMultipleOf8AndGT0(8, "test");
    checkIfMultipleOf8AndGT0(-1, "test");
  }

  @Test
  public void checkIsLessThanUnsigned() {
    final long n1 = 1;
    final long n2 = 3;
    final long n3 = -3;
    final long n4 = -1;
    Assert.assertTrue(isLessThanUnsigned(n1, n2));
    Assert.assertTrue(isLessThanUnsigned(n2, n3));
    Assert.assertTrue(isLessThanUnsigned(n3, n4));
    Assert.assertFalse(isLessThanUnsigned(n2, n1));
    Assert.assertFalse(isLessThanUnsigned(n3, n2));
    Assert.assertFalse(isLessThanUnsigned(n4, n3));
  }

  @Test
  public void checkZeroPad() {
    final long v = 123456789;
    final String vHex = Long.toHexString(v);
    final String out = zeroPad(vHex, 16);
    println("Pad 16, prepend 0: " + out);
  }

  @Test
  public void checkCharacterPad() {
    final String s = "Pad 30, postpend z:";
    final String out = characterPad(s, 30, 'z', true);
    println(out);
  }

  @Test
  public void checkProbabilityFn1() {
    checkProbability(.5, "Good");
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkProbabilityFn2() {
    checkProbability(-.5, "Too Low");
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkProbabilityFn3() {
    checkProbability(1.5, "Too High");
  }

  @Test
  public void checkEvenlySpaced() {
    double[] arr = evenlySpaced(0, 1, 3);
    assertEquals(arr[0], 0.0);
    assertEquals(arr[1], 0.5);
    assertEquals(arr[2], 1.0);
    arr = evenlySpaced(3, 7, 3);
    assertEquals(arr[0], 3.0);
    assertEquals(arr[1], 5.0);
    assertEquals(arr[2], 7.0);
  }

  @Test
  public void checkEvenlySpacedFloats() {
    float[] arr = evenlySpacedFloats(0, 1, 3);
    assertEquals(arr[0], 0.0f);
    assertEquals(arr[1], 0.5f);
    assertEquals(arr[2], 1.0f);
    arr = evenlySpacedFloats(3, 7, 3);
    assertEquals(arr[0], 3.0f);
    assertEquals(arr[1], 5.0f);
    assertEquals(arr[2], 7.0f);
  }

  @Test
  public void checkEvenlyLogSpaced() {
    final double[] arr = evenlyLogSpaced(1, 8, 4);
    assertEquals(arr[0], 1.0);
    assertEquals(arr[1], 2.0);
    assertEquals(arr[2], 4.0);
    assertEquals(arr[3], 8.0);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkEvenlySpacedExcep() {
    evenlySpaced(1, 2, 1);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkEvenlyLogSpacedExcep1() {
    evenlyLogSpaced(1, 2, 1);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkEvenlyLogSpacedExcep2() {
    evenlyLogSpaced(-1, 2, 2);
  }

  @Test
  public void checkBytesToInt() {
    final byte[] arr = new byte[] {4, 3, 2, 1};
    final int result = 4 + (3 << 8) + (2 << 16) + (1 << 24);
    Assert.assertEquals(bytesToInt(arr), result);
    final byte[] arr2 = intToBytes(result, new byte[4]);
    Assert.assertEquals(arr, arr2);
  }

  @Test
  public void checkBytesToLong() {
    final byte[] arr = new byte[] {8, 7, 6, 5, 4, 3, 2, 1};
    final long result = 8L + (7L << 8) + (6L << 16) + (5L << 24)
               + (4L << 32) + (3L << 40) + (2L << 48) + (1L << 56);
    Assert.assertEquals(bytesToLong(arr), result);
  }

  @Test
  public void checkBytesToString() {
    final long lng = 0XF8F7F6F504030201L;
    //println(Long.toHexString(lng));
    byte[] bytes = new byte[8];
    bytes = Util.longToBytes(lng, bytes);
    final String sep = ".";
    final String unsignLE = bytesToString(bytes, false, true, sep);
    final String signedLE = bytesToString(bytes, true, true, sep);
    final String unsignBE = bytesToString(bytes, false,  false, sep);
    final String signedBE = bytesToString(bytes, true,  false, sep);
    Assert.assertEquals(unsignLE, "1.2.3.4.245.246.247.248");
    Assert.assertEquals(signedLE, "1.2.3.4.-11.-10.-9.-8");
    Assert.assertEquals(unsignBE, "248.247.246.245.4.3.2.1");
    Assert.assertEquals(signedBE, "-8.-9.-10.-11.4.3.2.1");
  }

  @Test
  public void checkNsecToString() {
    final long nS = 1000000000L + 1000000L + 1000L + 1L;
    final String result = nanoSecToString(nS);
    final String expected = "1.001_001_001";
    Assert.assertEquals(result, expected);
  }

  @Test
  public void checkMsecToString() {
    final long nS = 60L * 60L * 1000L + 60L * 1000L + 1000L + 1L;
    final String result = milliSecToString(nS);
    final String expected = "1:01:01.001";
    Assert.assertEquals(result, expected);
  }

  @Test
  public void checkPwr2LawNext() {
    int next = pwr2LawNext(2, 1);
    Assert.assertEquals(next, 2);
    next = pwr2LawNext(2, 0);
    Assert.assertEquals(next, 1);
  }

  @Test
  public void checkPwr2LawNextDouble() {
    double next = pwrLawNextDouble(2, 1.0, true, 2.0);
    Assert.assertEquals(next, 2.0, 0.0);
    next = pwrLawNextDouble(2, 1.0, false, 2.0);
    Assert.assertEquals(next, Math.sqrt(2), 0.0);
    next = pwrLawNextDouble(2, 0.5, true, 2.0);
    Assert.assertEquals(next, 2.0, 0.0);
    next = pwrLawNextDouble(2, 0.5, false, 2.0);
    Assert.assertEquals(next, Math.sqrt(2), 0.0);
    next = pwrLawNextDouble(2, next, false, 2.0);
    Assert.assertEquals(next, 2.0, 0.0);

  }

  @Test
  public void checkPwr2LawExamples() {
    final int maxP = 32;
    final int minP = 1;
    final int ppo = 4;

    for (int p = minP; p <= maxP; p = pwr2LawNext(ppo, p)) {
      print(p + " ");
    }
    println("");

    for (int p = maxP; p >= minP; p = pwr2LawPrev(ppo, p)) {
      print(p + " ");
    }
    println("");
  }

  @Test
  public void checkSimpleLog2OfLong() {
    Assert.assertEquals(simpleLog2OfLong(2), 1);
    Assert.assertEquals(simpleLog2OfLong(1), 0);
    Assert.assertEquals(simpleLog2OfLong(1L << 62), 62);
    try {
      simpleLog2OfLong(0);
      fail();
    } catch (final SketchesArgumentException e) { }
  }

  //Resources

  @Test
  public void resourcefileExists() {
    final String shortFileName = "cpc-empty.sk";
    final File file = getResourceFile(shortFileName);
    assertTrue(file.exists());
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void resourceFileNotFound() {
    final String shortFileName = "cpc-empty.sk";
    getResourceFile(shortFileName + "123");
  }

  @Test
  public void resourceBytesCorrect() {
    final String shortFileName = "cpc-empty.sk";
    final byte[] bytes = getResourceBytes(shortFileName);
    assertTrue(bytes.length == 8);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void resourceBytesFileNotFound() {
    final String shortFileName = "cpc-empty.sk";
    getResourceBytes(shortFileName + "123");
  }

  @Test
  public void printlnTest() {
    println("PRINTING: " + this.getClass().getName());
  }

  static void println(final Object o) {
    if (o == null) { print(LS); }
    else { print(o.toString() + LS); }
  }

  /**
   * @param o value to print
   */
  static void print(final Object o) {
    if (o != null) {
      //System.out.print(o.toString()); //disable here
    }
  }

}
