using Cryville.EEW.Core.Map;
using Cryville.EEW.Unity.Map.Element;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
using Color = UnityEngine.Color;

namespace Cryville.EEW.Unity.Map {
	[RequireComponent(typeof(MeshFilter))]
	[RequireComponent(typeof(MeshRenderer))]
	class LineRenderer : MonoBehaviour {
		Material _sharedMaterial;
		public Material Material {
			get => _meshRenderer.sharedMaterial;
			set {
				if (value == _sharedMaterial) return;
				if (_sharedMaterial != null) Destroy(_meshRenderer.sharedMaterial);
				_sharedMaterial = value;
				if (_sharedMaterial == null) return;
				_meshRenderer.sharedMaterial = Instantiate(_sharedMaterial);
				_meshRenderer.sharedMaterial.color = m_color;
			}
		}

		[SerializeField]
		Color m_color = Color.white;
		public Color Color {
			get => m_color;
			set {
				if (m_color == value) return;
				m_color = value;
				_meshRenderer.sharedMaterial.color = value;
			}
		}

		[SerializeField]
		float m_width = 1;
		public float Width {
			get => m_width;
			set {
				if (m_width == value) return;
				m_width = value;
				Invalidate();
			}
		}

		[SerializeField]
		[Range(Vector2.kEpsilon, 1 - Vector2.kEpsilon)]
		float m_flatCornerThreshold = 1 - Vector2.kEpsilon;
		public float FlatCornerThreshold {
			get => m_flatCornerThreshold;
			set {
				if (m_flatCornerThreshold == value) return;
				m_flatCornerThreshold = Mathf.Clamp(value, Vector2.kEpsilon, 1 - Vector2.kEpsilon);
				Invalidate();
			}
		}

		[SerializeField]
		[Range(Vector2.kEpsilon, 1 - Vector2.kEpsilon)]
		float m_sharpCornerThreshold = 0.5f;
		public float SharpCornerThreshold {
			get => m_sharpCornerThreshold;
			set {
				if (m_sharpCornerThreshold == value) return;
				m_sharpCornerThreshold = Mathf.Clamp(value, Vector2.kEpsilon, 1 - Vector2.kEpsilon);
				Invalidate();
			}
		}

		[SerializeField]
		float m_tilingScale = 1;
		public float TilingScale {
			get => m_tilingScale;
			set {
				if (m_tilingScale == value) return;
				m_tilingScale = value;
				Invalidate();
			}
		}

		int _positionCount;
		Vector2[] _positions;
		public void SetPositions(Vector2[] positions) => SetPositions(positions, 0, positions.Length);
		public void SetPositions(Vector2[] positions, int index, int length) {
			_positionCount = length;
			if (_positions is not null)
				ArrayPool<Vector2>.Shared.Return(_positions);
			_positions = ArrayPool<Vector2>.Shared.Rent(length);
			Array.Copy(positions, index, _positions, 0, length);
			Invalidate();
		}

		public static LineRenderer Create(
			IEnumerable<PointF> line, float z,
			LineRenderer prefab, Material material, Color color, float width, MapElement parent,
			out RectangleF? aabb
		) {
			var tileLine = line.Select(p => MapTileUtils.WorldToTilePos(p)).ToArray();
			if (tileLine.Length == 0) {
				aabb = null;
				return null;
			}
			var lineRenderer = Instantiate(prefab);
			lineRenderer.SetPositions(line.Select(p => MapTileUtils.WorldToTilePos(p).ToVector2()).ToArray());
			lineRenderer.Material = material;
			lineRenderer.Color = color;
			lineRenderer.Width = width * parent.ComputedScale / 512;
			lineRenderer.transform.SetParent(parent.transform, false);
			lineRenderer.transform.localPosition = new(0, 0, z);
			aabb = tileLine.Select(p => new RectangleF(p, SizeF.Empty)).Aggregate((a, b) => MapTileUtils.UnionTileAABBs(a, b));
			return lineRenderer;
		}

		bool _valid;
		void Invalidate() {
			_valid = false;
		}

