/*-
 * 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/>.
 */
#ifndef REDSHIFT_COMMON_H
#define REDSHIFT_COMMON_H


#ifndef WINDOWS
# if defined(__WIN32__) || defined(_WIN32)
#  define WINDOWS
# endif
#endif


#include "arg.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef _POSIX_TIMERS
# include <sys/time.h>
#endif
#ifdef WINDOWS
# include <windows.h>
# define localtime_r(T, TM) localtime_s((TM), (T))
# define pause()  millisleep(100U)
#else
# include <sys/file.h>
# include <poll.h>
# include <pwd.h>
# include <time.h>
#endif

#include <libgamma.h>
#include <libred.h>


#ifdef ENABLE_NLS
# include <libintl.h>
#else
# define gettext(s) s
#endif

/**
 * List for translation, and translate in place
 * 
 * @param   s:string-literal  Translatable string
 * @return  :const char *     Translation of `s`
 */
#define _(s) gettext(s)

/**
 * List for translation without translating in place
 *
 * @param   s:string-literal  Translatable string
 * @return  :string-literal   `s` as is
 */
#define N_(s) s


#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunsafe-buffer-usage" /* broken in clang 19.1.7 */
# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" /* warns about system headers (also a stupid warning) */
# pragma clang diagnostic ignored "-Wassign-enum" /* warns about bit field enums */
# pragma clang diagnostic ignored "-Wpadded" /* only relevant for library headers */
# pragma clang diagnostic ignored "-Wcomma" /* comma is useful in loop conditions */
# pragma clang diagnostic ignored "-Wcovered-switch-default" /* stupid warning: not necessary true */
#elif defined(__GNUC__)
# pragma GCC diagnostic ignored "-Wunsuffixed-float-constants" /* stupid warning */
# pragma GCC diagnostic ignored "-Wpadded" /* only relevant for library headers */
#endif


#if defined(__GNUC__)
# define GCC_ONLY(...) __VA_ARGS__
#else
# define GCC_ONLY(...)
#endif


/**
 * Truncate a value into a closed range
 * 
 * @param   LO  The lower bound
 * @param   X   The value to truncated
 * @param   UP  The upper bound
 * @return      `X` truncated such that it is at least `LO` and at most `UP`
 */
#define CLAMP(LO, X, UP)  (((LO) > (X)) ? (LO) : (((X) < (UP)) ? (X) : (UP)))

/**
 * Check whether a value is within a closed range
 *
 * @param   LO    The lower bound
 * @param   X     The value to check
 * @param   UP    The upper bound
 * @return  :int  1 if `X` is within [`LO`, `UP`], 0 otherwise
 */
#define WITHIN(LO, X, UP)  ((LO) <= (X) && (X) <= (UP))

/**
 * Check whether a value is within a bounded, open range
 *
 * @param   LO    The lower bound
 * @param   X     The value to check
 * @param   UP    The upper bound
 * @return  :int  1 if `X` is within (`LO`, `UP`), 0 otherwise
 */
#define BETWEEN(LO, X, UP)  ((LO) < (X) && (X) < (UP))

/**
 * Quiet not-a-number `double`
 */
#define FNAN ((double)NAN) /* because clang warns when implicitly promoted to double */

/**
 * Get the number of elements in an array
 * 
 * @param   ARR      The array, must not be a pointer
 * @return  :size_t  The number of elements in `ARR` (constant
 *                   expression, unless its size is dynamic)
 */
#define ELEMSOF(ARR) (sizeof(ARR) / (sizeof(*(ARR))))

/**
 * Get the smallest of two numerical values
 * 
 * @param   A  One of the values
 * @param   B  The other value
 * @return     The smallest of `A` and `B`
 */
#define MIN(A, B) ((A) < (B) ? (A) : (B))

/**
 * Get the largest of two numerical values
 * 
 * @param   A  One of the values
 * @param   B  The other value
 * @return     The largest of `A` and `B`
 */
#define MAX(A, B) ((A) > (B) ? (A) : (B))


/**
 * Symbol used to delimit paths in environment
 * variables listing multiple paths
 */
#ifdef WINDOWS
# define PATH_DELIMITER ';'
#else
# define PATH_DELIMITER ':'
#endif

/**
 * The number of seconds in a day
 */
#define ONE_DAY  ((time_t)(24L * 60L * 60L))


/**
 * Minimum valid latitude
 */
#define MIN_LATITUDE  -90.0

/**
 * Maximum valid latitude
 */
#define MAX_LATITUDE  90.0

/**
 * Minimum valid longitude
 */
#define MIN_LONGITUDE  -180.0

/**
 * Maximum valid longitude
 */
#define MAX_LONGITUDE  180.0

/**
 * Minimum allowed colour temperature
 */
#define MIN_TEMPERATURE  ((unsigned long int)LIBRED_LOWEST_TEMPERATURE)

/**
 * Maximum allowed colour temperature
 */
#define MAX_TEMPERATURE  ULONG_MAX

/**
 * Minimum allowed whitepoint brightness
 */
#define MIN_BRIGHTNESS  0.1

/**
 * Maximum allowed whitepoint brightness
 */
#define MAX_BRIGHTNESS  1.0

