
package uk.co.wingpath.util;


/**
* This class provides various static methods for manipulating byte arrays.
*/
public class Bytes
{
    private Bytes () {}

    /**
    * Puts a 16-bit integer into a byte array in MSB-first order.
    * @param n the integer to be stored.
    * @param buf byte array.
    * @param offset offset in buf at which to store value.
    */
    public static void fromShort (short n, byte [] buf, int offset)
    {
        buf [offset++] = (byte) (n >> 8);
        buf [offset] = (byte) n;
    }

    /**
    * Gets a 16-bit integer in MSB-first order from a byte array.
    * @param buf byte array.
    * @param offset offset in buf from which to get value.
    * @return the integer value.
    */
    public static short toShort (byte [] buf, int offset)
    {
        return (short) (((buf [offset] & 0xff) << 8) | (buf [offset + 1] & 0xff));
    }

    /**
    * Puts a 32-bit integer into a byte array in MSB-first order.
    * @param n the integer to be stored.
    * @param buf byte array.
    * @param offset offset in buf at which to store value.
    */
    public static void fromInt (int n, byte [] buf, int offset)
    {
        buf [offset++] = (byte) (n >> 24);
        buf [offset++] = (byte) (n >> 16);
        buf [offset++] = (byte) (n >> 8);
        buf [offset] = (byte) n;
    }

    /**
    * Gets a 32-bit integer in MSB-first order from a byte array.
    * @param buf byte array.
    * @param offset offset in buf from which to get value.
    * @return the integer value.
    */
    public static int toInt (byte [] buf, int offset)
    {
        return ((buf [offset] & 0xff) << 24)
            | ((buf [offset + 1] & 0xff) << 16)
            | ((buf [offset + 2] & 0xff) << 8)
            | (buf [offset + 3] & 0xff);
    }

    /**
    * Puts a 64-bit integer into a byte array in MSB-first order.
    * @param n the integer to be stored.
    * @param buf byte array.
    * @param offset offset in buf at which to store value.
    */
    public static void fromLong (long n, byte [] buf, int offset)
    {
        buf [offset++] = (byte) (n >> 56);
        buf [offset++] = (byte) (n >> 48);
        buf [offset++] = (byte) (n >> 40);
        buf [offset++] = (byte) (n >> 32);
        buf [offset++] = (byte) (n >> 24);
        buf [offset++] = (byte) (n >> 16);
        buf [offset++] = (byte) (n >> 8);
        buf [offset] = (byte) n;
    }

    /**
    * Gets a 64-bit integer in MSB-first order from a byte array.
    * @param buf byte array.
    * @param offset offset in buf from which to get value.
    * @return the integer value.
    */
    public static long toLong (byte [] buf, int offset)
    {
        return ((long) (buf [offset] & 0xff) << 56)
            | ((long) (buf [offset + 1] & 0xff) << 48)
            | ((long) (buf [offset + 2] & 0xff) << 40)
            | ((long) (buf [offset + 3] & 0xff) << 32)
            | ((long) (buf [offset + 4] & 0xff) << 24)
            | ((long) (buf [offset + 5] & 0xff) << 16)
            | ((long) (buf [offset + 6] & 0xff) << 8)
            | (long) (buf [offset + 7] & 0xff);
    }

    /**
    * Puts 32-bit floating-point number into a byte array.
    * @param n the float to be stored.
    * @param buf byte array.
    * @param offset offset in buf at which to store value.
    */
    public static void fromFloat (float n, byte [] buf, int offset)
    {
        fromInt (Float.floatToIntBits (n), buf, offset);
    }

    /**
    * Gets a 32-bit floating-point number from a byte array.
    * @param buf byte array.
    * @param offset offset in buf from which to get value.
    * @return the float value.
    */
    public static float toFloat (byte [] buf, int offset)
    {
        return Float.intBitsToFloat (toInt (buf, offset));
    }

    /**
    * Puts 64-bit floating-point number into a byte array.
    * @param n the double to be stored.
    * @param buf byte array.
    * @param offset offset in buf at which to store value.
    */
    public static void fromDouble (double n, byte [] buf, int offset)
    {
        fromLong (Double.doubleToLongBits (n), buf, offset);
    }

    /**
    * Gets a 64-bit floating-point number from a byte array.
    * @param buf byte array.
    * @param offset offset in buf from which to get value.
    * @return the double value.
    */
    public static double toDouble (byte [] buf, int offset)
    {
        return Double.longBitsToDouble (toLong (buf, offset));
    }

    /**
    * Swaps the odd and even bytes in a byte array.
    * @param data the byte array.
    */
    public static void byteSwap (byte [] data)
    {
        for (int i = 0 ; i < data.length - 1 ; i += 2)
        {
            byte b = data [i];
            data [i] = data [i + 1];
            data [i + 1] = b;
        }
    }

