using Assets.Framework.Classes.Experiments;
using Framework.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Framework.StatSystem
{
    /// <summary>
    /// This action exposes an <see cref="Attributes"/> component to other components 
    /// within the game.
    /// </summary>
    public class ExposeAttributes : CoreAction
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ExposeAttributes"/> class.
        /// </summary>
        /// <param name="sender">The sender.</param>
        public ExposeAttributes(CoreBehaviour sender)
            : base(sender)
        {
        }

        /// <summary>
        /// Gets the attributes component.
        /// </summary>
        /// <returns></returns>
        public Attributes GetAttributes()
        {
            return (Attributes)GetSender();
        }
    }

    /// <summary>
    /// Holds stats that may be used by other <see cref="EntityComponents"/> on this gameObject
    /// or by other gameObjects. Is exposed to any internal components so that they may retrieve
    /// stats from it. Should be extended to define specific game stats e.g.: Strength, Speed etc 
    /// </summary>
    [AddComponentMenu("Scripts/Framework/Actor Components/Base/Attributes")]
    public class Attributes : CoreBehaviour
    {
        public Dictionary<StatType.Id, Statlike> stats;
        public List<StatEffect> statEffects;

        /// <summary>
        /// Gets a stat by id, if the stat does not exist then the default stat with
        /// a value of 1 is returned.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        public Statlike GetStat(StatType.Id id)
        {
            if (!this.stats.ContainsKey(id))
            {
                return new Stat(1f);
            }
            return this.stats[id];
        }

        /// <summary>
        /// Gets a stat by id, if the stat is not found returns a stat with the given
        /// default value.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public Statlike GetStat(StatType.Id id, float defaultValue)
        {
            if (!this.stats.ContainsKey(id))
            {
                return new Stat(defaultValue);
            }
            return this.stats[id];
        }

        /// <summary>
        /// Gets a stat by id, if the stat is not found returns the stat with the fallback
        /// id. If this too is not found then it returns a stat with the given default value.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="fallbackId">The fallback identifier.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public Statlike GetStat(StatType.Id id, StatType.Id fallbackId, float defaultValue)
        {
            if (!this.stats.ContainsKey(id))
            {
                if(!this.stats.ContainsKey(fallbackId))
                {
                    return new Stat(defaultValue);
                }
                return this.stats[id];
            }
            return this.stats[id];
        }

        /// <summary>
        /// Called when this component is awoken after the Unity
        /// Awake is called. Add stats here when implementing specific game
        /// implementation.
        /// </summary>
        protected override void InnerAwake()
        {
            this.stats = new Dictionary<StatType.Id, Statlike>();
            this.stats.Add(StatType.Example, new Stat("Example", 5f));
            this.stats.Add(StatType.WalkSpeed, new Stat("Walk Speed", 1f));
            this.stats.Add(StatType.JumpHeight, new Stat("Jump Height", 1f));
            this.stats.Add(StatType.MaxJumpCount, new Stat("Jump Count", 1f));

            this.statEffects = new List<StatEffect>();

            AddActionHandler<SendModifier>(ApplyModifier);
            AddActionHandler<ApplyStatEffect>(AddStatEffect);
        }

        /// <summary>
        /// Called when this component is started after the Unity
        /// Start is called.
        /// </summary>
        protected override void InnerStart()
        {
            SelfInteract(new ExposeAttributes(this));
        }

        protected override void InnerUpdate()
        {
            for (int i = 0; i < this.statEffects.Count; i++)
            {
                this.statEffects[i].Update();
            }
        }

        private void ApplyModifier(SendModifier action)
        {
            this.stats[action.GetStatId()].AddModifier(action.GetModifier());
        }

        private void AddStatEffect(ApplyStatEffect action)
        {
            this.statEffects.Add(action.GetStatEffect());
            action.GetStatEffect().Apply(this);
        }
    }
}
