/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

#include <mrpt/containers/NonCopiableData.h>
#include <mrpt/core/lock_helper.h>

#include <memory>
#include <mutex>
#include <thread>

namespace mrpt::opengl
{
/** A wrapper for an OpenGL buffer object (eg Vertex Buffer Object or VBO)
 * Refer to docs for glGenBuffers() and glBufferData().
 *
 * \sa FrameBuffer
 * \ingroup mrpt_opengl_grp
 * \note OpenGL Buffer Objects *can* be shared among threads.
 */
class Buffer
{
   public:
	enum class Type : unsigned int
	{
		Vertex = 0x8892,  // GL_ARRAY_BUFFER (Default)
		ElementIndex = 0x8893,	// GL_ELEMENT_ARRAY_BUFFER
		PixelPack = 0x88EB,	 // GL_PIXEL_PACK_BUFFER
		PixelUnpack = 0x88EC  // GL_PIXEL_UNPACK_BUFFER
	};

	enum class Usage : unsigned int
	{
		StreamDraw = 0x88E0,  // GL_STREAM_DRAW
		StreamRead = 0x88E1,  // GL_STREAM_READ
		StreamCopy = 0x88E2,  // GL_STREAM_COPY
		StaticDraw = 0x88E4,  // GL_STATIC_DRAW (Default)
		StaticRead = 0x88E5,  // GL_STATIC_READ
		StaticCopy = 0x88E6,  // GL_STATIC_COPY
		DynamicDraw = 0x88E8,  // GL_DYNAMIC_DRAW
		DynamicRead = 0x88E9,  // GL_DYNAMIC_READ
		DynamicCopy = 0x88EA  // GL_DYNAMIC_COPY
	};

	explicit Buffer(const Type type);
	Buffer() : Buffer(Type::Vertex) {}
	~Buffer() = default;

	Type type() const
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		return m_impl.type;
	}

	Usage usage() const
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		return m_impl.usage;
	}
	void setUsage(const Usage u)
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		m_impl.usage = u;
	}

	/** Calls create() only if the buffer has not been created yet. */
	void createOnce()
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		if (!m_impl.created) m_impl.create();
	}
	bool initialized() const
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		return m_impl.created;
	}

	/** Automatically called upon destructor, no need for the user to call it in
	 * normal situations. */
	void destroy()
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		m_impl.destroy();
	}

	void bind()
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		m_impl.bind();
	}
	void unbind()
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		m_impl.unbind();
	}

	unsigned int bufferId() const
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		return m_impl.buffer_id;
	}

	/** Reserves byteCount bytes in the buffer and copy to it the provided data.
	 * create() and bind() must be called before using this method.
	 */
	void allocate(const void* data, int byteCount)
	{
		auto lck = mrpt::lockHelper(m_implMtx.data);
		m_impl.allocate(data, byteCount);
	}

   private:
	struct RAII_Impl
	{
		RAII_Impl(Buffer::Type t);
		~RAII_Impl();

		Buffer::Type type;
		Buffer::Usage usage = Buffer::Usage::StaticDraw;

		void create();
		void destroy();
		void bind();
		void unbind();
		void allocate(const void* data, int byteCount);

		bool created = false;
		unsigned int buffer_id = 0;
		std::thread::id created_from;
	};
	RAII_Impl m_impl;
	mrpt::containers::NonCopiableData<std::mutex> m_implMtx;
};

// For use in glVertexAttribPointer()
#define BUFFER_OFFSET(offset) (reinterpret_cast<GLvoid*>(offset))

}  // namespace mrpt::opengl
