using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.Linq;

namespace Barotrauma
{
    public static class CPRSettings
    {
        public static float ReviveChancePerSkill { get; private set; }
        public static float ReviveChanceExponent { get; private set; }
        public static float ReviveChanceMin { get; private set; }
        public static float ReviveChanceMax { get; private set; }
        public static float StabilizationPerSkill { get; private set; }
        public static float StabilizationMin { get; private set; }
        public static float StabilizationMax { get; private set; }
        public static float DamageSkillThreshold { get; private set; }
        public static float DamageSkillMultiplier { get; private set; }

        public static void Load(XElement element)
        {
            ReviveChancePerSkill = Math.Max(element.GetAttributeFloat("revivechanceperskill", 0.01f), 0.0f);
            ReviveChanceExponent = Math.Max(element.GetAttributeFloat("revivechanceexponent", 2.0f), 0.0f);
            ReviveChanceMin = MathHelper.Clamp(element.GetAttributeFloat("revivechancemin", 0.05f), 0.0f, 1.0f);
            ReviveChanceMax = MathHelper.Clamp(element.GetAttributeFloat("revivechancemax", 0.9f), ReviveChanceMin, 1.0f);

            StabilizationPerSkill = Math.Max(element.GetAttributeFloat("stabilizationperskill", 0.01f), 0.0f);
            StabilizationMin = MathHelper.Max(element.GetAttributeFloat("stabilizationmin", 0.05f), 0.0f);
            StabilizationMax = MathHelper.Max(element.GetAttributeFloat("stabilizationmax", 2.0f), StabilizationMin);

            DamageSkillThreshold = MathHelper.Clamp(element.GetAttributeFloat("damageskillthreshold", 40.0f), 0.0f, 100.0f);
            DamageSkillMultiplier = MathHelper.Clamp(element.GetAttributeFloat("damageskillmultiplier", 0.1f), 0.0f, 100.0f);
        }
    }

    class AfflictionPrefab
    {
        public class Effect
        {
            //this effect is applied when the strength is within this range
            public float MinStrength, MaxStrength;

            public readonly float MinVitalityDecrease = 0.0f;
            public readonly float MaxVitalityDecrease = 0.0f;
            
            //how much the strength of the affliction changes per second
            public readonly float StrengthChange = 0.0f;

            public readonly bool MultiplyByMaxVitality;

            public float MinScreenBlurStrength, MaxScreenBlurStrength;
            public float MinScreenDistortStrength, MaxScreenDistortStrength;
            public float MinRadialDistortStrength, MaxRadialDistortStrength;
            public float MinChromaticAberrationStrength, MaxChromaticAberrationStrength;
            public float MinSpeedMultiplier, MaxSpeedMultiplier;
            public float MinBuffMultiplier, MaxBuffMultiplier;

            public float MinResistance, MaxResistance;
            public string ResistanceFor;
            public string DialogFlag;

            //statuseffects applied on the character when the affliction is active
            public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();

            public Effect(XElement element, string parentDebugName)
            {
                MinStrength =  element.GetAttributeFloat("minstrength", 0);
                MaxStrength =  element.GetAttributeFloat("maxstrength", 0);

                MultiplyByMaxVitality = element.GetAttributeBool("multiplybymaxvitality", false);

                MinVitalityDecrease = element.GetAttributeFloat("minvitalitydecrease", 0.0f);
                MaxVitalityDecrease = element.GetAttributeFloat("maxvitalitydecrease", 0.0f);
                MaxVitalityDecrease = Math.Max(MinVitalityDecrease, MaxVitalityDecrease);

                MinScreenDistortStrength = element.GetAttributeFloat("minscreendistort", 0.0f);
                MaxScreenDistortStrength = element.GetAttributeFloat("maxscreendistort", 0.0f);
                MaxScreenDistortStrength = Math.Max(MinScreenDistortStrength, MaxScreenDistortStrength);

                MinRadialDistortStrength = element.GetAttributeFloat("minradialdistort", 0.0f);
                MaxRadialDistortStrength = element.GetAttributeFloat("maxradialdistort", 0.0f);
                MaxRadialDistortStrength = Math.Max(MinRadialDistortStrength, MaxRadialDistortStrength);

                MinChromaticAberrationStrength = element.GetAttributeFloat("minchromaticaberration", 0.0f);
                MaxChromaticAberrationStrength = element.GetAttributeFloat("maxchromaticaberration", 0.0f);
                MaxChromaticAberrationStrength = Math.Max(MinChromaticAberrationStrength, MaxChromaticAberrationStrength);

                MinScreenBlurStrength = element.GetAttributeFloat("minscreenblur", 0.0f);
                MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
                MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);