/**
 * Minimum allowed gamma
 */
#define MIN_GAMMA  0.1

/**
 * Maximum allowed gamma
 */
#define MAX_GAMMA  10.0


/**
 * The colour temperature corresponding to no effect
 */
#define NEUTRAL_TEMPERATURE  6500UL

/**
 * The whitepoint brightness corresponding to
 * full brightness (no effect)
 */
#define NEUTRAL_BRIGHTNESS  1.0

/**
 * The gamma corresponding to no effect (linear output level curve)
 */
#define NEUTRAL_GAMMA  1.0


/**
 * Default daytime colour temperature
 */
#define DEFAULT_DAY_TEMPERATURE  6500UL

/**
 * Default night colour temperature
 */
#define DEFAULT_NIGHT_TEMPERATURE  4500UL

/**
 * Default daytime whitepoint brightness level
 */
#define DEFAULT_DAY_BRIGHTNESS  NEUTRAL_BRIGHTNESS

/**
 * Default night whitepoint brightness level
 */
#define DEFAULT_NIGHT_BRIGHTNESS  NEUTRAL_BRIGHTNESS

/**
 * Default daytime gamma value
 */
#define DEFAULT_DAY_GAMMA  NEUTRAL_GAMMA

/**
 * Default night gamma value
 */
#define DEFAULT_NIGHT_GAMMA  NEUTRAL_GAMMA

/**
 * The solar elevation, in degrees, that marks the
 * threshold to daytime
 */
#define DEFAULT_HIGH_ELEVATION  3.0

/**
 * The solar elevation, in degrees, that marks the
 * threshold to night
 */
#define DEFAULT_LOW_ELEVATION  LIBRED_SOLAR_ELEVATION_CIVIL_DUSK_DAWN


/**
 * Initialiser for `struct colour_setting`
 * 
 * Sets all values to their neutral values (no effects applied)
 */
#define COLOUR_SETTING_NEUTRAL\
	((struct colour_setting){\
		NEUTRAL_TEMPERATURE,\
		NEUTRAL_BRIGHTNESS,\
		{NEUTRAL_GAMMA, NEUTRAL_GAMMA, NEUTRAL_GAMMA}\
	})


/**
 * State of an adjustment method
 * 
 * Each method has their own definition of this structure
 */
typedef struct gamma_state GAMMA_STATE;

/**
 * State of a location provider
 * 
 * Each provider has their own definition of this structure
 */
typedef struct location_state LOCATION_STATE;


/**
 * The time of the day: day, night, or twilight
 */
enum period {
	/**
	 * None applied
	 */
	PERIOD_NONE,

	/**
	 * Full daytime
	 */
	PERIOD_DAYTIME,

	/**
	 * Full nighttime
	 */
	PERIOD_NIGHT,

	/**
	 * Transitioning between day and night
	 * (either direction) (twilight)
	 */
	PERIOD_TRANSITION
};


/**
 * The mode the program is running in
 */
enum program_mode {
	/**
	 * Run in foreground continually update temperature
	 */
	PROGRAM_MODE_CONTINUAL,

	/**
	 * Update temperature once then exit
	 */
	PROGRAM_MODE_ONE_SHOT,

	/**
	 * Update temperature once and reset when killed
	 */
	PROGRAM_MODE_UNTIL_DEATH,

	/**
	 * Print setting and exit
	 */
	PROGRAM_MODE_PRINT,

	/**
	 * Remove effects and exit
	 */
	PROGRAM_MODE_RESET
};


/**
 * By what the effects of the application change
 */
enum scheme_type {
	/**
	 * Effects are dependent on the Sun's elevation
	 */
	SOLAR_SCHEME,

	/**
	 * Effects are dependent on the wall clock time
	 */
	CLOCK_SCHEME,

	/**
	 * Effects do not change
	 */
	STATIC_SCHEME
};

/**
 * Scheme has not been specified
 */
#define UNSPECIFIED_SCHEME STATIC_SCHEME


/**
 * The sources where an setting was been loaded from
 * 
 * Higher valued sources have higher priority
 * 
 * This is a bitmask `enum`
 */
enum setting_source {
	/**
	 * No setting loaded, default value set
	 */
	SETTING_DEFAULT = 0x00,

	/**
	 * Setting loaded from configuration file
	 */
	SETTING_CONFIGFILE = 0x01,

	/**
	 * Setting loaded from command line arguments
	 */
	SETTING_CMDLINE = 0x02
};


/**
 * SIGUSR2 signal values as bitmask values
 */
enum signals {
	/**
	 * Block SIGUSR2 until all signals have been processed
	 */
	SIGNAL_ORDER_BARRIER = 1 << 0,

	/**
	 * Disable the effects of redshift
	 */
	SIGNAL_DISABLE = 1 << 1,

	/**
	 * Enable the effects of redshift
	 */
	SIGNAL_ENABLE = 1 << 2,

	/**
	 * Reload the configuration file
	 * 
	 * Settings from the command line will be overriden
	 */
	SIGNAL_RELOAD = 1 << 3,

	/**
	 * Execute into the currently installed version of redshift
	 */
	SIGNAL_REEXEC = 1 << 4,

