/**
 * 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.mapred.nativetask.util;

import org.apache.hadoop.classification.InterfaceAudience;

import java.nio.charset.StandardCharsets;

@InterfaceAudience.Private
public class ReadWriteBuffer {
  private byte[] _buff;
  private int _writePoint;
  private int _readPoint;
  final static int CACHE_LINE_SIZE = 16;

  public ReadWriteBuffer(int length) {
    if (length > 0) {
      _buff = new byte[length];
    }
  }

  public ReadWriteBuffer() {
    _buff = new byte[CACHE_LINE_SIZE];
  }

  public ReadWriteBuffer(byte[] bytes) {
    _buff = bytes;
    _writePoint = 0;
    _readPoint = 0;
  }

  public void reset(byte[] newBuff) {
    _buff = newBuff;
    _writePoint = 0;
    _readPoint = 0;
  }

  public void setReadPoint(int pos) {
    _readPoint = pos;
  }

  public void setWritePoint(int pos) {
    _writePoint = pos;
  }

  public byte[] getBuff() {
    return _buff;
  }

  public int getWritePoint() {
    return _writePoint;
  }

  public int getReadPoint() {
    return _readPoint;
  }

  public void writeInt(int v) {
    checkWriteSpaceAndResizeIfNecessary(4);

    _buff[_writePoint + 0] = (byte) ((v >>> 0) & 0xFF);
    _buff[_writePoint + 1] = (byte) ((v >>> 8) & 0xFF);
    _buff[_writePoint + 2] = (byte) ((v >>> 16) & 0xFF);
    _buff[_writePoint + 3] = (byte) ((v >>> 24) & 0xFF);

    _writePoint += 4;
  }

  public void writeLong(long v) {
    checkWriteSpaceAndResizeIfNecessary(8);

    _buff[_writePoint + 0] = (byte) (v >>> 0);
    _buff[_writePoint + 1] = (byte) (v >>> 8);
    _buff[_writePoint + 2] = (byte) (v >>> 16);
    _buff[_writePoint + 3] = (byte) (v >>> 24);
    _buff[_writePoint + 4] = (byte) (v >>> 32);
    _buff[_writePoint + 5] = (byte) (v >>> 40);
    _buff[_writePoint + 6] = (byte) (v >>> 48);
    _buff[_writePoint + 7] = (byte) (v >>> 56);

    _writePoint += 8;
  }

  public void writeBytes(byte b[], int off, int len) {
    writeInt(len);
    checkWriteSpaceAndResizeIfNecessary(len);
    System.arraycopy(b, off, _buff, _writePoint, len);
    _writePoint += len;
  }

  public int readInt() {
    final int ch4 = 0xff & (_buff[_readPoint + 0]);
    final int ch3 = 0xff & (_buff[_readPoint + 1]);
    final int ch2 = 0xff & (_buff[_readPoint + 2]);
    final int ch1 = 0xff & (_buff[_readPoint + 3]);
    _readPoint += 4;
    return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
  }

  public long readLong() {
    final long result =
      ((_buff[_readPoint + 0] & 255) << 0) +
      ((_buff[_readPoint + 1] & 255) << 8) +
      ((_buff[_readPoint + 2] & 255) << 16) +
      ((long) (_buff[_readPoint + 3] & 255) << 24) +
      ((long) (_buff[_readPoint + 4] & 255) << 32) +
      ((long) (_buff[_readPoint + 5] & 255) << 40) +
      ((long) (_buff[_readPoint + 6] & 255) << 48) +
      (((long) _buff[_readPoint + 7] << 56));

    _readPoint += 8;
    return result;
  }

  public byte[] readBytes() {
    final int length = readInt();
    final byte[] result = new byte[length];
    System.arraycopy(_buff, _readPoint, result, 0, length);
    _readPoint += length;
    return result;
  }

  public void writeString(String str) {
    final byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
    writeBytes(bytes, 0, bytes.length);
  }

  public String readString() {
    final byte[] bytes = readBytes();
    return new String(bytes, StandardCharsets.UTF_8);
  }

  private void checkWriteSpaceAndResizeIfNecessary(int toBeWritten) {

    if (_buff.length - _writePoint >= toBeWritten) {
      return;
    }
    final int newLength = (toBeWritten + _writePoint > CACHE_LINE_SIZE) ?
      (toBeWritten + _writePoint) : CACHE_LINE_SIZE;
    final byte[] newBuff = new byte[newLength];
    System.arraycopy(_buff, 0, newBuff, 0, _writePoint);
    _buff = newBuff;
  }

};
