/*
 * 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;

namespace MaterialColorUtilities {
	/**
	 * A color system built using CAM16 hue and chroma, and L* from L*a*b*.
	 *
	 * <p>Using L* creates a link between the color system, contrast, and thus accessibility. Contrast
	 * ratio depends on relative luminance, or Y in the XYZ color space. L*, or perceptual luminance can
	 * be calculated from Y.
	 *
	 * <p>Unlike Y, L* is linear to human perception, allowing trivial creation of accurate color tones.
	 *
	 * <p>Unlike contrast ratio, measuring contrast in L* is linear, and simple to calculate. A
	 * difference of 40 in HCT tone guarantees a contrast ratio >= 3.0, and a difference of 50
	 * guarantees a contrast ratio >= 4.5.
	 */

	/**
	 * HCT, hue, chroma, and tone. A color system that provides a perceptually accurate color
	 * measurement system that can also accurately render what colors will appear as in different
	 * lighting environments.
	 */
	public sealed class Hct : Object {
		private double _hue;
		private double _chroma;
		private double _tone;
		private int argb;

		/**
		 * 0 <= hue < 360; invalid values are corrected.
		 * Setting hue may reduce chroma as chroma has a different maximum for any given hue and tone.
		 */
		public double hue {
			get { return _hue; }
			set { set_internal_state(HctSolver.solve_to_int(value, _chroma, _tone)); }
		}

		/**
		 * 0 <= chroma < ?; Informally, colorfulness.
		 * The color returned may be lower than the requested chroma. Chroma has a different maximum for
		 * any given hue and tone.
		 */
		public double chroma {
			get { return _chroma; }
			set { set_internal_state(HctSolver.solve_to_int(_hue, value, _tone)); }
		}

		/**
		 * 0 <= tone <= 100; invalid values are corrected.
		 * Setting tone may reduce chroma as chroma has a different maximum for any given hue and tone.
		 */
		public double tone {
			get { return _tone; }
			set { set_internal_state(HctSolver.solve_to_int(_hue, _chroma, value)); }
		}

		/**
		 * Create an HCT color from hue, chroma, and tone.
		 *
		 * @param hue 0 <= hue < 360; invalid values are corrected.
		 * @param chroma 0 <= chroma < ?; Informally, colorfulness. The color returned may be lower than
		 *     the requested chroma. Chroma has a different maximum for any given hue and tone.
		 * @param tone 0 <= tone <= 100; invalid values are corrected.
		 * @return HCT representation of a color in default viewing conditions.
		 */
		public static Hct from(double hue, double chroma, double tone) {
			int argb = HctSolver.solve_to_int(hue, chroma, tone);
			return new Hct(argb);
		}

		/**
		 * Create an HCT color from a color.
		 *
		 * @param argb ARGB representation of a color.
		 * @return HCT representation of a color in default viewing conditions
		 */
		public static Hct from_int(int argb) {
			return new Hct(argb);
		}

		private Hct(int argb) {
			set_internal_state(argb);
		}

		public int to_int() {
			return argb;
		}

		public static uint hash(Hct hct) {
			return (uint) hct.to_int();
		}

		public static bool equal(Hct a, Hct b) {
			return a.to_int() == b.to_int();
		}

		public string format() {
			return new StringBuilder()
			        .append("HCT(")
			        .append(Math.round(hue).to_string())
			        .append(", ")
			        .append(Math.round(chroma).to_string())
			        .append(", ")
			        .append(Math.round(tone).to_string())
			        .append(")")
			        .str;
		}

		public static bool is_blue(double hue) {
			return hue >= 250 && hue < 270;
		}

		public static bool is_yellow(double hue) {
			return hue >= 105 && hue < 125;
		}

		public static bool is_cyan(double hue) {
			return hue >= 170 && hue < 207;
		}

		/**
		 * Translate a color into different ViewingConditions.
		 *
		 * <p>Colors change appearance. They look different with lights on versus off, the same color, as
		 * in hex code, on white looks different when on black. This is called color relativity, most
		 * famously explicated by Josef Albers in Interaction of Color.
		 *
		 * <p>In color science, color appearance models can account for this and calculate the appearance
		 * of a color in different settings. HCT is based on CAM16, a color appearance model, and uses it
		 * to make these calculations.
		 *
		 * <p>See ViewingConditions.make for parameters affecting color appearance.
		 */
		public Hct in_viewing_conditions(ViewingConditions vc) {
			// 1. Use CAM16 to find XYZ coordinates of color in specified VC.
			Cam16 cam16 = Cam16.from_int(to_int());
			double[] viewed_in_vc = cam16.xyz_in_viewing_conditions(vc, null);

			// 2. Create CAM16 of those XYZ coordinates in default VC.
			Cam16 recast_in_vc = Cam16.from_xyz_in_viewing_conditions(viewed_in_vc[0], viewed_in_vc[1], viewed_in_vc[2], ViewingConditions.DEFAULT);

			// 3. Create HCT from:
			// - CAM16 using default VC with XYZ coordinates in specified VC.
			// - L* converted from Y in XYZ coordinates in specified VC.
			return Hct.from(recast_in_vc.hue, recast_in_vc.chroma, ColorUtils.lstar_from_y(viewed_in_vc[1]));
		}

		private void set_internal_state(int argb) {
			this.argb = argb;
			Cam16 cam = Cam16.from_int(argb);
			_hue = cam.hue;
			_chroma = cam.chroma;
			_tone = ColorUtils.lstar_from_argb(argb);
		}
	}
}
