namespace Fusion {
  using System;
  using Fusion.Sockets;
  using System.Collections;
  using System.Threading.Tasks;
  using UnityEngine;
  using UnityEngine.SceneManagement;
  using System.Collections.Generic;
  using System.Linq;
  using Statistics;
  using UnityEngine.Serialization;

#if UNITY_EDITOR
  using UnityEditor;
#endif

  /// <summary>
  /// A Fusion prototyping class for starting up basic networking. Add this component to your startup scene, and supply a <see cref="RunnerPrefab"/>.
  /// Can be set to automatically startup the network, display an in-game menu, or allow simplified start calls like <see cref="StartHost()"/>.
  /// </summary>
  [DisallowMultipleComponent]
  [AddComponentMenu("Fusion/Fusion Bootstrap")]
  [ScriptHelp(BackColor = ScriptHeaderBackColor.Steel)]
  public class FusionBootstrap : Fusion.Behaviour {

    /// <summary>
    /// Selection for how <see cref="FusionBootstrap"/> will behave at startup.
    /// </summary>
    public enum StartModes {
      UserInterface,
      Automatic,
      Manual
    }

    /// <summary>
    /// The current stage of connection or shutdown.
    /// </summary>
    public enum Stage {
      Disconnected,
      StartingUp,
      UnloadOriginalScene,
      ConnectingServer,
      ConnectingClients,
      AllConnected,
    }

    [Serializable]
    class StartCommand : FusionMppmCommand {
      public string RoomName;
      public SceneRef InitialScene;
      public int ClientCount;
      public bool IsShared;

      public override void Execute() { 
        Instance = this;
      }

      public static StartCommand Instance;
    }

    /// <summary>
    /// Supply a Prefab or a scene object which has the <see cref="NetworkRunner"/> component on it, 
    /// as well as any runner dependent components which implement <see cref="INetworkRunnerCallbacks"/>, 
    /// such as <see cref="NetworkEvents"/> or your own custom INetworkInput implementations.
    /// </summary>
    [InlineHelp]
    [WarnIf(nameof(RunnerPrefab), false, "No " + nameof(RunnerPrefab) + " supplied. Will search for a " + nameof(NetworkRunner) + " in the scene at startup.")]
    public NetworkRunner RunnerPrefab;

    /// <summary>
    /// Select how network startup will be triggered. Automatically, by in-game menu selection, or exclusively by script.
    /// </summary>
    [InlineHelp]
    [WarnIf(nameof(StartMode), (long)StartModes.Manual, "Start network by calling the methods " + nameof(StartHost) + "(), " + nameof(StartServer) + "(), " + nameof(StartClient) + "(), " + nameof(StartHostPlusClients) + "(), or " + nameof(StartServerPlusClients) + "()")]
    public StartModes StartMode = StartModes.UserInterface;

    /// <summary>
    /// When <see cref="StartMode"/> is set to <see cref="StartModes.Automatic"/>, this option selects if the <see cref="NetworkRunner"/> 
    /// will be started as a dedicated server, or as a host (which is a server with a local player).
    /// </summary>
    [InlineHelp]
    [UnityEngine.Serialization.FormerlySerializedAs("Server")]
    [DrawIf(nameof(StartMode), (long)StartModes.Automatic, Hide = true)]
    public GameMode AutoStartAs = GameMode.Shared;

    /// <summary>
    /// <see cref="FusionBootstrapDebugGUI"/> will not render GUI elements while <see cref="CurrentStage"/> == <see cref="Stage.AllConnected"/>.
    /// </summary>
    [InlineHelp]
    [DrawIf(nameof(StartMode), (long)StartModes.UserInterface, Hide = true)]
    public bool AutoHideGUI = true;

    /// <summary>
    /// The number of client <see cref="NetworkRunner"/> instances that will be created if running in Mulit-Peer Mode. 
    /// When using the Select start mode, this number will be the default value for the additional clients option box.
    /// </summary>
    [InlineHelp]
    [DrawIf(nameof(ShowAutoClients), Hide = true)]
    public int AutoClients = 1;

