using System;
using System.Collections;
using System.Collections.Generic;

using Syntriax.Engine.Core.Abstract;

namespace Syntriax.Engine.Core;

public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : class, IBehaviour
{
    public event IAssignable.UnassignEventHandler? OnUnassigned = null;
    public event IHasGameManager.GameManagerAssignedEventHandler? OnGameManagerAssigned = null;

    public event IBehaviourCollector<T>.CollectedEventHandler? OnCollected = null;
    public event IBehaviourCollector<T>.RemovedEventHandler? OnRemoved = null;

    private readonly List<T> monitoringBehaviours = new(32);
    protected readonly List<T> activeBehaviours = new(32);
    protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);

    public IReadOnlyList<T> Behaviours => activeBehaviours;
    public IGameManager GameManager { get; private set; } = null!;

    public T this[Index index] => activeBehaviours[index];

    public ActiveBehaviourCollector() { }
    public ActiveBehaviourCollector(IGameManager gameManager) => Assign(gameManager);

    private void OnHierarchyObjectRegistered(IGameManager manager, IHierarchyObject hierarchyObject)
    {
        hierarchyObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
        hierarchyObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;

        foreach (IBehaviour item in hierarchyObject.BehaviourController)
            OnBehaviourAdded(hierarchyObject.BehaviourController, item);
    }

    private void OnHierarchyObjectUnregistered(IGameManager manager, IHierarchyObject hierarchyObject)
    {
        hierarchyObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
        hierarchyObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;

        foreach (IBehaviour item in hierarchyObject.BehaviourController)
            OnBehaviourRemoved(hierarchyObject.BehaviourController, item);
    }

    protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
    private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
    {
        if (behaviour is not T tBehaviour)
            return;

        monitoringBehaviours.Add(tBehaviour);
        monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
        tBehaviour.OnActiveChanged += OnBehaviourStateChanged;
        OnBehaviourStateChanged(tBehaviour, !tBehaviour.IsActive);
    }

    private void OnBehaviourStateChanged(IActive sender, bool previousState)
    {
        T behaviour = monitoringActiveToBehaviour[sender];
        if (sender.IsActive)
        {
            activeBehaviours.Add(behaviour);
            OnBehaviourAdd(behaviour);
            OnCollected?.Invoke(this, behaviour);
        }
        else if (activeBehaviours.Remove(behaviour))
        {
            OnBehaviourRemove(behaviour);
            OnRemoved?.Invoke(this, behaviour);
        }
    }

    protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
    private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
    {
        if (behaviour is not T tBehaviour)
            return;

        if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
            return;

        tBehaviour.OnActiveChanged -= OnBehaviourStateChanged;
        if (activeBehaviours.Remove(tBehaviour))
        {
            OnBehaviourRemove(tBehaviour);
            OnRemoved?.Invoke(this, tBehaviour);
        }
    }

    public bool Assign(IGameManager gameManager)
    {
        if (GameManager is not null)
            return false;

        foreach (IHierarchyObject hierarchyObject in gameManager.HierarchyObjects)
            OnHierarchyObjectRegistered(gameManager, hierarchyObject);

        gameManager.OnHierarchyObjectRegistered += OnHierarchyObjectRegistered;
        gameManager.OnHierarchyObjectUnRegistered += OnHierarchyObjectUnregistered;

        GameManager = gameManager;
        OnGameManagerAssigned?.Invoke(this);

        return true;
    }

    public bool Unassign()
    {
        if (GameManager is null)
            return false;

        foreach (IHierarchyObject hierarchyObject in GameManager.HierarchyObjects)
            OnHierarchyObjectUnregistered(GameManager, hierarchyObject);

        GameManager.OnHierarchyObjectRegistered -= OnHierarchyObjectRegistered;
        GameManager.OnHierarchyObjectUnRegistered -= OnHierarchyObjectUnregistered;

        GameManager = null!;
        OnUnassigned?.Invoke(this);
        return true;
    }

    public IEnumerator<T> GetEnumerator() => activeBehaviours.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => activeBehaviours.GetEnumerator();
}