/*
 *
 * Copyright (c) 1994, 2015
 * Hewlett-Packard Company
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Hewlett-Packard Company makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * Copyright (c) 1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Silicon Graphics makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * Contributions:
 *              IBM - Ported the code to Java
 */

package org.eclipse.ui.internal.views.markers;

import java.util.Arrays;
import java.util.Comparator;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.ui.views.markers.MarkerItem;

/**
 * @since 3.5
 *
 * @author Hitesh
 */
public class MarkerSortUtil {

	/*
	 * Note: partial quicksort or introsort would not be of much use here as the
	 * sorting direction can be reversed easily from the UI. These would perform
	 * at quadratic complexities in these conditions. The code below is based on
	 * a variant of Modified HeapSort.Runs in O(NlogN) in worst case.
	 */

	/*
	 * Increasing BATCH_SIZE increases memory consumption but increases speed, and
	 * vice-versa.This indirectly controls the number of active caches in
	 * MarkerEntry[] array passed for sorting. Batching is disabled by default.
	 * Batching can be enabled with system property
	 * org.eclipse.ui.MarkerSortUtil.batchSize=10000
	 */
	private static int BATCH_SIZE = Integer.getInteger("org.eclipse.ui.MarkerSortUtil.batchSize", Integer.MAX_VALUE); //$NON-NLS-1$

	/*
	 * For n/k ratios less than this , we will use Arrays.Sort(). The heapsort
	 * performs nearly as good as mergesort for small data.We can still benefit
	 * from the mergesort - Arrays.Sort(). When the number of elements to be sorted,
	 * are almost as much as the elements we have.
	 */
	private static float MERGE_OR_HEAP_SWITCH=1.5f;

	/**
	 * Sorts [first,middle] in the array of [first,last] using a variant of
	 * modified heapsort, such that {@literal
	 * array[first]<array[first+1]<...<array[middle] and
	 * array[middle]<arra[middle+1||middle+2|| ....last]}
	 *
	 * @param array
	 * @param first
	 * @param middle
	 * @param last
	 * @param comparator
	 */
	private static void partiallySort(MarkerEntry[] array, int first, int middle, int last,
			Comparator<MarkerItem> comparator) {
		heapify(array, first, middle, comparator);

		adjustMaxElement(array, first, middle, last, comparator);

		heapToSortedArray(array, first, middle, comparator);
	}

	/**
	 * Swap the max heap element with any greater elements in rest of the array
	 *
	 * @param heapArray
	 * @param first
	 * @param heapSize
	 * @param last
	 * @param comparator
	 */
	private static void adjustMaxElement(MarkerEntry[] heapArray, int first, int heapSize, int last,
			Comparator<MarkerItem> comparator) {
		/*
		 * we do not clear caches for heap elements when re-adjusting and
		 * sorting this will ensure sorting remains fast
		 */
		int current = heapSize;
		while (current <= last) {
			if (comparator.compare(heapArray[current], heapArray[first]) < 0) {
				MarkerEntry tmp = heapArray[current];
				heapArray[current] = heapArray[first];
				heapArray[first] = tmp;
				adjustHeap(heapArray, first, first, heapSize, comparator);
			}
			// clear cache of the one not in heap
			heapArray[current].clearCache();

			++current;
		}
		MarkerEntry.clearCollationCache();
	}

	/**
	 *  Re-adjust the elements in the heap to maintain heap-property
	 *
	 *  Note: caches are not cleared in this method, as it would offset
	 *  to a certain extent the benefit of caching in sorting.
	 *
	 * @param array
	 * @param first
	 * @param position
	 * @param last
	 * @param comparator
	 */
	private static void adjustHeap(MarkerEntry[] array, int first,
 int position, int last,
			Comparator<MarkerItem> comparator) {
		MarkerEntry hole = array[position];
		int holeIndex = position;
		holeIndex = leafSearch(array, first, holeIndex, last, comparator);
		holeIndex = bottomUpSearch(array, first, holeIndex,position,hole,last, comparator);
		array[holeIndex] = hole;
	}

