/*
 * Copyright 2025 Collabora, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <math.h>

#include "weston-test-client-helper.h"
#include "weston-test-assert.h"
#include "libweston/color.h"
#include "shared/helpers.h"
#include <libweston/colorimetry.h>

float lut_ascendent[] =
	{0.0, 2.0, 3.0, 6.0, 9.0, 12.0, 15.0, 16.0, 20.0, 25.0};

float lut_descendent[] =
	{25.0, 20.0, 16.0, 15.0, 12.0, 9.0, 6.0, 3.0, 2.0, 0.0};

struct test_case {
	float *lut;
	uint32_t len_lut;
	float val;
	uint32_t index_A;
	uint32_t index_B;
};

struct test_case tests[] = {
	{
		/* Value at the extreme left. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 0.0f,
		.index_A = 0,
		.index_B = 1,
	},
	{
		/* Value at the extreme right. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 25.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Just a value that is present in the LUT. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 6.0f,
		.index_A = 2,
		.index_B = 3,
	},
	{
		/* Value not present on LUT. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 10.0f,
		.index_A = 4,
		.index_B = 5,
	},
	{
		/* Another value not present on LUT. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 1.0f,
		.index_A = 0,
		.index_B = 1,
	},
	{
		/* Another value not present on LUT. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 23.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Value that would be before the extreme left, but not present. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = -1.0f,
		.index_A = 0,
		.index_B = 1,
	},
	{
		/* Value that would be after the extreme right, but not present. */
		.lut = lut_ascendent,
		.len_lut = ARRAY_LENGTH(lut_ascendent),
		.val = 26.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Value at the extreme left. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 25.0f,
		.index_A = 0,
		.index_B = 1,
	},
	{
		/* Value at the extreme right. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 0.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Just a value that is present in the LUT. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 6.0f,
		.index_A = 5,
		.index_B = 6,
	},
	{
		/* Value not present on LUT. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 10.0f,
		.index_A = 4,
		.index_B = 5,
	},
	{
		/* Another value not present on LUT. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 1.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Another value not present on LUT. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 23.0f,
		.index_A = 0,
		.index_B = 1,
	},
	{
		/* Value that would be before the extreme right, but not present. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = -1.0f,
		.index_A = 8,
		.index_B = 9,
	},
	{
		/* Value that would be after the extreme left, but not present. */
		.lut = lut_descendent,
		.len_lut = ARRAY_LENGTH(lut_descendent),
		.val = 26.0f,
		.index_A = 0,
		.index_B = 1,
	},
};

static enum test_result_code
find_neighbors_test(struct wet_testsuite_data *suite_data)
{
	struct weston_compositor *compositor = NULL;
	unsigned int i;
	uint32_t index_neigh_A, index_neigh_B;

	for (i = 0; i < ARRAY_LENGTH(tests); i++) {
		find_neighbors(compositor, tests[i].len_lut, tests[i].lut,
			       tests[i].val, &index_neigh_A, &index_neigh_B);

		test_assert_u32_eq(index_neigh_A, tests[i].index_A);
		test_assert_u32_eq(index_neigh_B, tests[i].index_B);
	}

	return RESULT_OK;
}

static float
sample_power_22(float input)
{
	return powf(input, 2.2f);
}

static float
sample_power_22_complement(float input)
{
	return 1.0f - powf(input, 2.2f);
}

static enum test_result_code
test_inverse_lut_with_curve(float (*sample_fn)(float))
{
	const uint32_t len_lut = 1024;
	struct weston_compositor *compositor = NULL;
	float lut[len_lut];
	uint32_t divider = len_lut - 1;
	float input, output;
	unsigned int i;

	/* Create 1D LUT using function sample_fn. */
	for (i = 0; i < len_lut; i++) {
		input = (float)i / divider;
		lut[i] = sample_fn(input);
	}

	/**
	 * Sample data (dividing i by a prime number, 79) not well behaved on
	 * purpose. Sample that with the LUT, then its inverse, and ensure that
	 * the result is the same as evaluating with an identity curve.
	 */
	for (i = 0; i < 80; i++) {
		input = i / 79.0f;
		output = sample_fn(input);
		output = weston_inverse_evaluate_lut1d(compositor,
						       len_lut, lut,
						       output);
		test_assert_f32_lt(fabs(input - output), 1e-3);
	}

	return RESULT_OK;
}

static enum test_result_code
inverse_lut(struct wet_testsuite_data *suite_data)
{
	return test_inverse_lut_with_curve(sample_power_22);
}

static enum test_result_code
inverse_lut_descendant(struct wet_testsuite_data *suite_data)
{
	return test_inverse_lut_with_curve(sample_power_22_complement);
}

struct npm_test_case {
	struct weston_color_gamut gm;
	struct weston_mat3f expected;
};

/*
 * The reference data is from https://www.colour-science.org/ Python library.
 * >>> import colour
 * We use the "Derived NPM" as the expected matrix.
 */
