package org.opensourcephysics.numerics;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Comparator;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * Library of supplementary array routines not
 *  supported by the java.util.Arrays class.
 *
 * @author jeffrey heer
 */
public abstract class ArrayLib {
    public static int IndexMax;
    public static int IndexMin;

    /**
     * Arrays with lengths beneath this value will be insertion sorted.
     */
    public static final int SORT_THRESHOLD = 30;

    // ------------------------------------------------------------------------
    // Shuffle

    /**
     * Randomly permute the contents of an array.
     * @param a the array to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(int[] a, Random r) {
        shuffle(a, 0, a.length, r);
    }

    /**
     * Randomly permute the contents of a range an array.
     * @param a the array to shuffle
     * @param start the starting index of the range to shuffle
     * @param len then length of the range to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(int[] a, int start, int len, Random r) {
        for ( int i=start+len; --i>0; ) {
            int t = a[i], j = r.nextInt(i);
            a[i] = a[j];
            a[j] = t;
        }
    }

    /**
     * Randomly permute the contents of an array.
     * @param a the array to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(long[] a, Random r) {
        shuffle(a, 0, a.length, r);
    }

    /**
     * Randomly permute the contents of a range an array.
     * @param a the array to shuffle
     * @param start the starting index of the range to shuffle
     * @param len then length of the range to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(long[] a, int start, int len, Random r) {
        for ( int i=start+len; i>1; --i ) {
            long t = a[i];
            int j = r.nextInt(i);
            a[i] = a[j];
            a[j] = t;
        }
    }

    /**
     * Randomly permute the contents of an array.
     * @param a the array to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(float[] a, Random r) {
        shuffle(a, 0, a.length, r);
    }

    /**
     * Randomly permute the contents of a range an array.
     * @param a the array to shuffle
     * @param start the starting index of the range to shuffle
     * @param len then length of the range to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(float[] a, int start, int len, Random r) {
        for ( int i=start+len; i>1; --i ) {
            float t = a[i];
            int j = r.nextInt(i);
            a[i] = a[j];
            a[j] = t;
        }
    }

    /**
     * Randomly permute the contents of an array.
     * @param a the array to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(double[] a, Random r) {
        shuffle(a, 0, a.length, r);
    }

    /**
     * Randomly permute the contents of a range an array.
     * @param a the array to shuffle
     * @param start the starting index of the range to shuffle
     * @param len then length of the range to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(double[] a, int start, int len, Random r) {
        for ( int i=start+len; i>1; --i ) {
            double t = a[i];
            int j = r.nextInt(i);
            a[i] = a[j];
            a[j] = t;
        }
    }

    /**
     * Randomly permute the contents of an array.
     * @param a the array to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(Object[] a, Random r) {
        shuffle(a, 0, a.length, r);
    }

    /**
     * Randomly permute the contents of a range an array.
     * @param a the array to shuffle
     * @param start the starting index of the range to shuffle
     * @param len then length of the range to shuffle
     * @param r the source of randomness to use
     */
    public static final void shuffle(Object[] a, int start, int len, Random r) {
        for ( int i=start+len; i>1; --i ) {
            Object t = a[i];
            int j = r.nextInt(i);
            a[i] = a[j];
            a[j] = t;
        }
    }

    // ------------------------------------------------------------------------
    // Max / Min / Sum

    /**
     * Find the maximum value in an array.
     * @param a the array
     * @param indMax the array minimum index
     * @return the maximum value in the array
     */
    public static final double max(double[] a) {
        double max = Double.NEGATIVE_INFINITY;
        for ( int i=0; i<a.length; ++i ) {
            if ( a[i] > max )
            {
              max = a[i];
              IndexMax = i;
            }
        }
        return max;
    }

    /**
     * Find the minimum value in an array.
     * @param a the array
     * @param indMin the array minimum index
     * @return the minimum value in the array
     */
    public static final double min(double[] a) {
        double min = Double.POSITIVE_INFINITY;
        for ( int i=0; i<a.length; ++i ) {
            if ( a[i] < min )
            {
              min = a[i];
              IndexMin = i;
            }
        }
        return min;
    }

    /**
     * @param IndexMax the array minimum index
     * @return IndexMax
     * @added by Javier E. Hasbun
     */
    public static final int getIndexMax(){
      return IndexMax;
    }

