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

#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>


struct gamma_state {
	Display *display;
	int screen_num;
	int ramp_size;
	uint16_t *saved_ramps;
};


static int
vidmode_create(struct gamma_state **state_out)
{
	struct gamma_state *state;

	state = *state_out = emalloc(sizeof(**state_out));
	state->screen_num = -1;
	state->saved_ramps = NULL;

	state->display = XOpenDisplay(NULL);
	if (!state->display) {
		weprintf(_("X request failed: %s"), "XOpenDisplay");
		return -1;
	}

	return 0;
}


static int
vidmode_start(struct gamma_state *state)
{
	int r;
	int screen_num = state->screen_num;
	int major, minor;
	uint16_t *gamma_r, *gamma_g, *gamma_b;

	if (screen_num < 0)
		screen_num = DefaultScreen(state->display);
	state->screen_num = screen_num;

	/* Query extension version */
	r = XF86VidModeQueryVersion(state->display, &major, &minor);
	if (!r) {
		weprintf(_("X request failed: %s"), "XF86VidModeQueryVersion");
		return -1;
	}

	/* Request size of gamma ramps */
	r = XF86VidModeGetGammaRampSize(state->display, state->screen_num, &state->ramp_size);
	if (!r) {
		weprintf(_("X request failed: %s"), "XF86VidModeGetGammaRampSize");
		return -1;
	}

	if (!state->ramp_size) {
		weprintf(_("Gamma ramp size too small: %zu"), (size_t)state->ramp_size);
		return -1;
	}

	/* Allocate space for saved gamma ramps */
	state->saved_ramps = emalloc(3 * state->ramp_size * sizeof(uint16_t));

	gamma_r = &state->saved_ramps[0 * state->ramp_size];
	gamma_g = &state->saved_ramps[1 * state->ramp_size];
	gamma_b = &state->saved_ramps[2 * state->ramp_size];

	/* Save current gamma ramps so we can restore them at program exit. */
	r = XF86VidModeGetGammaRamp(state->display, state->screen_num,
	                            state->ramp_size, gamma_r, gamma_g, gamma_b);
	if (!r) {
		weprintf(_("X request failed: %s"), "XF86VidModeGetGammaRamp");
		return -1;
	}

	return 0;
}


static void
vidmode_free(struct gamma_state *state)
{
	free(state->saved_ramps);
	XCloseDisplay(state->display);
	free(state);
}


static void
vidmode_print_help(FILE *f)
{
	fputs(_("Adjust gamma ramps with the X VidMode extension.\n"), f);
	fputs("\n", f);

	/* TRANSLATORS: VidMode help output left column must not be translated */
	fputs(_("  screen=N\t\tX screen to apply adjustments to\n"), f);
	fputs("\n", f);
}


static int
vidmode_set_option(struct gamma_state *state, const char *key, const char *value)
{
	if (!strcasecmp(key, "screen")) {
		state->screen_num = atoi(value);
	} else if (!strcasecmp(key, "preserve")) {
		weprintf(_("Parameter `%s' is now always on; use the `%s' command-line option to disable."), key, "-P");
	} else {
		weprintf(_("Unknown method parameter: `%s'."), key);
		return -1;
	}

	return 0;
}


static void
vidmode_restore(struct gamma_state *state)
{
	uint16_t *gamma_r = &state->saved_ramps[0 * state->ramp_size];
	uint16_t *gamma_g = &state->saved_ramps[1 * state->ramp_size];
	uint16_t *gamma_b = &state->saved_ramps[2 * state->ramp_size];
	int r;

	/* Restore gamma ramps */
	r = XF86VidModeSetGammaRamp(state->display, state->screen_num,
	                            state->ramp_size, gamma_r, gamma_g, gamma_b);
	if (!r)
		weprintf(_("X request failed: %s"), "XF86VidModeSetGammaRamp");
}


static int
vidmode_apply(struct gamma_state *state, const struct colour_setting *setting, int preserve)
{
	uint16_t value, *gamma_ramps, *gamma_r, *gamma_g, *gamma_b;
	int r, i;

	/* Create new gamma ramps */
	gamma_ramps = emalloc(3 * state->ramp_size * sizeof(uint16_t));

	gamma_r = &gamma_ramps[0 * state->ramp_size];
	gamma_g = &gamma_ramps[1 * state->ramp_size];
	gamma_b = &gamma_ramps[2 * state->ramp_size];

	if (preserve) {
		/* Initialize gamma ramps from saved state */
		memcpy(gamma_ramps, state->saved_ramps, 3 * state->ramp_size * sizeof(uint16_t));
	} else {
		/* Initialize gamma ramps to pure state */
		for (i = 0; i < state->ramp_size; i++) {
			value = (double)i / (state->ramp_size - 1) * UINT16_MAX;
			gamma_r[i] = value;
			gamma_g[i] = value;
			gamma_b[i] = value;
		}
	}

	fill_ramps_u16(gamma_r, gamma_g, gamma_b, state->ramp_size, state->ramp_size, state->ramp_size, setting);

	/* Set new gamma ramps */
	r = XF86VidModeSetGammaRamp(state->display, state->screen_num,
				    state->ramp_size, gamma_r, gamma_g, gamma_b);
	if (!r) {
		weprintf(_("X request failed: %s"), "XF86VidModeSetGammaRamp");
		free(gamma_ramps);
		return -1;
	}

	free(gamma_ramps);

	return 0;
}


const struct gamma_method vidmode_gamma_method = GAMMA_METHOD_INIT("vidmode", 1, 0, vidmode);