using System;
using Sirenix.OdinInspector;
using UnityEngine;
using VOID.Generic.Objects.Unit.Events;

namespace VOID.Generic.Objects.Unit
{
    /// <summary>
    /// Manages everything related to level and experience for <see cref="UnitObject"/>
    /// </summary>
    [Serializable]
    public class UnitObjectLevel : ObjectComponent<UnitObject>
    {
        #region Initialize

        protected override void SelfInitialize()
        {
            _expNeeded = GetExpNeededForLevel(_level + 1);
        }

        #endregion

        [Header("Constants:")]
        [ShowInInspector, ReadOnly] public const int ExpNeededForFirstLevel = 100;
        [ShowInInspector, ReadOnly] public const float ExpNeededMultiplierPerLevel = 1.25f;

        [Header("Current:")]
        [SerializeField, ReadOnly, MinValue(1)] private int _level = 1;
        [ShowInInspector, ReadOnly] private int _exp;
        [ShowInInspector, ReadOnly] private int _expNeeded;
        public int level => _level;
        public int exp => _exp;
        public int expNeeded => _expNeeded;

        [Button]
        public void SetLevel(int value)
        {
            _level = Mathf.Max(1, value);
            _exp = 0;
            _expNeeded = GetExpNeededForLevel(_level + 1);
        }

        [Button]
        [DisableInEditorMode]
        public void AddLevel(int amount) => Add(levelToAdd: amount);
        
        [Button]
        [DisableInEditorMode]
        public void AddExperience(int amount) => Add(expToAdd: amount);
        
        [Button]
        [DisableInEditorMode]
        public void Add(int levelToAdd = 0, int expToAdd = 0)
        {
            if (levelToAdd <= 0 && expToAdd <= 0) return;

            // Directly increasing level also resets current experience
            if (levelToAdd > 0)
            {
                _level += levelToAdd; 
                _exp = 0;
            }
            
            _exp += expToAdd;
            
            // Convert EXP to LEVELS
            while (_exp >= _expNeeded)
            {
                levelToAdd++;
                _exp -= _expNeeded;
                _expNeeded = GetExpNeededForLevel(_level + 1 + levelToAdd);
            }

            if (expToAdd > 0)
                parent.unitEvents.onExpGain.Invoke(new ExpGainEvent { unit = parent, amount = expToAdd });
            
            if (levelToAdd > 0)
                parent.unitEvents.onLevelGain.Invoke(new LevelGainEvent { unit = parent, amount = levelToAdd });
        }
        
        private int GetExpNeededForLevel(int forLevel)
        {
            // Exp needed can be calculated only for level 2 and higher!
            if (forLevel < 2) return 0;
            
            var expMultiplierForLevel = Mathf.Pow(ExpNeededMultiplierPerLevel, forLevel - 2);
            var expNeededForLevel = Mathf.FloorToInt(ExpNeededForFirstLevel * expMultiplierForLevel); 
            
            return expNeededForLevel;
        }
    }
}