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


/**
 * Union of libgamma gamma ramp structures
 */
union gamma_ramps {
	/**
	 * Gamma ramp sizes
	 */
	struct {
		size_t red;   /**< Size of the gamma ramp for the red channel */
		size_t green; /**< Size of the gamma ramp for the green channel */
		size_t blue;  /**< Size of the gamma ramp for the blue channel */
	} size;

	/* Ramp structures */
#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
	struct libgamma_gamma_##RAMPS RAMPS
	LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X
};


/**
 * State for partition (e.g. X screen or graphics card)
 */
struct partition_state {
	/**
	 * libgamma state
	 */
	struct libgamma_partition_state state;

	/**
	 * Index of CRTC when indexing spans multiple X screens
	 */
	size_t crtc_num_offset;
};


/**
 * CRTC state
 */
struct crtc_state {
	/**
	 * libgamma state
	 */
	struct libgamma_crtc_state state;

	/**
	 * Gamma ramp depth, 0 if uninitialised,
	 * -1 if single-precision float,
	 * -2 if double-precision float,
	 * otherwise the number of bits (of unsigned integer)
	 */
	signed gamma_depth;

	/**
	 * Gamma ramps used by the gamma adjustment function
	 */
	union gamma_ramps gamma_ramps;

	/**
	 * Original state of gamma ramps
	 */
	union gamma_ramps saved_gamma_ramps;
};


/**
 * CRTC number, either overall number or partion and number within partition
 */
struct crtc_number {
	/**
	 * The partition of the EDID, -1 if using overall ordinal
	 */
	ssize_t partition;

	/**
	 * CRTC number within the partition, or overall if no partition is specified
	 */
	size_t crtc;
};


struct gamma_state {
	/**
	 * libgamma state for the site (e.g. X display)
	 */
	struct libgamma_site_state site;

	/**
	 * Whether the adjustment method supports fetching
	 * EDIDs from the outputs
	 */
	unsigned supports_edid : 1;

	/**
	 * Whether the adjustment method supports multiple
	 * sites rather than just the default site
	 */
	unsigned multiple_sites : 1;

	/**
	 * Whether the adjustment method supports multiple partitions
	 * per site
	 */
	unsigned multiple_partitions : 1;

	/**
	 * Whether the adjustment method supports multiple CRTC:s
	 * per partition per site
	 */
	unsigned multiple_crtcs : 1;

	/**
	 * Whether partitions are graphics cards
	 */
	unsigned partitions_are_graphics_cards : 1;

	/**
	 * Whether a parition has been selected
	 */
	unsigned partitions_selected : 1;

	/**
	 * Whether CRTCs have been selected
	 */
	unsigned crtcs_selected : 1;

	/**
	 * Whether the program has connected to the site
	 */
	unsigned connected : 1;

	/**
	 * The libgamma constant for the adjustment method
	 */
	int method;

	/**
	 * Number of selected CRTCs
	 */
	size_t ncrtcs;

	/**
	 * Number of selected EDIDs
	 */
	size_t nedids;

	/**
	 * Number of selected parition
	 */
	size_t npartitions;

	/**
	 * Selected paritions (if `partitions_selected`)
	 */
	size_t *selected_partitions;

	/**
	 * Selected CRTC numbers
	 * 
	 * Deallocated by when no longer needed
	 */
	struct crtc_number *selected_crtcs;

	/**
	 * Selected EDIDs
	 * 
	 * Deallocated by when no longer needed
	 */
	char **selected_edids;

	/**
	 * Name of site to connect to, `NULL` if not selected
	 * or once connected to the site
	 */
	char *site_name;

	/**
	 * State for selected paritions
	 */
	struct partition_state *partitions;

	/**
	 * State for selected CRTCs
	 */
	struct crtc_state *crtcs;
};