    /**
     * @param IndexMin the array minimum index
     * @return IndexMin
     * @added by Javier E. Hasbun
     */
    public static final int getIndexMin(){
      return IndexMin;
    }

    /**
     * Compute the sum of the values in an array.
     * @param a the array
     * @return the sum of the values in the array
     */
    public static final double sum(double[] a) {
        double sum = 0;
        for ( int i=0; i<a.length; ++i ) {
            sum += a[i];
        }
        return sum;
    }

    //// -----------------------------------------------
    //// -- Searching Functions ------------------------

    /**
     * Perform a binary search over a sorted array for the given key.
     * @param a the array to search
     * @param key the key to search for
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(int[] a, int key) {
        int x1 = 0;
        int x2 = a.length;
        int i = x2 / 2;
        while (x1 < x2) {
            if (a[i] == key) {
                return i;
            } else if (a[i] < key) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key. The range is assumed to start at index 0.
     * @param a the array to search
     * @param key the key to search for
     * @param length the the length of the range to search over.
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(int[] a, int key, int length) {
        int x1 = 0;
        int x2 = length;
        int i = x2 / 2;

        while (x1 < x2) {
            if (a[i] == key) {
                return i;
            } else if (a[i] < key) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key.
     * @param a the array to search
     * @param key the key to search for
     * @param begin the starting index of the range
     * @param end the ending index of the range, exclusive
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(int[] a, int key, int begin, int end) {
        int x1 = begin;
        int x2 = end;
        int i = x1 + (x2 - x1) / 2;

        while (x1 < x2) {
            if (a[i] == key) {
                return i;
            } else if (a[i] < key) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }

        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted array for the given key.
     * @param a the array to search
     * @param key the key to search for
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    @SuppressWarnings("unchecked")
    public static final int binarySearch(Object[] a, Object key) {
        int x1 = 0;
        int x2 = a.length;
        int i = x2 / 2, c;
        while (x1 < x2) {
            c = ((Comparable<Object>)a[i]).compareTo(key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key. The range is assumed to start at index 0.
     * @param a the array to search
     * @param key the key to search for
     * @param length the the length of the range to search over.
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    @SuppressWarnings("unchecked")
    public static final int binarySearch(Object[] a, Object key, int length) {
        int x1 = 0;
        int x2 = length;
        int i = x2 / 2, c;

        while (x1 < x2) {
            c = ((Comparable<Object>)a[i]).compareTo(key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key.
     * @param a the array to search
     * @param key the key to search for
     * @param begin the starting index of the range
     * @param end the ending index of the range, exclusive
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    @SuppressWarnings("unchecked")
    public static final int binarySearch(Object[] a, Object key, int begin, int end) {
        int x1 = begin;
        int x2 = end;
        int i = x1 + (x2 - x1) / 2, c;

        while (x1 < x2) {
            c = ((Comparable<Object>)a[i]).compareTo(key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }

        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted array for the given key.
     * @param a the array to search
     * @param key the key to search for
     * @param cp the comparator to use to compare key values
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(Object[] a, Object key, Comparator<Object> cp) {
        int x1 = 0;
        int x2 = a.length;
        int i = x2 / 2, c;
        while (x1 < x2) {
            c = cp.compare(a[i], key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key. The range is assumed to start at index 0.
     * @param a the array to search
     * @param key the key to search for
     * @param cp the comparator to use to compare key values
     * @param length the the length of the range to search over.
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(Object[] a, Object key, Comparator<Object> cp, int length) {
        int x1 = 0;
        int x2 = length;
        int i = x2 / 2, c;

        while (x1 < x2) {
            c = cp.compare(a[i], key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }
        return -1*(i+1);
    }

    /**
     * Perform a binary search over a sorted range of an array for the given
     * key.
     * @param a the array to search
     * @param key the key to search for
     * @param cp the comparator to use to compare key values
     * @param begin the starting index of the range
     * @param end the ending index of the range, exclusive
     * @return the index of the given key if it exists in the array,
     * otherwise -1 times the index value at the insertion point that
     * would be used if the key were added to the array.
     */
    public static final int binarySearch(Object[] a, Object key, Comparator<Object> cp, int begin, int end) {
        int x1 = begin;
        int x2 = end;
        int i = x1 + (x2 - x1) / 2, c;

        while (x1 < x2) {
            c = cp.compare(a[i], key);
            if (c == 0) {
                return i;
            } else if (c < 0) {
                x1 = i + 1;
            } else {
                x2 = i;
            }
            i = x1 + (x2 - x1) / 2;
        }

        return -1*(i+1);
    }

