/*
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#ifndef _PICO_UTIL_PHEAP_H
#define _PICO_UTIL_PHEAP_H

#include "pico.h"

#ifdef __cplusplus
extern "C" {
#endif

// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PHEAP, Enable/disable assertions in the pheap module, type=bool, default=0, group=pico_util
#ifndef PARAM_ASSERTIONS_ENABLED_PHEAP
#define PARAM_ASSERTIONS_ENABLED_PHEAP 0
#endif

/**
 * \file pheap.h
 * \defgroup util_pheap pheap
 * \brief Pairing Heap Implementation
 * \ingroup pico_util
 *
 * pheap defines a simple pairing heap. The implementation simply tracks array indexes, it is up to
 * the user to provide storage for heap entries and a comparison function.
 *
 * NOTE: This class is not safe for concurrent usage. It should be externally protected. Furthermore
 * if used concurrently, the caller needs to protect around their use of the returned id.
 * For example, ph_remove_and_free_head returns the id of an element that is no longer in the heap.
 * The user can still use this to look at the data in their companion array, however obviously further operations
 * on the heap may cause them to overwrite that data as the id may be reused on subsequent operations
 *
 */
// PICO_CONFIG: PICO_PHEAP_MAX_ENTRIES, Maximum number of entries in the pheap, min=1, max=65534, default=255, group=pico_util
#ifndef PICO_PHEAP_MAX_ENTRIES
#define PICO_PHEAP_MAX_ENTRIES 255
#endif

// public heap_node ids are numbered from 1 (0 means none)
#if PICO_PHEAP_MAX_ENTRIES < 256
typedef uint8_t pheap_node_id_t;
#elif PICO_PHEAP_MAX_ENTRIES < 65535
typedef uint16_t pheap_node_id_t;
#else
#error invalid PICO_PHEAP_MAX_ENTRIES
#endif

/*! \brief A node within a pairing heap
 *  \ingroup util_pheap
 *
 * Stores the linkage indices for a single node in the heap tree.
 * User state for each node is maintained separately in a companion array.
 */
typedef struct pheap_node {
    pheap_node_id_t child;   ///< Id of the first child node, or 0 if none
    pheap_node_id_t sibling; ///< Id of the next sibling node, or 0 if none
    pheap_node_id_t parent;  ///< Id of the parent node, or 0 if this is the root
} pheap_node_t;

/**
 * \brief A user comparator function for nodes in a pairing heap.
 * \ingroup util_pheap
 *
 * \return true if a < b in natural order. Note this relative ordering must be stable from call to call.
 */
typedef bool (*pheap_comparator)(void *user_data, pheap_node_id_t a, pheap_node_id_t b);

/*! \brief A pairing heap instance
 *  \ingroup util_pheap
 *
 * Maintains the state for a pairing heap. Create with ph_create() or initialise
 * statically with PHEAP_DEFINE_STATIC() and ph_post_alloc_init().
 */
typedef struct pheap {
    pheap_node_t *nodes;          ///< Array of all nodes, indexed by node id minus one
    pheap_comparator comparator;  ///< Comparator used to determine relative ordering of nodes
    void *user_data;              ///< User data pointer passed to the comparator
    pheap_node_id_t max_nodes;    ///< Maximum number of nodes the heap can hold
    pheap_node_id_t root_id;      ///< Id of the current root (minimum) node, or 0 if the heap is empty
    // we remove from head and add to tail to stop reusing the same ids
    pheap_node_id_t free_head_id; ///< Id of the first node in the free list, or 0 if none
    pheap_node_id_t free_tail_id; ///< Id of the last node in the free list, or 0 if none
} pheap_t;

