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

import static org.apache.datasketches.common.Util.INVERSE_GOLDEN_U64;
import static org.apache.datasketches.common.Util.LS;
import static org.apache.datasketches.cpc.RuntimeAsserts.rtAssert;

import java.io.PrintStream;
import java.io.PrintWriter;

import org.apache.datasketches.common.SuppressFBWarnings;

/**
 * This code is used both by unit tests, for short running tests,
 * and by the characterization repository for longer running, more exhaustive testing. To be
 * accessible for both, this code is part of the main hierarchy. It is not used during normal
 * production runtime.
 *
 * <p>This test of merging is the equal K case and is less exhaustive than TestAlltest
 * but is more practical for large values of K.</p>
 *
 * @author Lee Rhodes
 * @author Kevin Lang
 */
public class QuickMergingValidation {
  private String hfmt;
  private String dfmt;
  private String[] hStrArr;
  private long vIn = 0;

  //inputs
  private int lgMinK;
  private int lgMaxK; //inclusive
  private int incLgK; //increment of lgK
  private PrintStream printStream;
  private PrintWriter printWriter;

  /**
   * Constructor
   * @param lgMinK lgMinK
   * @param lgMaxK lgMaxK
   * @param incLgK incLgK
   * @param ps ps
   * @param pw pw
   */
  @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "This is OK here")
  public QuickMergingValidation(final int lgMinK, final int lgMaxK, final int incLgK,
      final PrintStream ps, final PrintWriter pw) {
    this.lgMinK = lgMinK;
    this.lgMaxK = lgMaxK;
    this.incLgK = incLgK;
    printStream = ps;
    printWriter = pw;
    assembleFormats();
  }

  /**
   * Start the Quick Merging Validation process
   */
  public void start() {
    printf(hfmt, (Object[]) hStrArr); //print header
    doRangeOfLgK();
  }

  private void doRangeOfLgK() {
    for (int lgK = lgMinK; lgK <= lgMaxK; lgK += incLgK) {
      multiQuickTest(lgK);
    }
  }

  private void multiQuickTest(final int lgK) {
    final int k = 1 << lgK;
    final int[] targetC = new int[] { 0, 1, ((3 * k) / 32) - 1, k / 3, k, (7 * k) / 2 };
    final int len = targetC.length;
    for (int i = 0; i < len; i++) {
      for (int j = 0; j < len; j++) {
        quickTest(lgK, targetC[i], targetC[j]);
      }
    }
  }

  void quickTest(final int lgK, final long cA, final long cB) {
    final CpcSketch skA = new CpcSketch(lgK);
    final CpcSketch skB = new CpcSketch(lgK);
    final CpcSketch skD = new CpcSketch(lgK); // direct sketch

    final long t0, t1, t2, t3, t4, t5;

    t0 = System.nanoTime();
    while (skA.numCoupons < cA) {
      final long in = vIn += INVERSE_GOLDEN_U64;
      skA.update(in);
      skD.update(in);
    }
    t1 = System.nanoTime();
    while (skB.numCoupons < cB) {
      final long in = vIn += INVERSE_GOLDEN_U64;
      skB.update(in);
      skD.update(in);
    }
    t2 = System.nanoTime();

    final CpcUnion ugM = new CpcUnion(lgK);
    ugM.update(skA);
    t3 = System.nanoTime();

    ugM.update(skB);
    t4 = System.nanoTime();

    final CpcSketch skR = ugM.getResult();
    t5 = System.nanoTime();

    rtAssert(TestUtil.specialEquals(skD, skR, false, true));
    final Flavor fA = skA.getFlavor();
    final Flavor fB = skB.getFlavor();
    final Flavor fR = skR.getFlavor();
    final String aOff = Integer.toString(skA.windowOffset);
    final String bOff = Integer.toString(skB.windowOffset);
    final String rOff = Integer.toString(skR.windowOffset);
    final String fAoff = fA + String.format("%2s",aOff);
    final String fBoff = fB + String.format("%2s",bOff);
    final String fRoff = fR + String.format("%2s",rOff);
    final double updA_mS = (t1 - t0) / 2E6;  //update A,D to cA
    final double updB_mS = (t2 - t1) / 2E6;  //update B,D to cB
    final double mrgA_mS = (t3 - t2) / 1E6;  //merge A
    final double mrgB_mS = (t4 - t3) / 1E6;  //merge B
    final double rslt_mS = (t5 - t4) / 1E6;  //get Result

    printf(dfmt, lgK, cA, cB, fAoff, fBoff, fRoff,
        updA_mS, updB_mS, mrgA_mS, mrgB_mS, rslt_mS);
  }

  private void printf(final String format, final Object ... args) {
    if (printStream != null) { printStream.printf(format, args); }
    if (printWriter != null) { printWriter.printf(format, args); }
  }

  private void assembleFormats() {
    final String[][] assy = {
        {"lgK",         "%3s",  "%3d"},
        {"Ca",          "%10s", "%10d"},
        {"Cb",          "%10s", "%10d"},
        {"Flavor_a",    "%10s", "%10s"},
        {"Flavor_b",    "%10s", "%10s"},
        {"Flavor_m",    "%10s", "%10s"},
        {"updA_mS",     "%9s",  "%9.3f"},
        {"updB_mS",     "%9s",  "%9.3f"},
        {"mrgA_mS",     "%9s",  "%9.3f"},
        {"mrgB_mS",     "%9s",  "%9.3f"},
        {"rslt_mS",     "%9s",  "%9.3f"}
    };
    final int cols = assy.length;
    hStrArr = new String[cols];
    final StringBuilder headerFmt = new StringBuilder();
    final StringBuilder dataFmt = new StringBuilder();
    headerFmt.append(LS + "Quick Merging Validation" + LS);
    for (int i = 0; i < cols; i++) {
      hStrArr[i] = assy[i][0];
      headerFmt.append(assy[i][1]);
      headerFmt.append((i < (cols - 1)) ? "\t" : LS);
      dataFmt.append(assy[i][2]);
      dataFmt.append((i < (cols - 1)) ? "\t" : LS);
    }
    hfmt = headerFmt.toString();
    dfmt = dataFmt.toString();
  }
}
