// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT

#include <cairo/cairo.h>
#include <drm_fourcc.h>
#include <pango/pangocairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wlr/backend.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <wlr/render/allocator.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>

#include "message.h"
#include "output.h"
#include "pango.h"
#include "server.h"
#include "util.h"

struct msg_buffer {
	struct wlr_buffer base;
	void *data;
	uint32_t format;
	size_t stride;
};

static void
msg_buffer_destroy(struct wlr_buffer *wlr_buffer) {
	struct msg_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
	free(buffer->data);
	free(buffer);
}

static bool
msg_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
                                 __attribute__((unused)) uint32_t flags,
                                 void **data, uint32_t *format,
                                 size_t *stride) {
	struct msg_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
	if(data != NULL) {
		*data = (void *)buffer->data;
	}
	if(format != NULL) {
		*format = buffer->format;
	}
	if(stride != NULL) {
		*stride = buffer->stride;
	}
	return true;
}

static void
msg_buffer_end_data_ptr_access(
    __attribute__((unused)) struct wlr_buffer *wlr_buffer) {
	// This space is intentionally left blank
}

static const struct wlr_buffer_impl msg_buffer_impl = {
    .destroy = msg_buffer_destroy,
    .begin_data_ptr_access = msg_buffer_begin_data_ptr_access,
    .end_data_ptr_access = msg_buffer_end_data_ptr_access,
};

static struct msg_buffer *
msg_buffer_create(uint32_t width, uint32_t height, uint32_t stride) {
	struct msg_buffer *buffer = calloc(1, sizeof(*buffer));
	if(buffer == NULL) {
		return NULL;
	}

	wlr_buffer_init(&buffer->base, &msg_buffer_impl, width, height);
	buffer->format = DRM_FORMAT_ARGB8888;
	buffer->stride = stride;

	buffer->data = malloc(buffer->stride * height);
	if(buffer->data == NULL) {
		free(buffer);
		return NULL;
	}

	return buffer;
}
cairo_subpixel_order_t
to_cairo_subpixel_order(const enum wl_output_subpixel subpixel) {
	switch(subpixel) {
	case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB:
		return CAIRO_SUBPIXEL_ORDER_RGB;
	case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR:
		return CAIRO_SUBPIXEL_ORDER_BGR;
	case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB:
		return CAIRO_SUBPIXEL_ORDER_VRGB;
	case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR:
		return CAIRO_SUBPIXEL_ORDER_VBGR;
	default:
		return CAIRO_SUBPIXEL_ORDER_DEFAULT;
	}
	return CAIRO_SUBPIXEL_ORDER_DEFAULT;
}

struct msg_buffer *
create_message_texture(const char *string, const struct nedm_output *output) {
	const int WIDTH_PADDING = 8;
	const int HEIGHT_PADDING = 2;

	double scale = output->wlr_output->scale;
	int width = 0;
	int height = 0;

	// We must use a non-nil cairo_t for cairo_set_font_options to work.
	// Therefore, we cannot use cairo_create(NULL).
	cairo_surface_t *dummy_surface =
	    cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
	// This occurs when we are fuzzing. In that case, do nothing
	if(dummy_surface == NULL) {
		return NULL;
	}

	cairo_t *c = cairo_create(dummy_surface);

	cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
	cairo_font_options_t *fo = cairo_font_options_create();
	cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
	cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
	cairo_font_options_set_subpixel_order(
	    fo, to_cairo_subpixel_order(output->wlr_output->subpixel));
	cairo_set_font_options(c, fo);
	get_text_size(c, output->server->message_config.font, &width, &height, NULL,
	              scale, "%s", string);
	width += 2 * WIDTH_PADDING;
	height += 2 * HEIGHT_PADDING;
	cairo_surface_destroy(dummy_surface);
	cairo_destroy(c);

	cairo_surface_t *surface =
	    cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
	cairo_t *cairo = cairo_create(surface);
	cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
	cairo_set_font_options(cairo, fo);
	cairo_font_options_destroy(fo);
	float *bg_col = output->server->message_config.bg_color;
	cairo_set_source_rgba(cairo, bg_col[0], bg_col[1], bg_col[2], bg_col[3]);
	cairo_paint(cairo);
	float *fg_col = output->server->message_config.fg_color;
	cairo_set_source_rgba(cairo, fg_col[0], fg_col[1], fg_col[2], fg_col[3]);
	cairo_set_line_width(cairo, 2);
	cairo_rectangle(cairo, 0, 0, width, height);
	cairo_stroke(cairo);
	cairo_move_to(cairo, WIDTH_PADDING, HEIGHT_PADDING);

	pango_printf(cairo, output->server->message_config.font, scale, "%s",
	             string);

	cairo_surface_flush(surface);
	unsigned char *data = cairo_image_surface_get_data(surface);
	int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);

	struct msg_buffer *buf = msg_buffer_create(width, height, stride);
	void *data_ptr;

	if(!wlr_buffer_begin_data_ptr_access(&buf->base,
	                                     WLR_BUFFER_DATA_PTR_ACCESS_WRITE,
	                                     &data_ptr, NULL, NULL)) {
		wlr_log(WLR_ERROR, "Failed to get pointer access to message buffer");
		return NULL;
	}
	memcpy(data_ptr, data, stride * height);
	wlr_buffer_end_data_ptr_access(&buf->base);

	cairo_surface_destroy(surface);
	cairo_destroy(cairo);
	return buf;
}

