/*
 * 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.datasketches.common;

/**
 * Useful methods for byte arrays.
 * @author Lee Rhodes
 */
public final class ByteArrayUtil {

  /**
   * No argument constructor.
   */
  public ByteArrayUtil() { }

  /**
   * Copies bytes from source to target with offsets on both the source and target.
   * @param source the given source
   * @param srcStart the source starting index
   * @param target the give target
   * @param tgtStart the target starting index
   * @param numBytes the number of bytes to be transferred.
   */
  public static void copyBytes(final byte[] source, final int srcStart,
      final byte[] target, final int tgtStart, final int numBytes) {
    Util.checkBounds(srcStart, numBytes, source.length);
    Util.checkBounds(tgtStart, numBytes, target.length);
    for (int i = 0, j = srcStart, k = tgtStart; i < numBytes; i++) {
      target[k++] = source[j++];
    }
  }

  /**
   * Get a <i>short</i> from the given byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>short</i>
   */
  public static short getShortLE(final byte[] array, final int offset) {
    return (short) ((array[offset    ] & 0XFF       )
                 | ((array[offset + 1] & 0XFF) <<  8));
  }

  /**
   * Put the source <i>short</i> into the destination byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>short</i>
   */
  public static void putShortLE(final byte[] array, final int offset, final short value) {
    array[offset    ] = (byte) (value       );
    array[offset + 1] = (byte) (value >>>  8);
  }

  /**
   * Get a <i>short</i> from the given byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>short</i>
   */
  public static short getShortBE(final byte[] array, final int offset) {
    return (short) ((array[offset + 1] & 0XFF       )
                 | ((array[offset    ] & 0XFF) <<  8));
  }

  /**
   * Put the source <i>short</i> into the destination byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>short</i>
   */
  public static void putShortBE(final byte[] array, final int offset, final short value) {
    array[offset + 1] = (byte) (value       );
    array[offset    ] = (byte) (value >>>  8);
  }

  /**
   * Get a <i>int</i> from the given byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>int</i>
   */
  public static int getIntLE(final byte[] array, final int offset) {
    return  ( array[offset    ] & 0XFF       )
          | ((array[offset + 1] & 0XFF) <<  8)
          | ((array[offset + 2] & 0XFF) << 16)
          | ((array[offset + 3] & 0XFF) << 24);
  }

  /**
   * Put the source <i>int</i> into the destination byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>int</i>
   */
  public static void putIntLE(final byte[] array, final int offset, final int value) {
    array[offset    ] = (byte) (value       );
    array[offset + 1] = (byte) (value >>>  8);
    array[offset + 2] = (byte) (value >>> 16);
    array[offset + 3] = (byte) (value >>> 24);
  }

  /**
   * Get a <i>int</i> from the given byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>int</i>
   */
  public static int getIntBE(final byte[] array, final int offset) {
    return  ( array[offset + 3] & 0XFF       )
          | ((array[offset + 2] & 0XFF) <<  8)
          | ((array[offset + 1] & 0XFF) << 16)
          | ((array[offset    ] & 0XFF) << 24);
  }

  /**
   * Put the source <i>int</i> into the destination byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>int</i>
   */
  public static void putIntBE(final byte[] array, final int offset, final int value) {
    array[offset + 3] = (byte) (value       );
    array[offset + 2] = (byte) (value >>>  8);
    array[offset + 1] = (byte) (value >>> 16);
    array[offset    ] = (byte) (value >>> 24);
  }

  /**
   * Get a <i>long</i> from the given byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>long</i>
   */
  public static long getLongLE(final byte[] array, final int offset) {
    return  ( array[offset    ] & 0XFFL       )
          | ((array[offset + 1] & 0XFFL) <<  8)
          | ((array[offset + 2] & 0XFFL) << 16)
          | ((array[offset + 3] & 0XFFL) << 24)
          | ((array[offset + 4] & 0XFFL) << 32)
          | ((array[offset + 5] & 0XFFL) << 40)
          | ((array[offset + 6] & 0XFFL) << 48)
          | ((array[offset + 7] & 0XFFL) << 56);
  }

