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

import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE_UNALIGNED;
import static java.lang.foreign.ValueLayout.JAVA_INT_UNALIGNED;
import static org.apache.datasketches.common.Util.clear;
import static org.apache.datasketches.hll.PreambleUtil.CUR_MIN_COUNT_INT;
import static org.apache.datasketches.hll.PreambleUtil.HIP_ACCUM_DOUBLE;
import static org.apache.datasketches.hll.PreambleUtil.extractCompactFlag;
import static org.apache.datasketches.hll.PreambleUtil.extractCurMin;
import static org.apache.datasketches.hll.PreambleUtil.extractCurMode;
import static org.apache.datasketches.hll.PreambleUtil.extractEmptyFlag;
import static org.apache.datasketches.hll.PreambleUtil.extractHipAccum;
import static org.apache.datasketches.hll.PreambleUtil.extractKxQ0;
import static org.apache.datasketches.hll.PreambleUtil.extractKxQ1;
import static org.apache.datasketches.hll.PreambleUtil.extractLgK;
import static org.apache.datasketches.hll.PreambleUtil.extractNumAtCurMin;
import static org.apache.datasketches.hll.PreambleUtil.extractOooFlag;
import static org.apache.datasketches.hll.PreambleUtil.extractRebuildCurMinNumKxQFlag;
import static org.apache.datasketches.hll.PreambleUtil.extractTgtHllType;
import static org.apache.datasketches.hll.PreambleUtil.insertAuxCount;
import static org.apache.datasketches.hll.PreambleUtil.insertCompactFlag;
import static org.apache.datasketches.hll.PreambleUtil.insertCurMin;
import static org.apache.datasketches.hll.PreambleUtil.insertEmptyFlag;
import static org.apache.datasketches.hll.PreambleUtil.insertHipAccum;
import static org.apache.datasketches.hll.PreambleUtil.insertKxQ0;
import static org.apache.datasketches.hll.PreambleUtil.insertKxQ1;
import static org.apache.datasketches.hll.PreambleUtil.insertLgArr;
import static org.apache.datasketches.hll.PreambleUtil.insertNumAtCurMin;
import static org.apache.datasketches.hll.PreambleUtil.insertOooFlag;
import static org.apache.datasketches.hll.PreambleUtil.insertRebuildCurMinNumKxQFlag;

import java.lang.foreign.MemorySegment;

import org.apache.datasketches.common.MemorySegmentStatus;
import org.apache.datasketches.common.SketchesArgumentException;

/**
 * @author Lee Rhodes
 */
abstract class DirectHllArray extends AbstractHllArray {
  MemorySegment wseg; //used for writable direct
  MemorySegment seg;  //used for compact, read-only direct
  Object segObj;      //used temporarily for byte-array
  final boolean compact;

  private static int checkSegCompactFlag(final MemorySegment wseg, final int lgConfigK) {
    assert !extractCompactFlag(wseg);
    return lgConfigK;
  }

  //MemorySegment must be already initialized and may have data. Writable, must not be Compact.
  DirectHllArray(final int lgConfigK, final TgtHllType tgtHllType, final MemorySegment wseg) {
    super(checkSegCompactFlag(wseg, lgConfigK), tgtHllType, CurMode.HLL);
    this.wseg = wseg;
    seg = wseg;
    segObj = wseg.toArray(JAVA_BYTE);
    compact = extractCompactFlag(seg);
    insertEmptyFlag(wseg, false);
  }

  //MemorySegment must already be initialized and should have data. Read-only. May be Compact or not
  DirectHllArray(final int lgConfigK, final TgtHllType tgtHllType, final MemorySegment seg, final boolean readOnly) {
    super(lgConfigK, tgtHllType, CurMode.HLL);
    wseg = null;
    this.seg = readOnly ? seg.asReadOnly() : seg;
    segObj = seg.toArray(JAVA_BYTE);
    compact = extractCompactFlag(seg);
  }

  //only called by DirectAuxHashMap
  final void updateMemorySegment(final MemorySegment newWseg) {
    wseg = newWseg;
    seg = newWseg;
    segObj = wseg.toArray(JAVA_BYTE);
  }

  @Override
  void addToHipAccum(final double delta) {
    checkReadOnly(wseg);
    final double hipAccum = seg.get(JAVA_DOUBLE_UNALIGNED, HIP_ACCUM_DOUBLE);
    wseg.set(JAVA_DOUBLE_UNALIGNED, HIP_ACCUM_DOUBLE, hipAccum + delta);
  }

  @Override
  void decNumAtCurMin() {
    checkReadOnly(wseg);
    int numAtCurMin = seg.get(JAVA_INT_UNALIGNED, CUR_MIN_COUNT_INT);
    wseg.set(JAVA_INT_UNALIGNED, CUR_MIN_COUNT_INT, --numAtCurMin);
  }

  @Override
  int getCurMin() {
    return extractCurMin(seg);
  }