void
message_set_output(struct nedm_output *output, const char *string,
                   struct wlr_box *box, enum nedm_message_anchor anchor) {
	struct nedm_message *message = malloc(sizeof(struct nedm_message));
	if(!message) {
		wlr_log(WLR_ERROR, "Error allocating message structure");
		free(box);
		return;
	}
	struct msg_buffer *buf = create_message_texture(string, output);
	if(!buf) {
		wlr_log(WLR_ERROR, "Could not create message texture");
		free(box);
		free(message);
		return;
	}
	message->position = box;
	wl_list_insert(&output->messages, &message->link);

	double scale = output->wlr_output->scale;
	int width = buf->base.width / scale;
	int height = buf->base.height / scale;
	message->position->width = width;
	message->position->height = height;
	switch(anchor) {
	case NEDM_MESSAGE_TOP_LEFT:
		message->position->x = 0;
		message->position->y = 0;
		break;
	case NEDM_MESSAGE_TOP_CENTER:
		message->position->x -= width / 2;
		message->position->y = 0;
		break;
	case NEDM_MESSAGE_TOP_RIGHT:
		message->position->x -= width;
		message->position->y = 0;
		break;
	case NEDM_MESSAGE_BOTTOM_LEFT:
		message->position->x = 0;
		message->position->y -= height;
		break;
	case NEDM_MESSAGE_BOTTOM_CENTER:
		message->position->x -= width / 2;
		message->position->y -= height;
		break;
	case NEDM_MESSAGE_BOTTOM_RIGHT:
		message->position->x -= width;
		message->position->y -= height;
		break;
	case NEDM_MESSAGE_CENTER:
		message->position->x -= width / 2;
		message->position->y -= height / 2;
		break;
	default:
		break;
	}

	struct wlr_scene_output *scene_output =
	    wlr_scene_get_scene_output(output->server->scene, output->wlr_output);
	if(scene_output == NULL) {
		return;
	}
	message->message =
	    wlr_scene_buffer_create(&scene_output->scene->tree, &buf->base);
	message->buf = buf;
	wlr_scene_node_raise_to_top(&message->message->node);
	wlr_scene_node_set_enabled(&message->message->node, true);
	wlr_scene_buffer_set_dest_size(message->message, width, height);
	wlr_scene_node_set_position(
	    &message->message->node,
	    message->position->x + output_get_layout_box(output).x,
	    message->position->y + output_get_layout_box(output).y);
}

void
message_printf(struct nedm_output *output, const char *fmt, ...) {
	if(output->destroyed || output->server->message_config.enabled == 0) {
		return;
	}
	va_list ap;
	va_start(ap, fmt);
	char *buffer = malloc_vsprintf_va_list(fmt, ap);
	va_end(ap);
	if(buffer == NULL) {
		wlr_log(WLR_ERROR, "Failed to allocate buffer in message_printf");
		return;
	}

	struct wlr_box *box = malloc(sizeof(struct wlr_box));
	if(box == NULL) {
		wlr_log(WLR_ERROR, "Failed to allocate box in message_printf");
		free(buffer);
		return;
	}
	struct wlr_box output_box = output_get_layout_box(output);
	box->width = 0;
	box->height = 0;
	switch(output->server->message_config.anchor) {
	case NEDM_MESSAGE_TOP_LEFT:
		box->x = 0;
		box->y = 0;
		break;
	case NEDM_MESSAGE_TOP_CENTER:
		box->x = output_box.width / 2;
		box->y = 0;
		break;
	case NEDM_MESSAGE_TOP_RIGHT:
		box->x = output_box.width;
		box->y = 0;
		break;
	case NEDM_MESSAGE_BOTTOM_LEFT:
		box->x = 0;
		box->y = output_box.height;
		break;
	case NEDM_MESSAGE_BOTTOM_CENTER:
		box->x = output_box.width / 2;
		box->y = output_box.height;
		break;
	case NEDM_MESSAGE_BOTTOM_RIGHT:
		box->x = output_box.width;
		box->y = output_box.height;
		break;
	case NEDM_MESSAGE_CENTER:
		box->x = output_box.width / 2;
		box->y = output_box.height / 2;
		break;
	default:
		break;
	}

	message_set_output(output, buffer, box,
	                   output->server->message_config.anchor);
	free(buffer);
	alarm(output->server->message_config.display_time);
}

void
message_printf_pos(struct nedm_output *output, struct wlr_box *position,
                   const enum nedm_message_anchor anchor, const char *fmt, ...) {
	if(output->destroyed || output->server->message_config.enabled == 0) {
		free(position);
		return;
	}
	uint16_t buf_len = 256;
	char *buffer = (char *)malloc(buf_len * sizeof(char));
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buffer, buf_len, fmt, ap);
	va_end(ap);

	message_set_output(output, buffer, position, anchor);
	free(buffer);
	alarm(output->server->message_config.display_time);
}

void
message_clear(struct nedm_output *output) {
	struct nedm_message *message, *tmp;
	wl_list_for_each_safe(message, tmp, &output->messages, link) {
		wl_list_remove(&message->link);
		wlr_scene_node_destroy(&message->message->node);
		free(message->position);
		if(message->buf != NULL) {
			msg_buffer_destroy(&message->buf->base);
		}
		free(message);
	}
}
