# ****************
# Some array sorting functions that are useful for TSL programmers out there
# ** NOTE **: This modules requires some functions from the * func_ra * module.
#
# Created by: Ferry Firmansjah (firmanf@itechnologist.com)
#
# More information: http://www.itechnologist.com/tech/func_ra_sort.html
#
# Revisions: September 11, 2002 (major fixes on all sort functions so they'll work correctly consistently)
#			 November 8, 2000 (Initial release)
#
# Copyright (C) 2000-2002 Ferry Firmansjah
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# ****************


# ***********************
# * B U B B L E S O R T *
# ***********************

####
# Syntax: array_bubblesort (inout inra[] [, in elementcount]););
# Return values: E_OK for success.
# Parameters:
#    inra[]: the array to sort
#    elementcount: the number of elements in the array
#        (can be obtained by array_length() function)
# Description: Sort an array using bubblesort.
#    ** WARNING ** This sorting method is very inefficient for large arrays.
#    Seriously consider using array_mergesort() or array_quicksort()
#    instead.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_bubblesort (ra1);
#         array_bubblesort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_bubblesort (inout inra[], in n) {
	auto i, j, tmp;

	if (nargs() == 1) {
		n = array_length (inra);
	} else if (nargs() != 2) {
		return (report_param_msg ("array_bubblesort"));
	}

	# 9/11/2002: Corrected to stay within the array bounday
	for (i = 0; i < n-1; i++) {
		for (j = i+1; j< n; j++) {
			if (inra[i] > inra[j]) {
				tmp = inra[i];
				inra[i] = inra[j];
				inra[j] = tmp;
			}
		}
	}
}


####
# Syntax: array_bubblesort2 (inout inra[] [, in startindex[, in endindex]]);
# Return values: E_OK for success.
# Parameters:
#    inra[]: the array to sort
#    startindex: the index of the first element to be sorted
#    endindex: the index of the last element to be sorted
#        (can be obtained by array_get_last_index() function)
# Description: Sort an array using bubblesort.
#    The function allows sorting a subset of an array.
#    ** WARNING ** This sorting method is very inefficient for large arrays.
#    Seriously consider using array_mergesort() or array_quicksort()
#    instead.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_bubblesort (ra1);
#         array_bubblesort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_bubblesort2 (inout inra[], in startindex, in endindex) {
	auto i, j, tmp;
	if (nargs() < 1 || nargs() > 3) {
		return (report_param_msg ("array_bubblesort2"));
	}

	if (nargs() < 2) {
		startindex = array_get_first_index (inra);
	}

	if (nargs() < 3) {
		endindex = array_get_last_index (inra);
	}

	# 9/11/2002: Corrected to stay within the array bounday
	for (i = startindex; i < endindex; i++) {
		for (j = i+1; j<= endindex; j++) {
			if (inra[i] > inra[j]) {
				tmp = inra[i];
				inra[i] = inra[j];
				inra[j] = tmp;
			}
		}
	}
}



# *********************
# * M E R G E S O R T *
# *********************

####
# Syntax: array_mergesort (inout ra[] [, in elementcount]);
# Return values: E_OK for success.
# Parameters:
#    ra[]: the array to sort
#    elementcount: the number of elements in the array
#        (can be obtained by array_length() function)
# Description: Sort an array using mergesort.
#    ** NOTE **: This function requires the _array_mergesort() function.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_mergesort (ra1);
#         array_mergesort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_mergesort (inout ra[], in n) {
	auto tmpra[], copy;

	if (nargs() == 1) {
		n = array_length (ra);
	} else if (nargs() != 2) {
		return (report_param_msg ("array_mergesort"));
	}

	_array_mergesort (ra, tmpra, n);

	# copy the result back to the original array.
	array_copy (tmpra, ra);

	# cleanup unneeded array
	array_clear (tmpra);
}

