using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Barotrauma.Abilities
{
    abstract class CharacterAbilityGroup
    {
        public CharacterTalent CharacterTalent { get; }
        public Character Character { get; }

        // currently only used to turn off simulation if random conditions are in use
        public bool IsActive { get; private set; } = true;

        public readonly AbilityEffectType AbilityEffectType;

        protected readonly int maxTriggerCount;
        protected int timesTriggered = 0;

        // add support for OR conditions?
        protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();

        /// <summary>
        /// List of abilities that are triggered by this group.
        /// Fallback abilities are triggered if the conditional fails
        /// </summary>
        protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>(),
                                                  fallbackAbilities = new List<CharacterAbility>();

        public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, ContentXElement abilityElementGroup)
        {
            AbilityEffectType = abilityEffectType;
            CharacterTalent = characterTalent ?? throw new ArgumentNullException(nameof(characterTalent));
            Character = CharacterTalent.Character;
            maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
            foreach (var subElement in abilityElementGroup.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "abilities":
                        LoadAbilities(subElement);
                        break;
                    case "fallbackabilities":
                        LoadFallbackAbilities(subElement);
                        break;
                    case "condition":
                    case "conditions":
                        LoadConditions(subElement);
                        break;
                    default:
                        DebugConsole.ThrowError($"Error in talent {characterTalent.Prefab.Identifier}: unrecognized xml element \"{subElement.Name}\".");
                        break;
                }
            }

            switch (abilityEffectType)
            {
                case AbilityEffectType.OnDieToCharacter:
                    if (characterAbilities.Any(a => a.RequiresAlive))
                    {
                        DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.",
                            characterTalent.Prefab.ContentPackage);
                    }
                    break;
            }
        }

        public void ActivateAbilityGroup(bool addingFirstTime)
        {
            if (!CheckActivatingCondition()) { return; }

            foreach (var characterAbility in characterAbilities)
            {
                characterAbility.InitializeAbility(addingFirstTime);
            }

            foreach (var characterAbility in fallbackAbilities)
            {
                characterAbility.InitializeAbility(addingFirstTime);
            }
        }

        private bool CheckActivatingCondition()
        {
            if (AbilityEffectType is not AbilityEffectType.None) { return true; }
            return !abilityConditions.Any(static abilityCondition => !abilityCondition.MatchesCondition());
        }

        public void LoadConditions(ContentXElement conditionElements)
        {
            foreach (ContentXElement conditionElement in conditionElements.Elements())
            {
                AbilityCondition newCondition = ConstructCondition(CharacterTalent, conditionElement);

                if (newCondition == null)
                {
                    DebugConsole.ThrowError($"AbilityCondition was not found in talent {CharacterTalent.DebugIdentifier}!",
                        contentPackage: conditionElement.ContentPackage);
                    return;
                }

                if (!newCondition.AllowClientSimulation && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
                {
                    IsActive = false;
                }

                abilityConditions.Add(newCondition);
            }
        }

        public void AddAbility(CharacterAbility characterAbility)
        {
            if (characterAbility == null)
            {
                DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
                    contentPackage: CharacterTalent.Prefab.ContentPackage);
                return;
            }

            characterAbilities.Add(characterAbility);
        }

        public void AddFallbackAbility(CharacterAbility characterAbility)
        {
            if (characterAbility == null)
            {
                DebugConsole.ThrowError($"Trying to add null ability for talent {CharacterTalent.DebugIdentifier}!",
                    contentPackage: CharacterTalent.Prefab.ContentPackage);
                return;
            }

            fallbackAbilities.Add(characterAbility);
        }

        // XML
        private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true)
        {
            Type conditionType;
            string type = conditionElement.Name.ToString().ToLowerInvariant();
            try
            {
                conditionType = ReflectionUtils.GetTypeWithBackwardsCompatibility(ToolBox.BarotraumaAssembly, "Barotrauma.Abilities", type, false, true);
                if (conditionType == null)
                {
                    if (errorMessages)
                    {
                        DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")",
                            contentPackage: characterTalent.Prefab.ContentPackage);
                    }
                    return null;
                }
            }
            catch (Exception e)
            {
                if (errorMessages)
                {
                    DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + characterTalent.DebugIdentifier + ")", e,
                        contentPackage: characterTalent.Prefab.ContentPackage);
                }
                return null;
            }

            object[] args = { characterTalent, conditionElement };

            AbilityCondition newCondition;
            try
            {
                newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
            }
            catch (TargetInvocationException e)
            {
                DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ".", e.InnerException,
                    contentPackage: characterTalent.Prefab.ContentPackage);
                return null;
            }

            if (newCondition == null)
            {
                DebugConsole.ThrowError("Error while creating an instance of an ability condition of the type " + conditionType + ", instance was null",
                    contentPackage: characterTalent.Prefab.ContentPackage);
                return null;
            }

            return newCondition;
        }

        private void LoadAbilities(ContentXElement abilityElements)
        {
            foreach (var abilityElementGroup in abilityElements.Elements())
            {
                AddAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
            }
        }

        private void LoadFallbackAbilities(ContentXElement abilityElements)
        {
            foreach (var abilityElementGroup in abilityElements.Elements())
            {
                AddFallbackAbility(ConstructAbility(abilityElementGroup, CharacterTalent));
            }
        }

        private CharacterAbility ConstructAbility(ContentXElement abilityElement, CharacterTalent characterTalent)
        {
            CharacterAbility newAbility = CharacterAbility.Load(abilityElement, this);

            if (newAbility == null)
            {
                DebugConsole.ThrowError($"Unable to create an ability for {characterTalent.DebugIdentifier}!",
                    contentPackage: characterTalent.Prefab.ContentPackage);
                return null;
            }

            return newAbility;
        }

        public static List<StatusEffect> ParseStatusEffects(CharacterTalent characterTalent, ContentXElement statusEffectElements)
        {
            if (statusEffectElements == null)
            {
                DebugConsole.ThrowError("StatusEffect list was not found in talent " + characterTalent.DebugIdentifier,
                    contentPackage: characterTalent.Prefab.ContentPackage);
                return null;
            }

            List<StatusEffect> statusEffects = new List<StatusEffect>();

            foreach (var statusEffectElement in statusEffectElements.Elements())
            {
                var statusEffect = StatusEffect.Load(statusEffectElement, characterTalent.DebugIdentifier);
                statusEffects.Add(statusEffect);
            }

            return statusEffects;
        }

        public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
        {
            //backwards compatibility
            if (statTypeString.Equals("MedicalItemDurationMultiplier", StringComparison.OrdinalIgnoreCase))
            {
                statTypeString = "BuffItemApplyingMultiplier";
            }
            if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
            {
                DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
            }
            return statType;
        }

        public static List<Affliction> ParseAfflictions(CharacterTalent characterTalent, ContentXElement afflictionElements)
        {
            if (afflictionElements == null)
            {
                DebugConsole.ThrowError("Affliction list was not found in talent " + characterTalent.DebugIdentifier,
                    contentPackage: characterTalent.Prefab.ContentPackage);
                return null;
            }

            List<Affliction> afflictions = new List<Affliction>();

            // similar logic to affliction creation in statuseffects
            // might be worth unifying

            foreach (var afflictionElement in afflictionElements.Elements())
            {
                Identifier afflictionIdentifier = afflictionElement.GetAttributeIdentifier("identifier", "");
                AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == afflictionIdentifier);
                if (afflictionPrefab == null)
                {
                    DebugConsole.ThrowError("Error in CharacterTalent (" + characterTalent.DebugIdentifier + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.",
                        contentPackage: characterTalent.Prefab.ContentPackage);
                    continue;
                }

                Affliction afflictionInstance = afflictionPrefab.Instantiate(afflictionElement.GetAttributeFloat(1.0f, "amount", "strength"));
                afflictionInstance.Probability = afflictionElement.GetAttributeFloat(1.0f, "probability");
                afflictions.Add(afflictionInstance);
            }

            return afflictions;
        }

        public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
        {
            if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType))
            {
                DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
            }
            return flagType;
        }
    }
}