/**
 * \brief Create a pairing heap, which effectively maintains an efficient sorted ordering
 * of nodes. The heap itself stores no user per-node state, it is expected
 * that the user maintains a companion array. A comparator function must
 * be provided so that the heap implementation can determine the relative ordering of nodes
 * \ingroup util_pheap
 *
 * \param max_nodes the maximum number of nodes that may be in the heap (this is bounded by
 *                  PICO_PHEAP_MAX_ENTRIES which defaults to 255 to be able to store indexes
 *                  in a single byte).
 * \param comparator the node comparison function
 * \param user_data a user data pointer associated with the heap that is provided in callbacks
 * \return a newly allocated and initialized heap
 */
pheap_t *ph_create(uint max_nodes, pheap_comparator comparator, void *user_data);

/**
 * \brief Removes all nodes from the pairing heap
 * \ingroup util_pheap
 * \param heap the heap
 */
void ph_clear(pheap_t *heap);

/**
 * \brief De-allocates a pairing heap
 * \ingroup util_pheap
 *
 * Note this method must *ONLY* be called on heaps created by ph_create()
 * \param heap the heap
 */
void ph_destroy(pheap_t *heap);

// internal method
static inline pheap_node_t *ph_get_node(pheap_t *heap, pheap_node_id_t id) {
    assert(id && id <= heap->max_nodes);
    return heap->nodes + id - 1;
}

// internal method
static void ph_add_child_node(pheap_t *heap, pheap_node_id_t parent_id, pheap_node_id_t child_id) {
    pheap_node_t *n = ph_get_node(heap, parent_id);
    assert(parent_id);
    assert(child_id);
    assert(parent_id != child_id);
    pheap_node_t *c = ph_get_node(heap, child_id);
    c->parent = parent_id;
    if (!n->child) {
        n->child = child_id;
    } else {
        c->sibling = n->child;
        n->child = child_id;
    }
}

// internal method
static pheap_node_id_t ph_merge_nodes(pheap_t *heap, pheap_node_id_t a, pheap_node_id_t b) {
    if (!a) return b;
    if (!b) return a;
    if (heap->comparator(heap->user_data, a, b)) {
        ph_add_child_node(heap, a, b);
        return a;
    } else {
        ph_add_child_node(heap, b, a);
        return b;
    }
}

/**
 * \brief Allocate a new node from the unused space in the heap
 * \ingroup util_pheap
 *
 * \param heap the heap
 * \return an identifier for the node, or 0 if the heap is full
 */
static inline pheap_node_id_t ph_new_node(pheap_t *heap) {
    if (!heap->free_head_id) return 0;
    pheap_node_id_t id = heap->free_head_id;
    pheap_node_t *hn = ph_get_node(heap, id);
    heap->free_head_id = hn->sibling;
    if (!heap->free_head_id) heap->free_tail_id = 0;
    hn->child = hn->sibling = hn->parent = 0;
    return id;
}

/**
 * \brief Inserts a node into the heap.
 * \ingroup util_pheap
 *
 * This method inserts a node (previously allocated by ph_new_node())
 * into the heap, determining the correct order by calling
 * the heap's comparator
 *
 * \param heap the heap
 * \param id the id of the node to insert
 * \return the id of the new head of the pairing heap (i.e. node that compares first)
 */
static inline pheap_node_id_t ph_insert_node(pheap_t *heap, pheap_node_id_t id) {
    assert(id);
    pheap_node_t *hn = ph_get_node(heap, id);
    hn->child = hn->sibling = hn->parent = 0;
    heap->root_id = ph_merge_nodes(heap, heap->root_id, id);
    return heap->root_id;
}

/**
 * \brief Returns the head node in the heap, i.e. the node
 * which compares first, but without removing it from the heap.
 * \ingroup util_pheap
 *
 * \param heap the heap
 * \return the current head node id
 */
static inline pheap_node_id_t ph_peek_head(pheap_t *heap) {
    return heap->root_id;
}