	/**
	 * Set the "fade" setting to off
	 */
	SIGNAL_USE_FADE_OFF = 1 << 5,

	/**
	 * Set the "fade" setting to on
	 */
	SIGNAL_USE_FADE_ON = 1 << 6,

	/**
	 * Set the "preserve-gamma" setting to off
	 */
	SIGNAL_PRESERVE_GAMMA_OFF = 1 << 7,

	/**
	 * Set the "preserve-gamma" setting to on
	 */
	SIGNAL_PRESERVE_GAMMA_ON = 1 << 8,

	/**
	 * Exit the process without removing the its effects
	 * 
	 * If the used adjustment method does not support
	 * leaving the effects, they will be removed
	 */
	SIGNAL_EXIT_WITHOUT_RESET = 1 << 9,

	/**
	 * Do not terminate redshift the standard output
	 * and standard error are closed
	 */
	SIGNAL_IGNORE_SIGPIPE = 1 << 10,

	/**
	 * Enable verbose mode
	 */
	SIGNAL_VERBOSE_ON = 1 << 11,

	/**
	 * Disable verbose mode
	 * 
	 * Ignore if started in verbose mode (-v option)
	 */
	SIGNAL_VERBOSE_OFF = 1 << 12
};


/**
 * Specification for a path that consists of a two parts:
 * the first being defined by the environment, and the
 * seocnd being a static string
 */
struct env_path {
	/**
	 * Whether the environment variable referenced by `.prefix`
	 * should be split at each colon (:) into multiple paths to
	 * test
	 * 
	 * On Windows semicolon (;) is used instead of colon
	 */
	int multidir_env;

	/**
	 * Environment variable to use as the first part of the path
	 * 
	 * `NULL` if the user's home directory should be used
	 * 
	 * The empty string if `.suffix` should be used as is
	 */
	const char *prefix_env;

	/**
	 * The second part of the path
	 */
	const char *suffix;
};


/**
 * Geographical location, using GPS coordinates
 */
struct location {
	/**
	 * Degrees north of the equator
	 */
	double latitude;

	/**
	 * Degrees east of the prime meridian
	 */
	double longitude;
};


/**
 * Colour setting to apply
 */
struct colour_setting {
	/**
	 * Colour temperature, in Kelvin
	 */
	unsigned long int temperature;

	/**
	 * Whitepoint brightness level
	 */
	double brightness;

	/**
	 * Gamma correct, for the each RGB channel
	 * in the order: red, green, and blue
	 */
	double gamma[3];
};


/**
 * Linked list of time periods for `CLOCK_SCHEME`
 */
struct time_period {
	/**
	 * The number of seconds after midnight the period starts
	 */
	time_t start;

	/**
	 * 1 if at daytime at the the time `.start`,
	 * 0 if at nighttime at the the time `.start`
	 */
	double day_level;

	/**
	 * `.next->day_level - .day_level` dividied by
	 * the duration of the period, in seconds
	 *
	 * `.day_level` is added to the number of seconds
	 * elapsed since `.start` multiplied by this number
	 * to get the dayness level at that time
	 */
	double diff_over_duration;

	/**
	 * The following time period
	 */
	const struct time_period *next;
};


/**
 * Dayness level scheme
 */
union scheme {
	/**
	 * The scheme type
	 * 
	 * If `STATIC_SCHEME`, the union contains no scheme data
	 */
	enum scheme_type type;

	/**
	 * Used if `.type == SOLAR_SCHEME`
	 */
	struct solar_scheme {
		/**
		 * `SOLAR_SCHEME`
		 */
		enum scheme_type type;

		/**
		 * The lowest solar elevation of daytime
		 */
		double high;

		/**
		 * The highest solar elevation of nighttime
		 */
		double low;

		/**
		 * `.high - .low`
		 */
		double range;
	} elevation;

	/**
	 * Used if `.type == CLOCK_SCHEME`
	 */
	struct clock_scheme {
		/**
		 * `CLOCK_SCHEME`
		 */
		enum scheme_type type;

		/**
		 * Circularly linked list of time periods
		 * 
		 * The application will update this to always
		 * point to the current time period
		 */
		const struct time_period *periods;

		/**
		 * The memory allocation for the nodes in `.periods`
		 */
		struct time_period periods_array[4];
	} time;
};


struct config_ini_setting {
	struct config_ini_setting *next;
	char *name;
	char *value;
};


struct config_ini_section {
	struct config_ini_section *next;
	char *name;
	struct config_ini_setting *settings;
};


struct config_ini_state {
	struct config_ini_section *sections;
};


/**
 * `int` valued setting (used for booleans) with setting source
 */
struct setting_i {
	enum setting_source source; /**< Setting source */
	int value;                  /**< Setting value */
};

/**
 * `unsigned long int` valued setting with setting source
 */
struct setting_lu {
	enum setting_source source; /**< Setting source */
	unsigned long int value;    /**< Setting value */
};

/**
 * `double` valued setting with setting source
 */
struct setting_f {
	enum setting_source source; /**< Setting source */
	double value;               /**< Setting value */
};

/**
 * `double[3]` valued setting with setting source
 */
struct setting_f3 {
	enum setting_source source; /**< Setting source */
	double value[3];            /**< Setting values */
};

