/* -*- mode: C -*-  */
/*
   IGraph library.
   Copyright (C) 2007-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard street, Cambridge, MA 02139 USA

   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 Street, Fifth Floor, Boston, MA
   02110-1301 USA

*/

#include "igraph_memory.h"
#include "igraph_error.h"

#include <string.h>         /* memcpy & co. */
#include <stdlib.h>

#define PARENT(x)     (((x)+1)/2-1)
#define LEFTCHILD(x)  (((x)+1)*2-1)
#define RIGHTCHILD(x) (((x)+1)*2)

/* Declare internal functions */
static void FUNCTION(igraph_heap, i_build)(BASE* arr, igraph_integer_t size, igraph_integer_t head);
static void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, igraph_integer_t size, igraph_integer_t elem);
static void FUNCTION(igraph_heap, i_sink)(BASE* arr, igraph_integer_t size, igraph_integer_t head);
static void FUNCTION(igraph_heap, i_switch)(BASE* arr, igraph_integer_t e1, igraph_integer_t e2);

/**
 * \ingroup heap
 * \function igraph_heap_init
 * \brief Initializes an empty heap object.
 *
 * Creates an \em empty heap, and also allocates memory
 * for some elements.
 *
 * \param h Pointer to an uninitialized heap object.
 * \param capacity Number of elements to allocate memory for.
 * \return Error code.
 *
 * Time complexity: O(\p alloc_size), assuming memory allocation is a
 * linear operation.
 */

igraph_error_t FUNCTION(igraph_heap, init)(TYPE(igraph_heap)* h, igraph_integer_t capacity) {
    IGRAPH_ASSERT(capacity >= 0);
    if (capacity == 0 ) {
        capacity = 1;
    }
    h->stor_begin = IGRAPH_CALLOC(capacity, BASE);
    IGRAPH_CHECK_OOM(h->stor_begin, "Cannot initialize heap.");
    h->stor_end = h->stor_begin + capacity;
    h->end = h->stor_begin;
    h->destroy = true;

    return IGRAPH_SUCCESS;
}

/**
 * \ingroup heap
 * \function igraph_heap_init_array
 * \brief Build a heap from an array.
 *
 * Initializes a heap object from an array. The heap is also
 * built of course (constructor).
 *
 * \param h Pointer to an uninitialized heap object.
 * \param data Pointer to an array of base data type.
 * \param len The length of the array at \p data.
 * \return Error code.
 *
 * Time complexity: O(n), the number of elements in the heap.
 */

igraph_error_t FUNCTION(igraph_heap, init_array)(TYPE(igraph_heap) *h, const BASE *data, igraph_integer_t len) {
    h->stor_begin = IGRAPH_CALLOC(len, BASE);
    IGRAPH_CHECK_OOM(h->stor_begin, "Cannot initialize heap from array.");
    h->stor_end = h->stor_begin + len;
    h->end = h->stor_end;
    h->destroy = true;

    memcpy(h->stor_begin, data, (size_t) len * sizeof(BASE));

    FUNCTION(igraph_heap, i_build) (h->stor_begin, h->end - h->stor_begin, 0);

    return IGRAPH_SUCCESS;
}

/**
 * \ingroup heap
 * \function igraph_heap_destroy
 * \brief Destroys an initialized heap object.
 *
 * \param h The heap object.
 *
 * Time complexity: O(1).
 */

void FUNCTION(igraph_heap, destroy)(TYPE(igraph_heap)* h) {
    if (h->destroy) {
        if (h->stor_begin != NULL) {
            IGRAPH_FREE(h->stor_begin); /* sets to NULL */
        }
    }
}

/**
 * \ingroup heap
 * \function igraph_heap_empty
 * \brief Decides whether a heap object is empty.
 *
 * \param h The heap object.
 * \return \c true if the heap is empty, \c false otherwise.
 *
 * TIme complexity: O(1).
 */

igraph_bool_t FUNCTION(igraph_heap, empty)(const TYPE(igraph_heap)* h) {
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);
    return h->stor_begin == h->end;
}