int
direct_create(struct gamma_state **state_out, int method, const char *method_name)
{
	struct libgamma_method_capabilities caps;
	struct gamma_state *state;
	int err;

	*state_out = NULL;

	if (!libgamma_is_method_available(method)) {
		weprintf(_("Adjustment method `%s' is not available"), method_name);
		return -1;
	}

	err = libgamma_method_capabilities(&caps, sizeof(caps), method);
	if (err) {
		weprintf("libgamma_method_capabilities %s: %s", libgamma_const_of_method(method), libgamma_strerror(err));
		return -1;
	}

	state = *state_out = ecalloc(1, sizeof(**state_out));
	state->selected_crtcs = NULL;
	state->partitions = NULL;
	state->site_name = NULL;
	state->method = method;
	state->supports_edid = (caps.crtc_information & LIBGAMMA_CRTC_INFO_EDID) ? 1 : 0;
	state->multiple_sites = caps.multiple_sites;
	state->multiple_partitions = caps.multiple_partitions;
	state->multiple_crtcs = caps.multiple_crtcs;
	state->partitions_are_graphics_cards = caps.partitions_are_graphics_cards;

	if (state->multiple_sites)
		return 0;

	err = libgamma_site_initialise(&state->site, method, NULL);
	if (err) {
		weprintf("libgamma_site_initialise %s NULL: %s", libgamma_const_of_method(method), libgamma_strerror(err));
		free(state);
		*state_out = NULL;
		return -1;
	}

	return 0;
}


void
direct_print_help(int method)
{
	struct libgamma_method_capabilities caps;

	if (libgamma_method_capabilities(&caps, sizeof(caps), method)) {
		printf(_("Adjustment method not available\n"));
		printf("\n");
		return;
	}

	if (caps.multiple_sites)
		printf("  display=%s  %s\n", _("NAME  "), _("Display server instance to apply adjustments to"));

	if (caps.multiple_partitions && caps.partitions_are_graphics_cards) {
		/* TRANSLATORS: "N" represents "ordinal"; right-pad with spaces to preserve display width */
		printf("  card=%s     %s\n", _("N     "), _("Graphics card to apply adjustments to"));
	} else if (caps.multiple_partitions) {
		/* TRANSLATORS: "N" represents "ordinal"; right-pad with spaces to preserve display width */
		printf("  screen=%s   %s\n", _("N     "), _("List of comma-separated X screens to apply adjustments to"));
	}

	if (caps.multiple_crtcs) {
		/* TRANSLATORS: "N" represents "number"; right-pad with spaces to preserve display width */
		printf("  crtc=%s     %s\n", _("N     "), _("List of comma-separated CRTCs to apply adjustments to"));
	}
	if (caps.multiple_crtcs && (caps.crtc_information & LIBGAMMA_CRTC_INFO_EDID)) {
		printf("  edid=%s     %s\n", _("EDID  "), _("List of comma-separated EDIDS of monitors to apply "
	                                                        "adjustments to, enter `list' to list available monitors"));
	}

	if (caps.multiple_sites || caps.multiple_partitions || caps.multiple_crtcs)
		printf("\n");
}


int
direct_set_option(struct gamma_state *state, const char *key, const char *value)
{
	if (state->multiple_sites && !strcasecmp(key, "display")) {
		return direct_set_site(state, key, value);
	} else if (state->multiple_partitions && !strcasecmp(key, state->partitions_are_graphics_cards ? "card" : "screen")) {
		return direct_set_partitions(state, key, value);
	} else if (state->multiple_crtcs && !strcasecmp(key, "crtc")) {
		return direct_set_crtcs(state, key, value);
	} else if (state->multiple_crtcs && state->supports_edid && !strcasecmp(key, "edid")) {
		return direct_set_edids(state, key, value);
	} else if (!strcasecmp(key, "preserve")) {
		weprintf(_("Deprecated method parameter ignored: `%s'."), key);
		return 0;
	} else {
		weprintf(_("Unknown method parameter: `%s'."), key);
		return -1;
	}
}