	/**
	 * Percolate down the Heap: adjust left ,right, self nodes for heap starting
	 * from hole all the way down the heap
	 *
	 * @param array
	 * @param first
	 * @param position
	 * @param last
	 * @param comparator
	 * @return new holeIndex
	 */
	private static int leafSearch(MarkerEntry[] array, int first, int position,
 int last,
			Comparator<MarkerItem> comparator) {
		int holeOffset = position - first;
		int len = last - first;
		int childOffset = 2 * holeOffset + 2;
		//
		while (childOffset < len) {
			if (comparator.compare(array[first + childOffset], array[first
					+ (childOffset - 1)]) < 0)
				--childOffset;
			array[first + holeOffset] = array[first + childOffset];
			holeOffset = childOffset++;
			childOffset *= 2;
		}
		if (childOffset-- == len) {
			array[first + holeOffset] = array[first + childOffset];
			holeOffset = childOffset;
		}
		return holeOffset + first;
	}

	/**
	 * percolate up the Heap: add the hole element back to heap at the right
	 * position, all the way up the heap between fromIndex and toIndex
	 *
	 * @param array
	 * @param first
	 * @param fromIndex
	 * @param toIndex
	 * @param hole
	 * @param last
	 * @param comparator
	 * @return new holeIndex
	 */
	private static int bottomUpSearch(MarkerEntry[] array, int first, int fromIndex,
 int toIndex, MarkerEntry hole,
			int last, Comparator<MarkerItem> comparator) {
		int holeOffset = fromIndex - first;
		int parent = (holeOffset - 1) / 2;
		int top = toIndex - first;

		while (holeOffset != top
				&& (comparator.compare(array[first + parent], hole) < 0)) {
			array[first + holeOffset] = array[first + parent];
			holeOffset = parent;
			parent = (holeOffset - 1) / 2;
		}

		/*
		 * Using Binary search to locate the parent to replace.
		 * This is worse compared to linear search as most of the
		 * holes would replace only a few parents above them.
		 * This code has been left commented for future examination.
		 * */
		/*
		int top = position - first;
		int lowParent = 1;
		int highParent = 0;
		int from = holeOffset;
		while (from > top) {
			from = (from - 1) / 2;
			highParent++;
		}
		if (from != top)
			highParent--;// Between, exculding top//n++;

		int parentReplaceCount = -1;
		// start at lower ranges because of heap property
		int midParent = lowParent + (highParent - lowParent) / 4;
		while (lowParent <= highParent) {
			// mth parent
			int currenParentIndex = holeOffset;
			int mth = midParent;
			while (mth > 0) {
				currenParentIndex = (currenParentIndex - 1) / 2;
				mth--;
			}
			int value = comparator.compare(array[first + currenParentIndex], hole);
			if (value < 0) {
				parentReplaceCount = midParent;
				lowParent = midParent + 1;
			} else if (value > 0) {
				highParent = midParent - 1;
			} else {
				// we are looking for just lower
				// not exact match
				highParent = midParent - 1;
			}
			midParent = (highParent + lowParent) / 2;
		}
		int parent = (holeOffset - 1) / 2;
		for (int i = 1; i <= parentReplaceCount; i++) {
			array[first + holeOffset] = array[first + parent];
			holeOffset = parent;
			parent = (holeOffset - 1) / 2;
		}
		*/
		return first + holeOffset;
	}



	/**
	 * Makes a heap in the array
	 * @param array
	 * @param first
	 * @param last
	 * @param comparator
	 */
	private static void heapify(MarkerEntry[] array, int first, int last,
 Comparator<MarkerItem> comparator) {
		if (last - first < 2)
			return;
		int parent = (last - first - 2) / 2;
		do
			adjustHeap(array, first, first + parent, last, comparator);
		while (parent-- != 0);
	}

	/**
	 * Converts a heap array to sorted array
	 *
	 * @param array
	 * @param first
	 * @param last
	 * @param comparator
	 *
	 */
	private static void heapToSortedArray(MarkerEntry[] array, int first,
 int last, Comparator<MarkerItem> comparator) {
		//TODO:Use mergesort to convert the heap to sorted array?

		while (last - first > 1) {
			// clear cache sorted and present at the end
			array[last].clearCache();
			// leave out the max elements at the end
			MarkerEntry tmp = array[--last];
			array[last] = array[first];
			array[first] = tmp;
			// readjust for next max
			adjustHeap(array, first, first, last, comparator);
		}
		array[first+1].clearCache();
		array[first].clearCache();
	}

