/*
 * 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.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.HashSet;

import org.apache.datasketches.quantilescommon.QuantilesDoublesSketchIteratorAPI;
import org.testng.annotations.Test;

/**
 * @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;
    final UpdatableQuantilesDoublesSketch[] sketchArr = new UpdatableQuantilesDoublesSketch[numSketches];

    //builds the input sketches, all on heap
    QuantilesDoublesSketch.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
    QuantilesDoublesSketch.setRandom(1); //make deterministic for test
    final QuantilesDoublesUnion hUnion = QuantilesDoublesUnion.builder().setMaxK(unionK).build();
    for (int s = 0; s < numSketches; s++) { hUnion.union(sketchArr[s]); }
    final QuantilesDoublesSketch hSketch = hUnion.getResult();

    //loads the direct union
    QuantilesDoublesSketch.setRandom(1); //make deterministic for test
    QuantilesDoublesUnion dUnion;
    QuantilesDoublesSketch dSketch;
    try (Arena arena = Arena.ofConfined()) {
      final MemorySegment wseg = arena.allocate(10_000_000);
      dUnion = QuantilesDoublesUnion.builder().setMaxK(8).build(wseg, null);
      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
    final int hCount = hSketch.getNumRetained();
    final int dCount = dSketch.getNumRetained();

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

    int hErrors = 0;
    int dErrors = 0;

    final QuantilesDoublesSketchIteratorAPI hit = hSketch.iterator();
    final QuantilesDoublesSketchIteratorAPI dit = dSketch.iterator();

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

      final 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 UpdatableQuantilesDoublesSketch buildHeapSketch(final int k, final int n, final int valueLimit,
      final HashSet<Double> set) {
    final UpdatableQuantilesDoublesSketch uSk = QuantilesDoublesSketch.builder().setK(k).build();
    for (int i = 0; i < n; i++) {
      final double value = QuantilesDoublesSketch.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(final String s) {
    print(s+LS);
  }

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

}