int
direct_set_site(struct gamma_state *state, const char *key, const char *value)
{
	if (state->site_name) {
		weprintf(_("`%s' option specified multiple times, using last selection."), key);
		free(state->site_name);
	}
	state->site_name = estrdup(value);
	return 0;
}


int
direct_set_partitions(struct gamma_state *state, const char *key, const char *value)
{
	const char *end, *p;
	uintmax_t num;
	size_t count;

	/* Check previously unspecified */
	if (state->partitions_selected)
		weprintf(_("`%s' option specified multiple times, using last selection."), key);
	state->partitions_selected = 1;

	/* Check if all are selected */
	state->npartitions = 0;
	free(state->selected_partitions);
	state->selected_partitions = NULL;
	if (!*value || !strcasecmp(value, "all"))
		return 0;

	/* Get number count */
	for (p = value, count = 1; *p; p++)
		if (*p == ',')
			count++;
	state->selected_partitions = ecalloc(count, sizeof(*state->selected_partitions));

	/* Parse numbers */
	errno = 0;
	for (p = value; *p; p = end) {
		num = strtoumax(p, (void *)&end, 10);
		state->selected_partitions[state->npartitions++] = (size_t)num;
		if (num > (uintmax_t)SIZE_MAX || (*end && *end != ',') || !isdigit(*p) || errno) {
			weprintf(_("Invalid value of `%s' option: `%s'."), key, value);
			return -1;
		}
		end = &end[*end == ','];
	}

	return 0;
}


int
direct_set_crtcs(struct gamma_state *state, const char *key, const char *value)
{
	const char *end, *p;
	uintmax_t num;
	size_t count;

	/* Check previously unspecified */
	if (state->crtcs_selected)
		weprintf(_("`%s' option specified multiple times, using last selection."), key);
	state->crtcs_selected = 1;

	/* Check if all are selected */
	state->ncrtcs = 0;
	free(state->selected_crtcs);
	state->selected_crtcs = NULL;
	if (!*value || !strcasecmp(value, "all"))
		return 0;

	/* Get number count */
	for (p = value, count = 1; *p; p++)
		if (*p == ',')
			count++;
	state->selected_crtcs = ecalloc(count, sizeof(*state->selected_crtcs));

	/* Parse numbers */
	errno = 0;
	for (p = value; *p; p = end) {
		num = strtoumax(p, (void *)&end, 10);
		state->selected_crtcs[state->ncrtcs].partition = -1;
		state->selected_crtcs[state->ncrtcs].crtc = (size_t)num;
		if (num > (uintmax_t)SIZE_MAX || (*end && *end != ',' && *end != '.') || !isdigit(*p) || errno) {
		invalid:
			weprintf(_("Invalid value of `%s' option: `%s'."), key, value);
			return -1;
		}
		if (*end == '.') {
			if (!state->multiple_partitions || num > (uintmax_t)SSIZE_MAX)
				goto invalid;
			state->selected_crtcs[state->ncrtcs].partition = (ssize_t)num;
			p = &end[1];
			num = strtoumax(p, (void *)&end, 10);
			state->selected_crtcs[state->ncrtcs].crtc = (size_t)num;
			if (num > (uintmax_t)SIZE_MAX || (*end && *end != ',') || !isdigit(*p) || errno)
				goto invalid;
		}
		end = &end[*end == ','];
		state->ncrtcs++;
	}

	return 0;
}


int
direct_set_edids(GAMMA_STATE *state, const char *key, const char *value)
{
	const char *end, *p;
	size_t count, len;

	/* Check previously unspecified */
	if (state->nedids) {
		weprintf(_("`%s' option specified multiple times, using last selection."), key);
		while (state->nedids)
			free(state->selected_edids[--state->nedids]);
		free(state->selected_edids);
		state->selected_edids = NULL;
		state->nedids = 0;
	}

	/* Get number count */
	for (p = value, count = 1; *p; p++)
		if (*p == ',')
			count++;
	state->selected_edids = ecalloc(count, sizeof(*state->selected_edids));

	/* Split */
	for (p = value;; p = end) {
		end = strchr(p, ',');
		if (!end) {
			state->selected_edids[state->nedids++] = estrdup(p);
			break;
		}
		len = (size_t)(end++ - p);
		state->selected_edids[state->nedids] = emalloc(len + 1U);
		memcpy(state->selected_edids[state->nedids], p, len);
		state->selected_edids[state->nedids][len] = '\0';
		state->nedids++;
	}

	return 0;
}


