package aalg.algebra;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.zip.InflaterInputStream;


/**
 * Utility methods.
 * @author Ing. Petr Ležák
 */
public class Utils {
	
	private static final int NUMBER_LENGTH_BYTE_COUNT = 4;
	
	/**
	 * Writes given number into stream.
	 * @param number number to write
	 * @param stream output stream
	 * @throws IOException
	 */
	public static void writeNumber (final BigInteger number, final OutputStream stream) throws IOException {
		final byte[] array = number.toByteArray();
		
		writeByteArrayWithSize(stream, array, NUMBER_LENGTH_BYTE_COUNT);
	}

	/**
	 * Reads given number from stream.
	 * @param stream output stream
	 * @return number number to read
	 * @throws IOException
	 */
	public static BigInteger readNumber (final InputStream stream) throws IOException {
		final byte[] array = readByteArrayWithSize(stream, NUMBER_LENGTH_BYTE_COUNT);
		
		return new BigInteger(array);
	}
	
		
	public static final int LONG_BYTE_COUNT = Long.SIZE / Byte.SIZE;
	public static final int INT_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
	public static final long INT_MASK = (1L << (Integer.SIZE)) - 1L;
	
	
	/**
	 * Writes unsinged long number to the stream in big endian.
	 * @param number written number
	 * @param stream output stream
	 * @param byteCount number of bytes to write
	 * @throws IOException thrown if IO fails
	 */
	public static void writeLong(final long number, final OutputStream stream, final int byteCount) throws IOException {
		for (int i = byteCount - 1; i >= 0; i--) {
			final int digit = (int) ((number >>> (8 * i)) & 0xFF);
			
			stream.write(digit);
		}
	}

	/**
	 * Reads unsigned long number from the stream in big endian.
	 * @param stream input stream
	 * @param byteCount number of bytes to read
	 * @return read number
	 * @throws IOException
	 */
	public static long readLong(final InputStream stream, final int byteCount) throws IOException {
		long number = 0;
		
		for (int i = 0; i < byteCount; i++) {
			final int digit = readByte(stream);

			number = (number << 8) | digit;
		}
		
		return number;
	}

	static int readByte(final InputStream stream) throws IOException {
		final int digit = stream.read();

		if (digit < 0)
			throw new IOException("End of stream reached");
		
		return digit;
	}

	public static int readByteArray(final InputStream stream, final byte[] array, final int offset, final int count) throws IOException {
		int bytesRead = 0;
		
		while (bytesRead < count) {
			final int ret = stream.read(array, offset + bytesRead, count - bytesRead);
			
			if (ret >= 0)
				bytesRead += ret;
			else
				break;
		}
		
		return bytesRead;
	}
	
	public static byte[] readByteArrayWithSize(final InputStream stream, final int lengthBytes) throws IOException {
		final int length = (int) Utils.readLong(stream, lengthBytes);
		final byte[] data = new byte[length];
		
		if (Utils.readByteArray(stream, data, 0, length) < length)
			throw new RuntimeException("End of stream reached");
		
		return data;
	}
	
	public static void writeByteArrayWithSize(final OutputStream stream, final byte[] data, final int lengthBytes) throws IOException {
		Utils.writeLong(data.length, stream, lengthBytes);
		stream.write(data);
	}

	public static void verifyEndOfStream(final InputStream stream) throws IOException {
		if (stream.read() != -1)
			throw new IOException("End of stream expected");
	}
	
	public static void printByteArray (final byte[] array) {
		for (int i = 0; i < array.length; i++) {
			System.out.print (array[i] + " ");
		}
		
		System.out.println();
	}
	
	public static void intBlockToByteBlock(final int[] srcBlock, final byte[] dstBlock, final int srcOffset, final int dstOffset, final int longCount) {
		final int srcEnd = srcOffset + longCount;
		int dstIndex = dstOffset;
		
		for (int srcIndex = dstOffset; srcIndex < srcEnd; srcIndex++) {
			long word = srcBlock[srcIndex];
			
			for (int j = 0; j < INT_BYTE_COUNT; j++) {
				dstBlock[dstIndex] = (byte) word;
				word >>>= 8;
				dstIndex++;
			}
		}		
	}
	
	public static void byteBlockToIntBlock(final byte[] srcBlock, final int[] dstBlock, final int srcOffset, final int dstOffset, final int longCount) {
		final int dstEnd = dstOffset + longCount;
		int srcIndex = dstOffset;
		
		for (int dstIndex = dstOffset; dstIndex < dstEnd; dstIndex++) {
			int word = 0;
			
			for (int j = 0; j < INT_BYTE_COUNT; j++) {
				word |= (srcBlock[srcIndex] & 0xFF) << (Byte.SIZE * j);
				srcIndex++;
			}
			
			dstBlock[dstIndex] = word;
		}
	}

	public static byte[] readByteArrayToEof(final InputStream stream) throws IOException {
		byte[] data = new byte[127];
		int size = 0;
		
		while (true) {
			if (size >= data.length)
				data = Arrays.copyOf(data, 2*size + 1);
			
			final int read = stream.read(data, size, data.length - size);
			
			if (read < 0)
				break;
			
			size += read;
		}
		
		return Arrays.copyOf(data, size);
	}

	public static byte[] readByteArray(final InputStream stream, final int length) throws IOException {
		final byte[] array = new byte[length];
		Utils.readByteArray(stream, array, 0, length);
		
		return array;
	}

}
