/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 European Southern Observatory
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>

#include <cxmemory.h>
#include <cxmessages.h>

#include "giarray.h"


/**
 * @defgroup giarray C Array Utilities
 *
 * TBD
 */

/**@{*/

inline static void
_giraffe_swap(cxdouble* a, cxdouble* b)
{
    register cxdouble tmp = *a;

    *a = *b;
    *b = tmp;

    return;

}


inline static cxint
_giraffe_array_heap_sort(cxdouble* array, cxsize size)
{

    register cxsize l = size >> 1;

    cxsize ir = size - 1;


    while (1) {

        register cxsize i = 0;
        register cxsize j = 0;

        register cxdouble rra = 0.;

        if ( l > 0) {
            rra = array[--l];        /* Still in hiring phase. */
        }
        else {                       /* In retirement-and-promotion phase. */
            rra = array[ir];         /* Clear a space at end of array.  */
            array[ir--] = array[0];  /* Retire the top of the heap into it. */
            if (ir == 0) {           /* Done with the last promotion. */
                array[0] = rra;      /* The least competent worker of all! */
                return 0;
            }
        }

        i = l;               /* Whether in the hiring phase or promotion */
        j = (l << 1) + 1;    /* phase, we here set up to sift down element */
        while (j <= ir) {    /* rra to its proper level. */

            /* Compare to the better underling. */
            if (j < ir && (array[j+1] - array[j]) > DBL_EPSILON) {
                ++j;
            }

            if (array[j] - rra > DBL_EPSILON) {    /* Demote rra. */
                array[i] = array[j];
                j += (i = j) + 1;
            }
            else {
                j = ir + 1;
            }
        }
        array[i] = rra;              /* Put into its slot. */
    }

}


inline static cxdouble
_giraffe_array_get_sorted(cxdouble* a, cxint n, cxint k)
{

    register cxint l = 0;
    register cxint m = n - 1;


    while (l < m) {

        register cxint i = l;
        register cxint j = m;

        register cxdouble x = a[k];


        do {

            while (x - a[i] > DBL_EPSILON) {
                ++i;
            }

            while (a [j] - x > DBL_EPSILON) {
                --j;
            }

            if (i <= j) {
                _giraffe_swap(&a[i], &a[j]) ;
                ++i;
                --j;
            }

        } while (i <= j);

        if (j < k) {
            l = i;
        }

        if (k < i) {
            m = j;
        }

    }

    return a[k];

}


/**
 * @brief
 *   Sorts an array in ascending order.
 *
 * @param array  The array to sort.
 * @param size   The array size.
 *
 * @return The function returns 0 on success and -1 otherwise
 *
 * The function sorts the first @em size elements of @em array in ascending
 * order. During sorting the elements of @em array are reshuffled so that
 * on return the contents of @em array is sorted. The number of elements
 * @em size to be sorted may be less than the actual array size.
 */

cxint
giraffe_array_sort(cxdouble* array, cxsize size)
{

    return _giraffe_array_heap_sort(array, size);

}


cxdouble
giraffe_array_mean(const cxdouble* array, cxsize size)
{
    register cxsize i = 0;

    register cxdouble sum = 0.;


    for (i = 0; i < size; i++) {
        sum += array[i];
    }

    return sum / size;

}


cxdouble
giraffe_array_median(const cxdouble* array, cxsize size)
{

    register cxsize position = (size & 1) == 1 ? size / 2 : size / 2 - 1;

    cxdouble median = 0.;
    cxdouble* a = NULL;


    cx_assert(array != NULL);

    a = cx_calloc(size, sizeof(cxdouble));
    memcpy(a, array, size * sizeof(cxdouble));


    median = _giraffe_array_get_sorted(a, size, position);

    cx_free(a);
    a = NULL;

    return median;

}

/**@}*/