int
direct_start(struct gamma_state *state)
{
	struct {
		size_t partition;
		size_t crtc;
		char *edid;
	} *resolved_edids = NULL;
	struct libgamma_crtc_information crtc_info;
	struct libgamma_crtc_state crtc_state;
	size_t crtc_num_offset = 0;
	size_t crtcs_removed = 0;
	size_t nresolved_edids = 0;
	size_t i, j, k, num, part, count;
	int err;

	/* Connect to display server */
	if (state->multiple_sites) {
		if (state->site_name && !*state->site_name) {
			free(state->site_name);
			state->site_name = NULL;
		}
		err = libgamma_site_initialise(&state->site, state->method, state->site_name);
		state->site_name = NULL;
		if (err) {
			weprintf("libgamma_site_initialise %s %s: %s",
			         libgamma_const_of_method(state->method),
			         state->site_name ? state->site_name : "NULL",
			         libgamma_strerror(err));
			return -1;
		}
		state->connected = 1;
	}

	/* Allocate partition states */
	if (state->npartitions) {
		state->partitions = ecalloc(state->npartitions, sizeof(*state->partitions));
		for (i = 0; i < state->npartitions; i++) {
			state->partitions[i].state.partition = state->selected_partitions[i];
			state->partitions[i].state.data = NULL;
		}
		free(state->selected_partitions);
		state->selected_partitions = NULL;
	} else if (!state->site.partitions_available) {
		if (state->partitions_are_graphics_cards)
			weprintf(_("No graphics card found."));
		else
			weprintf(_("No X screen found."));
		return -1;
	} else {
		state->npartitions = state->site.partitions_available;
		state->partitions = ecalloc(state->npartitions, sizeof(*state->partitions));
		for (i = 0; i < state->npartitions; i++) {
			state->partitions[i].state.partition = i;
			state->partitions[i].state.data = NULL;
		}
	}

	/* Map the partition indices in CRTC numbers from indices of selected
	 * partitions, and allocate partitions specified via CRTC numbers */
	for (i = 0; i < state->ncrtcs; i++) {
		if (state->selected_crtcs[i].partition < 0)
			continue;
		for (j = 0; j < state->npartitions; j++) {
			if ((size_t)state->selected_crtcs[i].partition == state->partitions[j].state.partition) {
				state->selected_crtcs[i].partition = (ssize_t)j;
				break;
			}
		}
		if (j == state->npartitions) {
			state->partitions = realloc(state->partitions, (state->npartitions + 1U) * sizeof(*state->partitions));
			state->partitions[state->npartitions].state.partition = (size_t)state->selected_crtcs[i].partition;
			state->partitions[state->npartitions].state.data = NULL;
			state->npartitions++;
		}
	}

	/* Initialise partitions */
	for (i = 0; i < state->npartitions; i++) {
		num = state->partitions[i].state.partition;
		err = libgamma_partition_initialise(&state->partitions[i].state, &state->site, num);
		if (err) {
			weprintf("libgamma_partition_initialise %zu: %s", num, libgamma_strerror(err));
			return -1;
		}
		state->partitions[i].crtc_num_offset = crtc_num_offset;
		crtc_num_offset += state->partitions[i].state.crtcs_available;
	}

	/* Resolve EDIDs */
	if (state->nedids) {
		for (i = 0; i < state->npartitions; i++) {
			count = state->partitions[i].state.crtcs_available;
			if (!count)
				continue;
			resolved_edids = erealloc(resolved_edids, (nresolved_edids + count) + sizeof(*resolved_edids));
			for (j = 0; j < count; j++) {
				if (libgamma_crtc_initialise(&crtc_state, &state->partitions[i].state, j))
					continue;
				libgamma_get_crtc_information(&crtc_info, sizeof(crtc_info), &crtc_state, LIBGAMMA_CRTC_INFO_EDID);
				if (crtc_info.edid_error)
					goto next_crtc;
				resolved_edids[nresolved_edids].partition = i;
				resolved_edids[nresolved_edids].crtc = j;
				resolved_edids[nresolved_edids].edid = libgamma_behex_edid(crtc_info.edid, crtc_info.edid_length);
				if (!resolved_edids[nresolved_edids].edid)
					eprintf("libgamma_behex_edid:");
				nresolved_edids++;
			next_crtc:
				libgamma_crtc_information_destroy(&crtc_info);
				libgamma_crtc_destroy(&crtc_state);
			}
		}
		for (i = 0; i < state->nedids; i++) {
			if (!strcasecmp(state->selected_edids[i], "list")) {
				printf(_("Available outputs:\n"));
				for (i = 0; i < nresolved_edids; i++)
					printf("  %s\n", resolved_edids[i].edid);
				direct_free(state);
				exit(0);
			}
			for (j = 0; j < nresolved_edids; j++) {
				if (!strcasecmp(state->selected_edids[i], resolved_edids[i].edid)) {
					if (!state->multiple_partitions) {
						weprintf("Resolved output `%s' to CRTC %zu",
						         resolved_edids[i].edid, resolved_edids[i].crtc);
					} else if (state->partitions_are_graphics_cards) {
						weprintf("Resolved output `%s' to CRTC %zu on graphics card %zu",
						         resolved_edids[i].edid, resolved_edids[i].crtc,
						         resolved_edids[i].partition);
					} else {
						weprintf("Resolved output `%s' to CRTC %zu on X screen %zu",
						         resolved_edids[i].edid, resolved_edids[i].crtc,
						         resolved_edids[i].partition);
					}
					count = state->ncrtcs + 1U;
					state->selected_crtcs = erealloc(state->selected_crtcs, count * sizeof(*state->crtcs));
					state->selected_crtcs[state->ncrtcs].partition = (ssize_t)resolved_edids[i].partition;
					state->selected_crtcs[state->ncrtcs].crtc = resolved_edids[i].crtc;
					state->ncrtcs++;
					goto next_edid;
				}
			}
			weprintf(_("Output `%s' found."), state->selected_edids[i]);
		next_edid:
			free(state->selected_edids[i]);
			state->selected_edids[i] = NULL;
		}
		while (nresolved_edids)
			free(resolved_edids[--nresolved_edids].edid);
		free(resolved_edids);
		free(state->selected_edids);
		state->selected_edids = NULL;
	}

	/* Allocate CRTCs states and map to partition–CRTC pairs */
	if (state->ncrtcs) {
		state->crtcs = ecalloc(state->ncrtcs, sizeof(*state->crtcs));
		for (i = 0; i < state->ncrtcs; i++) {
			state->crtcs[i].state.data = NULL;
			state->crtcs[i].state.partition = NULL;
			if (state->selected_crtcs[i].partition >= 0)
				state->crtcs[i].state.partition = &state->partitions[state->selected_crtcs[i].partition].state;
			state->crtcs[i].state.crtc = state->selected_crtcs[i].crtc;
		}
		for (i = 0; i < state->ncrtcs; i++) {
			if (state->crtcs[i].state.partition)
				continue;
			for (j = 1; j < state->npartitions; j++)
				if (state->crtcs[i].state.crtc < state->partitions[j].crtc_num_offset)
					break;
			state->crtcs[i].state.partition = &state->partitions[j - 1U].state;
			state->crtcs[i].state.crtc -= state->partitions[j - 1U].crtc_num_offset;
		}
	} else if (!crtc_num_offset) {
		weprintf(_("No CRTCs found."));
		return -1;
	} else {
		state->ncrtcs = crtc_num_offset;
		state->crtcs = ecalloc(state->ncrtcs, sizeof(*state->crtcs));
		for (j = 0, i = 0; j < state->npartitions; j++) {
			for (k = 0; k < state->partitions[j].state.crtcs_available; k++, i++) {
				state->crtcs[i].state.data = NULL;
				state->crtcs[i].state.partition = &state->partitions[j].state;
				state->crtcs[i].state.crtc = k;
			}
		}
	}

	/* Initialise CRTCs and fetch original gamma adjustments */
	for (i = 0; i < state->ncrtcs; i++) {
		/* Get CRTC */
		part = state->crtcs[i].state.partition->partition;
		num = state->crtcs[i].state.crtc;
		err = libgamma_crtc_initialise(&state->crtcs[i].state, state->crtcs[i].state.partition, num);
		if (err) {
			weprintf("libgamma_crtc_initialise %zu %zu: %s", part, num, libgamma_strerror(err));
			return -1;
		}

		/* Get gamma ramp inforamtion for CRTC */
		libgamma_get_crtc_information(&crtc_info, sizeof(crtc_info), &state->crtcs[i].state,
		                              LIBGAMMA_CRTC_INFO_GAMMA_SIZE |
		                              LIBGAMMA_CRTC_INFO_GAMMA_DEPTH |
		                              LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT);
		if (crtc_info.gamma_size_error || crtc_info.gamma_depth_error) {
			libgamma_crtc_destroy(&state->crtcs[i].state);
			state->crtcs[i].state.data = NULL;
			crtcs_removed++;
			goto no_gamma_support;
		}
		if (!crtc_info.gamma_support_error && crtc_info.gamma_support == LIBGAMMA_NO) {
		no_gamma_support:
			if (state->multiple_crtcs) {
				if (!state->multiple_partitions)
					weprintf(_("Adjustments are not supported on CRTC %zu."), num);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Adjustments are not supported on CRTC %zu on graphics card %zu."), num, part);
				else
					weprintf(_("Adjustments are not supported on CRTC %zu on X screen %zu."), num, part);
			} else {
				if (!state->multiple_partitions)
					weprintf(_("Adjustments are not supported."));
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Adjustments are not supported on graphics card %zu."), part);
				else
					weprintf(_("Adjustments are not supported on X screen %zu."), part);
			}
			if (!state->crtcs[i].state.data)
				goto next;
		}

		/* Initialise and fetch gamma adjustments */
		state->crtcs[i].gamma_ramps.size.red   = crtc_info.red_gamma_size;
		state->crtcs[i].gamma_ramps.size.green = crtc_info.green_gamma_size;
		state->crtcs[i].gamma_ramps.size.blue  = crtc_info.blue_gamma_size;
		state->crtcs[i].saved_gamma_ramps.size.red   = crtc_info.red_gamma_size;
		state->crtcs[i].saved_gamma_ramps.size.green = crtc_info.green_gamma_size;
		state->crtcs[i].saved_gamma_ramps.size.blue  = crtc_info.blue_gamma_size;
		switch (crtc_info.gamma_depth) {
#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
		case DEPTH:\
			if (libgamma_gamma_##RAMPS##_initialise(&state->crtcs[i].gamma_ramps.RAMPS) ||\
			    libgamma_gamma_##RAMPS##_initialise(&state->crtcs[i].saved_gamma_ramps.RAMPS))\
				eprintf("libgamma_gamma_"#RAMPS"_initialise:");\
			j = 0;\
			do {\
				err = libgamma_crtc_get_gamma_##RAMPS(&state->crtcs[i].state,\
				                                      &state->crtcs[i].saved_gamma_ramps.RAMPS);\
			} while (err && j++ < 10);\
			if (!err) {\
				state->crtcs[i].gamma_depth = DEPTH;\
				break;\
			}\
			if (state->multiple_crtcs) {\
				if (!state->multiple_partitions)\
					weprintf(_("Could not get current adjustments for CRTC %zu."), num);\
				else if (state->partitions_are_graphics_cards)\
					weprintf(_("Could not get current adjustments for CRTC %zu on graphics card %zu."),\
					         num, part);\
				else\
					weprintf(_("Could not get current adjustments for CRTC %zu on X screen %zu."), num, part);\
			} else {\
				if (!state->multiple_partitions)\
					weprintf(_("Could not get current adjustments."));\
				else if (state->partitions_are_graphics_cards)\
					weprintf(_("Could not get current adjustments for graphics card %zu."), part);\
				else\
					weprintf(_("Could not get current adjustments for X screen %zu."), part);\
			}\
			libgamma_gamma_##RAMPS##_destroy(&state->crtcs[i].gamma_ramps.RAMPS);\
			libgamma_gamma_##RAMPS##_destroy(&state->crtcs[i].saved_gamma_ramps.RAMPS);\
			libgamma_crtc_destroy(&state->crtcs[i].state);\
			state->crtcs[i].state.data = NULL;\
			crtcs_removed++;\
			goto next

			LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X

		default:
			if (state->multiple_crtcs) {
				if (!state->multiple_partitions)
					weprintf(_("Unsupported gamma ramp type on CRTC %zu."), num);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unsupported gamma ramp type on CRTC %zu on graphics card %zu."), num, part);
				else
					weprintf(_("Unsupported gamma ramp type on CRTC %zu on X screen %zu."), num, part);
			} else {
				if (!state->multiple_partitions)
					weprintf(_("Unsupported gamma ramp type."));
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unsupported gamma ramp type on graphics card %zu."), part);
				else
					weprintf(_("Unsupported gamma ramp type on X screen %zu."), part);
			}
			return -1;
		}

	next:
		libgamma_crtc_information_destroy(&crtc_info);
	}

	/* Unlist removed CRTCs */
	if (crtcs_removed) {
		num = state->ncrtcs - crtcs_removed;
		for (i = j = 0; crtcs_removed; i++, j++)
			if (!state->crtcs[i].state.data)
				break;
		for (; crtcs_removed; i++) {
			if (state->crtcs[i].state.data)
				crtcs_removed--;
			else
				memmove(&state->crtcs[j++], &state->crtcs[i], sizeof(*state->crtcs));
		}
		memmove(&state->crtcs[j], &state->crtcs[i], (state->ncrtcs - i) * sizeof(*state->crtcs));
		state->ncrtcs = num;
	}
	if (!state->ncrtcs) {
		weprintf(_("No usable CRTCs available."));
		return -1;
	}

	free(state->selected_crtcs);
	state->selected_crtcs = NULL;
	return 0;
}


int
direct_apply(struct gamma_state *state, const struct colour_setting *setting, int preserve)
{
	size_t i, err_count = 0, crtc, part;
	const char *errstr;
	int err;

	for (i = 0; i < state->ncrtcs; i++) {
		switch (state->crtcs[i].gamma_depth) {
#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
		case DEPTH:\
			fill_ramps_##SUFFIX(state->crtcs[i].gamma_ramps.RAMPS.red,\
			                    state->crtcs[i].gamma_ramps.RAMPS.green,\
			                    state->crtcs[i].gamma_ramps.RAMPS.blue,\
			                    preserve ? state->crtcs[i].saved_gamma_ramps.RAMPS.red : NULL,\
			                    preserve ? state->crtcs[i].saved_gamma_ramps.RAMPS.green : NULL,\
			                    preserve ? state->crtcs[i].saved_gamma_ramps.RAMPS.blue : NULL,\
			                    state->crtcs[i].gamma_ramps.size.red,\
			                    state->crtcs[i].gamma_ramps.size.green,\
			                    state->crtcs[i].gamma_ramps.size.blue,\
			                    setting);\
			err = libgamma_crtc_set_gamma_##RAMPS(&state->crtcs[i].state, &state->crtcs[i].gamma_ramps.RAMPS);\
			break

			LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X

		default:
			err_count++;
			err = 0;
			break;
		}
		
		if (err) {
			err_count++;
			crtc = state->crtcs[i].state.crtc;
			part = state->crtcs[i].state.partition->partition;
			errstr = libgamma_strerror(err);
			if (state->multiple_crtcs) {
				if (!state->multiple_partitions)
					weprintf(_("Unable to set adjustments for CRTC %zu: %s."), crtc, errstr);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unable to set adjustments for CRTC %zu on graphics card %zu: %s."),
					         crtc, part, errstr);
				else
					weprintf(_("Unable to set adjustments for CRTC %zu on X screen %zu: %s."),
					         crtc, part, errstr);
			} else {
				if (!state->multiple_partitions)
					weprintf(_("Unable to set adjustments: %s."), errstr);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unable to set adjustments for graphics card %zu: %s."), part, errstr);
				else
					weprintf(_("Unable to set adjustments for X screen %zu: %s."), part, errstr);
			}
		}
	}

	return err_count == state->ncrtcs ? -1 : 0;
}


