package arrayOperations;

/**
 * A collection of APL-like operations on int arrays.
 * Used as an assignment in CIT590, Spring 2009.
 * 
 * @author Dave Matuszek
 * @version March 11, 2009
 */
public class ArrayOperations {
    
    /**
     * Creates an array of length integers, and fills it with the numbers 1 to length.
     * 
     * @param length The size of the array to create.
     * @return The new array, filled with the numbers 1 to length.
     */
    public static int[] fill(int length) {
        int[] result = new int[length];
        for (int i = 0; i < length; i++) {
            result[i] = i + 1;
        }
        return result;
    }

    /**
     * Returns a new array whose values are in the reverse order of those in the given array.
     * 
     * @param array The array to reverse.
     * @return The reversed array.
     */
    public static int[] reverse(int[] array) {
        int[] result = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            result[i] = array.length - i;
        }
        return result;
    }

    /**
     * Takes a two-dimensional input array and returns a new array which is "rotated"
     * a quarter-turn clockwise.
     * 
     * @param array The array to rotate.
     * @return The rotated array.
     */
    public static int[][] rotateRight(int[][] array) {
        int r = array.length;
        if (r == 0) {
            return new int[0][0]; // Special case: zero-length array
        }
        int c = array[0].length;
        int[][] result = new int[c][r];
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result[j][r - 1 - i] = array[i][j];
            }
        }
        return result;
    }


    /**
     * Takes a two-dimensional input array and returns a new array which is "rotated"
     * a quarter-turn counterclockwise.
     * 
     * @param array The array to rotate.
     * @return The rotated array.
     */
    public static int[][] rotateLeft(int[][] array) {
        int r = array.length;
        if (r == 0) {
            return new int[0][0]; // Special case: zero-length array
        }
        int c = array[0].length;
        int[][] result = new int[c][r];
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result[c - 1 - j][i] = array[i][j];
            }
        }
        return result;
    }

    /**
     * Takes an input array of m rows and n columns, and transposes it to form an array
     * of n rows and m columns. The value in location [i][j] of the input array is copied
     * into location [j][i] of the new array.
     * 
     * @param array The array to transpose.
     * @return The transposed array.
     */
    public static int[][] transpose(int[][] array) {
        int r = array.length;
        if (r == 0) {
            return new int[0][0]; // Special case: zero-length array
        }
        int c = array[0].length;
        int[][] result = new int[c][r];
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result[j][i] = array[i][j];
            }
        }
        return result;
    }

    /**
     * Takes a one-dimensional input array of m  n  numbers and returns a two-dimensional
     * array of m rows and n columns. The first n numbers of the given array are copied
     * into the first row of the new array, the second n numbers into the second row,
     * and so on. This method throws an IllegalArgumentException if the length of the input
     * array is not evenly divisible by n.
     * 
     * @param array The values to put into the new array.
     * @param n The number of desired columns in the new array.
     * @return The new m  n array.
     * @throws IllegalArgumentException If the length of the given array is not
     *  a multiple of n.
     */
    public static int[][] ravel(int[] array, int n) throws IllegalArgumentException {
        if (array.length % n != 0) {
            throw new IllegalArgumentException(array.length + " is not evenly divisible by " + n);
        }
        int length = array.length;
        int[][] result = new int[length / n][n];
        for (int i = 0; i < length; i++) {
            result[i / n][i % n] = array[i];
        }
        return result;
    }

    /**
     * Takes a m by n two dimensional array and returns a one-dimensional array of size m  n
     * containing the same numbers. The first n numbers of the new array are copied from the
     * first row of the given array, the second n numbers from the second row, and so on.
     * 
     * @param array The array to be unraveled.
     * @return The values in the given array.
     */
    public static int[] unravel(int[][] array) {
        int r = array.length;
        if (r == 0) {
            return new int[0]; // Special case: zero-length array
        }
        int c = array[0].length;
        int[] result = new int[r * c];
        int index = 0;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result[index] = array[i][j];
                index++;
            }
        }
        return result;
    }

    /**
     * Takes a two-dimensional array of r rows and c columns and reshapes it to
     * have (r*c)/n by n columns. The value in location [i][j] of the input array
     * is copied into location [j][i] of the new array.
     * 
     * @param array The array of values to be reshaped.
     * @param n The number of columns in the created array.
     * @return The new (r*c)/n by n array.
     * @throws IllegalArgumentException If r*c  is not evenly divisible by n.
     */
    public static int[][] reshape(int[][] array, int n) throws IllegalArgumentException {
        int r = array.length;
        if (r == 0) {
            return new int[0][0]; // Special case: zero-length array
        }
        if ((array.length * array[0].length) % n != 0) {
            int size = array.length * array[0].length;
            throw new IllegalArgumentException(size + " is not evenly divisible by " + n);
        }
        int c = array[0].length;
        int[][] result = new int[(r * c) / n][n];
        int ii = 0;
        int jj = 0;
        
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                result[ii][jj] = array[i][j];
                jj++;
                if (jj == n) {
                    jj = 0;
                    ii++;
                }
            }
        }
        return result;
    }

    /**
     * Takes two two-dimensional arrays, one with n rows and m1 columns, and the
     * other with n rows and m2 columns, and adjoins them to form a new array
     * with n rows and m1+m2 columns.
     * 
     * @param array1 The values forming the left part of the new array.
     * @param array2  The values forming the right part of the new array.
     * @return The join of the two arrays.
     * @throws IllegalArgumentException If the input arrays do not have the
     * same number of rows.        
     */
    public static int[][] join(int[][] array1, int[][] array2) throws IllegalArgumentException {
        int r1 = array1.length;
        int c1 = array1[0].length;
        int r2 = array2.length;
        int c2 = array2[0].length;
        if (r1 != r2) {
            throw new IllegalArgumentException("Cannot join " + r1 + "x" + c1 +
                                               " array with " + r2 + "x" + c2 + " array.");
        }
        int[][] result = new int[r1][c1 + c2];
        for (int i = 0; i < r1; i++) {
            for (int j = 0; j < c1 + c2; j++) {
                if (j < c1) {
                    result[i][j] = array1[i][j];
                } else {
                    result[i][j] = array2[i][j - c1];
                }
            }
        }
        return result;        
    }

    /**
     * Given an array of integers in the range 0 to 127, return an array of characters
     * with those ASCII values.
     * 
     * @param array Numbers in the range 0 to 127.
     * @return The corresponding characters.
     * @throws IllegalArgumentException If any of the integers are outside the range 0 to 127.
     */
    public static char[] toChars(int[] array) throws IllegalArgumentException {
        char[] result = new char[array.length];
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0 || array[i] > 127) {
                throw new IllegalArgumentException("Invalid character code: " + array[i]);
            }
            result[i] = (char) array[i];
        }
        return result;
    }

    /**
     * Given an array of integers in the range 0 to 127, return an String of characters
     * with those ASCII values.
     * 
     * @param array Numbers in the range 0 to 127.
     * @return A String of characters with the given ASCII values.
     * @throws IllegalArgumentException If any of the integers are outside the range 0 to 127.
     */
    public static String toString(int[] array) throws IllegalArgumentException {
        String result = "";
        for (int i = 0; i < array.length; i++) {
            if (array[i] < 0 || array[i] > 127) {
                throw new IllegalArgumentException("Invalid character code: " + array[i]);
            }
            result += (char) array[i];
        }
        return result;
    }

    /**
     * Given an array of characters, return an array of their ASCII values.
     * 
     * @param array The characters.
     * @return The ASCII values of the given characters.
     */
    public static int[] toInts(char[] array) {
        int[] result = new int[array.length];
        for (int i = 0; i < array.length; i++) {
            result[i] = array[i];
        }
        return result;
    }

    /**
     * Returns an array of the ASCII values of the characters in the given String.
     * @param string The input String.
     * @return The ASCII values of the characters in the given String.
     */
    public static int[] toInts(String string) {
        int[] result = new int[string.length()];
        for (int i = 0; i < string.length(); i++) {
            result[i] = string.charAt(i);
        }
        return result;
    }
}