/*=========================================================================

   Program: ParaView
   Module:    pqServerManagerModel.cxx

   Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
   All rights reserved.

   ParaView is a free software; you can redistribute it and/or modify it
   under the terms of the ParaView license version 1.2.

   See License_v1.2.txt for the full ParaView license.
   A copy of this license can be obtained by contacting
   Kitware Inc.
   28 Corporate Drive
   Clifton Park, NY 12065
   USA

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

========================================================================*/
#include "pqServerManagerModel.h"

#include "pqExtractor.h"
#include "pqInterfaceTracker.h"
#include "pqOutputPort.h"
#include "pqPipelineSource.h"
#include "pqProxy.h"
#include "pqRepresentation.h"
#include "pqServer.h"
#include "pqServerManagerModelInterface.h"
#include "pqServerManagerObserver.h"
#include "pqServerResource.h"
#include "pqSettings.h"
#include "pqView.h"
#include "vtkClientSession.h"
#include "vtkNew.h"
#include "vtkPVGUIApplication.h"
#include "vtkProxyDefinitionManager.h"
#include "vtkSMDoubleVectorProperty.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMOutputPort.h"
#include "vtkSMParaViewPipelineController.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMPropertyIterator.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMStringVectorProperty.h"
#include "vtkSmartPointer.h"
#include "vtkStringList.h"

// Qt Includes.
#include <QList>
#include <QMap>
#include <QPointer>
#include <QString>
#include <QStringList>
#include <QtDebug>

//-----------------------------------------------------------------------------
class pqServerManagerModel::pqInternal
{
public:
  typedef QMap<vtkTypeUInt32, QPointer<pqServer>> ServerMap;
  ServerMap Servers;

  typedef QMap<vtkSMProxy*, QPointer<pqProxy>> ProxyMap;
  ProxyMap Proxies;

  typedef QMap<vtkSMOutputPort*, QPointer<pqOutputPort>> OutputPortMap;
  OutputPortMap OutputPorts;

  QList<QPointer<pqServerManagerModelItem>> ItemList;

  pqServerResource ActiveResource;
};

//-----------------------------------------------------------------------------
pqServerManagerModel::pqServerManagerModel(
  pqServerManagerObserver* observer, QObject* _parent /*=0*/)
  : QObject(_parent)
{
  this->Internal = new pqServerManagerModel::pqInternal();
  QObject::connect(observer, SIGNAL(proxyRegistered(const QString&, const QString&, vtkSMProxy*)),
    this, SLOT(onProxyRegistered(const QString&, const QString&, vtkSMProxy*)));
  QObject::connect(observer, SIGNAL(proxyUnRegistered(const QString&, const QString&, vtkSMProxy*)),
    this, SLOT(onProxyUnRegistered(const QString&, const QString&, vtkSMProxy*)));

  QObject::connect(observer, SIGNAL(connectionCreated(vtkTypeUInt32)), this,
    SLOT(onConnectionCreated(vtkTypeUInt32)));
  QObject::connect(observer, SIGNAL(connectionClosed(vtkTypeUInt32)), this,
    SLOT(onConnectionClosed(vtkTypeUInt32)));
  QObject::connect(observer, SIGNAL(stateLoaded(vtkPVXMLElement*, vtkSMProxyLocator*)), this,
    SLOT(onStateLoaded(vtkPVXMLElement*, vtkSMProxyLocator*)));
}

