/*
 * Copyright (c) 1998-2003 by The FlexiProvider Group,
 *                            Technische Universitaet Darmstadt 
 *
 * For conditions of usage and distribution please refer to the
 * file COPYING in the root directory of this package.
 *
 */

package de.flexiprovider.common.util;

/**
 * This class is a utility class for manipulating byte arrays.
 */
public final class ByteUtils {

    private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5',
	    '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * Default constructor (private)
     */
    private ByteUtils() {
	// empty
    }

    /**
     * Compare two byte arrays (perform null checks beforehand).
     * 
     * @param left
     *                the first byte array
     * @param right
     *                the second byte array
     * @return the result of the comparison
     */
    public static boolean equals(byte[] left, byte[] right) {
	if (left == null) {
	    return right == null;
	}
	if (right == null) {
	    return false;
	}

	if (left.length != right.length) {
	    return false;
	}
	boolean result = true;
	for (int i = left.length - 1; i >= 0; i--) {
	    result &= left[i] == right[i];
	}
	return result;
    }

    /**
     * Compare two two-dimensional byte arrays. No null checks are performed.
     * 
     * @param left
     *                the first byte array
     * @param right
     *                the second byte array
     * @return the result of the comparison
     */
    public static boolean equals(byte[][] left, byte[][] right) {
	if (left.length != right.length) {
	    return false;
	}

	boolean result = true;
	for (int i = left.length - 1; i >= 0; i--) {
	    result &= ByteUtils.equals(left[i], right[i]);
	}

	return result;
    }

    /**
     * Compare two three-dimensional byte arrays. No null checks are performed.
     * 
     * @param left
     *                the first byte array
     * @param right
     *                the second byte array
     * @return the result of the comparison
     */
    public static boolean equals(byte[][][] left, byte[][][] right) {
	if (left.length != right.length) {
	    return false;
	}

	boolean result = true;
	for (int i = left.length - 1; i >= 0; i--) {
	    if (left[i].length != right[i].length) {
		return false;
	    }
	    for (int j = left[i].length - 1; j >= 0; j--) {
		result &= ByteUtils.equals(left[i][j], right[i][j]);
	    }
	}

	return result;
    }

    /**
     * Computes a hashcode based on the contents of a one-dimensional byte array
     * rather than its identity.
     * 
     * @param array
     *                the array to compute the hashcode of
     * @return the hashcode
     */
    public static int deepHashCode(byte[] array) {
	int result = 1;
	for (int i = 0; i < array.length; i++) {
	    result = 31 * result + array[i];
	}
	return result;
    }

    /**
     * Computes a hashcode based on the contents of a two-dimensional byte array
     * rather than its identity.
     * 
     * @param array
     *                the array to compute the hashcode of
     * @return the hashcode
     */
    public static int deepHashCode(byte[][] array) {
	int result = 1;
	for (int i = 0; i < array.length; i++) {
	    result = 31 * result + deepHashCode(array[i]);
	}
	return result;
    }

    /**
     * Computes a hashcode based on the contents of a three-dimensional byte
     * array rather than its identity.
     * 
     * @param array
     *                the array to compute the hashcode of
     * @return the hashcode
     */
    public static int deepHashCode(byte[][][] array) {
	int result = 1;
	for (int i = 0; i < array.length; i++) {
	    result = 31 * result + deepHashCode(array[i]);
	}
	return result;
    }    
    
    
    /**
     * Return a clone of the given byte array (performs null check beforehand).
     * 
     * @param array
     *                the array to clone
     * @return the clone of the given array, or <tt>null</tt> if the array is
     *         <tt>null</tt>
     */
    public static byte[] clone(byte[] array) {
	if (array == null) {
	    return null;
	}
	byte[] result = new byte[array.length];
	System.arraycopy(array, 0, result, 0, array.length);
	return result;
    }

