/*
 * 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 org.apache.datasketches.Util.DEFAULT_UPDATE_SEED;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;

import org.testng.annotations.Test;

import org.apache.datasketches.memory.WritableMemory;
import org.apache.datasketches.Family;
import org.apache.datasketches.Util;

/**
 * @author Lee Rhodes
 */
@SuppressWarnings("javadoc")
public class HeapAnotBTest {

  @Test
  public void checkExactAnotB_AvalidNoOverlap() {
    int k = 512;

    UpdateSketch usk1 = UpdateSketch.builder().setNominalEntries(k).build();
    UpdateSketch usk2 = UpdateSketch.builder().setNominalEntries(k).build();

    for (int i=0; i<(k/2); i++) {
      usk1.update(i);
    }
    for (int i=k/2; i<k; i++) {
      usk2.update(i);
    }

    AnotB aNb = SetOperation.builder().buildANotB();
    assertTrue(aNb.isEmpty());
    assertNull(aNb.getCache());
    assertEquals(aNb.getThetaLong(), Long.MAX_VALUE);
    assertEquals(aNb.getSeedHash(), Util.computeSeedHash(DEFAULT_UPDATE_SEED));

    aNb.update(usk1, usk2);
    assertEquals(aNb.getRetainedEntries(true), 256);

    CompactSketch rsk1;

    rsk1 = aNb.getResult(false, null);
    assertEquals(rsk1.getEstimate(), k/2.0);

    aNb.update(usk1, usk2);
    rsk1 = aNb.getResult(true, null);
    assertEquals(rsk1.getEstimate(), k/2.0);

    //getCurrentBytes( compact )
    int bytes = rsk1.getCurrentBytes(true);
    byte[] byteArray = new byte[bytes];
    WritableMemory mem = WritableMemory.wrap(byteArray);

    aNb.update(usk1, usk2);
    rsk1 = aNb.getResult(false, mem);
    assertEquals(rsk1.getEstimate(), k/2.0);

    aNb.update(usk1, usk2);
    rsk1 = aNb.getResult(true, mem);
    assertEquals(rsk1.getEstimate(), k/2.0);
  }

  @Test
  public void checkCombinations() {
    int k = 512;
    UpdateSketch aNull = null;
    UpdateSketch bNull = null;
    UpdateSketch aEmpty = UpdateSketch.builder().setNominalEntries(k).build();
    UpdateSketch bEmpty = UpdateSketch.builder().setNominalEntries(k).build();

    UpdateSketch aHT = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=0; i<k; i++) {
      aHT.update(i);
    }
    CompactSketch aC = aHT.compact(false, null);
    CompactSketch aO = aHT.compact(true,  null);

    UpdateSketch bHT = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=k/2; i<(k+(k/2)); i++)
     {
      bHT.update(i); //overlap is k/2
    }
    CompactSketch bC = bHT.compact(false, null);
    CompactSketch bO = bHT.compact(true,  null);

    CompactSketch res;
    AnotB aNb;
    boolean ordered = true;

    aNb = SetOperation.builder().buildANotB();

    aNb.update(aNull, bNull);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aNull, bEmpty);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aNull, bC);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aNull, bO);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aNull, bHT);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);


    aNb.update(aEmpty, bNull);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aEmpty, bEmpty);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aEmpty, bC);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aEmpty, bO);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aEmpty, bHT);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), 0.0);
    assertTrue(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);


    aNb.update(aC, bNull);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aC, bEmpty);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aC, bC);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aC, bO);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aC, bHT);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);


    aNb.update(aO, bNull);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aO, bEmpty);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aO, bC);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aO, bO);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aO, bHT);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);


    aNb.update(aHT, bNull);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aHT, bEmpty);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aHT, bC);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aHT, bO);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);

    aNb.update(aHT, bHT);
    res = aNb.getResult(!ordered, null);
    assertEquals(res.getEstimate(), (double) k/2);
    assertFalse(res.isEmpty());
    assertEquals(res.getThetaLong(), Long.MAX_VALUE);
  }

  @Test
  public void checkAnotBnotC() {
    int k = 1024;
    boolean ordered = true;

    UpdateSketch aU = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=0; i<k; i++) {
      aU.update(i);  //All 1024
    }

    UpdateSketch bU = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=0; i<(k/2); i++) {
      bU.update(i);  //512
    }

    UpdateSketch cU = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=k/2; i<((3*k)/4); i++) {
      cU.update(i);  //256
    }

    int memBytes = Sketch.getMaxUpdateSketchBytes(k);
    CompactSketch r1, r2;

    byte[] memArr1 = new byte[memBytes];
    byte[] memArr2 = new byte[memBytes];
    WritableMemory mem1 = WritableMemory.wrap(memArr1);
    WritableMemory mem2 = WritableMemory.wrap(memArr2);

    AnotB aNb = SetOperation.builder().buildANotB();
    aNb.update(aU, bU);
    r1 = aNb.getResult(ordered, mem1);

    aNb.update(r1, cU);
    r2 = aNb.getResult(ordered, mem2);
    double est = r2.getEstimate();
    println("est: "+est);
    assertEquals(est, k/4.0, 0.0);
  }

  @Test
  public void checkAnotBnotC_sameMemory() {
    int k = 1024;
    boolean ordered = true;

    UpdateSketch a = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=0; i<k; i++)
     {
      a.update(i);        //All 1024
    }

    UpdateSketch b = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=0; i<(k/2); i++)
     {
      b.update(i);      //512
    }

    UpdateSketch c = UpdateSketch.builder().setNominalEntries(k).build();
    for (int i=k/2; i<((3*k)/4); i++)
     {
      c.update(i);  //256
    }

    int memBytes = Sketch.getMaxCompactSketchBytes(a.getCurrentBytes(true));

    byte[] memArr = new byte[memBytes];
    WritableMemory mem = WritableMemory.wrap(memArr);

    CompactSketch r;
    AnotB aNb = SetOperation.builder().buildANotB();

    //Iterative loop: {
    aNb.update(a, b);
    r = aNb.getResult(ordered, mem);

    aNb.update(r, c);
    r = aNb.getResult(ordered, mem);
    //}

    double est = r.getEstimate();
    println("est: "+est);
    assertEquals(est, k/4.0, 0.0);
  }

  @Test
  public void checkAnotBsimple() {
    UpdateSketch skA = Sketches.updateSketchBuilder().build();
    UpdateSketch skB = Sketches.updateSketchBuilder().build();
    AnotB aNotB = Sketches.setOperationBuilder().buildANotB();
    CompactSketch csk = aNotB.aNotB(skA, skB);
    assertEquals(csk.getCurrentBytes(true), 8);
  }

  @Test
  public void checkGetResult() {
    UpdateSketch skA = Sketches.updateSketchBuilder().build();
    UpdateSketch skB = Sketches.updateSketchBuilder().build();

    AnotB aNotB = Sketches.setOperationBuilder().buildANotB();
    aNotB.update(skA, skB);
    CompactSketch csk = aNotB.getResult();
    assertEquals(csk.getCurrentBytes(true), 8);
  }

  @Test
  public void checkGetFamily() {
    //cheap trick
    HeapAnotB anotb = new HeapAnotB(Util.DEFAULT_UPDATE_SEED);
    assertEquals(anotb.getFamily(), Family.A_NOT_B);
  }

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

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

}
