/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil ; -*- */
/*
 *  (C) 2001 by Argonne National Laboratory.
 *      See COPYRIGHT in top-level directory.
 */

#include "mpiimpl.h"
#include <stdlib.h>
#include <assert.h>

#define GROUPSIZE 5

/* Insertion sort on array 'a' containing n elements */
static inline void isort(int a[], const int n)
{
    int i;
    int j;
    int key;

    for (j = 1; j < n; ++j) {
	key = a[j];
	i = j - 1;
	while (i >= 0 && a[i] > key) {
	    a[i+1] = a[i];
	    i -= 1;
	}
	a[i+1] = key;
    }
}


/* Partition array 'a' of size n around the value pivot and returns
 * the index of the pivot point */
static inline int partition(const int pivot, int a[], const int n)
{
    int i = 0;
    int j = n-1;
    int tmp;

    while (1) {
	while ((j > 0) && (a[j] < pivot)) --j;
	while ((i < n) && (a[i] >= pivot)) ++i;

	if (i < j) {
	    tmp = a[i];
	    a[i] = a[j];
	    a[j] = tmp;
	}
	else {
	    return j;
	}
    }
}


/* This does all of the work for k_select */
static void k_select_r(const int k, int a[], const int n, int *value)
{
    int ngroups;
    int i;
    int median_median;
    int surfeit;
    int mk;
    int *medians;

    assert (k < n);
    ngroups = (n + GROUPSIZE-1) / GROUPSIZE;
    medians = MPIU_Malloc(ngroups * sizeof (int));

    /* Divide 'a' into groups, sort the elements of each group and
       pull out the median of each group */
    for (i = 0; i < ngroups-1; ++i) {
	isort(&a[i * GROUPSIZE], GROUPSIZE);
	medians[i] = a[i*GROUPSIZE + GROUPSIZE/2];
    }
    surfeit = (n - i * GROUPSIZE);
    isort(&a[i * GROUPSIZE], surfeit);
    medians[i] = a[i*GROUPSIZE + surfeit/2];

    if (ngroups == 1) {
	*value = a[n - 1 - k];
	MPIU_Free(medians);
	return;
    }

    /* use k_select to find the median of medians */
    k_select_r((ngroups / 2) - 1 + (ngroups % 2), medians, ngroups, &median_median);
    MPIU_Free(medians);

    /* partition array a around the median of medians and return the
       index of the median of medians */
    mk = partition(median_median, a, n);

    /* if this is the last element, we're done */
    if (mk == n - 1)
	*value = a[mk];
    else if (k <= mk)
	/* call k_select to find the kth element in a[0..mk-1] (the
	   array up to the element before the median of medians) */
	k_select_r(k, &a[0], mk + 1, value);
    else
	/* call k_select to find the (k-mk)th element in a[mk+1..n] (the
	   array starting from the element after the median of medians) */
	k_select_r(k - (mk+1), &a[mk+1], n - (mk+1), value);
}


/* Returns the value of the kth smallest element in array a of size
 * n. Allocates an array and calls the recursive function k_select_r
 * which does all the work */
static void k_select(const int k, int a[], const int n, int *value)
{
    k_select_r(k - 1, a, n, value);
}

/* silence "no previous prototype" warnings */
int MPIU_Outlier_ratio(int * sizes, int n_sizes, double outlier_frac, double threshold);

/* Returns the ratio of the maximum size and the outlier_frac
 * percentile size. */
int MPIU_Outlier_ratio(int * sizes, int n_sizes, double outlier_frac, double threshold)
{
    int max, k_max, i;
    double retval;

    k_select(n_sizes, sizes, n_sizes, &max);
    max = sizes[0];
    for (i = 0; i < n_sizes; i++)
	if (max < sizes[i])
	    max = sizes[i];
    k_select((int) (n_sizes * outlier_frac), sizes, n_sizes, &k_max);
    retval = (double) max / k_max;

    if (retval > threshold) {
	return 1;
    }
    else {
	return 0;
    }
}