/**
 * `time_t` valued setting with setting source
 */
struct setting_time {
	enum setting_source source; /**< Setting source */
	time_t value;               /**< Setting value */
};

/**
 * Intermediate settings representation of colour settings
 * (for a non-transitional period) with settings sources
 * used for determining whether settings from the configuration
 * file (which is parsed after the command line) applied or are
 * specified multiple times in the configuration file
 */
struct setting_colour {
	/**
	 * Colour temperature, in Kelvin
	 * 
	 * Set by the "-t" and "-o" options and the "temperature"
	 * ("temp") settings, optionally suffixed "-day" if
	 * a daytime setting or "-night" if a nighttime setting
	 */
	struct setting_lu temperature;

	/**
	 * Whitepoint brightness level, as a [0, 1] value
	 * 
	 * Set by the "-b" option and the "brightness" settings,
	 * optionally suffixed "-day" if a daytime setting or
	 * "-night" if a nighttime setting
	 */
	struct setting_f brightness;

	/**
	 * Gamma values, in the order red, green, blue
	 * 
	 * Set by the "-g" option and the "gamma" settings,
	 * optionally suffixed "-day" if a daytime setting or
	 * "-night" if a nighttime setting
	 */
	struct setting_f3 gamma;
};

/**
 * Intermediate settings representation with settings sources
 * used for determining whether settings from the configuration
 * file (which is parsed after the command line) applied or
 * are specified multiple times in the configuration file
 * 
 * Settings without a source can only be specified in the
 * command line
 */
struct settings {
	/**
	 * The path to the configuration, `NULL` if unspecified
	 * (search default paths)
	 * 
	 * This represents the "-c" option
	 */
	const char *config_file;

	/**
	 * Scheme type to use as according the the command line
	 * options, `UNSPECIFIED_SCHEME` if not unspecified
	 */
	enum scheme_type scheme_type;

	/**
	 * Whether the program should, if ran in one-shot mode,
	 * pause after applying the effect and reset them when
	 * killed
	 * 
	 * This represents the "-d" option
	 */
	int until_death;

	/**
	 * Whether the program should preserve preapplied
	 * colour calibrations
	 * 
	 * This represents the "preserve-gamma" setting and
	 * "-P" (off) and "+P" (on) options
	 */
	struct setting_i preserve_gamma;

	/**
	 * Whether smooth transitions shall be applied when
	 * a large change in colour settings occurs
	 * 
	 * This represents the "fade" setting (or the deprecated
	 * alias "transition") and "-r" (off) and "+r" (on) options
	 */
	struct setting_i use_fade;

	/**
	 * Whether the program should start in disabled mode
	 * 
	 * This represents the "start-disabled" setting and
	 * "-D" (off) and "+D" (on) options
	 */
	struct setting_i disabled;

	/**
	 * The colour settings to apply at daytime
	 */
	struct setting_colour day;

	/**
	 * The colour settings to apply at nighttime
	 */
	struct setting_colour night;

	/**
	 * The lowest solar elevation, in degrees, at daytime,
	 * when using solar scheme
	 * 
	 * This represents the "elevation-high" setting and the
	 * left value of the -e option
	 */
	struct setting_f elevation_high;

	/**
	 * The highest solar elevation, in degrees, at nighttime,
	 * when using solar scheme
	 * 
	 * This represents the "elevation-low" setting
	 * 
	 * This represents the "elevation-low" setting and the
	 * right value of the -e option
	 */
	struct setting_f elevation_low;

	/**
	 * The wall-clock time that marks the end of nighttime,
	 * when using clock scheme
	 *
	 * This represents the left value of the "dawn-time" setting
	 */
	struct setting_time dawn_start; /* TODO no cmdline option */

	/**
	 * The wall-clock time that marks the start of daytime,
	 * when using clock scheme
	 *
	 * This represents the right value of the "dawn-time" setting
	 */
	struct setting_time dawn_end; /* TODO no cmdline option */

	/**
	 * The wall-clock time that marks the end of daytime,
	 * when using clock scheme
	 *
	 * This represents the left value of the "dusk-time" setting
	 */
	struct setting_time dusk_start; /* TODO no cmdline option */

	/**
	 * The wall-clock time that marks the start of nighttime,
	 * when using clock scheme
	 *
	 * This represents the right value of the "dusk-time" setting
	 */
	struct setting_time dusk_end; /* TODO no cmdline option */

	/* Selected gamma method */
	const struct gamma_method *method;
	/* Arguments for gamma method */
	char *method_args;

	/* Selected location provider */
	const struct location_provider *provider;
	/* Arguments for location provider */
	char *provider_args;

	struct config_ini_state config;
};


/**
 * Adjustment method information and interface
 */
struct gamma_method {
	/**
	 * The name of the adjustment method
	 */
	const char *name;

	/**
	 * 1 if the method should be tried if none is explicitly chosen,
	 * 0 otherwise
	 */
	int autostart;

	/**
	 * 1 if the method automatically resets the adjustments when disconnected,
	 * 0 otherwise
	 */
	int autoreset;

	/**
	 * Check if the adjustment method is available in the used backend
	 * 
	 * @return  1 if the adjustment method is available, 0 otherwise
	 */
	int (*is_available)(void);