static const struct npm_test_case npm_test_cases[] = {
	{
		/* >>> print(colour.RGB_COLOURSPACES['sRGB']) */
		.gm = {
			.primary = { { 0.64, 0.33 }, /* RGB order */
				     { 0.30, 0.60 },
				     { 0.15, 0.06 },
			},
			.white_point = { 0.3127, 0.3290 },
		},
		.expected = WESTON_MAT3F(
			0.4123908,   0.35758434,  0.18048079,
			0.21263901,  0.71516868,  0.07219232,
			0.01933082,  0.11919478,  0.95053215
		),
	},
	{
		/* >>> print(colour.RGB_COLOURSPACES['Adobe RGB (1998)']) */
		.gm = {
			.primary = { { 0.64, 0.33 }, /* RGB order */
				     { 0.21, 0.71 },
				     { 0.15, 0.06 },
			},
			.white_point = { 0.3127, 0.3290 },
		},
		.expected = WESTON_MAT3F(
			0.57666904,  0.18555824,  0.18822865,
			0.29734498,  0.62736357,  0.07529146,
			0.02703136,  0.07068885,  0.99133754
		),
	},
	{
		/* >>> print(colour.RGB_COLOURSPACES['ITU-R BT.2020']) */
		.gm = {
			.primary = { { 0.708, 0.292 }, /* RGB order */
				     { 0.170, 0.797 },
				     { 0.131, 0.046 },
			},
			.white_point = { 0.3127, 0.3290 },
		},
		.expected = WESTON_MAT3F(
			6.36958048e-01,   1.44616904e-01,   1.68880975e-01,
			2.62700212e-01,   6.77998072e-01,   5.93017165e-02,
			4.99410657e-17,   2.80726930e-02,   1.06098506e+00
		),
	},
	{
		/* >>> print(colour.RGB_COLOURSPACES['NTSC (1953)']) */
		.gm = {
			.primary = { { 0.67, 0.33 }, /* RGB order */
				     { 0.21, 0.71 },
				     { 0.14, 0.08 },
			},
			.white_point = { 0.31006, 0.31616 },
		},
		.expected = WESTON_MAT3F(
			 6.06863809e-01,   1.73507281e-01,   2.00334881e-01,
			 2.98903070e-01,   5.86619855e-01,   1.14477075e-01,
			-5.02801622e-17,   6.60980118e-02,   1.11615148e+00
		),
	},
};

/** Return the equivalence precision in bits
 *
 * Infinity norm of the residual is our measure.
 * See https://gitlab.freedesktop.org/pq/fourbyfour/-/blob/master/README.d/precision_testing.md
 */
static float
diff_precision(struct weston_mat3f M, struct weston_mat3f ref)
{
	struct weston_mat3f E = weston_m3f_sub_m3f(M, ref);
	return -log2f(weston_m3f_inf_norm(E));
}

/*
 * Test that weston_normalized_primary_matrix() produces known-good results
 * for NPM, an that the NPM⁻¹ is actually the inverse matrix.
 */
static enum test_result_code
npm(struct wet_testsuite_data *suite_data,
    const struct npm_test_case *t)
{
	const float precision_bits = 21;
	struct weston_mat3f npm;
	struct weston_mat3f npm_inv;
	struct weston_mat3f roundtrip;

	test_assert_true(weston_normalized_primary_matrix_init(&npm, &t->gm, WESTON_NPM_FORWARD));
	test_assert_f32_ge(diff_precision(npm, t->expected), precision_bits);

	test_assert_true(weston_normalized_primary_matrix_init(&npm_inv, &t->gm, WESTON_NPM_INVERSE));
	roundtrip = weston_m3f_mul_m3f(npm_inv, npm);
	test_assert_f32_ge(diff_precision(roundtrip, WESTON_MAT3F_IDENTITY), precision_bits);

	return RESULT_OK;
}

/* https://www.color.org/chadtag.xalter */
static enum test_result_code
bradform_adaptation_D65_D50(struct wet_testsuite_data *suite_data)
{
	const struct weston_CIExy D65 = { 0.3127, 0.3290 };
	const struct weston_CIExy D50 = { 0.3457, 0.3585 };
	const struct weston_mat3f ref = WESTON_MAT3F(
		1.04790738171017, 0.0229333845542104, -0.0502016347980104,
		0.0296059594177168, 0.990456039910785, -0.01707552919587,
		-0.00924679432678241, 0.0150626801401488, 0.751791232609078
	);
	struct weston_mat3f M;

	M = weston_bradford_adaptation(D65, D50);
	test_assert_f32_ge(diff_precision(M, ref), 13);

	return RESULT_OK;
}

DECLARE_TEST_LIST(
	TESTFN(find_neighbors_test),
	TESTFN(inverse_lut),
	TESTFN(inverse_lut_descendant),
	TESTFN_ARG(npm, npm_test_cases),
	TESTFN(bradform_adaptation_D65_D50),
);
