/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ---------------------------------------------------------------------
 *
 * Portions of this file were modified and ported to Vala by Victor Vintorez in 2026.
 * These modifications are licensed under the European Union Public License (EUPL),
 * Version 1.2 or later. You may obtain a copy of the License at:
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 */

using GLib;
using MaterialColorUtilities;

public class ColorUtilsTest : McuTestCase {
	public ColorUtilsTest () {
		base ("color_utils");
		add_test ("range_integrity", test_range_integrity);
		add_test ("argb_from_rgb_returns_correct_value_for_black", test_argb_from_rgb_returns_correct_value_for_black);
		add_test ("argb_from_rgb_returns_correct_value_for_white", test_argb_from_rgb_returns_correct_value_for_white);
		add_test ("argb_from_rgb_returns_correct_value_for_random_color", test_argb_from_rgb_returns_correct_value_for_random_color);
		add_test ("y_to_lstar_to_y", test_y_to_lstar_to_y);
		add_test ("lstar_to_y_to_lstar", test_lstar_to_y_to_lstar);
		add_test ("lstar_from_y", test_lstar_from_y);
		add_test ("y_from_lstar", test_y_from_lstar);
		add_test ("y_continuity", test_y_continuity);
		add_test ("rgb_to_xyz_to_rgb", test_rgb_to_xyz_to_rgb);
		add_test ("rgb_to_lab_to_rgb", test_rgb_to_lab_to_rgb);
		add_test ("rgb_to_lstar_to_rgb", test_rgb_to_lstar_to_rgb);
		add_test ("rgb_to_lstar_to_y_commutes", test_rgb_to_lstar_to_y_commutes);
		add_test ("lstar_to_rgb_to_y_commutes", test_lstar_to_rgb_to_y_commutes);
		add_test ("linearize_delinearize", test_linearize_delinearize);
	}

	public override void set_up () {}

	public void test_range_integrity () {
		var range = create_range (3, 9999, 1234);
		for (int i = 0; i < 1234; i++) {
			McuAssert.doubles_equal (range[i], 3 + 8.1070559611 * (double) i, 1e-5);
		}
	}

	public void test_argb_from_rgb_returns_correct_value_for_black () {
		var answer = ColorUtils.argb_from_rgb (0, 0, 0);

		McuAssert.int_and_uint_equal (answer, 0xff000000u);
		McuAssert.ints_equal (answer, (int) 4278190080);
	}

	public void test_argb_from_rgb_returns_correct_value_for_white () {
		var answer = ColorUtils.argb_from_rgb (255, 255, 255);

		McuAssert.int_and_uint_equal (answer, 0xffffffffu);
		McuAssert.ints_equal (answer, (int) 4294967295u);
	}

	public void test_argb_from_rgb_returns_correct_value_for_random_color () {
		var answer = ColorUtils.argb_from_rgb (50, 150, 250);

		McuAssert.int_and_uint_equal (answer, 0xff3296fau);
		McuAssert.ints_equal (answer, (int) 4281505530u);
	}

	public void test_y_to_lstar_to_y () {
		foreach (var y in create_range (0.0, 100.0, 1001)) {
			var lstar = ColorUtils.lstar_from_y (y);
			var answer = ColorUtils.y_from_lstar (lstar);

			McuAssert.doubles_equal (answer, y, 1e-5);
		}
	}

	public void test_lstar_to_y_to_lstar () {
		foreach (var lstar in create_range (0.0, 100.0, 1001)) {
			var y = ColorUtils.y_from_lstar (lstar);
			var answer = ColorUtils.lstar_from_y (y);

			McuAssert.doubles_equal (answer, lstar, 1e-5);
		}
	}

