/* gamma-w32gdi.c -- Windows GDI gamma adjustment 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) 2010-2017  Jon Lund Steffensen <jonlst@gmail.com>
 * Copyright (c) 2025       Mattias Andrée <m@maandree.se>
 */
#ifndef WINVER
# define WINVER  0x0500
#endif
#include "common.h"

#include <wingdi.h>

#define GAMMA_RAMP_SIZE  256
#define MAX_ATTEMPTS  10


struct gamma_state {
	WORD *saved_ramps;
};


static int
w32gdi_init(struct gamma_state **state)
{
	*state = emalloc(sizeof(**state));
	(*state)->saved_ramps = NULL;
	return 0;
}

static int
w32gdi_start(struct gamma_state *state, program_mode_t mode)
{
	HDC hDC;
	int cmcap;

	/* Open device context */
	hDC = GetDC(NULL);
	if (!hDC) {
		weprintf(_("Unable to open device context."));
		return -1;
	}

	/* Check support for gamma ramps */
	cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
	if (cmcap != CM_GAMMA_RAMP) {
		weprintf(_("Display device does not support gamma ramps."));
		return -1;
	}

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

	/* Save current gamma ramps so we can restore them at program exit */
	if (!GetDeviceGammaRamp(hDC, state->saved_ramps)) {
		weprintf(_("Unable to save current gamma ramp."));
		ReleaseDC(NULL, hDC);
		return -1;
	}

	/* Release device context */
	ReleaseDC(NULL, hDC);

	return 0;
}

static void
w32gdi_free(struct gamma_state *state)
{
	free(state->saved_ramps);
	free(state);
}


static void
w32gdi_print_help(FILE *f)
{
	fputs(_("Adjust gamma ramps with the Windows GDI.\n"), f);
	fputs("\n", f);
}

static int
w32gdi_set_option(struct gamma_state *state, const char *key, const char *value)
{
	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
w32gdi_restore(struct gamma_state *state)
{
	HDC hDC;
	int i;

	/* Open device context */
	hDC = GetDC(NULL);
	if (!hDC) {
		weprintf(_("Unable to open device context."));
		return;
	}

	/* Restore gamma ramps */
	for (i = 0; i < MAX_ATTEMPTS; i++) {
		/* We retry a few times before giving up because some
		   buggy drivers fail on the first invocation of
		   SetDeviceGammaRamp just to succeed on the second. */
		if (SetDeviceGammaRamp(hDC, state->saved_ramps))
			goto done;
	}
	weprintf(_("Unable to restore gamma ramps."));

done:
	/* Release device context */
	ReleaseDC(NULL, hDC);
}

static int
w32gdi_set_temperature(
	struct gamma_state *state, const color_setting_t *setting, int preserve)
{
	WORD *gamma_ramps, *gamma_r, *gamma_b, *gamma_g, value;
	HDC hDC;
	int i;

	/* Open device context */
	hDC = GetDC(NULL);
	if (!hDC) {
		weprintf(_("Unable to open device context."));
		return -1;
	}

	/* Create new gamma ramps */
	gamma_ramps = emalloc(3 * GAMMA_RAMP_SIZE * sizeof(WORD));

	gamma_r = &gamma_ramps[0 * GAMMA_RAMP_SIZE];
	gamma_g = &gamma_ramps[1 * GAMMA_RAMP_SIZE];
	gamma_b = &gamma_ramps[2 * GAMMA_RAMP_SIZE];

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

	colorramp_fill_u16(gamma_r, gamma_g, gamma_b, GAMMA_RAMP_SIZE,
	                   GAMMA_RAMP_SIZE, GAMMA_RAMP_SIZE, setting);

	/* Set new gamma ramps */
	for (i = 0; i < MAX_ATTEMPTS; i++) {
		/* We retry a few times before giving up because some
		   buggy drivers fail on the first invocation of
		   SetDeviceGammaRamp just to succeed on the second. */
		if (SetDeviceGammaRamp(hDC, gamma_ramps))
			goto done;
	}
	weprintf(_("Unable to set gamma ramps."));
	free(gamma_ramps);
	ReleaseDC(NULL, hDC);
	return -1;

done:
	free(gamma_ramps);

	/* Release device context */
	ReleaseDC(NULL, hDC);

	return 0;
}


const struct gamma_method w32gdi_gamma_method = {
	"wingdi", 1,
	&w32gdi_init,
	&w32gdi_start,
	&w32gdi_free,
	&w32gdi_print_help,
	&w32gdi_set_option,
	&w32gdi_restore,
	&w32gdi_set_temperature
};