using Cryville.EEW.Core;
using Cryville.EEW.Core.Map;
using System;
using System.Drawing;
using UnityEngine;

namespace Cryville.EEW.Unity.Map.Element {
	class WaveCircleElement : MapElement {
		PointF _hypocenterLocation;
		PointF _hypocenterTilePos;
		internal void SetHypocenterLocation(PointF value, ref int order) {
			_hypocenterLocation = value;
			_hypocenterTilePos = MapTileUtils.WorldToTilePos(value);
			transform.localPosition = new(0, 0, OrderToZ(ref order));
		}

		public override RectangleF? AABB {
			get {
				var time = (float)(DateTime.UtcNow - OriginTime).TotalSeconds;
				float angle = WaveTimeCalculator.Instance.CalculatePWaveAngleOrPercentage(Depth, time);
				if (float.IsNaN(angle)) angle = WaveTimeCalculator.Instance.CalculateSWaveAngleOrPercentage(Depth, time);
				if (float.IsNaN(angle)) {
					var rTime = (float?)(ReportTime - OriginTime)?.TotalSeconds;
					if (rTime != null) {
						angle = WaveTimeCalculator.Instance.CalculatePWaveAngleOrPercentage(Depth, rTime.Value);
						if (float.IsNaN(angle)) angle = WaveTimeCalculator.Instance.CalculateSWaveAngleOrPercentage(Depth, rTime.Value);
					}
				}
				var inflation = angle > 0 ? angle / 180 : 0;
				return RectangleF.Inflate(new(_hypocenterTilePos, SizeF.Empty), inflation, inflation);
			}
		}

		public DateTime OriginTime { get; set; }
		public float Depth { get; set; }
		public DateTime? ReportTime { get; set; }

		[SerializeField] MultiLineRenderer m_lineRendererP;
		[SerializeField] MultiLineRenderer m_lineRendererS;
		[SerializeField] Material m_ongoingMaterial;
		[SerializeField] Material m_historyMaterial;

		protected override void OnSetScale() {
			m_lineRendererP.Width = ComputedScale / 256;
			m_lineRendererS.Width = ComputedScale / 256;
		}

		void Update() {
			var time = (float)(DateTime.UtcNow - OriginTime).TotalSeconds;
			var rTime = (float?)(ReportTime - OriginTime)?.TotalSeconds;

			float pAngle = WaveTimeCalculator.Instance.CalculatePWaveAngleOrPercentage(Depth, time);
			if (float.IsNaN(pAngle) && rTime != null) {
				DrawCore(WaveTimeCalculator.Instance.CalculatePWaveAngleOrPercentage(Depth, rTime.Value), m_lineRendererP, true);
			}
			else {
				DrawCore(pAngle, m_lineRendererP);
			}

			float sAngle = WaveTimeCalculator.Instance.CalculateSWaveAngleOrPercentage(Depth, time);
			if (float.IsNaN(sAngle) && rTime != null) {
				DrawCore(WaveTimeCalculator.Instance.CalculateSWaveAngleOrPercentage(Depth, rTime.Value), m_lineRendererS, true);
			}
			else {
				DrawCore(sAngle, m_lineRendererS);
			}
		}

		readonly Vector2[] _vertexBuffer = new Vector2[361];
		void DrawCore(float angle, MultiLineRenderer renderer, bool isHistory = false) {
			renderer.Clear();
			if (float.IsNaN(angle)) return;
			if (angle < -1) return;
			if (angle < 0) {
				renderer.Width = ComputedScale / 64;
				renderer.TilingScale = 1;
				var point = _hypocenterTilePos.ToVector2();
				var offset = ComputedScale / 20;
				angle = (angle + 1) * 360;
				int segCount = (int)Math.Ceiling(angle);
				for (int d = 0; d < segCount - 1; d++) {
					float rad = d * Mathf.PI / 180;
					_vertexBuffer[d] = point + offset * new Vector2(Mathf.Sin(rad), Mathf.Cos(rad));
				}
				float rad2 = angle * Mathf.PI / 180;
				if (segCount > 0) _vertexBuffer[segCount - 1] = point + offset * new Vector2(Mathf.Sin(rad2), Mathf.Cos(rad2));
				renderer.AddSegment(_vertexBuffer, 0, segCount);
			}
			else {
				renderer.Width = ComputedScale / 256;
				renderer.TilingScale = 8;
				float radlat = _hypocenterLocation.Y / 180f * MathF.PI, radlon = _hypocenterLocation.X / 180f * MathF.PI;
				float rplat = radlat + angle / 180f * MathF.PI;
				Vector3 rp = new(MathF.Cos(radlon) * MathF.Cos(rplat), MathF.Sin(rplat), MathF.Sin(radlon) * MathF.Cos(rplat));
				Vector3 axis = new(MathF.Cos(radlon) * MathF.Cos(radlat), MathF.Sin(radlat), MathF.Sin(radlon) * MathF.Cos(radlat));
				Vector2? lp2 = null;
				int segmentIndex = 0;
				for (int d = 0; d < 360; d++) {
					Quaternion q = Quaternion.AngleAxis(d, axis);
					Vector3 p = q * rp;
					Vector2 p2 = ToTilePos(p).ToVector2();
					if (lp2 != null) {
						float dx = p2.x - lp2.Value.x;
						if (MathF.Abs(dx) >= 0.5) {
							_vertexBuffer[d] = p2.x < 0.5 ? p2 + new Vector2(1, 0) : p2 - new Vector2(1, 0);
							renderer.AddSegment(_vertexBuffer, segmentIndex, d - segmentIndex + 1);
							segmentIndex = d;
						}
					}
					_vertexBuffer[d] = p2;
					lp2 = p2;
				}
				Vector2 rp2 = ToTilePos(rp).ToVector2();
				_vertexBuffer[360] = rp2;
				renderer.AddSegment(_vertexBuffer, segmentIndex, 361 - segmentIndex);
			}
			renderer.SetMaterial(isHistory ? m_historyMaterial : m_ongoingMaterial);
		}
		static PointF ToTilePos(Vector3 p) => MapTileUtils.WorldToTilePos(new(MathF.Atan2(p.z, p.x) / MathF.PI * 180f, MathF.Asin(p.y) / MathF.PI * 180f));
	}
}
