package net.jacksum.zzadopt.de.flexiprovider.common.util;

import net.jacksum.zzadopt.de.flexiprovider.common.math.IntegerFunctions;

/**
 * This is a utility class containing data type conversions using big-endian
 * byte order.
 * 
 * see LittleEndianConversions
 */
public final class BigEndianConversions {

    /**
     * Default constructor (private).
     */
    private BigEndianConversions() {
	// empty
    }

    /**
     * Convert an integer to an octet string of length 4 according to IEEE 1363,
     * Section 5.5.3.
     * 
     * @param x
     *                the integer to convert
     * @return the converted integer
     */
    public static byte[] I2OSP(int x) {
	byte[] result = new byte[4];
	result[0] = (byte) (x >>> 24);
	result[1] = (byte) (x >>> 16);
	result[2] = (byte) (x >>> 8);
	result[3] = (byte) x;
	return result;
    }

    /**
     * Convert an integer to an octet string according to IEEE 1363, Section
     * 5.5.3. Length checking is performed.
     * 
     * @param x
     *                the integer to convert
     * @param oLen
     *                the desired length of the octet string
     * @return an octet string of length <code>oLen</code> representing the
     *         integer <code>x</code>, or <code>null</code> if the integer is
     *         negative
     * @throws ArithmeticException
     *                 if <code>x</code> can't be encoded into <code>oLen</code>
     *                 octets.
     */
    public static byte[] I2OSP(int x, int oLen) throws ArithmeticException {
	if (x < 0) {
	    return null;
	}
	int octL = IntegerFunctions.ceilLog256(x);
	if (octL > oLen) {
	    throw new ArithmeticException(
		    "Cannot encode given integer into specified number of octets.");
	}
	byte[] result = new byte[oLen];
	for (int i = oLen - 1; i >= oLen - octL; i--) {
	    result[i] = (byte) (x >>> (8 * (oLen - 1 - i)));
	}
	return result;
    }

    /**
     * Convert an integer to an octet string of length 4 according to IEEE 1363,
     * Section 5.5.3.
     * 
     * @param input
     *                the integer to convert
     * @param output
     *                byte array holding the output
     * @param outOff
     *                offset in output array where the result is stored
     */
    public static void I2OSP(int input, byte[] output, int outOff) {
	output[outOff++] = (byte) (input >>> 24);
	output[outOff++] = (byte) (input >>> 16);
	output[outOff++] = (byte) (input >>> 8);
	output[outOff] = (byte) input;
    }

    /**
     * Convert an integer to an octet string of length 8 according to IEEE 1363,
     * Section 5.5.3.
     * 
     * @param input
     *                the integer to convert
     * @return the converted integer
     */
    public static byte[] I2OSP(long input) {
	byte[] output = new byte[8];
	output[0] = (byte) (input >>> 56);
	output[1] = (byte) (input >>> 48);
	output[2] = (byte) (input >>> 40);
	output[3] = (byte) (input >>> 32);
	output[4] = (byte) (input >>> 24);
	output[5] = (byte) (input >>> 16);
	output[6] = (byte) (input >>> 8);
	output[7] = (byte) input;
	return output;
    }

    /**
     * Convert an integer to an octet string of length 8 according to IEEE 1363,
     * Section 5.5.3.
     * 
     * @param input
     *                the integer to convert
     * @param output
     *                byte array holding the output
     * @param outOff
     *                offset in output array where the result is stored
     */
    public static void I2OSP(long input, byte[] output, int outOff) {
	output[outOff++] = (byte) (input >>> 56);
	output[outOff++] = (byte) (input >>> 48);
	output[outOff++] = (byte) (input >>> 40);
	output[outOff++] = (byte) (input >>> 32);
	output[outOff++] = (byte) (input >>> 24);
	output[outOff++] = (byte) (input >>> 16);
	output[outOff++] = (byte) (input >>> 8);
	output[outOff] = (byte) input;
    }

    /**
     * Convert an integer to an octet string of the specified length according
     * to IEEE 1363, Section 5.5.3. No length checking is performed (i.e., if
     * the integer cannot be encoded into <code>length</code> octets, it is
     * truncated).
     * 
     * @param input
     *                the integer to convert
     * @param output
     *                byte array holding the output
     * @param outOff
     *                offset in output array where the result is stored
     * @param length
     *                the length of the encoding
     */
    public static void I2OSP(int input, byte[] output, int outOff, int length) {
	for (int i = length - 1; i >= 0; i--) {
	    output[outOff + i] = (byte) (input >>> (8 * (length - 1 - i)));
	}
    }