    /**
    * Performs a bitwise-AND of two byte arrays.
    * @param dest first operand of the 'AND', and where the result is stored.
    * @param src second operand of the 'AND'.
    */
    public static void and (byte [] dest, byte [] src)
    {
        for (int i = 0 ; i < dest.length ; i++)
            dest [i] &= src [i];
    }

    /**
    * Performs a bitwise-OR of two byte arrays.
    * @param dest first operand of the 'OR', and where the result is stored.
    * @param src second operand of the 'OR'.
    */
    public static void or (byte [] dest, byte [] src)
    {
        for (int i = 0 ; i < dest.length ; i++)
            dest [i] |= src [i];
    }

    /**
    * Performs a bitwise-XOR of two byte arrays.
    * @param dest first operand of the 'XOR', and where the result is stored.
    * @param src second operand of the 'XOR'.
    */
    public static void xor (byte [] dest, byte [] src)
    {
        for (int i = 0 ; i < dest.length ; i++)
            dest [i] ^= src [i];
    }

    /**
    * Performs a bitwise-AND_NOT of two byte arrays.
    * @param dest first operand of the 'AND_NOT', and where the result is
    * stored.
    * @param src second operand of the 'AND_NOT'.
    */
    public static void andNot (byte [] dest, byte [] src)
    {
        for (int i = 0 ; i < dest.length ; i++)
            dest [i] &= ~src [i];
    }

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

    /**
    * Converts part of a byte array to a hex string.
    * <p>The string will contain a space-separated sequence of
    * hex-digit-pairs, e.g. "a1 22 4c 2a".
    * @param a the byte array to be converted.
    * @param offset position in the array at which to start conversion.
    * @param count how many bytes to convert.
    * @return hex representation of the specified bytes.
    */ 
    public static String toHexString (byte [] a, int offset, int count)
    {
        StringBuilder str = new StringBuilder ();

        for (int i = 0 ; i < count ; i++)
        {
            if (i != 0)
                str.append (' ');
            byte b = a [offset + i];
            str.append (digits [(b >> 4) & 0x0f]);
            str.append (digits [b & 0x0f]);
        }

        if (str.length () == 0)
            return "";
        return str.toString ();
    }

    /**
    * Converts a byte array to a hex string.
    * <p>The string will contain a space-separated sequence of
    * hex-digit-pairs, e.g. "a1 22 4c 2a".
    * @param a the byte array to be converted.
    * @return hex representation of the byte array.
    */ 
    public static String toHexString (byte [] a)
    {
        return toHexString (a, 0, a.length);
    }

    /**
    * Converts a hex string to a byte array.
    * <p>The string should contain a space-separated sequence of
    * hex-digit-pairs, e.g. "a1 22 4c 2a".
    * @param str the string to be converted.
    * @return the byte array.
    * @throws ValueException if the string was not in the right format.
    */
    public static byte [] fromHexString (String str)
        throws ValueException
    {
        String msg = "Must be hex numbers in the range 00..ff";
        if (str.equals (""))
            return new byte [0];
        try
        {
            String [] a = str.split ("\\s+", 0);
            byte [] result = new byte [a.length];

            for (int i = 0 ; i < a.length ; i++)
            {
                int n = Integer.parseInt (a [i], 16);
                if (n < 0 || n > 255)
                    throw new ValueException (msg);
                result [i] = (byte) n;
            }

            return result;
        }
        catch (NumberFormatException e)
        {
            throw new ValueException (msg);
        }
    }

    /**
    * Performs a lexical comparison of two byte arrays.
    */
    public static int compare (byte [] a1, byte [] a2)
    {
        for (int i = 0 ; i < a1.length && i < a2.length ; ++i)
        {
            if ((a1 [i] & 0xff) < (a2 [i] & 0xff))
                return -1;
            if ((a1 [i] & 0xff) > (a2 [i] & 0xff))
                return 1;
        }

        if (a1.length < a2.length)
            return -1;
        if (a1.length > a2.length)
            return 1;
        return 0;
    }

    private static byte reverseBits (byte b)
    {
        byte r = 0;

        for (int i = 0 ; i < 8 ; i++)
        {
            r <<= 1;
            if ((b & 1) != 0)
                r |= 1;
            b >>= 1;
        }

        return r;
    }

    private static byte [] buildReverseBitTable ()
    {
        byte [] table = new byte [256];

        for (int i = 0 ; i < 256 ; i++)
            table [i] = reverseBits ((byte) i);

        return table;
    }

    private static byte [] reverseBitsTable = buildReverseBitTable ();

    /**
    * Reverses the order of the bits in each byte of a byte array.
    * @param data the byte array.
    */
    public static void reverseBits (byte [] data)
    {
        for (int i = 0 ; i < data.length ; i++)
            data [i] = reverseBitsTable [data [i] & 0xff];
    }
}

