/* redshift.c -- Main program source
 * This file is part of redshift-ng.
 * 
 * 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/>.
 * 
 * Copyright (c) 2009-2017  Jon Lund Steffensen <jonlst@gmail.com>
 * Copyright (c) 2025       Mattias Andrée <m@maandre.se>
 */
#include "common.h"

/* poll.h is not available on Windows but there is no Windows location provider
 * using polling. On Windows, we just define some stubs to make things compile.
 */
#ifndef WINDOWS
# include <poll.h>
#else
#define POLLIN 0
struct pollfd {
	int fd;
	short events;
	short revents;
};
int poll(struct pollfd *fds, int nfds, int timeout) { abort(); return -1; }
#endif


/* Bounds for parameters. */
#define MIN_LAT   -90.0
#define MAX_LAT    90.0
#define MIN_LON  -180.0
#define MAX_LON   180.0
#define MIN_TEMP   1000
#define MAX_TEMP  25000
#define MIN_BRIGHTNESS  0.1
#define MAX_BRIGHTNESS  1.0
#define MIN_GAMMA   0.1
#define MAX_GAMMA  10.0

/* Duration of sleep between screen updates (milliseconds). */
#define SLEEP_DURATION        5000
#define SLEEP_DURATION_SHORT  100

/* Length of fade in numbers of short sleep durations. */
#define FADE_LENGTH  40


const struct gamma_method *gamma_methods[] = {
#ifdef ENABLE_COOPGAMMA
	&coopgamma_gamma_method,
#endif
#ifdef ENABLE_DRM
	&drm_gamma_method,
#endif
#ifdef ENABLE_RANDR
	&randr_gamma_method,
#endif
#ifdef ENABLE_VIDMODE
	&vidmode_gamma_method,
#endif
#ifdef ENABLE_QUARTZ
	&quartz_gamma_method,
#endif
#ifdef ENABLE_WINGDI
	&w32gdi_gamma_method,
#endif
	&dummy_gamma_method,
	NULL
};


const struct location_provider *location_providers[] = {
#ifdef ENABLE_GEOCLUE2
	&geoclue2_location_provider,
#endif
#ifdef ENABLE_CORELOCATION
	&corelocation_location_provider,
#endif
	&manual_location_provider,
	NULL
};


/* Names of periods of day */
static const char *period_names[] = {
	/* TRANSLATORS: Name printed when period of day is unknown */
	N_("None"),
	N_("Daytime"),
	N_("Night"),
	N_("Transition")
};


#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
static int
exact_eq(double a, double b)
{
	return a == b;
}
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif


/* Determine which period we are currently in based on time offset. */
static enum period
get_period_from_time(const struct transition_scheme *transition, int time_offset)
{
	if (time_offset < transition->dawn.start || time_offset >= transition->dusk.end)
		return PERIOD_NIGHT;
	else if (time_offset >= transition->dawn.end && time_offset < transition->dusk.start)
		return PERIOD_DAYTIME;
	else
		return PERIOD_TRANSITION;
}

/* Determine which period we are currently in based on solar elevation. */
static enum period
get_period_from_elevation(const struct transition_scheme *transition, double elevation)
{
	if (elevation < transition->low)
		return PERIOD_NIGHT;
	else if (elevation < transition->high)
		return PERIOD_TRANSITION;
	else
		return PERIOD_DAYTIME;
}

/* Determine how far through the transition we are based on time offset. */
static double
get_transition_progress_from_time(const struct transition_scheme *transition, int time_offset)
{
	if (time_offset < transition->dawn.start || time_offset >= transition->dusk.end)
		return 0.0;
	else if (time_offset < transition->dawn.end)
		return (transition->dawn.start - time_offset) / (double)(transition->dawn.start - transition->dawn.end);
	else if (time_offset > transition->dusk.start)
		return (transition->dusk.end - time_offset) / (double)(transition->dusk.end - transition->dusk.start);
	else
		return 1.0;
}

/* Determine how far through the transition we are based on elevation. */
static double
get_transition_progress_from_elevation(const struct transition_scheme *transition, double elevation)
{
	if (elevation < transition->low)
		return 0.0;
	else if (elevation < transition->high)
		return (transition->low - elevation) / (transition->low - transition->high);
	else
		return 1.0;
}

