using System;
using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;

namespace Cryville.Common.Unity.UI {
	/// <summary>
	/// An image that is sliced into three parts, with the middle part stretched and the two borders preserved their aspect ratio.
	/// </summary>
	[ExecuteInEditMode]
	public class ImageSliced3 : MaskableGraphic {
		[SerializeField]
		[Tooltip("The sliced sprite.")]
		private Sprite m_sprite;
		/// <summary>
		/// The sliced sprite.
		/// </summary>
		public Sprite Sprite {
			get { return m_sprite; }
			set { m_sprite = value; }
		}

		/// <summary>
		/// The mode how a sliced image is generated when it is too compact.
		/// </summary>
		public enum CompactMode {
			/// <summary>
			/// Squeezes both the left border and the right border.
			/// </summary>
			SqueezeBoth = 0,
			/// <summary>
			/// Squeezes the left border and preserves the width of the right border.
			/// </summary>
			SqueezeLeft = 2,
			/// <summary>
			/// Squeezes the right border and preserves the width of the left border.
			/// </summary>
			SqueezeRight = 3,
			/// <summary>
			/// Squeezes the lower edge of the left border and the upper edge of the right border.
			/// </summary>
			DiagonalLeft = 4,
			/// <summary>
			/// Squeezes the upper edge of the left border and the lower edge of the right border.
			/// </summary>
			DiagonalRight = 5,
		}
		[SerializeField]
		[Tooltip("The mode how a sliced image is generated when it is too compact.")]
		private CompactMode m_compact;
		/// <summary>
		/// The mode how a sliced image is generated when it is too compact.
		/// </summary>
		public CompactMode Compact {
			get { return m_compact; }
			set { m_compact = value; }
		}

		[SerializeField]
		[Tooltip("Whether the sprite is vertical.")]
		private bool m_isVertical;
		/// <summary>
		/// Whether the sprite is vertical.
		/// </summary>
		public bool IsVertical {
			get { return m_isVertical; }
			set { m_isVertical = value; }
		}

		public override Texture mainTexture {
			get { return Sprite == null ? s_WhiteTexture : Sprite.texture; }
		}

