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

import static org.apache.datasketches.kll.KllHelper.checkM;
import static org.apache.datasketches.kll.KllSketch.getMaxSerializedSizeBytes;
import static org.apache.datasketches.kll.KllSketch.SketchType.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;

import org.apache.datasketches.common.ArrayOfStringsSerDe;
import org.apache.datasketches.common.MemorySegmentRequest;
import org.apache.datasketches.common.MemorySegmentStatus;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.Util;
import org.apache.datasketches.kll.KllDoublesSketch;
import org.apache.datasketches.kll.KllFloatsSketch;
import org.apache.datasketches.kll.KllHelper;
import org.apache.datasketches.kll.KllItemsSketch;
import org.apache.datasketches.kll.KllSketch;
import org.apache.datasketches.kll.KllSketch.SketchType;
import org.testng.annotations.Test;

public class KllHelperTest {
  public ArrayOfStringsSerDe serDe = new ArrayOfStringsSerDe();


  @Test
  public void checkConvertToCumulative() {
    final long[] array = {1,2,3,2,1};
    final long out = KllHelper.convertToCumulative(array);
    assertEquals(out, 9);
  }

  @Test
  public void checkCheckM() {
    try {
      checkM(0);
      fail();
    } catch (final SketchesArgumentException e) {}
    try {
      checkM(3);
      fail();
    } catch (final SketchesArgumentException e) {}
    try {
      checkM(10);
      fail();
    } catch (final SketchesArgumentException e) {}
  }

  @Test
  public void checkGetKFromEps() {
    final int k = KllSketch.DEFAULT_K;
    final double eps = KllHelper.getNormalizedRankError(k, false);
    final double epsPmf = KllHelper.getNormalizedRankError(k, true);
    final int kEps = KllSketch.getKFromEpsilon(eps, false);
    final int kEpsPmf = KllSketch.getKFromEpsilon(epsPmf, true);
    assertEquals(kEps, k);
    assertEquals(kEpsPmf, k);
  }

  @Test
  public void checkIntCapAux() {
    int lvlCap = KllHelper.levelCapacity(10, 61, 0, 8);
    assertEquals(lvlCap, 8);
    lvlCap = KllHelper.levelCapacity(10, 61, 60, 8);
    assertEquals(lvlCap, 10);
  }

  @Test
  public void checkSuperLargeKandLevels() {
    //This is beyond what the sketch can be configured for.
    final int size = KllHelper.computeTotalItemCapacity(1 << 29, 8, 61);
    assertEquals(size, 1_610_612_846);
  }

  @Test
  public void checkUbOnNumLevels() {
    assertEquals(KllHelper.ubOnNumLevels(0), 1);
  }

  @Test
  public void checkUpdatableSerDeDouble() {
    final KllDoublesSketch sk = KllDoublesSketch.newHeapInstance(200);
    for (int i = 1; i <= 533; i++) { sk.update(i); }
    final int retained = sk.getNumRetained();
    final int numLevels = ((KllSketch)sk).getNumLevels();
    println("NumLevels: " + numLevels);
    println("NumRetained: " + retained);

    final byte[] compByteArr1 = sk.toByteArray();
    final int compBytes1 = compByteArr1.length;
    println("compBytes1: " + compBytes1);

    final byte[] upByteArr1 = KllHelper.toByteArray(sk, true);
    final int upBytes1 = upByteArr1.length;
    println("upBytes1: " + upBytes1);

    MemorySegment seg;
    KllDoublesSketch sk2;

    seg = MemorySegment.ofArray(compByteArr1);
    sk2 = KllDoublesSketch.heapify(seg);
    final byte[] compByteArr2 = sk2.toByteArray();
    final int compBytes2 = compByteArr2.length;
    println("compBytes2: " + compBytes2);
    assertEquals(compBytes1, compBytes2);
    assertEquals(sk2.getNumRetained(), retained);

    seg = MemorySegment.ofArray(compByteArr2);
    sk2 = KllDoublesSketch.heapify(seg);
    final byte[] upByteArr2 = KllHelper.toByteArray(sk2, true);
    final int upBytes2 = upByteArr2.length;
    println("upBytes2: " + upBytes2);
    assertEquals(upBytes1, upBytes2);
    assertEquals(sk2.getNumRetained(), retained);
  }

