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

import static java.lang.Math.ceil;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static org.apache.datasketches.common.Util.ceilingPowerOf2;
import static org.apache.datasketches.thetacommon.HashOperations.hashInsertOnly;
import static org.apache.datasketches.thetacommon.HashOperations.hashSearch;

import org.apache.datasketches.thetacommon.ThetaUtil;

class HashTables {
  private long[] hashTable = null;
  private double[][] valueTable = null;
  private int numValues = 0;
  private int lgTableSize = 0;
  private int numKeys = 0;

  //Construct from sketch
  HashTables(final ArrayOfDoublesSketch sketchIn) {
    numKeys = sketchIn.getRetainedEntries();
    numValues = sketchIn.getNumValues();

    lgTableSize = getLgTableSize(numKeys);
    final int tableSize = 1 << lgTableSize;
    hashTable = new long[tableSize];
    valueTable = new double[tableSize][];
    final ArrayOfDoublesSketchIterator it = sketchIn.iterator();

    while (it.next()) {
      final long hash = it.getKey();
      final int index = hashInsertOnly(hashTable, lgTableSize, hash);
      valueTable[index] = new double[numValues];
      System.arraycopy(it.getValues(), 0, valueTable[index], 0, numValues);
    }
  }

  //Construct: Load the hash and value tables from packed hash and value arrays
  private HashTables(final long[] hashArr, final double[][] valuesArr, final int numKeys, final int numValues) {
    this.numValues = numValues;
    this.numKeys = numKeys;
    lgTableSize = getLgTableSize(numKeys);

    final int tableSize = 1 << lgTableSize;
    hashTable = new long[tableSize];
    valueTable = new double[tableSize][];

    for (int i = 0; i < numKeys; i++) {
      final long hash = hashArr[i];
      final int index = hashInsertOnly(hashTable, lgTableSize, hash);
      valueTable[index] = new double[numValues];
      System.arraycopy(valuesArr[i], 0, valueTable[index], 0, numValues);
    }
  }

  HashTables getIntersectHashTables(
      final ArrayOfDoublesSketch nextTupleSketch,
      final long thetaLong,
      final ArrayOfDoublesCombiner combiner) {
    //Match nextSketch data with local instance data, filtering by theta
    final int maxMatchSize = min(numKeys, nextTupleSketch.getRetainedEntries());
    assert numValues == nextTupleSketch.numValues_;
    final long[] matchHashArr = new long[maxMatchSize];
    final double[][] matchValuesArr = new double[maxMatchSize][];

    //Copy the intersecting items from local hashTables_
    // sequentially into local packed matchHashArr_ and matchValuesArr
    int matchCount = 0;
    final ArrayOfDoublesSketchIterator it = nextTupleSketch.iterator();
    while (it.next()) {
      final long hash = it.getKey();
      if (hash >= thetaLong) { continue; }
      final int index = hashSearch(hashTable, lgTableSize, hash);
      if (index < 0) { continue; }
      matchHashArr[matchCount] = hash;
      matchValuesArr[matchCount] = combiner.combine(valueTable[index], it.getValues());
      matchCount++;
    }
    return new HashTables(matchHashArr, matchValuesArr, matchCount, numValues);
  }

  int getNumKeys() {
    return numKeys;
  }

  int getNumValues() {
    return numValues;
  }

  long[] getHashTable() {
    return hashTable;
  }

  double[][] getValueTable() {
    return valueTable;
  }

  void clear() {
    hashTable = null;
    valueTable = null;
    numValues = 0;
    lgTableSize = 0;
    numKeys = 0;
  }

  static int getLgTableSize(final int numKeys) {
    final int tableSize = max(ceilingPowerOf2((int) ceil(numKeys / 0.75)), 1 << ThetaUtil.MIN_LG_NOM_LONGS);
    return Integer.numberOfTrailingZeros(tableSize);
  }

}