	/**
	 * Sorts <code>[from,first+k-1]</code> in the array of <code>[from,to]</code>
	 * using a variant of modified heapsort, such that
	 *
	 * <pre>
	 * array[from]&lt;array[from+1]&lt;...&lt;array[from+k-1] and
	 * array[from+k-1]&lt;array[from+k||from+k+1||from+k+2|| ....to]
	 * </pre>
	 *
	 * Note: if k is greater than a number,the sorting happens in batches of that
	 * number, this for performance reasons.
	 *
	 * @param entries
	 * @param comparator
	 * @param from
	 * @param to
	 * @param k
	 * @param monitor
	 */
	public static void sortStartingKElement(MarkerEntry[] entries,
 Comparator<MarkerItem> comparator, int from, int to,
			int k, IProgressMonitor monitor) {
		// check range valid
		int last = from + k-1;
		if (entries.length == 0 || from < 0 || from >= to || last < from
				|| last > to || to > entries.length - 1 || to < 0)
			return;
		int n=to-from+1;
		if (BATCH_SIZE == Integer.MAX_VALUE || (n <= BATCH_SIZE && (((float) n / k) <= MERGE_OR_HEAP_SWITCH))
				/*|| ((float) n / k) <= MERGE_OR_HEAP_SWITCH*/) {
			// use arrays sort
			Arrays.sort(entries, from, to + 1, comparator);
			// clear cache for first to middle since we are done with sort
			for (int i = from; i <= to; i++) {
				entries[i].clearCache();
			}
			MarkerEntry.clearCollationCache();
			return;
		}

		// do it in blocks of BATCH_SIZE so we get a chance
		// of clearing caches to keep memory usage to a minimum

		//we choose k-1 so that last batch includes last element
		//in case k is a multiple of  BATCH_SIZE
		int totalBatches = (k-1) / BATCH_SIZE;
		int batchCount = 0;
		while (totalBatches > 0) {
			if(monitor.isCanceled()){
				return;
			}
			int fromTemp = from + batchCount * BATCH_SIZE;
			int toTemp = from + (batchCount + 1) * BATCH_SIZE;
			partiallySort(entries, fromTemp, toTemp, to, comparator);
			batchCount++;
			totalBatches--;
		}
		if(monitor.isCanceled()){
			return;
		}
		if (last >= from + batchCount * BATCH_SIZE) {
			// the last remaining enteries
			if (last == to) {
				partiallySort(entries, from + batchCount * BATCH_SIZE, last,
						to, comparator);
			} else {
				partiallySort(entries, from + batchCount * BATCH_SIZE, last+1,
						to, comparator);
			}
		}
	}

	/**
	 * @param fArray1
	 * @param comparator
	 * @param from
	 * @param k
	 * @param limit
	 */
	public static void sortStartingKElement(MockMarkerEntry[] fArray1,
 Comparator<MarkerItem> comparator, int from,
			int k, int limit) {
		sortStartingKElement(fArray1, comparator, from, k, limit,new NullProgressMonitor());
	}

	/**
	 * Sorts <code>[0,k-1]</code> in the array of <code>[0,entries.length-1]</code>
	 * using a variant of modified heapsort, such that
	 *
	 * <pre>
	 * array[0]&lt;array[1]&lt;...&lt;array[k-1] and
	 * array[k-1]&lt;array[k||k+1||k+2|| ....entries.length-1]
	 * </pre>
	 *
	 * Note: if k is greater than a number,the sorting happens in batches of that
	 * number, this for performance reasons.
	 *
	 * @param entries
	 * @param comparator
	 * @param k
	 * @param monitor
	 */
	public static void sortStartingKElement(MarkerEntry[] entries,
 Comparator<MarkerItem> comparator, int k,
			IProgressMonitor monitor) {
		sortStartingKElement(entries, comparator, 0, entries.length - 1, k,monitor);
	}

	/**
	 *
	 * Sorts [from,first+k-1] in the array of [from,entries.length-1] using a variant of
	 * modified heapsort, such that {@literal
	 * array[from]<array[from+1]<...<array[from+k-1] and
	 * array[from+k-1]<arra[from+k||from+k+1||from+k+2|| ....entries.length-1]}
	 *
	 * Note: if k is greater than a number,the sorting happens in batches of
	 * that number, this for performance reasons.
	 *
	 * @param entries
	 * @param comparator
	 * @param from
	 * @param k
	 * @param monitor
	 */
	public static void sortStartingKElement(MarkerEntry[] entries,
 Comparator<MarkerItem> comparator, int from, int k,
			IProgressMonitor monitor) {
		sortStartingKElement(entries, comparator, from, entries.length - 1, k,monitor);
	}

}