  @Test
  public void checkUpdatableSerDeFloat() {
    final KllFloatsSketch sk = KllFloatsSketch.newHeapInstance(200);
    for (int i = 1; i <= 533; i++) { sk.update(i); }
    final int retained = sk.getNumRetained();
    final int numLevels = ((KllSketch)sk).getNumLevels();
    println("NumLevels: " + numLevels);
    println("NumRetained: " + retained);

    final byte[] compByteArr1 = sk.toByteArray();
    final int compBytes1 = compByteArr1.length;
    println("compBytes1: " + compBytes1);

    final byte[] upByteArr1 = KllHelper.toByteArray(sk, true);
    final int upBytes1 = upByteArr1.length;
    println("upBytes1: " + upBytes1);

    MemorySegment seg;
    KllFloatsSketch sk2;

    seg = MemorySegment.ofArray(compByteArr1);
    sk2 = KllFloatsSketch.heapify(seg);
    final byte[] compByteArr2 = sk2.toByteArray();
    final int compBytes2 = compByteArr2.length;
    println("compBytes2: " + compBytes2);
    assertEquals(compBytes1, compBytes2);
    assertEquals(sk2.getNumRetained(), retained);

    seg = MemorySegment.ofArray(compByteArr2);
    sk2 = KllFloatsSketch.heapify(seg);
    final byte[] upByteArr2 = KllHelper.toByteArray(sk2, true);
    final int upBytes2 = upByteArr2.length;
    println("upBytes2: " + upBytes2);
    assertEquals(upBytes1, upBytes2);
    assertEquals(sk2.getNumRetained(), retained);
  }

  @Test
  public void checkUpdatableSerDeItem() {
    final KllItemsSketch<String> sk = KllItemsSketch.newHeapInstance(200, Comparator.naturalOrder(), serDe);
    final int n = 533;
    final int digits = Util.numDigits(n);
    for (int i = 1; i <= n; i++) { sk.update(Util.longToFixedLengthString(i, digits)); }

    final int retained = sk.getNumRetained();
    final int numLevels = ((KllSketch)sk).getNumLevels();
    println("NumLevels: " + numLevels);
    println("NumRetained: " + retained);

    final byte[] compByteArr1 = sk.toByteArray();
    final int compBytes1 = compByteArr1.length;
    println("compBytes1: " + compBytes1);

    final byte[] upByteArr1 = KllHelper.toByteArray(sk, true);
    final int upBytes1 = upByteArr1.length;
    println("upBytes1: " + upBytes1);
    assertEquals(upBytes1, compBytes1); //only true for Items Sketch

    MemorySegment seg;
    KllItemsSketch<String> sk2;

    seg = MemorySegment.ofArray(compByteArr1);
    sk2 = KllItemsSketch.heapify(seg, Comparator.naturalOrder(), serDe);
    final byte[] compByteArr2 = sk2.toByteArray();
    final int compBytes2 = compByteArr2.length;
    println("compBytes2: " + compBytes2);
    assertEquals(compBytes1, compBytes2);
    assertEquals(sk2.getNumRetained(), retained);

    seg = MemorySegment.ofArray(compByteArr2);
    sk2 = KllItemsSketch.heapify(seg, Comparator.naturalOrder(), serDe);
    final byte[] upByteArr2 = KllHelper.toByteArray(sk2, true);
    final int upBytes2 = upByteArr2.length;
    println("upBytes2: " + upBytes2);
    assertEquals(upBytes1, upBytes2);
    assertEquals(sk2.getNumRetained(), retained);
  }


  @Test
  public void getMaxCompactDoublesSerializedSizeBytes() {
    final int sizeBytes =
        KllSketch.getMaxSerializedSizeBytes(KllSketch.DEFAULT_K, 1L << 30, KLL_DOUBLES_SKETCH, false);
    assertEquals(sizeBytes, 5704);
  }

  @Test
  public void getMaxCompactFloatsSerializedSizeBytes() {
    final int sizeBytes =
        KllSketch.getMaxSerializedSizeBytes(KllSketch.DEFAULT_K, 1L << 30, KLL_FLOATS_SKETCH, false);
    assertEquals(sizeBytes, 2908);
  }

  @Test
  public void getMaxUpdatableDoubleSerializedSizeBytes() {
    final int sizeBytes =
        KllSketch.getMaxSerializedSizeBytes(KllSketch.DEFAULT_K, 1L << 30, KLL_DOUBLES_SKETCH, true);
    assertEquals(sizeBytes, 5708);
  }

