#include "GLVertexFormat.h"
#include "GLBufferObject.h"
#include "../../IGfxCapabilities.h"
#include "../../../ServiceLocator.h"
#include "../../../../Main.h"

namespace nCine::RHI::GL
{
	GLVertexFormat::Attribute::Attribute()
		: vbo_(nullptr), pointer_(nullptr), index_(0), size_(-1), type_(GL_FLOAT), stride_(0), baseOffset_(0), enabled_(false), normalized_(GL_FALSE)
	{
	}

	GLVertexFormat::GLVertexFormat()
		: ibo_(nullptr), attributes_(MaxAttributes)
	{
	}

	bool GLVertexFormat::Attribute::operator==(const Attribute& other) const
	{
		return ((other.enabled_ == false && enabled_ == false) ||
				((other.enabled_ == true && enabled_ == true) &&
					(other.vbo_ && vbo_ && other.vbo_->GetGLHandle() == vbo_->GetGLHandle()) &&
					other.index_ == index_ &&
					other.size_ == size_ &&
					other.type_ == type_ &&
					other.normalized_ == normalized_ &&
					other.stride_ == stride_ &&
					other.pointer_ == pointer_ &&
					other.baseOffset_ == baseOffset_));
	}

	bool GLVertexFormat::Attribute::operator!=(const Attribute& other) const
	{
		return !operator==(other);
	}

	void GLVertexFormat::Attribute::Init(std::uint32_t index, GLint size, GLenum type)
	{
		enabled_ = true;
		vbo_ = nullptr;
		index_ = index;
		size_ = size;
		type_ = type;
		normalized_ = GL_FALSE;
		stride_ = 0;
		pointer_ = nullptr;
		baseOffset_ = 0;
	}

	void GLVertexFormat::Attribute::SetVboParameters(GLsizei stride, const GLvoid* pointer)
	{
#if !defined(DEATH_TARGET_EMSCRIPTEN) && !(defined(DEATH_TARGET_APPLE) && defined(DEATH_TARGET_ARM)) && (!defined(WITH_OPENGLES) || (defined(WITH_OPENGLES) && GL_ES_VERSION_3_1))
		static const std::int32_t MaxVertexAttribStride = theServiceLocator().GetGfxCapabilities().GetValue(IGfxCapabilities::IntValues::MAX_VERTEX_ATTRIB_STRIDE);

		if (stride > MaxVertexAttribStride) {
			stride_ = MaxVertexAttribStride;
			LOGW("Vertex attribute stride ({}) is bigger than the maximum value supported ({})", stride, MaxVertexAttribStride);
		} else
#endif
		{
			stride_ = stride;
		}

		pointer_ = pointer;
	}

	void GLVertexFormat::Define()
	{
		for (std::uint32_t i = 0; i < MaxAttributes; i++) {
			if (attributes_[i].enabled_) {
				attributes_[i].vbo_->Bind();
				glEnableVertexAttribArray(attributes_[i].index_);

#if (defined(WITH_OPENGLES) && !GL_ES_VERSION_3_2) || defined(DEATH_TARGET_EMSCRIPTEN)
				const GLubyte* initialPointer = reinterpret_cast<const GLubyte*>(attributes_[i].pointer_);
				const GLvoid* pointer = reinterpret_cast<const GLvoid*>(initialPointer + attributes_[i].baseOffset_);
#else
				const GLvoid* pointer = attributes_[i].pointer_;
#endif

#if defined(RHI_GL_PROFILE_ES2)
				// ES2 has no integer vertex attributes (glVertexAttribIPointer is ES 3.0) and no GL_(UNSIGNED_)INT
				// attribute data type at all; the ESSL 100 emitter already declares every attribute float-typed and
				// the only integer-typed streams belong to the batched-mesh programs this profile never compiles
				glVertexAttribPointer(attributes_[i].index_, attributes_[i].size_, attributes_[i].type_, attributes_[i].normalized_, attributes_[i].stride_, pointer);
#else
				switch (attributes_[i].type_) {
					case GL_BYTE:
					case GL_UNSIGNED_BYTE:
					case GL_SHORT:
					case GL_UNSIGNED_SHORT:
					case GL_INT:
					case GL_UNSIGNED_INT:
						if (attributes_[i].normalized_) {
							glVertexAttribPointer(attributes_[i].index_, attributes_[i].size_, attributes_[i].type_, GL_TRUE, attributes_[i].stride_, pointer);
						} else {
							glVertexAttribIPointer(attributes_[i].index_, attributes_[i].size_, attributes_[i].type_, attributes_[i].stride_, pointer);
						}
						break;
					default:
						glVertexAttribPointer(attributes_[i].index_, attributes_[i].size_, attributes_[i].type_, attributes_[i].normalized_, attributes_[i].stride_, pointer);
						break;
				}
#endif
			}
		}

		if (ibo_) {
			ibo_->Bind();
		}
	}

	void GLVertexFormat::Reset()
	{
		for (std::uint32_t i = 0; i < MaxAttributes; i++) {
			attributes_[i].enabled_ = false;
		}
		ibo_ = nullptr;
	}

	namespace
	{
		inline std::uint64_t hashCombine(std::uint64_t h, std::uint64_t v)
		{
			// One splitmix64 step over the previous hash and the new value
			h += 0x9E3779B97F4A7C15ull + v;
			h = (h ^ (h >> 30)) * 0xBF58476D1CE4E5B9ull;
			h = (h ^ (h >> 27)) * 0x94D049BB133111EBull;
			return h ^ (h >> 31);
		}
	}

	std::uint64_t GLVertexFormat::CalculateFingerprint() const
	{
		// Mirrors operator==(): only enabled attributes and the IBO identity contribute
		std::uint64_t hash = std::uint64_t(std::uintptr_t(ibo_));
		for (std::uint32_t i = 0; i < MaxAttributes; i++) {
			const Attribute& attribute = attributes_[i];
			if (!attribute.enabled_) {
				continue;
			}
			hash = hashCombine(hash, (std::uint64_t(attribute.index_) << 32) | (attribute.vbo_ != nullptr ? attribute.vbo_->GetGLHandle() : 0));
			hash = hashCombine(hash, (std::uint64_t(std::uint32_t(attribute.size_)) << 32) | attribute.type_);
			hash = hashCombine(hash, (std::uint64_t(std::uint32_t(attribute.stride_)) << 33) | (std::uint64_t(attribute.baseOffset_) << 1) | (attribute.normalized_ ? 1u : 0u));
			hash = hashCombine(hash, std::uint64_t(std::uintptr_t(attribute.pointer_)));
		}
		return hash;
	}

	bool GLVertexFormat::operator==(const GLVertexFormat& other) const
	{
		bool areEqual = (other.ibo_ == ibo_ && other.attributes_.size() == attributes_.size());

		// If indices are the same then check attributes too
		if (areEqual) {
			for (std::uint32_t i = 0; i < attributes_.size(); i++) {
				if (other.attributes_[i] != attributes_[i]) {
					areEqual = false;
					break;
				}
			}
		}

		return areEqual;
	}

	bool GLVertexFormat::operator!=(const GLVertexFormat& other) const
	{
		return !operator==(other);
	}
}