/* Return number of seconds since midnight from timestamp. */
static int
get_seconds_since_midnight(double timestamp)
{
	time_t t = (time_t)timestamp;
	struct tm tm;
	localtime_r(&t, &tm);
	return tm.tm_sec + tm.tm_min * 60 + tm.tm_hour * 3600;
}

/* Print verbose description of the given period. */
static void
print_period(enum period period, double transition)
{
	switch (period) {
	case PERIOD_NONE:
	case PERIOD_NIGHT:
	case PERIOD_DAYTIME:
		printf(_("Period: %s\n"), gettext(period_names[period]));
		break;
	case PERIOD_TRANSITION:
		printf(_("Period: %s (%.2f%% day)\n"), gettext(period_names[period]), transition * 100);
		break;
	}
}

/* Print location */
static void
print_location(const struct location *location)
{
	/* TRANSLATORS: Abbreviation for `north' */
	const char *north = _("N");
	/* TRANSLATORS: Abbreviation for `south' */
	const char *south = _("S");
	/* TRANSLATORS: Abbreviation for `east' */
	const char *east = _("E");
	/* TRANSLATORS: Abbreviation for `west' */
	const char *west = _("W");

	/* TRANSLATORS: Append degree symbols after %f if possible.
	   The string following each number is an abreviation for
	   north, source, east or west (N, S, E, W). */
	printf(_("Location: %.2f %s, %.2f %s\n"),
	       fabs(location->lat), location->lat >= 0.0 ? north : south,
	       fabs(location->lon), location->lon >= 0.0 ? east : west);
}

/* Interpolate color setting structs given alpha. */
static void
interpolate_color_settings(const struct color_setting *first, const struct color_setting *second,
                           double alpha, struct color_setting *result)
{
	int i;
	alpha = CLAMP(0.0, alpha, 1.0);
	result->temperature = (1.0 - alpha) * first->temperature + alpha * second->temperature;
	result->brightness  = (1.0 - alpha) * first->brightness  + alpha * second->brightness;
	for (i = 0; i < 3; i++)
		result->gamma[i] = (1.0 - alpha) * first->gamma[i] + alpha * second->gamma[i];
}

/* Interpolate color setting structs transition scheme. */
static void
interpolate_transition_scheme(const struct transition_scheme *transition, double alpha, struct color_setting *result)
{
	interpolate_color_settings(&transition->night, &transition->day, alpha, result);
}

/* Return 1 if color settings have major differences, otherwise 0.
   Used to determine if a fade should be applied in continual mode. */
static int
color_setting_diff_is_major(const struct color_setting *first, const struct color_setting *second)
{
	return abs(first->temperature - second->temperature) > 25 ||
	       fabs(first->brightness - second->brightness) > 0.1 ||
	       fabs(first->gamma[0] - second->gamma[0]) > 0.1 ||
	       fabs(first->gamma[1] - second->gamma[1]) > 0.1 ||
	       fabs(first->gamma[2] - second->gamma[2]) > 0.1;
}

/* Reset color setting to default values. */
static void
color_setting_reset(struct color_setting *color)
{
	color->temperature = NEUTRAL_TEMP;
	color->gamma[0] = 1.0;
	color->gamma[1] = 1.0;
	color->gamma[2] = 1.0;
	color->brightness = 1.0;
}


static int
provider_try_start(const struct location_provider *provider, LOCATION_STATE **state,
                   struct config_ini_state *config, char *args)
{
	const char *manual_keys[] = {"lat", "lon"};
	struct config_ini_section *section;
	struct config_ini_setting *setting;
	char *next_arg, *value;
	const char *key;
	int i;

	if (provider->init(state) < 0) {
		weprintf(_("Initialization of %s failed."), provider->name);
		return -1;
	}

	/* Set provider options from config file. */
	if ((section = config_ini_get_section(config, provider->name))) {
		for (setting = section->settings; setting; setting = setting->next) {
			if (provider->set_option(*state, setting->name, setting->value) < 0) {
				provider->free(*state);
				weprintf(_("Failed to set %s option."), provider->name);
				/* TRANSLATORS: `help' must not be translated. */
				weprintf(_("Try `-l %s:help' for more information."), provider->name);
				return -1;
			}
		}
	}