/**
 * \ingroup heap
 * \function igraph_heap_clear
 * \brief Removes all elements from a heap.
 *
 * This function simply sets the size of the heap to zero, it does
 * not free any allocated memory. For that you have to call
 * \ref igraph_heap_destroy().
 *
 * \param h The heap object.
 *
 * Time complexity: O(1).
 */

void FUNCTION(igraph_heap, clear)(TYPE(igraph_heap)* h) {
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);
    h->end = h->stor_begin;
}

/**
 * \ingroup heap
 * \function igraph_heap_push
 * \brief Add an element.
 *
 * Adds an element to the heap.
 *
 * \param h The heap object.
 * \param elem The element to add.
 * \return Error code.
 *
 * Time complexity: O(log n), n is the number of elements in the
 * heap if no reallocation is needed, O(n) otherwise. It is ensured
 * that n push operations are performed in O(n log n) time.
 */

igraph_error_t FUNCTION(igraph_heap, push)(TYPE(igraph_heap)* h, BASE elem) {
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);

    /* full, allocate more storage */
    if (h->stor_end == h->end) {
        igraph_integer_t old_size = FUNCTION(igraph_heap, size)(h);
        igraph_integer_t new_size =  old_size < IGRAPH_INTEGER_MAX/2 ? old_size * 2 : IGRAPH_INTEGER_MAX;
        if (old_size == IGRAPH_INTEGER_MAX) {
            IGRAPH_ERROR("Cannot push to heap, already at maximum size.", IGRAPH_EOVERFLOW);
        }
        if (new_size == 0) {
            new_size = 1;
        }
        IGRAPH_CHECK(FUNCTION(igraph_heap, reserve)(h, new_size));
    }

    *(h->end) = elem;
    h->end += 1;

    /* maintain heap */
    FUNCTION(igraph_heap, i_shift_up)(h->stor_begin, FUNCTION(igraph_heap, size)(h),
                                      FUNCTION(igraph_heap, size)(h) - 1);

    return IGRAPH_SUCCESS;
}

/**
 * \ingroup heap
 * \function igraph_heap_top
 * \brief Top element.
 *
 * For maximum heaps this is the largest, for minimum heaps the
 * smallest element of the heap.
 *
 * \param h The heap object.
 * \return The top element.
 *
 * Time complexity: O(1).
 */

BASE FUNCTION(igraph_heap, top)(const TYPE(igraph_heap)* h) {
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);
    IGRAPH_ASSERT(h->stor_begin != h->end);

    return h->stor_begin[0];
}

/**
 * \ingroup heap
 * \function igraph_heap_delete_top
 * \brief Removes and returns the top element.
 *
 * Removes and returns the top element of the heap. For maximum heaps
 * this is the largest, for minimum heaps the smallest element.
 *
 * \param h The heap object.
 * \return The top element.
 *
 * Time complexity: O(log n), n is the number of elements in the
 * heap.
 */

BASE FUNCTION(igraph_heap, delete_top)(TYPE(igraph_heap)* h) {
    BASE tmp;

    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);

    tmp = h->stor_begin[0];
    FUNCTION(igraph_heap, i_switch)(h->stor_begin, 0, FUNCTION(igraph_heap, size)(h) - 1);
    h->end -= 1;
    FUNCTION(igraph_heap, i_sink)(h->stor_begin, h->end - h->stor_begin, 0);

    return tmp;
}

/**
 * \ingroup heap
 * \function igraph_heap_size
 * \brief Number of elements in the heap.
 *
 * Gives the number of elements in a heap.
 *
 * \param h The heap object.
 * \return The number of elements in the heap.
 *
 * Time complexity: O(1).
 */

igraph_integer_t FUNCTION(igraph_heap, size)(const TYPE(igraph_heap)* h) {
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);
    return h->end - h->stor_begin;
}

