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

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

namespace Syntriax.Engine.Core;

[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : IBehaviourController
{
    public event IBehaviourController.PreUpdateEventHandler? OnPreUpdate = null;
    public event IBehaviourController.UpdateEventHandler? OnUpdate = null;
    public event IBehaviourController.PreDrawEventHandler? OnPreDraw = null;

    public event IBehaviourController.BehaviourAddedEventHandler? OnBehaviourAdded = null;
    public event IBehaviourController.BehaviourRemovedEventHandler? OnBehaviourRemoved = null;
    public event IHasHierarchyObject.HierarchyObjectAssignedEventHandler? OnHierarchyObjectAssigned = null;

    public event IInitializable.InitializedEventHandler? OnInitialized = null;
    public event IInitializable.FinalizedEventHandler? OnFinalized = null;

    public event IAssignable.UnassignEventHandler? OnUnassigned = null;

    private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL);

    private IHierarchyObject _hierarchyObject = null!;
    private bool _initialized = false;

    public IHierarchyObject HierarchyObject => _hierarchyObject;

    public bool IsInitialized
    {
        get => _initialized;
        private set
        {
            if (value == _initialized)
                return;

            _initialized = value;
            if (value)
                OnInitialized?.Invoke(this);
            else
                OnFinalized?.Invoke(this);
        }
    }

    public T AddBehaviour<T>(T behaviour) where T : class, IBehaviour
    {
        InsertBehaviourByPriority(behaviour);

        behaviour.Assign(this);
        behaviour.Assign(Factory.StateEnableFactory.Instantiate(behaviour));

        behaviour.Initialize();
        behaviour.OnPriorityChanged += OnPriorityChange;
        OnBehaviourAdded?.Invoke(this, behaviour);
        return behaviour;
    }

    public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour
        => AddBehaviour(Factory.BehaviourFactory.Instantiate<T>(_hierarchyObject, args));

    public T? GetBehaviour<T>()
    {
        foreach (IBehaviour behaviourItem in behaviours)
            if (behaviourItem is T result)
                return result;

        return default;
    }

    public IList<T> GetBehaviours<T>()
    {
        List<T>? behaviours = null;
        foreach (IBehaviour behaviourItem in this.behaviours)
        {
            if (behaviourItem is not T behaviour)
                continue;

            behaviours ??= [];
            behaviours.Add(behaviour);
        }

        return behaviours ?? Enumerable.Empty<T>().ToList();
    }

    public void GetBehaviours<T>(IList<T> results)
    {
        results.Clear();
        foreach (IBehaviour behaviourItem in behaviours)
        {
            if (behaviourItem is not T behaviour)
                continue;

            results.Add(behaviour);
        }
    }

    public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
    {
        for (int i = behaviours.Count; i >= 0; i--)
        {
            if (behaviours[i] is not T behaviour)
                continue;

            RemoveBehaviour(behaviour);

            if (!removeAll)
                return;
        }
    }

    public void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour
    {
        if (!behaviours.Contains(behaviour))
            throw new Exception($"{behaviour.GetType().Name} does not exist in {HierarchyObject.Name}'s {nameof(IBehaviourController)}.");

        behaviour.OnPriorityChanged -= OnPriorityChange;
        behaviour.Finalize();
        behaviours.Remove(behaviour);
        OnBehaviourRemoved?.Invoke(this, behaviour);
    }

    protected virtual void OnAssign(IHierarchyObject hierarchyObject) { }
    public bool Assign(IHierarchyObject hierarchyObject)
    {
        if (HierarchyObject is not null && HierarchyObject.IsInitialized)
            return false;

        _hierarchyObject = hierarchyObject;
        OnAssign(hierarchyObject);
        OnHierarchyObjectAssigned?.Invoke(this);
        return true;
    }

    public bool Initialize()
    {
        if (IsInitialized)
            return false;

        NotAssignedException.Check(this, _hierarchyObject);

        foreach (IBehaviour behaviour in behaviours)
            behaviour.Initialize();

        IsInitialized = true;
        return true;
    }

    public bool Finalize()
    {
        if (!IsInitialized)
            return false;

        foreach (IBehaviour behaviour in behaviours)
            behaviour.Finalize();

        IsInitialized = false;
        return true;
    }

    public bool Unassign()
    {
        if (IsInitialized)
            return false;

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

    public void Update()
    {
        if (!HierarchyObject.StateEnable.Enabled)
            return;

        OnPreUpdate?.Invoke(this);
        OnUpdate?.Invoke(this);
    }

    public void UpdatePreDraw()
    {
        if (!HierarchyObject.StateEnable.Enabled)
            return;

        OnPreDraw?.Invoke(this);
    }

    public BehaviourController() { }
    public BehaviourController(IHierarchyObject hierarchyObject) => Assign(hierarchyObject);

    private void InsertBehaviourByPriority<T>(T behaviour) where T : class, IBehaviour
    {
        int i;

        for (i = 0; i < behaviours.Count; i++)
        {
            if (behaviours[i].Priority > behaviour.Priority)
                continue;

            behaviours.Insert(i, behaviour);
            return;
        }

        if (i == 0 || i == behaviours.Count)
            behaviours.Add(behaviour);
    }

    private void OnPriorityChange(IBehaviour sender, int previousPriority)
    {
        behaviours.Remove(sender);
        InsertBehaviourByPriority(sender);
    }

    public IEnumerator<IBehaviour> GetEnumerator() => behaviours.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => behaviours.GetEnumerator();
}