####
# Syntax: array_mergesort2 (inout ra[] [, in startindex[, endindex]]);
# Return values: E_OK for success.
# Parameters:
#    ra[]: the array to sort.
#    startindex: the first index of the element to be sorted in ra[].
#    endindex: the last index of the element to be sorted in ra[].
# Description: Sort an array or a subset of an array using mergesort.
#    The function allows sorting a subset of an array.
#    ** NOTE **: This function requires the _array_mergesort() function.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_mergesort (ra1);
#         array_mergesort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_mergesort2 (inout ra[], in startindex, endindex) {
	auto tmpra[], copy, pre_ra[], post_ra[], sort_ra[], fidx, lidx;

	if (nargs() < 1 || nargs() > 3) {
		return (report_param_msg ("array_mergesort2"));
	}

	# get first and last index of the array ra[]
	array_get_first_last_index (ra, fidx, lidx);

	if (nargs() < 2) {
		startindex = fidx;
	} else if (fidx < startindex) {
		array_subset (ra, pre_ra, fidx, startindex - 1, fidx);
	}

	if (nargs() < 3) {
		endindex = lidx;
	} else if (lidx > endindex) {
		array_subset (ra, post_ra, endindex + 1, lidx, endindex + 1);
	}

	array_subset (ra, sort_ra, startindex, endindex, 0);

	_array_mergesort (sort_ra, tmpra, endindex - startindex + 1);

	# copy the result back to the original array.
	array_clear (ra);
	if (fidx < startindex) {
		array_copy (pre_ra, ra);
		# 9/11/2002: Fixed bug in logic
		#array_append (tmpra, ra, startindex, endindex, startindex - 1);
		array_append (tmpra, ra, 0, endindex - startindex, startindex - 1);
	} else {
		array_copy (tmpra, ra);
	}
	if (lidx > endindex) {
		array_append (post_ra, ra, endindex + 1, lidx, endindex);
	}

	# cleanup unneeded array
	array_clear (tmpra);
	array_clear (pre_ra);
	array_clear (post_ra);
	array_clear (sort_ra);
}



####
# Syntax: _array_mergesort (inout origra[], out outra[], in n);
# Return values: E_OK for success.
# Parameters:
#    origra[]: the original array containing unsorted elements
#        (has to start at index 0)
#    outra[]: the target array to store sorted elements into
#    n: the number of elements in the array
#        (can be obtained by array_length() function)
# Description: Underlying Implementation of the array_mergesort.
#    This function requires an input and target array as parameters.
#    ** NOTE ** Normally, this function is not called directly,
#      use array_mergesort() instead.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         _array_mergesort (ra1, ra2, 5);
#         -> ra1[] is unchanged: {5, 1, 4, 5, 9}.
#         -> ra2[] is now: {1, 4, 5, 5, 9}.
####
function _array_mergesort (inout origra[], inout outra[], in n) {
	auto mid, ra1[], ra2[], ra1s[], ra2s[];

	if (nargs() == 2) {
		n = array_length (origra);
	} else if (nargs() != 3) {
		return (report_param_msg ("_array_mergesort"));
	}

	if (n < 2) {
		outra[0] = origra[0];
		return;
	}
	mid = int (n / 2);
	array_subset (origra, ra1, 0, mid - 1, 0);
	
	# 9/11/2002: Fixed bug (TYPO ERROR!!) that caused the array to contain empty string
	#array_subset (origra, ra2, mid, n - 1, mid);
	array_subset (origra, ra2, mid, n - 1, 0);

	_array_mergesort (ra1, ra1s, mid);
	_array_mergesort (ra2, ra2s, n - mid);

	array_merge (ra1s, ra2s, outra, mid, n - mid);

	array_clear (ra1);
	array_clear (ra1s);
	array_clear (ra2);
	array_clear (ra2s);
}

####
# Syntax: array_merge (inout a[], inout b[], out c[], m, n);
# Return values: E_OK for success.
# Parameters:
#    a[]: the first sorted array to merge
#    b[]: the second sorted array to merge
#    m: size of the first array (a[])
#    n: size of the second array (b[])
# Description: Merges the content of two sorted arrays into
#    one array.
#    ** NOTE ** This function is used by array_mergesort.
#    E.g. public ra1[] = {1, 4, 7}; # sorted array
#         public ra2[] = {4, 5, 7, 8}; # sorted array
#         array_merge (ra1, ra2, ra3, 3, 4);
#         -> ra1[] and ra2[] are unchanged.
#         -> ra3[] is now: {1, 4, 4, 5, 7, 7, 8}.
####
function array_merge (inout a[], inout b[], out c[], in m, in n) {
	auto i = 0, j = 0, k = 0;

	while (i < m && j < n) {
		if (a[i] < b[j])
			c[k++] = a[i++];
		else
			c[k++] = b[j++];
	}
	while (i < m) {
		c[k++] = a[i++];
	}
	while (j < n) {
		c[k++] = b[j++];
	}
}


# *********************
# * Q U I C K S O R T *
# *********************

####
# Syntax: array_quicksort (inout ra[] [, in elementcount]);
# Return values: E_OK for success.
# Parameters:
#    ra[]: the array to sort
#    elementcount: the number of elements in the array
#        (can be obtained by array_length() function)
# Description: Sort an array using quicksort.
#    ** NOTE **: This function requires the _array_mergesort() function.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_quicksort (ra1);
#         array_quicksort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_quicksort (inout ra[], in n) {
	auto tmpra[], copy;

	if (nargs() == 1) {
		n = array_length (ra);
	} else if (nargs() != 2) {
		return (report_param_msg ("array_quicksort"));
	}

	_array_quicksort (ra, tmpra, n);

	# copy the result back to the original array.
	array_copy (tmpra, ra);
	array_clear (tmpra);
}

