#include "priority_queue.h"
#include "erl_nif.h"
#include <stdlib.h>
#include <string.h>

// Algorithm described at : http://robin-thomas.github.io/min-heap/

#define left(x) 2 * x + 1
#define right(x) 2 * x + 2
#define parent(x) (x - 1) / 2

#define less(a, b) less_(heap_[a], heap_[b])

PriorityQueue::PriorityQueue(LessFun ls, UpdatePositionFun upd, DestroyElementFun dtor) :
    capacity_(0),
    length_(0),
    heap_(NULL),
    less_(ls),
    update_pos_fun_(upd),
    item_dtor_(dtor) { }

PriorityQueue::~PriorityQueue()
{
    if(heap_)
    {
        for(int i = 0; i < length_; i++)
            item_dtor_(heap_[i]);

        enif_free(heap_);
    }
}

bool PriorityQueue::insert(void* item)
{
    int pos;

    if (length_ == capacity_)
    {
        int new_capacity = (length_+1) * 2;
        void** new_heap = reinterpret_cast<void**>(enif_alloc(sizeof(void*) * new_capacity));

        if (!new_heap)
            return false;

        memcpy(new_heap, heap_, sizeof(void*)*length_);
        enif_free(heap_);
        heap_ = new_heap;
        capacity_ = new_capacity;
    }

    pos = (length_)++;
    set(pos, item);
    bubble_down(pos);
    return true;
}

bool PriorityQueue::remove(void* item, int pos)
{
    if (pos >= length_)
        return false;

    if(heap_[pos] != item)
        return false;

    return remove(pos) == item;
}

void* PriorityQueue::remove(int pos)
{
    if (pos >= length_)
        return NULL;

    void* item = heap_[pos];
    length_--;

    int ls = less(pos, length_);

    set(pos, heap_[length_]);

    if(ls)
        bubble_up(pos);
    else
        bubble_down(pos);

    // todo: resize down the heap in case we have a lot of empty slots

    update_pos_fun_(item, -1);
    return item;
}

void* PriorityQueue::peek()
{
    if(length_ == 0)
        return NULL;

    return heap_[0];
}

// private

void PriorityQueue::set(int pos, void* item)
{
    heap_[pos] = item;
    update_pos_fun_(item, pos);
}

void PriorityQueue::pos_swap(int pos1, int pos2)
{
    void* tmp = heap_[pos1];
    set(pos1, heap_[pos2]);
    set(pos2, tmp);
}

void PriorityQueue::bubble_down(int pos)
{
    while(true)
    {
        int parent = parent(pos);

        if (pos == 0 || less(parent, pos))
            return;

        pos_swap(pos, parent);
        pos = parent;
    }
}

void PriorityQueue::bubble_up(int pos)
{
    while (true)
    {
        int left = left(pos);
        int right = right(pos);
        int smallest = pos;

        if (left < length_ && less(left, smallest))
            smallest = left;

        if (right < length_ && less(right, smallest))
            smallest = right;

        if (smallest == pos)
            return;

        pos_swap(pos, smallest);
        pos = smallest;
    }
}