	/**
	 * Create an initialised state object
	 * 
	 * @param   state_out  Output parameter for the state object
	 * @return             0 on success, -1 on failure
	 * 
	 * `*state_out` is set (potentially to `NULL`) on failure
	 */
	int (*create)(GAMMA_STATE **state_out);

	/**
	 * Configure the adjustment method
	 * 
	 * @param   state  State object for the adjustment method
	 * @param   key    Option to configure
	 * @param   value  Option value to set
	 * @return         0 on success, -1 on failure
	 */
	int (*set_option)(GAMMA_STATE *state, const char *key, const char *value);

	/**
	 * Print help on options for the adjustment method
	 * 
	 * @param  f  Output sink
	 */
	void (*print_help)(FILE *f);

	/**
	 * Finalise option-dependent initialisation and connections
	 * 
	 * @param   state  State object for the adjustment method
	 * @return         0 on success, -1 on failure
	 */
	int (*start)(GAMMA_STATE *state);

	/**
	 * Apply colour settings
	 * 
	 * @param   state     State object for the adjustment method
	 * @param   settings  The colour settings to apply
	 * @param   preserve  Whether currently applied adjustments (assumed
	 *                    to be colour calibration) shall remain applied
	 * @return            0 on success, -1 on failure
	 */
	int (*apply)(GAMMA_STATE *state, const struct colour_setting *setting, int preserve);

	/**
	 * Restore the adjustments to the `.state` before start was called
	 * 
	 * @param  state  State object for the adjustment method
	 */
	void (*restore)(GAMMA_STATE *state);

	/**
	 * Close connections and deallocate all state resources
	 * 
	 * @param  state  The state to terminate
	 *
	 * The pointer `state` will become invalid
	 */
	void (*free)(GAMMA_STATE *state);
};

/**
 * Initialiser for `struct gamma_method`
 *
 * @param  NAME:const char *  Value for `.name`
 * @param  AUTOSTART:int      Value for `.autostart`
 * @param  AUTORESET:int      Value for `.autoreset`
 * @param  PREFIX:identifier  The text, sans terminal underscore (_), prefixed to the
 *                            names of each function implementing the adjustment method
 */
#define GAMMA_METHOD_INIT(NAME, AUTOSTART, AUTORESET, PREFIX)\
	{\
		.name = (NAME),\
		.autostart = (AUTOSTART),\
		.autoreset = (AUTORESET),\
		.is_available = &PREFIX##_is_available,\
		.create = &PREFIX##_create,\
		.set_option = &PREFIX##_set_option,\
		.print_help = &PREFIX##_print_help,\
		.start = &PREFIX##_start,\
		.free = &PREFIX##_free,\
		.restore = &PREFIX##_restore,\
		.apply = &PREFIX##_apply\
	}


/**
 * Location provider information and interface
 */
struct location_provider {
	/**
	 * The name of the location provider
	 */
	const char *name;

	/**
	 * Create an initialised state object
	 * 
	 * @param   state_out  Output parameter for the state object
	 * @return             0 on success, -1 on failure
	 * 
	 * `*state_out` is set (potentially to `NULL`) on failure
	 */
	int (*create)(LOCATION_STATE **state_out);

	/**
	 * Configure the location provider
	 * 
	 * @param   state  State object for the location provider
	 * @param   key    Option to configure
	 * @param   value  Option value to set
	 * @return         0 on success, -1 on failure
	 */
	int (*set_option)(LOCATION_STATE *state, const char *key, const char *value);

	/**
	 * Print help on options for the location provider
	 * 
	 * @param  f  Output sink
	 */
	void (*print_help)(FILE *f);

	/**
	 * Finalise option-dependent initialisation and connections
	 * 
	 * @param   state  State object for the location provider
	 * @return         0 on success, -1 on failure
	 */
	int (*start)(LOCATION_STATE *state);

	/**
	 * Get the file descriptor used by the location provider
	 * 
	 * The application may use it for detecting when there
	 * is data available for `.handle` to act upon
	 * 
	 * @param   state  State object for the location provider
	 * @return         The file descriptor used by location provider, -1 if none
	 */
	int (*get_fd)(LOCATION_STATE *state);

	/**
	 * Get the current location
	 * 
	 * This function shall only be caused if `.get_fd` returns -1
	 * or the file descriptor it returns has data available on it
	 * as indicated by input polling, otherwise `*location` and
	 * `*available` will not be set
	 * 
	 * @param   state          State object for the location provider
	 * @param   location_out   Output parameter for the current location
	 * @param   available_out  Output parameter for whether the location provider
	 *                         is currently available
	 * @return                 0 on success, -1 on unrecoverable failure
	 */
	int (*fetch)(LOCATION_STATE *state, struct location *location, int *available);

	/**
	 * Close connections and deallocate all state resources
	 * 
	 * @param  state  The state to terminate
	 *
	 * The pointer `state` will become invalid
	 */
	void (*free)(LOCATION_STATE *state);
};

/**
 * Initialiser for `struct location_provider`
 *
 * @param  NAME:const char *  Value for `.name`
 * @param  PREFIX:identifier  The text, sans terminal underscore (_), prefixed to the
 *                            names of each function implementing the location provider
 */
