using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using BITKit.Core.Entites;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace BITKit;
/// <summary>
/// 单例SCADA Service,从http接口获取json后解析为指定数据
/// </summary>
public partial class SCADAService : Node
{
	/// <summary>
	/// 在构造函数中注入依赖
	/// </summary>
	public SCADAService()
	{
		BITAppForGodot.ServiceCollection.AddSingleton(this);
	}
	/// <summary>
	/// 获取json的Url
	/// </summary>
	[Export]
	private string url;
	/// <summary>
	/// 是否固定控制的Entity，如果固定，只会刷新一次，如果不固定，每帧都会刷新
	/// </summary>
	[Export]
	private bool fixedEntities;
	/// <summary>
	/// 已加载的Entity
	/// </summary>
	private readonly Dictionary<string, IEntity> _entities = new();
	/// <summary>
	/// 最大并行请求数量
	/// </summary>
	private readonly LimitTimes requestRate =new (1);
	/// <summary>
	/// 请求数据的间隔
	/// </summary>
	private readonly IntervalTimer _intervalTimer = new(1);
	/// <summary>
	/// 取消令牌，用于取消Http Get
	/// </summary>
	private CancellationToken _cancellationToken;
	/// <summary>
	/// http客户端
	/// </summary>
	private System.Net.Http.HttpClient httpClient;
	/// <summary>
	/// 获取Entity并加载依赖
	/// </summary>
	public override async void _Ready()
	{
		_cancellationToken = new CancellationToken();
		if (!fixedEntities) return;
		await UniTask.Yield();
		LoadAllEntities();
		BIT4Log.Log<SCADAService>($"已加载{_entities.Count}个设备");
		httpClient = BITAppForGodot.ServiceProvider.GetService<System.Net.Http.HttpClient>();
	}
	/// <summary>
	/// 内部方法，从EntityService加载所有Entity
	/// </summary>
	private void LoadAllEntities()
	{
		foreach (var entity in DI.Get<IEntitiesService>().Entities)
		{
			if (entity.TryGetComponent<IdComponent>(out var deviceComponent))
			{
				_entities.Add(deviceComponent.Id,deviceComponent.Entity);
			}
		}
	}
	/// <summary>
	/// 物理帧用于控制并发和间隔的同时请求数据
	/// </summary>
	/// <param name="delta"></param>
	public override void _PhysicsProcess(double delta)
	{
		//依赖加载
		httpClient ??= BITAppForGodot.ServiceProvider.GetService<System.Net.Http.HttpClient>();
		//请求间隔控制+请求并发控制+检查依赖是否为Null
		if (_intervalTimer.Allow && requestRate && httpClient is not null)
		{
			//发送请求
			Request();
		}
	}
	/// <summary>
	/// 从http请求json
	/// </summary>
	private async void Request()
	{
		//获取json
		var json =await httpClient.GetStringAsync(url, _cancellationToken);
		try
		{
			//取消执行，如果已取消令牌
			_cancellationToken.ThrowIfCancellationRequested();
		}
		catch (OperationCanceledException)
		{
			//返回并发数量
			requestRate.Release();
			return;
		}
		//返回并发数量
		requestRate.Release();
		//处理json
		ProcessJson(json);
	}
	/// <summary>
	/// 解析json
	/// </summary>
	/// <param name="json">从SCADA获取的Json</param>
	private void ProcessJson(string json)
	{
		//首先从result中获取数组
		var jArray = JsonConvert.DeserializeObject<JObject>(json)["result"]!.ToObject<JArray>();
		//然后遍历所有数组的内容
		foreach (var element in jArray)
		{
			//获取数组元素的Id
			var id = element["id"]!.ToObject<string>();
			//通过Id查找已加载的Entity
			if (!_entities.TryGetValue(id, out var entity)) continue;
			//加载数组中的"value"为json
			var _json = element["value"]!.ToObject<string>();
			//获取被加载为string的json
			var obj = Json.ParseString(_json);
			//反序列化string为原始json
			var value = JObject.Parse(obj.ToString());
			//提交json和entity
			ProcessEntity(value,entity);
		}
	}
	/// <summary>
	/// 提交jObject数据和Entity进行解析和处理
	/// </summary>
	/// <param name="jObject">json [result] [0] [value] 中的原始json</param>
	/// <param name="entity">引用实体，如PLC-ZL</param>
	private static void ProcessEntity(JObject jObject, IEntity entity)
	{
		//从Entity中加载所有Rotation Component
		var rotationComponents = entity
			.Components
			.Where(x => x is RotationComponent)
			.Select((x => (RotationComponent)x));
		//遍历所有Rotation Component
		foreach (var rotationComponent in rotationComponents)
		{
			//加载rotation需要的path,如 var angle = value["J1"]
			var path = rotationComponent.Path;
			//加载以获取到的角度
			var currentAngle = jObject[path]!.ToObject<float>();
			//最终角度 = 当前角度*角度权重 + 角度偏移 + 原始角度
			var euler = currentAngle * rotationComponent.Weight + rotationComponent.Offset + rotationComponent.OriginalEuler;
			//为Node3D.Rotation提交最后的角度计算结果
			rotationComponent.Rotation = euler;
		}
	}
}
