/*
 * 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.tsfile.encoding.decoder;

import org.apache.tsfile.common.conf.TSFileConfig;
import org.apache.tsfile.common.conf.TSFileDescriptor;
import org.apache.tsfile.exception.encoding.TsFileDecodingException;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
import org.apache.tsfile.utils.Binary;
import org.apache.tsfile.utils.ReadWriteForEncodingUtils;
import org.apache.tsfile.utils.ReadWriteIOUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;

/**
 * Abstract class for all rle decoder. Decoding values according to following grammar: {@code
 * <length> <bitwidth> <encoded-data>}. For more information about rle format, see RleEncoder
 */
public abstract class RleDecoder extends Decoder {

  protected TSFileConfig config = TSFileDescriptor.getInstance().getConfig();

  /** mode to indicate current encoding type 0 - RLE 1 - BIT_PACKED. */
  protected Mode mode;

  /** bit width for bit-packing and rle to decode. */
  protected int bitWidth;

  /** number of data left for reading in current buffer. */
  protected int currentCount;

  /**
   * how many bytes for all encoded data like [{@code <bitwidth> <encoded-data>}] in inputstream.
   */
  protected int length;

  /**
   * a flag to indicate whether current pattern is end. false - need to start reading a new page
   * true - current page isn't over.
   */
  protected boolean isLengthAndBitWidthReaded;

  /** buffer to save data format like [{@code <bitwidth> <encoded-data>}] for decoder. */
  protected ByteBuffer byteCache;

  /** number of bit-packing group in which is saved in header. */
  protected int bitPackingNum;

  /** a constructor, init with endianType, default encoding is <code>TSEncoding.RLE</code>. */
  protected RleDecoder() {
    super(TSEncoding.RLE);
    reset();
  }

  @Override
  public void reset() {
    currentCount = 0;
    isLengthAndBitWidthReaded = false;
    bitPackingNum = 0;
    byteCache = ByteBuffer.allocate(0);
  }

  /**
   * get header for both rle and bit-packing current encode mode which is saved in first bit of
   * header.
   *
   * @return int value
   * @throws IOException cannot get header
   */
  public int getHeader() throws IOException {
    int header = ReadWriteForEncodingUtils.readUnsignedVarInt(byteCache);
    mode = (header & 1) == 0 ? Mode.RLE : Mode.BIT_PACKED;
    return header;
  }

  /**
   * get all encoded data according to mode.
   *
   * @throws IOException cannot read next value
   */
  protected void readNext() throws IOException {
    int header = getHeader();
    switch (mode) {
      case RLE:
        currentCount = header >> 1;
        readNumberInRle();
        break;
      case BIT_PACKED:
        callReadBitPackingBuffer(header);
        break;
      default:
        throw new TsFileDecodingException(
            String.format("tsfile-encoding IntRleDecoder: unknown encoding mode %s", mode));
    }
  }

  protected void callReadBitPackingBuffer(int header) throws IOException {
    int bitPackedGroupCount = header >> 1;
    // in last bit-packing group, there may be some useless value,
    // lastBitPackedNum indicates how many values is useful
    int lastBitPackedNum = ReadWriteIOUtils.read(byteCache);
    if (bitPackedGroupCount > 0) {

      currentCount =
          (bitPackedGroupCount - 1) * TSFileConfig.RLE_MIN_REPEATED_NUM + lastBitPackedNum;
      bitPackingNum = currentCount;
    } else {
      throw new TsFileDecodingException(
          String.format(
              "tsfile-encoding IntRleDecoder: bitPackedGroupCount %d, smaller than 1",
              bitPackedGroupCount));
    }
    readBitPackingBuffer(bitPackedGroupCount, lastBitPackedNum);
  }

  /**
   * read length and bit width of current package before we decode number.
   *
   * @param buffer ByteBuffer
   */
  protected void readLengthAndBitWidth(ByteBuffer buffer) {
    length = ReadWriteForEncodingUtils.readUnsignedVarInt(buffer);
    byte[] tmp = new byte[length];
    buffer.get(tmp, 0, length);
    byteCache = ByteBuffer.wrap(tmp);
    isLengthAndBitWidthReaded = true;
    bitWidth = ReadWriteIOUtils.read(byteCache);
    initPacker();
  }

  /**
   * Check whether there is number left for reading.
   *
   * @param buffer decoded data saved in ByteBuffer
   * @return true or false to indicate whether there is number left
   * @throws IOException cannot check next value
   */
  @Override
  public boolean hasNext(ByteBuffer buffer) throws IOException {
    if (currentCount > 0 || buffer.remaining() > 0 || hasNextPackage()) {
      return true;
    }
    return false;
  }

  /**
   * Check whether there is another pattern left for reading.
   *
   * @return true or false to indicate whether there is another pattern left
   */
  protected boolean hasNextPackage() {
    return currentCount > 0 || byteCache.remaining() > 0;
  }

  protected abstract void initPacker();

  /**
   * Read rle package and save them in buffer.
   *
   * @throws IOException cannot read number
   */
  protected abstract void readNumberInRle() throws IOException;

  /**
   * Read bit-packing package and save them in buffer.
   *
   * @param bitPackedGroupCount number of group number
   * @param lastBitPackedNum number of useful value in last group
   * @throws IOException cannot read bit pack
   */
  protected abstract void readBitPackingBuffer(int bitPackedGroupCount, int lastBitPackedNum)
      throws IOException;

  @Override
  public boolean readBoolean(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readBoolean is not supproted by RleDecoder");
  }

  @Override
  public short readShort(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readShort is not supproted by RleDecoder");
  }

  @Override
  public int readInt(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readInt is not supproted by RleDecoder");
  }

  @Override
  public long readLong(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readLong is not supproted by RleDecoder");
  }

  @Override
  public float readFloat(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readFloat is not supproted by RleDecoder");
  }

  @Override
  public double readDouble(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readDouble is not supproted by RleDecoder");
  }

  @Override
  public Binary readBinary(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readBinary is not supproted by RleDecoder");
  }

  @Override
  public BigDecimal readBigDecimal(ByteBuffer buffer) {
    throw new TsFileDecodingException("Method readBigDecimal is not supproted by RleDecoder");
  }

  protected enum Mode {
    RLE,
    BIT_PACKED
  }
}