#define LOCATION_PROVIDER_INIT(NAME, PREFIX)\
	{\
		.name = (NAME),\
		.create = &PREFIX##_create,\
		.set_option = &PREFIX##_set_option,\
		.print_help = &PREFIX##_print_help,\
		.start = &PREFIX##_start,\
		.get_fd = &PREFIX##_get_fd,\
		.fetch = &PREFIX##_fetch,\
		.free = &PREFIX##_free\
	}


/**
 * `NULL` terminated list of adjustment methods
 */
extern const struct gamma_method *gamma_methods[];

/**
 * `NULL` terminated list of location providers
 */
extern const struct location_provider *location_providers[];

/**
 * Set to 1 once the process has received a signal to terminate
 */
extern volatile sig_atomic_t exiting;

/**
 * Set to 1 once the process has received a signal to remove its effect
 */
extern volatile sig_atomic_t disable;

/**
 * Bitwise or OR of received SIGUSR2 signal values
 */
extern volatile enum signals signals;

/**
 * The colour settings applied at daytime
 */
extern struct colour_setting day_settings;

/**
 * The colour settings applied at nighttime
 */
extern struct colour_setting night_settings;

/**
 * The colour settings applied at nighttime
 */
extern union scheme scheme;

/**
 * The mode the application is running in
 */
extern enum program_mode mode;

/**
 * Whether initially applied adjustments (assumed
 * to be colour calibration) shall remain applied
 */
extern int preserve_gamma;

/**
 * Whether smooth transitions shall be applied when
 * a large change in colour settings occurs
 */
extern int use_fade;

/**
 * Whether the application is in verbose mode
 */
extern int verbose;


/* backend-direct.c */

/**
 * Create an initialised state object for direct gamma adjustments
 * 
 * @param   state_out    Output parameter for the state object
 * @param   method       libgamma constant for the adjustment method
 * @param   method_name  redshift's name for the adjustment method
 * @return               0 on success, -1 on failure
 * 
 * `*state_out` is set (potentially to `NULL`) on failure
 */
int direct_create(GAMMA_STATE **state_out, int method, const char *method_name);

/**
 * Print help on options for the adjustment method using direct gamma adjustments
 * 
 * @param  f       Output sink
 * @param  method  libgamma constant for the adjustment method
 */
void direct_print_help(FILE *f, int method);

/**
 * Configure the adjustment method using direct gamma adjustments
 * 
 * @param   state  State object for the adjustment method
 * @param   key    Option to configure
 * @param   value  Option value to set
 * @return         0 on success, -1 on failure
 */
int direct_set_option(GAMMA_STATE *state, const char *key, const char *value);

/**
 * Select partitions to apply adjustments to using direct gamma adjustments
 * 
 * @param   state  State object for the adjustment method
 * @param   key    Option to configure
 * @param   value  Option value to set
 * @return         0 on success, -1 on failure
 */
int direct_set_partitions(GAMMA_STATE *state, const char *key, const char *value);

/**
 * Select CRTCs to apply adjustments to using direct gamma adjustments
 * 
 * @param   state  State object for the adjustment method
 * @param   key    Option to configure
 * @param   value  Option value to set
 * @return         0 on success, -1 on failure
 */
int direct_set_crtcs(GAMMA_STATE *state, const char *key, const char *value);

/**
 * Finalise option-dependent initialisation and connections
 * for direct gamma adjustments
 * 
 * @param   state  State object for the adjustment method
 * @return         0 on success, -1 on failure
 */
int direct_start(GAMMA_STATE *state);

/**
 * Apply colour settings using direct gamma adjustments
 * 
 * @param   state     State object for the adjustment method
 * @param   settings  The colour settings to apply
 * @param   preserve  Whether currently applied adjustments (assumed
 *                    to be colour calibration) shall remain applied
 * @return            0 on success, -1 on failure
 */
int direct_apply(GAMMA_STATE *state, const struct colour_setting *setting, int preserve);

/**
 * Restore the adjustments to the `.state` before start was called
 * using direct gamma adjustments
 * 
 * @param  state  State object for the adjustment method
 */
void direct_restore(GAMMA_STATE *state);

/**
 * Close connections and deallocate all state resources
 * for direct gamma adjustments
 * 
 * @param  state  The state to terminate
 *
 * The pointer `state` will become invalid
 */
void direct_free(GAMMA_STATE *state);


/* colour.c */

/**
 * Interpolate between two colour settings
 * 
 * @param  a       The first colour setting, used wholly when `t` is 0
 * @param  b       The second colour setting, used wholly when `t` is 1
 * @param  t       The degree to which `second` second be applied
 * @param  result  Output parameter for `(1 - t) * a + t * b`
 */
void interpolate_colour_settings(const struct colour_setting *a, const struct colour_setting *b,
                                 double t, struct colour_setting *result);

/**
 * Check whether the differences between two colours settings
 * are large enough to warrant fading between the two
 * 
 * @param   a  The first colour setting
 * @param   b  The second colour setting
 * @return     1 if the difference between `a` and `b` is large, 0 otherwise
 */