                ResistanceFor = element.GetAttributeString("resistancefor", "");
                MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
                MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
                MaxResistance = Math.Max(MinResistance, MaxResistance);

                MinSpeedMultiplier = element.GetAttributeFloat("minspeedmultiplier", 1.0f);
                MaxSpeedMultiplier = element.GetAttributeFloat("maxspeedmultiplier", 1.0f);
                MaxSpeedMultiplier = Math.Max(MinSpeedMultiplier, MaxSpeedMultiplier);

                MinBuffMultiplier = element.GetAttributeFloat("minmultiplier", 1.0f);
                MaxBuffMultiplier = element.GetAttributeFloat("maxmultiplier", 1.0f);
                MaxBuffMultiplier = Math.Max(MinBuffMultiplier, MaxBuffMultiplier);

                DialogFlag = element.GetAttributeString("dialogflag", "");

                StrengthChange = element.GetAttributeFloat("strengthchange", 0.0f);

                foreach (XElement subElement in element.Elements())
                {
                    switch (subElement.Name.ToString().ToLowerInvariant())
                    {
                        case "statuseffect":
                            StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
                            break;
                    }
                }
            }
        }

        public static AfflictionPrefab InternalDamage;
        public static AfflictionPrefab Bleeding;
        public static AfflictionPrefab Burn;
        public static AfflictionPrefab OxygenLow;
        public static AfflictionPrefab Bloodloss;
        public static AfflictionPrefab Pressure;
        public static AfflictionPrefab Stun;
        public static AfflictionPrefab Husk;

        public static List<AfflictionPrefab> List = new List<AfflictionPrefab>();

        //Arbitrary string that is used to identify the type of the affliction.
        //Afflictions with the same type stack up, and items may be configured to cure specific types of afflictions.
        public readonly string AfflictionType;

        //Does the affliction affect a specific limb or the whole character
        public readonly bool LimbSpecific;

        //If not a limb-specific affliction, which limb is the indicator shown on in the health menu
        //(e.g. mental health problems on head, lack of oxygen on torso...)
        public readonly LimbType IndicatorLimb;

        public readonly string Identifier;

        public readonly string Name, Description;
        public readonly bool IsBuff;

        public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription;

        //how high the strength has to be for the affliction to take affect
        public readonly float ActivationThreshold = 0.0f;
        //how high the strength has to be for the affliction icon to be shown in the UI
        public readonly float ShowIconThreshold = 0.05f;
        public readonly float MaxStrength = 100.0f;

        //how high the strength has to be for the affliction icon to be shown with a health scanner
        public readonly float ShowInHealthScannerThreshold = 0.05f;

        public float BurnOverlayAlpha;
        public float DamageOverlayAlpha;

        //steam achievement given when the affliction is removed from the controlled character
        public readonly string AchievementOnRemoved;

        public readonly Sprite Icon;
        public readonly Color IconColor;

        private List<Effect> effects = new List<Effect>();

        private Dictionary<string, float> treatmentSuitability = new Dictionary<string, float>();

        private readonly string typeName;

        private readonly ConstructorInfo constructor;

        public Dictionary<string, float> TreatmentSuitability
        {
            get { return treatmentSuitability; }
        }