    /**
     * Convert an octet string to an integer according to IEEE 1363, Section
     * 5.5.3.
     * 
     * @param input
     *                the byte array holding the octet string
     * @return an integer representing the octet string <code>input</code>, or
     *         <code>0</code> if the represented integer is negative or too large
     *         or the byte array is empty
     * @throws ArithmeticException
     *                 if the length of the given octet string is larger than 4.
     */
    public static int OS2IP(byte[] input) {
	if (input.length > 4) {
	    throw new ArithmeticException("invalid input length");
	}
	if (input.length == 0) {
	    return 0;
	}
	int result = 0;
	for (int j = 0; j < input.length; j++) {
	    result |= (input[j] & 0xff) << (8 * (input.length - 1 - j));
	}
	return result;
    }

    /**
     * Convert a byte array of length 4 beginning at <code>offset</code> into an
     * integer.
     * 
     * @param input
     *                the byte array
     * @param inOff
     *                the offset into the byte array
     * @return the resulting integer
     */
    public static int OS2IP(byte[] input, int inOff) {
	int result = (input[inOff++] & 0xff) << 24;
	result |= (input[inOff++] & 0xff) << 16;
	result |= (input[inOff++] & 0xff) << 8;
	result |= input[inOff] & 0xff;
	return result;
    }

    /**
     * Convert an octet string to an integer according to IEEE 1363, Section
     * 5.5.3.
     * 
     * @param input
     *                the byte array holding the octet string
     * @param inOff
     *                the offset in the input byte array where the octet string
     *                starts
     * @param inLen
     *                the length of the encoded integer
     * @return an integer representing the octet string <code>bytes</code>, or
     *         <code>0</code> if the represented integer is negative or too large
     *         or the byte array is empty
     */
    public static int OS2IP(byte[] input, int inOff, int inLen) {
        if ((input.length == 0) || input.length < inOff + inLen - 1) {
            return 0;
        }
        int result = 0;
        for (int j = 0; j < inLen; j++) {
            result |= (input[inOff + j] & 0xff) << (8 * (inLen - j - 1));
        }
        return result;
    }

    /**
     * Convert a byte array of length 8 beginning at <code>inOff</code> into a
     * long integer.
     * 
     * @param input
     *                the byte array
     * @param inOff
     *                the offset into the byte array
     * @return the resulting long integer
     */
    public static long OS2LIP(byte[] input, int inOff) {
	long result = ((long) input[inOff++] & 0xff) << 56;
	result |= ((long) input[inOff++] & 0xff) << 48;
	result |= ((long) input[inOff++] & 0xff) << 40;
	result |= ((long) input[inOff++] & 0xff) << 32;
	result |= ((long) input[inOff++] & 0xff) << 24;
	result |= (input[inOff++] & 0xff) << 16;
	result |= (input[inOff++] & 0xff) << 8;
	result |= input[inOff] & 0xff;
	return result;
    }

    /**
     * Convert an int array into a byte array.
     * 
     * @param input
     *                the int array
     * @return the converted array
     */
    public static byte[] toByteArray(final int[] input) {
	byte[] result = new byte[input.length << 2];
	for (int i = 0; i < input.length; i++) {
	    I2OSP(input[i], result, i << 2);
	}
	return result;
    }

    /**
     * Convert an int array into a byte array of the specified length. No length
     * checking is performed (i.e., if the last integer cannot be encoded into
     * <code>length % 4</code> octets, it is truncated).
     * 
     * @param input
     *                the int array
     * @param length
     *                the length of the converted array
     * @return the converted array
     */
    public static byte[] toByteArray(final int[] input, int length) {
	final int intLen = input.length;
	byte[] result = new byte[length];
	int index = 0;
	for (int i = 0; i <= intLen - 2; i++, index += 4) {
	    I2OSP(input[i], result, index);
	}
	I2OSP(input[intLen - 1], result, index, length - index);
	return result;
    }

    /**
     * Convert a byte array into an int array.
     * 
     * @param input
     *                the byte array
     * @return the converted array
     */
    public static int[] toIntArray(byte[] input) {
	final int intLen = (input.length + 3) / 4;
	final int lastLen = input.length & 0x03;
	int[] result = new int[intLen];

	int index = 0;
	for (int i = 0; i <= intLen - 2; i++, index += 4) {
	    result[i] = OS2IP(input, index);
	}
	if (lastLen != 0) {
	    result[intLen - 1] = OS2IP(input, index, lastLen);
	} else {
	    result[intLen - 1] = OS2IP(input, index);
	}

	return result;
    }

}
