using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace Barotrauma
{
    class TriggerAction : EventAction
    {
        public enum TriggerType
        {
            Inside,
            Outside
        }

        [Serialize("", IsPropertySaveable.Yes, description: "Tag of the first entity that will be used for trigger checks.")]
        public Identifier Target1Tag { get; set; }

        [Serialize("", IsPropertySaveable.Yes, description: "Tag of the second entity that will be used for trigger checks.")]
        public Identifier Target2Tag { get; set; }

        [Serialize("", IsPropertySaveable.Yes, description: "If set, the first target has to be within an outpost module of this type.")]
        public Identifier TargetModuleType { get; set; }

        [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first entity when the trigger check succeeds.")]
        public Identifier ApplyToTarget1 { get; set; }

        [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
        public Identifier ApplyToTarget2 { get; set; }

        [Serialize(TriggerType.Inside, IsPropertySaveable.Yes, description: "Determines if the targets must be inside or outside of the radius.")]
        public TriggerType Type { get; set; }

        [Serialize(0.0f, IsPropertySaveable.Yes, description: "Range to activate the trigger.")]
        public float Radius { get; set; }

        [Serialize(true, IsPropertySaveable.Yes, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
        public bool DisableInCombat { get; set; }

        [Serialize(true, IsPropertySaveable.Yes, description: "If true, dead/unconscious characters cannot trigger the action.")]
        public bool DisableIfTargetIncapacitated { get; set; }

        [Serialize(false, IsPropertySaveable.Yes, description: "If true, one target must interact with the other to trigger the action.")]
        public bool WaitForInteraction { get; set; }

        [Serialize(false, IsPropertySaveable.Yes, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")]
        public bool AllowMultipleTargets { get; set; }

        [Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
        public bool CheckAllTargets { get; set; }

        private float distance;

        public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }

        private bool isFinished = false;
        public override bool IsFinished(ref string goTo)
        {
            return isFinished;
        }
        public override void Reset()
        {
            ResetTargetIcons();
            isRunning = false;
            isFinished = false;
        }

        public bool isRunning = false;

        private readonly List<Either<Character, Item>> npcsOrItems = new List<Either<Character, Item>>();
        
        private readonly List<(Entity e1, Entity e2)> triggerers = new List<(Entity e1, Entity e2)>();

        public override void Update(float deltaTime)
        {
            if (isFinished) { return; }

            isRunning = true;

            var targets1 = ParentEvent.GetTargets(Target1Tag);
            if (!targets1.Any()) { return; }

            triggerers.Clear();
            foreach (Entity e1 in targets1)
            {
                if (DisableInCombat && IsInCombat(e1))
                {
                    if (CheckAllTargets)
                    {
                        return;
                    }
                    continue;
                }
                if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated))
                {
                    if (CheckAllTargets)
                    {
                        return;
                    }
                    continue;
                }
                if (!TargetModuleType.IsEmpty)
                {
                    if (!CheckAllTargets && CheckDistanceToHull(e1, out Hull hull))
                    {
                        Trigger(e1, hull);
                        return;
                    }
                    else if (CheckAllTargets)
                    {
                        if (CheckDistanceToHull(e1, out hull))
                        {
                            triggerers.Add((e1, hull));
                        }
                        else
                        {
                            return;
                        }
                    }
                    continue;
                }

                var targets2 = ParentEvent.GetTargets(Target2Tag);
                
                foreach (Entity e2 in targets2)
                {
                    if (e1 == e2)
                    {
                        continue;
                    }
                    if (DisableInCombat && IsInCombat(e2))
                    {
                        if (CheckAllTargets)
                        {
                            return;
                        }
                        continue;
                    }
                    if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated))
                    {
                        if (CheckAllTargets)
                        {
                            return;
                        }
                        continue;
                    }

                    if (WaitForInteraction)
                    {
                        Character player = null;
                        Character npc = null;
                        Item item = null;
                        if (e1 is Character char1)
                        {
                            if (char1.IsBot) 
                            { 
                                npc ??= char1; 
                            }
                            else 
                            { 
                                player = char1; 
                            }
                        }
                        else
                        {
                            item ??= e1 as Item;
                        }
                        if (e2 is Character char2)
                        {
                            if (char2.IsBot) 
                            { 
                                npc ??= char2; 
                            }
                            else 
                            {
                                player = char2; 
                            }
                        }
                        else
                        {
                            item ??= e2 as Item;
                        }

                        if (player != null)
                        {
                            if (npc != null)
                            {
                                if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
                                {
                                    if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc)) 
                                    {
                                        npcsOrItems.Add(npc);
                                    } 
                                    npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
                                    npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated;
#if CLIENT
                                    npc.SetCustomInteract(
                                        (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
                                        TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
#else
                                    npc.SetCustomInteract(
                                        (speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } }, 
                                        TextManager.Get("CampaignInteraction.Talk"));
                                    GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
#endif
                                }
                                if (!AllowMultipleTargets) { return; }
                            }
                            else if (item != null)
                            {
                                if (!npcsOrItems.Any(n => n.TryGet(out Item item2) && item2 == item))
                                {
                                    npcsOrItems.Add(item);
                                }
                                item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
                                if (player.SelectedItem == item ||
                                    player.SelectedSecondaryItem == item ||
                                    (player.Inventory != null && player.Inventory.Contains(item)) ||
                                    (player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
                                {
                                    Trigger(e1, e2);
                                    return;
                                }
                            }
                        }
                    }
                    else
                    {
                        Vector2 pos1 = e1.WorldPosition;
                        Vector2 pos2 = e2.WorldPosition;
                        distance = Vector2.Distance(pos1, pos2);
                        if ((Type == TriggerType.Inside) == IsWithinRadius())
                        {
                            if (!CheckAllTargets)
                            {
                                Trigger(e1, e2);
                                return;
                            }
                            else
                            {
                                triggerers.Add((e1, e2));
                            }
                        }
                        else if (CheckAllTargets)
                        {
                            return;
                        }

                        bool IsWithinRadius() =>
                            ((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
                            ((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
                            Vector2.DistanceSquared(pos1, pos2) < Radius * Radius;
                    }
                }
            }

            foreach (var (e1, e2) in triggerers)
            {
                Trigger(e1, e2);
            }
        }

        private void ResetTargetIcons()
        {
            foreach (var npcOrItem in npcsOrItems)
            {
                if (npcOrItem.TryGet(out Character npc))
                {
                    npc.CampaignInteractionType = CampaignMode.InteractionType.None;
                    npc.SetCustomInteract(null, null);
                    npc.RequireConsciousnessForCustomInteract = true;
    #if SERVER
                    GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
    #endif
                }
                else if (npcOrItem.TryGet(out Item item))
                {
                    item.CampaignInteractionType = CampaignMode.InteractionType.None;
                }
            }
        }

        private bool CheckDistanceToHull(Entity e, out Hull hull)
        {
            hull = null;
            if (Radius <= 0)
            {
                if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
                {
                    hull = character.CurrentHull;
                    return Type == TriggerType.Inside;
                }
                else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
                {
                    hull = item.CurrentHull;
                    return Type == TriggerType.Inside;
                }
                return Type == TriggerType.Outside;
            }
            else
            {
                foreach (Hull potentialHull in Hull.HullList)
                {
                    if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
                    Rectangle hullRect = potentialHull.WorldRect;
                    hullRect.Inflate(Radius, Radius);
                    if (Submarine.RectContains(hullRect, e.WorldPosition))
                    {
                        hull = potentialHull;
                        return Type == TriggerType.Inside;
                    }
                }
                return Type == TriggerType.Outside;
            }
        }

        private static bool IsInCombat(Entity entity)
        {
            if (entity is not Character character) { return false; }
            foreach (Character c in Character.CharacterList)
            {
                if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
                if (c.IsBot && c.AIController is HumanAIController humanAi)
                {
                    if (humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective &&
                        combatObjective.Enemy == character)
                    {
                        return true;
                    }
                }
                else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack))
                {
                    if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private void Trigger(Entity entity1, Entity entity2)
        {
            ResetTargetIcons();
            if (!ApplyToTarget1.IsEmpty)
            {
                ParentEvent.AddTarget(ApplyToTarget1, entity1);
            }
            if (!ApplyToTarget2.IsEmpty)
            {
                ParentEvent.AddTarget(ApplyToTarget2, entity2);
            }

            isRunning = false;
            isFinished = true;
        }

        public override string ToDebugString()
        {
            if (TargetModuleType.IsEmpty)
            {
                return
                    $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" +
                    (WaitForInteraction ?
                        $"Selected non-player target: {(npcsOrItems?.ToString() ?? "<null>").ColorizeObject()}, " :
                        $"Distance: {((int)distance).ColorizeObject()}, ") +
                    $"Radius: {Radius.ColorizeObject()}, " +
                    $"TargetTags: {Target1Tag.ColorizeObject()}, " +
                    $"{Target2Tag.ColorizeObject()})";
            }
            else
            {
                return $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})";
            }
        }
    }
}