/*-
 * 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 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_create(struct gamma_state **state_out)
{
	*state_out = emalloc(sizeof(**state_out));
	(*state_out)->saved_ramps = NULL;
	return 0;
}


static int
w32gdi_start(struct gamma_state *state)
{
	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_apply(struct gamma_state *state, const colour_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;
		}
	}

	fill_ramps_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 = GAMMA_METHOD_INIT("winfdi", 1, 0, w32gdi);