GCC_ONLY(__attribute__((__pure__)))
int colour_setting_diff_is_major(const struct colour_setting *a, const struct colour_setting *b);

#define LIST_RAMPS_STOP_VALUE_TYPES(X, D)\
	X(u8, ramps8, uint8_t, UINT8_MAX, 8) D	\
	X(u16, ramps16, uint16_t, UINT16_MAX, 16) D\
	X(u32, ramps32, uint32_t, UINT32_MAX, 32) D\
	X(u64, ramps64, uint64_t, UINT64_MAX, 64) D\
	X(float, rampsf, float, 1, -1) D\
	X(double, rampsd, double, 1, -2)

#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
	/**
	 * Fill the gamma ramps
	 * 
	 * @param  gamma_r   The gamma ramp for the red channel
	 * @param  gamma_g   The gamma ramp for the green channel
	 * @param  gamma_b   The gamma ramp for the blue channel
	 * @param  size_r    The number of stops in `gamma_r`
	 * @param  size_g    The number of stops in `gamma_g`
	 * @param  size_b    The number of stops in `gamma_b`
	 * @param  settings  The colour settings to apply (temperature, brightness, gamma)
	 */\
	void fill_ramps_##SUFFIX(TYPE *gamma_r, TYPE *gamma_g, TYPE *gamma_b,\
	                         size_t size_r, size_t size_g, size_t size_b,\
	                         const struct colour_setting *setting)
LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X


/* config-ini.c */

/**
 * Load the configuration file
 * 
 * @param  state  Output parameter for the configurations
 * @param  path   The path to the configuration file, or `NULL` if the
 *                application should look for it in the default paths
 */
void config_ini_init(struct config_ini_state *state, const char *path);

/**
 * Deallocate the settings loaded
 * from the configurations file
 * 
 * @param  state  The configurations
 */
void config_ini_free(struct config_ini_state *state);

/**
 * Get a section from the configuration file
 * 
 * @param   state  The configurations
 * @param   name   The name of the section
 * @return         The section; `NULL` if missing
 */
GCC_ONLY(__attribute__((__pure__)))
struct config_ini_section *config_ini_get_section(struct config_ini_state *state, const char *name);


/* config.c */

/**
 * Load settings
 * 
 * @param  settings  Output parameter for the settings
 * @param  argc      Number of command line arguments
 * @param  argv      `NULL` terminated list of command line arguments,
 *                   including argument zero
 */
void load_settings(struct settings *settings, int argc, char *argv[]);


/* gamma.c */

/**
 * Get and configure adjustment method
 * 
 * @param  settings          The loaded application settings, will be updated
 *                           to point `settings->method` to the adjustment method
 * @param  method_state_out  Output parameter for the state of the adjustment method
 * 
 * The function will print an error message and exit the
 * process if no adjustment method is available
 */
void acquire_adjustment_method(struct settings *settings, GAMMA_STATE **method_state_out);


/* location.c */

/**
 * Get the current location from the location provider
 *
 * @param   provider      The location provider functions
 * @param   state         The location provider state
 * @param   timeout       The number of milliseconds to wait, -1 for indefinitely
 * @param   location_out  Output parameter for the location, in GPS coordinates
 * @return                1 if `*location_out` was updated,
 *                        0 if the timeout was reached,
 *                        -1 on error
 */
int get_location(const struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *location_out);

/**
 * Get and configure location provider
 * 
 * @param  settings            The loaded application settings, will be updated
 *                             to point `settings->provider` to the location provider
 * @param  location_state_out  Output parameter for the state of the location provider
 * 
 * The function will print an error message and exit the
 * process if no location provider is available
 */
void acquire_location_provider(struct settings *settings, LOCATION_STATE **location_state_out);

/**
 * Check whether location is valid
 * 
 * If the message is invalid, and error message is printed
 * 
 * @param   location  The location to check
 * @return            1 if the location is valid, 0 otherwise
 */
int location_is_valid(const struct location *location);

/**
 * Print the current location to standard output
 * 
 * @param  location  The current location
 */
void print_location(const struct location *location);


/* hooks.c */

/**
 * Run hooks with a signal that the period changed
 * 
 * @param  prev_period  The previous period
 * @param  period       The new current period
 */
void run_period_change_hooks(enum period prev_period, enum period period);


/* signals.c */

/**
 * Install signal handlers for the process
 */
void install_signal_handlers(void);

/**
 * Install signal handlers for forcefully terminating
 * the process
 * 
 * This is useful if slave thread is blocked
 * 
 * SIGINT, SIGTERM, and SIGQUIT are set to at the
 * first arrival arm a SIGARLM timer with a short
 * expiration time, and on the second arrival
 * immediately terminate the process. SIGARLM is
 * set to immediately terminate the process.
 */
#ifndef WINDOWS
void install_forceful_exit_signal_handlers(void);
#endif


/* util.c */

/**
 * Remove trailing whitespace
 * 
 * @param   s    The string to trim, will be truncated
 * @param   end  The current end of `s`; will be looked up if `NULL`
 * @return       `s`
 */
char *rtrim(char *s, char *end);

/**
 * Remove leading whitespace
 * 
 * @param   s  The string to trim (will not be modified)
 * @return     `s` with an offset
 */