void
direct_restore(struct gamma_state *state)
{
	size_t i, crtc, part;
	const char *errstr;
	int err;

	for (i = 0; i < state->ncrtcs; i++) {
		switch (state->crtcs[i].gamma_depth) {
#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
		case DEPTH:\
			err = libgamma_crtc_set_gamma_##RAMPS(&state->crtcs[i].state, &state->crtcs[i].saved_gamma_ramps.RAMPS);\
			break

			LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X

		default:
			err = 0;
			break;
		}

		if (err) {
			crtc = state->crtcs[i].state.crtc;
			part = state->crtcs[i].state.partition->partition;
			errstr = libgamma_strerror(err);
			if (state->multiple_crtcs) {
				if (!state->multiple_partitions)
					weprintf(_("Unable to restore adjustments for CRTC %zu: %s."), crtc, errstr);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unable to restore adjustments for CRTC %zu on graphics card %zu: %s."),
					         crtc, part, errstr);
				else
					weprintf(_("Unable to restore adjustments for CRTC %zu on X screen %zu: %s."),
					         crtc, part, errstr);
			} else {
				if (!state->multiple_partitions)
					weprintf(_("Unable to restore adjustments: %s."), errstr);
				else if (state->partitions_are_graphics_cards)
					weprintf(_("Unable to restore adjustments for graphics card %zu: %s."), part, errstr);
				else
					weprintf(_("Unable to restore adjustments for X screen %zu: %s."), part, errstr);
			}
		}
	}
}