        public static void LoadAll(IEnumerable<string> filePaths)
        {
            foreach (string filePath in filePaths)
            {
                XDocument doc = XMLExtensions.TryLoadXml(filePath);
                if (doc == null || doc.Root == null) continue;

                foreach (XElement element in doc.Root.Elements())
                {
                    switch (element.Name.ToString().ToLowerInvariant())
                    {
                        case "internaldamage":
                            List.Add(InternalDamage = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "bleeding":
                            List.Add(Bleeding = new AfflictionPrefab(element, typeof(AfflictionBleeding)));
                            break;
                        case "burn":
                            List.Add(Burn = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "oxygenlow":
                            List.Add(OxygenLow = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "bloodloss":
                            List.Add(Bloodloss = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "pressure":
                            List.Add(Pressure = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "stun":
                            List.Add(Stun = new AfflictionPrefab(element, typeof(Affliction)));
                            break;
                        case "husk":
                        case "afflictionhusk":
                            List.Add(Husk = new AfflictionPrefab(element, typeof(AfflictionHusk)));
                            break;
                        case "cprsettings":
                            CPRSettings.Load(element);
                            break;
                        default:
                            List.Add(new AfflictionPrefab(element));
                            break;
                    }
                }
            }

            if (InternalDamage == null) DebugConsole.ThrowError("Affliction \"Internal Damage\" not defined in the affliction prefabs.");
            if (Bleeding == null) DebugConsole.ThrowError("Affliction \"Bleeding\" not defined in the affliction prefabs.");
            if (Burn == null) DebugConsole.ThrowError("Affliction \"Burn\" not defined in the affliction prefabs.");
            if (OxygenLow == null) DebugConsole.ThrowError("Affliction \"OxygenLow\" not defined in the affliction prefabs.");
            if (Bloodloss == null) DebugConsole.ThrowError("Affliction \"Bloodloss\" not defined in the affliction prefabs.");
            if (Pressure == null) DebugConsole.ThrowError("Affliction \"Pressure\" not defined in the affliction prefabs.");
            if (Stun == null) DebugConsole.ThrowError("Affliction \"Stun\" not defined in the affliction prefabs.");
            if (Husk == null) DebugConsole.ThrowError("Affliction \"Husk\" not defined in the affliction prefabs.");
        }

        public AfflictionPrefab(XElement element, Type type = null)
        {
            typeName = type == null ? element.Name.ToString() : type.Name;

            Identifier = element.GetAttributeString("identifier", "");

            AfflictionType = element.GetAttributeString("type", "");
            Name = TextManager.Get("AfflictionName." + Identifier, true) ?? element.GetAttributeString("name", "");
            Description = TextManager.Get("AfflictionDescription." + Identifier, true) ?? element.GetAttributeString("description", "");
            IsBuff = element.GetAttributeBool("isbuff", false);

            LimbSpecific = element.GetAttributeBool("limbspecific", false);
            if (!LimbSpecific)
            {
                string indicatorLimbName = element.GetAttributeString("indicatorlimb", "Torso");
                if (!Enum.TryParse(indicatorLimbName, out IndicatorLimb))
                {
                    DebugConsole.ThrowError("Error in affliction prefab " + Name + " - limb type \"" + indicatorLimbName + "\" not found.");
                }
            }

            ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
            ShowIconThreshold   = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
            MaxStrength         = element.GetAttributeFloat("maxstrength", 100.0f);

            ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));

            DamageOverlayAlpha  = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
            BurnOverlayAlpha    = element.GetAttributeFloat("burnoverlayalpha", 0.0f);

            CauseOfDeathDescription     = TextManager.Get("AfflictionCauseOfDeath." + Identifier, true) ?? element.GetAttributeString("causeofdeathdescription", "");
            SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + Identifier, true) ?? element.GetAttributeString("selfcauseofdeathdescription", "");

            AchievementOnRemoved = element.GetAttributeString("achievementonremoved", "");
            
            foreach (XElement subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "icon":
                        Icon = new Sprite(subElement);
                        IconColor = subElement.GetAttributeColor("color", Color.White);
                        break;
                    case "effect":
                        effects.Add(new Effect(subElement, Name));
                        break;
                }
            }

            try
            {
                if (type == null)
                {
                    type = Type.GetType("Barotrauma." + typeName, true, true);
                    if (type == null)
                    {
                        DebugConsole.ThrowError("Could not find an affliction class of the type \"" + typeName + "\".");
                        return;
                    }
                }
            }
            catch
            {
                DebugConsole.ThrowError("Could not find an affliction class of the type \"" + typeName + "\".");
                return;
            }

            constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
        }

        public override string ToString()
        {
            return "AfflictionPrefab (" + Name + ")";
        }

        public Affliction Instantiate(float strength, Character source = null)
        {
            object instance = null;
            try
            {
                instance = constructor.Invoke(new object[] { this, strength });
            }
            catch (Exception ex)
            {
                DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
            }
            Affliction affliction = instance as Affliction;
            affliction.Source = source;
            return affliction;
        }

        public Effect GetActiveEffect(float currentStrength)
        {
            foreach (Effect effect in effects)
            {
                if (currentStrength > effect.MinStrength && currentStrength <= effect.MaxStrength) return effect;
            }

            //if above the strength range of all effects, use the highest strength effect
            Effect strongestEffect = null;
            float largestStrength = currentStrength;
            foreach (Effect effect in effects)
            {
                if (currentStrength > effect.MaxStrength && 
                    (strongestEffect == null || effect.MaxStrength > largestStrength))
                {
                    strongestEffect = effect;
                    largestStrength = effect.MaxStrength;
                }
            }
            return strongestEffect;
        }

        public float GetTreatmentSuitability(Item item)
        {
            if (item == null || !treatmentSuitability.ContainsKey(item.Prefab.Identifier.ToLowerInvariant()))
            {
                return 0.0f;
            }
            return treatmentSuitability[item.Prefab.Identifier.ToLowerInvariant()];
        }
    }
}