	/* Set provider options from command line. */
	for (i = 0; args; i++) {
		next_arg = strchr(args, ':');
		if (next_arg)
			*next_arg++ = '\0';

		key = args;
		value = strchr(args, '=');
		if (!value) {
			/* The options for the "manual" method can be set
			   without keys on the command line for convencience
			   and for backwards compatability. We add the proper
			   keys here before calling set_option(). */
			if (!strcmp(provider->name, "manual") && i < ELEMSOF(manual_keys)) {
				key = manual_keys[i];
				value = args;
			} else {
				weprintf(_("Failed to parse option `%s'."), args);
				return -1;
			}
		} else {
			*value++ = '\0';
		}

		if (provider->set_option(*state, key, value) < 0) {
			provider->free(*state);
			weprintf(_("Failed to set %s option."), provider->name);
			/* TRANSLATORS: `help' must not be translated. */
			weprintf(_("Try `-l %s:help' for more information."), provider->name);
			return -1;
		}

		args = next_arg;
	}

	/* Start provider. */
	if (provider->start(*state) < 0) {
		provider->free(*state);
		weprintf(_("Failed to start provider %s.\n"), provider->name);
		return -1;
	}

	return 0;
}

static int
method_try_start(const struct gamma_method *method, GAMMA_STATE **state,
		 enum program_mode mode, struct config_ini_state *config, char *args)
{
	struct config_ini_section *section;
	struct config_ini_setting *setting;
	char *next_arg, *value;
	const char *key;

	if (method->init(state) < 0) {
		weprintf(_("Initialization of %s failed."), method->name);
		return -1;
	}

	/* Set method options from config file. */
	if ((section = config_ini_get_section(config, method->name))) {
		for (setting = section->settings; setting; setting = setting->next) {
			if (method->set_option(*state, setting->name, setting->value) < 0) {
				method->free(*state);
				weprintf(_("Failed to set %s option."), method->name);
				/* TRANSLATORS: `help' must not be translated. */
				weprintf(_("Try `-m %s:help' for more information.\n"), method->name); /* TODO \n */
				return -1;
			}
		}
	}

	/* Set method options from command line. */
	while (args) {
		next_arg = strchr(args, ':');
		if (next_arg)
			*next_arg++ = '\0';

		key = args;
		value = strchr(args, '=');
		if (!value) {
			weprintf(_("Failed to parse option `%s'."), args);
			return -1;
		}
		*value++ = '\0';

		if (method->set_option(*state, key, value) < 0) {
			method->free(*state);
			weprintf(_("Failed to set %s option."), method->name);
			/* TRANSLATORS: `help' must not be translated. */
			weprintf(_("Try -m %s:help' for more information.\n"), method->name); /* TODO missing ` and \n */
			return -1;
		}

		args = next_arg;
	}

	/* Start method. */
	if (method->start(*state, mode) < 0) {
		method->free(*state);
		weprintf(_("Failed to start adjustment method %s.\n"), method->name); /* TODO \n */
		return -1;
	}

	return 0;
}


/* Check whether gamma is within allowed levels. */
static int
gamma_is_valid(const double gamma[3])
{
	return !(gamma[0] < MIN_GAMMA || gamma[0] > MAX_GAMMA ||
		 gamma[1] < MIN_GAMMA || gamma[1] > MAX_GAMMA ||
		 gamma[2] < MIN_GAMMA || gamma[2] > MAX_GAMMA);
}

/* Check whether location is valid.
   Prints error message on stderr and returns 0 if invalid, otherwise
   returns 1. */
static int
location_is_valid(const struct location *location)
{
	/* Latitude */
	if (location->lat < MIN_LAT || location->lat > MAX_LAT) {
		/* TRANSLATORS: Append degree symbols if possible. */
		weprintf(_("Latitude must be between %.1f and %.1f.\n"), MIN_LAT, MAX_LAT); /* TODO \n */
		return 0;
	}

	/* Longitude */
	if (location->lon < MIN_LON || location->lon > MAX_LON) {
		/* TRANSLATORS: Append degree symbols if possible. */
		weprintf(_("Longitude must be between %.1f and %.1f.\n"), MIN_LON, MAX_LON); /* TODO \n */
		return 0;
	}

	return 1;
}

/* Wait for location to become available from provider.
   Waits until timeout (milliseconds) has elapsed or forever if timeout
   is -1. Writes location to loc. Returns -1 on error,
   0 if timeout was reached, 1 if location became available. */
