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

using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;

namespace Syntriax.Engine.Core;

[System.Diagnostics.DebuggerDisplay("HierarchyObject Count: {_hierarchyObjects.Count}")]
public class GameManager : BaseEntity, IGameManager
{
    public event IGameManager.UpdateEventHandler? OnUpdate = null;
    public event IGameManager.PreDawEventHandler? OnPreDraw = null;

    public event IGameManager.HierarchyObjectRegisteredEventHandler? OnHierarchyObjectRegistered = null;
    public event IGameManager.HierarchyObjectUnRegisteredEventHandler? OnHierarchyObjectUnRegistered = null;

    private readonly List<IHierarchyObject> _hierarchyObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL);

    public IReadOnlyList<IHierarchyObject> HierarchyObjects => _hierarchyObjects;

    public override IStateEnable StateEnable
    {
        get
        {
            if (base.StateEnable is null)
            {
                Assign(Factory.StateEnableFactory.Instantiate(this));
                if (base.StateEnable is null)
                    throw NotAssignedException.From(this, base.StateEnable);
            }

            return base.StateEnable;
        }
    }

    public EngineTime Time { get; private set; } = new();

    public void Register(IHierarchyObject hierarchyObject)
    {
        if (_hierarchyObjects.Contains(hierarchyObject))
            throw new Exception($"{nameof(IHierarchyObject)} named {hierarchyObject.Name} is already registered to the {nameof(GameManager)}.");

        hierarchyObject.OnFinalized += OnHierarchyObjectFinalize;
        hierarchyObject.OnExitedHierarchy += OnHierarchyObjectExitedHierarchy;

        if (!hierarchyObject.Initialize())
            throw new Exception($"{hierarchyObject.Name} can't be initialized");

        foreach (IHierarchyObject child in hierarchyObject.Children)
            Register(child);

        _hierarchyObjects.Add(hierarchyObject);

        if (!hierarchyObject.EnterHierarchy(this))
            throw new Exception($"{hierarchyObject.Name} can't enter the hierarchy");

        OnHierarchyObjectRegistered?.Invoke(this, hierarchyObject);
    }

    public T InstantiateHierarchyObject<T>(params object?[]? args) where T : class, IHierarchyObject
    {
        T hierarchyObject = Factory.HierarchyObjectFactory.Instantiate<T>(args);
        Register(hierarchyObject);
        return hierarchyObject;
    }

    public void Remove(IHierarchyObject hierarchyObject)
    {
        if (!_hierarchyObjects.Contains(hierarchyObject))
            throw new Exception($"{nameof(IHierarchyObject)} named {hierarchyObject.Name} is not registered to the {nameof(GameManager)}.");

        hierarchyObject.OnFinalized -= OnHierarchyObjectFinalize;
        hierarchyObject.OnExitedHierarchy -= OnHierarchyObjectExitedHierarchy;

        foreach (IHierarchyObject child in hierarchyObject.Children)
            Remove(child);

        _hierarchyObjects.Remove(hierarchyObject);

        if (!hierarchyObject.ExitHierarchy())
            throw new Exception($"{hierarchyObject.Name} can't exit the hierarchy");

        if (!hierarchyObject.Finalize())
            throw new Exception($"{hierarchyObject.Name} can't be finalized");

        OnHierarchyObjectUnRegistered?.Invoke(this, hierarchyObject);
    }

    protected override void InitializeInternal()
    {
        base.InitializeInternal();
        NotAssignedException.Check(this, StateEnable);

        foreach (IHierarchyObject hierarchyObject in HierarchyObjects)
            hierarchyObject.Initialize();
    }

    protected override void FinalizeInternal()
    {
        base.FinalizeInternal();
        for (int i = HierarchyObjects.Count; i >= 0; i--)
            HierarchyObjects[i].Finalize();
    }

    public void Update(EngineTime engineTime)
    {
        Time = engineTime;

        for (int i = 0; i < HierarchyObjects.Count; i++)
            HierarchyObjects[i].BehaviourController.Update();

        OnUpdate?.Invoke(this, engineTime);
    }

    public void PreDraw()
    {
        for (int i = 0; i < HierarchyObjects.Count; i++)
            HierarchyObjects[i].BehaviourController.UpdatePreDraw();

        OnPreDraw?.Invoke(this);
    }

    private void OnHierarchyObjectFinalize(IInitializable initializable)
    {
        if (initializable is IHierarchyObject hierarchyObject)
            Remove(hierarchyObject);
    }

    private void OnHierarchyObjectExitedHierarchy(IHierarchyObject sender, IGameManager gameManager)
    {
        if (sender is IHierarchyObject hierarchyObject)
            Remove(hierarchyObject);
    }

    public IEnumerator<IHierarchyObject> GetEnumerator() => _hierarchyObjects.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _hierarchyObjects.GetEnumerator();
}