package de.flexiprovider.common.util;

import de.flexiprovider.common.math.MathFunctions;

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

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

    /**
     * Convert an octet string of length 4 to an integer. No length checking is
     * performed.
     * 
     * @param input
     *                the byte array holding the octet string
     * @return an integer representing the octet string <tt>input</tt>
     * @throws ArithmeticException
     *                 if the length of the given octet string is larger than 4.
     */
    public static int OS2IP(byte[] input) {
	return ((input[0] & 0xff)) | ((input[1] & 0xff) << 8)
		| ((input[2] & 0xff) << 16) | ((input[3] & 0xff)) << 24;
    }

    /**
     * Convert an byte array of length 4 beginning at <tt>offset</tt> 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;
	result |= (input[inOff++] & 0xff) << 8;
	result |= (input[inOff++] & 0xff) << 16;
	result |= (input[inOff] & 0xff) << 24;
	return result;
    }

    /**
     * Convert a byte array of the given length beginning at <tt>offset</tt>
     * into an integer.
     * 
     * @param input
     *                the byte array
     * @param inOff
     *                the offset into the byte array
     * @param inLen
     *                the length of the encoding
     * @return the resulting integer
     */
    public static int OS2IP(byte[] input, int inOff, int inLen) {
	int result = 0;
	for (int i = inLen - 1; i >= 0; i--) {
	    result |= (input[inOff + i] & 0xff) << (8 * i);
	}
	return result;
    }

    /**
     * Convert a byte array of length 8 beginning at <tt>inOff</tt> 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 = input[inOff++] & 0xff;
	result |= (input[inOff++] & 0xff) << 8;
	result |= (input[inOff++] & 0xff) << 16;
	result |= ((long) input[inOff++] & 0xff) << 24;
	result |= ((long) input[inOff++] & 0xff) << 32;
	result |= ((long) input[inOff++] & 0xff) << 40;
	result |= ((long) input[inOff++] & 0xff) << 48;
	result |= ((long) input[inOff++] & 0xff) << 56;
	return result;
    }

    /**
     * Convert an integer to an octet string of length 4.
     * 
     * @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;
	result[1] = (byte) (x >>> 8);
	result[2] = (byte) (x >>> 16);
	result[3] = (byte) (x >>> 24);
	return result;
    }

    /**
     * Convert an integer into a byte array beginning at the specified offset.
     * 
     * @param value
     *                the integer to convert
     * @param output
     *                the byte array to hold the result
     * @param outOff
     *                the integer offset into the byte array
     */
    public static void I2OSP(int value, byte[] output, int outOff) {
	output[outOff++] = (byte) value;
	output[outOff++] = (byte) (value >>> 8);
	output[outOff++] = (byte) (value >>> 16);
	output[outOff++] = (byte) (value >>> 24);
    }

    /**
     * Convert an integer to a byte array beginning at the specified offset. No
     * length checking is performed (i.e., if the integer cannot be encoded with
     * <tt>length</tt> octets, it is truncated).
     * 
     * @param value
     *                the integer to convert
     * @param output
     *                the byte array to hold the result
     * @param outOff
     *                the integer offset into the byte array
     * @param outLen
     *                the length of the encoding
     */
    public static void I2OSP(int value, byte[] output, int outOff, int outLen) {
	for (int i = outLen - 1; i >= 0; i--) {
	    output[outOff + i] = (byte) (value >>> (8 * i));
	}
    }

    /**
     * Convert an integer to a byte array of length 8.
     * 
     * @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;
	output[1] = (byte) (input >>> 8);
	output[2] = (byte) (input >>> 16);
	output[3] = (byte) (input >>> 24);
	output[4] = (byte) (input >>> 32);
	output[5] = (byte) (input >>> 40);
	output[6] = (byte) (input >>> 48);
	output[7] = (byte) (input >>> 56);
	return output;
    }

    /**
     * Convert an integer to a byte array of length 8.
     * 
     * @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;
	output[outOff++] = (byte) (input >>> 8);
	output[outOff++] = (byte) (input >>> 16);
	output[outOff++] = (byte) (input >>> 24);
	output[outOff++] = (byte) (input >>> 32);
	output[outOff++] = (byte) (input >>> 40);
	output[outOff++] = (byte) (input >>> 48);
	output[outOff] = (byte) (input >>> 56);
    }

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

    /**
     * Convert a byte array to an int array.
     * 
     * @param input
     *                the byte array
     * @return the converted array
     */
    public static int[] toIntArray(byte[] input) {
	int intLen = (input.length + 3) / 4;
	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;
    }
    
    
    /**
     * Converts a byte array of length <= 8 to a long value.
     * @param b
     * 			the byte array
     * @return the long value represented by the byte array
     */
    public static long toLong(byte[] b){
	if(b==null){
	    return 0;
	}
	if(b.length>8){
	    throw new ArithmeticException("Conversion to long value not possible: array to long.");
	}
	byte[] temp=b;
	if(b.length<8){
	    temp= new byte[8];
	    System.arraycopy(b, 0, temp, 0, b.length);
	}
	long result=OS2LIP(temp, 0);
	return result;
    }
    
    /**
     * Converts a byte array of length <= 4 to an int value.
     * @param b
     * 			the byte array
     * @return the int value represented by the byte array
     */
    public static int toInt(byte[] b){
	if(b==null){
	    return 0;
	}
	if(b.length>4){
	    throw new ArithmeticException("Conversion to int value not possible: array to long.");
	}
	byte[] temp=b;
	if(b.length<4){
	    temp= new byte[4];
	    System.arraycopy(b, 0, temp, 0, b.length);
	}
	int result=OS2IP(temp);
	return result;
    }
    
    /**
     * Converts a long value to a byte array of smallest possible length.
     * @param l	
     * 			the long value
     * @return the byte array representation of l
     */
    public static byte[] toVariableByteArray(long l){
	if(l==0){
	    return null;
	}
	byte[] temp=I2OSP(l);
	int bytes=8;
	if(l>0)
	    bytes=(int)Math.ceil((MathFunctions.log(l)+1)/8);
	if(bytes<8){
	    byte[] result=new byte[bytes];
	    System.arraycopy(temp, 0, result, 0, bytes);
	    return result;
	}else{
	    return temp;
	}
    }
    
    /**
     * Converts an int value to a byte array of smallest possible length.
     * @param l	
     * 			the long value
     * @return the byte array representation of l
     */
    public static byte[] toVariableByteArray(int i){
	if(i==0){
	    return null;
	}
	byte[] temp=I2OSP(i);
	int bytes=4;
	if(i>0)
	    bytes=(int)Math.ceil((MathFunctions.log(i)+1)/8);
	if(bytes<4){
	    byte[] result=new byte[bytes];
	    System.arraycopy(temp, 0, result, 0, bytes);
	    return result;
	}else{
	    return temp;
	}
    }

}
