using Cryville.Common.Buffers;
using Cryville.Common.Pdt;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Cryville.Crtr.Components {
	public abstract class SectionalGameObject : SkinComponent {
		protected Vector3? prevpt;
		protected Quaternion? prevrot;
		protected int vertCount = 0;
		protected MeshWrapper mesh = new MeshWrapper();

		protected override void OnDestroy() {
			mesh.Destroy();
		}

		public override void Init() {
			Reset();
		}
		
		public void AppendPoint(Vector3 p) {
			AppendPoint(p, Quaternion.identity);
		}

		public void AppendPoint(Vector3 p, Quaternion r) {
			if (prevpt == p && prevrot == r) return;
			AppendPointInternal(p, r);
			// if (!headGenerated) Logger.Log("main", 0, "Skin/Polysec", "{0}", r);
			prevpt = p;
			prevrot = r;
			vertCount++;
		}

		protected abstract void AppendPointInternal(Vector3 wp, Quaternion r);

		public abstract void Seal();

		public virtual void Reset() {
			vertCount = 0;
			prevpt = null;
			prevrot = null;
		}
	}

	public class PolygonSGO : SectionalGameObject {
		static readonly SimpleObjectPool<List<Vector3>> _ptPool = new SimpleObjectPool<List<Vector3>>(1024);
		static readonly SimpleObjectPool<List<float>> _lPool = new SimpleObjectPool<List<float>>(1024);

		static readonly ListPool<int> _indexPool = new ListPool<int>();
		static readonly ListPool<Vector3> _vertPool = new ListPool<Vector3>();
		static readonly ListPool<Vector2> _uvPool = new ListPool<Vector2>();
		static readonly ArrayPool<Vector2> _shapePool = new ArrayPool<Vector2>(0x100, 0x10000);

		public PolygonSGO() {
			SubmitProperty("head", new PropOp.String(v => head.FrameName = v));
			SubmitProperty("body", new PropOp.String(v => body.FrameName = v));
			SubmitProperty("tail", new PropOp.String(v => tail.FrameName = v));
			SubmitProperty("transparent", new PropOp.Boolean(v => transparent = v));
			SubmitProperty("shape", new op_set_shape(this));
		}

#pragma warning disable IDE1006
		public class op_set_shape : PdtOperator {
			readonly PolygonSGO _self;
			public op_set_shape(PolygonSGO self) : base(1) {
				_self = self;
			}
			protected unsafe override void Execute() {
				var o = GetOperand(0);
				if (o.Type != PdtInternalType.Vector) throw new ArgumentException("Not a vector");
				_self._shapeLength = (o.Length - sizeof(int)) / sizeof(Vector2);
				var ptr = (Vector2*)o.TrustedAsOfLength(sizeof(Vector2));
				if (_self._shape != null) _shapePool.Return(_self._shape);
				_self._shape = _shapePool.Rent(_self._shapeLength);
				for (int i = 0; i < _self._shapeLength; i++) {
					_self._shape[i] = ptr[i];
				}
			}
		}
#pragma warning restore IDE1006

		int _shapeLength = 0;
		Vector2[] _shape = null;
		float GetWidth() {
			float r = 0;
			for (int i = 1; i < _shapeLength; i++) {
				r += (_shape[i] - _shape[i-1]).magnitude;
			}
			return r;
		}
		
		public SpriteInfo head = new SpriteInfo();
		public SpriteInfo body = new SpriteInfo();
		public SpriteInfo tail = new SpriteInfo();

		public bool transparent;
		List<Vector3> vertices;
		List<float> lengths;
		float sumLength = 0;

		public override void Init() {
			base.Init();

			head.Load();
			body.Load();
			tail.Load();

			mesh.Init(transform, transparent);

			List<Material> materials = new List<Material>();
			if (head.FrameName != null) AddMat(materials, head.FrameName);
			if (body.FrameName != null) AddMat(materials, body.FrameName);
			if (tail.FrameName != null) AddMat(materials, tail.FrameName);
			mesh.Renderer.materials = materials.ToArray();
		}

		void AddMat(List<Material> list, string frame) {
			var mat = mesh.NewMaterial;
			mat.mainTexture = ChartPlayer.frames[frame].Texture;
			list.Add(mat);
		}

		protected override void OnDestroy() {
			base.OnDestroy();
			Reset();
			foreach (var m in mesh.Renderer.materials) Material.Destroy(m);
			if (_shape != null) _shapePool.Return(_shape);
			if (vertices != null) {
				_ptPool.Return(vertices);
				_lPool.Return(lengths);
			}
		}

		// Vector3 prevp = Vector3.zero;
		protected override void AppendPointInternal(Vector3 p, Quaternion r) {
			if (vertices == null) {
				vertices = _ptPool.Rent();
				lengths = _lPool.Rent();
			}

			for (int i = 0; i < _shapeLength; i++) {
				Vector2 sp = r * _shape[i];
				vertices.Add(p + (Vector3)sp);
			}

			if (prevpt != null) {
				float len = (p - prevpt.Value).magnitude;
				lengths.Add(len);
				sumLength += len;
			}
		}

		List<Vector3> verts;
		List<Vector2> uvs;
		List<int> trih = null, trib = null, trit = null;

		public override void Seal() {
			if (vertCount <= 1 || sumLength == 0) return;
			int vcpsec = _shapeLength; // Vertex Count Per Section
			float width = GetWidth();
			float headLength = 0;
			if (head.FrameName != null) headLength = width / head.Ratio;
			float tailLength = 0;
			if (tail.FrameName != null) tailLength = width / tail.Ratio;
			float endLength = headLength + tailLength;
			if (sumLength <= endLength) {
				// The total length of the two ends is longer than the whole mesh, squeeze the two ends
				float ratio = sumLength / endLength;
				headLength *= ratio;
				tailLength *= ratio;
			}

			// Find the vertex counts needed for the three parts from the head
			float l0 = 0;
			int hvc = 0;
			for (; l0 < headLength && hvc < lengths.Count; hvc++)
				l0 += lengths[hvc];
			int bvc = 0;
			for (; l0 < sumLength - tailLength && hvc + bvc < lengths.Count; bvc++)
				l0 += lengths[hvc + bvc];
			int tvc = lengths.Count - hvc - bvc + 1;
			if (hvc > 0) {
				hvc++;
				bvc++;
			}
			bvc++;
			int vc = hvc + bvc + tvc;

			verts = _vertPool.Rent(vc * vcpsec);
			uvs = _uvPool.Rent(vc * vcpsec);
			int i = 0; int t = 0; float l = 0; int m = 0;
			if (head.FrameName != null) { m++; GenerateMeshTo(verts, uvs, out trih, head, ref i, ref t, ref l, 0, headLength, vcpsec, hvc); }
			if (body.FrameName != null) { m++; GenerateMeshTo(verts, uvs, out trib, body, ref i, ref t, ref l, headLength, sumLength - tailLength, vcpsec, hvc + bvc); }
			if (tail.FrameName != null) { m++; GenerateMeshTo(verts, uvs, out trit, tail, ref i, ref t, ref l, sumLength - tailLength, sumLength, vcpsec, vc); }

			mesh.Mesh.subMeshCount = m;
			m = 0;
			mesh.Mesh.SetVertices(verts);
			mesh.Mesh.SetUVs(0, uvs);
			if (head.FrameName != null) mesh.Mesh.SetTriangles(trih, m++);
			if (body.FrameName != null) mesh.Mesh.SetTriangles(trib, m++);
			if (tail.FrameName != null) mesh.Mesh.SetTriangles(trit, m++);
			mesh.Mesh.RecalculateNormals();

			_vertPool.Return(verts); verts = null;
			_uvPool.Return(uvs); uvs = null;
			if (trih != null) { _indexPool.Return(trih); trih = null; }
			if (trib != null) { _indexPool.Return(trib); trib = null; }
			if (trit != null) { _indexPool.Return(trit); trit = null; }
		}

		void GenerateMeshTo(List<Vector3> verts, List<Vector2> uvs, out List<int> tris, SpriteInfo info, ref int i, ref int t, ref float l, float startl, float endl, int vcpsec, int vend) {
			if (i > lengths.Count) {
				tris = _indexPool.Rent(0);
				return;
			}

			int t0 = t;
			var frame = info.Frame;

			if (startl != 0) {
				float pl2 = l - lengths[i - 1];
				float fr2 = (startl - pl2) / (l - pl2);
				for (int j = 0; j < vcpsec; j++, t++) {
					verts[t] = 
						vertices[vcpsec*(i-1)+j] * (1-fr2)
						+ vertices[vcpsec*i+j] * fr2;
					var u = j / (vcpsec - 1);
					if (frame != null) uvs[t] = frame.GetUV(u, 0);
				}
			}
			for (; t / vcpsec < vend - 1; i++) {
				var v = (l - startl) / (endl - startl);
				for (int j = 0; j < vcpsec; j++, t++) {
					verts[t] = vertices[vcpsec*i+j];
					var u = j / (vcpsec - 1);
					if (frame != null) uvs[t] = frame.GetUV(u, v);
				}
				l += lengths[i];
			}
			float pl = l - lengths[i - 1];
			float fr = (endl - pl) / (l - pl);
			for (int j = 0; j < vcpsec; j++, t++) {
				verts[t] = 
					vertices[vcpsec*(i-1)+j] * (1-fr)
					+ vertices[vcpsec*i+j] * fr;
				var u = j / (vcpsec - 1);
				if (frame != null) uvs[t] = frame.GetUV(u, 1);
			}

			tris = _indexPool.Rent((vend - t0 / vcpsec - 1) * (vcpsec - 1) * 6);
			int i3 = 0;
			for (int i1 = t0 + vcpsec + 1; i1 < t; i1++) {
				if (i1 % vcpsec == 0) continue;
				int i2 = i1 - vcpsec;
				tris[i3++] = i1 - 1;
				tris[i3++] = i2;
				tris[i3++] = i2 - 1;
				tris[i3++] = i1 - 1;
				tris[i3++] = i1;
				tris[i3++] = i2;
			}
		}

		public override void Reset() {
			base.Reset();
			if (mesh.Initialized) mesh.Mesh.Clear();
			if (vertices != null) {
				vertices.Clear();
				lengths.Clear();
			}
			sumLength = 0;
		}
	}
}
