/*
 * Copyright 2022 Peter Han
 * 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 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.
 */
using PeterHan.PLib.Core;
using PeterHan.PLib.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace PeterHan.PLib.Options {
	/// 
	/// The abstract base of options entries that display a color picker with sliders.
	/// 
	internal abstract class ColorBaseOptionsEntry : OptionsEntry {
		/// 
		/// The margin between the color sliders and the rest of the dialog.
		/// 
		protected static readonly RectOffset ENTRY_MARGIN = new RectOffset(10, 10, 2, 5);
		
		/// 
		/// The margin around each slider.
		/// 
		protected static readonly RectOffset SLIDER_MARGIN = new RectOffset(10, 0, 2, 2);
		/// 
		/// The size of the sample swatch.
		/// 
		protected const float SWATCH_SIZE = 32.0f;
		/// 
		/// The hue displayed gradient.
		/// 
		protected ColorGradient hueGradient;
		
		/// 
		/// The hue slider.
		/// 
		protected KSlider hueSlider;
		
		/// 
		/// The realized text field for BLUE.
		/// 
		protected TMP_InputField blue;
		/// 
		/// The realized text field for GREEN.
		/// 
		protected TMP_InputField green;
		/// 
		/// The realized text field for RED.
		/// 
		protected TMP_InputField red;
		
		/// 
		/// The saturation displayed gradient.
		/// 
		protected ColorGradient satGradient;
		/// 
		/// The saturation slider.
		/// 
		protected KSlider satSlider;
		/// 
		/// The color sample swatch.
		/// 
		protected Image swatch;
		/// 
		/// The value displayed gradient.
		/// 
		protected ColorGradient valGradient;
		/// 
		/// The value slider.
		/// 
		protected KSlider valSlider;
		/// 
		/// The value as a Color.
		/// 
		protected Color value;
		protected ColorBaseOptionsEntry(string field, IOptionSpec spec) : base(field, spec) {
			value = Color.white;
			blue = null;
			green = null;
			red = null;
			hueGradient = null; hueSlider = null;
			satGradient = null; satSlider = null;
			valGradient = null; valSlider = null;
			swatch = null;
		}
		
		public override void CreateUIEntry(PGridPanel parent, ref int row) {
			base.CreateUIEntry(parent, ref row);
			// Add 3 rows for the H, S, and V
			parent.AddRow(new GridRowSpec());
			var h = new PSliderSingle("Hue") {
				ToolTip = PLibStrings.TOOLTIP_HUE, MinValue = 0.0f, MaxValue = 1.0f,
				CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnHueChanged
			}.AddOnRealize(OnHueRealized);
			var s = new PSliderSingle("Saturation") {
				ToolTip = PLibStrings.TOOLTIP_SATURATION, MinValue = 0.0f, MaxValue = 1.0f,
				CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnSatChanged
			}.AddOnRealize(OnSatRealized);
			var v = new PSliderSingle("Value") {
				ToolTip = PLibStrings.TOOLTIP_VALUE, MinValue = 0.0f, MaxValue = 1.0f,
				CustomTrack = true, FlexSize = Vector2.right, OnValueChanged = OnValChanged
			}.AddOnRealize(OnValRealized);
			var sw = new PLabel("Swatch") {
				ToolTip = LookInStrings(Tooltip), DynamicSize = false,
				Sprite = PUITuning.Images.BoxBorder, SpriteMode = Image.Type.Sliced,
				SpriteSize = new Vector2(SWATCH_SIZE, SWATCH_SIZE)
			}.AddOnRealize(OnSwatchRealized);
			var panel = new PRelativePanel("ColorPicker") {
				FlexSize = Vector2.right, DynamicSize = false
			}.AddChild(h).AddChild(s).AddChild(v).AddChild(sw).SetRightEdge(h, fraction: 1.0f).
				SetRightEdge(s, fraction: 1.0f).SetRightEdge(v, fraction: 1.0f).
				SetLeftEdge(sw, fraction: 0.0f).SetMargin(h, SLIDER_MARGIN).
				SetMargin(s, SLIDER_MARGIN).SetMargin(v, SLIDER_MARGIN).AnchorYAxis(sw).
				SetLeftEdge(h, toRight: sw).SetLeftEdge(s, toRight: sw).
				SetLeftEdge(v, toRight: sw).SetTopEdge(h, fraction: 1.0f).
				SetBottomEdge(v, fraction: 0.0f).SetTopEdge(s, below: h).
				SetTopEdge(v, below: s);
			parent.AddChild(panel, new GridComponentSpec(++row, 0) {
				ColumnSpan = 2, Margin = ENTRY_MARGIN
			});
		}
		public override GameObject GetUIComponent() {
			Color32 rgb = value;
			var go = new PPanel("RGB") {
				DynamicSize = false, Alignment = TextAnchor.MiddleRight, Spacing = 5,
				Direction = PanelDirection.Horizontal
			}.AddChild(new PLabel("Red") {
				TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_R
			}).AddChild(new PTextField("RedValue") {
				OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_RED,
				Text = rgb.r.ToString(), MinWidth = 32, MaxLength = 3,
				Type = PTextField.FieldType.Integer
			}.AddOnRealize(OnRedRealized)).AddChild(new PLabel("Green") {
				TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_G
			}).AddChild(new PTextField("GreenValue") {
				OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_GREEN,
				Text = rgb.g.ToString(), MinWidth = 32, MaxLength = 3,
				Type = PTextField.FieldType.Integer
			}.AddOnRealize(OnGreenRealized)).AddChild(new PLabel("Blue") {
				TextStyle = PUITuning.Fonts.TextLightStyle, Text = PLibStrings.LABEL_B
			}).AddChild(new PTextField("BlueValue") {
				OnTextChanged = OnRGBChanged, ToolTip = PLibStrings.TOOLTIP_BLUE,
				Text = rgb.b.ToString(), MinWidth = 32, MaxLength = 3,
				Type = PTextField.FieldType.Integer
			}.AddOnRealize(OnBlueRealized)).Build();
			UpdateAll();
			return go;
		}
		
		private void OnBlueRealized(GameObject realized) {
			blue = realized.GetComponentInChildren();
		}
		private void OnGreenRealized(GameObject realized) {
			green = realized.GetComponentInChildren();
		}
		private void OnHueChanged(GameObject _, float newHue) {
			if (hueGradient != null && hueSlider != null) {
				float oldAlpha = value.a;
				hueGradient.Position = hueSlider.value;
				value = hueGradient.SelectedColor;
				value.a = oldAlpha;
				UpdateRGB();
				UpdateSat(false);
				UpdateVal(false);
			}
		}
		private void OnHueRealized(GameObject realized) {
			hueGradient = realized.AddOrGet();
			realized.TryGetComponent(out hueSlider);
		}
		private void OnRedRealized(GameObject realized) {
			red = realized.GetComponentInChildren();
		}
		
		/// 
		/// Called when the red, green, or blue field's text is changed.
		/// 
		/// The new color value.
		protected void OnRGBChanged(GameObject _, string text) {
			Color32 rgb = value;
			if (byte.TryParse(red.text, out byte r))
				rgb.r = r;
			if (byte.TryParse(green.text, out byte g))
				rgb.g = g;
			if (byte.TryParse(blue.text, out byte b))
				rgb.b = b;
			value = rgb;
			UpdateAll();
		}
		private void OnSatChanged(GameObject _, float newSat) {
			if (satGradient != null && satSlider != null) {
				float oldAlpha = value.a;
				satGradient.Position = satSlider.value;
				value = satGradient.SelectedColor;
				value.a = oldAlpha;
				UpdateRGB();
				UpdateHue(false);
				UpdateVal(false);
			}
		}
		private void OnSatRealized(GameObject realized) {
			satGradient = realized.AddOrGet();
			realized.TryGetComponent(out satSlider);
		}
		
		private void OnSwatchRealized(GameObject realized) {
			swatch = realized.GetComponentInChildren();
		}
		private void OnValChanged(GameObject _, float newValue) {
			if (valGradient != null && valSlider != null) {
				float oldAlpha = value.a;
				valGradient.Position = valSlider.value;
				value = valGradient.SelectedColor;
				value.a = oldAlpha;
				UpdateRGB();
				UpdateHue(false);
				UpdateSat(false);
			}
		}
		private void OnValRealized(GameObject realized) {
			valGradient = realized.AddOrGet();
			realized.TryGetComponent(out valSlider);
		}
		/// 
		/// If the color is changed externally, updates all sliders.
		/// 
		protected void UpdateAll() {
			UpdateRGB();
			UpdateHue(true);
			UpdateSat(true);
			UpdateVal(true);
		}
		/// 
		/// Updates the position of the hue slider with the currently selected color.
		/// 
		/// true to move the slider handle if necessary, or false to
		/// leave it where it is.
		protected void UpdateHue(bool moveSlider) {
			if (hueGradient != null && hueSlider != null) {
				Color.RGBToHSV(value, out _, out float s, out float v);
				hueGradient.SetRange(0.0f, 1.0f, s, s, v, v);
				hueGradient.SelectedColor = value;
				if (moveSlider)
					hueSlider.value = hueGradient.Position;
			}
		}
		/// 
		/// Updates the displayed value.
		/// 
		protected void UpdateRGB() {
			Color32 rgb = value;
			if (red != null)
				red.text = rgb.r.ToString();
			if (green != null)
				green.text = rgb.g.ToString();
			if (blue != null)
				blue.text = rgb.b.ToString();
			if (swatch != null)
				swatch.color = value;
		}
		/// 
		/// Updates the position of the saturation slider with the currently selected color.
		/// 
		/// true to move the slider handle if necessary, or false to
		/// leave it where it is.
		protected void UpdateSat(bool moveSlider) {
			if (satGradient != null && satSlider != null) {
				Color.RGBToHSV(value, out float h, out _, out float v);
				satGradient.SetRange(h, h, 0.0f, 1.0f, v, v);
				satGradient.SelectedColor = value;
				if (moveSlider)
					satSlider.value = satGradient.Position;
			}
		}
		/// 
		/// Updates the position of the value slider with the currently selected color.
		/// 
		/// true to move the slider handle if necessary, or false to
		/// leave it where it is.
		protected void UpdateVal(bool moveSlider) {
			if (valGradient != null && valSlider != null) {
				Color.RGBToHSV(value, out float h, out float s, out _);
				valGradient.SetRange(h, h, s, s, 0.0f, 1.0f);
				valGradient.SelectedColor = value;
				if (moveSlider)
					valSlider.value = valGradient.Position;
			}
		}
	}
}