using Cryville.Common.Buffers;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using UnityEngine;
using Logger = Cryville.Common.Logging.Logger;

namespace Cryville.Crtr.Skin.Components {
	public class SpriteText : SpriteBase {
		public SpriteText() {
			SubmitProperty("frames", new op_set_frames(this));
			SubmitProperty("value", new PropOp.TargetString(() => Value));
			SubmitProperty("size", new PropOp.Float(v => Size = v));
			SubmitProperty("spacing", new PropOp.Float(v => Spacing = v));
		}

#pragma warning disable IDE1006
		class op_set_frames : PdtOperator {
			readonly SpriteText _self;
			public op_set_frames(SpriteText self) : base(2) {
				_self = self;
			}
			protected override unsafe void Execute() {
				var keys = GetOperand(0).AsString();
				var values = GetOperand(1);
				int arrtype; int len;
				values.GetArraySuffix(out arrtype, out len);
				if (arrtype != PdtInternalType.String) throw new InvalidCastException("Not an array of strings");
				if (len != keys.Length) throw new ArgumentException("Length of key not equal to frame count");
				var result = new Dictionary<char, SpriteInfo>(len);
				int o = 0;
				for (int i = 0; i < len; i++) {
					string v = values.AsString(o);
					o += v.Length * sizeof(char) + sizeof(int);
					result.Add(keys[i], new SpriteInfo { FrameName = v });
				}
				_self.Frames = result;
			}
		}
#pragma warning restore IDE1006

		protected override void OnDestroy() {
			base.OnDestroy();
			foreach (var m in meshes) m.Value.Destroy();
		}

		Dictionary<char, SpriteInfo> m_frames;
		public Dictionary<char, SpriteInfo> Frames {
			get { return m_frames; }
			set { m_frames = value; UpdateFrames(); }
		}

		readonly TargetString m_value = new TargetString();
		public TargetString Value { get { return m_value; } }

		public float m_size;
		public float Size {
			get { return m_size; }
			set { m_size = value; UpdateScale(); }
		}

		public float m_spacing;
		public float Spacing {
			get { return m_spacing; }
			set { m_spacing = value; UpdateScale(); }
		}

		protected override void UpdateScale() {
			if (!mesh.Initialized) return;
			UpdateMeshes();
			base.UpdateScale();
		}

		void UpdateFrames() {
			if (!mesh.Initialized) return;
			float frameHeight = 0;
			foreach (var m in meshes) m.Value.Destroy();
			meshes.Clear();
			verts.Clear();
			uvs.Clear();
			DestroyMaterials();
			materials = new Material[m_frames.Count];
			int i = 0;
			foreach (var f in m_frames) {
				if (SpriteInfo.IsNullOrEmpty(f.Value)) continue;
				if (frameHeight == 0) frameHeight = f.Value.Rect.height;
				else if (frameHeight != f.Value.Rect.height) throw new Exception("Inconsistent frame height for text component");
				var tex = f.Value.Frame.Texture;
				if (!meshes.ContainsKey(tex)) {
					var m = new MeshWrapper();
					m.Init(mesh.MeshTransform);
					m.Mesh = new Mesh();
					var mat = MeshWrapper.NewMaterial();
					mat.mainTexture = tex;
					m.Renderer.sharedMaterial = materials[i++] = mat;
					meshes.Add(tex, m);
					verts.Add(tex, new List<Vector3>());
					uvs.Add(tex, new List<Vector2>());
					tris.Add(tex, new List<int>());
				}
			}
			UpdateColor();
			UpdateScale();
		}

		float sum_x;
		readonly Dictionary<Texture2D, MeshWrapper> meshes = new Dictionary<Texture2D, MeshWrapper>();
		readonly Dictionary<Texture2D, List<Vector3>> verts = new Dictionary<Texture2D, List<Vector3>>();
		readonly Dictionary<Texture2D, List<Vector2>> uvs = new Dictionary<Texture2D, List<Vector2>>();
		readonly Dictionary<Texture2D, List<int>> tris = new Dictionary<Texture2D, List<int>>();
		void UpdateMeshes() {
			if (meshes.Count == 0) return;
			sum_x = 0;
			foreach (var t in meshes) {
				var key = t.Key;
				verts[key].Clear();
				uvs[key].Clear();
				tris[key].Clear();
			}
			foreach (var c in m_value) {
				SpriteInfo f;
				if (!m_frames.TryGetValue(c, out f)) {
					Logger.Log("main", 3, "Skin", "Could not render the character '{0}' in the string \"{1}\" on the text component because no image is assigned to that character.", c, m_value);
					continue;
				}
				var t = f.Frame.Texture;
				float w = f.Ratio * m_size;
				verts[t].Add(new Vector3(sum_x    , 0,      0));
				verts[t].Add(new Vector3(sum_x + w, 0,      0));
				verts[t].Add(new Vector3(sum_x + w, 0, m_size));
				verts[t].Add(new Vector3(sum_x    , 0, m_size));
				uvs[t].Add(f.Frame.GetUV(new Vector2(0, 0)));
				uvs[t].Add(f.Frame.GetUV(new Vector2(1, 0)));
				uvs[t].Add(f.Frame.GetUV(new Vector2(1, 1)));
				uvs[t].Add(f.Frame.GetUV(new Vector2(0, 1)));
				sum_x += w + m_spacing;
			}
			foreach (var t in meshes) {
				var key = t.Key;
				var m = meshes[key].Mesh;
				m.Clear();
				int cc = verts[key].Count / 4;
				var _tris = tris[key];
				for (int i = 0; i < cc; i++) {
					_tris.Add(i * 4);
					_tris.Add(i * 4 + 3);
					_tris.Add(i * 4 + 1);
					_tris.Add(i * 4 + 1);
					_tris.Add(i * 4 + 3);
					_tris.Add(i * 4 + 2);
				}
				m.SetVertices(verts[key]);
				m.SetUVs(0, uvs[key]);
				m.SetTriangles(tris[key], 0);
				m.RecalculateNormals();
			}
			sum_x -= m_spacing;
		}

		protected override Vector3 BaseSize {
			get { return new Vector3(sum_x, 1, m_size); }
		}
		protected override Vector3 BaseScale {
			get {
				return Vector3.one;
			}
		}
		protected override Vector2 BasePivot {
			get { return new Vector2(-0.5f, -0.5f); }
		}

		public override void Init() {
			InternalInit();
			UpdateFrames();
			mesh.Mesh.Clear();
			UpdateScale();
			Value.OnUpdate += UpdateScale;
		}
	}
}
