// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkOpenXRManagerRemoteConnection.h"

#include "vtkObjectFactory.h"
#include "vtkOpenXRManager.h"
#include "vtkResourceFileLocator.h"
#include "vtksys/SystemTools.hxx"

// Starting with Microsoft.Holographic.Remoting.OpenXr 2.9.3, the struct
// `XrRemotingPreferredGraphicsAdapterMSFT` is defined in `openxr_msft_holographic_remoting.h`
// and it requires the type LUID to be defined.
// Since LUID is defined in `windows.h` (see `OpenXR-SDK-Source/specification/registry/xr.xml`),
// we include the corresponding VTK header.
#include <vtkWindows.h> // Defines LUID used in XrRemotingPreferredGraphicsAdapterMSFT

#include <openxr_msft_holographic_remoting.h> // Defines XR_MSFT_holographic_remoting

#include "XrConnectionExtensions.h" // Provides holographic remoting extensions

#include <chrono> // for chrono_literals
#include <thread> // used to sleep after connection

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkOpenXRManagerRemoteConnection);

static bool vtkOpenXRManagerRemoteConnectionInitializeSymbol()
{
  return true;
}

//------------------------------------------------------------------------------
bool vtkOpenXRManagerRemoteConnection::Initialize()
{
  // Get the path for the library
  std::string libPath =
    vtkGetLibraryPathForSymbol(vtkOpenXRManagerRemoteConnectionInitializeSymbol);
  std::string libDir = vtksys::SystemTools::GetFilenamePath(libPath);

  // Get the path for the current executable
  std::string exePath = vtkResourceFileLocator::GetCurrentExecutablePath();
  std::string exeDir = vtksys::SystemTools::GetFilenamePath(exePath);

  // Look for the RemotingXR.json file provided by the microsoft.holographic.remoting.openxr
  // package, in the system PATH, next to the vtkRenderingOpenXR library and also next to the
  // executable.
  // If found, set the XR_RUNTIME_JSON environment variable. It will be used by the OpenXR loader
  // to not use the system default OpenXR runtime but instead redirect to the Holographic Remoting
  // OpenXR runtime.
  std::string remotingXRPath =
    vtksys::SystemTools::FindFile("RemotingXR.json", { libDir, exeDir, this->RemotingXRDirectory });
  if (remotingXRPath.empty())
  {
    return false;
  }

  std::string oldValue;
  if (vtksys::SystemTools::GetEnv("XR_RUNTIME_JSON", oldValue))
  {
    this->OldXrRuntimeEnvValue = std::move(oldValue);
  }

  if (!vtksys::SystemTools::PutEnv("XR_RUNTIME_JSON=" + remotingXRPath))
  {
    return false;
  }

  return true;
}

//------------------------------------------------------------------------------
bool vtkOpenXRManagerRemoteConnection::EndInitialize()
{
  if (!this->OldXrRuntimeEnvValue.empty())
  {
    if (!vtksys::SystemTools::PutEnv("XR_RUNTIME_JSON=" + this->OldXrRuntimeEnvValue))
    {
      return false;
    }
  }
  else if (!vtksys::SystemTools::UnPutEnv("XR_RUNTIME_JSON"))
  {
    return false;
  }

  return true;
}

//------------------------------------------------------------------------------
bool vtkOpenXRManagerRemoteConnection::ConnectToRemote(vtk::detail::vtkOpenXRManager& manager)
{
  if (this->IPAddress.empty())
  {
    vtkErrorMacro("Remoting IP address unspecified.");
    return false;
  }

  XrRemotingConnectionStateMSFT connectionState;
  xr::ConnectionExtensionDispatchTable extensions;
  extensions.PopulateDispatchTable(manager.GetXrRuntimeInstance());
  extensions.xrRemotingGetConnectionStateMSFT(
    manager.GetXrRuntimeInstance(), manager.GetSystemID(), &connectionState, nullptr);
  if (connectionState != XR_REMOTING_CONNECTION_STATE_DISCONNECTED_MSFT)
  {
    vtkErrorMacro("Error connecting to " << this->IPAddress << ": " << connectionState);
    return false;
  }

  // Apply remote context properties while disconnected.
  {
    XrRemotingRemoteContextPropertiesMSFT contextProperties;
    contextProperties = XrRemotingRemoteContextPropertiesMSFT{ static_cast<XrStructureType>(
      XR_TYPE_REMOTING_REMOTE_CONTEXT_PROPERTIES_MSFT) };
    contextProperties.enableAudio = false;
    contextProperties.maxBitrateKbps = 20000;
    contextProperties.videoCodec = XR_REMOTING_VIDEO_CODEC_ANY_MSFT;
    contextProperties.depthBufferStreamResolution =
      XR_REMOTING_DEPTH_BUFFER_STREAM_RESOLUTION_HALF_MSFT;

    extensions.xrRemotingSetContextPropertiesMSFT(
      manager.GetXrRuntimeInstance(), manager.GetSystemID(), &contextProperties);
  }

  XrRemotingConnectInfoMSFT connectInfo{ static_cast<XrStructureType>(
    XR_TYPE_REMOTING_CONNECT_INFO_MSFT) };
  connectInfo.remoteHostName = this->IPAddress.c_str();
  connectInfo.remotePort = 8265;
  connectInfo.secureConnection = false;

  if (!vtk::detail::vtkOpenXRManager::GetInstance().XrCheckOutput(
        vtk::detail::vtkOpenXRManager::ErrorOutput,
        extensions.xrRemotingConnectMSFT(
          manager.GetXrRuntimeInstance(), manager.GetSystemID(), &connectInfo),
        "Failed to connect"))
  {
    return false;
  }

  // Make sure the connection is established before the event loop gets started
  std::this_thread::sleep_for(std::chrono::milliseconds(2500));

  return true;
}

//------------------------------------------------------------------------------
const char* vtkOpenXRManagerRemoteConnection::GetExtensionName()
{
  return XR_MSFT_HOLOGRAPHIC_REMOTING_EXTENSION_NAME;
}

VTK_ABI_NAMESPACE_END