    /// <summary>
    /// How long to wait after starting a peer before starting the next one.
    /// </summary>
    [InlineHelp]
    public float ClientStartDelay = 0.1f;

    /// <summary>
    /// The port that server/host <see cref="NetworkRunner"/> will use.
    /// </summary>
    [InlineHelp]
    public ushort ServerPort; // Any port

    /// <summary>
    /// The default room name to use when connecting to photon cloud.
    /// </summary>
    [InlineHelp]
    public string DefaultRoomName = string.Empty; // empty/null means Random Room Name

    [NonSerialized]
    NetworkRunner _server;

    /// <summary>
    /// The Scene that will be loaded after network shutdown completes (all peers have disconnected). 
    /// If this field is null or invalid, will be set to the current scene when <see cref="FusionBootstrap"/> runs Awake().
    /// </summary>
    [InlineHelp]
    [ScenePath]
    public string InitialScenePath;
    
    // TODO: this is debt
    static string _initialScenePath;
    
    /// <summary>
    /// Indicates which step of the startup process <see cref="FusionBootstrap"/> is currently in.
    /// </summary>
    [InlineHelp]
    [SerializeField]
    [ReadOnly]
    protected Stage _currentStage;
    
    /// <summary>
    /// Requires Multiplayer Play Mode (MPPM) to be installed. If enabled, <see cref="FusionBootstrap"/> will automatically join the virtual instance.
    /// </summary>
    [DrawIf(nameof(IsMPPMEnabled), true)] 
    [Header("Multiplayer Play Mode")]
    public bool AutoConnectVirtualInstances = true;
    /// <summary>
    /// How much to wait before the main instance lets the virtual instances connect.
    /// </summary>
    [DrawIf(nameof(IsMPPMEnabled), true)] 
    public float VirtualInstanceConnectDelay = 1f;
    
    /// <summary>
    /// Indicates which step of the startup process <see cref="FusionBootstrap"/> is currently in.
    /// </summary>
    public Stage CurrentStage {
      get => _currentStage;
      internal set {
        _currentStage = value;
#if UNITY_EDITOR
        // Hack to force an inspector refresh when this value changes, as it affects which buttons are shown.
        EditorUtility.SetDirty(this);
#endif
      }
    }

    /// <summary>
    /// The index number used for the last created peer.
    /// </summary>
    public int LastCreatedClientIndex { get; internal set; }

    /// <summary>
    /// The server mode that was used for initial startup. Used to inform UI which client modes should be available.
    /// </summary>
    public GameMode CurrentServerMode { get; internal set; }

    protected bool CanAddClients => CurrentStage == Stage.AllConnected && CurrentServerMode > 0 && CurrentServerMode != GameMode.Shared && CurrentServerMode != GameMode.Single;
    protected bool CanAddSharedClients => CurrentStage == Stage.AllConnected && CurrentServerMode > 0 && CurrentServerMode == GameMode.Shared;
    protected bool IsShutdown => CurrentStage == Stage.Disconnected;
    protected bool IsShutdownAndMultiPeer => CurrentStage == Stage.Disconnected && UsingMultiPeerMode;

    protected bool UsingMultiPeerMode => NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple;
    protected bool ShowAutoClients    => UsingMultiPeerMode && (StartMode == StartModes.UserInterface || (StartMode == StartModes.Automatic && AutoStartAs != GameMode.Single));


#if UNITY_EDITOR
    protected virtual void Reset() {
      if (TryGetComponent<FusionBootstrapDebugGUI>(out var ndsg) == false) {
        ndsg = gameObject.AddComponent<FusionBootstrapDebugGUI>();
      }
    }

#endif


