// SPDX-License-Identifier: GPL-2.0-only

#include "output-virtual.h"
#include <stdlib.h>
#include <wlr/backend/headless.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include "common/string-helpers.h"
#include "labwc.h"
#include "output.h"

static struct wlr_output *fallback_output = NULL;

void
output_virtual_add(const char *output_name,
		struct wlr_output **store_wlr_output)
{
	if (output_name) {
		/* Prevent creating outputs with the same name */
		struct output *output;
		wl_list_for_each(output, &server.outputs, link) {
			if (wlr_output_is_headless(output->wlr_output) &&
					!strcmp(output->wlr_output->name, output_name)) {
				wlr_log(WLR_DEBUG,
					"refusing to create virtual output with duplicate name");
				return;
			}
		}
	}

	/*
	 * The headless backend will always emit the new output signal (and
	 * thus call our handler) before `wlr_headless_add_output()` returns.
	 *
	 * This makes it impossible to
	 * - modify the output before it gets enabled in the handler
	 * - use a pointer of the new wlr_output within the handler
	 *
	 * So we temporarily disconnect the handler when creating the output
	 * and then call the handler manually.
	 *
	 * This is important because some operations like `wlr_output_set_name()`
	 * can only be done before the output has been enabled.
	 *
	 * If we add a virtual output before the headless backend has been started
	 * we may end up calling the new output handler twice, one time manually
	 * and one time by the headless backend when it starts up and sends the
	 * signal for all its configured outputs. Rather than keeping a global
	 * server.headless.started state around that we could check here we just
	 * ignore duplicated new output calls in handle_new_output().
	 */
	wl_list_remove(&server.new_output.link);

	struct wlr_output *wlr_output = wlr_headless_add_output(
		server.headless.backend, 1920, 1080);

	if (!wlr_output) {
		wlr_log(WLR_ERROR, "Failed to create virtual output %s",
			output_name ? output_name : "");
		goto restore_handler;
	}

	if (output_name) {
		wlr_output_set_name(wlr_output, output_name);
	}
	if (store_wlr_output) {
		/* Ensures that we can use the new wlr_output pointer within handle_new_output() */
		*store_wlr_output = wlr_output;
	}

	/* Notify about the new output manually */
	if (server.new_output.notify) {
		server.new_output.notify(&server.new_output, wlr_output);
	}

restore_handler:
	/* And finally restore output notifications */
	wl_signal_add(&server.backend->events.new_output, &server.new_output);
}

void
output_virtual_remove(const char *output_name)
{
	struct output *output;
	wl_list_for_each(output, &server.outputs, link) {
		if (!wlr_output_is_headless(output->wlr_output)
				|| output->wlr_output == fallback_output) {
			continue;
		}

		if (output_name) {
			/*
			 * Given virtual output name, find and
			 * destroy virtual output by that name.
			 */
			if (!strcmp(output->wlr_output->name, output_name)) {
				wlr_output_destroy(output->wlr_output);
				return;
			}
		} else {
			/*
			 * When virtual output name was not supplied by user,
			 * simply destroy the first virtual output found.
			 */
			wlr_output_destroy(output->wlr_output);
			return;
		}
	}
}

void
output_virtual_update_fallback(void)
{
	struct wl_list *layout_outputs = &server.output_layout->outputs;
	const char *fallback_output_name = getenv("LABWC_FALLBACK_OUTPUT");

	if (!fallback_output && wl_list_empty(layout_outputs)
			&& !string_null_or_empty(fallback_output_name)) {
		wlr_log(WLR_DEBUG, "adding fallback output %s", fallback_output_name);

		output_virtual_add(fallback_output_name, &fallback_output);
	} else if (fallback_output && (wl_list_length(layout_outputs) > 1
			|| string_null_or_empty(fallback_output_name))) {
		wlr_log(WLR_DEBUG, "destroying fallback output %s",
			fallback_output->name);

		/*
		 * We must reset fallback_output to NULL before destroying it.
		 *
		 * Otherwise we may end up trying to destroy the same wlr_output
		 * twice due to wlr_output_destroy() removing the output from the
		 * layout which in turn causes us to be called again.
		 */
		struct wlr_output *wlr_output = fallback_output;
		fallback_output = NULL;
		wlr_output_destroy(wlr_output);
	}
}
