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

import org.apache.commons.lang3.ArrayUtils;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.ClassSize;
import org.apache.yetus.audience.InterfaceAudience;

@InterfaceAudience.Private
public class IndividualBytesFieldCell implements ExtendedCell, Cloneable {

  private static final long FIXED_OVERHEAD = ClassSize.align(  // do alignment(padding gap)
        ClassSize.OBJECT              // object header
      + KeyValue.TIMESTAMP_TYPE_SIZE  // timestamp and type
      + Bytes.SIZEOF_LONG             // sequence id
      + 5 * ClassSize.REFERENCE);     // references to all byte arrays: row, family, qualifier, value, tags

  // The following fields are backed by individual byte arrays
  private final byte[] row;
  private final int rOffset;
  private final int rLength;
  private final byte[] family;
  private final int fOffset;
  private final int fLength;
  private final byte[] qualifier;
  private final int qOffset;
  private final int qLength;
  private final byte[] value;
  private final int vOffset;
  private final int vLength;
  private final byte[] tags;  // A byte array, rather than an array of org.apache.hadoop.hbase.Tag
  private final int tagsOffset;
  private final int tagsLength;

  // Other fields
  private long timestamp;
  private final byte type;  // A byte, rather than org.apache.hadoop.hbase.KeyValue.Type
  private long seqId;

  public IndividualBytesFieldCell(byte[] row, byte[] family, byte[] qualifier,
                                  long timestamp, KeyValue.Type type,  byte[] value) {
    this(row, family, qualifier, timestamp, type, 0L /* sequence id */, value, null /* tags */);
  }

  public IndividualBytesFieldCell(byte[] row, byte[] family, byte[] qualifier,
                                  long timestamp, KeyValue.Type type, long seqId, byte[] value, byte[] tags) {
    this(row, 0, ArrayUtils.getLength(row),
            family, 0, ArrayUtils.getLength(family),
            qualifier, 0, ArrayUtils.getLength(qualifier),
            timestamp, type, seqId,
            value, 0, ArrayUtils.getLength(value),
            tags, 0, ArrayUtils.getLength(tags));
  }

  public IndividualBytesFieldCell(byte[] row, int rOffset, int rLength,
                                  byte[] family, int fOffset, int fLength,
                                  byte[] qualifier, int qOffset, int qLength,
                                  long timestamp, KeyValue.Type type, long seqId,
                                  byte[] value, int vOffset, int vLength,
                                  byte[] tags, int tagsOffset, int tagsLength) {

    // Check row, family, qualifier and value
    KeyValue.checkParameters(row, rLength,     // row and row length
                             family, fLength,  // family and family length
                             qLength,          // qualifier length
                             vLength);         // value length

    // Check timestamp
    if (timestamp < 0) {
      throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + timestamp);
    }

    // Check tags
    RawCell.checkForTagsLength(tagsLength);
    checkArrayBounds(row, rOffset, rLength);
    checkArrayBounds(family, fOffset, fLength);
    checkArrayBounds(qualifier, qOffset, qLength);
    checkArrayBounds(value, vOffset, vLength);
    checkArrayBounds(tags, tagsOffset, tagsLength);
    // No local copy is made, but reference to the input directly
    this.row        = row;
    this.rOffset    = rOffset;
    this.rLength    = rLength;
    this.family     = family;
    this.fOffset    = fOffset;
    this.fLength    = fLength;
    this.qualifier  = qualifier;
    this.qOffset    = qOffset;
    this.qLength    = qLength;
    this.value      = value;
    this.vOffset    = vOffset;
    this.vLength    = vLength;
    this.tags       = tags;
    this.tagsOffset = tagsOffset;
    this.tagsLength = tagsLength;