//-----------------------------------------------------------------------------
pqServerManagerModel::~pqServerManagerModel()
{
  delete this->Internal;
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::setActiveResource(const pqServerResource& resource)
{
  this->Internal->ActiveResource = resource;
}

//-----------------------------------------------------------------------------
pqServer* pqServerManagerModel::findServer(vtkClientSession* session) const
{
  auto* pvapp = vtkPVApplication::GetInstance();
  return this->findServer(pvapp->GetSessionID(session));
}

//-----------------------------------------------------------------------------
pqServer* pqServerManagerModel::findServer(vtkTypeUInt32 cid) const
{
  pqInternal::ServerMap::iterator iter = this->Internal->Servers.find(cid);
  if (iter != this->Internal->Servers.end())
  {
    return iter.value();
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
pqServer* pqServerManagerModel::findServer(const pqServerResource& resource) const
{
  Q_FOREACH (pqServer* server, this->Internal->Servers)
  {
    if (server && server->resource() == resource)
    {
      return server;
    }
  }
  return nullptr;
}

//-----------------------------------------------------------------------------
pqServerManagerModelItem* pqServerManagerModel::findItemHelper(
  const pqServerManagerModel* const model, const QMetaObject& vtkNotUsed(mo), vtkSMProxy* proxy)
{
  pqInternal::ProxyMap::iterator iter = model->Internal->Proxies.find(proxy);
  if (iter != model->Internal->Proxies.end())
  {
    return iter.value();
  }

  vtkSMOutputPort* port = vtkSMOutputPort::SafeDownCast(proxy);
  if (port)
  {
    pqPipelineSource* src = model->findItem<pqPipelineSource*>(port->GetSourceProxy());
    if (src)
    {
      for (int cc = 0; cc < src->getNumberOfOutputPorts(); cc++)
      {
        pqOutputPort* pqport = src->getOutputPort(cc);
        if (pqport && pqport->getOutputPortProxy() == port)
        {
          return pqport;
        }
      }
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
pqServerManagerModelItem* pqServerManagerModel::findItemHelper(
  const pqServerManagerModel* const model, const QMetaObject& mo, vtkTypeUInt32 id)
{
  Q_FOREACH (pqServerManagerModelItem* item, model->Internal->ItemList)
  {
    if (item && mo.cast(item))
    {
      pqProxy* proxy = qobject_cast<pqProxy*>(item);
      if (proxy && proxy->getProxy()->GetGlobalID() == id)
      {
        return proxy;
      }
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
pqServerManagerModelItem* pqServerManagerModel::findItemHelper(
  const pqServerManagerModel* const model, const QMetaObject& mo, const QString& name)
{
  Q_FOREACH (pqServerManagerModelItem* item, model->Internal->ItemList)
  {
    if (item && mo.cast(item))
    {
      pqProxy* proxy = qobject_cast<pqProxy*>(item);
      if (proxy && proxy->registrationName() == name)
      {
        return proxy;
      }
    }
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::findItemsHelper(const pqServerManagerModel* const model,
  const QMetaObject& mo, QList<void*>* list, pqServer* server /*=0*/)
{
  if (!model || !list)
  {
    return;
  }

  Q_FOREACH (pqServerManagerModelItem* item, model->Internal->ItemList)
  {
    if (item && mo.cast(item))
    {
      if (server)
      {
        pqProxy* pitem = qobject_cast<pqProxy*>(item);
        if (pitem && pitem->getServer() != server)
        {
          continue;
        }
      }
      list->push_back(item);
    }
  }
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::onProxyRegistered(
  const QString& group, const QString& name, vtkSMProxy* proxy)
{
  if (group.endsWith("_prototypes") || proxy->GetSession() == nullptr)
  {
    // Ignore prototype proxies.
    return;
  }
  if (name == "StreamingOptionsInstance" || name == "AdaptiveOptionsInstance")
  {
    // Ignore this particular proxy
    return;
  }
  if (!proxy)
  {
    qCritical() << "Null proxy cannot be registered.";
    return;
  }

  pqServer* server = this->findServer(proxy->GetSession());

  // Warn and return if the server can't be found. This indicates some logic error in
  // the application.
  if (!server)
  {
    qDebug() << "Failed to locate server for newly registered proxy (" << group << ", " << name
             << ")";
    return;
  }

  if (this->findItem<pqProxy*>(proxy))
  {
    // item already exists for this proxy, don't create a new one.
    return;
  }

  pqProxy* item = nullptr;

  QObjectList ifaces = vtkPVGUIApplication::GetInstance()->GetInterfaceTracker()->interfaces();
  Q_FOREACH (QObject* iface, ifaces)
  {
    pqServerManagerModelInterface* smi = qobject_cast<pqServerManagerModelInterface*>(iface);
    if (smi)
    {
      item = smi->createPQProxy(group, name, proxy, server);
      if (item)
      {
        break;
      }
    }
  }

  if (!item)
  {
    return;
  }

  // Set the QObject parent, simplifying clean up of objects.
  item->setParent(this);

  Q_EMIT this->preItemAdded(item);
  Q_EMIT this->preProxyAdded(item);

  pqView* view = qobject_cast<pqView*>(item);
  pqPipelineSource* source = qobject_cast<pqPipelineSource*>(item);
  pqRepresentation* repr = qobject_cast<pqRepresentation*>(item);
  auto extrator = qobject_cast<pqExtractor*>(item);

  if (view)
  {
    Q_EMIT this->preViewAdded(view);
  }
  else if (source)
  {
    QObject::connect(source, SIGNAL(connectionAdded(pqPipelineSource*, pqPipelineSource*, int)),
      this, SIGNAL(connectionAdded(pqPipelineSource*, pqPipelineSource*, int)));
    QObject::connect(source, SIGNAL(connectionRemoved(pqPipelineSource*, pqPipelineSource*, int)),
      this, SIGNAL(connectionRemoved(pqPipelineSource*, pqPipelineSource*, int)));
    QObject::connect(source, SIGNAL(preConnectionAdded(pqPipelineSource*, pqPipelineSource*, int)),
      this, SIGNAL(preConnectionAdded(pqPipelineSource*, pqPipelineSource*, int)));
    QObject::connect(source,
      SIGNAL(preConnectionRemoved(pqPipelineSource*, pqPipelineSource*, int)), this,
      SIGNAL(preConnectionRemoved(pqPipelineSource*, pqPipelineSource*, int)));
    QObject::connect(source, SIGNAL(nameChanged(pqServerManagerModelItem*)), this,
      SIGNAL(nameChanged(pqServerManagerModelItem*)));
    QObject::connect(source, SIGNAL(modifiedStateChanged(pqServerManagerModelItem*)), this,
      SIGNAL(modifiedStateChanged(pqServerManagerModelItem*)));
    QObject::connect(
      source, SIGNAL(dataUpdated(pqPipelineSource*)), this, SIGNAL(dataUpdated(pqPipelineSource*)));

    QObject::connect(source, SIGNAL(annotationsChanged(pqServerManagerModelItem*)), this,
      SIGNAL(annotationsChanged(pqServerManagerModelItem*)));

    Q_EMIT this->preSourceAdded(source);
  }
  else if (repr)
  {
    Q_EMIT this->preRepresentationAdded(repr);
  }
  else if (extrator)
  {
    this->connect(extrator, SIGNAL(producerAdded(pqServerManagerModelItem*, pqExtractor*)),
      SIGNAL(connectionAdded(pqServerManagerModelItem*, pqExtractor*)));
    this->connect(extrator, SIGNAL(producerRemoved(pqServerManagerModelItem*, pqExtractor*)),
      SIGNAL(connectionRemoved(pqServerManagerModelItem*, pqExtractor*)));
    Q_EMIT this->preExtractorAdded(extrator);
  }

  this->Internal->Proxies[proxy] = item;
  this->Internal->ItemList.push_back(item);

  Q_EMIT this->itemAdded(item);
  Q_EMIT this->proxyAdded(item);

  if (view)
  {
    // This ensures that the pqQVTKWidgetBase (or any other rendering
    // widget) for the view is created thus avoiding the window from ever
    // popping up and causing issues as as seen in BUG #13855.
    view->widget();
    Q_EMIT this->viewAdded(view);
  }
  else if (source)
  {
    Q_EMIT this->sourceAdded(source);
  }
  else if (repr)
  {
    Q_EMIT this->representationAdded(repr);
  }
  else if (extrator)
  {
    Q_EMIT this->extractorAdded(extrator);
  }

  // It is essential to let the world know of the addition of pqProxy
  // before we start emitting signals as we update the initial state
  // of the pqProxy from its underlying proxy. Hence we Q_EMIT this->proxyAdded()
  // before we do a pqSource->initialize();
  item->initialize();

  // FIXME: I think item->initialize() must happen before these signals are
  // fired.
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::onProxyUnRegistered(
  const QString& group, const QString& name, vtkSMProxy* proxy)
{
  // Handle proxy renaming.
  pqProxy* item = this->findItem<pqProxy*>(proxy);
  if (!item || item->registrationName() != name || item->registrationGroup() != group)
  {
    // Proxy is unknown or being registered from a group/name for which we
    // didn't create any pq object.
    return;
  }

  // Verify if the proxy is registered under a different name in the same group.
  // If so, we are simply renaming the proxy.
  vtkSmartPointer<vtkStringList> names = vtkSmartPointer<vtkStringList>::New();
  vtkSMSessionProxyManager* pxm = proxy->GetSessionProxyManager();
  pxm->GetProxyNames(group.toUtf8().data(), proxy, names);
  for (int cc = 0; cc < names->GetLength(); cc++)
  {
    if (name == names->GetString(cc))
    {
      continue;
    }
    // Change the name of the pqsource.
    item->setRegistrationName(names->GetString(cc));
    return;
  }

  pqView* view = qobject_cast<pqView*>(item);
  pqPipelineSource* source = qobject_cast<pqPipelineSource*>(item);
  pqRepresentation* repr = qobject_cast<pqRepresentation*>(item);
  auto extrator = qobject_cast<pqExtractor*>(item);

  if (view)
  {
    Q_EMIT this->preViewRemoved(view);
  }
  else if (source)
  {
    Q_EMIT this->preSourceRemoved(source);
  }
  else if (repr)
  {
    Q_EMIT this->preRepresentationRemoved(repr);
  }
  else if (extrator)
  {
    Q_EMIT this->preExtractorRemoved(extrator);
  }

  Q_EMIT this->preProxyRemoved(item);
  Q_EMIT this->preItemRemoved(item);

  QObject::disconnect(item, nullptr, this, nullptr);
  this->Internal->ItemList.removeAll(item);
  this->Internal->Proxies.remove(item->getProxy());

  if (view)
  {
    view->cancelPendingRenders();
    Q_EMIT this->viewRemoved(view);
  }
  else if (source)
  {
    Q_EMIT this->sourceRemoved(source);
  }
  else if (repr)
  {
    Q_EMIT this->representationRemoved(repr);
  }
  else if (extrator)
  {
    Q_EMIT this->extractorRemoved(extrator);
  }

  Q_EMIT this->proxyRemoved(item);
  Q_EMIT this->itemRemoved(item);
  delete item;
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::onConnectionCreated(vtkTypeUInt32 id)
{
  // Avoid duplicate server creations.
  if (this->findServer(id))
  {
    return; // server already exists.
  }

  auto* pvapp = vtkPVApplication::GetInstance();
  pqServer* server = new pqServer(pvapp->GetSession(id), this);

  // Make sure the server resource is valid otherwise use session URL information
  // (this is used when we connect from Python in multi-server mode)
  if (this->Internal->ActiveResource.scheme().isEmpty())
  {
    auto* session = pvapp->GetSession(id);
    const auto& url = session->GetURL();
    server->setResource(pqServerResource(url.empty() ? "builtin:" : url.c_str()));
  }
  else
  {
    server->setResource(this->Internal->ActiveResource);
  }

  this->Internal->ActiveResource = pqServerResource();

  Q_EMIT this->preItemAdded(server);
  Q_EMIT this->preServerAdded(server);

  this->Internal->Servers[id] = server;
  this->Internal->ItemList.push_back(server);

  // Lets the world know when the server name changes.
  this->connect(server, SIGNAL(nameChanged(pqServerManagerModelItem*)), this,
    SIGNAL(nameChanged(pqServerManagerModelItem*)));

  // The session must be initialized here as ParaView expects that the essential
  // proxies are created when following signals are fired. Also, registering
  // those proxies before pqServer is setup can result in those pqProxy classes
  // not being setup properly. Hence the need for doing this initialization
  // here.
  vtkNew<vtkSMParaViewPipelineController> controller;
  controller->InitializeSession(server->session());

  Q_EMIT this->serverReady(server);
  Q_EMIT this->itemAdded(server);
  Q_EMIT this->serverAdded(server);

  this->updateSettingsFromQSettings(server);
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::updateSettingsFromQSettings(pqServer* server)
{
  if (!server)
  {
    return;
  }

  vtkSMSessionProxyManager* pxm = server->proxyManager();
  if (!pxm)
  {
    return;
  }

  auto* pdm = pxm->GetProxyDefinitionManager();
  auto* pvapp = vtkPVGUIApplication::GetInstance();
  pqSettings* settings = pvapp->GetSettings();

  for (auto item : pdm->GetDefinitions("settings"))
  {
    vtkSMProxy* proxy = pxm->GetProxy(item.GetGroup().c_str(), item.GetName().c_str());
    if (!proxy)
    {
      continue;
    }

    vtkSmartPointer<vtkSMPropertyIterator> propIter;
    propIter.TakeReference(proxy->NewPropertyIterator());
    for (propIter->Begin(); !propIter->IsAtEnd(); propIter->Next())
    {
      vtkSMProperty* prop = propIter->GetProperty();
      const char* propName = propIter->GetKey();
      QString qsettingsName(proxy->GetXMLName());
      qsettingsName.append(".");
      qsettingsName.append(propName);

      if (!settings->contains(qsettingsName))
      {
        continue;
      }

      QVariant value = settings->value(qsettingsName);
      if (!value.isValid())
      {
        continue;
      }

      if (vtkSMDoubleVectorProperty* dvp = vtkSMDoubleVectorProperty::SafeDownCast(prop))
      {
        dvp->SetElement(0, value.toDouble());
      }
      else if (vtkSMIntVectorProperty* ivp = vtkSMIntVectorProperty::SafeDownCast(prop))
      {
        ivp->SetElement(0, value.toInt());
      }
      else if (vtkSMStringVectorProperty* svp = vtkSMStringVectorProperty::SafeDownCast(prop))
      {
        svp->SetElement(0, value.toString().toUtf8().data());
      }
      else
      {
        qWarning() << "Could not set settings proxy property for property '" << propName
                   << "' of type " << prop->GetClassName();
      }
    }

    proxy->UpdateVTKObjects();
  }
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::onConnectionClosed(vtkTypeUInt32 id)
{
  pqServer* server = this->findServer(id);
  if (!server)
  {
    qDebug() << "Unknown connection closed, simply ignoring it.";
    return;
  }

  Q_EMIT this->preServerRemoved(server);
  Q_EMIT this->preItemRemoved(server);

  this->Internal->Servers.remove(server->sessionID());
  this->Internal->ItemList.removeAll(server);

  Q_EMIT this->serverRemoved(server);
  Q_EMIT this->itemRemoved(server);
  delete server;
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::onStateLoaded(vtkPVXMLElement* root, vtkSMProxyLocator* locator)
{
  Q_UNUSED(root);
  Q_UNUSED(locator);
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::beginRemoveServer(pqServer* server)
{
  Q_EMIT this->aboutToRemoveServer(server);
}

//-----------------------------------------------------------------------------
void pqServerManagerModel::endRemoveServer()
{
  Q_EMIT this->finishedRemovingServer();
}
