using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Engine.Core.Exceptions;
namespace Engine.Core;
public static class BehaviourControllerExtensions
{
    /// 
    /// Tries to get a  of the specified type.
    /// 
    /// The type of  to get.
    /// The  to search in.
    /// When this method returns, contains the  of the specified type, if found; otherwise, null.
    ///  if a  of the specified type was found; otherwise, .
    public static bool TryGetBehaviour(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour)
    {
        behaviour = behaviourController.GetBehaviour();
        return behaviour is not null;
    }
    /// 
    /// Gets a  of the specified type in the provided . Throws an error if not found.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// The  of the specified type if found; otherwise, throws .
    public static T GetRequiredBehaviour(this IBehaviourController behaviourController) where T : class
        => behaviourController.GetBehaviour() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
    /// 
    /// Gets an existing  of the specified type, or adds and returns a new one if it doesn't exist.
    /// 
    /// The type of  to get or add.
    /// The  to search in.
    /// Optional arguments to pass to the constructor of the  if a new one is added.
    /// The existing or newly added  of the specified type.
    public static T GetOrAddBehaviour(this IBehaviourController behaviourController, params object?[]? args) where T : class, IBehaviour
        => behaviourController.GetBehaviour() ?? behaviourController.AddBehaviour(args);
    /// 
    /// Gets an existing  of the specified type, or adds and returns the fallback type if it doesn't exist.
    /// 
    /// The type of  to get.
    /// The type of  to add. It must be assignable from 
    /// The  to search in.
    /// Optional arguments to pass to the constructor of the  if a new one is added.
    /// The existing or newly added  of the specified type.
    public static TOriginal GetOrAddBehaviour(this IBehaviourController behaviourController, params object?[]? args)
            where TOriginal : class
            where TFallback : class, IBehaviour, TOriginal
        => behaviourController.GetBehaviour() ?? behaviourController.AddBehaviour(args);
    /// 
    /// Tries to get a  of the specified type in it's 's parents recursively.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// When this method returns, contains the  of the specified type, if found; otherwise, null.
    ///  if a  of the specified type was found in the parent universe; otherwise, .
    public static bool TryGetBehaviourInParent(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
    {
        behaviour = GetBehaviourInParent(behaviourController);
        return behaviour is not null;
    }
    /// 
    /// Gets a  of the specified type in it's 's parents recursively.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// The  of the specified type if found; otherwise, null.
    public static T? GetBehaviourInParent(this IBehaviourController behaviourController) where T : class
    {
        IBehaviourController? controller = behaviourController;
        while (controller is not null)
        {
            if (controller.GetBehaviour() is T behaviour)
                return behaviour;
            controller = controller.UniverseObject.Parent?.BehaviourController;
        }
        return default;
    }
    /// 
    /// Gets a  of the specified type in it's 's parents recursively. Throws an error if not found.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// The  of the specified type if found; otherwise, throws .
    public static T GetRequiredBehaviourInParent(this IBehaviourController behaviourController) where T : class
        => behaviourController.GetBehaviourInParent() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
    /// 
    /// Gets all s of the specified type in it's 's parents recursively and stores them in the provided list.
    /// 
    /// The type of s to get.
    /// The list to store the s.
    /// Whether to clear the  before collection or append the results to the list.
    public static void GetBehavioursInParent(this IBehaviourController behaviourController, IList behavioursInParent, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
    {
        if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
            behavioursInParent.Clear();
        IBehaviourController? controller = behaviourController;
        while (controller is not null)
        {
            controller.GetBehaviours(behavioursInParent, IBehaviourController.CollectionMethod.Append);
            controller = controller.UniverseObject.Parent?.BehaviourController;
        }
    }
    /// 
    /// Tries to get a  of the specified type in it's 's children recursively.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// When this method returns, contains the  of the specified type, if found; otherwise, null.
    ///  if a  of the specified type was found in the child universe; otherwise, .
    public static bool TryGetBehaviourInChildren(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
    {
        behaviour = GetBehaviourInChildren(behaviourController);
        return behaviour is not null;
    }
    /// 
    /// Gets a  of the specified type in it's 's children recursively.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// The  of the specified type if found; otherwise, null.
    public static T? GetBehaviourInChildren(this IBehaviourController behaviourController) where T : class
    {
        if (behaviourController.GetBehaviour() is T localBehaviour)
            return localBehaviour;
        foreach (IUniverseObject child in behaviourController.UniverseObject.Children)
            if (GetBehaviourInChildren(child.BehaviourController) is T behaviour)
                return behaviour;
        return default;
    }
    /// 
    /// Gets a  of the specified type in the children recursively. Throws an error if not found.
    /// 
    /// The type of  to get.
    /// The  to start searching from.
    /// The  of the specified type if found; otherwise, throws .
    public static T GetRequiredBehaviourInChildren(this IBehaviourController behaviourController) where T : class
        => behaviourController.GetBehaviourInChildren() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
    /// 
    /// Gets all s of the specified type in it's 's children recursively and stores them in the provided list.
    /// 
    /// The type of s to get.
    /// The list to store the s.
    /// Whether to clear the  before collection or append the results to the list.
    public static void GetBehavioursInChildren(this IBehaviourController behaviourController, IList behavioursInChildren, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
    {
        if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
            behavioursInChildren.Clear();
        TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren);
    }
    private static void TraverseChildrenForBehaviour(IUniverseObject universeObject, IList behaviours) where T : class
    {
        universeObject.BehaviourController.GetBehaviours(behaviours, IBehaviourController.CollectionMethod.Append);
        foreach (IUniverseObject child in universeObject.Children)
            TraverseChildrenForBehaviour(child, behaviours);
    }
}