using System.Linq;
using VOID.Generic.Dict;
using VOID.Generic.Dict.Ability;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Unit.Events;
using VOID.ScriptableObjects.Abilities;

namespace VOID.Generic.Objects.Unit.Actions
{
    /// <summary>
    /// Simply make <see cref="UnitObject"/> go into "casting" state allowing player or AI to aim and select targets.
    /// <br/><br/>
    /// <see cref="AbstractAction"/>:<br/>
    /// <inheritdoc cref="AbstractAction"/>
    /// </summary>
    public class CastingAction : AbstractAction
    {
        private readonly UnitObject _unit;
        private readonly BasicAbility _ability;

        public CastingAction(UnitObject unit, BasicAbility ability)
        {
            _unit = unit;
            _ability = ability;
        }

        public override bool IsInRange() => true;

        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 >= _ability.actionPointCost,
                    UnitActionErrorType.UnitNotEnoughActionPoints);
                Check(
                    _unit.unitData.currentResources.movePoints >= _ability.movePointCost,
                    UnitActionErrorType.UnitNotEnoughMovePoints);
            }
            Check(
                _unit.unitState.isControllable,
                UnitActionErrorType.UnitStateIsNotControllable);
            Check(
                _ability.requirements.All(requirement => requirement.Check(_unit)),
                UnitActionErrorType.RequirementsNotMeetToUseThatAbility);
        }

        protected override void OnDoIt()
        {
            BeforePerform();
            
            if (_ability.castingType is AbilityCastingType.Instant or AbilityCastingType.InstantCasting)
            {
                // Ending action early
                AfterPerform();
                EndIt();
                
                // Make sure that there is no more actions after CASTING and before ABILITY
                _unit.OrderStop(false);
                
                // Instantly starting ability
                _unit.Order(new AbilityAction(_unit, _ability));
                return;
            }

            var animationGenericEvent = new AnimationGenericEvent
            {
                animationType = AnimationType.Casting,
                unit = _unit,
                ability = _ability
            };

            // Enables "casting" mode for this unit (usually only for player) to select targets before firing ability
            _unit.unitEvents.onStartCasting.Invoke(new CastingEvent { caster = _unit, ability = _ability });
            
            // Starting loop animation of selected ability
            _unit.unitAnimator.SetLoopAnimation(AnimationType.Casting, _ability.castingAnimationList);
            _unit.unitAnimator.StartLoopAnimation(animationGenericEvent);
        }

        protected override void OnEndIt()
        {
            AfterPerform();

            // Instant abilities don't have casting - do nothing
            if (_ability.castingType is AbilityCastingType.Instant or AbilityCastingType.InstantCasting) return;
            
            // Somehow casting ended (no matter is by player or something else)
            _unit.unitAnimator.EndLoopAnimation();
            _unit.unitEvents.onEndCasting.Invoke(new CastingEvent { caster = _unit, ability = _ability });
        }

        protected override void OnCancelIt()
        {
            OnEndIt();
        }
    }
}