using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Interfaces;
using Robust.Client.GameObjects;
using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;

namespace Content.Client.GameObjects.Components.Mobs
{
    /// <summary>
    /// A character UI component which shows the current damage state of the mob (living/dead)
    /// </summary>
    [RegisterComponent]
    [ComponentReference(typeof(SharedOverlayEffectsComponent))]
    public sealed class ClientOverlayEffectsComponent : SharedOverlayEffectsComponent//, ICharacterUI
    {
        /// <summary>
        /// A list of overlay containers representing the current overlays applied
        /// </summary>
        private List<OverlayContainer> _currentEffects = new List<OverlayContainer>();

        [ViewVariables(VVAccess.ReadOnly)]
        public List<OverlayContainer> ActiveOverlays
        {
            get => _currentEffects;
            set => SetEffects(value);
        }

#pragma warning disable 649
        // Required dependencies
        [Dependency] private readonly IOverlayManager _overlayManager;
        [Dependency] private readonly IReflectionManager _reflectionManager;
        [Dependency] private readonly IClientNetManager _netManager;
#pragma warning restore 649

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

            UpdateOverlays();
        }

        public override void HandleMessage(ComponentMessage message, IComponent component)
        {
            switch (message)
            {
                case PlayerAttachedMsg _:
                    UpdateOverlays();
                    break;
                case PlayerDetachedMsg _:
                    ActiveOverlays.ForEach(o => _overlayManager.RemoveOverlay(o.ID));
                    break;
            }
        }

        public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null)
        {
            base.HandleNetworkMessage(message, netChannel, session);
            if (message is OverlayEffectComponentMessage overlayMessage)
            {
                SetEffects(overlayMessage.Overlays);
            }
        }

        private void UpdateOverlays()
        {
            _currentEffects = _overlayManager.AllOverlays
                .Where(overlay => Enum.IsDefined(typeof(SharedOverlayID), overlay.ID))
                .Select(overlay => new OverlayContainer(overlay.ID))
                .ToList();

            foreach (var overlayContainer in ActiveOverlays)
            {
                if (!_overlayManager.HasOverlay(overlayContainer.ID))
                {
                    if (TryCreateOverlay(overlayContainer, out var overlay))
                    {
                        _overlayManager.AddOverlay(overlay);
                    }
                }
            }

            SendNetworkMessage(new ResendOverlaysMessage(), _netManager.ServerChannel);
        }

        private void SetEffects(List<OverlayContainer> newOverlays)
        {
            foreach (var container in ActiveOverlays.ToArray())
            {
                if (!newOverlays.Contains(container))
                {
                    RemoveOverlay(container);
                }
            }

            foreach (var container in newOverlays)
            {
                if (!ActiveOverlays.Contains(container))
                {
                    AddOverlay(container);
                }
                else
                {
                    UpdateOverlayConfiguration(container, _overlayManager.GetOverlay(container.ID));
                }
            }

            _currentEffects = newOverlays;
        }

        private void RemoveOverlay(OverlayContainer container)
        {
            ActiveOverlays.Remove(container);
            _overlayManager.RemoveOverlay(container.ID);
        }

        private void AddOverlay(OverlayContainer container)
        {
            if (_overlayManager.HasOverlay(container.ID))
            {
                return;
            }

            ActiveOverlays.Add(container);
            if (TryCreateOverlay(container, out var overlay))
            {
                _overlayManager.AddOverlay(overlay);
            }
            else
            {
                Logger.ErrorS("overlay", $"Could not add overlay {container.ID}");
            }
        }

        private void UpdateOverlayConfiguration(OverlayContainer container, Overlay overlay)
        {
            var configurableTypes = overlay.GetType()
                .GetInterfaces()
                .Where(type =>
                    type.IsGenericType
                    && type.GetGenericTypeDefinition() == typeof(IConfigurable<>)
                    && container.Parameters.Exists(p => p.GetType() == type.GenericTypeArguments.First()))
                .ToList();

            if (configurableTypes.Count > 0)
            {
                foreach (var type in configurableTypes)
                {
                    var method = type.GetMethod(nameof(IConfigurable<object>.Configure));
                    var parameter = container.Parameters
                        .First(p => p.GetType() == type.GenericTypeArguments.First());

                    method!.Invoke(overlay, new []{ parameter });
                }
            }
        }

        private bool TryCreateOverlay(OverlayContainer container, out Overlay overlay)
        {
            var overlayTypes = _reflectionManager.GetAllChildren<Overlay>();
            var overlayType = overlayTypes.FirstOrDefault(t => t.Name == container.ID);

            if (overlayType != null)
            {
                overlay = Activator.CreateInstance(overlayType) as Overlay;
                UpdateOverlayConfiguration(container, overlay);
                return true;
            }

            overlay = default;
            return false;
        }
    }
}
