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

import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static org.apache.datasketches.theta.ConcurrentHeapQuickSelectSketchTest.waitForBgPropagationToComplete;
import static org.apache.datasketches.theta.PreambleUtil.FAMILY_BYTE;
import static org.apache.datasketches.theta.PreambleUtil.LG_NOM_LONGS_BYTE;
import static org.apache.datasketches.theta.PreambleUtil.SER_VER_BYTE;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.lang.foreign.MemorySegment;

import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.Util;
import org.apache.datasketches.theta.ConcurrentHeapQuickSelectSketchTest.SharedLocal;
import org.apache.datasketches.thetacommon.HashOperations;
import org.testng.annotations.Test;

/**
 * @author eshcar
 */
public class ConcurrentDirectQuickSelectSketchTest {
  private static final long SEED = Util.DEFAULT_UPDATE_SEED;

  @Test
  public void checkDirectCompactConversion() {
    final int lgK = 9;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    assertTrue(sl.shared instanceof ConcurrentDirectQuickSelectSketch);
    assertTrue(sl.shared.compact().isCompact());
  }

  @Test
  public void checkHeapifyMemorySegmentEstimating() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final int u = 2*k;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);

    final UpdatableThetaSketch shared = sl.shared; //off-heap
    final UpdatableThetaSketch local = sl.local;

    for (int i=0; i<u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    assertEquals(shared.getClass().getSimpleName(), "ConcurrentDirectQuickSelectSketch");
    assertEquals(local.getClass().getSimpleName(), "ConcurrentHeapThetaBuffer");

    //This sharedHeap is not linked to the concurrent local buffer
    final UpdatableThetaSketch sharedHeap = UpdatableThetaSketch.heapify(sl.wseg);
    assertEquals(sharedHeap.getClass().getSimpleName(), "HeapQuickSelectSketch");

    checkMemorySegmentDirectProxyMethods(local, shared);
    checkOtherProxyMethods(local, shared);
    checkOtherProxyMethods(local, sharedHeap);

    final int curCount1 = shared.getRetainedEntries(true);
    final int curCount2 = sharedHeap.getRetainedEntries(true);
    assertEquals(curCount1, curCount2);
    final long[] cache = sharedHeap.getCache();
    final long thetaLong = sharedHeap.getThetaLong();
    final int cacheCount = HashOperations.count(cache, thetaLong);
    assertEquals(curCount1, cacheCount);
    assertEquals(local.getCurrentPreambleLongs(), 3);
  }

  @Test
  public void checkHeapifyByteArrayExact() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    for (int i=0; i< k; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    final byte[]  serArr = shared.toByteArray();
    final MemorySegment srcSeg = MemorySegment.ofArray(serArr).asReadOnly();
    final ThetaSketch recoveredShared = ThetaSketch.heapify(srcSeg);

    //reconstruct to Native/Direct
    final int bytes = ThetaSketch.getMaxUpdateSketchBytes(k);
    final MemorySegment wseg = MemorySegment.ofArray(new byte[bytes]);
    shared = sl.bldr.buildSharedFromSketch((UpdatableThetaSketch)recoveredShared, wseg);
    final UpdatableThetaSketch local2 = sl.bldr.buildLocal(shared);

    assertEquals(local2.getEstimate(), k, 0.0);
    assertEquals(local2.getLowerBound(2), k, 0.0);
    assertEquals(local2.getUpperBound(2), k, 0.0);
    assertEquals(local2.isEmpty(), false);
    assertEquals(local2.isEstimationMode(), false);
    assertEquals(recoveredShared.getClass().getSimpleName(), "HeapQuickSelectSketch");

    // Run toString just to make sure that we can pull out all of the relevant information.
    // That is, this is being run for its side-effect of accessing things.
    // If something is wonky, it will generate an exception and fail the test.
    local2.toString(true, true, 8, true);
  }

  @Test
  public void checkHeapifyByteArrayEstimating() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final int u = 2*k;

    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    for (int i=0; i<u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    final double uskEst = local.getEstimate();
    final double uskLB  = local.getLowerBound(2);
    final double uskUB  = local.getUpperBound(2);
    assertEquals(local.isEstimationMode(), true);

    final byte[]  serArr = shared.toByteArray();
    final MemorySegment srcSeg = MemorySegment.ofArray(serArr).asReadOnly();
    final ThetaSketch recoveredShared = ThetaSketch.heapify(srcSeg);

    //reconstruct to Native/Direct
    final int bytes = ThetaSketch.getMaxUpdateSketchBytes(k);
    final MemorySegment wseg = MemorySegment.ofArray(new byte[bytes]);
    shared = sl.bldr.buildSharedFromSketch((UpdatableThetaSketch)recoveredShared, wseg);
    final UpdatableThetaSketch local2 = sl.bldr.buildLocal(shared);

    assertEquals(local2.getEstimate(), uskEst);
    assertEquals(local2.getLowerBound(2), uskLB);
    assertEquals(local2.getUpperBound(2), uskUB);
    assertEquals(local2.isEmpty(), false);
    assertEquals(local2.isEstimationMode(), true);
    assertEquals(recoveredShared.getClass().getSimpleName(), "HeapQuickSelectSketch");
  }

  @Test
  public void checkWrapMemorySegmentEst() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final int u = 2*k;
    //boolean estimating = (u > k);

    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    for (int i=0; i<u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    final double sk1est = local.getEstimate();
    final double sk1lb  = local.getLowerBound(2);
    final double sk1ub  = local.getUpperBound(2);
    assertTrue(local.isEstimationMode());

    final ThetaSketch local2 = ThetaSketch.wrap(sl.wseg);

    assertEquals(local2.getEstimate(), sk1est);
    assertEquals(local2.getLowerBound(2), sk1lb);
    assertEquals(local2.getUpperBound(2), sk1ub);
    assertEquals(local2.isEmpty(), false);
    assertTrue(local2.isEstimationMode());
  }

  @Test
  public void checkDQStoCompactForms() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final int u = 4*k;
    //boolean estimating = (u > k);
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    assertEquals(local.getClass().getSimpleName(), "ConcurrentHeapThetaBuffer");
    assertFalse(local.isOffHeap());
    assertTrue(local.hasMemorySegment());

    for (int i=0; i<u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    shared.rebuild(); //forces size back to k

    //get baseline values
    final double localEst = local.getEstimate();
    final double localLB  = local.getLowerBound(2);
    final double localUB  = local.getUpperBound(2);
    assertTrue(local.isEstimationMode());

    CompactThetaSketch csk;

    csk = shared.compact(false,  null);
    assertEquals(csk.getEstimate(), localEst);
    assertEquals(csk.getLowerBound(2), localLB);
    assertEquals(csk.getUpperBound(2), localUB);
    assertFalse(csk.isEmpty());
    assertTrue(csk.isEstimationMode());
    assertEquals(csk.getClass().getSimpleName(), "HeapCompactSketch");

    csk = shared.compact(true, null);
    assertEquals(csk.getEstimate(), localEst);
    assertEquals(csk.getLowerBound(2), localLB);
    assertEquals(csk.getUpperBound(2), localUB);
    assertFalse(csk.isEmpty());
    assertTrue(csk.isEstimationMode());
    assertEquals(csk.getClass().getSimpleName(), "HeapCompactSketch");

    final int bytes = shared.getCompactBytes();
    assertEquals(bytes, k*8 + (Family.COMPACT.getMaxPreLongs() << 3));
    final byte[] segArr2 = new byte[bytes];
    final MemorySegment seg2 = MemorySegment.ofArray(segArr2);

    csk = shared.compact(false,  seg2);
    assertEquals(csk.getEstimate(), localEst);
    assertEquals(csk.getLowerBound(2), localLB);
    assertEquals(csk.getUpperBound(2), localUB);
    assertFalse(csk.isEmpty());
    assertTrue(csk.isEstimationMode());
    assertEquals(csk.getClass().getSimpleName(), "DirectCompactSketch");

    Util.clear(seg2);
    csk = shared.compact(true, seg2);
    assertEquals(csk.getEstimate(), localEst);
    assertEquals(csk.getLowerBound(2), localLB);
    assertEquals(csk.getUpperBound(2), localUB);
    assertFalse(csk.isEmpty());
    assertTrue(csk.isEstimationMode());
    assertEquals(csk.getClass().getSimpleName(), "DirectCompactSketch");
    csk.toString(false, true, 0, false);
  }

  @Test
  public void checkDQStoCompactEmptyForms() {
    final int lgK = 9;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    //empty
    local.toString(false, true, 0, false); //exercise toString
    assertEquals(local.getClass().getSimpleName(), "ConcurrentHeapThetaBuffer");
    final double localEst = local.getEstimate();
    final double localLB  = local.getLowerBound(2);
    final double localUB  = local.getUpperBound(2);
    assertFalse(local.isEstimationMode());

    final int bytes = local.getCompactBytes(); //compact form
    assertEquals(bytes, 8);
    final byte[] segArr2 = new byte[bytes];
    final MemorySegment seg2 = MemorySegment.ofArray(segArr2);

    final CompactThetaSketch csk2 = shared.compact(false,  seg2);
    assertEquals(csk2.getEstimate(), localEst);
    assertEquals(csk2.getLowerBound(2), localLB);
    assertEquals(csk2.getUpperBound(2), localUB);
    assertTrue(csk2.isEmpty());
    assertFalse(csk2.isEstimationMode());
    assertTrue(csk2.isOrdered());
    final CompactThetaSketch csk3 = shared.compact(true, seg2);
    csk3.toString(false, true, 0, false);
    csk3.toString();
    assertEquals(csk3.getEstimate(), localEst);
    assertEquals(csk3.getLowerBound(2), localLB);
    assertEquals(csk3.getUpperBound(2), localUB);
    assertTrue(csk3.isEmpty());
    assertFalse(csk3.isEstimationMode());
    assertTrue(csk2.isOrdered());
  }

  @Test
  public void checkEstMode() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    assertTrue(local.isEmpty());
    final int u = 3*k;

    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);
    assertTrue(shared.getRetainedEntries(false) > k);
  }

  @Test
  public void checkErrorBounds() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    //Exact mode
    for (int i = 0; i < k; i++ ) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    double est = local.getEstimate();
    double lb = local.getLowerBound(2);
    double ub = local.getUpperBound(2);
    assertEquals(est, ub, 0.0);
    assertEquals(est, lb, 0.0);

    //Est mode
    final int u = 100*k;
    for (int i = k; i < u; i++ ) {
      local.update(i);
      local.update(i); //test duplicate rejection
    }
    waitForBgPropagationToComplete(shared);
    est = local.getEstimate();
    lb = local.getLowerBound(2);
    ub = local.getUpperBound(2);
    assertTrue(est <= ub);
    assertTrue(est >= lb);
  }


  @Test
  public void checkUpperAndLowerBounds() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final int u = 2*k;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    for (int i = 0; i < u; i++ ) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    final double est = local.getEstimate();
    final double ub = local.getUpperBound(1);
    final double lb = local.getLowerBound(1);
    assertTrue(ub > est);
    assertTrue(lb < est);
  }

  @Test
  public void checkRebuild() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final int u = 4*k;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    assertTrue(local.isEmpty());

    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    assertFalse(local.isEmpty());
    assertTrue(local.getEstimate() > 0.0);
    assertTrue(shared.getRetainedEntries(false) >= k);

    shared.rebuild();
    assertEquals(shared.getRetainedEntries(false), k);
    assertEquals(shared.getRetainedEntries(true), k);
    local.rebuild();
    assertEquals(shared.getRetainedEntries(false), k);
    assertEquals(shared.getRetainedEntries(true), k);
  }

  @Test
  public void checkResetAndStartingSubMultiple() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    assertTrue(local.isEmpty());

    final int u = 4*k;
    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    assertFalse(local.isEmpty());
    assertTrue(shared.getRetainedEntries(false) >= k);
    assertTrue(local.getThetaLong() < Long.MAX_VALUE);

    shared.reset();
    local.reset();
    assertTrue(local.isEmpty());
    assertEquals(shared.getRetainedEntries(false), 0);
    assertEquals(local.getEstimate(), 0.0, 0.0);
    assertEquals(local.getThetaLong(), Long.MAX_VALUE);
  }

  @Test
  public void checkExactModeMemorySegmentArr() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final int u = k;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;
    assertTrue(local.isEmpty());

    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    assertEquals(local.getEstimate(), u, 0.0);
    assertEquals(shared.getRetainedEntries(false), u);
  }

  @Test
  public void checkEstModeMemorySegmentArr() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;
    assertTrue(local.isEmpty());

    final int u = 3*k;
    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    final double est = local.getEstimate();
    assertTrue(est < u * 1.05 && est > u * 0.95);
    assertTrue(shared.getRetainedEntries(false) >= k);
  }

  @Test
  public void checkEstModeNativeMemorySegment() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;
    assertTrue(local.isEmpty());

    final int u = 3*k;
    for (int i = 0; i< u; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);
    final double est = local.getEstimate();
    assertTrue(est < u * 1.05 && est > u * 0.95);
    assertTrue(shared.getRetainedEntries(false) >= k);
  }

  @Test
  public void checkConstructReconstructFromMemorySegment() {
    final int lgK = 12;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;

    assertTrue(local.isEmpty());
    final int u = 3*k;

    for (int i = 0; i< u; i++) { local.update(i); } //force estimation
    waitForBgPropagationToComplete(shared);

    final double est1 = local.getEstimate();
    final int count1 = shared.getRetainedEntries(false);
    assertTrue(est1 < u * 1.05 && est1 > u * 0.95);
    assertTrue(count1 >= k);

    byte[] serArr;
    double est2;

    serArr = shared.toByteArray();
    final MemorySegment seg = MemorySegment.ofArray(serArr);
    final UpdatableThetaSketch recoveredShared = UpdatableThetaSketch.wrap(seg);

    //reconstruct to Native/Direct
    final int bytes = ThetaSketch.getMaxUpdateSketchBytes(k);
    final MemorySegment wseg = MemorySegment.ofArray(new byte[bytes]);
    shared = sl.bldr.buildSharedFromSketch(recoveredShared, wseg);
    final UpdatableThetaSketch local2 = sl.bldr.buildLocal(shared);
    est2 = local2.getEstimate();

    assertEquals(est2, est1, 0.0);
  }

  @Test
  public void checkNullMemorySegment() {
    final UpdatableThetaSketchBuilder bldr = new UpdatableThetaSketchBuilder();
    final UpdatableThetaSketch sk = bldr.build();
    for (int i = 0; i < 1000; i++) { sk.update(i); }
    final UpdatableThetaSketch shared = bldr.buildSharedFromSketch(sk, null);
    assertEquals(shared.getRetainedEntries(true), 1000);
    assertFalse(shared.hasMemorySegment());
  }

  //checks Alex's bug where lgArrLongs > lgNomLongs +1.
  @Test
  public void checkResizeInBigSeg() {
    final int lgK = 14;
    final int u = 1 << 20;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, SEED, useSeg, true, 8); //seg is 8X larger than needed
    final UpdatableThetaSketch local = sl.local;

    for (int i = 0; i < u; i++) { local.update(i); }
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkConstructorKtooSmall() {
    final int lgK = 3;
    final boolean useSeg = true;
    new SharedLocal(lgK, lgK, useSeg);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkConstructorSegTooSmall() {
    final int lgK = 4;
    final int k = 1 << lgK;
    final MemorySegment wseg = MemorySegment.ofArray(new byte[k/2]);
    final UpdatableThetaSketchBuilder bldr = new UpdatableThetaSketchBuilder();
    bldr.setLogNominalEntries(lgK);
    bldr.buildShared(wseg);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkHeapifyIllegalFamilyID_heapify() {
    final int lgK = 9;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    sl.wseg.set(JAVA_BYTE, FAMILY_BYTE, (byte) 0); //corrupt the Family ID byte
    //try to heapify the corrupted seg
    ThetaSketch.heapify(sl.wseg); //catch in ThetaSketch.constructHeapSketch
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkBadLgNomLongs() {
    final int lgK = 4;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    sl.wseg.set(JAVA_BYTE, LG_NOM_LONGS_BYTE, (byte) 3); //Corrupt LgNomLongs byte
    DirectQuickSelectSketch.writableWrap(sl.wseg, null, Util.DEFAULT_UPDATE_SEED);
  }

  @Test
  public void checkBackgroundPropagation() {
    final int lgK = 4;
    final int k = 1 << lgK;
    final int u = 10*k;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;
    assertTrue(local.isEmpty());
    final ConcurrentHeapThetaBuffer sk1 = (ConcurrentHeapThetaBuffer)local; //for internal checks

    int i = 0;
    for (; i< k; i++) {
      local.update(i);
    }
    waitForBgPropagationToComplete(shared);
    assertFalse(local.isEmpty());
    assertTrue(local.getEstimate() > 0.0);
    final long theta1 = ((ConcurrentSharedThetaSketch)shared).getVolatileTheta();

    for (; i< u; i++) {
      local.update(i);
    }
    waitForBgPropagationToComplete(shared);

    final long theta2 = ((ConcurrentSharedThetaSketch)shared).getVolatileTheta();
    final int entries = shared.getRetainedEntries(false);
    assertTrue(entries > k || theta2 < theta1,
        "entries="+entries+" k="+k+" theta1="+theta1+" theta2="+theta2);

    shared.rebuild();
    assertEquals(shared.getRetainedEntries(false), k);
    assertEquals(shared.getRetainedEntries(true), k);
    sk1.rebuild();
    assertEquals(shared.getRetainedEntries(false), k);
    assertEquals(shared.getRetainedEntries(true), k);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkBadSerVer() {
    final int lgK = 9;
    final int k = 1 << lgK;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    final UpdatableThetaSketch local = sl.local;
    assertTrue(local.isEmpty());

    for (int i = 0; i< k; i++) { local.update(i); }
    waitForBgPropagationToComplete(shared);

    assertFalse(local.isEmpty());
    assertEquals(local.getEstimate(), k, 0.0);
    assertEquals(shared.getRetainedEntries(false), k);

    sl.wseg.set(JAVA_BYTE, SER_VER_BYTE, (byte) 0); //corrupt the SerVer byte
    ThetaSketch.wrap(sl.wseg);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkWrapIllegalFamilyID_wrap() {
    final int lgK = 9;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);

    sl.wseg.set(JAVA_BYTE, FAMILY_BYTE, (byte) 0); //corrupt the FamilyID byte
    //try to wrap the corrupted seg
    ThetaSketch.wrap(sl.wseg); //catch in ThetaSketch.constructDirectSketch
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkWrapIllegalFamilyID_direct() {
    final int lgK = 9;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);

    sl.wseg.set(JAVA_BYTE, FAMILY_BYTE, (byte) 0); //corrupt the FamilyID byte
    //try to wrap the corrupted seg
    DirectQuickSelectSketch.writableWrap(sl.wseg, null, Util.DEFAULT_UPDATE_SEED);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkHeapifySeedConflict() {
    final int lgK = 9;
    final long seed1 = 1021;
    final long seed2 = Util.DEFAULT_UPDATE_SEED;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, seed1, useSeg, true, 1);
    final UpdatableThetaSketch shared = sl.shared;

    final MemorySegment srcSeg = MemorySegment.ofArray(shared.toByteArray()).asReadOnly();
    ThetaSketch.heapify(srcSeg, seed2);
  }

  @Test(expectedExceptions = SketchesArgumentException.class)
  public void checkCorruptLgNomLongs() {
    final int lgK = 4;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);

    sl.wseg.set(JAVA_BYTE, LG_NOM_LONGS_BYTE, (byte)2); //corrupt
    ThetaSketch.heapify(sl.wseg, Util.DEFAULT_UPDATE_SEED);
  }

  @Test(expectedExceptions = UnsupportedOperationException.class)
  public void checkIllegalHashUpdate() {
    final int lgK = 4;
    final boolean useSeg = true;
    final SharedLocal sl = new SharedLocal(lgK, lgK, useSeg);
    final UpdatableThetaSketch shared = sl.shared;
    shared.hashUpdate(1);
  }

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

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

  private static void checkMemorySegmentDirectProxyMethods(final ThetaSketch local, final ThetaSketch shared) {
    assertEquals(
        local.hasMemorySegment(),
        shared.hasMemorySegment());
    assertEquals(local.isOffHeap(), shared.isOffHeap());
  }

  //Does not check hasMemorySegment(), isOffHeap()
  private static void checkOtherProxyMethods(final ThetaSketch local, final ThetaSketch shared) {
    assertEquals(local.getCompactBytes(), shared.getCompactBytes());
    assertEquals(local.getCurrentBytes(), shared.getCurrentBytes());
    assertEquals(local.getEstimate(), shared.getEstimate());
    assertEquals(local.getLowerBound(2), shared.getLowerBound(2));
    assertEquals(local.getUpperBound(2), shared.getUpperBound(2));
    assertEquals(local.isEmpty(), shared.isEmpty());
    assertEquals(local.isEstimationMode(), shared.isEstimationMode());
  }

}