		Mesh _mesh;
		MeshFilter _meshFilter;
		MeshRenderer _meshRenderer;
		void Awake() {
			_meshFilter = GetComponent<MeshFilter>();
			_meshRenderer = GetComponent<MeshRenderer>();
			if (!_meshFilter.mesh) {
				_meshFilter.mesh = new();
			}
			_mesh = _meshFilter.mesh;
			if (_sharedMaterial == null && _meshRenderer.sharedMaterial != null) {
				_sharedMaterial = _meshRenderer.sharedMaterial;
				_meshRenderer.sharedMaterial = Instantiate(_sharedMaterial);
				_meshRenderer.sharedMaterial.color = m_color;
			}
		}
		void OnDestroy() {
			if (_positions is not null)
				ArrayPool<Vector2>.Shared.Return(_positions);
			Destroy(_mesh);
			Destroy(Material);
		}
		void LateUpdate() {
			if (_valid) return;
			_valid = true;

			_mesh.Clear();
			if (_positions == null) return;
			if (_positionCount <= 1) return;
			float hw = m_width / 2;
			int maxVertexCount = 4 * (_positionCount - 1);
			var vbuf = ArrayPool<Vector3>.Shared.Rent(maxVertexCount);
			var ubuf = ArrayPool<Vector2>.Shared.Rent(maxVertexCount);
			var ibuf = ArrayPool<int>.Shared.Rent(3 * (2 + 4 * (_positionCount - 2)));

			int i, vi = 0, ii = 0, li = 0, ri = 1;
			float uvScale = 1 / (m_tilingScale * m_width);
			Vector2 p0 = _positions[0], p1 = default;
			for (i = 1; i < _positionCount; i++) {
				if ((p1 = _positions[i]) != p0) break;
			}
			if (i >= _positionCount) return;
			Vector2 dp0 = NormalizeSmallVector(p1 - p0), np0 = GetNormal(dp0 * hw);
			vbuf[vi] = p0 - np0; ubuf[vi++] = new(0, 0);
			vbuf[vi] = p0 + np0; ubuf[vi++] = new(0, 1);
			float dist = (p1 - p0).magnitude * uvScale;
			p0 = p1;
			for (i++; i < _positionCount; i++) {
				p1 = _positions[i];
				if (p1 == p0) continue;
				Vector2 dp1 = NormalizeSmallVector(p1 - p0), np1 = GetNormal(dp1 * hw);
				Vector2 dpm = NormalizeSmallVector(dp1 - dp0);
				float cc = dp1 == dp0 ? 1 : Mathf.Abs(Vector2.Dot(dpm, NormalizeSmallVector(np0)));
				if (cc > m_flatCornerThreshold) {
					vbuf[vi] = p0 - np0; ubuf[vi++] = new(dist, 0);
					vbuf[vi] = p0 + np0; ubuf[vi++] = new(dist, 0);
					ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
					ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;
					li = vi - 2; ri = vi - 1;
				}
				else {
					if (cc < m_sharpCornerThreshold) {
						cc = m_sharpCornerThreshold;
					}
					float chw = hw / cc;
					float cl = Mathf.Sqrt(chw * chw - hw * hw);
					float cluv = cl * uvScale;
					Vector2 sp0 = p0 - dp0 * cl, sp1 = p0 + dp1 * cl;
					bool isRight = Vector3.Cross(dp0, dp1).z < 0;

					if (isRight) {
						vbuf[vi] = sp0 - np0; ubuf[vi++] = new(dist - cluv, 0);
						vbuf[vi] = p0 + chw * dpm; ubuf[vi++] = new(dist, 1);
					}
					else {
						vbuf[vi] = p0 + chw * dpm; ubuf[vi++] = new(dist, 0);
						vbuf[vi] = sp0 + np0; ubuf[vi++] = new(dist - cluv, 1);
					}
					ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
					ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;

					vbuf[vi] = p0 - chw * dpm; ubuf[vi++] = new(dist, isRight ? 0 : 1);
					ibuf[ii++] = vi - 3; ibuf[ii++] = vi - 1; ibuf[ii++] = vi - 2;

					if (isRight) {
						li = vi; ri = vi - 2;
						vbuf[vi] = sp1 - np1; ubuf[vi++] = new(dist + cluv, 0);
					}
					else {
						li = vi - 3; ri = vi;
						vbuf[vi] = sp1 + np1; ubuf[vi++] = new(dist + cluv, 1);
					}
					ibuf[ii++] = vi - 2; ibuf[ii++] = li; ibuf[ii++] = ri;
				}

				dist += (p1 - p0).magnitude * uvScale;
				p0 = p1; dp0 = dp1; np0 = np1;
			}

			vbuf[vi] = p0 - np0; ubuf[vi++] = new(dist, 0);
			vbuf[vi] = p0 + np0; ubuf[vi++] = new(dist, 1);
			ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
			ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;

			_mesh.SetVertices(vbuf, 0, vi);
			_mesh.SetUVs(0, ubuf, 0, vi);
			_mesh.SetTriangles(ibuf, 0, ii, 0);
			_mesh.RecalculateNormals();
			_mesh.RecalculateBounds();

			ArrayPool<int>.Shared.Return(ibuf);
			ArrayPool<Vector2>.Shared.Return(ubuf);
			ArrayPool<Vector3>.Shared.Return(vbuf);
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		static Vector2 NormalizeSmallVector(Vector2 np0) => np0 / np0.magnitude;

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		static Vector2 GetNormal(Vector2 dp) => new(dp.y, -dp.x);
	}
}
