using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Assets.Scripts;
using Assets.Scripts.Objects.Electrical;
using Assets.Scripts.Objects.Motherboards;
using EmbedIO.WebSockets;
using RemoteControl.Message;
using Swan;
using Swan.Diagnostics;
using GameDevice = Assets.Scripts.Objects.Pipes.Device;

namespace RemoteControl
{
    internal class SubscriptionModule: WebSocketModule
    {
        private HashSet<IWebSocketContext> _jsonSubscribers = new();
        public void SendUpdate(Message.Update update)
        {
            if (_jsonSubscribers.Count > 0)
            {
                var encoded = System.Text.Encoding.UTF8.GetBytes(update.ToJson());
                foreach (var webSocketContext in _jsonSubscribers)
                {
                    _ = SendAsync(webSocketContext, encoded);
                }
            }
            
        }
        
        public SubscriptionModule(string urlPath, bool enableConnectionWatchdog = true) : base(urlPath, enableConnectionWatchdog)
        {
        }

        protected override Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
        {
            return Task.CompletedTask;
        }

        protected override Task OnClientConnectedAsync(IWebSocketContext context)
        {
            _jsonSubscribers.Add(context);
            return base.OnClientConnectedAsync(context);
        }

        protected override Task OnClientDisconnectedAsync(IWebSocketContext context)
        {
            _jsonSubscribers.Remove(context);
            return base.OnClientDisconnectedAsync(context);
        }

        internal static  Update GetUpdate()
        {
            Dictionary<long, Device> cache = new();
            Dictionary<long, Message.Network> networks = new();
            
            
            // lock (SubscriptionManager.Lock)
            var timer = new HighResolutionTimer();
            timer.Start();
            GameDevice.AllDevices.ForEach(dev =>
            {
                if (dev is CableAnalyser analyser)
                {
                    var network = networks.GetOrAdd(analyser.CableNetwork.ReferenceId, (long _) => new Message.Network
                    {
                        ReferenceId = analyser.CableNetwork.ReferenceId,
                        Devices = analyser.CableNetwork.DeviceList
                            .Select(subdev => cache.GetOrAdd(subdev.ReferenceId, _ => new Device(subdev)))
                            .ToList()
                    });
                    network.Probes.Add(analyser.DisplayName);
                }
            });

            Update update = new()
            {
                Networks = networks.Values.ToList(),
                CalculationTime = timer.ElapsedMicroseconds,
            };
            
            
            return update;
        }

        internal void SendUpdate()
        {
            SendUpdate(GetUpdate());
        }
    }

    namespace Message
    {
        public class Update
        {
            public long CalculationTime { get; set; }
           public List<Network> Networks { get; set; } = new();
        }

        public class Network
        {
            public long ReferenceId { get; set; }
            public List<string> Probes { get; set; } = new();
            
            public List<Device> Devices { get; set; } = new();
        }
        
        public class Device
        {
            public Device() {}

            public Device(GameDevice device)
            {
                ReferenceId = device.ReferenceId;
                Name = device.DisplayName;
                NameHash = device.GetNameHash();
                PrefabHash = device.PrefabHash;
                PrefabName = device.PrefabName;
                LogicValues = EnumCollections.LogicTypes.Values.Where(device.CanLogicRead)
                    .ToDictionary(ty => ty, device.GetLogicValue);
                Slots = Enumerable.Range(0, device.TotalSlots)
                    .Select(slot => EnumCollections.LogicSlotTypes.Values.Where(sty => device.CanLogicRead(sty, slot))
                        .ToDictionary(ty => ty, sty => device.GetLogicValue(sty, slot)))
                    .ToList();
                IsCircuitHolder = device is ICircuitHolder;
            }
            
            public long ReferenceId { get; set; }
            public long NameHash{ get; set; }
            public string Name{ get; set; }
            public long PrefabHash{ get; set; }
            public string PrefabName{ get; set; }
            public Dictionary<LogicType, double> LogicValues { get; set; } = new();
            public List<Dictionary<LogicSlotType, double>> Slots { get; set; }= new();
            public bool IsCircuitHolder { get; set; }
        }
    }
}