  @Override
  CurMode getCurMode() {
    return extractCurMode(seg);
  }

  @Override
  double getHipAccum() {
    return extractHipAccum(seg);
  }

  @Override
  double getKxQ0() {
    return extractKxQ0(seg);
  }

  @Override
  double getKxQ1() {
    return extractKxQ1(seg);
  }

  @Override
  int getLgConfigK() {
    return extractLgK(seg);
  }

  @Override
  MemorySegment getMemorySegment() {
    return seg;
  }

  @Override
  AuxHashMap getNewAuxHashMap() {
    return new DirectAuxHashMap(this, true);
  }

  @Override
  int getNumAtCurMin() {
    return extractNumAtCurMin(seg);
  }

  @Override
  TgtHllType getTgtHllType() {
    return extractTgtHllType(seg);
  }

  @Override
  boolean isCompact() {
    return compact;
  }

  @Override
  boolean isEmpty() {
    return extractEmptyFlag(seg);
  }

  @Override
  boolean hasMemorySegment() {
    return seg.scope().isAlive();
  }

  @Override
  boolean isOffHeap() {
    return seg.isNative();
  }

  @Override
  boolean isOutOfOrder() {
    return extractOooFlag(seg);
  }

  @Override
  boolean isSameResource(final MemorySegment seg) {
    return MemorySegmentStatus.isSameResource(this.seg, seg);
  }

  @Override
  boolean isRebuildCurMinNumKxQFlag() {
    return extractRebuildCurMinNumKxQFlag(seg);
  }

  @Override
  void putAuxHashMap(final AuxHashMap auxHashMap, final boolean compact) {
    if (auxHashMap instanceof HeapAuxHashMap) {
      if (compact) {
        this.auxHashMap = auxHashMap; //heap and compact
      } else { //heap and not compact
        final int[] auxArr = auxHashMap.getAuxIntArr();
        MemorySegment.copy(auxArr, 0, wseg, JAVA_INT_UNALIGNED, auxStart, auxArr.length);
        insertLgArr(wseg, auxHashMap.getLgAuxArrInts());
        insertAuxCount(wseg, auxHashMap.getAuxCount());
        this.auxHashMap = new DirectAuxHashMap(this, false);
      }
    } else { //DirectAuxHashMap
      assert !compact; //must not be compact
      this.auxHashMap = auxHashMap; //In case of read-only this works.
    }
  }

  @Override
  void putCurMin(final int curMin) {
    checkReadOnly(wseg);
    insertCurMin(wseg, curMin);
  }

  @Override
  void putEmptyFlag(final boolean empty) {
    checkReadOnly(wseg);
    insertEmptyFlag(wseg, empty);
  }

  @Override
  void putHipAccum(final double hipAccum) {
    checkReadOnly(wseg);
    insertHipAccum(wseg, hipAccum);
  }

  @Override
  void putKxQ0(final double kxq0) {
    checkReadOnly(wseg);
    insertKxQ0(wseg, kxq0);
  }

  @Override //called very very very rarely
  void putKxQ1(final double kxq1) {
    checkReadOnly(wseg);
    insertKxQ1(wseg, kxq1);
  }

  @Override
  void putNumAtCurMin(final int numAtCurMin) {
    checkReadOnly(wseg);
    insertNumAtCurMin(wseg, numAtCurMin);
  }

  @Override //not used on the direct side
  void putOutOfOrder(final boolean oooFlag) {
    if (oooFlag) { putHipAccum(0); }
    checkReadOnly(wseg);
    insertOooFlag(wseg, oooFlag);
  }

  @Override
  void putRebuildCurMinNumKxQFlag(final boolean rebuild) {
    checkReadOnly(wseg);
    insertRebuildCurMinNumKxQFlag(wseg, rebuild);
  }

  @Override //used by HLL6 and HLL8, overridden by HLL4
  byte[] toCompactByteArray() {
    return toUpdatableByteArray(); //indistinguishable for HLL6 and HLL8
  }

  @Override //used by HLL6 and HLL8, overridden by HLL4
  byte[] toUpdatableByteArray() {
    final int totBytes = getCompactSerializationBytes();
    final byte[] byteArr = new byte[totBytes];
    final MemorySegment segOut = MemorySegment.ofArray(byteArr);
    MemorySegment.copy(seg, 0, segOut, 0, totBytes);
    insertCompactFlag(segOut, false);
    return byteArr;
  }

  @Override
  HllSketchImpl reset() {
    checkReadOnly(wseg);
    insertEmptyFlag(wseg, true);
    final int bytes = HllSketch.getMaxUpdatableSerializationBytes(lgConfigK, tgtHllType);
    clear(wseg, 0, bytes);
    return DirectCouponList.newInstance(lgConfigK, tgtHllType, wseg);
  }

  private static final void checkReadOnly(final MemorySegment wseg) {
    if (wseg == null) {
      throw new SketchesArgumentException("Cannot modify a read-only sketch");
    }
  }
}