  @Test
  public void getMaxUpdatableFloatsSerializedSizeBytes() {
    final int sizeBytes =
        KllSketch.getMaxSerializedSizeBytes(KllSketch.DEFAULT_K, 1L << 30, KLL_FLOATS_SKETCH, true);
    assertEquals(sizeBytes, 2912);
  }

  @Test
  public void getMaxUpdatableItemsSerializedSizeBytes() {
    try {
      KllSketch.getMaxSerializedSizeBytes(KllSketch.DEFAULT_K, 1L << 30, KLL_ITEMS_SKETCH, true);
    } catch (final SketchesArgumentException e) { }
  }

  @Test
  public void getStatsAtNumLevels() {
    final int k = 200;
    final int m = 8;
    final int numLevels = 23;
    final KllHelper.LevelStats lvlStats =
        KllHelper.getFinalSketchStatsAtNumLevels(k, m, numLevels, true);
    assertEquals(lvlStats.numItems, 697);
    assertEquals(lvlStats.n, 1257766904);
  }

  @Test
  public void getStatsAtNumLevels2() {
    final int k = 20;
    final int m = KllSketch.DEFAULT_M;
    final int numLevels = 2;
    final KllHelper.LevelStats lvlStats =
        KllHelper.getFinalSketchStatsAtNumLevels(k, m, numLevels, true);
    assertEquals(lvlStats.numLevels, 2);
    assertEquals(lvlStats.numItems, 33);
  }

  @Test
  public void testGetAllLevelStatsDoubles() {
    final long n = 1L << 30;
    final int k = 200;
    final int m = KllSketch.DEFAULT_M;
    final KllHelper.GrowthStats gStats =
        KllHelper.getGrowthSchemeForGivenN(k, m, n, KLL_DOUBLES_SKETCH, true);
    assertEquals(gStats.maxN, 1_257_766_904);
    assertEquals(gStats.numLevels, 23);
    assertEquals(gStats.maxItems, 697);
    assertEquals(gStats.compactBytes, 5704);
    assertEquals(gStats.updatableBytes, 5708);
  }

  @Test
  public void testGetAllLevelStatsFloats() {
    final long n = 1L << 30;
    final int k = 200;
    final int m = KllSketch.DEFAULT_M;
    final KllHelper.GrowthStats gStats =
        KllHelper.getGrowthSchemeForGivenN(k, m, n, KLL_FLOATS_SKETCH, true);
    assertEquals(gStats.maxN, 1_257_766_904);
    assertEquals(gStats.numLevels, 23);
    assertEquals(gStats.maxItems, 697);
    assertEquals(gStats.compactBytes, 2908);
    assertEquals(gStats.updatableBytes, 2912);
  }

  @Test
  public void testGetAllLevelStatsDoubles2() {
    final long n = 533;
    final int k = 200;
    final int m = KllSketch.DEFAULT_M;
    final KllHelper.GrowthStats gStats =
        KllHelper.getGrowthSchemeForGivenN(k, m, n, KLL_DOUBLES_SKETCH, true);
    assertEquals(gStats.maxN, 533);
    assertEquals(gStats.numLevels, 2);
    assertEquals(gStats.maxItems, 333);
    assertEquals(gStats.compactBytes, 2708);
    assertEquals(gStats.updatableBytes, 2712);
  }

  @Test
  public void testGetAllLevelStatsFloats2() {
    final long n = 533;
    final int k = 200;
    final int m = KllSketch.DEFAULT_M;
    final KllHelper.GrowthStats gStats =
        KllHelper.getGrowthSchemeForGivenN(k, m, n, KLL_FLOATS_SKETCH, true);
    assertEquals(gStats.maxN, 533);
    assertEquals(gStats.numLevels, 2);
    assertEquals(gStats.maxItems, 333);
    assertEquals(gStats.compactBytes, 1368);
    assertEquals(gStats.updatableBytes, 1372);
  }

  @Test
  public void testGetAllLevelStatsItems() {
    final long n = 533;
    final int k = 200;
    final int m = KllSketch.DEFAULT_M;
    try {
      KllHelper.getGrowthSchemeForGivenN(k, m, n, KLL_ITEMS_SKETCH, true);
    } catch (final SketchesArgumentException e) { }
  }

  /**
   * Println Object o
   * @param o object to print
   */
  static void println(final Object o) {
    //System.out.println(o.toString());
  }

}
