using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Framework.StatSystem
{
    /// <summary>
    /// Represents a single ability value for an entity within the game world,
    /// for example 'Strength' or 'Speed'. These values can be used to change
    /// how an object acts numerically. Stats are modifiable variables so they
    /// are capable of accumulating modifications that change the value temporarily.
    /// </summary>
    [Serializable]
    public class Stat : Statlike
    {
        /// <summary>
        /// Performs an implicit conversion from <see cref="Stat"/> to <see cref="System.Single"/>.
        /// </summary>
        /// <param name="self">The self.</param>
        /// <returns>
        /// The result of the conversion.
        /// </returns>
        public static implicit operator float(Stat self)
        {
            return self.GetValue();
        }

        public static implicit operator int(Stat self)
        {
            return (int)self.GetValue();
        }

        private string name; // The name of the stat TODO, decide purpose
        public float baseValue; // The base value of the stat
        private float currentValue; // The current mid-calculation value of the stat
        private List<Modifierlike> modifiers; // Modifiers that are currently applied to this stat
        private Statlike superstat; // The stat this stat inherits modifiers from
        // Modifiers

        /// <summary>
        /// Initializes a new instance of the <see cref="Stat"/> class.
        /// </summary>
        /// <param name="value">The value.</param>
        public Stat(float value)
        {
            this.baseValue = value;
            this.modifiers = new List<Modifierlike>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Stat"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public Stat(string name, float value) : this(value)
        {
            this.name = name;
        }

        public string GetName()
        {
            return this.name;
        }

        /// <summary>
        /// Sets the superstat.
        /// </summary>
        /// <param name="superstat">The superstat.</param>
        /// <exception cref="System.ArgumentException">Superstat is the same as stat it was passed into, will cause an infinite loop!</exception>
        public void SetSuperstat(Statlike superstat)
        {
            if (superstat == this)
            {
                throw new ArgumentException("Superstat is the same as stat it was passed into, will cause an infinite loop!");
            }
            this.superstat = superstat;
        }

        /// <summary>
        /// Adds a modifier to this variable.
        /// </summary>
        /// <param name="modifier">The modifier.</param>
        public void AddModifier(Modifierlike modifier)
        {
            this.modifiers.Add(modifier); 
        }

        /// <summary>
        /// Removes a modifier from this variable.
        /// </summary>
        /// <param name="modifier">The modifier.</param>
        public void RemoveModifier(Modifierlike modifier)
        {
            this.modifiers.Remove(modifier);
        }

        /// <summary>
        /// Removes a modifier from this variable by type and strength.
        /// </summary>
        /// <typeparam name="ModifierType">The type of the odifier type.</typeparam>
        /// <param name="strength"></param>
        public void RemoveModifier<ModifierType>(float strength) where ModifierType : Modifierlike
        {
            for (int i = 0; i < this.modifiers.Count; i++)
            {
                if (this.modifiers[i].GetType() == typeof(ModifierType) && 
                    this.modifiers[i].GetStrength() == strength)
                {
                    this.modifiers.RemoveAt(i);
                    return;
                }
            }
        }

        /// <summary>
        /// Returns the current modifiers applied to this stat.
        /// </summary>
        /// <returns></returns>
        public List<Modifierlike> GetModifiers()
        {
            List<Modifierlike> modifiers = new List<Modifierlike>();
            if (this.superstat != null)
            {
                modifiers.AddRange(this.superstat.GetModifiers());
            }
            modifiers.AddRange(this.modifiers);
            return modifiers;
        }

        /// <summary>
        /// Gets the base value of this stat before modifications have been applied.
        /// </summary>
        /// <returns></returns>
        public float GetBaseValue()
        {
            return this.baseValue;
        }

        /// <summary>
        /// Gets the current mid calculation value of this stat, should only be used for
        /// modifier processing.
        /// </summary>
        /// <returns></returns>
        public float GetCurrentValue()
        {
            return this.currentValue;
        }

        /// <summary>
        /// Gets the value of this stat with modifications applied.
        /// </summary>
        /// <returns></returns>
        public float GetValue()
        {
            this.currentValue = this.baseValue;

            // Apply superstat modifiers
            if (this.superstat != null)
            {
                for (int i = 0; i < this.superstat.GetModifiers().Count; i++)
                {
                    this.currentValue = this.superstat.GetModifiers()[i].Apply(this);
                }
            }

            // Apply this stat's modifiers
            for (int i = 0; i < this.modifiers.Count; i++)
            {
                this.currentValue = this.modifiers[i].Apply(this);
            }

            return this.currentValue;
        }
    }
}
