using System;
using System.Linq;
using Schema;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Actions;
using VOID.Generic.Objects.Unit.Events;
using VOID.ScriptableObjects.Abilities;
using Action = Schema.Action;

namespace VOID.Generic.AI.Schema.Actions
{
    [Description("Order this unit to use selected ability")]
    [Category(SchemaCategory.ThisUnit)]
    public class UnitUseAbility : Action
    {
        private class UnitUseAbilityMemory
        {
            public SchemaUnitEventWrapper<AnimationGenericEvent, UnitUseAbilityMemory> onCastAnimationEnd;
            public bool isRunning;
            public bool isFinished;
        }

        private enum TargetType
        {
            Position,
            Unit
        }

        [SerializeField] private BlackboardEntrySelector<BasicAbility> _ability;
        [SerializeField, Space(15)] private TargetType _targetType;
        [SerializeField] private BlackboardEntrySelector<Transform> _position;
        [SerializeField] private BlackboardEntrySelector<UnitObject> _unit;

        public override NodeStatus Tick(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitUseAbilityMemory)nodeMemory;
            var unitAgent = (UnitObjectSchemaAgent)agent;
            var thisUnit = unitAgent.unit;

            // Ability not selected
            if (!_ability.value) return NodeStatus.Failure;

            // Whenever is during casting or already done that
            if (memory.isFinished) return NodeStatus.Success;
            if (memory.isRunning) return NodeStatus.Running;

            // Targets not selected
            if (_targetType == TargetType.Position && !_position.value) return NodeStatus.Failure;
            if (_targetType == TargetType.Unit && !_unit.value) return NodeStatus.Failure;

            AbilityAction abilityAction;
            if (_targetType is TargetType.Position)
            {
                // TODO: for now if ability needs multiple targets all will be identical
                var targets = Enumerable.Repeat(_position.value.position, _ability.value.castUsages).ToArray();
                abilityAction = new AbilityAction(thisUnit, _ability.value, targets);
            }
            else if (_targetType is TargetType.Unit)
            {
                // TODO: for now if ability needs multiple targets all will be identical
                var targets = Enumerable.Repeat(_unit.value, _ability.value.castUsages).ToArray();
                abilityAction = new AbilityAction(thisUnit, _ability.value, targets);
            }
            else
            {
                throw new ArgumentOutOfRangeException();
            }

            // Check if that action can be done
            if (!abilityAction.CanDoItBoolean()) return NodeStatus.Failure;

            // Listen to end of abilisty casting animation
            memory.onCastAnimationEnd?.Off();
            memory.onCastAnimationEnd = new SchemaUnitEventWrapper<AnimationGenericEvent, UnitUseAbilityMemory>(
                thisUnit.unitEvents.onAnimation[AnimationType.Ability].onEnd, OnAbilityCastEnd, unitAgent, memory);
            memory.onCastAnimationEnd.On();

            // Everything seems ok - order unit to do that action
            thisUnit.OrderStop();
            thisUnit.Order(abilityAction);
            memory.isRunning = true;

            return NodeStatus.Running;
        }

        public override void OnNodeExit(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitUseAbilityMemory)nodeMemory;
            memory.onCastAnimationEnd?.Off();
            memory.isFinished = false;
            memory.isRunning = false;
        }

        public override void OnNodeAbort(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitUseAbilityMemory)nodeMemory;
            memory.onCastAnimationEnd?.Off();
            memory.isFinished = false;
            memory.isRunning = false;
        }

        private void OnAbilityCastEnd(AnimationGenericEvent abilityCastEvent, UnitUseAbilityMemory memory,
            UnitObjectSchemaAgent agent)
        {
            memory.isFinished = true;
        }
    }
}