    /**
     * Convert a string containing hexadecimal characters to a byte-array.
     * 
     * @param s
     *                a hex string
     * @return a byte array with the corresponding value
     */
    public static byte[] fromHexString(String s) {
	char[] rawChars = s.toUpperCase().toCharArray();

	int hexChars = 0;
	for (int i = 0; i < rawChars.length; i++) {
	    if ((rawChars[i] >= '0' && rawChars[i] <= '9')
		    || (rawChars[i] >= 'A' && rawChars[i] <= 'F')) {
		hexChars++;
	    }
	}

	byte[] byteString = new byte[(hexChars + 1) >> 1];

	int pos = hexChars & 1;

	for (int i = 0; i < rawChars.length; i++) {
	    if (rawChars[i] >= '0' && rawChars[i] <= '9') {
		byteString[pos >> 1] <<= 4;
		byteString[pos >> 1] |= rawChars[i] - '0';
	    } else if (rawChars[i] >= 'A' && rawChars[i] <= 'F') {
		byteString[pos >> 1] <<= 4;
		byteString[pos >> 1] |= rawChars[i] - 'A' + 10;
	    } else {
		continue;
	    }
	    pos++;
	}

	return byteString;
    }

    /**
     * Convert a byte array to the corresponding hexstring.
     * 
     * @param input
     *                the byte array to be converted
     * @return the corresponding hexstring
     */
    public static String toHexString(byte[] input) {
	String result = "";
	for (int i = 0; i < input.length; i++) {
	    result += HEX_CHARS[(input[i] >>> 4) & 0x0f];
	    result += HEX_CHARS[(input[i]) & 0x0f];
	}
	return result;
    }

    /**
     * Convert a byte array to the corresponding hex string.
     * 
     * @param input
     *                the byte array to be converted
     * @param prefix
     *                the prefix to put at the beginning of the hex string
     * @param seperator
     *                a separator string
     * @return the corresponding hex string
     */
    public static String toHexString(byte[] input, String prefix,
	    String seperator) {
	String result = new String(prefix);
	for (int i = 0; i < input.length; i++) {
	    result += HEX_CHARS[(input[i] >>> 4) & 0x0f];
	    result += HEX_CHARS[(input[i]) & 0x0f];
	    if (i < input.length - 1) {
		result += seperator;
	    }
	}
	return result;
    }

    /**
     * Convert a byte array to the corresponding bit string.
     * 
     * @param input
     *                the byte array to be converted
     * @return the corresponding bit string
     */
    public static String toBinaryString(byte[] input) {
	String result = "";
	int i;
	for (i = 0; i < input.length; i++) {
	    int e = input[i];
	    for (int ii = 0; ii < 8; ii++) {
		int b = (e >>> ii) & 1;
		result += b;
	    }
	    if (i != input.length - 1) {
		result += " ";
	    }
	}
	return result;
    }
    
    /**
     * Convert a binary String into a byte array. The last byte of the array is
     * filled with zeros if necessary.
     * 
     * @param s
     *            the binary String to be converted
     * @return the byte array
     * 
     */
    public static byte[] fromBinaryString(String s) {
	int length = (int) Math.ceil((double) s.length() / 8.0);
	byte[] b = new byte[length];

	for (int j = 0; j < length; j++) {
	    String sub;
	    if (j * 8 + 8 < s.length())
		sub = s.substring(j * 8, j * 8 + 8);
	    else
		sub = s.substring(j * 8);

	    byte tmp = 0;
	    for (int i = 0; i < sub.length(); i++) {
		if (sub.charAt(i) == '1') {
		    tmp += 1 << i;
		}
	    }
	    b[j] = tmp;
	}
	return b;
    }

    /**
     * Compute the bitwise XOR of two arrays of bytes. The arrays have to be of
     * same length. No length checking is performed.
     * 
     * @param x1
     *                the first array
     * @param x2
     *                the second array
     * @return x1 XOR x2
     */
    public static byte[] xor(byte[] x1, byte[] x2) {
	byte[] out = new byte[x1.length];

	for (int i = x1.length - 1; i >= 0; i--) {
	    out[i] = (byte) (x1[i] ^ x2[i]);
	}
	return out;
    }

