/* gamma-quartz.c -- Quartz (OSX) gamma adjustment
 * 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) 2014-2017  Jon Lund Steffensen <jonlst@gmail.com>
 * Copyright (c) 2025       Mattias Andrée <m@maandree.se>
 */
#include "common.h"

#include <ApplicationServices/ApplicationServices.h>


struct quartz_display_state {
	CGDirectDisplayID display;
	uint32_t ramp_size;
	float *saved_ramps;
};

struct gamma_state {
	struct quartz_display_state *displays;
	uint32_t display_count;
};


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

static int
quartz_start(struct gamma_state *state, program_mode_t mode)
{
	float *gamma_r, *gamma_g, *gamma_b;
	uint32_t i, display_count, ramp_size, sample_count;
	CGDirectDisplayID *displays, display;
	CGError error;

	/* Get display count */
	error = CGGetOnlineDisplayList(0, NULL, &display_count);
	if (error != kCGErrorSuccess)
		return -1;

	state->display_count = display_count;

	displays = emalloc(sizeof(CGDirectDisplayID) * display_count);

	/* Get list of displays */
	error = CGGetOnlineDisplayList(display_count, displays, &display_count);
	if (error != kCGErrorSuccess) {
		free(displays);
		return -1;
	}

	/* Allocate list of display state */
	state->displays = emalloc(display_count * sizeof(struct quartz_display_state));

	/* Copy display indentifiers to display state */
	for (i = 0; i < display_count; i++) {
		state->displays[i].display = displays[i];
		state->displays[i].saved_ramps = NULL;
	}

	free(displays);

	/* Save gamma ramps for all displays in display state */
	for (i = 0; i < display_count; i++) {
		display = state->displays[i].display;

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

		state->displays[i].ramp_size = ramp_size;

		/* Allocate space for saved ramps */
		state->displays[i].saved_ramps = emalloc(3 * ramp_size * sizeof(float));

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

		/* Copy the ramps to allocated space */
		error = CGGetDisplayTransferByTable(display, ramp_size, gamma_r, gamma_g, gamma_b, &sample_count);
		if (error != kCGErrorSuccess || sample_count != ramp_size) {
			weprintf(_("Unable to save current gamma ramp."));
			return -1;
		}
	}

	return 0;
}

static void
quartz_restore(struct gamma_state *state)
{
	CGDisplayRestoreColorSyncSettings();
}

static void
quartz_free(struct gamma_state *state)
{
	uint32_t i;
	if (state->displays)
		for (i = 0; i < state->display_count; i++)
			free(state->displays[i].saved_ramps);
	free(state->displays);
	free(state);
}

static void
quartz_print_help(FILE *f)
{
	fputs(_("Adjust gamma ramps on macOS using Quartz.\n"), f);
	fputs("\n", f);
}

static int
quartz_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
quartz_set_temperature_for_display(struct gamma_state *state, int display_index,
                                   const color_setting_t *setting, int preserve)
{
	float *gamma_ramps, *gamma_r, *gamma_g, *gamma_b, value;
	CGDirectDisplayID display = state->displays[display_index].display;
	uint32_t ramp_size = state->displays[display_index].ramp_size;
	CGError error;
	uint32_t i;

	/* Create new gamma ramps */
	gamma_ramps = emalloc(3 * ramp_size * sizeof(float));

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

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

	colorramp_fill_float(gamma_r, gamma_g, gamma_b, ramp_size, ramp_size, ramp_size, setting);

	error = CGSetDisplayTransferByTable(display, ramp_size, gamma_r, gamma_g, gamma_b);
	if (error != kCGErrorSuccess) {
		free(gamma_ramps);
		return;
	}

	free(gamma_ramps);
}

static int
quartz_set_temperature(
	struct gamma_state *state, const color_setting_t *setting, int preserve)
{
	uint32_t i;

	for (i = 0; i < state->display_count; i++) {
		quartz_set_temperature_for_display(
			state, i, setting, preserve);
	}

	return 0;
}


const struct gamma_method quartz_gamma_method = {
	"quartz", 1,
	&quartz_init,
	&quartz_start,
	&quartz_free,
	&quartz_print_help,
	&quartz_set_option,
	&quartz_restore,
	&quartz_set_temperature
};