/*
 * gphoto.c - thin dispatcher for GNU Photo
 *
 * Launches either the gphoto2 command-line tool or the gnome-photos
 * graphical browser, depending on how it is invoked:
 *
 * gphoto --cli [gphoto2-args...]   -> exec gphoto2 gphoto --gui
 * [gnome-photos-args] -> exec gnome-photos gphoto (no args)
 * -> pick a sensible default: GUI if a display is available and gnome-photos
 * is installed, otherwise fall back to the CLI.
 *
 * This program intentionally does NOT link against GTK, GLib, or libgphoto2.
 * It only knows how to find and exec other binaries, so it carries no extra
 * dependencies of its own. Users who don't want a GNOME dependency can
 * simply not install this wrapper (or not install gnome-photos) and use
 * gphoto2 directly.
 *
 * Compile: gcc -o gphoto gphoto.c
 *
 * Author: (template contributed for the GNU Photo project) License:
 * GPLv3-or-later, to match the rest of GNU Photo / gphoto2.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

static const char *CLI_BIN = "gphoto2";
static const char *GUI_BIN = "gnome-photos";

/* Print usage/help text to stderr and exit with the given status. */
static void
usage(const char *progname, int status)
{
	FILE	       *out = (status == 0) ? stdout : stderr;
	fprintf(out,
		"Usage: %s [--cli | --gui] [ARGS...]\n"
		"\n"
	   "gphoto is a small dispatcher that launches either the gphoto2\n"
		"command-line tool or the gnome-photos graphical browser.\n"
		"\n"
		"  --cli [ARGS...]   Run 'gphoto2 [ARGS...]'\n"
		"  --gui [ARGS...]   Run 'gnome-photos [ARGS...]'\n"
		"  --help            Show this help and exit\n"
		"\n"
	"With no options, gphoto picks a sensible default: it prefers the\n"
	     "GUI (gnome-photos) when a graphical session is detected and\n"
	    "gnome-photos is installed, and falls back to the gphoto2 CLI\n"
		"otherwise.\n",
		progname);
	exit(status);
}

/* Return 1 if 'name' is found on PATH and executable, else 0. */
static int
binary_exists(const char *name)
{
	const char     *path_env = getenv("PATH");
	if (!path_env)
		return 0;

	char	       *path_copy = strdup(path_env);
	if (!path_copy)
		return 0;

	int		found = 0;
	char	       *saveptr = NULL;
	char	       *dir = strtok_r(path_copy, ":", &saveptr);

	while (dir != NULL) {
		char		candidate[4096];
		snprintf(candidate, sizeof(candidate), "%s/%s", dir, name);
		if (access(candidate, X_OK) == 0) {
			found = 1;
			break;
		}
		dir = strtok_r(NULL, ":", &saveptr);
	}

	free(path_copy);
	return found;
}

/* Return 1 if we appear to be in a graphical session. */
static int
have_display(void)
{
	const char     *x11 = getenv("DISPLAY");
	const char     *wayland = getenv("WAYLAND_DISPLAY");
	return (x11 && x11[0] != '\0') || (wayland && wayland[0] != '\0');
}

/* exec() the given binary with the given argv, or report an error. */
static void
run(const char *binary, char *const argv[])
{
	execvp(binary, argv);

	/* execvp only returns on failure. */
	fprintf(stderr, "gphoto: could not run '%s': %s\n",
		binary, strerror(errno));

	if (errno == ENOENT) {
		fprintf(stderr,
		   "gphoto: is '%s' installed and on your PATH?\n", binary);
	}

	exit(127);
}

/*
 * Build a new argv array for the target program: [binary, extra..., NULL].
 * 'extra' is argv[offset..argc) from the original command line.
 */
static char   **
build_argv(const char *binary, int argc, char **argv, int offset)
{
	int		extra_count = argc - offset;
	/* +1 for binary name in slot 0, +1 for NULL terminator */
	char	      **new_argv = calloc((size_t) (extra_count + 2), sizeof(char *));
	if (!new_argv) {
		fprintf(stderr, "gphoto: out of memory\n");
		exit(1);
	}

	new_argv[0] = (char *)binary;
	for (int i = 0; i < extra_count; i++) {
		new_argv[i + 1] = argv[offset + i];
	}
	new_argv[extra_count + 1] = NULL;

	return new_argv;
}

int
main(int argc, char **argv)
{
	if (argc >= 2 && (strcmp(argv[1], "--help") == 0 ||
			  strcmp(argv[1], "-h") == 0)) {
		usage(argv[0], 0);
	}

	if (argc >= 2 && strcmp(argv[1], "--cli") == 0) {
		char	      **cli_argv = build_argv(CLI_BIN, argc, argv, 2);
		run(CLI_BIN, cli_argv);
	}

	if (argc >= 2 && strcmp(argv[1], "--gui") == 0) {
		char	      **gui_argv = build_argv(GUI_BIN, argc, argv, 2);
		run(GUI_BIN, gui_argv);
	}

	if (argc >= 2 && argv[1][0] == '-') {
		/* Unrecognized option: don't silently swallow it. */
		fprintf(stderr, "gphoto: unrecognized option '%s'\n\n", argv[1]);
		usage(argv[0], 1);
	}

	/*
	 * No --cli/--gui given. Any remaining bare arguments (e.g. a camera
	 * port spec) are passed straight through to whichever program we
	 * choose, exactly like a --cli/--gui invocation would.
	 */
	if (have_display() && binary_exists(GUI_BIN)) {
		char	      **gui_argv = build_argv(GUI_BIN, argc, argv, 1);
		run(GUI_BIN, gui_argv);
	} else {
		if (!binary_exists(CLI_BIN)) {
			fprintf(stderr,
			  "gphoto: neither '%s' nor '%s' could be found on "
				"your PATH.\n", GUI_BIN, CLI_BIN);
			exit(127);
		}
		char	      **cli_argv = build_argv(CLI_BIN, argc, argv, 1);
		run(CLI_BIN, cli_argv);
	}

	/* Unreachable */
	return 0;
}
