#include <cstdarg>

#include "Common/GPU/OpenGL/GLProfiler.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/GPU/OpenGL/GLDebugLog.h"
#include "Common/Log.h"

// For iOS, define function pointer types and variables locally since gl3stub.h
// content is excluded on iOS. These will remain NULL since iOS doesn't support
// GL_EXT_disjoint_timer_query.
#if PPSSPP_PLATFORM(IOS)
typedef void (*PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target);
typedef void (*PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64 *params);
static PFNGLQUERYCOUNTERPROC glQueryCounter = nullptr;
static PFNGLGETQUERYOBJECTUI64VPROC glGetQueryObjectui64v = nullptr;
#endif

// Constants - same values for both EXT and ARB versions
#ifndef GL_TIMESTAMP
#define GL_TIMESTAMP 0x8E28
#endif
#ifndef GL_QUERY_RESULT
#define GL_QUERY_RESULT 0x8866
#endif
#ifndef GL_GPU_DISJOINT_EXT
#define GL_GPU_DISJOINT_EXT 0x8FBB
#endif

// GLCommon.h provides access to GL functions:
// - On GLES: function pointers from gl3stub.h, loaded via eglGetProcAddress
// - On desktop GL: GLEW provides the functions

void GLProfiler::Init() {
	supported_ = false;
	firstFrame_ = true;
	numQueries_ = 0;
	scopes_.clear();
	scopeStack_.clear();

	// NOTE: Getting a lot of hangs on Android from this, I'm disabling it entirely for now.
	// Will later re-enable on desktop.
#if 0

	// Check for extension support
	// Function pointers are declared in gl3stub.h and loaded appropriately per platform
	if (gl_extensions.EXT_disjoint_timer_query) {
		// GLES path - use function pointers loaded with EXT suffix
		if (glQueryCounter && glGetQueryObjectui64v) {
			supported_ = true;
			INFO_LOG(Log::G3D, "GLProfiler: Using GL_EXT_disjoint_timer_query");
		}
	} else if (gl_extensions.ARB_timer_query) {
		// Desktop GL path - use function pointers (no suffix)
		if (glQueryCounter && glGetQueryObjectui64v) {
			supported_ = true;
			INFO_LOG(Log::G3D, "GLProfiler: Using GL_ARB_timer_query");
		}
	}
#endif

	if (supported_) {
		// Pre-allocate query objects
		queries_.resize(MAX_QUERY_COUNT);
		glGenQueries(MAX_QUERY_COUNT, queries_.data());
		CHECK_GL_ERROR_IF_DEBUG();
	}
}

void GLProfiler::Shutdown() {
	if (supported_ && !queries_.empty()) {
		glDeleteQueries((GLsizei)queries_.size(), queries_.data());
		queries_.clear();
	}
	supported_ = false;
	scopes_.clear();
	scopeStack_.clear();
}

void GLProfiler::BeginFrame() {
	if (!supported_) {
		return;
	}

	// Check if profiling is enabled
	if (enabledPtr_ && !*enabledPtr_) {
		scopes_.clear();
		scopeStack_.clear();
		numQueries_ = 0;
		return;
	}

	// Check for disjoint operation (GPU frequency changed) - GLES only
	if (gl_extensions.EXT_disjoint_timer_query) {
		GLint disjoint = 0;
		glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint);
		if (disjoint) {
			// Results are invalid, just clear and start fresh
			WARN_LOG(Log::G3D, "GLProfiler: GPU disjoint detected, timing results discarded");
			scopes_.clear();
			scopeStack_.clear();
			numQueries_ = 0;
			firstFrame_ = true;
			return;
		}
	}

	// Read results from previous frame (guaranteed complete now)
	if (numQueries_ > 0 && !firstFrame_) {
		static const char * const indent[4] = { "", "  ", "    ", "      " };

		if (!scopes_.empty()) {
			DEBUG_LOG(Log::G3D, "OpenGL profiling events this frame:");
		}

		// Log results
		for (auto &scope : scopes_) {
			if (scope.endQueryId == -1) {
				WARN_LOG(Log::G3D, "Unclosed scope: %s", scope.name);
				continue;
			}

			GLuint64 startTime = 0, endTime = 0;
			glGetQueryObjectui64v(queries_[scope.startQueryId], GL_QUERY_RESULT, &startTime);
			glGetQueryObjectui64v(queries_[scope.endQueryId], GL_QUERY_RESULT, &endTime);

			// Times are in nanoseconds, convert to milliseconds
			double milliseconds = (double)(endTime - startTime) / 1000000.0;

			DEBUG_LOG(Log::G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name, milliseconds);
		}
	}

	firstFrame_ = false;
	scopes_.clear();
	scopeStack_.clear();
	numQueries_ = 0;
}

void GLProfiler::Begin(const char *fmt, ...) {
	if (!supported_ || (enabledPtr_ && !*enabledPtr_) || numQueries_ >= MAX_QUERY_COUNT - 1) {
		return;
	}

	GLProfilerScope scope;
	va_list args;
	va_start(args, fmt);
	vsnprintf(scope.name, sizeof(scope.name), fmt, args);
	va_end(args);
	scope.startQueryId = numQueries_;
	scope.endQueryId = -1;
	scope.level = (int)scopeStack_.size();

	scopeStack_.push_back(scopes_.size());
	scopes_.push_back(scope);

	glQueryCounter(queries_[numQueries_], GL_TIMESTAMP);
	numQueries_++;
}

void GLProfiler::End() {
	if (!supported_ || (enabledPtr_ && !*enabledPtr_) || numQueries_ >= MAX_QUERY_COUNT - 1) {
		return;
	}

	if (scopeStack_.empty()) {
		WARN_LOG(Log::G3D, "GLProfiler::End called without matching Begin");
		return;
	}

	size_t scopeId = scopeStack_.back();
	scopeStack_.pop_back();

	GLProfilerScope &scope = scopes_[scopeId];
	scope.endQueryId = numQueries_;

	glQueryCounter(queries_[numQueries_], GL_TIMESTAMP);
	numQueries_++;
}
