/*
 
    Texel - A UCI chess engine.
 
    Copyright (C) 2013  Peter Ă–sterlund, peterosterlund2@gmail.com
 
 
 
    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 3 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, see <http://www.gnu.org/licenses/>.
 
*/
 
 
 
/*
 
 * heap.hpp
 
 *
 
 *  Created on: Oct 3, 2013
 
 *      Author: petero
 
 */
 
 
 
#ifndef HEAP_HPP_
 
#define HEAP_HPP_
 
 
 
#include <vector>
 
#include <memory>
 
#include <type_traits>
 
#include <iostream>
 
 
 
template <typename T> class Heap;
 
 
 
/** A Heap that holds pointers to elements of type T.
 
 * T must inherit from HeapObject.
 
 * Destructing a T removes it from its heap.
 
 */
 
template <typename T>
 
class Heap {
 
public:
 
    /** Base class for objects that can be inserted in a heap. */
 
    class HeapObject {
 
    public:
 
        HeapObject();
 
        ~HeapObject();
 
        HeapObject(const HeapObject&) = delete;
 
        HeapObject& operator=(const HeapObject&) = delete;
 
 
 
        /** Get priority of element in heap. */
 
        int getPrio() const;
 
 
 
        /** Changes the priority. Does nothing if element not in a heap. */
 
        void newPrio(int prio);
 
    private:
 
        friend class Heap;
 
 
 
        Heap<T>* owner;
 
        int prio;
 
        int heapIdx;
 
    };
 
 
 
    /** Constructor. Creates an empty heap. */
 
    Heap();
 
 
 
    /** Destructor. Removes all elements from heap. */
 
    ~Heap();
 
 
 
    Heap(const Heap& other) = delete;
 
    Heap& operator=(const Heap& other) = delete;
 
 
 
    /** Insert an element in the heap. */
 
    void insert(const std::shared_ptr<T>& e, int prio);
 
 
 
    /** Remove an element from the heap. Does nothing if e not in heap. */
 
    void remove(const std::shared_ptr<T>& e);
 
    void remove(T* e);
 
 
 
    /** Change the priority of an element in the heap. */
 
    void newPrio(const std::shared_ptr<T>& e, int prio);
 
    void newPrio(T* e, int prio);
 
 
 
    /** Get the element with the highest priority. The element is not removed.
 
     * Returns null if heap is empty. */
 
    std::shared_ptr<T> front() const;
 
 
 
    /** Return true if heap is empty. */
 
    bool empty() const;
 
 
 
    /** Return number of elements in the heap. */
 
    int size() const;
 
 
 
    /** For debugging. */
 
    void print(std::ostream& os) const;
 
 
 
private:
 
    /** Swap two elements in the heap vector and update heapIdx. */
 
    void swapElems(int idx1, int idx2);
 
 
 
    /** Calls upHeap() or downHeap() as needed to restore heap condition. */
 
    void fixHeap(int idx);
 
 
 
    /** Moves an element up until the heap condition is satisfied.*/
 
    void upHeap(int idx);
 
 
 
    /** Moves an element down until the heap condition is satisfied. */
 
    void downHeap(int idx);
 
 
 
    /** Vector of heap elements. */
 
    std::vector<std::shared_ptr<T>> heap;
 
};
 
 
 
template <typename T> inline Heap<T>::Heap() {
 
    static_assert(std::is_base_of<HeapObject,T>::value, "T must inherit HeapObject");
 
}
 
 
 
template <typename T> inline Heap<T>::~Heap() {
 
    for (int i = (int)heap.size() - 1; i >= 0; i--)
 
        remove(heap[i]);
 
}
 
 
 
template <typename T> inline void Heap<T>::insert(const std::shared_ptr<T>& e, int prio) {
 
    assert(!e->owner);
 
    e->owner = this;
 
    e->prio = prio;
 
    int idx = (int)heap.size();
 
    e->heapIdx = idx;
 
    heap.push_back(e);
 
    upHeap(idx);
 
}
 
 
 