void
direct_free(struct gamma_state *state)
{
	size_t i;
	if (state->crtcs) {
		for (i = 0; i < state->ncrtcs; i++) {
			switch (state->crtcs[i].gamma_depth) {
#define X(SUFFIX, RAMPS, TYPE, MAX, DEPTH)\
			case DEPTH:\
				libgamma_gamma_##RAMPS##_destroy(&state->crtcs[i].gamma_ramps.RAMPS);\
				libgamma_gamma_##RAMPS##_destroy(&state->crtcs[i].saved_gamma_ramps.RAMPS);\
				break
				LIST_RAMPS_STOP_VALUE_TYPES(X, ;);
#undef X
			default:
			case 0: /* not initialised */
				break;
			}
			if (state->crtcs[i].state.data)
				libgamma_crtc_destroy(&state->crtcs[i].state);
		}
		free(state->crtcs);
	}
	if (state->partitions) {
		for (i = 0; i < state->npartitions; i++)
			if (state->partitions[i].state.data)
				libgamma_partition_destroy(&state->partitions[i].state);
		free(state->partitions);
	}
	free(state->selected_partitions);
	free(state->selected_crtcs);
	if (state->selected_edids) {
		for (i = 0; i < state->nedids; i++)
			free(state->selected_edids[i]);
		free(state->selected_edids);
	}
	if (state->connected)
		libgamma_site_destroy(&state->site);
	free(state->site_name);
	free(state);
}