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

import static org.apache.datasketches.common.Util.LS;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import java.util.HashSet;

import org.testng.annotations.Test;

import java.lang.foreign.Arena;
import java.nio.ByteOrder;

import org.apache.datasketches.memory.WritableMemory;
import org.apache.datasketches.quantilescommon.QuantilesDoublesSketchIterator;

/**
 * @author Lee Rhodes
 */
public class DebugUnionTest {

  @Test
  public void test() {
    final int n = 70_000;
    final int valueLimit = 1000;
    final int numSketches = 3;
    final int sketchK = 8;
    final int unionK = 8;
    UpdateDoublesSketch[] sketchArr = new UpdateDoublesSketch[numSketches];

    //builds the input sketches, all on heap
    DoublesSketch.setRandom(1); //make deterministic for test
    final HashSet<Double> set = new HashSet<>(); //holds input values
    for (int s = 0; s < numSketches; s++) {
      sketchArr[s] = buildHeapSketch(sketchK, n, valueLimit, set);
    }

    //loads the on heap union
    DoublesSketch.setRandom(1); //make deterministic for test
    DoublesUnion hUnion = DoublesUnion.builder().setMaxK(unionK).build();
    for (int s = 0; s < numSketches; s++) { hUnion.union(sketchArr[s]); }
    DoublesSketch hSketch = hUnion.getResult();

    //loads the direct union
    DoublesSketch.setRandom(1); //make deterministic for test
    DoublesUnion dUnion;
    DoublesSketch dSketch;
    try (Arena arena = Arena.ofConfined()) {
      WritableMemory wmem = WritableMemory.allocateDirect(10_000_000, arena);
      dUnion = DoublesUnion.builder().setMaxK(8).build(wmem);
      for (int s = 0; s < numSketches; s++) { dUnion.union(sketchArr[s]); }
      dSketch = dUnion.getResult(); //result is on heap
    } catch (final Exception e) {
      throw new RuntimeException(e);
    }

    //iterates and counts errors
    int hCount = hSketch.getNumRetained();
    int dCount = dSketch.getNumRetained();

    assertEquals(hCount, dCount); //Retained items must be the same

    int hErrors = 0;
    int dErrors = 0;

    QuantilesDoublesSketchIterator hit = hSketch.iterator();
    QuantilesDoublesSketchIterator dit = dSketch.iterator();

    while (hit.next() && dit.next()) {
      double v = hit.getQuantile();
      if (!set.contains(v)) { hErrors++; }

      double w = dit.getQuantile();
      if (!set.contains(w)) { dErrors++; }
      assertEquals(v, w, 0); //Items must be returned in same order and be equal
    }
    assertTrue(hErrors == 0);
    assertTrue(dErrors == 0);

    println("HeapUnion  : Values: " + hCount + ", errors: " + hErrors);
    //println(hSketch.toString(true, true));

    println("DirectUnion: Values: " + dCount + ", errors: " + dErrors);
    //println(dSketch.toString(true, true));
  }

  private static UpdateDoublesSketch buildHeapSketch(final int k, final int n, final int valueLimit,
      final HashSet<Double> set) {
    final UpdateDoublesSketch uSk = DoublesSketch.builder().setK(k).build();
    for (int i = 0; i < n; i++) {
      final double value = DoublesSketch.rand.nextInt(valueLimit) + 1;
      uSk.update(value);
      set.add(value);
    }
    return uSk;
  }

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

  /**
   * @param s value to print
   */
  static void println(String s) {
    print(s+LS);
  }

  /**
   * @param s value to print
   */
  static void print(String s) {
    //System.out.print(s); //disable here
  }

}