GCC_ONLY(__attribute__((__warn_unused_result__, __pure__)))
char *ltrim(char *s);

/**
 * Get the user's home directory
 *
 * This function looks up the user's home directory
 * once and caches the result, later calls to this
 * function will use the cached result
 * 
 * @return  The user's home directory; the empty string if not found
 */
const char *get_home(void);

/**
 * Search for a file and open it for reading
 * 
 * @param   path_spec    Specification for the path to try
 * @param   path_out     Output parameter for the found file
 * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`;
 *                       shall be free(3)d by the caller
 * @return               `FILE` object for the reading the file; `NULL` if not found
 */
FILE *try_path_fopen(const struct env_path *path_spec, const char **path_out, char **pathbuf_out);

/**
 * Search for a directory and open it for reading
 * 
 * @param   path_spec    Specification for the path to try
 * @param   path_out     Output parameter for the found directory
 * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`;
 *                       shall be free(3)d by the caller
 * @return               `DIR` object for the reading the directory; `NULL` if not found
 */
DIR *try_path_opendir(const struct env_path *path_spec, const char **path_out, char **pathbuf_out);

#ifndef WINDOWS
/**
 * Create a pipe(7) where both ends have `O_CLOEXEC`,
 * the read-end will also have `O_NONBLOCK` applied
 * 
 * @param  pipefds  Output parameter for the pipe's file descriptors:
 *                  0) reading file descriptor, and
 *                  1) writing file descriptor
 */
void pipe_rdnonblock(int pipefds[2]);
#endif

/**
 * Wrapper for calloc(3) that prints and error message
 * and terminates the process on failure
 * 
 * @param   n  Number of elements to allocate memory for
 * @param   m  The size, in bytes, of each element
 * @return     Pointer to zero-initialised storage of at least `n*m` bytes, with default alignment
 */
GCC_ONLY(__attribute__((__warn_unused_result__, __malloc__, __alloc_size__(1, 2), __returns_nonnull__)))
void *ecalloc(size_t n, size_t m);

/**
 * Wrapper for malloc(3) that prints and error message
 * and terminates the process on failure
 * 
 * @param   n  Number of bytes to allocate
 * @return     Pointer to uninitialised storage of at least `n` bytes, with default alignment
 */
GCC_ONLY(__attribute__((__warn_unused_result__, __malloc__, __alloc_size__(1), __returns_nonnull__)))
void *emalloc(size_t n);

/**
 * Wrapper for realloc(3) that prints and error message
 * and terminates the process on failure
 * 
 * @param   ptr  Pointer to reallocate
 * @param   n    Despired allocation size in bytes
 * @return       Replacement pointer for `ptr`, pointing to storage of at least `n` bytes,
 *               any byte that could be copied from `ptr` is copied over, and additional
 *               memory is uninitialised
 */
GCC_ONLY(__attribute__((__warn_unused_result__, __returns_nonnull__, __alloc_size__(2))))
void *erealloc(void *ptr, size_t n);

/**
 * Wrapper for strdup(3) that prints and error message
 * and terminates the process on failure
 * 
 * @param   s  String to copy
 * @return     Copy of `s`
 */
GCC_ONLY(__attribute__((__warn_unused_result__, __returns_nonnull__, __malloc__, __assume_aligned__(1), __nonnull__)))
char *estrdup(const char *s);

/**
 * Print a message, prefixed with the process name (followed by ": "),
 * to standard error
 * 
 * @param  fmt   Message text format string, see fprintf(3)
 * @param  args  Message text arguments
 */
GCC_ONLY(__attribute__((__nonnull__(1), __format__(__printf__, 1, 0))))
void vweprintf(const char *fmt, va_list args);

/**
 * Print a message, prefixed with the process name (followed by ": "),
 * to standard error
 * 
 * @param  fmt  Message text format string, see fprintf(3)
 * @param  ...  Message text arguments
 */
GCC_ONLY(__attribute__((__nonnull__(1), __format__(__printf__, 1, 2))))
void weprintf(const char *fmt, ...);

/**
 * Print a message, prefixed with the process name (followed by ": "),
 * to standard error and terminate the process with exit value
 * indicating error
 * 
 * @param  fmt  Message text format string, see fprintf(3)
 * @param  ...  Message text arguments
 */
GCC_ONLY(__attribute__((__nonnull__(1), __format__(__printf__, 1, 2), __noreturn__)))
void eprintf(const char *fmt, ...);


extern const struct gamma_method dummy_gamma_method;
#ifdef ENABLE_COOPGAMMA
extern const struct gamma_method coopgamma_gamma_method;
#endif
extern const struct gamma_method randr_gamma_method;
extern const struct gamma_method vidmode_gamma_method;
extern const struct gamma_method drm_gamma_method;
extern const struct gamma_method quartz_gamma_method;
extern const struct gamma_method w32gdi_gamma_method;

extern const struct location_provider manual_location_provider;
#ifdef ENABLE_GEOCLUE2
extern const struct location_provider geoclue2_location_provider;
#endif
#ifdef ENABLE_CORELOCATION
extern const struct location_provider corelocation_location_provider;
#endif


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


#endif