using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;

namespace Barotrauma
{
    class AIObjectiveRepairItem : AIObjective
    {
        public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();

        public override string DebugTag => $"{Identifier} ({Item?.Name ?? "null"})";

        protected override bool AllowInFriendlySubs => true;
        public override bool KeepDivingGearOn => Item?.CurrentHull == null;
        protected override bool AllowWhileHandcuffed => false;

        public Item Item { get; private set; }

        private AIObjectiveGoTo goToObjective;
        private AIObjectiveContainItem refuelObjective;
        private RepairTool repairTool;

        private const float WaitTimeBeforeRepair = 0.5f;
        private float waitTimer;

        private bool IsRepairing() => IsRepairing(character, Item);
        private readonly bool isPriority;

        public static bool IsRepairing(Character character, Item item) => character.SelectedItem == item && item.Repairables.Any(r => r.CurrentFixer == character);

        public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
            : base(character, objectiveManager, priorityModifier)
        {
            Item = item;
            this.isPriority = isPriority;
        }

        protected override float GetPriority()
        {
            if (!IsAllowed) { HandleDisallowed(); }
            if (Item.IgnoreByAI(character))
            {
                Abandon = true;
            }
            if (Abandon)
            {
                if (IsRepairing())
                {
                    Item.Repairables.ForEach(r => r.StopRepairing(character));
                }
                return Priority;
            }
            if (AIObjectiveRepairItems.IsItemRepairedByAnother(character, Item))
            {
                Priority = 0;
                IsCompleted = true;
            }
            else if (Item.IsClaimedByBallastFlora)
            {
                Priority = 0;
            }
            else
            {
                float distanceFactor = 1;
                if (!isPriority && Item.CurrentHull != character.CurrentHull)
                {
                    distanceFactor = GetDistanceFactor(Item.WorldPosition, factorAtMaxDistance: 0.25f, verticalDistanceMultiplier: 5, maxDistance: 4000);
                }
                float requiredSuccessFactor = objectiveManager.HasOrder<AIObjectiveRepairItems>() ? 0 : AIObjectiveRepairItems.RequiredSuccessFactor;
                float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor) / 100;
                bool isSelected = IsRepairing();
                float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
                float devotion = (CumulatedDevotion + selectedBonus) / 100;
                float reduction = isPriority ? 1 : isSelected ? 2 : 3;
                float max = AIObjectiveManager.LowestOrderPriority - reduction;
                float highestWeight = -1;
                foreach (Identifier tag in Item.Prefab.Tags)
                {
                    if (JobPrefab.ItemRepairPriorities.TryGetValue(tag, out float weight) && weight > highestWeight)
                    {
                        highestWeight = weight;
                    }
                }
                if (highestWeight == -1)
                {
                    // Predefined weight not found.
                    highestWeight = 1;
                }
                Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * highestWeight * PriorityModifier), 0, 1));
            }
            return Priority;
        }

        protected override bool CheckObjectiveState()
        {
            IsCompleted = Item.IsFullCondition;
            if (character.IsOnPlayerTeam && IsCompleted && IsRepairing())
            {
                character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "itemrepaired".ToIdentifier(), 10.0f);
            }
            return IsCompleted;
        }

        protected override void Act(float deltaTime)
        {
            // Only continue when the get item sub objectives have been completed.
            if (subObjectives.Any()) { return; }
            foreach (Repairable repairable in Item.Repairables)
            {
                if (!repairable.HasRequiredItems(character, false))
                {
                    //make sure we have all the items required to fix the target item
                    foreach (var kvp in repairable.RequiredItems)
                    {
                        foreach (RelatedItem requiredItem in kvp.Value)
                        {
                            var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, equip: true)
                            {
                                AllowVariants = requiredItem.AllowVariants
                            };
                            if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
                            {
                                if (character.IsOnPlayerTeam)
                                {
                                    getItemObjective.Abandoned += () => character.Speak(TextManager.Get("dialogcannotfindrequireditemtorepair").Value, null, 0.0f, "dialogcannotfindrequireditemtorepair".ToIdentifier(), 10.0f);
                                }
                            }
                            subObjectives.Add(getItemObjective);
                        }
                    }
                    return;
                }
            }
            if (repairTool == null)
            {
                FindRepairTool();
            }
            if (repairTool != null)
            {
                if (repairTool.RequiredItems.TryGetValue(RelatedItem.RelationType.Contained, out var requiredItems))
                {
                    if (repairTool.Item.OwnInventory == null)
                    {
#if DEBUG
                        DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"{repairTool}\" has no proper inventory.");
#endif
                        Abandon = true;
                        return;
                    }
                    RelatedItem item = null;
                    Item fuel = null;
                    foreach (RelatedItem requiredItem in requiredItems)
                    {
                        item = requiredItem;
                        fuel = repairTool.Item.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
                        if (fuel != null) { break; }
                    }
                    if (fuel == null)
                    {
                        RemoveSubObjective(ref goToObjective);
                        TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
                        {
                            RemoveExisting = true
                        },
                        onCompleted: () => RemoveSubObjective(ref refuelObjective),
                        onAbandon: () => Abandon = true);
                        return;
                    }
                }
            }
            if (character.CanInteractWith(Item, out _, checkLinked: false))
            {
                waitTimer += deltaTime;
                if (waitTimer < WaitTimeBeforeRepair) { return; }

                HumanAIController.FaceTarget(Item);

                bool repairThroughRepairInterface = false;
                foreach (Repairable repairable in Item.Repairables)
                {
                    if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
                    {
                        // Someone else is repairing the target. Abandon the objective if the other is better at this than us.
                        Abandon = repairable.CurrentFixer.IsPlayer || repairable.DegreeOfSuccess(character) < repairable.DegreeOfSuccess(repairable.CurrentFixer);
                    }
                    if (!Abandon)
                    {
                        if (character.SelectedItem != Item)
                        {
                            if (Item.TryInteract(character, forceUseKey: true) ||
                                Item.TryInteract(character, forceSelectKey: true))
                            {
                                character.SelectedItem = Item;
                                repairThroughRepairInterface = true;
                            }
                            else
                            {
                                Abandon = true;
                            }
                        }
                        CheckPreviousCondition(deltaTime);
                    }
                    if (Abandon)
                    {
                        if (character.IsOnPlayerTeam && IsRepairing())
                        {
                            character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
                        }
                        repairable.StopRepairing(character);
                    }
                    else if (repairable.CurrentFixer != character)
                    {
                        repairable.StartRepairing(character, Repairable.FixActions.Repair);
                    }
                    else
                    {
                        repairThroughRepairInterface = true;
                    }
                    break;
                }

                if (!repairThroughRepairInterface && repairTool != null && !Abandon)
                {
                    OperateRepairTool(deltaTime);
                }
            }
            else
            {
                waitTimer = 0.0f;
                RemoveSubObjective(ref refuelObjective);
                // If cannot reach the item, approach it.
                TryAddSubObjective(ref goToObjective,
                    constructor: () =>
                    {
                        var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
                        {
                            DialogueIdentifier = AIObjectiveGoTo.DialogCannotReachTarget,
                            TargetName = Item.Name,
                            SpeakCannotReachCondition = () => isPriority
                        };
                        if (repairTool != null)
                        {
                            objective.CloseEnough = AIObjectiveFixLeak.CalculateReach(repairTool, character);
                        }
                        return objective;
                    },                    
                    onAbandon: () =>
                    {
                        Abandon = true;
                        if (character.IsOnPlayerTeam && IsRepairing())
                        {
                            character.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, FormatCapitals.Yes).Value, null, 0.0f, "cannotrepair".ToIdentifier(), 10.0f);
                        }
                    });
            }
        }

        private const float conditionCheckDelay = 1;
        private float conditionCheckTimer;
        private float previousCondition;
        private void CheckPreviousCondition(float deltaTime)
        {
            if (Item == null || Item.Removed) { return; }
            conditionCheckTimer -= deltaTime;
            if (conditionCheckTimer > 0) { return; }
            conditionCheckTimer = conditionCheckDelay;
            if (previousCondition > -1 && Item.Condition < previousCondition)
            {
                // If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
                Abandon = true;
            }
            else
            {
                // If the previous condition is not yet stored or if it's valid (greater or equal to current condition), save the condition for the next check here.
                previousCondition = Item.Condition;
            }
        }

        private void FindRepairTool()
        {
            foreach (Repairable repairable in Item.Repairables)
            {
                foreach (var kvp in repairable.RequiredItems)
                {
                    foreach (RelatedItem requiredItem in kvp.Value)
                    {
                        foreach (var item in character.Inventory.AllItems)
                        {
                            if (requiredItem.MatchesItem(item))
                            {
                                repairTool = item.GetComponent<RepairTool>();
                            }
                        }
                    }
                }
            }
        }

        private void OperateRepairTool(float deltaTime)
        {
            character.CursorPosition = Item.WorldPosition;
            if (character.Submarine != null)
            {
                character.CursorPosition -= character.Submarine.Position;
            }
            if (repairTool.Item.RequireAimToUse)
            {
                character.SetInput(InputType.Aim, false, true);
            }
            Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position;
            if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2))
            {
                // Too close -> steer away
                character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2);
            }
            else
            {
                character.AIController.SteeringManager.Reset();
            }
            if (VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
            {
                repairTool.Use(deltaTime, character);
            }
        }

        public override void Reset()
        {
            base.Reset();
            goToObjective = null;
            refuelObjective = null;
            repairTool = null;
        }
    }
}
