using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using PositionType = Barotrauma.Level.PositionType;

namespace Barotrauma
{
    partial class MineralMission : Mission
    {
        private readonly Dictionary<Identifier, int> resourceAmounts  = new Dictionary<Identifier, int>();
        private readonly Dictionary<Identifier, List<Item>> spawnedResources = new Dictionary<Identifier, List<Item>>();
        private readonly Dictionary<Identifier, Item[]> relevantLevelResources = new Dictionary<Identifier, Item[]>();
        private readonly List<(Identifier Identifier, Vector2 Position)> missionClusterPositions = new List<(Identifier Identifier, Vector2 Position)>();

        private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();

        private readonly PositionType positionType = PositionType.Cave;
        /// <remarks>
        /// The list order is important.
        /// It defines the order in which we "override" <see cref="positionType"/> in case no valid position types are found
        /// in the level when generating them in <see cref="Level.GenerateMissionResources(ItemPrefab, int, PositionType, out float)"/>.
        /// </remarks>
        public static readonly ImmutableArray<PositionType> ValidPositionTypes = new PositionType[]
        {
            PositionType.Cave,
            PositionType.SidePath,
            PositionType.MainPath,
            PositionType.AbyssCave,
        }.ToImmutableArray();

        /// <summary>
        /// Percentage. Value between 0 and 1.
        /// </summary>
        private readonly float resourceHandoverAmount;

        public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
        {
            get
            {
                return missionClusterPositions
                    .Where(p => spawnedResources.ContainsKey(p.Identifier) && AnyAreUncollected(spawnedResources[p.Identifier]))
                    .Select(p => (ModifyMessage(Prefab.SonarLabel, color: false), p.Position));
            }
        }

        public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage);
        public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage);
        public override LocalizedString Description => ModifyMessage(description);
        public override LocalizedString Name => ModifyMessage(base.Name, false);

        public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
        {
            var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType);
            if (ValidPositionTypes.Contains(positionType))
            {
                this.positionType = positionType;
            }

            float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f);
            resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f);

            var configElement = prefab.ConfigElement.GetChildElement("Items");
            foreach (var c in configElement.GetChildElements("Item"))
            {
                var identifier = c.GetAttributeIdentifier("identifier", Identifier.Empty);
                if (identifier.IsEmpty) { continue; }
                if (resourceAmounts.ContainsKey(identifier))
                {
                    resourceAmounts[identifier]++;
                }
                else
                {
                    resourceAmounts.Add(identifier, 1);
                }
            }
        }

        protected override void StartMissionSpecific(Level level)
        {
            if (spawnedResources.Any())
            {
#if DEBUG
                throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})");
#else
                DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
                spawnedResources.Clear();
#endif
            }

            if (relevantLevelResources.Any())
            {
#if DEBUG
                throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})");
#else
                DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
                relevantLevelResources.Clear();
#endif
            }

            if (missionClusterPositions.Any())
            {
#if DEBUG
                throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})");
#else
                DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
                missionClusterPositions.Clear();
#endif
            }

            caves.Clear();

            if (IsClient) { return; }

            foreach ((Identifier identifier, int amount) in resourceAmounts)
            {
                if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab)
                {
                    DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")",
                        contentPackage: Prefab.ContentPackage);
                    continue;
                }

                var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves);
                if (spawnedResources.Count < amount)
                {
                    DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}", 
                        contentPackage: Prefab.ContentPackage);
                }

                if (spawnedResources.None()) { continue; }

                this.spawnedResources.Add(identifier, spawnedResources);

                foreach (var cave in Level.Loaded.Caves)
                {
                    foreach (var spawnedResource in spawnedResources)
                    {
                        if (cave.Area.Contains(spawnedResource.WorldPosition))
                        {
                            cave.MissionsToDisplayOnSonar.Add(this);
                            caves.Add(cave);
                            break;
                        }
                    }
                }
            }

            CalculateMissionClusterPositions();
            FindRelevantLevelResources();
        }

        protected override void UpdateMissionSpecific(float deltaTime)
        {
            if (IsClient) { return; }
            switch (State)
            {
                case 0:
                    if (!EnoughHaveBeenCollected()) { return; }
                    State = 1;
                    break;
                case 1:
                    if (!Submarine.MainSub.AtEitherExit) { return; }
                    State = 2;
                    break;
            }
        }

        protected override bool DetermineCompleted()
        {
            return EnoughHaveBeenCollected();
        }

        protected override void EndMissionSpecific(bool completed)
        {
            failed = !completed && state > 0;
            if (completed)
            {
                if (!IsClient)
                {
                    // When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
                    var handoverResources = new List<Item>();
                    foreach (Identifier identifier in resourceAmounts.Keys)
                    {
                        if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
                        {
                            var collectedResources = availableResources.Where(HasBeenCollected);
                            if (!collectedResources.Any()) { continue; }
                            int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count());
                            for (int i = 0; i < handoverCount; i++)
                            {
                                handoverResources.Add(collectedResources.ElementAt(i));
                            }
                        }
                    }
                    foreach (var resource in handoverResources)
                    {
                        resource.Remove();
                    }
                }
            }
            foreach (var kvp in spawnedResources)
            {
                foreach (var i in kvp.Value)
                {
                    if (i != null && !i.Removed && !HasBeenCollected(i))
                    {
                        i.Remove();
                    }
                }
            }
            spawnedResources.Clear();
            relevantLevelResources.Clear();
            missionClusterPositions.Clear();
        }

        private void FindRelevantLevelResources()
        {
            relevantLevelResources.Clear();
            foreach (var identifier in resourceAmounts.Keys)
            {
                var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
                    i.Submarine == null && i.ParentInventory == null &&
                    (i.GetComponent<Holdable>() is not Holdable h || (h.Attachable && h.Attached)))
                    .ToArray();
                relevantLevelResources.Add(identifier, items);
            }
        }

        private bool EnoughHaveBeenCollected()
        {
            foreach (var kvp in resourceAmounts)
            {
                if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
                {
                    var collected = availableResources.Count(HasBeenCollected);
                    var needed = kvp.Value;
                    if (collected < needed) { return false; }
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        private bool HasBeenCollected(Item item)
        {
            if (item == null) { return false; }
            if (item.Removed) { return false; }
            var owner = item.GetRootInventoryOwner();
            if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
            {
                return true;
            }
            else if (owner is Character c)
            {
                return c.Info != null && GameMain.GameSession.CrewManager.GetCharacterInfos().Contains(c.Info);
            }
            return false;
        }

        private bool AnyAreUncollected(IEnumerable<Item> items)
            => items.Any(i => !HasBeenCollected(i));

        private void CalculateMissionClusterPositions()
        {
            missionClusterPositions.Clear();
            foreach (var kvp in spawnedResources)
            {
                if (kvp.Value.None()) { continue; }
                var pos = Vector2.Zero;
                var itemCount = 0;
                foreach (var i in kvp.Value.Where(i => i != null && !i.Removed))
                {
                    pos += i.WorldPosition;
                    itemCount++;
                }
                pos /= itemCount;
                missionClusterPositions.Add((kvp.Key, pos));
            }
        }

        protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
        {
            int i = 1;
            foreach ((Identifier identifier, int amount) in resourceAmounts)
            {
                Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
                Replace($"[resourcequantity{i}]", amount.ToString());
                i++;
            }
            Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
            return message;

            void Replace(string find, string replace)
            {
                if (color)
                {
                    replace = $"‖color:gui.orange‖{replace}‖end‖";
                }
                message = message.Replace(find, replace);
            }
        }
    }    
}
