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

import java.io.IOException;
import java.lang.reflect.Field;

import static org.apache.giraph.utils.ByteUtils.SIZE_OF_BOOLEAN;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_BYTE;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_CHAR;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_SHORT;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_INT;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_LONG;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_FLOAT;
import static org.apache.giraph.utils.ByteUtils.SIZE_OF_DOUBLE;

/**
 * Byte array input stream that uses Unsafe methods to deserialize
 * much faster
 */
@edu.umd.cs.findbugs.annotations.SuppressWarnings(
  "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
public class UnsafeArrayReads extends UnsafeReads {
  /** Access to the unsafe class */
  private static final sun.misc.Unsafe UNSAFE;
  static {
    try {
      Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      UNSAFE = (sun.misc.Unsafe) field.get(null);
      // Checkstyle exception due to needing to check if unsafe is allowed
      // CHECKSTYLE: stop IllegalCatch
    } catch (Exception e) {
      // CHECKSTYLE: resume IllegalCatch
      throw new RuntimeException("UnsafeArrayReads: Failed to " +
          "get unsafe", e);
    }
  }
  /** Offset of a byte array */
  private static final long BYTE_ARRAY_OFFSET  =
      UNSAFE.arrayBaseOffset(byte[].class);

  /**
   * Constructor
   *
   * @param buf Buffer to read from
   */
  public UnsafeArrayReads(byte[] buf) {
    super(buf.length);
    this.buffer = buf;
  }

  /**
   * Constructor.
   *
   * @param buf Buffer to read from
   * @param offset Offsetin the buffer to start reading from
   * @param length Max length of the buffer to read
   */
  public UnsafeArrayReads(byte[] buf, int offset, int length) {
    super(offset, length);
    this.buffer = buf;
  }

  @Override
  public int available() {
    return (int) (limit - position);
  }

  @Override
  public boolean endOfInput() {
    return available() == 0;
  }


  @Override
  public int getPos() {
    return (int) position;
  }

  @Override
  public void readFully(byte[] b) throws IOException {
    require(b.length);
    System.arraycopy(buffer, (int) position, b, 0, b.length);
    position += b.length;
  }

  @Override
  public void readFully(byte[] b, int off, int len) throws IOException {
    require(len);
    System.arraycopy(buffer, (int) position, b, off, len);
    position += len;
  }

  @Override
  public boolean readBoolean() {
    require(SIZE_OF_BOOLEAN);
    boolean value = UNSAFE.getBoolean(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_BOOLEAN;
    return value;
  }

  @Override
  public byte readByte() {
    require(SIZE_OF_BYTE);
    byte value = UNSAFE.getByte(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_BYTE;
    return value;
  }

  @Override
  public int readUnsignedByte() throws IOException {
    return (short) (readByte() & 0xFF);
  }

  @Override
  public short readShort() {
    require(SIZE_OF_SHORT);
    short value = UNSAFE.getShort(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_SHORT;
    return value;
  }

  @Override
  public int readUnsignedShort() throws IOException {
    return readShort() & 0xFFFF;
  }

  @Override
  public char readChar() {
    require(SIZE_OF_CHAR);
    char value = UNSAFE.getChar(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_CHAR;
    return value;
  }

  @Override
  public int readInt() {
    require(SIZE_OF_INT);
    int value = UNSAFE.getInt(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_INT;
    return value;
  }

  @Override
  public long readLong() {
    require(SIZE_OF_LONG);
    long value = UNSAFE.getLong(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_LONG;
    return value;
  }

  @Override
  public float readFloat() {
    require(SIZE_OF_FLOAT);
    float value = UNSAFE.getFloat(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_FLOAT;
    return value;
  }

  @Override
  public double readDouble() {
    require(SIZE_OF_DOUBLE);
    double value = UNSAFE.getDouble(buffer,
        BYTE_ARRAY_OFFSET + position);
    position += SIZE_OF_DOUBLE;
    return value;
  }

  /**
   * Get an int at an arbitrary position in a byte[]
   *
   * @param buf Buffer to get the int from
   * @param pos Position in the buffer to get the int from
   * @return Int at the buffer position
   */
  public static int getInt(byte[] buf, int pos) {
    return UNSAFE.getInt(buf,
        BYTE_ARRAY_OFFSET + pos);
  }
}
