/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.utils;

import java.util.Comparator;


/**
 * Implementation of Tony Hoare's quickselect algorithm.
 * Running time is generally O(n), but worst case is O(n**2)
 * Pivot choice is median of three method, providing better performance
 * than a random pivot for partially sorted data.
 * @author Jon Renner
 */
public class QuickSelect<T> {
	private T[] array;
	private Comparator<? super T> comp;

	public int select(T[] items, Comparator<T> comp, int n, int size) {
		this.array = items;
		this.comp = comp;
		return recursiveSelect(0, size - 1, n);
	}

	private int partition(int left, int right, int pivot) {
		T pivotValue = array[pivot];
		swap(right, pivot);
		int storage = left;
		for (int i = left; i < right; i++) {
			if (comp.compare(array[i], pivotValue) < 0) {
				swap(storage, i);
				storage++;
			}
		}
		swap(right, storage);
		return storage;
	}

	private int recursiveSelect(int left, int right, int k) {
		if (left == right) return left;
		int pivotIndex = medianOfThreePivot(left, right);
		int pivotNewIndex = partition(left, right, pivotIndex);
		int pivotDist = (pivotNewIndex - left) + 1;
		int result;
		if (pivotDist == k) {
			result = pivotNewIndex;
		}
		else if (k < pivotDist) {
			result = recursiveSelect(left, pivotNewIndex - 1, k);
		} else {
			result = recursiveSelect(pivotNewIndex + 1, right, k - pivotDist);
		}
		return result;
	}

	/** Median of Three has the potential to outperform a random pivot, especially for partially sorted arrays */
	private int medianOfThreePivot(int leftIdx, int rightIdx) {
		T left = array[leftIdx];
		int midIdx = (leftIdx + rightIdx) / 2;
		T mid = array[midIdx];
		T right = array[rightIdx];

		// spaghetti median of three algorithm
		// does at most 2 comparisons
		if (comp.compare(left, mid) > 0) {
			if (comp.compare(mid, right) > 0) {
				return midIdx;
			} else if (comp.compare(left, right) > 0) {
				return rightIdx;
			} else {
				return leftIdx;
			}
		} else {
			if (comp.compare(left, right) > 0) {
				return leftIdx;
			} else if (comp.compare(mid, right) > 0) {
				return rightIdx;
			} else {
				return midIdx;
			}
		}
	}

	private void swap(int left, int right) {
		T tmp = array[left];
		array[left] = array[right];
		array[right] = tmp;
	}
}