static int
provider_get_location(const struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *loc)
{
	int r, available, loc_fd;
	struct pollfd pollfds[1];
	double now, later;

	do {
		loc_fd = provider->get_fd(state);
		if (loc_fd >= 0) {
			/* Provider is dynamic. */
			/* TODO: This should use a monotonic time source. */
			now = systemtime_get_time();

			/* Poll on file descriptor until ready. */
			pollfds[0].fd = loc_fd;
			pollfds[0].events = POLLIN;
			r = poll(pollfds, 1, timeout);
			if (r < 0) {
				weprintf("poll {{.fd=<location provider>, .events=EPOLLIN}} 1 %i:", timeout);
				return -1;
			} else if (r == 0) {
				return 0;
			}

			later = systemtime_get_time();

			/* Adjust timeout by elapsed time */
			if (timeout >= 0) {
				timeout -= (later - now) * 1000;
				timeout = MAX(timeout, 0);
			}
		}

		if (provider->handle(state, loc, &available) < 0)
			return -1;
	} while (!available);

	return 1;
}

/* Easing function for fade.
   See https://github.com/mietek/ease-tween */
static double
ease_fade(double t)
{
	if (t <= 0) return 0;
	if (t >= 1) return 1;
	return 1.0042954579734844 * exp(
		-6.4041738958415664 * exp(-7.2908241330981340 * t));
}


/* Run continual mode loop
   This is the main loop of the continual mode which keeps track of the
   current time and continuously updates the screen to the appropriate
   color temperature. */
