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

namespace Barotrauma
{
    abstract class ShipIssueWorker
    {
        public const float MaxImportance = 100f;
        public const float MinImportance = 0f;
        public Order SuggestedOrder { get; }

        private float importance;
        public float Importance 
        {
            get
            {
                return importance;
            }
            set
            {
                importance = MathHelper.Clamp(value, MinImportance, MaxImportance);
            } 
        }
        public float CurrentRedundancy { get; set; }

        public readonly ShipCommandManager shipCommandManager;
        public Identifier Option => SuggestedOrder.Option;
        public Character OrderedCharacter { get; set; }
        public Order CurrentOrder { get; private set; }
        public ItemComponent TargetItemComponent => SuggestedOrder.TargetItemComponent;
        public Item TargetItem => SuggestedOrder.TargetEntity as Item;
        public bool Active { get; protected set; } = true; // used to turn off the instance if errors are detected

        protected virtual Character CommandingCharacter => shipCommandManager.character;
        public virtual float TimeSinceLastAttempt { get; set; }
        public virtual float RedundantIssueModifier => 0.5f;
        public virtual bool StopDuringEmergency => true; // limit certain issue assessments when invaded by the enemies
        public virtual bool AllowEasySwitching => false;

        public ShipIssueWorker(ShipCommandManager shipCommandManager, Order suggestedOrder)
        {
            this.shipCommandManager = shipCommandManager;
            SuggestedOrder = suggestedOrder;
        }

        public void SetOrder(Character orderedCharacter)
        {
            OrderedCharacter = orderedCharacter;
            if (OrderedCharacter.AIController is HumanAIController humanAI && 
                humanAI.ObjectiveManager.CurrentOrders.None(o => o.MatchesOrder(SuggestedOrder.Identifier, Option) && o.TargetEntity == TargetItem))
            {
                bool orderGivenByDifferentCharacter = orderedCharacter != CommandingCharacter;
                if (orderGivenByDifferentCharacter)
                {
                    CommandingCharacter.Speak(SuggestedOrder.GetChatMessage(OrderedCharacter.Name, "", givingOrderToSelf: false),
                        minDurationBetweenSimilar: 5,
                        identifier: ("GiveOrder." + SuggestedOrder.Prefab.Identifier).ToIdentifier());
                }
                CurrentOrder = SuggestedOrder
                    .WithOption(Option)
                    .WithItemComponent(TargetItem, TargetItemComponent)
                    .WithOrderGiver(CommandingCharacter)
                    .WithManualPriority(CharacterInfo.HighestManualOrderPriority);
                OrderedCharacter.SetOrder(CurrentOrder, CommandingCharacter != OrderedCharacter);
                if (orderGivenByDifferentCharacter)
                {
                    OrderedCharacter.Speak(TextManager.Get("DialogAffirmative").Value, delay: 1.0f,
                        minDurationBetweenSimilar: 5,
                        identifier: ("ReceiveOrder." + SuggestedOrder.Prefab.Identifier).ToIdentifier());
                }
            }
            TimeSinceLastAttempt = 0f;
        }

        public void RemoveOrder()
        {
            OrderedCharacter = null;
            CurrentOrder = null;
        }

        protected virtual bool IsIssueViable() 
        {
            return true;
        }

        public float CalculateImportance(bool isEmergency)
        {
            Importance = 0f; // reset anything that needs resetting

            if (!Active)
            {
                return Importance;
            }

            Active = IsIssueViable();

            if (isEmergency && StopDuringEmergency)
            {
                return Importance;
            }

            CalculateImportanceSpecific();

            // if there are other orders of the same type already being attended to, such as fixing leaks
            // reduce the relative importance of this issue
            CurrentRedundancy = 1f;
            foreach (ShipIssueWorker shipIssueWorker in shipCommandManager.ShipIssueWorkers)
            {
                if (shipIssueWorker.GetType() == GetType() && shipIssueWorker != this && shipIssueWorker.OrderAttendedTo()) 
                {
                    CurrentRedundancy *= RedundantIssueModifier;
                }
            }
            Importance *= CurrentRedundancy;

            return Importance;
        }

        public bool OrderAttendedTo(float timeSinceLastCheck = 0f)
        {
            if (!HumanAIController.IsActive(OrderedCharacter))
            {
                return false;
            }

            // accept only the highest priority order
            if (CurrentOrder == null || OrderedCharacter.GetCurrentOrderWithTopPriority() != CurrentOrder)
            {
#if DEBUG
                ShipCommandManager.ShipCommandLog($"{this} is no longer the top priority of {OrderedCharacter}, considering the issue unattended.");
#endif
                return false;
            }

            if (!shipCommandManager.AbleToTakeOrder(OrderedCharacter))
            {
#if DEBUG
                ShipCommandManager.ShipCommandLog(OrderedCharacter + " was unable to perform assigned order in " + this);
#endif
                return false;
            }
            return true;
        }
        public abstract void CalculateImportanceSpecific();
    }
}