  /**
   * Put the source <i>long</i> into the destination byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>long</i>
   */
  public static void putLongLE(final byte[] array, final int offset, final long value) {
    array[offset    ] = (byte) (value       );
    array[offset + 1] = (byte) (value >>>  8);
    array[offset + 2] = (byte) (value >>> 16);
    array[offset + 3] = (byte) (value >>> 24);
    array[offset + 4] = (byte) (value >>> 32);
    array[offset + 5] = (byte) (value >>> 40);
    array[offset + 6] = (byte) (value >>> 48);
    array[offset + 7] = (byte) (value >>> 56);
  }

  /**
   * Get a <i>long</i> from the source byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source starting point
   * @return the <i>long</i>
   */
  public static long getLongBE(final byte[] array, final int offset) {
    return  ( array[offset + 7] & 0XFFL       )
          | ((array[offset + 6] & 0XFFL) <<  8)
          | ((array[offset + 5] & 0XFFL) << 16)
          | ((array[offset + 4] & 0XFFL) << 24)
          | ((array[offset + 3] & 0XFFL) << 32)
          | ((array[offset + 2] & 0XFFL) << 40)
          | ((array[offset + 1] & 0XFFL) << 48)
          | ((array[offset    ] & 0XFFL) << 56);
  }

  /**
   * Put the source <i>long</i> into the destination byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination starting point
   * @param value source <i>long</i>
   */
  public static void putLongBE(final byte[] array, final int offset, final long value) {
    array[offset + 7] = (byte) (value       );
    array[offset + 6] = (byte) (value >>>  8);
    array[offset + 5] = (byte) (value >>> 16);
    array[offset + 4] = (byte) (value >>> 24);
    array[offset + 3] = (byte) (value >>> 32);
    array[offset + 2] = (byte) (value >>> 40);
    array[offset + 1] = (byte) (value >>> 48);
    array[offset    ] = (byte) (value >>> 56);
  }

  /**
   * Get a <i>float</i> from the given byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>float</i>
   */
  public static float getFloatLE(final byte[] array, final int offset) {
    return Float.intBitsToFloat(getIntLE(array, offset));
  }

  /**
   * Put the source <i>float</i> into the destination byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>float</i>
   */
  public static void putFloatLE(final byte[] array, final int offset, final float value) {
    putIntLE(array, offset, Float.floatToRawIntBits(value));
  }

  /**
   * Get a <i>float</i> from the given byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>float</i>
   */
  public static float getFloatBE(final byte[] array, final int offset) {
    return Float.intBitsToFloat(getIntBE(array, offset));
  }

  /**
   * Put the source <i>float</i> into the destination byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>float</i>
   */
  public static void putFloatBE(final byte[] array, final int offset, final float value) {
    putIntBE(array, offset, Float.floatToRawIntBits(value));
  }

  /**
   * Get a <i>double</i> from the given byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>double</i>
   */
  public static double getDoubleLE(final byte[] array, final int offset) {
    return Double.longBitsToDouble(getLongLE(array, offset));
  }

  /**
   * Put the source <i>double</i> into the destination byte array starting at the given offset
   * in little endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>double</i>
   */
  public static void putDoubleLE(final byte[] array, final int offset, final double value) {
    putLongLE(array, offset, Double.doubleToRawLongBits(value));
  }

  /**
   * Get a <i>double</i> from the given byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array source byte array
   * @param offset source offset
   * @return the <i>double</i>
   */
  public static double getDoubleBE(final byte[] array, final int offset) {
    return Double.longBitsToDouble(getLongBE(array, offset));
  }

  /**
   * Put the source <i>double</i> into the destination byte array starting at the given offset
   * in big endian order.
   * There is no bounds checking.
   * @param array destination byte array
   * @param offset destination offset
   * @param value source <i>double</i>
   */
  public static void putDoubleBE(final byte[] array, final int offset, final double value) {
    putLongBE(array, offset, Double.doubleToRawLongBits(value));
  }

}