static void
run_continual_mode(const struct location_provider *provider, LOCATION_STATE *location_state,
                   const struct transition_scheme *scheme, const struct gamma_method *method,
                   GAMMA_STATE *method_state, int use_fade, int preserve_gamma, int verbose)
{
	int done = 0;
	int prev_disabled = 1;
	int disabled = 0;
	int location_available = 1;
	struct color_setting fade_start_interp;
	struct color_setting prev_target_interp;
	struct color_setting interp;
	struct location loc;
	int need_location;

	/* Short fade parameters */
	int fade_length = 0;
	int fade_time = 0;

	/* Save previous parameters so we can avoid printing status updates if
	   the values did not change. */
	enum period prev_period = PERIOD_NONE;

	signals_install_handlers();

	/* Previous target color setting and current actual color setting.
	   Actual color setting takes into account the current color fade. */
	color_setting_reset(&prev_target_interp);

	color_setting_reset(&interp);

	loc = (struct location){ NAN, NAN };
	need_location = !scheme->use_time;
	if (need_location) {
		weprintf(_("Waiting for initial location to become available...\n")); /* TODO \n */

		/* Get initial location from provider */
		if (provider_get_location(provider, location_state, -1, &loc) < 0)
			eprintf(_("Unable to get location from provider."));

		if (!location_is_valid(&loc))
			eprintf(_("Invalid location returned from provider.\n")); /* TODO \n */

		print_location(&loc);
	}

	if (verbose) {
		printf(_("Color temperature: %uK\n"), interp.temperature);
		printf(_("Brightness: %.2f\n"), interp.brightness);
	}

	/* Continuously adjust color temperature */
	for (;;) {
		double now;
		enum period period;
		double transition_prog;
		struct color_setting target_interp;
		int delay, loc_fd;

		/* Check to see if disable signal was caught */
		if (disable && !done) {
			disabled = !disabled;
			disable = 0;
		}

		/* Check to see if exit signal was caught */
		if (exiting) {
			if (done)
				break; /* On second signal stop the ongoing fade. */
			done = 1;
			disabled = 1;
			exiting = 0;
		}

		/* Print status change */
		if (verbose && disabled != prev_disabled)
			printf(_("Status: %s\n"), disabled ? _("Disabled") : _("Enabled"));

		prev_disabled = disabled;

		/* Read timestamp */
		now = systemtime_get_time();

		if (scheme->use_time) {
			int time_offset = get_seconds_since_midnight(now);

			period = get_period_from_time(scheme, time_offset);
			transition_prog = get_transition_progress_from_time(scheme, time_offset);
		} else {
			/* Current angular elevation of the sun */
			double elevation;
			if (libred_solar_elevation(loc.lat, loc.lon, &elevation))
				eprintf("libred_solar_elevation:");

			period = get_period_from_elevation(scheme, elevation);
			transition_prog = get_transition_progress_from_elevation(scheme, elevation);
		}

		/* Use transition progress to get target color temperature. */
		interpolate_transition_scheme(scheme, transition_prog, &target_interp);

		if (disabled) {
			period = PERIOD_NONE;
			color_setting_reset(&target_interp);
		}

		if (done)
			period = PERIOD_NONE;

		/* Print period if it changed during this update,
		   or if we are in the transition period. In transition we
		   print the progress, so we always print it in
		   that case. */
		if (verbose && (period != prev_period || period == PERIOD_TRANSITION))
			print_period(period, transition_prog);

		/* Activate hooks if period changed */
		if (period != prev_period)
			hooks_signal_period_change(prev_period, period);

		/* Start fade if the parameter differences are too big to apply
		   instantly. */
		if (use_fade) {
			if (fade_length ? color_setting_diff_is_major(&target_interp, &prev_target_interp)
			                : color_setting_diff_is_major(&interp, &target_interp)) {
				fade_length = FADE_LENGTH;
				fade_time = 0;
				fade_start_interp = interp;
			}
		}

		/* Handle ongoing fade */
		if (fade_length != 0) {
			double frac = ++fade_time / (double)fade_length;
			double alpha = CLAMP(0.0, ease_fade(frac), 1.0);

			interpolate_color_settings(&fade_start_interp, &target_interp, alpha, &interp);

			if (fade_time > fade_length) {
				fade_time = 0;
				fade_length = 0;
			}
		} else {
			interp = target_interp;
		}

		/* Break loop when done and final fade is over */
		if (done && fade_length == 0)
			break;

		if (verbose) {
			if (prev_target_interp.temperature != target_interp.temperature)
				printf(_("Color temperature: %uK\n"), target_interp.temperature);
			if (!exact_eq(prev_target_interp.brightness, target_interp.brightness))
				printf(_("Brightness: %.2f\n"), target_interp.brightness);
		}

		/* Adjust temperature */
		if (method->set_temperature(method_state, &interp, preserve_gamma) < 0)
			eprintf(_("Temperature adjustment failed."));

		/* Save period and target color setting as previous */
		prev_period = period;
		prev_target_interp = target_interp;

		/* Sleep length depends on whether a fade is ongoing. */
		delay = fade_length ? SLEEP_DURATION_SHORT : SLEEP_DURATION;

		/* Update location. */
		loc_fd = need_location ? provider->get_fd(location_state) : -1;

		if (loc_fd >= 0) {
			struct pollfd pollfds[1];
			struct location new_loc;
			int r, new_available;

			/* Provider is dynamic. */
			pollfds[0].fd = loc_fd;
			pollfds[0].events = POLLIN;
			r = poll(pollfds, 1, delay);
			if (r < 0) {
				if (errno == EINTR)
					continue;
				weprintf("poll:");
				eprintf(_("Unable to get location from provider."));
			} else if (!r) {
				continue;
			}

			/* Get new location and availability
			   information. */
			if (provider->handle(location_state, &new_loc, &new_available) < 0)
				eprintf(_("Unable to get location from provider."));

			if (!new_available && new_available != location_available) {
				weprintf(_("Location is temporarily unavailable; Using previous" /* TODO captial U efter ; and \n*/
				           " location until it becomes available...\n"));
			}

			if (new_available &&
			    (!exact_eq(new_loc.lat, loc.lat) ||
			     !exact_eq(new_loc.lon, loc.lon) ||
			     new_available != location_available)) {
				loc = new_loc;
				print_location(&loc);
			}

			location_available = new_available;

			if (!location_is_valid(&loc))
				eprintf(_("Invalid location returned from provider."));
		} else {
			systemtime_msleep(delay);
		}
	}

	/* Restore saved gamma ramps */
	method->restore(method_state);
}


