/*
 * 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.MemorySegmentStatus.isSameResource;
import static org.apache.datasketches.quantiles.PreambleUtil.COMBINED_BUFFER;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;

import org.testng.annotations.Test;

/**
 * The concept for these tests is that the "MemorySegment Manager" classes below are proxies for the
 * implementation that <i>owns</i> the native MemorySegment allocations, thus is responsible for
 * allocating larger MemorySegment when requested and the actual freeing of the old MemorySegment allocations.
 */
public class DirectQuantilesMemoryRequestTest {

  @Test
  public void checkLimitedMemoryScenarios() { //Requesting application
    final int k = 128;
    final int u = 40 * k;
    final int initBytes = ((2 * k) + 4) << 3; //just the BaseBuffer

    //########## Owning Implementation
    // This part would actually be part of the application owning the MemorySegment so it is faked here
    MemorySegment wseg;
    try (Arena arena = Arena.ofConfined()) {
      wseg = arena.allocate(initBytes);
      println("Initial seg size: " + wseg.byteSize());

      //########## Receiving Application
      // The receiving application has been given wseg to use for a sketch,
      // but alas, it is not ultimately large enough.
      final UpdatableQuantilesDoublesSketch usk = QuantilesDoublesSketch.builder().setK(k).build(wseg);
      assertTrue(usk.isEmpty());

      //Load the sketch
      for (int i = 0; i < u; i++) {
        // The sketch uses The MemorySegmentRequestto acquire more MemorySegment as needed,
        //  which, for the default, the new MemorySegments will be on-heap.
        usk.update(i);
      }
      final double result = usk.getQuantile(0.5);
      println("Result: " + result);
      assertEquals(result, u / 2.0, 0.05 * u); //Success

      //########## Owning Implementation
      //The actual MemorySegment has been re-allocated several times,
      // so the sketch is using a different object.
      final MemorySegment wseg2 = usk.getMemorySegment();
      println("\nFinal seg size: " + wseg2.byteSize());
    }
    assertFalse(wseg.scope().isAlive());
  }

  @Test
  public void checkGrowBaseBuf() {
    final int k = 128;
    final int u = 32; // don't need the BB to fill here
    final int initBytes = (4 + (u / 2)) << 3; // not enough to hold everything
    MemorySegment wseg;
    try (Arena arena = Arena.ofConfined()) {
      wseg = arena.allocate(initBytes);
      println("Initial seg size: " + wseg.byteSize());
      final UpdatableQuantilesDoublesSketch usk1 = QuantilesDoublesSketch.builder().setK(k).build(wseg);
      for (int i = 1; i <= u; i++) {
        usk1.update(i);
      }
      final int currentSpace = usk1.getCombinedBufferItemCapacity();
      println("curCombBufItemCap: " + currentSpace);
      assertEquals(currentSpace, 2 * k);
      assertTrue(wseg.scope().isAlive());
    }
    assertFalse(wseg.scope().isAlive());
  }

  @Test
  public void checkGrowCombBuf() {
    final int k = 128;
    final int u = (2 * k) - 1; //just to fill the BB
    final int initBytes = ((2 * k) + 4) << 3; //just room for BB
    MemorySegment wseg;
    try (Arena arena = Arena.ofConfined()) {
      wseg = arena.allocate(initBytes);
      println("Initial seg size: " + wseg.byteSize());
      final UpdatableQuantilesDoublesSketch usk1 = QuantilesDoublesSketch.builder().setK(k).build(wseg);
      for (int i = 1; i <= u; i++) {
        usk1.update(i);
      }
      final int currentSpace = usk1.getCombinedBufferItemCapacity();
      println("curCombBufItemCap: " + currentSpace);
      final double[] newCB = usk1.growCombinedBuffer(currentSpace, 3 * k);
      final int newSpace = usk1.getCombinedBufferItemCapacity();
      println("newCombBurItemCap: " + newSpace);
      assertEquals(newCB.length, 3 * k);
      assertTrue(wseg.scope().isAlive());
    }
    assertFalse(wseg.scope().isAlive());
  }

  @Test
  public void checkUpdatableStorageBytes() {
    final int k = 16;
    final int initBytes = QuantilesDoublesSketch.getUpdatableStorageBytes(k, 1);
    println("Predicted Updatable Storage Bytes: " + initBytes);
    final UpdatableQuantilesDoublesSketch usk1 = QuantilesDoublesSketch.builder().setK(k).build();
    usk1.update(1.0);
    final byte[] uarr = usk1.toByteArray();
    println("Actual Storage Bytes " + uarr.length);
    assertEquals(initBytes, uarr.length);
    assertEquals(initBytes, 64);
  }


  @Test
  public void checkGrowFromWrappedEmptySketch() {
    final int k = 16;
    final int n = 0;
    final int initBytes = QuantilesDoublesSketch.getUpdatableStorageBytes(k, n); //empty: 8 bytes
    final UpdatableQuantilesDoublesSketch usk1 = QuantilesDoublesSketch.builder().setK(k).build();
    final MemorySegment origSketchSeg = MemorySegment.ofArray(usk1.toByteArray()); //on heap
    MemorySegment wseg;
    try (Arena arena = Arena.ofConfined()) {
      wseg = arena.allocate(initBytes); //off heap
      MemorySegment.copy(origSketchSeg, 0, wseg, 0, initBytes);
      final UpdatableQuantilesDoublesSketch usk2 = DirectUpdateDoublesSketch.wrapInstance(wseg, null);
      assertTrue(isSameResource(wseg, usk2.getMemorySegment()));
      assertEquals(wseg.byteSize(), initBytes);
      assertTrue(wseg.isNative());
      assertTrue(usk2.isEmpty());

      //update the sketch forcing it to grow on-heap
      usk2.update(1.0);
      assertEquals(usk2.getN(), 1);
      final MemorySegment seg2 = usk2.getMemorySegment();
      assertFalse(isSameResource(wseg, seg2));
      assertFalse(seg2.isNative()); //should now be on-heap

      final int expectedSize = COMBINED_BUFFER + ((2 * k) << 3);
      assertEquals(seg2.byteSize(), expectedSize);
      assertTrue(wseg.scope().isAlive());
    }
    assertFalse(wseg.scope().isAlive());
  }

  @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
  }

}
