// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause
#include "pqVRConnectionManager.h"

// --------------------------------------------------------------------includes
#include "pqApplicationCore.h"
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
#include "pqVRPNConnection.h"
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
#include "pqVRUIConnection.h"
#endif

#include "vtkObjectFactory.h"
#include "vtkPVVRConfig.h"
#include "vtkPVXMLElement.h"
#include "vtkVRQueue.h"
#include "vtkWeakPointer.h"

#include <QList>
#include <QtDebug>

#include <algorithm>
#include <cassert>

// ----------------------------------------------------------------------------
QPointer<pqVRConnectionManager> pqVRConnectionManager::Instance;
void pqVRConnectionManager::setInstance(pqVRConnectionManager* mgr)
{
  pqVRConnectionManager::Instance = mgr;
}

// ----------------------------------------------------------------------------
pqVRConnectionManager* pqVRConnectionManager::instance()
{
  return pqVRConnectionManager::Instance;
}

// --------------------------------------------------------------------internal
// IMPORTANT: Make sure that this struct has no pointers.  All pointers should
// be put in the class declaration. For all newly defined pointers make sure to
// update constructor and destructor methods.
class pqVRConnectionManager::pqInternals
{
public:
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  QList<QPointer<pqVRPNConnection>> VRPNConnections;
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  QList<QPointer<pqVRUIConnection>> VRUIConnections;
#endif
  vtkWeakPointer<vtkVRQueue> Queue;
};

// -----------------------------------------------------------------------cnstr
pqVRConnectionManager::pqVRConnectionManager(vtkVRQueue* queue, QObject* parentObject)
  : Superclass(parentObject)
{
  this->Internals = new pqInternals();
  this->Internals->Queue = queue;
  QObject::connect(pqApplicationCore::instance(),
    SIGNAL(stateLoaded(vtkPVXMLElement*, vtkSMProxyLocator*)), this,
    SLOT(configureConnections(vtkPVXMLElement*, vtkSMProxyLocator*)));
  QObject::connect(pqApplicationCore::instance(), SIGNAL(stateSaved(vtkPVXMLElement*)), this,
    SLOT(saveConnectionsConfiguration(vtkPVXMLElement*)));
}

// -----------------------------------------------------------------------destr
pqVRConnectionManager::~pqVRConnectionManager()
{
  delete this->Internals;
}

