/*******************************************************************************
 * Author: Ahmed Kosba <akosba@cs.umd.edu>
 *******************************************************************************/
package util;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;

import circuit.structure.Wire;

public class Util {

	// seeded by 1 for testing purposes
	static Random rand = new Random(1);

	public static BigInteger[] split(BigInteger x, int numchunks, int chunksize) {
		BigInteger[] chunks = new BigInteger[numchunks];
		BigInteger mask = new BigInteger("2").pow(chunksize).subtract(BigInteger.ONE);
		for (int i = 0; i < numchunks; i++) {
			chunks[i] = x.shiftRight(chunksize * i).and(mask);
		}
		return chunks;
	}

	public static BigInteger combine(BigInteger[] table, Wire[] blocks, int bitwidth) {
		BigInteger sum = BigInteger.ZERO;
		for (int i = 0; i < blocks.length; i++) {
			if (table[blocks[i].getWireId()] == null) {
				continue;
			}
			sum = sum.add(table[blocks[i].getWireId()].multiply(new BigInteger("2").pow(bitwidth * i)));
		}
		return sum;
	}

	public static BigInteger group(BigInteger[] list, int width) {
		BigInteger x = BigInteger.ZERO;
		for (int i = 0; i < list.length; i++) {
			x = x.add(list[i].shiftLeft(width * i));
		}
		return x;
	}

	public static int[] concat(int[] a1, int[] a2) {
		int[] all = new int[a1.length + a2.length];
		for (int i = 0; i < all.length; i++) {
			all[i] = i < a1.length ? a1[i] : a2[i - a1.length];
		}
		return all;
	}

	public static Wire[] concat(Wire[] a1, Wire[] a2) {
		Wire[] all = new Wire[a1.length + a2.length];
		for (int i = 0; i < all.length; i++) {
			all[i] = i < a1.length ? a1[i] : a2[i - a1.length];
		}
		return all;
	}

	public static Wire[] concat(Wire w, Wire[] a) {
		Wire[] all = new Wire[1 + a.length];
		for (int i = 0; i < all.length; i++) {
			all[i] = i < 1 ? w : a[i - 1];
		}
		return all;
	}

	public static int[] concat(int[][] arrays) {
		int sum = 0;
		for (int i = 0; i < arrays.length; i++) {
			sum += arrays[i].length;
		}
		int[] all = new int[sum];
		int idx = 0;
		for (int i = 0; i < arrays.length; i++) {
			for (int j = 0; j < arrays[i].length; j++) {
				all[idx++] = arrays[i][j];
			}
		}
		return all;
	}

	public static BigInteger[] randomBigIntegerArray(int num, BigInteger n) {

		BigInteger[] result = new BigInteger[num];
		for (int i = 0; i < num; i++) {
			result[i] = nextRandomBigInteger(n);
		}
		return result;
	}

	public static BigInteger nextRandomBigInteger(BigInteger n) {

		BigInteger result = new BigInteger(n.bitLength(), rand);
		while (result.compareTo(n) >= 0) {
			result = new BigInteger(n.bitLength(), rand);
		}
		return result;
	}

	public static BigInteger[] randomBigIntegerArray(int num, int numBits) {

		BigInteger[] result = new BigInteger[num];
		for (int i = 0; i < num; i++) {
			result[i] = nextRandomBigInteger(numBits);
		}
		return result;
	}

	public static BigInteger nextRandomBigInteger(int numBits) {

		BigInteger result = new BigInteger(numBits, rand);
		return result;
	}

	public static String getDesc(String... desc) {
		if (desc.length == 0) {
			return "";
		} else {
			return desc[0];
		}

	}

	public static ArrayList<Integer> parseSequenceLists(String s) {

		ArrayList<Integer> list = new ArrayList<Integer>();
		String[] chunks = s.split(",");
		for (String chunk : chunks) {
			if (chunk.equals(""))
				continue;
			int lower = Integer.parseInt(chunk.split(":")[0]);
			int upper = Integer.parseInt(chunk.split(":")[1]);
			for (int i = lower; i <= upper; i++) {
				list.add(i);
			}
		}
		return list;
	}

	public static Wire[] reverseBytes(Wire[] inBitWires) {
		Wire[] outs = Arrays.copyOf(inBitWires, inBitWires.length);
		int numBytes = inBitWires.length / 8;
		for (int i = 0; i < numBytes / 2; i++) {
			int other = numBytes - i - 1;
			for (int j = 0; j < 8; j++) {
				Wire temp = outs[i * 8 + j];
				outs[i * 8 + j] = outs[other * 8 + j];
				outs[other * 8 + j] = temp;
			}
		}
		return outs;
	}

	public static String arrayToString(int[] a, String separator) {
		StringBuilder s = new StringBuilder();
		for (int i = 0; i < a.length - 1; i++) {
			s.append(a[i] + separator);
		}
		s.append(a[a.length - 1]);
		return s.toString();
	}

	public static String arrayToString(Wire[] a, String separator) {
		StringBuilder s = new StringBuilder();
		for (int i = 0; i < a.length - 1; i++) {
			s.append(a[i] + separator);
		}
		s.append(a[a.length - 1]);
		return s.toString();
	}

	public static boolean isBinary(BigInteger v) {
		return v.equals(BigInteger.ZERO) || v.equals(BigInteger.ONE);
	}

	public static String padZeros(String s, int l) {
		return String.format("%" + l + "s",s).replace(' ', '0');
	}

	public static BigInteger computeMaxValue(int numBits){
		return BigIntStorage.getInstance().getBigInteger(
				new BigInteger("2").pow(numBits).subtract(
						BigInteger.ONE));
	}
	
	public static BigInteger computeBound(int numBits){
		return BigIntStorage.getInstance().getBigInteger(
				new BigInteger("2").pow(numBits));
	}
	
	public static BigInteger[] split(BigInteger x, int chunksize) {
		int numChunks = (int)Math.ceil(x.bitLength()*1.0/chunksize);
		BigInteger[] chunks = new BigInteger[numChunks];
		BigInteger mask = new BigInteger("2").pow(chunksize).subtract(BigInteger.ONE);
		for (int i = 0; i < numChunks; i++) {
			chunks[i] = x.shiftRight(chunksize * i).and(mask);
		}
		return chunks;
	}
	
	public static Wire[] padWireArray(Wire[] a, int length, Wire p) {
		if (a.length == length) {
			return a;
		} else if (a.length > length) {
			System.err.println("No padding needed!");
			return a;
		} else {
			Wire[] newArray = new Wire[length];
			System.arraycopy(a, 0, newArray, 0, a.length);
			for (int k = a.length; k < length; k++) {
				newArray[k] = p;
			}
			return newArray;
		}
	}
}
