using System;
using System.Linq;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Accessory;
using VOID.Generic.Objects.Armor;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Weapon;
using VOID.Generic.Objects.Wearable;
using VOID.Generic.Objects.Wearable.Events;
using static VOID.Generic.Util.WearableUtils;

namespace VOID.Generic.Objects.Unit.Actions
{
    public class EquipAction : AbstractAction
    {
        private readonly UnitObject _unit;
        private readonly WearableObject _item;
        private readonly int _slotIndex;
        private readonly EquipmentSlotType _slot;

        public EquipAction(UnitObject unit, WearableObject item, int slotIndex)
        {
            _unit = unit;
            _item = item;
            _slotIndex = slotIndex;
            _slot = (EquipmentSlotType)slotIndex;
        }

        public EquipAction(UnitObject unit, WearableObject item)
        {
            _unit = unit;
            _item = item;
            _slot = FindEquipmentSlot();
            _slotIndex = (int)_slot;
        }

        public override bool IsInRange() => true;

        protected override void OnEndIt() {}

        protected override void OnCancelIt() {}
        
        public override void CanDoIt()
        {
            Check(
                state == ActionState.Ready,
                UnitActionErrorType.ActionStateIsNotReady);
            if (_unit.basicState.turnManager)
            {
                Check(
                    _unit.unitState.isSelfTurn,
                    UnitActionErrorType.UnitSelfTurnNotActive);
                Check(
                    _unit.unitData.currentResources.actionPoints >= 1,
                    UnitActionErrorType.UnitNotEnoughActionPoints);
            }
            Check(
                _unit.unitEquipment.CanEquipInSlot(_slotIndex, _item),
                UnitActionErrorType.CannotEquipItemInThatSlot);
            Check(
                _unit.unitState.isControllable,
                UnitActionErrorType.UnitStateIsNotControllable);
            Check(
                _item.itemState.carriedBy == _unit,
                UnitActionErrorType.UnitDontCarryThatItem);
            Check(
                _item.wearableData.requirements.All(requirement => requirement.Check(_unit)),
                UnitActionErrorType.RequirementsNotMeetToEquipThatItem);
        }

        protected override void OnDoIt()
        {
            if (_unit.basicState.turnManager)
                _unit.unitData.UseResource(UnitResourceType.ActionPoints, 1);
            
            var equipEvent = new EquipEvent
            {
                wearable = _item,
                unit = _unit,
                slotIndex = _slotIndex,
                slotType = _slot
            };
            
            _item.wearableEvents.onEquippedBefore.Invoke(equipEvent);
            _unit.unitEvents.onEquipBefore.Invoke(equipEvent);

            Check(!equipEvent.prevented, UnitActionErrorType.EquipIsPrevented);
            
            TryToUnEquip();
            
            BeforePerform();
            
            // Move item from Backpack to Equipment
            _unit.unitBackpack.Drop(_item);
            _unit.unitEquipment.Equip(_slotIndex, _item);
            
            // Execute events
            _item.wearableEvents.onEquippedAfter.Invoke(equipEvent);
            _unit.unitEvents.onEquipAfter.Invoke(equipEvent);

            AfterPerform();
            EndIt();
        }

        private void TryToUnEquip()
        {
            var toUnEquip = _unit.unitEquipment.GetEquipped(_slotIndex);
            if (toUnEquip) new UnEquipAction(_unit, toUnEquip).DoIt();

            // All WearableObjects un-equip only this item where it will be equipped
            // EXCEPT WeaponObject - these also can un-equip additionally mainHand or offHand, only when dealing with two-handed weapons
            if (_item is not WeaponObject weapon) return;

            var mainHand = _unit.unitEquipment.GetEquipped(EquipmentSlotType.MainHand) as WeaponObject;
            var offHand = _unit.unitEquipment.GetEquipped(EquipmentSlotType.OffHand) as WeaponObject;

            // UNEQUIP MAINHAND - if it is two-hand, we try to equip something in offhand
            if (mainHand && _slot == EquipmentSlotType.OffHand && mainHand.weaponData.weaponCarryType is WeaponCarryType.TwoHand)
                new UnEquipAction(_unit, mainHand).DoIt();

            // UNEQUIP OFFHAND - if we try to equip something two-handed 
            if (offHand && weapon.weaponData.weaponCarryType == WeaponCarryType.TwoHand)
                new UnEquipAction(_unit, offHand).DoIt();
        }

        private EquipmentSlotType FindEquipmentSlot()
        {
            var equippedRingOne = _unit.unitEquipment.GetEquipped(EquipmentSlotType.RingOne);
            var equippedRingTwo = _unit.unitEquipment.GetEquipped(EquipmentSlotType.RingTwo);
            return _item switch
            {
                WeaponObject when !IsWeaponCarryType(_item, WeaponCarryType.OneHandOff) => EquipmentSlotType.MainHand,
                WeaponObject when IsWeaponCarryType(_item, WeaponCarryType.OneHandOff) => EquipmentSlotType.OffHand,
                ArmorObject when IsType(_item, ArmorType.Helmet) => EquipmentSlotType.Helmet,
                ArmorObject when IsType(_item, ArmorType.Armor) => EquipmentSlotType.Armor,
                ArmorObject when IsType(_item, ArmorType.Pants) => EquipmentSlotType.Pants,
                ArmorObject when IsType(_item, ArmorType.Belt) => EquipmentSlotType.Belt,
                ArmorObject when IsType(_item, ArmorType.Boots) => EquipmentSlotType.Boots,
                ArmorObject when IsType(_item, ArmorType.Gloves) => EquipmentSlotType.Gloves,
                AccessoryObject when IsType(_item, AccessoryType.Necklace) => EquipmentSlotType.Necklace,
                AccessoryObject when IsType(_item, AccessoryType.Ring) && (!equippedRingOne || equippedRingTwo) => EquipmentSlotType.RingOne,
                AccessoryObject when IsType(_item, AccessoryType.Ring) && (equippedRingOne && !equippedRingTwo) => EquipmentSlotType.RingTwo,
                _ => throw new ArgumentOutOfRangeException(nameof(_item))
            };
        }
    }
}