using System;
using UnityEngine;
using Logger = Cryville.Common.Logging.Logger;

namespace Cryville.Crtr.Skin.Components {
	public class SpriteInfo {
		string m_frameName;
		public string FrameName {
			get {
				return m_frameName;
			}
			set {
				m_frameName = value;
				Reload();
			}
		}
		public SpriteFrame Frame {
			get;
			private set;
		}
		public Rect Rect {
			get { return Frame.Frame; }
		}
		/// <summary>
		/// The ratio of width divided by height.
		/// </summary>
		public float Ratio {
			get {
				return Rect.width / Rect.height;
			}
		}
		Material _mat;
		public void Bind(Material mat) {
			_mat = mat;
			Reload();
		}
		public void Reload() {
			if (!string.IsNullOrEmpty(FrameName)) {
				if (ChartPlayer.frames.ContainsKey(FrameName)) {
					Frame = ChartPlayer.frames[FrameName];
				}
				else {
					Logger.Log("main", 4, "Skin", "Texture {0} not found", FrameName);
					Frame = null;
				}
			}
			else Frame = null;
			if (_mat != null) {
				_mat.mainTexture = Frame == null ? null : Frame.Texture;
			}
		}
		public static bool IsNullOrEmpty(SpriteInfo sprite) {
			return sprite == null || sprite.Frame == null;
		}
	}

	public class SpritePlane : SpriteBase {
		public SpritePlane() {
			SubmitProperty("frame", new PropOp.String(v => Frame = v));
			SubmitProperty("frames", new PropOp.StringArray(v => Frames = v));
			SubmitProperty("index", new PropOp.Integer(v => Index = v));
			SubmitProperty("fit", new PropOp.Enum<FitMode>(v => Fit = v, v => (FitMode)v));
			SubmitProperty("shader", new PropOp.String(v => Shader = v));
		}

		static Vector2[] _origuv;
		protected static Vector2[] OriginalUV {
			get {
				if (_origuv == null) {
					var m = BuiltinResources.Meshes["quad"];
					Vector2[] uv = new Vector2[m.vertices.Length];
					for (int i = 0; i < uv.Length; i++) {
						uv[i] = new Vector2(
							m.vertices[i].x == 0.5 ? 1 : 0,
							m.vertices[i].z == 0.5 ? 1 : 0
						);
					}
					_origuv = uv;
				}
				return _origuv;
			}
		}

		int m_index;
		public int Index {
			get { return m_index; }
			set {
				m_index = value;
				OnFrameUpdate();
			}
		}
		SpriteInfo[] m_frames = new SpriteInfo[] { new SpriteInfo() };
		public string[] Frames {
			set {
				m_frames = new SpriteInfo[value.Length];
				for (int i = 0; i < value.Length; i++) {
					m_frames[i] = new SpriteInfo() { FrameName = value[i] };
				}
				OnFrameUpdate();
			}
		}
		public string Frame {
			set {
				if (value == CurrentFrame.FrameName) return;
				CurrentFrame.FrameName = value;
				OnFrameUpdate();
			}
		}
		protected SpriteInfo CurrentFrame {
			get {
				if (m_frames.Length == 0) return null;
				if (m_index < 0) m_index = 0;
				else if (m_index >= m_frames.Length) m_index = m_frames.Length - 1;
				return m_frames[m_index];
			}
		}
		protected void OnFrameUpdate() {
			if (!mesh.Initialized) return;
			var frame = CurrentFrame;
			if (SpriteInfo.IsNullOrEmpty(frame)) {
				mesh.Renderer.enabled = false;
				return;
			}
			mesh.Renderer.enabled = true;
			mesh.Renderer.sharedMaterial.mainTexture = frame.Frame.Texture;
			UpdateUV();
			UpdateScale();
		}
		Shader m_shader;
		public string Shader {
			set {
				m_shader = BuiltinResources.Shaders[value];
				UpdateShader();
			}
		}
		void UpdateShader() {
			if (!mesh.Initialized) return;
			if (m_shader == null) m_shader = BuiltinResources.Shaders["default"];
			mesh.Renderer.sharedMaterial.shader = m_shader;
			UpdateZIndex();
		}
		readonly Vector2[] _uvs = new Vector2[4];
		protected virtual void UpdateUV() {
			var frame = CurrentFrame;
			if (SpriteInfo.IsNullOrEmpty(frame)) return;
			Vector2[] muv = OriginalUV;
			for (int i = 0; i < _uvs.Length; i++) {
				_uvs[i] = frame.Frame.GetUV(muv[i]);
			}
			mesh.Mesh.uv = _uvs;
		}

		private FitMode m_fit = FitMode.height;
		public FitMode Fit {
			get { return m_fit; }
			set {
				m_fit = value;
				if (m_fit != FitMode.none && Scale.x != Scale.y) m_fit = FitMode.none;
			}
		}
		public enum FitMode {
			none, width, height
		}

		protected override void UpdateScale() {
			if (SpriteInfo.IsNullOrEmpty(CurrentFrame)) return;
			base.UpdateScale();
			if (m_fit != FitMode.none && Scale.x != Scale.y) m_fit = FitMode.none;
		}

		protected override Vector3 BaseScale {
			get {
				switch (m_fit) {
					case FitMode.none: return Vector3.one;
					case FitMode.width: return new Vector3(1, 1, 1 / CurrentFrame.Ratio);
					case FitMode.height: return new Vector3(CurrentFrame.Ratio, 1, 1);
					default: throw new NotSupportedException("Unsupported fit mode");
				}
			}
		}

		public override void Init() {
			InternalInit();
			OnFrameUpdate();
			UpdateShader();
		}
	}
}