	public void test_lstar_from_y () {
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.0), 0.0, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.1), 0.9032962, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.2), 1.8065925, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.3), 2.7098888, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.4), 3.6131851, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.5), 4.5164814, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (0.8856451), 8.0, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (1.0), 8.9914424, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (2.0), 15.4872443, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (3.0), 20.0438970, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (4.0), 23.6714419, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (5.0), 26.7347653, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (10.0), 37.8424304, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (15.0), 45.6341970, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (20.0), 51.8372115, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (25.0), 57.0754208, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (30.0), 61.6542222, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (40.0), 69.4695307, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (50.0), 76.0692610, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (60.0), 81.8381891, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (70.0), 86.9968642, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (80.0), 91.6848609, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (90.0), 95.9967686, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (95.0), 98.0335184, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (99.0), 99.6120372, 1e-5);
		McuAssert.doubles_equal (ColorUtils.lstar_from_y (100.0), 100.0, 1e-5);
	}

	public void test_y_from_lstar () {
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.0), 0.0, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.1), 0.0110705, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.2), 0.0221411, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.3), 0.0332116, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.4), 0.0442822, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (0.5), 0.0553528, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (1.0), 0.1107056, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (2.0), 0.2214112, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (3.0), 0.3321169, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (4.0), 0.4428225, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (5.0), 0.5535282, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (8.0), 0.8856451, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (10.0), 1.1260199, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (15.0), 1.9085832, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (20.0), 2.9890524, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (25.0), 4.4154767, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (30.0), 6.2359055, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (40.0), 11.2509737, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (50.0), 18.4186518, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (60.0), 28.1233342, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (70.0), 40.7494157, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (80.0), 56.6812907, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (90.0), 76.3033539, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (95.0), 87.6183294, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (99.0), 97.4360239, 1e-5);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (100.0), 100.0, 1e-5);
	}

	public void test_y_continuity () {
		var epsilon = 1e-6;
		var delta = 1e-8;
		var left = 8.0 - delta;
		var mid = 8.0;
		var right = 8.0 + delta;

		McuAssert.doubles_equal (ColorUtils.y_from_lstar (left), ColorUtils.y_from_lstar (mid), epsilon);
		McuAssert.doubles_equal (ColorUtils.y_from_lstar (mid), ColorUtils.y_from_lstar (right), epsilon);
	}

	public void test_rgb_to_xyz_to_rgb () {
		var range = create_rgb_range ();

		foreach (var r in range) {
			foreach (var g in range) {
				foreach (var b in range) {
					var argb = ColorUtils.argb_from_rgb (r, g, b);
					var xyz = ColorUtils.xyz_from_argb (argb);
					var answer = ColorUtils.argb_from_xyz (xyz[0], xyz[1], xyz[2]);

					McuAssert.doubles_equal (ColorUtils.red_from_argb (answer), (double) r, 1.5);
					McuAssert.doubles_equal (ColorUtils.green_from_argb (answer), (double) g, 1.5);
					McuAssert.doubles_equal (ColorUtils.blue_from_argb (answer), (double) b, 1.5);
				}
			}
		}
	}

	public void test_rgb_to_lab_to_rgb () {
		var range = create_rgb_range ();

		foreach (var r in range) {
			foreach (var g in range) {
				foreach (var b in range) {
					var argb = ColorUtils.argb_from_rgb (r, g, b);
					var lab = ColorUtils.lab_from_argb (argb);
					var answer = ColorUtils.argb_from_lab (lab[0], lab[1], lab[2]);

					McuAssert.doubles_equal (ColorUtils.red_from_argb (answer), (double) r, 1.5);
					McuAssert.doubles_equal (ColorUtils.green_from_argb (answer), (double) g, 1.5);
					McuAssert.doubles_equal (ColorUtils.blue_from_argb (answer), (double) b, 1.5);
				}
			}
		}
	}

	public void test_rgb_to_lstar_to_rgb () {
		var range = create_full_rgb_range ();

		foreach (var component in range) {
			var argb = ColorUtils.argb_from_rgb (component, component, component);
			var lstar = ColorUtils.lstar_from_argb (argb);
			var answer = ColorUtils.argb_from_lstar (lstar);

			McuAssert.ints_equal (answer, argb);
		}
	}

	public void test_rgb_to_lstar_to_y_commutes () {
		var range = create_rgb_range ();

		foreach (var r in range) {
			foreach (var g in range) {
				foreach (var b in range) {
					var argb = ColorUtils.argb_from_rgb (r, g, b);
					var lstar = ColorUtils.lstar_from_argb (argb);
					var y_from_lstar = ColorUtils.y_from_lstar (lstar);
					var y_from_argb = ColorUtils.xyz_from_argb (argb)[1];

					McuAssert.doubles_equal (y_from_lstar, y_from_argb, 1e-5);
				}
			}
		}
	}

	public void test_lstar_to_rgb_to_y_commutes () {
		foreach (var lstar in create_range (0.0, 100.0, 1001)) {
			var argb = ColorUtils.argb_from_lstar (lstar);
			var y_from_argb = ColorUtils.xyz_from_argb (argb)[1];
			var y_from_lstar = ColorUtils.y_from_lstar (lstar);

			McuAssert.doubles_equal (y_from_lstar, y_from_argb, 1);
		}
	}

	public void test_linearize_delinearize () {
		var range = create_full_rgb_range ();

		foreach (var component in range) {
			var linearized = ColorUtils.linearized (component);
			var delinearized = ColorUtils.delinearized (linearized);

			McuAssert.doubles_equal (delinearized, (double) component);
		}
	}

	public override void tear_down () {}

	private double[] create_range (double start, double stop, int case_count) {
		var step_size = (stop - start) / (double) (case_count - 1);
		var result = new double[case_count];
		for (int i = 0; i < case_count; i++) {
			result[i] = start + step_size * (double) i;
		}
		return result;
	}

	private int[] create_rgb_range () {
		var values = create_range (0.0, 255.0, 8);
		var result = new int[values.length];
		for (int i = 0; i < values.length; i++) {
			result[i] = (int) Math.round (values[i]);
		}
		return result;
	}

	private int[] create_full_rgb_range () {
		var result = new int[256];
		for (int i = 0; i < 256; i++) {
			result[i] = i;
		}
		return result;
	}
}