    /**
     * Concatenate two byte arrays. No null checks are performed.
     * 
     * @param x1
     *                the first array
     * @param x2
     *                the second array
     * @return (x2||x1) (little-endian order, i.e. x1 is at lower memory
     *         addresses)
     */
    public static byte[] concatenate(byte[] x1, byte[] x2) {
	byte[] result = new byte[x1.length + x2.length];

	System.arraycopy(x1, 0, result, 0, x1.length);
	System.arraycopy(x2, 0, result, x1.length, x2.length);

	return result;
    }

    /**
     * Convert a 2-dimensional byte array into a 1-dimensional byte array by
     * concatenating all entries.
     * 
     * @param array
     *                a 2-dimensional byte array
     * @return the concatenated input array
     */
    public static byte[] concatenate(byte[][] array) {
	int rowLength = array[0].length;
	byte[] result = new byte[array.length * rowLength];
	int index = 0;
	for (int i = 0; i < array.length; i++) {
	    System.arraycopy(array[i], 0, result, index, rowLength);
	    index += rowLength;
	}
	return result;
    }

    /**
     * Split a byte array <tt>input</tt> into two arrays at <tt>index</tt>,
     * i.e. the first array will have the lower <tt>index</tt> bytes, the
     * second one the higher <tt>input.length - index</tt> bytes.
     * 
     * @param input
     *                the byte array to be split
     * @param index
     *                the index where the byte array is split
     * @return the splitted input array as an array of two byte arrays
     * @throws ArrayIndexOutOfBoundsException
     *                 if <tt>index</tt> is out of bounds
     */
    public static byte[][] split(byte[] input, int index)
	    throws ArrayIndexOutOfBoundsException {
	if (index > input.length) {
	    throw new ArrayIndexOutOfBoundsException();
	}
	byte[][] result = new byte[2][];
	result[0] = new byte[index];
	result[1] = new byte[input.length - index];
	System.arraycopy(input, 0, result[0], 0, index);
	System.arraycopy(input, index, result[1], 0, input.length - index);
	return result;
    }
    
    /**
     * Split a byte array <tt>input</tt> into <tt>n</tt> arrays of length
     * <tt>length</tt>.
     * 
     * @param input
     *            the byte array to be split
     * @param n
     *            the number of resulting arrays
     * @param length
     *            the length of the resulting arrays
     * 
     * @return the splitted input array as an array of multiple byte arrays
     * @throws ArrayIndexOutOfBoundsException
     *             if <tt>n*length</tt> is out of bounds
     */
    public static byte[][] split(byte[] input, int n, int length)
	    throws ArrayIndexOutOfBoundsException {
	if (n*length > input.length) {
	    throw new ArrayIndexOutOfBoundsException();
	}
	byte[][] result = new byte[n][length];
	
	for(int i=0;i<n;i++){
	    System.arraycopy(input, i*length, result[i], 0, length);
	}
	return result;
    }

    

    /**
     * Generate a subarray of a given byte array.
     * 
     * @param input
     *                the input byte array
     * @param start
     *                the start index
     * @param end
     *                the end index
     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt>
     *         (inclusively) to <tt>end</tt> (exclusively)
     */
    public static byte[] subArray(byte[] input, int start, int end) {
	byte[] result = new byte[end - start];
	System.arraycopy(input, start, result, 0, end - start);
	return result;
    }

    /**
     * Generate a subarray of a given byte array.
     * 
     * @param input
     *                the input byte array
     * @param start
     *                the start index
     * @return a subarray of <tt>input</tt>, ranging from <tt>start</tt> to
     *         the end of the array
     */
    public static byte[] subArray(byte[] input, int start) {
	return subArray(input, start, input.length);
    }

    /**
     * Rewrite a byte array as a char array
     * 
     * @param input -
     *                the byte array
     * @return char array
     */
    public static char[] toCharArray(byte[] input) {
	char[] result = new char[input.length];
	for (int i = 0; i < input.length; i++) {
	    result[i] = (char) input[i];
	}
	return result;
    }

}