/**
 * \brief Remove the head node from the pairing heap. This head node is
 * the node which compares first in the logical ordering provided
 * by the comparator.
 * \ingroup util_pheap
 *
 * Note that in the case of free == true, the returned id is no longer
 * allocated and may be re-used by future node allocations, so the caller
 * should retrieve any per node state from the companion array before modifying
 * the heap further.
 *
 * @param heap the heap
 * @param free true if the id is also to be freed; false if not - useful if the caller
 *        may wish to re-insert an item with the same id)
 * @return the old head node id.
 */
pheap_node_id_t ph_remove_head(pheap_t *heap, bool free);

/**
 * \brief Remove the head node from the pairing heap. This head node is
 * the node which compares first in the logical ordering provided
 * by the comparator.
 * \ingroup util_pheap
 *
 * Note that the returned id will be freed, and thus may be re-used by future node allocations,
 * so the caller should retrieve any per node state from the companion array before modifying
 * the heap further.
 *
 * @param heap the heap
 * @return the old head node id.
 */
static inline pheap_node_id_t ph_remove_and_free_head(pheap_t *heap) {
    return ph_remove_head(heap, true);
}

/**
 * \brief Remove and free an arbitrary node from the pairing heap. This is a more
 * costly operation than removing the head via ph_remove_and_free_head()
 * \ingroup util_pheap
 *
 * @param heap the heap
 * @param id the id of the node to free
 * @return true if the the node was in the heap, false otherwise
 */
bool ph_remove_and_free_node(pheap_t *heap, pheap_node_id_t id);

/**
 * \brief Determine if the heap contains a given node. Note containment refers
 * to whether the node is inserted (ph_insert_node()) vs allocated (ph_new_node())
 * \ingroup util_pheap
 *
 * @param heap the heap
 * @param id the id of the node
 * @return true if the heap contains a node with the given id, false otherwise.
 */
static inline bool ph_contains_node(pheap_t *heap, pheap_node_id_t id) {
    return id == heap->root_id || ph_get_node(heap, id)->parent;
}


/**
 * \brief Free a node that is not currently in the heap, but has been allocated
 * \ingroup util_pheap
 *
 * @param heap the heap
 * @param id the id of the node
 */
static inline void ph_free_node(pheap_t *heap, pheap_node_id_t id) {
    assert(id && !ph_contains_node(heap, id));
    if (heap->free_tail_id) {
        ph_get_node(heap, heap->free_tail_id)->sibling = id;
    }
    if (!heap->free_head_id) {
        assert(!heap->free_tail_id);
        heap->free_head_id = id;
    }
    heap->free_tail_id = id;
}

/**
 * \brief Print a representation of the heap for debugging
 * \ingroup util_pheap
 *
 * @param heap the heap
 * @param dump_key a method to print a node value
 * @param user_data the user data to pass to the dump_key method
 */
void ph_dump(pheap_t *heap, void (*dump_key)(pheap_node_id_t id, void *user_data), void *user_data);

/**
 * \brief Initialize a statically allocated heap (ph_create() using the C heap).
 * The heap member `nodes` must be allocated of size max_nodes.
 * \ingroup util_pheap
 *
 * @param heap the heap
 * @param max_nodes the max number of nodes in the heap (matching the size of the heap's nodes array)
 * @param comparator the comparator for the heap
 * @param user_data the user data for the heap.
 */
void ph_post_alloc_init(pheap_t *heap, uint max_nodes, pheap_comparator comparator, void *user_data);

/**
 * \brief Define a statically allocated pairing heap. This must be initialized
 * by ph_post_alloc_init
 * \ingroup util_pheap
 */
#define PHEAP_DEFINE_STATIC(name, _max_nodes) \
    static_assert(_max_nodes && _max_nodes < (1u << (8 * sizeof(pheap_node_id_t))), ""); \
    static pheap_node_t name ## _nodes[_max_nodes]; \
    static pheap_t name = { \
            .nodes = name ## _nodes, \
            .max_nodes = _max_nodes \
    };


#ifdef __cplusplus
}
#endif

#endif
