using Sirenix.OdinInspector;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract;
using VOID.Generic.Objects.Unit.Data;
using VOID.Generic.Objects.Unit.Events;
using VOID.Generic.Objects.Weapon;

namespace VOID.Generic.Objects.Unit
{
    /// <summary>
    /// Contain and manages all generic data needed and used by <see cref="AbstractObject"/> or its children.
    /// </summary>
    [System.Serializable]
    public class UnitObjectData : ObjectComponent<UnitObject>
    {
        #region Initialize

        protected override void SelfInitialize()
        {
            // Instantiate stats
            baseMaxResources = baseMaxResources with {};
            maxResources = maxResources with {};
            currentResources = currentResources with {};
            baseMainAttributes = baseMainAttributes with {};
            mainAttributes = mainAttributes with {};
            baseSubAttributes = baseSubAttributes with {};
            subAttributes = subAttributes with {};
        }

        protected override void EventInitialize()
        {
            RegisterUpdateWeaponRelatedAttributes();
        }

        #endregion

        #region Events

        private void RegisterUpdateWeaponRelatedAttributes()
        {
            parent.unitEvents.onEquipmentEquip.AddListener(equipEvent =>
            {
                if (equipEvent.wearable is WeaponObject)  UpdateWeaponRelatedAttributes();
            });
            parent.unitEvents.onEquipmentUnEquip.AddListener(equipEvent =>
            {
                if (equipEvent.wearable is WeaponObject)  UpdateWeaponRelatedAttributes();
            });
        }

        /// <summary>
        /// When changing weapons <see cref="UnitObject"/> also inherit <see cref="WeaponObject"/>'s damage and critical attributes. 
        /// </summary>
        private void UpdateWeaponRelatedAttributes()
        {
            var mainWeapon = parent.unitEquipment.GetEquipped(EquipmentSlotType.MainHand) as WeaponObject;
            var offWeapon = parent.unitEquipment.GetEquipped(EquipmentSlotType.OffHand) as WeaponObject;

            if (mainWeapon)
            {
                baseSubAttributes.damageMin = mainWeapon.weaponData.damageMin;
                baseSubAttributes.damageMax = mainWeapon.weaponData.damageMax;
                baseSubAttributes.criticalDamageChance = mainWeapon.weaponData.criticalDamageChange;
                baseSubAttributes.criticalDamageMultiplier = mainWeapon.weaponData.criticalDamageMultiplier;

                if (offWeapon)
                {
                    baseSubAttributes.damageMin += offWeapon.weaponData.damageMin;
                    baseSubAttributes.damageMax += offWeapon.weaponData.damageMax;
                }
            }
            else if (offWeapon)
            {
                baseSubAttributes.damageMin = offWeapon.weaponData.damageMin;
                baseSubAttributes.damageMax = offWeapon.weaponData.damageMax;
                baseSubAttributes.criticalDamageChance = offWeapon.weaponData.criticalDamageChange;
                baseSubAttributes.criticalDamageMultiplier = offWeapon.weaponData.criticalDamageMultiplier;
            }
            else
            {
                baseSubAttributes.damageMin = 1;
                baseSubAttributes.damageMax = 1;
                baseSubAttributes.criticalDamageChance = 0;
                baseSubAttributes.criticalDamageMultiplier = 1;
            }
            
            // Recalc those attributes
            parent.unitDataCalculator.Queue(SubAttributeType.DamageMin);
            parent.unitDataCalculator.Queue(SubAttributeType.DamageMax);
            parent.unitDataCalculator.Queue(SubAttributeType.CriticalDamageChance);
            parent.unitDataCalculator.Queue(SubAttributeType.CriticalDamageMultiplier);
        }

        #endregion

        [Title("Possible actions")]
        public bool canBeTalked;
        
        [Title("Possible actions to do")]
        public bool canMove = true;
        public bool canAttack = true;
        public bool canUse = true;
        public bool canTalk = true;
        public bool canTake = true;
        public bool canInspect = true;

        [Title("Unit Resources")]
        public UnitResources baseMaxResources;
        [ReadOnly] public UnitResources maxResources;
        [ReadOnly] public UnitResources currentResources;

        [Title("Main Attributes")]
        public MainAttributes baseMainAttributes;
        [ReadOnly] public MainAttributes mainAttributes;

        [Title("Sub Attributes")]
        public SubAttributes baseSubAttributes;
        [ReadOnly] public SubAttributes subAttributes;
        
        [Title("Interaction")]
        [MinValue(1f)] public float useRange = 1f;
        [MinValue(1f)] public float talkRange = 2f;
        [MinValue(1f)] public float takeRange = 2f;
        [MinValue(1f)] public float inspectRange = 20f;
        
        [Title("Offset positions")]
        [Required] public Transform castPosition;
        [Required] public Transform ladderPosition;
        [Required] public Transform rightHandPosition;
        [Required] public Transform leftHandPosition;
        
        [Title("Visual")]
        public GameObject topUnderwear;
        public GameObject bottomUnderwear;

        /// <summary>
        /// Uses given resource by given amount.<br/>
        /// That means positive values decreases and negative values increases that resource.
        /// </summary>
        /// <param name="resourceType">Resource to use</param>
        /// <param name="change">Amount to change</param>
        /// <param name="force">If TRUE then we can use this resource even if we dont have enough of it</param>
        /// <returns>Returns TRUE if done successfully.</returns>
        public bool UseResource(UnitResourceType resourceType, float change, bool force = false)
        {
            var oldValue = currentResources.Get(resourceType);
            var maxValue = maxResources.Get(resourceType);
            
            // Attempt to use more than available
            if (oldValue - change < 0f)
            {
                // If not forced - not enough resource
                if (!force) return false;

                // Else - use only remaining resource
                change += oldValue - change;
            }

            // Attempt to go over limit
            if (oldValue - change > maxValue)
            {
                change = oldValue - maxValue;
            }
            
            currentResources.Set(resourceType, oldValue - change);
            parent.unitEvents.onCurrentResourceChange.Invoke(new UnitResourceChangeEvent
            {
                unit = parent,
                type = resourceType,
                oldValue = oldValue,
                newValue = oldValue - change,
                changedValue = change,
            });
            return true;
        }

        /// <summary>
        /// Sets given resource to given value.<br/>
        /// It uses <see cref="UseResource"/> so whole process will be executed as change from starting value to given value.
        /// </summary>
        /// <param name="resourceType">Resource type to set</param>
        /// <param name="value">Value to set</param>
        /// <returns>Return TRUE if done successfully.</returns>
        public bool SetResource(UnitResourceType resourceType, float value)
        {
            var max = maxResources.Get(resourceType);
            var oldValue = Mathf.Clamp(currentResources.Get(resourceType), 0f, max);
            var change = oldValue - value;
            return UseResource(resourceType, change, true);
        }

        /// <summary>
        /// Sets given resource to percentage of its max value.<br/>
        /// It uses <see cref="SetResource"/> so whole process will be executed as change from starting value to given value.
        /// </summary>
        /// <param name="resourceType">Resource type to set</param>
        /// <param name="percentage">float as percent, value needs to be between 0 and 1</param>
        /// <returns>Return TRUE if done successfully.</returns>
        public bool SetResourcePercentage(UnitResourceType resourceType, float percentage)
        {
            percentage = Mathf.Clamp01(percentage);
            var max = maxResources.Get(resourceType);
            var value = percentage * max;
            var oldValue = currentResources.Get(resourceType);
            var change = oldValue - value;
            return UseResource(resourceType, change, true);
        }
    }
}