####
# Syntax: array_quicksort2 (inout ra[] [, in startindex[, endindex]]);
# Return values: E_OK for success.
# Parameters:
#    ra[]: the array to sort
#    startindex: the first index of the element to be sorted in ra[].
#    endindex: the last index of the element to be sorted in ra[].
# Description: Sort an array or a portion of an array using quicksort.
#    The function allows sorting a subset of an array.
#    ** NOTE **: This function requires the _array_mergesort() function.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         public ra2[] = {"d", "z", "a", "y"};
#         array_quicksort (ra1);
#         array_quicksort (ra2);
#         -> ra1[] is now: {1, 4, 5, 5, 9}.
#         -> ra2[] is now: {"a", "d", "y", "z"}.
####
function array_quicksort2 (inout ra[], in startindex, in endindex) {
	auto tmpra[], copy, fidx, lidx, pre_ra[], post_ra[], sort_ra[];

	if (nargs() < 1 || nargs() > 3) {
		return (report_param_msg ("array_quicksort"));
	}

	# get first and last index of the array ra[]
	array_get_first_last_index (ra, fidx, lidx);

	if (nargs() < 2) {
		startindex = fidx;
	} else if (fidx < startindex) {
		array_subset (ra, pre_ra, fidx, startindex - 1, fidx);
	}

	if (nargs() < 3) {
		endindex = lidx;
	} else if (lidx > endindex) {
		array_subset (ra, post_ra, endindex + 1, lidx, endindex + 1);
	}

	# 9/11/2002: Fixed bug in logic
	#array_subset (ra, sort_ra, startindex, endindex - startindex + 1, 0);
	array_subset (ra, sort_ra, startindex, endindex, 0);

	_array_quicksort (sort_ra, tmpra, endindex - startindex + 1);

	# copy the result back to the original array.
	array_clear (ra);
	if (fidx < startindex) {
		array_copy (pre_ra, ra);
		# 9/11/2002: fixed bug in logic
		#array_append (tmpra, ra, startindex, endindex, startindex - 1);
		array_append (tmpra, ra, 0, endindex - startindex, startindex - 1);
	} else {
		array_copy (tmpra, ra);
	}
	array_append (post_ra, ra, endindex + 1, lidx, endindex);

	# cleanup unneeded array
	array_clear (tmpra);
	array_clear (pre_ra);
	array_clear (post_ra);
	array_clear (sort_ra);
}




####
# Syntax: _array_quicksort (inout origra[], out outra[], in n);
# Return values: E_OK for success.
# Parameters:
#    origra[]: the original array containing unsorted elements
#        (has to start at index 0)
#    outra[]: the target array to store sorted elements into
#    n: the number of elements in the array
#        (can be obtained by array_length() function)
# Description: Underlying Implementation of the array_quicksort.
#    This function requires an input and target array as parameters.
#    ** NOTE ** Normally, this function is not called directly,
#      use array_quicksort() instead.
#    E.g. public ra1[] = {5, 1, 4, 5, 9};
#         _array_quicksort (ra1, ra2, 5);
#         -> ra1[] is unchanged: {5, 1, 4, 5, 9}.
#         -> ra2[] is now: {1, 4, 5, 5, 9}.
####
function _array_quicksort (inout origra[], out outra[], in n) {
	auto r1[], r2[], r3[], r1s[], r2s[];
	auto r1c = 0, r2c = 0, r3c = 0, i, x, y;
	if (nargs() == 2) {
		n = array_length (origra);
	} else if (nargs() != 3) {
		return (report_param_msg ("_array_quicksort"));
	}
	if (n < 1) {
		return;
	}
	if (n < 2) {
		outra[0] = origra[0];
		return;
	}

	# pick a number
	x = origra[ int(n / 2)];

	for (i = 0; i < n; i++) {
		y = origra[i];
		if (y < x) {
			r1[r1c++] = y;
		} else if (y > x) {
			r2[r2c++] = y;
		} else {
			r3[r3c++] = y;
		}
	}

	_array_quicksort (r1, r1s, r1c);
	_array_quicksort (r2, r2s, r2c);

	# concantenate all the arrays
	if(r1c > 0) {array_append (r1s, outra, 0, r1c - 1, -1);}
	array_append (r3, outra, 0, r3c - 1, r1c - 1);
	if(r2c > 0) {array_append (r2s, outra, 0, r2c - 1);}

	# clean up all unused arrays
	array_clear (r1);
	array_clear (r2);
	array_clear (r3);
	array_clear (r1s);
	array_clear (r2s);
}



# Signifies that this module has been loaded
# Can be used by calling test to minimize reloading
# of the function.
# e.g.  
# if (!func_ra_sort_loaded) {
#   reload ("func_ra_sort", 1, 1);
# }
const func_ra_sort_loaded = TRUE;
