package tools;
import localException.SizeError;
import localException.ProtocolError;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;


/** Helper to manipulate byte[].
 * @author	Louis Royer
 * @author	Flavien Haas
 * @author	JS Auge
 * @version	1.0
 */
public class BytesArrayTools {
	/** Write int in a bytearray
	 * @param array	the array to write
	 * @param start	where to begin writting
	 * @param value	int to write
	 */
	public static void write(byte[] array, int start, int value) {
		for(int i=0;i<4;i++) {
			array[start + i] = (byte) ((value >> (8 * (3 - i))) & 0xFF);
		}
	}
	/** Write int in a bytearray 16 bits version
	 * @param array	the array to write
	 * @param start	where to begin writting
	 * @param value	int to write
	 * @throws SizeError
	 */
	public static void write16Bits(byte[] array, int start, int value) throws SizeError {
		assert (value & 0xFFFF0000) == 0x00000000 : "Wrong size for value, value should be only 16 bits";
		if ((value & 0xFFFF0000) != 0x00000000){
			throw new SizeError();
		}
		for(int i=0;i<2;i++) {
			array[start + i] = (byte) ((value >> (8 * (1 - i))) & 0xFF);
		}
	}

	/** Write long in a bytearray
	 * @param array	the array to write
	 * @param start	where to begin writting
	 * @param value	long to write
	 */
	public static void write(byte[] array, int start, long value) {
		for(int i=0;i<8;i++) {
			array[start + i] = (byte) ((value >> (8 * (7 - i))) & 0xFF);
		}
	}

	/** Read int from a bytearray
	 * @param array	the array to read
	 * @param start	where to begin reading
	 * @return value read as int
	 * @throws SizeError
	 */
	public static int readInt(byte[] array, int start) throws SizeError {
		int size = 0;
		for(int i=0;i<4;i++) {
			size |= (0xFF & (int)array[start + i]) << (8* (3 -i));
		}
		if (size < 0) {
			// Size in array is probably correct
			// but we cannot store it into a int (or this will be negative)
			throw new SizeError();
		}
		return size;
	}

	/** Read int from a bytearray of 16 bits
	 * @param array	the array to read
	 * @param start	where to begin reading
	 * @return value read as 16 bits int
	 */
	public static int readInt16Bits(byte[] array, int start) {
		int size = 0;
		for(int i=0;i<2;i++) {
			try {
				size |= (0xFF & (int)array[start + i]) << (8* (1 -i));
			} catch (ArrayIndexOutOfBoundsException e){
				// 0 padding
			}
		}
		assert (size & 0xFFFF0000) == 0 : "Read on more than 16 bits";
		return size;
	}

	/** Read long from a bytearray
	 * @param array	the array to read
	 * @param start	where to begin reading
	 * @return value read as long
	 * @throws SizeError
	 */
	public static long readLong(byte[] array, int start) throws SizeError {
		long size = 0;
		for(int i=0;i<8;i++) {
			size |= (0xFF & (int)array[start + i]) << (8* (7 - i));
		}
		if (size < 0) {
			// Size in array is probably correct
			// but we cannot store it into a int (or this will be negative)
			throw new SizeError();
		}
		return size;
	}

	/** Remove trailing null bytes from string.
	 * @param str	input string
	 * @return	str without trailing null bytes
	 */
	public static String cleanStrings(String str) {
		int i = 0;
		int cpt = 0;
		byte[] bArray = str.getBytes();
		for (byte b : bArray) {
			if (b != 0) {
				cpt = i;
			}
			i++;
		}
		return new String(bArray, 0, cpt + 1);
	}

	/** Write a list of string to a byte array. String are separated by a separator.
	 * @param byteArray	array to write
	 * @param strArray	string array to read
	 * @param start	start position to write in byteArray
	 * @param separator	separator to use
	 * @throws	InternalError
	 */
	public static void write(byte[] byteArray, String[] strArray, int start, String separator) throws InternalError {
		int bCount = start;
		try {
			byte[] sepb = separator.getBytes("UTF-8");
			for(String s : strArray) {
				// write separator
				if (bCount != start) { // not on first iteration
					for (byte b : sepb) {
						byteArray[bCount] = b;
						bCount += 1;
					}
				}
				// Copy string
				byte[] sb = s.getBytes("UTF-8");
				for(byte b : sb) {
					byteArray[bCount] = b;
					bCount += 1;
				}
			}
		} catch (UnsupportedEncodingException e) {
			throw new InternalError();
		}
	}

	/** Read string array from byte array starting at start with size parameter.
	 * @param byteArray	Array to read
	 * @param start	Start position
	 * @param size	Size to read
	 * @param separator	Separator used
	 * @return	String array output
	 * @throws	InternalError
	 */
	public static String[] readStringArray(byte[] byteArray, int start, int size, String separator) throws InternalError {
		try {
			return (new String(byteArray, start, size, "UTF-8")).split(separator);
		} catch (UnsupportedEncodingException e) {
			throw new InternalError();
		}
	}

	/** Compute size of string array once converted to byte array with separator
	 * @param strArray	String Array
	 * @param separator	Separator used
	 * @return	size
	 */
	public static int computeStringArraySize(String[] strArray, String separator) {
		int size = 0;
		for (String s: strArray) {
			if (size != 0) {
				size += separator.length();
			}
			size += s.length();
		}
		return size;
	}

	/** Write a string to a byte array.
	 * @param array	Byte Array to write
	 * @param str	String to read
	 * @param start	Start position in byte array to write
	 * @throws	InternalError
	 */
	public static void write(byte[] array, String str, int start) throws InternalError {
		int bCount = start;
		try {
			byte[] sb = str.getBytes("UTF-8");
			for(byte b : sb) {
				array[bCount] = b;
				bCount += 1;
			}
		} catch (UnsupportedEncodingException e) {
			throw new InternalError();
		}
	}

	/** Read string from byte array
	 * @param array	Byte array to read
	 * @param start	start position in byte array
	 * @param size	size to read
	 * @return	String read
	 * @throws	InternalError
	 */
	public static String readString(byte[] array, int start, int size) throws InternalError {
		try {
			return new String(array, start, size, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new InternalError();
		}
	}

	/** Read string from byte array
	 * @param array	Byte array to read
	 * @param start	start position in byte array
	 * @param endStr	End string delimiter
	 * @return	String read
	 * @throws	InternalError
	 */
	public static String readString(byte[] array, int start, String endStr) throws InternalError {
		boolean failed = false;
		try {
			int i = start;
			while(true) {
				int j = i;
				for(byte b: endStr.getBytes()) {
					if (b != array[j]) {
						failed = true;
						break;
					}
					j++;
				}
				if (failed) {
					i++;
					failed = false;
				} else {
					break;
				}
			}
			return readString(array, start, i - start);
		} catch(IndexOutOfBoundsException e) {
			throw new InternalError();
		}
	}

	/** Write byte Array to byte Array.
	 * @param dst	Destination byte Array
	 * @param src	Source byte Array
	 * @param start	Start in dest array
	 */
	public static void write(byte[] dst, byte[] src, int start) {
		int i = start;
		for (byte b: src) {
			dst[i] = b;
			i += 1;
		}
	}

	/** Read byte Array to byte Array
	 * @param src	Source byte Array
	 * @param start	Start position in source
	 * @param end	End position in source
	 * @return	Resulting byte array
	 * @throws	ProtocolError
	 */
	public static byte[] readByteArray(byte[] src, int start, int end) throws ProtocolError {
		try {
			return Arrays.copyOfRange(src, start, end);
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new ProtocolError();
		}
	}
}
