using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Globalization;

namespace Barotrauma.Items.Components
{
    partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
    {
        //[power/min]        
        private float capacity;
        private float adjustedCapacity;

        private float charge, prevCharge;

        //how fast the battery can be recharged
        private float maxRechargeSpeed;

        //how fast it's currently being recharged (can be changed, so that
        //charging can be slowed down or disabled if there's a shortage of power)
        private float rechargeSpeed;
        private float lastSentCharge;

        //charge indicator description
        protected Vector2 indicatorPosition, indicatorSize;

        protected bool isHorizontal;

        protected override PowerPriority Priority { get { return PowerPriority.Battery; } }

        private float currPowerOutput;
        public float CurrPowerOutput
        {
            get { return currPowerOutput; }
            private set
            {
                System.Diagnostics.Debug.Assert(value >= 0.0f, $"Tried to set PowerContainer's output to a negative value ({value})");
                currPowerOutput = Math.Max(0, value);
            }
        }

        [Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the progress bar indicating the charge of the item. In pixels as an offset from the upper left corner of the sprite.")]
        public Vector2 IndicatorPosition
        {
            get { return indicatorPosition; }
            set { indicatorPosition = value; }
        }

        [Serialize("0,0", IsPropertySaveable.Yes, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
        public Vector2 IndicatorSize
        {
            get { return indicatorSize; }
            set { indicatorSize = value; }
        }

        [Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
        public bool IsHorizontal
        {
            get { return isHorizontal; }
            set { isHorizontal = value; }
        }

        [Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "Maximum output of the device when fully charged (kW).")]
        public float MaxOutPut { set; get; }

        [Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
        public float Capacity
        {
            get => capacity;
            set
            {
                capacity = Math.Max(value, 1.0f);
                adjustedCapacity = GetCapacity();
            }
        }

        [Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current charge of the device.")]
        public float Charge
        {
            get { return charge; }
            set 
            {
                if (!MathUtils.IsValid(value)) return;
                charge = MathHelper.Clamp(value, 0.0f, adjustedCapacity); 

                //send a network event if the charge has changed by more than 5%
                if (Math.Abs(charge - lastSentCharge) / adjustedCapacity > 0.05f)
                {
#if SERVER
                    if (GameMain.Server != null && item.FullyInitialized) { item.CreateServerEvent(this); }
#endif
                    lastSentCharge = charge;
                }
            }
        }

        public float ChargePercentage => MathUtils.Percentage(Charge, adjustedCapacity);
        
        [Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
        public float MaxRechargeSpeed
        {
            get { return maxRechargeSpeed; }
            set { maxRechargeSpeed = Math.Max(value, 1.0f); }
        }

        [Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
        public float RechargeSpeed
        {
            get { return rechargeSpeed; }
            set
            {
                if (!MathUtils.IsValid(value)) return;              
                rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
                rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
                if (isRunning)
                {
                    HasBeenTuned = true;
                }
            }
        }

        [Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
        public bool ExponentialRechargeSpeed { get; set; }

        private float efficiency;
        [Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
        public float Efficiency
        {
            get { return efficiency; }
            set { efficiency = MathHelper.Clamp(value, 0.0f, 1.0f); }
        }

        private bool flipIndicator;
        [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge be flipped to fill from the other side.")]
        public bool FlipIndicator
        {
            get { return flipIndicator; }
            set { flipIndicator = value; }
        }

        public bool OutputDisabled { get; private set; }

        public float RechargeRatio => RechargeSpeed / MaxRechargeSpeed;

        public const float aiRechargeTargetRatio = 0.5f;
        private bool isRunning;

        public bool HasBeenTuned { get; private set; }

        public PowerContainer(Item item, ContentXElement element)
            : base(item, element)
        {
            IsActive = true;
            InitProjSpecific();
            prevCharge = Charge;
        }

        partial void InitProjSpecific();

        public override bool Pick(Character picker)
        {
            return picker != null;
        }

        public override void Update(float deltaTime, Camera cam)
        {
            if (item.Connections == null) 
            {
                IsActive = false;
                return; 
            }

            adjustedCapacity = GetCapacity();
            isRunning = true;
            float chargeRatio = charge / adjustedCapacity;
            
            if (chargeRatio > 0.0f)
            {
                ApplyStatusEffects(ActionType.OnActive, deltaTime);
            }

            float loadReading = 0;
            if (powerOut != null && powerOut.Grid != null)
            {
                loadReading = powerOut.Grid.Load;
            }

            item.SendSignal(((int)Math.Round(CurrPowerOutput)).ToString(), "power_value_out");
            item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
            item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
            item.SendSignal(((int)Math.Round(Charge / adjustedCapacity * 100)).ToString(), "charge_%");
            item.SendSignal(((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate");
        }

        /// <summary>
        /// Returns the power consumption if checking the powerIn connection, or a negative value if the output can provide power when checking powerOut.
        /// Power consumption is proportional to set recharge speed and if there is less than max charge.
        /// </summary>
        public override float GetCurrentPowerConsumption(Connection connection = null)
        {
            if (connection == powerIn)
            {
                //Don't draw power if fully charged
                if (charge >= adjustedCapacity)
                {
                    charge = adjustedCapacity;
                    return 0;
                }
                else
                {
                    if (item.Condition <= 0.0f) { return 0.0f; }

                    float missingCharge = adjustedCapacity - charge;
                    float targetRechargeSpeed = rechargeSpeed;

                    if (ExponentialRechargeSpeed)
                    {
                        targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
                    }
                    //For the last kwMin scale the recharge rate linearly to prevent overcharging and to have a smooth cutoff
                    if (missingCharge < 1.0f)
                    {
                        targetRechargeSpeed *= missingCharge;
                    }

                    return MathHelper.Clamp(targetRechargeSpeed, 0, MaxRechargeSpeed);
                }
            }
            else
            {
                CurrPowerOutput = 0;
                return charge > 0 ? -1 : 0;
            }
        }

        /// <summary>
        /// Minimum and maximum output for the queried connection.
        /// Powerin min max equals CurrPowerConsumption as its abnormal for there to be power out.
        /// PowerOut min power out is zero and max is the maxout unless below 10% charge where
        /// the output is scaled relative to the 10% charge.
        /// </summary>
        /// <param name="connection">Connection being queried</param>
        /// <param name="load">Current grid load</param>
        /// <returns>Minimum and maximum power output for the connection</returns>
        public override PowerRange MinMaxPowerOut(Connection connection, float load = 0)
        {
            if (OutputDisabled) { return PowerRange.Zero; }
            if (connection == powerOut)
            {
                float maxOutput;
                float chargeRatio = prevCharge / adjustedCapacity;
                if (chargeRatio < 0.1f)
                {
                    maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
                }
                else
                {
                    maxOutput = MaxOutPut;
                }

                //Limit max power out to not exceed the charge of the container
                maxOutput = Math.Min(maxOutput, prevCharge * 60 / UpdateInterval);
                return new PowerRange(0.0f, maxOutput);
            }

            return PowerRange.Zero;
        }

        /// <summary>
        /// Finalized power out from the container for the connection, provided the given grid information
        /// Output power based on the maxpower all batteries can output. So all batteries can
        /// equally share powerout based on their output capabilities.
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="power"></param>
        /// <param name="minMaxPower"></param>
        /// <param name="load"></param>
        /// <returns></returns>
        public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
        {
            if (OutputDisabled) { return 0; }
            //Only power out connection can provide power and Max poweroutput can't be negative
            if (connection == powerOut && minMaxPower.Max > 0)
            {
                //Set power output based on the relative max power output capabilities and load demand
                CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * MinMaxPowerOut(connection, load).Max;
                return CurrPowerOutput;
            }
            return 0.0f;            
        }

        /// <summary>
        /// When the corresponding grid connection is resolved, adjust the container's charge.
        /// </summary>
        public override void GridResolved(Connection conn)
        {
            if (conn == powerIn)
            {
                //Increase charge based on how much power came in from the grid
                Charge += (CurrPowerConsumption * Voltage) / 60 * UpdateInterval * efficiency;
            }
            else
            {
                //Decrease charge based on how much power is leaving the device
                Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, adjustedCapacity);
                prevCharge = Charge;
            }
        }

        public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
        {
            if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }

            if (objective.Override)
            {
                HasBeenTuned = false;
            }
            if (HasBeenTuned) { return true; }

            float targetRatio = objective.Option.IsEmpty || objective.Option == "charge" ? aiRechargeTargetRatio : -1;
            if (targetRatio > 0 || float.TryParse(objective.Option.Value, out targetRatio))
            {
                if (Math.Abs(rechargeSpeed - maxRechargeSpeed * targetRatio) > 0.05f)
                {
#if SERVER
                    item.CreateServerEvent(this);
#endif
                    RechargeSpeed = maxRechargeSpeed * targetRatio;
#if CLIENT
                    if (rechargeSpeedSlider != null)
                    {
                        rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
                    }
#endif                   
                    if (character.IsOnPlayerTeam)
                    {
                        character.Speak(TextManager.GetWithVariables("DialogChargeBatteries",
                            ("[itemname]", item.Name, FormatCapitals.Yes),
                            ("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
                            null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
                    }
                }
            }
            else
            {
                if (rechargeSpeed > 0.0f)
                {
#if SERVER
                    item.CreateServerEvent(this);
#endif
                    RechargeSpeed = 0.0f;
#if CLIENT
                    if (rechargeSpeedSlider != null)
                    {
                        rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
                    }
#endif
                    if (character.IsOnPlayerTeam)
                    {
                        character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries",
                            ("[itemname]", item.Name, FormatCapitals.Yes),
                            ("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
                            null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
                    }
                }
            }

            return true;
        }

        public override void ReceiveSignal(Signal signal, Connection connection)
        {
            if (connection.IsPower) { return; }
            switch (connection.Name)
            {
                case "disable_output":
                    OutputDisabled = signal.value != "0";
                    break;
                case "set_rate":
                    if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
                    {
                        if (!MathUtils.IsValid(tempSpeed)) { return; }

                        float rechargeRate = MathHelper.Clamp(tempSpeed / 100.0f, 0.0f, 1.0f);
                        RechargeSpeed = rechargeRate * MaxRechargeSpeed;
#if CLIENT
                        if (rechargeSpeedSlider != null)
                        {
                            rechargeSpeedSlider.BarScroll = rechargeRate;
                        }
#endif
                    }
                    break;

            }
        }

        public float GetCapacity() => item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.BatteryCapacity, Capacity);
    }
}