    //// -----------------------------------------------
    //// -- Finding Functions --------------------------

    /**
     * Linearly search an array for a given key value.
     * @param a the array to search
     * @param key the key to search for
     * @return the index of the first occurrence of the key in the array,
     * of -1 if the key is not found.
     */
    public static final int find(int[] a, int key) {
        for (int i = 0; i < a.length; i++) {
            if (a[i] == key) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Linearly search an array range for a given key value. Assumes that
     * the range begins at index 0.
     * @param a the array to search
     * @param key the key to search for
     * @param length the length of the range to search over
     * @return the index of the first occurrence of the key in the array,
     * of -1 if the key is not found.
     */
    public static final int find(int[] a, int key, int length) {
        for (int i = 0; i < length; i++) {
            if (a[i] == key) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Linearly search an array range for a given key value.
     * @param a the array to search
     * @param key the key to search for
     * @param begin the starting index of the range
     * @param end the ending index of the range, exclusive
     * @return the index of the first occurrence of the key in the array,
     * of -1 if the key is not found.
     */
    public static final int find(int[] a, int key, int begin, int end) {
        for (int i = begin; i < end; i++) {
            if (a[i] == key) {
                return i;
            }
        }
        return -1;
    }

    //// -----------------------------------------------
    //// -- Resizing Functions -------------------------

    /**
     * Resize the given array as needed to meet a target size.
     * @param a the array to potentially resize
     * @param size the minimum size of the target array
     * @return the resized array, if the original array meets the size
     * requirement, it is simply return, otherwise a new array is
     * allocated and the contents of the original array are copied over.
     */
    public static final int[] resize(int[] a, int size) {
        if ( a.length >= size ) return a;
        int[] b = new int[size];
        System.arraycopy(a, 0, b, 0, a.length);
        return b;
    }

    /**
     * Resize the given array as needed to meet a target size.
     * @param a the array to potentially resize
     * @param size the minimum size of the target array
     * @return the resized array, if the original array meets the size
     * requirement, it is simply return, otherwise a new array is
     * allocated and the contents of the original array are copied over.
     */
    public static final float[] resize(float[] a, int size) {
        if ( a.length >= size ) return a;
        float[] b = new float[size];
        System.arraycopy(a, 0, b, 0, a.length);
        return b;
    }

    /**
     * Resize the given array as needed to meet a target size.
     * @param a the array to potentially resize
     * @param size the minimum size of the target array
     * @return the resized array, if the original array meets the size
     * requirement, it is simply return, otherwise a new array is
     * allocated and the contents of the original array are copied over.
     */
    public static final double[] resize(double[] a, int size) {
        if ( a.length >= size ) return a;
        double[] b = new double[size];
        System.arraycopy(a, 0, b, 0, a.length);
        return b;
    }

    /**
     * Resize the given array as needed to meet a target size.
     * @param a the array to potentially resize
     * @param size the minimum size of the target array
     * @return the resized array, if the original array meets the size
     * requirement, it is simply return, otherwise a new array is
     * allocated and the contents of the original array are copied over.
     */
    public static final Object[] resize(Object[] a, int size) {
        if ( a.length >= size ) return a;
        Object[] b = new Object[size];
        System.arraycopy(a, 0, b, 0, a.length);
        return b;
    }

    /**
     * Trims an array to be exactly the target a size.
     * @param a the array to trim
     * @param size the desired size of the array. This value must be lesser
     * than or equal to the size of the input array.
     * @return a trimmed array instance
     */
    public static final int[] trim(int[] a, int size) {
        //assert (size <= a.length);
        if ( a.length == size ) {
            return a;
        }
		int[] b = new int[size];
		System.arraycopy(a, 0, b, 0, size);
		return b;
    }

    /**
     * Trims an array to be exactly the target a size.
     * @param a the array to trim
     * @param size the desired size of the array. This value must be lesser
     * than or equal to the size of the input array.
     * @return a trimmed array instance
     */
    public static final float[] trim(float[] a, int size) {
        //assert (size <= a.length);
        if ( a.length == size ) {
            return a;
        }
		float[] b = new float[size];
		System.arraycopy(a, 0, b, 0, size);
		return b;
    }

    /**
     * Trims an array to be exactly the target a size.
     * @param a the array to trim
     * @param size the desired size of the array. This value must be lesser
     * than or equal to the size of the input array.
     * @return a trimmed array instance
     */
    public static final double[] trim(double[] a, int size) {
        //assert (size <= a.length);
        if ( a.length == size ) {
            return a;
        }
		double[] b = new double[size];
		System.arraycopy(a, 0, b, 0, size);
		return b;
    }

    /**
     * Trims an array to be exactly the target a size.
     * @param a the array to trim
     * @param size the desired size of the array. This value must be lesser
     * than or equal to the size of the input array.
     * @return a trimmed array instance
     */
    public static final Object[] trim(Object[] a, int size) {
        //assert (size <= a.length);
        if ( a.length == size ) {
            return a;
        }
		Object[] b = new Object[size];
		System.arraycopy(a, 0, b, 0, size);
		return b;
    }

    //// -----------------------------------------------
    //// -- Sorting Functions --------------------------

    // -- int / double sorting ------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     */
    public static final void sort(int[] a, double[] b) {
        mergesort(a, b, 0, a.length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param length the array range length to sort over
     */
    public static final void sort(int[] a, double[] b, int length) {
        mergesort(a, b, 0, length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(int[] a, double[] b, int begin, int end) {
        mergesort(a, b, begin, end - 1);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(int[] a, double[] b, int p, int r) {
        for (int j = p + 1; j <= r; ++j) {
            int key = a[j];
            double val = b[j];
            int i = j - 1;
            while (i >= p && a[i] > key) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                i--;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static final void mergesort(int[] a, double[] b, int p, int r) {
        if (p >= r) {
            return;
        }
        if (r - p + 1 < SORT_THRESHOLD) {
            insertionsort(a, b, p, r);
        } else {
            int q = (p + r) / 2;
            mergesort(a, b, p, q);
            mergesort(a, b, q + 1, r);
            merge(a, b, p, q, r);
        }
    }

    protected static final void merge(int[] a, double[] b, int p, int q, int r) {
        int[] t = new int[r - p + 1];
        double[] v = new double[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if (a[p1] < a[p2]) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; ++i, ++p1) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // -- int / int sorting ---------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     */
    public static final void sort(int[] a, int[] b) {
        mergesort(a, b, 0, a.length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param length the array range length to sort over
     */
    public static final void sort(int[] a, int[] b, int length) {
        mergesort(a, b, 0, length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(int[] a, int[] b, int begin, int end) {
        mergesort(a, b, begin, end - 1);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(int[] a, int[] b, int p, int r) {
        for (int j = p + 1; j <= r; ++j) {
            int key = a[j];
            int val = b[j];
            int i = j - 1;
            while (i >= p && a[i] > key) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                i--;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static final void mergesort(int[] a, int[] b, int p, int r) {
        if (p >= r) {
            return;
        }
        if (r - p + 1 < SORT_THRESHOLD) {
            insertionsort(a, b, p, r);
        } else {
            int q = (p + r) / 2;
            mergesort(a, b, p, q);
            mergesort(a, b, q + 1, r);
            merge(a, b, p, q, r);
        }
    }

    protected static final void merge(int[] a, int[] b, int p, int q, int r) {
        int[] t = new int[r - p + 1];
        int[] v = new int[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if (a[p1] < a[p2]) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; ++i, ++p1) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // -- int / Object sorting ---------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(int[] a, Object[] b, int begin, int end) {
        int length = end-begin;

        if ( length < SORT_THRESHOLD ) {
            insertionsort(a, b, begin, end-1);
            return;
        }

        // allocate source arrays
        int[] ks = new int[length];
        Object[] vs = new Object[length];
        for ( int i=0, idx=begin; i<length; ++i, ++idx ) {
            ks[i] = a[idx];
            vs[i] = b[idx];
        }
        mergesort(ks, a, vs, b, begin, end, -begin);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param abuf a buffer array to perform the sorting without
     * allocating any additional memory
     * @param bbuf a buffer array to perform the sorting without
     * allocating any additional memory
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(int[] a, Object[] b,
            int[] abuf, Object[] bbuf, int begin, int end)
    {
        int length = end-begin;

        if ( length < SORT_THRESHOLD ) {
            insertionsort(a, b, begin, end-1);
            return;
        }

        // allocate source arrays
        for ( int i=0, idx=begin; i<length; ++i, ++idx ) {
            abuf[i] = a[idx];
            bbuf[i] = b[idx];
        }
        mergesort(abuf, a, bbuf, b, begin, end, -begin);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(int[] a, Object[] b, int p, int r) {
        int i, key;
        Object val;
        for (int j = p + 1; j <= r; ++j) {
            key = a[j];
            val = b[j];
            i = j - 1;
            while (i >= p && a[i] > key) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                i--;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static void mergesort(int ks[], int kd[], Object[] vs,
            Object[] vd, int lo, int hi, int off)
    {
        int length = hi-lo;

        if (length < SORT_THRESHOLD) {
            insertionsort(kd, vd, lo, hi-1);
            return;
        }

        // Recursively sort halves of dest into src
        int dlo = lo;
        int dhi = hi;
        lo += off;
        hi += off;
        int mid = (lo + hi) >> 1;
        mergesort(kd, ks, vd, vs, lo, mid, -off);
        mergesort(kd, ks, vd, vs, mid, hi, -off);

        // If list is already sorted, just copy from src to dest. This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if ( ks[mid-1] <= ks[mid] ) {
            System.arraycopy(ks, lo, kd, dlo, length);
            System.arraycopy(vs, lo, vd, dlo, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for (int i = dlo, p = lo, q = mid; i < dhi; i++) {
            if (q >= hi || p < mid && ks[p] <= ks[q] ) {
                vd[i] = vs[p];
                kd[i] = ks[p++];
            } else {
                vd[i] = vs[q];
                kd[i] = ks[q++];
            }
        }
    }

    protected static final void merge(int[] a, Object[] b, int p, int q, int r) {
        int[] t = new int[r - p + 1];
        Object[] v = new Object[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if (a[p1] < a[p2]) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; ++i, ++p1) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // -- double / int sorting -------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     */
    public static final void sort(double[] a, int[] b) {
        mergesort(a, b, 0, a.length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param length the length of the range to be sorted
     */
    public static final void sort(double[] a, int[] b, int length) {
        mergesort(a, b, 0, length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(double[] a, int[] b, int begin, int end) {
        mergesort(a, b, begin, end - 1);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(double[] a, int[] b, int p, int r) {
        for (int j = p + 1; j <= r; ++j) {
            double key = a[j];
            int val = b[j];
            int i = j - 1;
            while (i >= p && a[i] > key) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                --i;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static final void mergesort(double[] a, int[] b, int p, int r) {
        if (p >= r) {
            return;
        }
        if (r - p + 1 < SORT_THRESHOLD) {
            insertionsort(a, b, p, r);
        } else {
            int q = (p + r) / 2;
            mergesort(a, b, p, q);
            mergesort(a, b, q + 1, r);
            merge(a, b, p, q, r);
        }
    }

    protected static final void merge(double[] a, int[] b, int p, int q, int r) {
        double[] t = new double[r - p + 1];
        int[] v = new int[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if (a[p1] < a[p2]) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; i++, p1++) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // -- float / int sorting -------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     */
    public static final void sort(float[] a, int[] b) {
        mergesort(a, b, 0, a.length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param length the length of the range to be sorted
     */
    public static final void sort(float[] a, int[] b, int length) {
        mergesort(a, b, 0, length - 1);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     */
    public static final void sort(float[] a, int[] b, int begin, int end) {
        mergesort(a, b, begin, end - 1);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(float[] a, int[] b, int p, int r) {
        for (int j = p + 1; j <= r; ++j) {
            float key = a[j];
            int val = b[j];
            int i = j - 1;
            while (i >= p && a[i] > key) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                --i;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static final void mergesort(float[] a, int[] b, int p, int r) {
        if (p >= r) {
            return;
        }
        if (r - p + 1 < SORT_THRESHOLD) {
            insertionsort(a, b, p, r);
        } else {
            int q = (p + r) / 2;
            mergesort(a, b, p, q);
            mergesort(a, b, q + 1, r);
            merge(a, b, p, q, r);
        }
    }

    protected static final void merge(float[] a, int[] b, int p, int q, int r) {
        float[] t = new float[r - p + 1];
        int[] v = new int[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if (a[p1] < a[p2]) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; i++, p1++) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // -- Object / int sorting ------------------------------------------------

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param cmp the comparator to use to compare key values
     */
    public static final void sort(Object[] a, int[] b, Comparator<Object> cmp) {
        mergesort(a, b, 0, a.length - 1, cmp);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param length the length of the range to be sorted
     * @param cmp the comparator to use to compare key values
     */
    public static final void sort(Object[] a, int[] b, int length,
            Comparator<Object> cmp)
    {
        mergesort(a, b, 0, length - 1, cmp);
    }

    /**
     * Sort two arrays simultaneously, using the sort order of the values
     * in the first array to determine the sort order for both arrays.
     * @param a the array to sort by
     * @param b the array to re-arrange based on the sort order of the
     * first array.
     * @param begin the start index of the range to sort
     * @param end the end index, exclusive, of the range to sort
     * @param cmp the comparator to use to compare key values
     */
    public static final void sort(Object[] a, int[] b, int begin, int end,
            Comparator<Object> cmp)
    {
        mergesort(a, b, begin, end - 1, cmp);
    }

    // -- Insertion Sort --

    protected static final void insertionsort(Object[] a, int[] b, int p, int r,
            Comparator<Object> cmp)
    {
        for (int j = p + 1; j <= r; ++j) {
            Object key = a[j];
            int val = b[j];
            int i = j - 1;
            while (i >= p && cmp.compare(a[i],key) > 0 ) {
                a[i + 1] = a[i];
                b[i + 1] = b[i];
                --i;
            }
            a[i + 1] = key;
            b[i + 1] = val;
        }
    }

    // -- Mergesort --

    protected static final void mergesort(Object[] a, int[] b, int p, int r,
            Comparator<Object> cmp)
    {
        if (p >= r) {
            return;
        }
        if (r - p + 1 < SORT_THRESHOLD) {
            insertionsort(a, b, p, r, cmp);
        } else {
            int q = (p + r) / 2;
            mergesort(a, b, p, q, cmp);
            mergesort(a, b, q + 1, r, cmp);
            merge(a, b, p, q, r, cmp);
        }
    }

    protected static final void merge(Object[] a, int[] b, int p, int q, int r,
            Comparator<Object> cmp)
    {
        Object[] t = new Object[r - p + 1];
        int[] v = new int[r - p + 1];
        int i, p1 = p, p2 = q + 1;
        for (i = 0; p1 <= q && p2 <= r; ++i) {
            if ( cmp.compare(a[p1],a[p2]) < 0 ) {
                v[i] = b[p1];
                t[i] = a[p1++];
            } else {
                v[i] = b[p2];
                t[i] = a[p2++];
            }
        }
        for (; p1 <= q; ++p1, ++i) {
            v[i] = b[p1];
            t[i] = a[p1];
        }
        for (; p2 <= r; ++p2, ++i) {
            v[i] = b[p2];
            t[i] = a[p2];
        }
        for (i = 0, p1 = p; i < t.length; i++, p1++) {
            b[p1] = v[i];
            a[p1] = t[i];
        }
    }

    // ------------------------------------------------------------------------
    // Array File I/O

    /**
     * Read in a text file as an array of integers. Uses the default java
     * StringTokenizer to segment the text file. Additionally, tokens beginning
     * with the '#' character are ignored.
     * @param filename the name of the file to read in
     * @return an array of integers parsed from the file
     */
    public static int[] getIntArray(String filename) {
        int[] array = null;
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            String line = br.readLine();
            StringTokenizer st = new StringTokenizer(line);
            int maxlen = st.countTokens();
            int len = 0;
            array = new int[maxlen];
            while ( st.hasMoreTokens() ) {
                String tok = st.nextToken();
                if ( tok.startsWith("#") ) // commented int //$NON-NLS-1$
                    continue;
                array[len++] = Integer.parseInt(tok);
            }
            if ( len != maxlen )
                array = ArrayLib.trim(array, len);
            return array;
        } catch ( Exception e ) {
            e.printStackTrace();
            return null;
        }
    }

} // end of class ArrayLib
