using System.Collections.Generic;
using System.Linq;
using Schema;
using UnityEngine;
using VOID.Generic.Objects.Unit.Actions;
using VOID.Generic.Objects.Unit.Events;

namespace VOID.Generic.AI.Schema.Actions
{
    [Description("This unit will patrol between given points")]
    [Category(SchemaCategory.RealtimeThisUnit)]
    public class UnitPatrol : Action
    {
        private class UnitPatrolMemory
        {
            public SchemaUnitEventWrapper<MoveEvent, UnitPatrolMemory> onMoveEnd;
            public int nextStop = - 1;
            public bool isMoving = false;
        }

        public override void OnNodeEnter(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitPatrolMemory)nodeMemory;
            var unitAgent = (UnitObjectSchemaAgent)agent;
            var patrolPoints = unitAgent.unitAI.patrol;
            var closestDistance = float.MaxValue;

            for (var index = 0; index < patrolPoints.Count; index++)
            {
                var distance = Vector3.Distance(unitAgent.unit.transform.position, patrolPoints[index].position);
                if (distance >= closestDistance) continue;
                closestDistance = distance;
                memory.nextStop = index;
            }

            memory.onMoveEnd = new SchemaUnitEventWrapper<MoveEvent, UnitPatrolMemory>(
                unitAgent.unit.unitEvents.onMoveEnd, OnMoveEnd, unitAgent, memory);
            memory.onMoveEnd.On();
        }

        public override NodeStatus Tick(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitPatrolMemory)nodeMemory;
            var unitAgent = (UnitObjectSchemaAgent)agent;

            if (memory.nextStop == - 1) return NodeStatus.Failure;
            if (memory.isMoving) return NodeStatus.Running;

            // Get next patrol points in order to check
            var orderedPatrolPoints = new List<Transform>();
            orderedPatrolPoints.AddRange(unitAgent.unitAI.patrol.Skip(memory.nextStop+1));
            orderedPatrolPoints.AddRange(unitAgent.unitAI.patrol.Take(memory.nextStop+1));
            
            // Find next valid patrol point and order move to it
            foreach (var point in orderedPatrolPoints)
            {
                var moveAction = new MoveAction(unitAgent.unit, point.position);
                if (moveAction.CanDoItBoolean() == false) continue;
                unitAgent.unit.Order(moveAction);
                memory.isMoving = true;
                return NodeStatus.Running;
            }

            // Next point cant be found - error!
            return NodeStatus.Failure;
        }
        
        public override void OnNodeExit(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitPatrolMemory)nodeMemory;
            memory.onMoveEnd.Off();
            memory.isMoving = false;
        }

        public override void OnNodeAbort(object nodeMemory, SchemaAgent agent)
        {
            var memory = (UnitPatrolMemory)nodeMemory;
            memory.onMoveEnd.Off();
            memory.isMoving = false;
        }

        private void OnMoveEnd(MoveEvent moveEvent, UnitPatrolMemory memory, UnitObjectSchemaAgent agent)
        {
            memory.nextStop = (memory.nextStop + 1) % agent.unitAI.patrol.Count;
            memory.isMoving = false;
        }
    }
}