		void GetCornerLength(int axis, int corner, Vector4 uv, out float length, out float uvLength) {
			float border = Sprite.border[(corner << 1) | axis];
			float sizeRatio = border / (axis == 0 ? Sprite.rect.height : Sprite.rect.width);
			length = sizeRatio * (axis == 0 ? rectTransform.rect.height : rectTransform.rect.width);
			uvLength = (uv[(axis ^ 1) | 2] - uv[axis ^ 1]) * (border / (axis == 0 ? Sprite.rect.width : Sprite.rect.height));
		}
		protected override void OnPopulateMesh(VertexHelper vh) {
			if (Sprite == null) {
				throw new UnityException("No sprite");
			}
			else if (Sprite.border == Vector4.zero) {
				throw new UnityException("No sprite border");
			}

			int axis = IsVertical ? 1 : 0;

			float actualLength = axis == 0 ? rectTransform.rect.width : rectTransform.rect.height;

			Vector4 uv = DataUtility.GetOuterUV(Sprite);

			GetCornerLength(axis, 0, uv, out var actualStartCornerLength, out var actualStartUVWidth);
			GetCornerLength(axis, 1, uv, out var actualEndCornerLength, out var actualEndUVLength);

			float actualTotalCornerLength = actualStartCornerLength + actualEndCornerLength;

			float w3, w4, w5, w6;
			if (actualTotalCornerLength > actualLength) {
				switch (Compact) {
					case CompactMode.SqueezeBoth:
						w3 = w4 = actualStartCornerLength / actualTotalCornerLength * actualLength;
						w5 = w6 = actualEndCornerLength / actualTotalCornerLength * actualLength;
						break;
					case CompactMode.SqueezeLeft:
						w3 = w4 = actualLength - actualEndCornerLength;
						w5 = w6 = actualEndCornerLength;
						break;
					case CompactMode.SqueezeRight:
						w3 = w4 = actualStartCornerLength;
						w5 = w6 = actualLength - actualStartCornerLength;
						break;
					case CompactMode.DiagonalLeft:
						w3 = actualStartCornerLength;
						w4 = actualLength - actualEndCornerLength;
						w5 = actualLength - actualStartCornerLength;
						w6 = actualEndCornerLength;
						break;
					case CompactMode.DiagonalRight:
						w3 = actualLength - actualEndCornerLength;
						w4 = actualStartCornerLength;
						w5 = actualEndCornerLength;
						w6 = actualLength - actualStartCornerLength;
						break;
					default:
						throw new ArgumentOutOfRangeException("Compact");
				}
			}
			else {
				w3 = w4 = actualStartCornerLength;
				w5 = w6 = actualEndCornerLength;
			}

			Vector2 corner1 = Vector2.zero;
			Vector2 corner2 = Vector2.one;

			corner1.x -= rectTransform.pivot[axis];
			corner1.y -= rectTransform.pivot[axis ^ 1];
			corner2.x -= rectTransform.pivot[axis];
			corner2.y -= rectTransform.pivot[axis ^ 1];

			corner1.x *= actualLength;
			corner1.y *= axis == 0 ? rectTransform.rect.height : rectTransform.rect.width;
			corner2.x *= actualLength;
			corner2.y *= axis == 0 ? rectTransform.rect.height : rectTransform.rect.width;

			vh.Clear();
			if (axis == 0) {
				vh.AddVert(new Vector3(corner1.x, corner2.y), color, new Vector2(uv.x, uv.w));
				vh.AddVert(new Vector3(corner1.x, corner1.y), color, new Vector2(uv.x, uv.y));
				vh.AddVert(new Vector3(corner1.x + w3, corner2.y), color, new Vector2(uv.x + actualStartUVWidth, uv.w));
				vh.AddVert(new Vector3(corner1.x + w4, corner1.y), color, new Vector2(uv.x + actualStartUVWidth, uv.y));
				vh.AddVert(new Vector3(corner2.x - w5, corner2.y), color, new Vector2(uv.z - actualEndUVLength, uv.w));
				vh.AddVert(new Vector3(corner2.x - w6, corner1.y), color, new Vector2(uv.z - actualEndUVLength, uv.y));
				vh.AddVert(new Vector3(corner2.x, corner2.y), color, new Vector2(uv.z, uv.w));
				vh.AddVert(new Vector3(corner2.x, corner1.y), color, new Vector2(uv.z, uv.y));
			}
			else {
				vh.AddVert(new Vector3(corner1.y, corner1.x), color, new Vector2(uv.x, uv.w));
				vh.AddVert(new Vector3(corner2.y, corner1.x), color, new Vector2(uv.z, uv.w));
				vh.AddVert(new Vector3(corner1.y, corner1.x + w4), color, new Vector2(uv.x, uv.w - actualStartUVWidth));
				vh.AddVert(new Vector3(corner2.y, corner1.x + w3), color, new Vector2(uv.z, uv.w - actualStartUVWidth));
				vh.AddVert(new Vector3(corner1.y, corner2.x - w6), color, new Vector2(uv.x, uv.y + actualEndUVLength));
				vh.AddVert(new Vector3(corner2.y, corner2.x - w5), color, new Vector2(uv.z, uv.y + actualEndUVLength));
				vh.AddVert(new Vector3(corner1.y, corner2.x), color, new Vector2(uv.x, uv.y));
				vh.AddVert(new Vector3(corner2.y, corner2.x), color, new Vector2(uv.z, uv.y));
			}

			if (((int)Compact & 0x1) == 0) {
				vh.AddTriangle(2, 1, 0);
				vh.AddTriangle(1, 2, 3);
				vh.AddTriangle(4, 3, 2);
				vh.AddTriangle(3, 4, 5);
				vh.AddTriangle(6, 5, 4);
				vh.AddTriangle(5, 6, 7);
			}
			else {
				vh.AddTriangle(3, 1, 0);
				vh.AddTriangle(0, 2, 3);
				vh.AddTriangle(5, 3, 2);
				vh.AddTriangle(2, 4, 5);
				vh.AddTriangle(7, 5, 4);
				vh.AddTriangle(4, 6, 7);
			}
		}
	}
}
