/*-
 * redshift-ng - Automatically adjust display colour temperature according the Sun
 * 
 * Copyright (c) 2009-2018        Jon Lund Steffensen <jonlst@gmail.com>
 * Copyright (c) 2014-2016, 2025  Mattias Andrée <m@maandree.se>
 * 
 * redshift-ng is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * redshift-ng is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with redshift-ng.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "common.h"


/**
 * Names of periods supplied to scripts
 */
static const char *period_names[] = {
	[PERIOD_NONE]       = "none",
	[PERIOD_DAYTIME]    = "daytime",
	[PERIOD_NIGHT]      = "night",
	[PERIOD_TRANSITION] = "transition"
};


/**
 * Path name of the hook directory, `NULL` if not found
 */
static char *dirpath = NULL;

/**
 * The allocation size of `dirpath`
 */
static size_t dirpathsize;

/**
 * The length of the string in `dirpath`
 */
static size_t dirpathlen;


/**
 * Paths, in order of priority, to test when looking for
 * the hooks directory for redshift
 */
static const struct env_path paths[] = {
	{0, "XDG_CONFIG_HOME", "/redshift-ng/hooks"},
	{0, "XDG_CONFIG_HOME", "/redshift/hooks"},
#if defined(WINDOWS)
	{0, "localappdata", "/redshift-ng/hooks"},
	{0, "localappdata", "/redshift/hooks"},
#endif
	{0, "HOME", "/.config/redshift-ng/hooks"},
	{0, "HOME", "/.config/redshift/hooks"},
	{0, NULL, "/.config/redshift-ng/hooks"},
	{0, NULL, "/.config/redshift/hooks"},
	{1, "XDG_CONFIG_DIRS", "/redshift-ng/hooks"},
	{1, "XDG_CONFIG_DIRS", "/redshift/hooks"},
#if !defined(WINDOWS)
	{0, "", "/etc/redshift-ng/hooks"},
	{0, "", "/etc/redshift/hooks"}
#endif
};


/**
 * Deallocates `dirpath`
 */
static void
cleanup(void)
{
	free(dirpath);
	dirpath = NULL;
}


/**
 * Search for and open the hook directory for reading
 * 
 * @param   path_out     Output parameter for the hook directroy path
 * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`;
 *                       will be set to `NULL`; shall be free(3)d by the caller
 * @return               `DIR` object for the reading the directory; `NULL` if not found
 */
static DIR *
open_hooks_dir(const char **path_out, char **pathbuf_out)
{
	DIR *dir = NULL;
	size_t i;

	*path_out = NULL;
	*pathbuf_out = NULL;

	for (i = 0; !dir && i < ELEMSOF(paths); i++)
		dir = try_path_opendir(&paths[i], path_out, pathbuf_out);
	if (dir)
		weprintf(_("Found hook directory `%s'."), *path_out);
	else
		weprintf(_("No hook directory found."));

	return dir;
}


/**
 * Run hooks
 *
 * @param  argv  `NULL` terminated list of command line arguments
 *               for the hooks; must contain an unused initial slot,
 *               which the function will use to provide the zeroth
 *               argument
 */
static void
run_hooks(const char *argv[])
{
	static int looked_up_dir = 0;

	DIR *dir;
	struct dirent *f;
	size_t required;
	const char *dirpath_static;

	if (!looked_up_dir) {
		looked_up_dir = 1;
		dir = open_hooks_dir(&dirpath_static, &dirpath);
		if (!dir)
			return;
		if (!dirpath)
			dirpath = estrdup(dirpath_static);
		dirpathsize = dirpathlen = strlen(dirpath);
		atexit(&cleanup);
	} else if (dirpath) {
		dir = opendir(dirpath);
		if (!dir) {
			weprintf("opendir %s:", dirpath);
			cleanup();
			return;
		}
	} else {
		return;
	}

	while ((errno = 0, f = readdir(dir))) {
		if (f->d_name[0] == '.' || !f->d_name[0] || strchr(f->d_name, '\0')[-1] == '~')
			continue;

		required = dirpathlen + sizeof("/") + strlen(f->d_name);
		if (required > dirpathsize)
			dirpath = erealloc(dirpath, dirpathsize = required);

#ifdef WINDOWS
		/* TODO [Windows] hooks are not support on Windows */
#else
		switch (fork()) {
		case -1:
			weprintf("fork:");
			break;

		case 0:
			if (dup2(STDOUT_FILENO, STDERR_FILENO) != STDERR_FILENO)
				_eprintf("dup2 <stdout> <stderr>:");
			stpcpy(stpcpy(&dirpath[dirpathlen], "/"), f->d_name);
			argv[0] = dirpath;
			execv(dirpath, (const void *)argv);
			if (errno != EACCES)
				weprintf("execv %s:", dirpath);
			_exit(1);

		default:
			/* SIGCHLD is ignored */
			break;
		}
#endif
	}

	if (errno)
		weprintf("readdir %s:", dirpath);

	closedir(dir);
}


void
run_period_change_hooks(enum period prev_period, enum period period)
{
	const char *argv[] = {NULL, "period-changed", period_names[prev_period], period_names[period], NULL};
	run_hooks(argv);
}