/**
 * \ingroup heap
 * \function igraph_heap_reserve
 * \brief Reserves memory for a heap.
 *
 * Allocates memory for future use. The size of the heap is
 * unchanged. If the heap is larger than the \p capacity parameter then
 * nothing happens.
 *
 * \param h The heap object.
 * \param capacity The number of elements to allocate memory for.
 * \return Error code.
 *
 * Time complexity: O(\p capacity) if \p capacity is larger than the current
 * number of elements. O(1) otherwise.
 */

igraph_error_t FUNCTION(igraph_heap, reserve)(TYPE(igraph_heap)* h, igraph_integer_t capacity) {
    igraph_integer_t actual_size = FUNCTION(igraph_heap, size)(h);
    BASE *tmp;
    IGRAPH_ASSERT(h != NULL);
    IGRAPH_ASSERT(h->stor_begin != NULL);
    IGRAPH_ASSERT(capacity >= 0);

    if (capacity <= actual_size) {
        return IGRAPH_SUCCESS;
    }

    tmp = IGRAPH_REALLOC(h->stor_begin, (size_t) capacity, BASE);
    IGRAPH_CHECK_OOM(tmp, "Cannot reserve space for heap.");

    h->stor_begin = tmp;
    h->stor_end = h->stor_begin + capacity;
    h->end = h->stor_begin + actual_size;

    return IGRAPH_SUCCESS;
}

/**
 * \ingroup heap
 * \brief Build a heap, this should not be called directly.
 */

void FUNCTION(igraph_heap, i_build)(BASE* arr,
                                    igraph_integer_t size, igraph_integer_t head) {

    if (RIGHTCHILD(head) < size) {
        /* both subtrees */
        FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head) );
        FUNCTION(igraph_heap, i_build)(arr, size, RIGHTCHILD(head));
        FUNCTION(igraph_heap, i_sink)(arr, size, head);
    } else if (LEFTCHILD(head) < size) {
        /* only left */
        FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head));
        FUNCTION(igraph_heap, i_sink)(arr, size, head);
    } else {
        /* none */
    }
}

/**
 * \ingroup heap
 * \brief Shift an element upwards in a heap, this should not be
 * called directly.
 */

void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, igraph_integer_t size, igraph_integer_t elem) {

    if (elem == 0 || arr[elem] HEAPLESS arr[PARENT(elem)]) {
        /* at the top */
    } else {
        FUNCTION(igraph_heap, i_switch)(arr, elem, PARENT(elem));
        FUNCTION(igraph_heap, i_shift_up)(arr, size, PARENT(elem));
    }
}

/**
 * \ingroup heap
 * \brief Moves an element down in a heap, this function should not be
 * called directly.
 */

void FUNCTION(igraph_heap, i_sink)(BASE* arr, igraph_integer_t size, igraph_integer_t head) {

    if (LEFTCHILD(head) >= size) {
        /* no subtrees */
    } else if (RIGHTCHILD(head) == size ||
               arr[LEFTCHILD(head)] HEAPMOREEQ arr[RIGHTCHILD(head)]) {
        /* sink to the left if needed */
        if (arr[head] HEAPLESS arr[LEFTCHILD(head)]) {
            FUNCTION(igraph_heap, i_switch)(arr, head, LEFTCHILD(head));
            FUNCTION(igraph_heap, i_sink)(arr, size, LEFTCHILD(head));
        }
    } else {
        /* sink to the right */
        if (arr[head] HEAPLESS arr[RIGHTCHILD(head)]) {
            FUNCTION(igraph_heap, i_switch)(arr, head, RIGHTCHILD(head));
            FUNCTION(igraph_heap, i_sink)(arr, size, RIGHTCHILD(head));
        }
    }
}

/**
 * \ingroup heap
 * \brief Switches two elements in a heap, this function should not be
 * called directly.
 */

void FUNCTION(igraph_heap, i_switch)(BASE* arr, igraph_integer_t e1, igraph_integer_t e2) {
    if (e1 != e2) {
        BASE tmp = arr[e1];
        arr[e1] = arr[e2];
        arr[e2] = tmp;
    }
}