template <typename T> inline void Heap<T>::remove(const std::shared_ptr<T>& e) {
 
    remove(e.get());
 
}
 
 
 
template <typename T> inline void Heap<T>::remove(T* e) {
 
    if (!e->owner)
 
        return;
 
    int idx = e->heapIdx;
 
    int last = (int)heap.size() - 1;
 
    if (idx < last)
 
        swapElems(e->heapIdx, last);
 
    e->owner = nullptr;
 
    e->heapIdx = -1;
 
    heap.pop_back();
 
    if (idx < last)
 
        fixHeap(idx);
 
}
 
 
 
template <typename T> inline void Heap<T>::newPrio(const std::shared_ptr<T>& e, int prio) {
 
    newPrio(e.get(), prio);
 
}
 
 
 
template <typename T> inline void Heap<T>::newPrio(T* e, int prio) {
 
    e->prio = prio;
 
    fixHeap(e->heapIdx);
 
}
 
 
 
template <typename T> inline std::shared_ptr<T> Heap<T>::front() const {
 
    if (heap.empty())
 
        return nullptr;
 
    return heap[0];
 
}
 
 
 
template <typename T> bool Heap<T>::empty() const {
 
    return heap.empty();
 
}
 
 
 
template <typename T> int Heap<T>::size() const {
 
    return (int)heap.size();
 
}
 
 
 
template <typename T> inline void Heap<T>::swapElems(int idx1, int idx2) {
 
    std::swap(heap[idx1]->heapIdx, heap[idx2]->heapIdx);
 
    heap[idx1].swap(heap[idx2]);
 
}
 
 
 
template <typename T> inline void Heap<T>::fixHeap(int idx) {
 
    int parent = (idx - 1) / 2;
 
    if ((idx > 0) && (heap[parent]->prio < heap[idx]->prio)) {
 
        swapElems(idx, parent);
 
        upHeap(parent);
 
    } else {
 
        downHeap(idx);
 
    }
 
}
 
 
 
template <typename T> inline void Heap<T>::upHeap(int idx) {
 
    while (idx > 0) {
 
        int parent = (idx - 1) / 2;
 
        if (heap[parent]->prio >= heap[idx]->prio)
 
            break;
 
        swapElems(idx, parent);
 
        idx = parent;
 
    }
 
}
 
 
 
template <typename T> inline void Heap<T>::downHeap(int idx) {
 
    int hSize = (int)heap.size();
 
    while (true) {
 
        int child = idx * 2 + 1;
 
        if (child >= hSize)
 
            break;
 
        if ((child + 1 < hSize) && (heap[child]->prio < heap[child+1]->prio))
 
            child++;
 
        if (heap[idx]->prio >= heap[child]->prio)
 
            break;
 
        swapElems(idx, child);
 
        idx = child;
 
    }
 
}
 
 
 
template <typename T> inline Heap<T>::HeapObject::HeapObject()
 
    : owner(nullptr), prio(0), heapIdx(-1) {
 
}
 
 
 
template <typename T> inline Heap<T>::HeapObject::~HeapObject() {
 
    if (owner)
 
        owner->remove(static_cast<T*>(this));
 
}
 
 
 
template <typename T> inline int Heap<T>::HeapObject::getPrio() const {
 
    return prio;
 
}
 
 
 
template <typename T> inline void Heap<T>::HeapObject::newPrio(int prio) {
 
    if (owner)
 
        owner->newPrio(static_cast<T*>(this), prio);
 
}
 
 
 
template <typename T> inline void Heap<T>::print(std::ostream& os) const {
 
    int hSize = heap.size();
 
    for (int i = 0; i < hSize; i++)
 
        os << std::setw(2) << i << ' ';
 
    os << '\n';
 
    for (int i = 0; i < hSize; i++)
 
        os << std::setw(2) << heap[i]->prio << ' ';
 
    os << '\n';
 
}
 
 
 
#endif /* HEAP_HPP_ */