using Engine.Graphics.Camera;
using Engine.Graphics.Pipeline;
using Engine.Scene.Component.BuiltIn;

namespace Engine.Scene;

public class Scene : IUpdate, IRender
{
  public bool IsPlaying { get; private set; }
  public IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras;
  public float TimeScale { get; set; } = 1.0f;

  private readonly Dictionary<RenderLayer, ICamera> _cameras = new();

  internal Hierarchy<GameObject> Hierarchy { get; } = new();

  private readonly Queue<Action> _sceneActions = [];

  internal void Enter()
  {
    if (IsPlaying)
    {
      throw new InvalidOperationException("Scene is already playing");
    }

    ProcessChanges();

    var allCameras = FindAllComponents<Camera>();
    foreach (var camera in allCameras)
    {
      _cameras.Add(camera.RenderLayer, camera);
    }

    IsPlaying = true;
  }

  public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component
  {
    return Hierarchy.Objects
      .Where(parGameObject => !parOnlyEnabled || parGameObject.IsEnabled)
      .Select(parGameObject => parGameObject.GetComponent<T>())
      .Where(parComponent => parComponent != null).ToList()!;
  }

  public T? FindFirstComponent<T>() where T : Component.Component
  {
    return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>())
      .FirstOrDefault(parComponent => parComponent != null);
  }

  public void Update(double parDeltaTime)
  {
    if (!IsPlaying)
    {
      throw new InvalidOperationException("Scene is not playing");
    }

    ProcessChanges();

    var hierarchyObjects = Hierarchy.Objects;

    foreach (var gameObject in hierarchyObjects)
    {
      gameObject.PreUpdate();
    }

    foreach (var gameObject in hierarchyObjects)
    {
      gameObject.Update(parDeltaTime * TimeScale);
    }
  }

  public void Render()
  {
    if (!IsPlaying)
    {
      throw new InvalidOperationException("Scene is not playing");
    }

    foreach (var gameObject in Hierarchy.Objects)
    {
      gameObject.Render();
    }
  }

  internal void Exit()
  {
    if (!IsPlaying)
    {
      throw new InvalidOperationException("Scene is not playing");
    }

    foreach (var gameObject in Hierarchy.Objects)
    {
      gameObject.Destroy();
    }

    IsPlaying = false;
  }

  public void Add(GameObject parGameObject)
  {
    parGameObject.Scene = this;
    Hierarchy.Add(parGameObject);
  }

  public void AddChild(GameObject parParent, GameObject parChild)
  {
    Add(parChild);
    Hierarchy.AddChild(parParent, parChild);
  }

  public void Remove(GameObject parGameObject)
  {
    var children = Hierarchy.GetAllChildren(parGameObject).ToList();
    Hierarchy.Remove(parGameObject);

    _sceneActions.Enqueue(() =>
    {
      foreach (var child in children)
      {
        child.Destroy();
        child.Scene = null;
      }

      parGameObject.Destroy();

      parGameObject.Scene = null;
    });
  }

  public IEnumerable<GameObject> GetChildren(GameObject parParent, bool parRecursive = false)
  {
    return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent);
  }

  private void ProcessChanges()
  {
    Hierarchy.ProcessChanges();

    foreach (var gameObject in Hierarchy.Objects)
    {
      gameObject.ProcessChanges();
    }

    while (_sceneActions.TryDequeue(out var action))
    {
      action();
    }
  }
}