    protected virtual void Start() {

      if (_initialScenePath == null) {
        if (string.IsNullOrEmpty(InitialScenePath)) {
          var currentScene = SceneManager.GetActiveScene();
          if (currentScene.IsValid()) {
            _initialScenePath = currentScene.path;
          } else {
            // Last fallback is the first entry in the build settings
            _initialScenePath = SceneManager.GetSceneByBuildIndex(0).path;
          }

          InitialScenePath = _initialScenePath;
        } else {
          _initialScenePath = InitialScenePath;
        }
      }

      var config      = NetworkProjectConfig.Global;
      var isMultiPeer = config.PeerMode == NetworkProjectConfig.PeerModes.Multiple;

      var existingRunner = FindFirstObjectByType<NetworkRunner>();

      if (existingRunner && existingRunner != RunnerPrefab) {
        if (existingRunner.State != NetworkRunner.States.Shutdown) {
          // disable
          enabled = false;

          // destroy this and GUI (if exists), and return
          var gui = GetComponent<FusionBootstrapDebugGUI>();
          if (gui) {
            Destroy(gui);
          }

          Destroy(this);
          return;
        } else {
          // If no RunnerPrefab is supplied, use the scene runner.
          if (RunnerPrefab == null) {
            RunnerPrefab = existingRunner;
          }
        }
      }

      if (FusionMppm.Status == FusionMppmStatus.VirtualInstance && AutoConnectVirtualInstances) {
        StartCoroutine(StartWithMppmVirtualInstance());
        return;
      }

      switch (StartMode) {
        case StartModes.Manual:
          // skip
          return;
        case StartModes.Automatic: {
          if (TryGetSceneRef(out var sceneRef)) {
            int clientCount;
            if (AutoStartAs == GameMode.Single) {
              clientCount = 0;
            } else if (isMultiPeer) {
              clientCount = AutoClients;
            } else if (AutoStartAs == GameMode.Client || AutoStartAs == GameMode.Shared || AutoStartAs == GameMode.AutoHostOrClient) {
              clientCount = 1;
            } else {
              clientCount = 0;
            }

            StartCoroutine(StartWithClients(AutoStartAs, sceneRef, clientCount));
          }

          break;
        }
        default: {
          ShowUserInterface();
          break;
        }
      }
    }

    protected void ShowUserInterface() {
      if (TryGetComponent<FusionBootstrapDebugGUI>(out var gui) == false) {
        gui = gameObject.AddComponent<FusionBootstrapDebugGUI>();
      }
      gui.enabled = true;
    }
    
    private bool TryGetSceneRef(out SceneRef sceneRef) {
      var activeScene = SceneManager.GetActiveScene();
      if (activeScene.buildIndex < 0 || activeScene.buildIndex >= SceneManager.sceneCountInBuildSettings) {
        sceneRef = default;
        return false;
      } else {
        sceneRef = SceneRef.FromIndex(activeScene.buildIndex);
        return true;
      }
    }

    /// <summary>
    /// Start a single player instance.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartSinglePlayer() {
      if (TryGetSceneRef(out var sceneRef)) {
        StartCoroutine(StartWithClients(GameMode.Single, sceneRef, 0));
      }
    }

