// Copyright 2019-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  OpenGL sink functions.
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @author Moshi Turner <moshiturner@protonmail.com>
 * @ingroup gui
 */

#include "xrt/xrt_frame.h"
#include "util/u_misc.h"
#include "util/u_logging.h"

#include "ogl/ogl_api.h"

#include "gui_common.h"

#include <pthread.h>
#include <limits.h>


/*!
 * An @ref xrt_frame_sink that shows sunk frames in the GUI.
 * @implements xrt_frame_sink
 * @implements xrt_frame_node
 */
struct gui_ogl_sink
{
	struct gui_ogl_texture tex;

	struct xrt_frame_sink sink;
	struct xrt_frame_node node;

	struct xrt_frame *frame;

	pthread_mutex_t mutex;

	bool running;
};

static void
push_frame(struct xrt_frame_sink *xs, struct xrt_frame *xf)
{
	struct gui_ogl_sink *s = container_of(xs, struct gui_ogl_sink, sink);

	// The fields are protected.
	pthread_mutex_lock(&s->mutex);

	// If we are in the process of shutting down, don't take the reference.
	if (s->running) {
		xrt_frame_reference(&s->frame, xf);
	}

	// Done
	pthread_mutex_unlock(&s->mutex);
}

static void
break_apart(struct xrt_frame_node *node)
{
	struct gui_ogl_sink *s = container_of(node, struct gui_ogl_sink, node);

	// Stop receiving any more reference.
	pthread_mutex_lock(&s->mutex);
	s->running = false;
	pthread_mutex_unlock(&s->mutex);

	// Release any frame waiting for upload.
	xrt_frame_reference(&s->frame, NULL);
}

static void
destroy(struct xrt_frame_node *node)
{
	struct gui_ogl_sink *s = container_of(node, struct gui_ogl_sink, node);

	glDeleteTextures(1, &s->tex.id);

	pthread_mutex_destroy(&s->mutex);

	free(s);
}

static void
update_r8g8b8x8(struct gui_ogl_sink *s, GLint w, GLint h, GLint stride, uint8_t *data)
{
	glBindTexture(GL_TEXTURE_2D, s->tex.id);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / 4);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
	glBindTexture(GL_TEXTURE_2D, 0);
}

static void
update_r8g8b8(struct gui_ogl_sink *s, GLint w, GLint h, GLint stride, uint8_t *data)
{
	glBindTexture(GL_TEXTURE_2D, s->tex.id);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / 3);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
	glBindTexture(GL_TEXTURE_2D, 0);
}

static void
update_l8(struct gui_ogl_sink *s, GLint w, GLint h, GLint stride, uint8_t *data)
{
	glBindTexture(GL_TEXTURE_2D, s->tex.id);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
	GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
	glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
	glBindTexture(GL_TEXTURE_2D, 0);
}

void
gui_ogl_sink_update(struct gui_ogl_texture *tex)
{
	struct gui_ogl_sink *s = container_of(tex, struct gui_ogl_sink, tex);
	(void)s;

	pthread_mutex_lock(&s->mutex);

	struct xrt_frame *frame = NULL;

	// Only take the frame if we are running.
	if (s->running) {
		// Take the frame no need to adjust reference.
		frame = s->frame;
		s->frame = NULL;
	}

	pthread_mutex_unlock(&s->mutex);

	if (frame == NULL) {
		return;
	}

	/*
	 * The frame properties are immutable while it is alive,
	 * so no need to take any locks while reading them.
	 */

	// Too large of stride for GLint.
	if (frame->stride > INT_MAX) {
		U_LOG_E("Stride unreasonably large!");
		xrt_frame_reference(&frame, NULL);
		return;
	}


	GLint w = frame->width;
	GLint h = frame->height;
	GLint stride = (GLint)frame->stride;
	uint8_t *data = frame->data;

	if (tex->w != (uint32_t)w || tex->h != (uint32_t)h) {
		tex->w = w;
		tex->h = h;

		// Automatically set the half scaling.
		if (tex->w >= 1024 || tex->h >= 1024) {
			tex->half = true;
		}
	}

	tex->seq = frame->source_sequence;

	switch (frame->format) {
	case XRT_FORMAT_R8G8B8: update_r8g8b8(s, w, h, stride, data); break;
	case XRT_FORMAT_R8G8B8A8:
	case XRT_FORMAT_R8G8B8X8: update_r8g8b8x8(s, w, h, stride, data); break;
	case XRT_FORMAT_L8: update_l8(s, w, h, stride, data); break;
	default: break;
	}

	xrt_frame_reference(&frame, NULL);
}

struct gui_ogl_texture *
gui_ogl_sink_create(const char *name, struct xrt_frame_context *xfctx, struct xrt_frame_sink **out_sink)
{
	struct gui_ogl_sink *s = U_TYPED_CALLOC(struct gui_ogl_sink);
	int ret = 0;

	s->sink.push_frame = push_frame;
	s->node.break_apart = break_apart;
	s->node.destroy = destroy;
	s->tex.name = name;
	s->tex.w = 256;
	s->tex.h = 256;
	s->running = true;

	ret = pthread_mutex_init(&s->mutex, NULL);
	if (ret != 0) {
		free(s);
		return NULL;
	}

	// Temporary texture
	glGenTextures(1, &s->tex.id);
	glBindTexture(GL_TEXTURE_2D, s->tex.id);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	GLint w = 1;
	GLint h = 1;
	struct xrt_colour_rgb_u8 pink = {255, 0, 255};

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, &pink.r);

	glBindTexture(GL_TEXTURE_2D, 0);

	xrt_frame_context_add(xfctx, &s->node);

	*out_sink = &s->sink;

	return &s->tex;
}