#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
// ----------------------------------------------------------------------------
void pqVRConnectionManager::add(pqVRPNConnection* conn)
{
  this->Internals->VRPNConnections.push_front(conn);
  conn->setQueue(this->Internals->Queue);
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::remove(pqVRPNConnection* conn)
{
  conn->stop();
  this->Internals->VRPNConnections.removeAll(conn);
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
pqVRPNConnection* pqVRConnectionManager::GetVRPNConnection(const QString& name)
{
  std::string target = name.toStdString();
  Q_FOREACH (const QPointer<pqVRPNConnection>& conn, this->Internals->VRPNConnections)
  {
    if (!conn.isNull())
    {
      if (conn->name() == target)
      {
        return conn.data();
      }
    }
  }
  return nullptr;
}
#endif

#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
// ----------------------------------------------------------------------------
void pqVRConnectionManager::add(pqVRUIConnection* conn)
{
  this->Internals->VRUIConnections.push_front(conn);
  conn->setQueue(this->Internals->Queue);
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::remove(pqVRUIConnection* conn)
{
  conn->stop();
  this->Internals->VRUIConnections.removeAll(conn);
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
pqVRUIConnection* pqVRConnectionManager::GetVRUIConnection(const QString& name)
{
  std::string target = name.toStdString();
  Q_FOREACH (const QPointer<pqVRUIConnection>& conn, this->Internals->VRUIConnections)
  {
    if (!conn.isNull())
    {
      if (conn->name() == target)
      {
        return conn.data();
      }
    }
  }
  return nullptr;
}
#endif

// ----------------------------------------------------------------------------
void pqVRConnectionManager::clear()
{
  this->stop();
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  this->Internals->VRPNConnections.clear();
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  this->Internals->VRUIConnections.clear();
#endif
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
QList<QString> pqVRConnectionManager::connectionNames() const
{
  QList<QString> result;
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  Q_FOREACH (pqVRPNConnection* conn, this->Internals->VRPNConnections)
  {
    if (conn)
    {
      result << QString::fromStdString(conn->name());
    }
  }
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  Q_FOREACH (pqVRUIConnection* conn, this->Internals->VRUIConnections)
  {
    if (conn)
    {
      result << QString::fromStdString(conn->name());
    }
  }
#endif
  std::sort(result.begin(), result.end());
  return result;
}

// ----------------------------------------------------------------------------
int pqVRConnectionManager::numConnections() const
{
  int result = 0;
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  Q_FOREACH (pqVRPNConnection* conn, this->Internals->VRPNConnections)
  {
    if (conn)
    {
      result++;
    }
  }
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  Q_FOREACH (pqVRUIConnection* conn, this->Internals->VRUIConnections)
  {
    if (conn)
    {
      result++;
    }
  }
#endif
  return result;
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::start()
{
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  Q_FOREACH (pqVRPNConnection* conn, this->Internals->VRPNConnections)
  {
    if (conn && conn->init())
    {
      conn->start();
    }
  }
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  Q_FOREACH (pqVRUIConnection* conn, this->Internals->VRUIConnections)
  {
    if (conn && conn->init())
    {
      conn->start();
    }
  }
#endif
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::stop()
{
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  Q_FOREACH (pqVRPNConnection* conn, this->Internals->VRPNConnections)
  {
    if (conn)
    {
      conn->stop();
    }
  }
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  Q_FOREACH (pqVRUIConnection* conn, this->Internals->VRUIConnections)
  {
    if (conn)
    {
      conn->stop();
    }
  }
#endif
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::configureConnections(vtkPVXMLElement* xml, vtkSMProxyLocator* locator)
{
  if (!xml)
  {
    return;
  }

  if (xml->GetName() && strcmp(xml->GetName(), "VRConnectionManager") == 0)
  {
    this->clear();
    for (unsigned cc = 0; cc < xml->GetNumberOfNestedElements(); cc++)
    {
      vtkPVXMLElement* child = xml->GetNestedElement(cc);
      if (child && child->GetName())
      {
        if (strcmp(child->GetName(), "VRPNConnection") == 0)
        {
          const char* name = child->GetAttributeOrEmpty("name");
          const char* address = child->GetAttributeOrEmpty("address");
          (void)name;
          (void)address;
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
          // TODO: Need to throw some warning if VRPN is used when not
          // compiled. For now we will simply ignore VRPN configuration
          pqVRPNConnection* device = new pqVRPNConnection(this);
          device->setName(name);
          device->setAddress(address);
          device->configure(child, locator);
          this->add(device);
#endif
        }
        else if (strcmp(child->GetName(), "VRUIConnection") == 0)
        {
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
          // TODO: Need to throw some warning if VRUI is used when not
          // compiled. For now we will simply ignore VRUI configuration
          const char* name = child->GetAttributeOrEmpty("name");
          const char* address = child->GetAttributeOrEmpty("address");
          const char* port = child->GetAttribute("port");
          pqVRUIConnection* device = new pqVRUIConnection(this);
          device->setName(name);
          device->setAddress(address);
          device->setPort(port ? port : "8555");
          device->configure(child, locator);
          this->add(device);
#endif
        }
        else
        {
          qWarning() << "Unknown Connection type : \"" << child->GetName() << "\"";
        }
      }
    }
  }
  else
  {
    this->configureConnections(xml->FindNestedElementByName("VRConnectionManager"), locator);
  }
  Q_EMIT this->connectionsChanged();
}

// ----------------------------------------------------------------------------
void pqVRConnectionManager::saveConnectionsConfiguration(vtkPVXMLElement* root)
{
  assert(root != nullptr);
  vtkPVXMLElement* tempParent = vtkPVXMLElement::New();
  tempParent->SetName("VRConnectionManager");
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRPN
  Q_FOREACH (pqVRPNConnection* conn, this->Internals->VRPNConnections)
  {
    vtkPVXMLElement* child = conn->saveConfiguration();
    if (child)
    {
      tempParent->AddNestedElement(child);
      child->Delete();
    }
  }
#endif
#if PARAVIEW_PLUGIN_CAVEInteraction_USE_VRUI
  Q_FOREACH (pqVRUIConnection* conn, this->Internals->VRUIConnections)
  {
    vtkPVXMLElement* child = conn->saveConfiguration();
    if (child)
    {
      tempParent->AddNestedElement(child);
      child->Delete();
    }
  }
#endif
  root->AddNestedElement(tempParent);
  tempParent->Delete();
}