int
main(int argc, char *argv[])
{
	struct options options;
	struct config_ini_state config_state;
	struct transition_scheme *scheme;
	GAMMA_STATE *method_state;
	LOCATION_STATE *location_state;
	int r, need_location;
	size_t i;
	struct location loc = { NAN, NAN };
	double now, transition_prog;
	enum period period;
	struct color_setting color;

	argv0 = argv[0];

#ifdef ENABLE_NLS
	setlocale(LC_CTYPE, "");
	setlocale(LC_MESSAGES, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
#endif

	options_init(&options);
	options_parse_args(&options, argc, argv);

	/* Load settings from config file. */
	config_ini_init(&config_state, options.config_filepath);

	free(options.config_filepath);

	options_parse_config_file(&options, &config_state);

	options_set_defaults(&options);

	if (options.scheme.dawn.start >= 0 || options.scheme.dawn.end >= 0 ||
	    options.scheme.dusk.start >= 0 || options.scheme.dusk.end >= 0) {
		if (options.scheme.dawn.start < 0 || options.scheme.dawn.end < 0 ||
		    options.scheme.dusk.start < 0 || options.scheme.dusk.end < 0)
			eprintf(_("Partial time-configuration not supported!"));

		if (options.scheme.dawn.start > options.scheme.dawn.end ||
		    options.scheme.dawn.end > options.scheme.dusk.start ||
		    options.scheme.dusk.start > options.scheme.dusk.end)
			eprintf(_("Invalid dawn/dusk time configuration!"));

		options.scheme.use_time = 1;
	}

	/* Initialize location provider if needed. If provider is NULL
	   try all providers until one that works is found. */

	/* Location is not needed for reset mode and manual mode. */
	need_location = options.mode != PROGRAM_MODE_RESET &&
	                options.mode != PROGRAM_MODE_MANUAL &&
	                !options.scheme.use_time;
	if (need_location) {
		if (options.provider) {
			/* Use provider specified on command line. */
			if (provider_try_start(options.provider, &location_state, &config_state, options.provider_args) < 0)
				exit(1);
		} else {
			/* Try all providers, use the first that works. */
			for (i = 0; location_providers[i]; i++) {
				const struct location_provider *p = location_providers[i];
				weprintf(_("Trying location provider `%s'..."), p->name);
				if (provider_try_start(p, &location_state, &config_state, NULL) < 0) {
					weprintf(_("Trying next provider..."));
					continue;
				}

				/* Found provider that works. */
				printf(_("Using provider `%s'.\n"), p->name);
				options.provider = p;
				break;
			}

			/* Failure if no providers were successful at this point. */
			if (!options.provider)
				eprintf(_("No more location providers to try."));
		}

		/* Solar elevations */
		if (options.scheme.high < options.scheme.low) {
			eprintf(_("High transition elevation cannot be lower than the low transition elevation."));
		}

		if (options.verbose) {
			/* TRANSLATORS: Append degree symbols if possible. */
			printf(_("Solar elevations: day above %.1f, night below %.1f\n"),
			       options.scheme.high, options.scheme.low);
		}
	}

	if (options.mode != PROGRAM_MODE_RESET &&
	    options.mode != PROGRAM_MODE_MANUAL) {
		if (options.verbose) {
			printf(_("Temperatures: %iK at day, %iK at night\n"),
			       options.scheme.day.temperature, options.scheme.night.temperature);
		}

		/* Color temperature */
		if (options.scheme.day.temperature < MIN_TEMP ||
		    options.scheme.day.temperature > MAX_TEMP ||
		    options.scheme.night.temperature < MIN_TEMP ||
		    options.scheme.night.temperature > MAX_TEMP)
			eprintf(_("Temperature must be between %uK and %uK."), MIN_TEMP, MAX_TEMP);
	}

	if (options.mode == PROGRAM_MODE_MANUAL) {
		/* Check color temperature to be set */
		if (options.temp_set < MIN_TEMP || options.temp_set > MAX_TEMP)
			eprintf(_("Temperature must be between %uK and %uK."), MIN_TEMP, MAX_TEMP);
	}

	/* Brightness */
	if (options.scheme.day.brightness < MIN_BRIGHTNESS ||
	    options.scheme.day.brightness > MAX_BRIGHTNESS ||
	    options.scheme.night.brightness < MIN_BRIGHTNESS ||
	    options.scheme.night.brightness > MAX_BRIGHTNESS)
		eprintf(_("Brightness values must be between %.1f and %.1f."), MIN_BRIGHTNESS, MAX_BRIGHTNESS);

	if (options.verbose)
		printf(_("Brightness: %.2f:%.2f\n"), options.scheme.day.brightness, options.scheme.night.brightness);

	/* Gamma */
	if (!gamma_is_valid(options.scheme.day.gamma) ||
	    !gamma_is_valid(options.scheme.night.gamma))
		eprintf(_("Gamma value must be between %.1f and %.1f."), MIN_GAMMA, MAX_GAMMA);

	if (options.verbose) {
		/* TRANSLATORS: The string in parenthesis is either
		   Daytime or Night (translated). */
		printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"),
		       _("Daytime"), options.scheme.day.gamma[0],
		       options.scheme.day.gamma[1],
		       options.scheme.day.gamma[2]);
		printf(_("Gamma (%s): %.3f, %.3f, %.3f\n"),
		       _("Night"), options.scheme.night.gamma[0],
		       options.scheme.night.gamma[1],
		       options.scheme.night.gamma[2]);
	}

	scheme = &options.scheme;

	/* Initialize gamma adjustment method. If method is NULL
	   try all methods until one that works is found. */

	/* Gamma adjustment not needed for print mode */
	if (options.mode != PROGRAM_MODE_PRINT) {
		if (options.method) {
			/* Use method specified on command line. */
			r = method_try_start(options.method, &method_state, options.mode,
			                     &config_state, options.method_args);
			if (r < 0)
				exit(1);
		} else {
			/* Try all methods, use the first that works. */
			for (i = 0; gamma_methods[i]; i++) {
				const struct gamma_method *m = gamma_methods[i];
				if (!m->autostart)
					continue;

				if (method_try_start(m, &method_state, options.mode, &config_state, NULL) < 0) {
					weprintf(_("Trying next method..."));
					continue;
				}

				/* Found method that works. */
				printf(_("Using method `%s'.\n"), m->name);
				options.method = m;
				break;
			}

			/* Failure if no methods were successful at this point. */
			if (!options.method)
				eprintf(_("No more methods to try."));
		}
	}

	config_ini_free(&config_state);

	switch (options.mode) {
	case PROGRAM_MODE_ONE_SHOT:
	case PROGRAM_MODE_PRINT:
		if (need_location) {
			weprintf(_("Waiting for current location to become available..."));

			/* Wait for location provider. */
			if (provider_get_location(options.provider, location_state, -1, &loc) < 0)
				eprintf(_("Unable to get location from provider."));

			if (!location_is_valid(&loc))
				exit(1);

			print_location(&loc);
		}

		now = systemtime_get_time();

		if (options.scheme.use_time) {
			int time_offset = get_seconds_since_midnight(now);
			period = get_period_from_time(scheme, time_offset);
			transition_prog = get_transition_progress_from_time(scheme, time_offset);
		} else {
			/* Current angular elevation of the sun */
			double elevation;
			if (libred_solar_elevation(loc.lat, loc.lon, &elevation))
				eprintf("libred_solar_elevation:");
			if (options.verbose) {
				/* TRANSLATORS: Append degree symbol if possible. */
				printf(_("Solar elevation: %f\n"), elevation);
			}

			period = get_period_from_elevation(scheme, elevation);
			transition_prog = get_transition_progress_from_elevation(scheme, elevation);
		}

		/* Use transition progress to set color temperature */
		interpolate_transition_scheme(scheme, transition_prog, &color);

		if (options.verbose || options.mode == PROGRAM_MODE_PRINT) {
			print_period(period, transition_prog);
			printf(_("Color temperature: %uK\n"), color.temperature);
			printf(_("Brightness: %.2f\n"), color.brightness);
		}

		if (options.mode == PROGRAM_MODE_PRINT)
			break;

	apply:
		if (options.method->set_temperature(method_state, &color, options.preserve_gamma) < 0)
			eprintf(_("Temperature adjustment failed."));

#ifndef WINDOWS
		/* In Quartz (OSX) the gamma adjustments will automatically revert when
		 * the process exits. Therefore, we have to loop until CTRL-C is received. */
		if (!strcmp(options.method->name, "quartz")) {
			weprintf(_("Press ctrl-c to stop..."));
			while (!exiting)
				pause();
		}
#endif
		break;

	case PROGRAM_MODE_MANUAL:
		if (options.verbose)
			printf(_("Color temperature: %uK\n"), options.temp_set);
		color = scheme->day;
		color.temperature = options.temp_set;
		goto apply;

	case PROGRAM_MODE_RESET:
		color_setting_reset(&color);
		options.preserve_gamma = 0;
		goto apply;

	case PROGRAM_MODE_CONTINUAL:
		run_continual_mode(options.provider, location_state, scheme, options.method, method_state,
		                   options.use_fade, options.preserve_gamma, options.verbose);
		break;
	}

	if (options.mode != PROGRAM_MODE_PRINT)
		options.method->free(method_state);
	if (need_location)
		options.provider->free(location_state);
	return 0;
}