package com.google.flatbuffers;

import java.util.Arrays;

/**
 * Implements {@code ReadBuf} using an array of bytes as a backing storage. Using array of bytes are
 * usually faster than {@code ByteBuffer}.
 *
 * <p>This class is not thread-safe, meaning that it must operate on a single thread. Operating from
 * multiple thread leads into a undefined behavior
 */
public class ArrayReadWriteBuf implements ReadWriteBuf {

  private byte[] buffer;
  private int writePos;

  public ArrayReadWriteBuf() {
    this(10);
  }

  public ArrayReadWriteBuf(int initialCapacity) {
    this(new byte[initialCapacity]);
  }

  public ArrayReadWriteBuf(byte[] buffer) {
    this.buffer = buffer;
    this.writePos = 0;
  }

  public ArrayReadWriteBuf(byte[] buffer, int startPos) {
    this.buffer = buffer;
    this.writePos = startPos;
  }

  @Override
  public void clear() {
    this.writePos = 0;
  }

  @Override
  public boolean getBoolean(int index) {
    return buffer[index] != 0;
  }

  @Override
  public byte get(int index) {
    return buffer[index];
  }

  @Override
  public short getShort(int index) {
    return (short) ((buffer[index + 1] << 8) | (buffer[index] & 0xff));
  }

  @Override
  public int getInt(int index) {
    return (((buffer[index + 3]) << 24)
        | ((buffer[index + 2] & 0xff) << 16)
        | ((buffer[index + 1] & 0xff) << 8)
        | ((buffer[index] & 0xff)));
  }

  @Override
  public long getLong(int index) {
    return ((((long) buffer[index++] & 0xff))
        | (((long) buffer[index++] & 0xff) << 8)
        | (((long) buffer[index++] & 0xff) << 16)
        | (((long) buffer[index++] & 0xff) << 24)
        | (((long) buffer[index++] & 0xff) << 32)
        | (((long) buffer[index++] & 0xff) << 40)
        | (((long) buffer[index++] & 0xff) << 48)
        | (((long) buffer[index]) << 56));
  }

  @Override
  public float getFloat(int index) {
    return Float.intBitsToFloat(getInt(index));
  }

  @Override
  public double getDouble(int index) {
    return Double.longBitsToDouble(getLong(index));
  }

  @Override
  public String getString(int start, int size) {
    return Utf8Safe.decodeUtf8Array(buffer, start, size);
  }

  @Override
  public byte[] data() {
    return buffer;
  }

  @Override
  public void putBoolean(boolean value) {
    setBoolean(writePos, value);
    writePos++;
  }

  @Override
  public void put(byte[] value, int start, int length) {
    set(writePos, value, start, length);
    writePos += length;
  }

  @Override
  public void put(byte value) {
    set(writePos, value);
    writePos++;
  }

  @Override
  public void putShort(short value) {
    setShort(writePos, value);
    writePos += 2;
  }

  @Override
  public void putInt(int value) {
    setInt(writePos, value);
    writePos += 4;
  }

  @Override
  public void putLong(long value) {
    setLong(writePos, value);
    writePos += 8;
  }

  @Override
  public void putFloat(float value) {
    setFloat(writePos, value);
    writePos += 4;
  }

  @Override
  public void putDouble(double value) {
    setDouble(writePos, value);
    writePos += 8;
  }

  @Override
  public void setBoolean(int index, boolean value) {
    set(index, value ? (byte) 1 : (byte) 0);
  }

  @Override
  public void set(int index, byte value) {
    requestCapacity(index + 1);
    buffer[index] = value;
  }

  @Override
  public void set(int index, byte[] toCopy, int start, int length) {
    requestCapacity(index + (length - start));
    System.arraycopy(toCopy, start, buffer, index, length);
  }

  @Override
  public void setShort(int index, short value) {
    requestCapacity(index + 2);

    buffer[index++] = (byte) ((value) & 0xff);
    buffer[index] = (byte) ((value >> 8) & 0xff);
  }

  @Override
  public void setInt(int index, int value) {
    requestCapacity(index + 4);

    buffer[index++] = (byte) ((value) & 0xff);
    buffer[index++] = (byte) ((value >> 8) & 0xff);
    buffer[index++] = (byte) ((value >> 16) & 0xff);
    buffer[index] = (byte) ((value >> 24) & 0xff);
  }

  @Override
  public void setLong(int index, long value) {
    requestCapacity(index + 8);

    int i = (int) value;
    buffer[index++] = (byte) ((i) & 0xff);
    buffer[index++] = (byte) ((i >> 8) & 0xff);
    buffer[index++] = (byte) ((i >> 16) & 0xff);
    buffer[index++] = (byte) ((i >> 24) & 0xff);
    i = (int) (value >> 32);
    buffer[index++] = (byte) ((i) & 0xff);
    buffer[index++] = (byte) ((i >> 8) & 0xff);
    buffer[index++] = (byte) ((i >> 16) & 0xff);
    buffer[index] = (byte) ((i >> 24) & 0xff);
  }

  @Override
  public void setFloat(int index, float value) {
    requestCapacity(index + 4);

    int iValue = Float.floatToRawIntBits(value);
    buffer[index++] = (byte) ((iValue) & 0xff);
    buffer[index++] = (byte) ((iValue >> 8) & 0xff);
    buffer[index++] = (byte) ((iValue >> 16) & 0xff);
    buffer[index] = (byte) ((iValue >> 24) & 0xff);
  }

  @Override
  public void setDouble(int index, double value) {
    requestCapacity(index + 8);

    long lValue = Double.doubleToRawLongBits(value);
    int i = (int) lValue;
    buffer[index++] = (byte) ((i) & 0xff);
    buffer[index++] = (byte) ((i >> 8) & 0xff);
    buffer[index++] = (byte) ((i >> 16) & 0xff);
    buffer[index++] = (byte) ((i >> 24) & 0xff);
    i = (int) (lValue >> 32);
    buffer[index++] = (byte) ((i) & 0xff);
    buffer[index++] = (byte) ((i >> 8) & 0xff);
    buffer[index++] = (byte) ((i >> 16) & 0xff);
    buffer[index] = (byte) ((i >> 24) & 0xff);
  }

  @Override
  public int limit() {
    return writePos;
  }

  @Override
  public int writePosition() {
    return writePos;
  }

  @Override
  public boolean requestCapacity(int capacity) {
    if (capacity < 0) {
      throw new IllegalArgumentException(
          "Capacity may not be negative (likely a previous int overflow)");
    }
    if (buffer.length >= capacity) {
      return true;
    }
    // implemented in the same growing fashion as ArrayList
    int oldCapacity = buffer.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity < capacity) { // Note: this also catches newCapacity int overflow
      newCapacity = capacity;
    }
    buffer = Arrays.copyOf(buffer, newCapacity);
    return true;
  }
}
