/*
 * 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 HctTest : McuTestCase {
	private const int black = (int) 0xff000000u;
	private const int white = (int) 0xffffffffu;
	private const int red = (int) 0xffff0000u;
	private const int green = (int) 0xff00ff00u;
	private const int blue = (int) 0xff0000ffu;
	private const int midgray = (int) 0xff777777u;

	public HctTest() {
		base("hct");
		add_test("conversions_are_reflexive", test_conversions_are_reflexive);
		add_test("y_midgray", test_y_midgray);
		add_test("y_black", test_y_black);
		add_test("y_white", test_y_white);
		add_test("cam_red", test_cam_red);
		add_test("cam_green", test_cam_green);
		add_test("cam_blue", test_cam_blue);
		add_test("cam_black", test_cam_black);
		add_test("cam_white", test_cam_white);
		add_test("camut_map_red", test_camut_map_red);
		add_test("camut_map_green", test_camut_map_green);
		add_test("camut_map_blue", test_camut_map_blue);
		add_test("camut_map_white", test_camut_map_white);
		add_test("camut_map_midgray", test_camut_map_midgray);
		add_test("camut_map_black", test_camut_map_black);
		add_test("hct_returns_sufficiently_close_color", test_hct_returns_sufficiently_close_color);
		add_test("cam16_to_xyz_without_array", test_cam16_to_xyz_without_array);
		add_test("cam16_to_xyz_with_array", test_cam16_to_xyz_with_array);
		add_test("color_relativity_red_in_black", test_color_relativity_red_in_black);
		add_test("color_relativity_red_in_white", test_color_relativity_red_in_white);
		add_test("color_relativity_green_in_black", test_color_relativity_green_in_black);
		add_test("color_relativity_green_in_white", test_color_relativity_green_in_white);
		add_test("color_relativity_blue_in_black", test_color_relativity_blue_in_black);
		add_test("color_relativity_blue_in_white", test_color_relativity_blue_in_white);
		add_test("color_relativity_white_in_black", test_color_relativity_white_in_black);
		add_test("color_relativity_white_in_white", test_color_relativity_white_in_white);
		add_test("color_relativity_midgray_in_black", test_color_relativity_midgray_in_black);
		add_test("color_relativity_midgray_in_white", test_color_relativity_midgray_in_white);
		add_test("color_relativity_black_in_black", test_color_relativity_black_in_black);
		add_test("color_relativity_black_in_white", test_color_relativity_black_in_white);
	}

	public override void set_up() {}

	public void test_conversions_are_reflexive() {
		var cam = Cam16.from_int(red);
		var answer = cam.viewed(ViewingConditions.DEFAULT);

		McuAssert.ints_equal(answer, red);
	}

	public void test_y_midgray() {
		var answer = ColorUtils.y_from_lstar(50);

		McuAssert.doubles_equal(answer, 18.418);
	}

	public void test_y_black() {
		var answer = ColorUtils.y_from_lstar(0);

		McuAssert.doubles_equal(answer, 0.0);
	}

	public void test_y_white() {
		var answer = ColorUtils.y_from_lstar(100);

		McuAssert.doubles_equal(answer, 100.0);
	}

	public void test_cam_red() {
		var answer = Cam16.from_int(red);

		McuAssert.doubles_equal(answer.j, 46.445);
		McuAssert.doubles_equal(answer.chroma, 113.357);
		McuAssert.doubles_equal(answer.hue, 27.408);
		McuAssert.doubles_equal(answer.m, 89.494);
		McuAssert.doubles_equal(answer.s, 91.889);
		McuAssert.doubles_equal(answer.q, 105.988);
	}

	public void test_cam_green() {
		var answer = Cam16.from_int(green);

		McuAssert.doubles_equal(answer.j, 79.331);
		McuAssert.doubles_equal(answer.chroma, 108.410);
		McuAssert.doubles_equal(answer.hue, 142.139);
		McuAssert.doubles_equal(answer.m, 85.587);
		McuAssert.doubles_equal(answer.s, 78.604);
		McuAssert.doubles_equal(answer.q, 138.520);
	}

	public void test_cam_blue() {
		var answer = Cam16.from_int(blue);

		McuAssert.doubles_equal(answer.j, 25.465);
		McuAssert.doubles_equal(answer.chroma, 87.230);
		McuAssert.doubles_equal(answer.hue, 282.788);
		McuAssert.doubles_equal(answer.m, 68.867);
		McuAssert.doubles_equal(answer.s, 93.674);
		McuAssert.doubles_equal(answer.q, 78.481);
	}

	public void test_cam_black() {
		var answer = Cam16.from_int(black);

		McuAssert.doubles_equal(answer.j, 0.0);
		McuAssert.doubles_equal(answer.chroma, 0.0);
		McuAssert.doubles_equal(answer.hue, 0.0);
		McuAssert.doubles_equal(answer.m, 0.0);
		McuAssert.doubles_equal(answer.s, 0.0);
		McuAssert.doubles_equal(answer.q, 0.0);
	}

	public void test_cam_white() {
		var answer = Cam16.from_int(white);

		McuAssert.doubles_equal(answer.j, 100.0);
		McuAssert.doubles_equal(answer.chroma, 2.869);
		McuAssert.doubles_equal(answer.hue, 209.492);
		McuAssert.doubles_equal(answer.m, 2.265);
		McuAssert.doubles_equal(answer.s, 12.068);
		McuAssert.doubles_equal(answer.q, 155.521);
	}

	public void test_camut_map_red() {
		var cam = Cam16.from_int(red);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(red)).to_int();

		McuAssert.ints_equal(answer, red);
	}

	public void test_camut_map_green() {
		var cam = Cam16.from_int(green);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(green)).to_int();

		McuAssert.ints_equal(answer, green);
	}

	public void test_camut_map_blue() {
		var cam = Cam16.from_int(blue);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(blue)).to_int();

		McuAssert.ints_equal(answer, blue);
	}

	public void test_camut_map_white() {
		var cam = Cam16.from_int(white);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(white)).to_int();

		McuAssert.ints_equal(answer, white);
	}

	public void test_camut_map_midgray() {
		var cam = Cam16.from_int(midgray);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(midgray)).to_int();

		McuAssert.ints_equal(answer, midgray);
	}

	public void test_camut_map_black() {
		var cam = Cam16.from_int(black);
		var answer = Hct.from(cam.hue, cam.chroma, ColorUtils.lstar_from_argb(black)).to_int();

		McuAssert.ints_equal(answer, black);
	}

	public void test_hct_returns_sufficiently_close_color() {
		for (double hue = 15; hue < 361; hue += 30) {
			for (double chroma = 0; chroma < 100; chroma += 10) {
				for (double tone = 20; tone < 80; tone += 10) {
					var hct_request_description = @"H$hue C$chroma T$tone";
					var hct_color = Hct.from(hue, chroma, tone);

					if (chroma > 0) {
						var actual_hue = hct_color.hue;
						McuAssert.doubles_equal(actual_hue, hue, 4);
					}

					if (!(hct_color.chroma >= 0.0 && hct_color.chroma <= (chroma + 2.5))) {
						stderr.printf("\n====================\n");
						stderr.printf(@"FAILED: Chroma should be close or less for $hct_request_description\n");
						stderr.printf("  Expected: %f\n", chroma);
						stderr.printf("  Actual: %f\n", hct_color.chroma);
						stderr.printf("====================\n");

						assert_not_reached();
					}

					if (hct_color.chroma < (chroma - 2.5)) {
						if (!color_is_on_boundary(hct_color.to_int())) {
							stderr.printf("\n====================\n");
							stderr.printf(@"FAILED: HCT request for non-sRGB color should return a color on the boundary of the sRGB cube for $hct_request_description\n");
							stderr.printf("  Actual: 0x%08x\n", hct_color.to_int());
							stderr.printf("====================\n");
						}
					}

					McuAssert.doubles_equal(hct_color.tone, tone, 0.5);
				}
			}
		}
	}

	public void test_cam16_to_xyz_without_array() {
		var cam = Cam16.from_int(red);
		var answer = cam.xyz_in_viewing_conditions(ViewingConditions.DEFAULT);

		McuAssert.doubles_equal(answer[0], 41.23, 0.01);
		McuAssert.doubles_equal(answer[1], 21.26, 0.01);
		McuAssert.doubles_equal(answer[2], 1.93, 0.01);
	}

	public void test_cam16_to_xyz_with_array() {
		var cam = Cam16.from_int(red);
		var answer = cam.xyz_in_viewing_conditions(ViewingConditions.DEFAULT, new double[] { 0, 0, 0 });

		McuAssert.doubles_equal(answer[0], 41.23, 0.01);
		McuAssert.doubles_equal(answer[1], 21.26, 0.01);
		McuAssert.doubles_equal(answer[2], 1.93, 0.01);
	}

	public void test_color_relativity_red_in_black() {
		var hct = Hct.from_int(red);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff9f5c51u);
	}

	public void test_color_relativity_red_in_white() {
		var hct = Hct.from_int(red);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xffff5d48u);
	}

	public void test_color_relativity_green_in_black() {
		var hct = Hct.from_int(green);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xffacd69du);
	}

	public void test_color_relativity_green_in_white() {
		var hct = Hct.from_int(green);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff8eff77u);
	}

	public void test_color_relativity_blue_in_black() {
		var hct = Hct.from_int(blue);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff343654u);
	}

	public void test_color_relativity_blue_in_white() {
		var hct = Hct.from_int(blue);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff3f49ffu);
	}

	public void test_color_relativity_white_in_black() {
		var hct = Hct.from_int(white);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xffffffffu);
	}

	public void test_color_relativity_white_in_white() {
		var hct = Hct.from_int(white);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xffffffffu);
	}

	public void test_color_relativity_midgray_in_black() {
		var hct = Hct.from_int(midgray);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff605f5fu);
	}

	public void test_color_relativity_midgray_in_white() {
		var hct = Hct.from_int(midgray);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff8e8e8eu);
	}

	public void test_color_relativity_black_in_black() {
		var hct = Hct.from_int(black);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(0.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff000000u);
	}

	public void test_color_relativity_black_in_white() {
		var hct = Hct.from_int(black);
		var answer = hct.in_viewing_conditions(ViewingConditions.default_with_background_lstar(100.0));

		McuAssert.int_and_uint_equal(answer.to_int(), 0xff000000u);
	}

	public override void tear_down() {}

	private bool color_is_on_boundary(int argb) {
		return ColorUtils.red_from_argb(argb) == 0 ||
		       ColorUtils.red_from_argb(argb) == 255 ||
		       ColorUtils.green_from_argb(argb) == 0 ||
		       ColorUtils.green_from_argb(argb) == 255 ||
		       ColorUtils.blue_from_argb(argb) == 0 ||
		       ColorUtils.blue_from_argb(argb) == 255;
	}
}