    // Set others
    this.timestamp  = timestamp;
    this.type       = type.getCode();
    this.seqId      = seqId;
  }

  private void checkArrayBounds(byte[] bytes, int offset, int length) {
    if (offset < 0 || length < 0) {
      throw new IllegalArgumentException("Negative number! offset=" + offset + "and length=" + length);
    }
    if (bytes == null && (offset != 0 || length != 0)) {
      throw new IllegalArgumentException("Null bytes array but offset=" + offset + "and length=" + length);
    }
    if (bytes != null && bytes.length < offset + length) {
      throw new IllegalArgumentException("Out of bounds! bytes.length=" + bytes.length
        + ", offset=" + offset + ", length=" + length);
    }
  }

  private long heapOverhead() {
    return   FIXED_OVERHEAD
           + ClassSize.ARRAY                               // row      , can not be null
           + ((family    == null) ? 0 : ClassSize.ARRAY)   // family   , can be null
           + ((qualifier == null) ? 0 : ClassSize.ARRAY)   // qualifier, can be null
           + ((value     == null) ? 0 : ClassSize.ARRAY)   // value    , can be null
           + ((tags      == null) ? 0 : ClassSize.ARRAY);  // tags     , can be null
  }

  /**
   * Implement Cell interface
   */
  // 1) Row
  @Override
  public byte[] getRowArray() {
    // If row is null, the constructor will reject it, by {@link KeyValue#checkParameters()},
    // so it is safe to return row without checking.
    return row;
  }

  @Override
  public int getRowOffset() {
        return rOffset;
    }

  @Override
  public short getRowLength() {
    // If row is null or rLength is invalid, the constructor will reject it, by {@link KeyValue#checkParameters()},
    // so it is safe to call rLength and make the type conversion.
    return (short)(rLength);
  }

  // 2) Family
  @Override
  public byte[] getFamilyArray() {
    // Family could be null
    return (family == null) ? HConstants.EMPTY_BYTE_ARRAY : family;
  }

  @Override
  public int getFamilyOffset() {
    return fOffset;
  }

  @Override
  public byte getFamilyLength() {
    // If fLength is invalid, the constructor will reject it, by {@link KeyValue#checkParameters()},
    // so it is safe to make the type conversion.
    return (byte)(fLength);
  }

  // 3) Qualifier
  @Override
  public byte[] getQualifierArray() {
    // Qualifier could be null
    return (qualifier == null) ? HConstants.EMPTY_BYTE_ARRAY : qualifier;
  }

  @Override
  public int getQualifierOffset() {
    return qOffset;
  }

  @Override
  public int getQualifierLength() {
    return qLength;
  }

  // 4) Timestamp
  @Override
  public long getTimestamp() {
    return timestamp;
  }

  //5) Type
  @Override
  public byte getTypeByte() {
    return type;
  }

  //6) Sequence id
  @Override
  public long getSequenceId() {
    return seqId;
  }

  //7) Value
  @Override
  public byte[] getValueArray() {
    // Value could be null
    return (value == null) ? HConstants.EMPTY_BYTE_ARRAY : value;
  }

  @Override
  public int getValueOffset() {
    return vOffset;
  }

  @Override
  public int getValueLength() {
    return vLength;
  }

  // 8) Tags
  @Override
  public byte[] getTagsArray() {
    // Tags can could null
    return (tags == null) ? HConstants.EMPTY_BYTE_ARRAY : tags;
  }

  @Override
  public int getTagsOffset() {
    return tagsOffset;
  }

  @Override
  public int getTagsLength() {
    return tagsLength;
  }

  /**
   * Implement HeapSize interface
   */
  @Override
  public long heapSize() {
    // Size of array headers are already included into overhead, so do not need to include it for each byte array
    return   heapOverhead()                         // overhead, with array headers included
           + ClassSize.align(getRowLength())        // row
           + ClassSize.align(getFamilyLength())     // family
           + ClassSize.align(getQualifierLength())  // qualifier
           + ClassSize.align(getValueLength())      // value
           + ClassSize.align(getTagsLength());      // tags
  }

  /**
   * Implement Cloneable interface
   */
  @Override
  public Object clone() throws CloneNotSupportedException {
    return super.clone();  // only a shadow copy
  }

  @Override
  public void setSequenceId(long seqId) {
    if (seqId < 0) {
      throw new IllegalArgumentException("Sequence Id cannot be negative. ts=" + seqId);
    }
    this.seqId = seqId;
  }

  @Override
  public void setTimestamp(long ts) {
    if (ts < 0) {
      throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + ts);
    }
    this.timestamp = ts;
  }

  @Override
  public void setTimestamp(byte[] ts) {
    setTimestamp(Bytes.toLong(ts, 0));
  }

  @Override
  public String toString() {
    return CellUtil.toString(this, true);
  }
}