    /// <summary>
    /// Start a server instance.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartServer() {
      if (TryGetSceneRef(out var sceneRef)) {
        StartCoroutine(StartWithClients(GameMode.Server, sceneRef, 0));
      }
    }

    /// <summary>
    /// Start a host instance. This is a server instance, with a local player.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartHost() {
      if (TryGetSceneRef(out var sceneRef)) {
        StartCoroutine(StartWithClients(GameMode.Host, sceneRef, 0));
      }
    }

    /// <summary>
    /// Start a client instance.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartClient() {
      StartCoroutine(StartWithClients(GameMode.Client, default, 1));
    }
    
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartSharedClient() {
      if (TryGetSceneRef(out var sceneRef)) {
        StartCoroutine(StartWithClients(GameMode.Shared, sceneRef, 1));
      }
    }
    
    [EditorButton("Start Auto Host Or Client", EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartAutoClient() {
      if (TryGetSceneRef(out var sceneRef)) {
        StartCoroutine(StartWithClients(GameMode.AutoHostOrClient, sceneRef, 1));
      }
    }

    /// <summary>
    /// Start a Fusion server instance, and the number of client instances indicated by <see cref="AutoClients"/>. 
    /// InstanceMode must be set to Multi-Peer mode, as this requires multiple <see cref="NetworkRunner"/> instances.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public virtual void StartServerPlusClients() {
      StartServerPlusClients(AutoClients);
    }

    /// <summary>
    /// Start a Fusion host instance, and the number of client instances indicated by <see cref="AutoClients"/>. 
    /// InstanceMode must be set to Multi-Peer mode, as this requires multiple <see cref="NetworkRunner"/> instances.
    /// </summary>
    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(IsShutdown), Hide = true)]
    public void StartHostPlusClients() {
      StartHostPlusClients(AutoClients);
    }

    [EditorButton(EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(CurrentStage), Hide = true)]
    public void Shutdown() {
      ShutdownAll();
    }

    /// <summary>
    /// Start a Fusion server instance, and the indicated number of client instances. 
    /// InstanceMode must be set to Multi-Peer mode, as this requires multiple <see cref="NetworkRunner"/> instances.
    /// </summary>
    public virtual void StartServerPlusClients(int clientCount) {
      if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) {
        if (TryGetSceneRef(out var sceneRef)) {
          StartCoroutine(StartWithClients(GameMode.Server, sceneRef, clientCount));
        }
      } else {
        Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode.");
      }
    }

    /// <summary>
    /// Start a Fusion host instance (server with local player), and the indicated number of additional client instances. 
    /// InstanceMode must be set to Multi-Peer mode, as this requires multiple <see cref="NetworkRunner"/> instances.
    /// </summary>
    public void StartHostPlusClients(int clientCount) {
      if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) {
        if (TryGetSceneRef(out var sceneRef)) {
          StartCoroutine(StartWithClients(GameMode.Host, sceneRef, clientCount));
        }
      } else {
        Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode.");
      }
    }

    /// <summary>
    /// Start a Fusion host instance (server with local player), and the indicated number of additional client instances. 
    /// InstanceMode must be set to Multi-Peer mode, as this requires multiple <see cref="NetworkRunner"/> instances.
    /// </summary>
    public void StartMultipleClients(int clientCount) {
      if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) {
        if (TryGetSceneRef(out var sceneRef)) {
          StartCoroutine(StartWithClients(GameMode.Client, sceneRef, clientCount));
        }
      } else {
        Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode.");
      }
    }

    /// <summary>
    /// Start as Room on the Photon cloud, and connects as one or more clients.
    /// </summary>
    /// <param name="clientCount"></param>
    public void StartMultipleSharedClients(int clientCount) {
      if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) {
        if (TryGetSceneRef(out var sceneRef)) {
          StartCoroutine(StartWithClients(GameMode.Shared, sceneRef, clientCount));
        }
      } else {
        Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode.");
      }
    }

    public void StartMultipleAutoClients(int clientCount) {
      if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) {
        if (TryGetSceneRef(out var sceneRef)) {
          StartCoroutine(StartWithClients(GameMode.AutoHostOrClient, sceneRef, clientCount));
        }
      } else {
        Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode.");
      }
    }

    public void ShutdownAll() {
      foreach (var runner in NetworkRunner.Instances.ToList()) {
        if (runner != null && runner.IsRunning) {
          runner.Shutdown();
        }
      }

      SceneManager.LoadSceneAsync(_initialScenePath);
      // Destroy our DontDestroyOnLoad objects to finish the reset
      Destroy(RunnerPrefab.gameObject);
      Destroy(gameObject);
      CurrentStage = Stage.Disconnected;
      CurrentServerMode = 0;
    }


    protected IEnumerator StartWithClients(GameMode serverMode, SceneRef sceneRef, int clientCount) {
      // Avoid double clicks or disallow multiple startup calls.
      if (CurrentStage != Stage.Disconnected) {
        yield break;
      }

      bool includesServerStart = serverMode != GameMode.Shared && serverMode != GameMode.Client && serverMode != GameMode.AutoHostOrClient;

      if (!includesServerStart && clientCount == 0) {
        Debug.LogError($"{nameof(GameMode)} is set to {serverMode}, and {nameof(clientCount)} is set to zero. Starting no network runners.");
        yield break;
      }

      CurrentStage = Stage.StartingUp;

      var currentScene = SceneManager.GetActiveScene();

      // must have a runner
      if (!RunnerPrefab) {
        Debug.LogError($"{nameof(RunnerPrefab)} not set, can't perform debug start.");
        yield break;
      }

      // Clone the RunnerPrefab so we can safely delete the startup scene (the prefab might be part of it, rather than an asset).
      RunnerPrefab = Instantiate(RunnerPrefab);
      DontDestroyOnLoad(RunnerPrefab);
      RunnerPrefab.name = "Temporary Runner Prefab";

      // Single-peer can't start more than one peer. Validate clientCount to make sure it complies with current PeerMode.
      var config = NetworkProjectConfig.Global;
      if (config.PeerMode != NetworkProjectConfig.PeerModes.Multiple) {
        int maxClientsAllowed = includesServerStart ? 0 : 1;
        if (clientCount > maxClientsAllowed) {
          Debug.LogWarning($"Instance mode must be set to {nameof(NetworkProjectConfig.PeerModes.Multiple)} to perform a debug start multiple peers. Restricting client count to {maxClientsAllowed}.");
          clientCount = maxClientsAllowed;
        }
      }

      // If NDS is starting more than 1 shared or auto client, they need to use the same Session Name, otherwise, they will end up on different Rooms
      // as Fusion creates a Random Session Name when no name is passed on the args
      var localMultipeerCheck = (serverMode == GameMode.Shared || serverMode == GameMode.AutoHostOrClient || serverMode == GameMode.Server || serverMode == GameMode.Host) &&
                                clientCount     > 1                                                                                                                        &&
                                config.PeerMode == NetworkProjectConfig.PeerModes.Multiple;
      var isMppmMainInstance = FusionMppm.Status == FusionMppmStatus.MainInstance;
      
      if ((localMultipeerCheck || isMppmMainInstance) && string.IsNullOrEmpty(DefaultRoomName)) {
        DefaultRoomName = Guid.NewGuid().ToString();
        Debug.Log($"Generated Session Name: {DefaultRoomName}");
      }

      if (gameObject.transform.parent) {
        Debug.LogWarning($"{nameof(FusionBootstrap)} can't be a child game object, un-parenting.");
        gameObject.transform.parent = null;
      }

      DontDestroyOnLoad(gameObject);
      CurrentServerMode = serverMode;

      // start server, just take address from it
      if (includesServerStart) {
        _server = Instantiate(RunnerPrefab);
        _server.name = serverMode.ToString();

        var serverTask = InitializeNetworkRunner(_server, serverMode, NetAddress.Any(ServerPort), sceneRef, (runner) => {
#if FUSION_DEV
          var name = _server.name; // closures do not capture values, need a local var to save it
          Debug.Log($"Server NetworkRunner '{name}' started.");
#endif
        });

        while (serverTask.IsCompleted == false) {
          yield return new WaitForSeconds(1f);
        }

        if (serverTask.IsFaulted) {
          Log.Debug($"Unable to start server: {serverTask.Exception}");

          ShutdownAll();
          yield break;
        }

        // this action is called after InitializeNetworkRunner for the server has completed startup
        yield return StartClients(clientCount, serverMode, sceneRef);
      } else {
        yield return StartClients(clientCount, serverMode, sceneRef);
      }
      
      if (FusionMppm.Status == FusionMppmStatus.MainInstance && serverMode != GameMode.Single) {
        if (VirtualInstanceConnectDelay > 0) {
          yield return new WaitForSecondsRealtime(VirtualInstanceConnectDelay);
        }
        FusionMppm.MainEditor?.Send(new StartCommand {
          RoomName = DefaultRoomName,
          InitialScene = sceneRef,
          ClientCount =  1,
          IsShared = serverMode == GameMode.Shared
        });
      }
    }

    protected IEnumerator StartWithMppmVirtualInstance() {
      while (StartCommand.Instance == null) {
        yield return null;
      }
      
      var command = StartCommand.Instance;
      StartCommand.Instance = null;
      
      DefaultRoomName = command.RoomName;
      yield return StartClients(command.ClientCount, command.IsShared ? GameMode.Shared : GameMode.Client, command.InitialScene);
    }

    [EditorButton("Add Additional Client", EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(CanAddClients), Hide = true)]
    public void AddClient() {
      if (TryGetSceneRef(out var sceneRef)) {
        AddClient(GameMode.Client, sceneRef);
      }
    }

    [EditorButton("Add Additional Shared Client", EditorButtonVisibility.PlayMode)]
    [DrawIf(nameof(CanAddSharedClients), Hide = true)]
    public void AddSharedClient() {
      if (TryGetSceneRef(out var sceneRef)) {
        AddClient(GameMode.Shared, sceneRef);
      }
    }

    public Task AddClient(GameMode serverMode, SceneRef sceneRef) {
      var client = Instantiate(RunnerPrefab);
      DontDestroyOnLoad(client);

      client.name = $"Client {(Char)(65 + LastCreatedClientIndex++)}";

      // if server mode is Shared or AutoHostOrClient, then game client mode is the same as the server, otherwise it is client
      var mode = GameMode.Client;
      switch (serverMode) {
        case GameMode.Shared:
        case GameMode.AutoHostOrClient:
          mode = serverMode;
          break;
      }

#if FUSION_DEV
      var clientTask = InitializeNetworkRunner(client, mode, NetAddress.Any(), sceneRef, (runner) => {
        var name = client.name; // closures do not capture values, need a local var to save it
        Debug.Log($"Client NetworkRunner '{name}' started.");
      });
#else
      var clientTask = InitializeNetworkRunner(client, mode, NetAddress.Any(), sceneRef, null);
#endif

      return clientTask;
    }

    protected IEnumerator StartClients(int clientCount, GameMode serverMode, SceneRef sceneRef = default) {

      CurrentStage = Stage.ConnectingClients;

      var clientTasks = new List<Task>();
      for (int i = 0; i < clientCount; ++i) {
        clientTasks.Add(AddClient(serverMode, sceneRef));
        yield return new WaitForSeconds(ClientStartDelay);
      }

      var clientsStartTask = Task.WhenAll(clientTasks);

      while (clientsStartTask.IsCompleted == false) {
        yield return new WaitForSeconds(1f);
      }

      if (clientsStartTask.IsFaulted) {
        Debug.LogWarning(clientsStartTask.Exception);
      }

      CurrentStage = Stage.AllConnected;
    }

    protected virtual Task InitializeNetworkRunner(NetworkRunner runner, GameMode gameMode, NetAddress address, SceneRef scene, Action<NetworkRunner> onGameStarted, 
      INetworkRunnerUpdater updater = null) {

      var sceneManager = runner.GetComponent<INetworkSceneManager>();
      if (sceneManager == null) {
        Debug.Log($"NetworkRunner does not have any component implementing {nameof(INetworkSceneManager)} interface, adding {nameof(NetworkSceneManagerDefault)}.", runner);
        sceneManager = runner.gameObject.AddComponent<NetworkSceneManagerDefault>();
      }

      var objectProvider = runner.GetComponent<INetworkObjectProvider>();
      if (objectProvider == null) {
        Debug.Log($"NetworkRunner does not have any component implementing {nameof(INetworkObjectProvider)} interface, adding {nameof(NetworkObjectProviderDefault)}.", runner);
        objectProvider = runner.gameObject.AddComponent<NetworkObjectProviderDefault>();
      }

      var sceneInfo = new NetworkSceneInfo();
      if (scene.IsValid) {
        sceneInfo.AddSceneRef(scene, LoadSceneMode.Additive);
      }

      return runner.StartGame(new StartGameArgs {
        GameMode       = gameMode,
        Address        = address,
        Scene          = sceneInfo,
        SessionName    = DefaultRoomName,
        OnGameStarted    = onGameStarted,
        SceneManager   = sceneManager,
        Updater        = updater,
        ObjectProvider = objectProvider,
      });
    }
    
    private static bool IsMPPMEnabled => FusionMppm.Status != FusionMppmStatus.Disabled;
    
    /// <summary>
    /// Only show the GUI if the StartMode is set to UserInterface and not being run in a Virtual Instance (MPPM).
    /// </summary>
    public bool ShouldShowGUI => StartMode == StartModes.UserInterface &&
                                 !(AutoConnectVirtualInstances && FusionMppm.Status == FusionMppmStatus.VirtualInstance);
  }
}