// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file fennec/containers/priority_queue.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_PRIORITY_QUEUE_H
#define FENNEC_CONTAINERS_PRIORITY_QUEUE_H

#include <fennec/containers/object_pool.h>
#include <fennec/lang/compare.h>
#include <fennec/lang/types.h>
#include <fennec/memory/allocator.h>

// Binary heaps are just kinda busted.
// In-array binary heaps are one of the most efficient data structures for computers
// -> Cache Locality
// -> log(n) runtime
// -> No auxiliary structures or constant runtimes
//
// I tried just about every heap under the sun
// -> strict fibonacci heap, got blown out of the water by std::priority_queue
// -> fibonacci heap, got blown out of the water by std::priority_queue
// -> binomial heap, on-par with std::set, blown out of the water by std::priority_queue
//
// Then I relented and fell back to ye old binary heap
// This implementation roughly matches gcc's std::priority_queue

namespace fennec
{

///
/// \brief a priority queue data structure implemented using a binary heap
/// \details
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ⛔     |
/// | dynamic     |      ✅     |
/// | homogeneous |      ✅     |
/// | distinct    |      ⛔     |
/// | ordered     |      ✅     |
/// | space       | \emph{O(N)} |
/// | linear      |      ✅     |
/// | access      | \emph{O(1)} |
/// | find        |      ⛔     |
/// | insertion   | \emph{O(1)} |
/// | deletion    | \emph{O(1)} |
/// | space       | \emph{O(N)} |
///
/// \tparam ValueT The value type
/// \tparam CompareT The compare type, defaults to `fennec::less`
/// \tparam AllocT The allocator type, defaults to `fennec::allocator`
template<typename ValueT, class CompareT = less<ValueT>, class AllocT = allocator<ValueT>>
struct priority_queue {

// Definitions & Constants =============================================================================================
public:

	/// \name Definitions
	/// @{

	using value_t   = ValueT;                      //!< Alias for the value type
	using compare_t = CompareT;                    //!< Alias for the compare type
	using alloc_t   = allocation<value_t, AllocT>; //!< The underlying allocation type

	/// @}

	static constexpr size_t npos = -1; //!< value representing a null node

private:
	constexpr size_t left(size_t n) const {
		n = n * 2 + 1;
		return n >= _size ? npos : n;
	}

	constexpr size_t right(size_t n) const {
		n = n * 2 + 2;
		return n >= _size ? npos : n;
	}

	constexpr size_t parent(size_t n) const {
		return n == 0 ? npos : (n - 1) / 2;
	}


// Constructors & Destructor ===========================================================================================
public:

	///
	/// \brief default constructor
	/// \details initializes an empty queue
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr priority_queue()
		: _size(0) {
	}

	///
	/// \brief destructor
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr ~priority_queue() {
		while (_size > 0) {
			--_size;
			fennec::destruct(&_table[_size]);
		}
	}


// Properties ==========================================================================================================
public:

	///
	/// \returns the size of the queue
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t size() const {
		return _size;
	}

	///
	/// \returns the capacity of the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t capacity() const {
		return _table.capacity();
	}

	///
	/// \returns \emph{true} if the queue holds no elements
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return size() == 0;
	}


// Access ==============================================================================================================
public:

	///
	/// \returns the value at the front of the queue
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const value_t& front() const {
		return _table[0];
	}


// Modifiers ===========================================================================================================
public:

	///
	/// \brief push a new key into the queue
	/// \param key the key to insert
	///
	/// \par Complexity
	/// \emph{O(\log N)}
	///
	constexpr void push(const value_t& key) {
		this->_insert(key);
	}

	///
	/// \param key the key to insert
	///
	/// \par Complexity
	/// \emph{O(\log N)}
	///
	constexpr void push(value_t&& key) {
		this->_insert(fennec::forward<value_t>(key));
	}

	///
	/// \brief emplace a new key into the queue
	/// \tparam ArgsT the argument types
	/// \param args the argument values
	///
	/// \par Complexity
	/// \emph{O(\log N)}
	///
	template<typename...ArgsT>
	constexpr void emplace(ArgsT&&...args) {
		this->_insert(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief pop the element at the front of the queue
	///
	/// \par Complexity
	/// \emph{O(\log N)}
	///
	constexpr void pop() {
		fennec::swap(_table[0], _table[--_size]);
		fennec::destruct(&_table[_size]);
		_fix_erase(0);
	}


// Private Member Variables ============================================================================================
private:
	compare_t _compare;
	alloc_t   _table;
	size_t    _size;


// Private Helpers =====================================================================================================
private:

	template<typename...ArgsT>
	constexpr void _insert(ArgsT&&...args) {
		if (_size == _table.capacity()) {
			_expand();
		}
		fennec::construct(&_table[_size], fennec::forward<ArgsT>(args)...);
		_fix_insert(_size++);
	}

	constexpr void _expand() {
		_table.reallocate((_table.capacity() + 1) * 2 - 1);
	}

	constexpr size_t _min(size_t a, size_t b) {
		if (a == npos) { return b; }
		if (b == npos) { return a; }
		return _compare(_table[a], _table[b]) ? a : b;
	}

	void _fix_insert(size_t n) {
		size_t p = parent(n);
		while (p != npos && _compare(_table[n], _table[p])) {
			fennec::swap(_table[n], _table[p]);
			n = p;
			p = parent(n);
		}
	}

	void _fix_erase(size_t n) {
		size_t c = _min(left(n), right(n));
		while (n != npos && c != npos && _compare(_table[c], _table[n])) {
			fennec::swap(_table[c], _table[n]);
			n = c;
			c = _min(left(n), right(n));
		}
	}


};

}


#endif // FENNEC_CONTAINERS_PRIORITY_QUEUE_H