/*-
 * 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"


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


/**
 * Open the configuration file for reading
 * 
 * @param   path         The path to the configuration file, or `NULL` if the
 *                       application should look for it in the default paths
 * @param   path_out     Output parameter for the configuration file path
 * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`;
 *                       will be set to `NULL` unless `path` is `NULL`; shall be
 *                       free(3)d by the caller
 * @return               `FILE` object for the reading the file; `NULL` if not
 *                       found and `path` is `NULL`
 */
static FILE *
open_config_file(const char *path, const char **path_out, char **pathbuf_out)
{
	FILE *f = NULL;
	size_t i;

	*path_out = path;
	*pathbuf_out = NULL;

	if (!path) {
		for (i = 0; !f && i < ELEMSOF(paths); i++)
			f = try_path_fopen(&paths[i], path_out, pathbuf_out);
		if (f)
			weprintf(_("Found configuration file `%s'."), *path_out);
		else
			weprintf(_("No configuration file found."));
	} else {
		f = fopen(path, "r");
		if (!f)
			eprintf("fopen %s \"r\":", path);
	}

	return f;
}


void
config_ini_init(struct config_ini_state *state, const char *path)
{
	struct config_ini_section *section = NULL;
	struct config_ini_setting *setting;
	char *line = NULL, *s, *p, *value, *end, *pathbuf;
	char *next_line = NULL, *name;
	size_t size = 0;
	ssize_t len = 0; /* initialised to silence false warning from clang */
	FILE *f;

	state->sections = NULL;

	f = open_config_file(path, &path, &pathbuf);
	if (!f)
		return;

#ifndef WINDOWS
again:
#endif
	while ((s = next_line) || (len = getline(&line, &size, f)) >= 0) {
		if (!s && (s = line, strlen(s) != (size_t)len))
			eprintf(_("Config file contains NUL byte."));

		s = ltrim(s);
		next_line = NULL;
		end = &s[strcspn(s, "\r\n")];
		if (*end) {
			p = end;
			do {
				*p++ = '\0';
			} while (*p == '\r' || *p == '\n');
			if (*p)
				next_line = p;
		}
		rtrim(s, end);

		switch (*s) {
		case ';': /* comment line */
		case '#': /* comment line */
		case '\0': /* blank line */
			break;

		case '[': /* "[%s]", section */
			end = strchr(name = &s[1], ']');
			if (!end || end[1] || end == name)
				eprintf(_("Malformed section header in config file."));
			*end = '\0';
			section = emalloc(sizeof(*section));
			section->name = estrdup(name);
			section->settings = NULL;
			section->next = state->sections;
			state->sections = section;
			break;

		default: /* "%s = %s", name, value */
			value = p = strchr(name = s, '=');
			if (!value || value == name)
				eprintf(_("Malformed assignment in config file."));
			*value++ = '\0';
			if (!section)
				eprintf(_("Assignment outside section in config file."));
			setting = emalloc(sizeof(*setting));
			setting->name = estrdup(rtrim(name, p));
			setting->value = estrdup(rtrim(ltrim(value), NULL));
			setting->next = section->settings;
			section->settings = setting;
			break;
		}
	}
	if (ferror(f)) {
#ifndef WINDOWS
		if (errno == EINTR) {
			clearerr(f);
			goto again;
		}
#endif
		eprintf("getline %s:", path);
	}

	free(pathbuf);
	free(line);
	fclose(f);
}


void
config_ini_free(struct config_ini_state *state)
{
	struct config_ini_section *section, *section_next;
	struct config_ini_setting *setting, *setting_next;
	for (section = state->sections; section; section = section_next) {
		section_next = section->next;
		for (setting = section->settings; setting; setting = setting_next) {
			setting_next = setting->next;
			free(setting->name);
			free(setting->value);
			free(setting);
		}
		free(section->name);
		free(section);
	}
}


struct config_ini_section *
config_ini_get_section(struct config_ini_state *state, const char *name)
{
	/* TODO deal with multiple section definitions */
	struct config_ini_section *section;
	for (section = state->sections; section; section = section->next)
		if (!strcasecmp(section->name, name))
			return section;
	return NULL;
}