//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "pqACE3PRemoteParaViewBehavior.h"

#include "plugin/modelbuilder_cori_pvsc.h"
#include "smtk/simulation/ace3p/qt/qtMessageDialog.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqServer.h"
#include "pqServerConfiguration.h"
#include "pqServerConnectReaction.h"
#include "pqServerManagerModel.h"
#include "vtkPVXMLElement.h"
#include "vtkPVXMLParser.h"
#include "vtkProcessModule.h"

#include "vtkNew.h"

#include <QAction>
#include <QDebug>
#include <QMenu>
#include <QString>
#include <QStringList>

//=============================================================================
pqACE3PRemoteParaViewMenu::pqACE3PRemoteParaViewMenu(QMenu* menu, QObject* p)
  : QObject(p)
  , m_menu(menu)
{
  if (menu)
  {
    this->buildMenu();
    QObject::connect(m_menu, &QMenu::triggered, this, &pqACE3PRemoteParaViewMenu::onTriggered);
  }
}

//-----------------------------------------------------------------------------
void pqACE3PRemoteParaViewMenu::buildMenu()
{
  if (!m_menu)
  {
    return;
  }

  m_menu->clear();

  // Add Cori
  QAction* const action = new QAction("Cori", m_menu);
  action->setData("cori");
  m_menu->addAction(action);

  // Future: Add Perlmutter
}

//-----------------------------------------------------------------------------
void pqACE3PRemoteParaViewMenu::onTriggered(QAction* action)
{
  QString machine = action->data().toString();
  pqACE3PRemoteParaViewBehavior::instance()->startRemoteServer(machine);
}

//=============================================================================
static pqACE3PRemoteParaViewBehavior* g_instance = nullptr;

//-----------------------------------------------------------------------------
pqACE3PRemoteParaViewBehavior::pqACE3PRemoteParaViewBehavior(QObject* parent)
  : Superclass(parent)
{
  // Listen for remote server added signals (for debug only)
  pqServerManagerModel* model = pqApplicationCore::instance()->getServerManagerModel();

  // Listen for remote server disconnect signals and update m_serverMap
  QObject::connect(model, &pqServerManagerModel::serverRemoved, [this](pqServer* server) {
    QString name = server->getResource().configuration().name();
    qDebug() << "Server Removed:" << name << server->isRemote();
    for (auto it = m_serverMap.begin(); it != m_serverMap.end(); ++it)
    {
      if (it.value()->getResource().configuration().name() == name)
      {
        qDebug() << "Server removed. Updating m_serverMap";
        m_serverMap.erase(it);
        break;
      }
    }
  });
}

//-----------------------------------------------------------------------------
pqACE3PRemoteParaViewBehavior* pqACE3PRemoteParaViewBehavior::instance(QObject* parent)
{
  if (!g_instance)
  {
    g_instance = new pqACE3PRemoteParaViewBehavior(parent);
  }

  if (g_instance->parent() == nullptr && parent)
  {
    g_instance->setParent(parent);
  }

  return g_instance;
}

//-----------------------------------------------------------------------------
pqACE3PRemoteParaViewBehavior::~pqACE3PRemoteParaViewBehavior()
{
  if (g_instance == this)
  {
    g_instance = nullptr;
  }

  QObject::disconnect(this);
}

//-----------------------------------------------------------------------------
pqServer* pqACE3PRemoteParaViewBehavior::remoteServer(const QString& machine) const
{
  auto iter = m_serverMap.find(machine);
  if (iter == m_serverMap.end())
  {
    return nullptr;
  }
  return *iter;
}

//-----------------------------------------------------------------------------
bool pqACE3PRemoteParaViewBehavior::startRemoteServer(const QString& machine)
{
  // Check for --multi-servers
  if (!vtkProcessModule::GetProcessModule()->GetMultipleSessionsSupport())
  {
    auto* mainWindow = pqCoreUtilities::mainWidget();
    QString msg = "Modelbuilder cannot launch remote paraview servers because it was not"
                  " started with the --multi-servers command line option.";
    QMessageBox::critical(mainWindow, "Missing --multi-servers option", msg);
    return false;
  }

  pqServerManagerModel* model = pqApplicationCore::instance()->getServerManagerModel();
  QObject::connect(model, &pqServerManagerModel::serverAdded, [&](pqServer* server) {
    if (server->isRemote())
    {
      qDebug() << "Server added:" << machine << "Updating m_serverMap";
      m_serverMap[machine] = server;
      pqActiveObjects::instance().setActiveServer(server);

      // Wait for active server events to finish, then emit our signal
      pqCoreUtilities::processEvents();
      emit this->serverAdded(server);

      // And disconnect until next time
      QObject::disconnect(model, &pqServerManagerModel::serverAdded, this, nullptr);

      // these connections notify user when the remote server is about to time out
      QObject::connect(
        server,
        &pqServer::fiveMinuteTimeoutWarning,
        this,
        &pqACE3PRemoteParaViewBehavior::serverTimeoutIn5Mins);
      QObject::connect(
        server,
        &pqServer::finalTimeoutWarning,
        this,
        &pqACE3PRemoteParaViewBehavior::serverTimeoutIn1Min);
      QObject::connect(
        server,
        &pqServer::serverSideDisconnected,
        this,
        &pqACE3PRemoteParaViewBehavior::serverTimeout);
    }
  });

  // Create server configuration and connect
  vtkNew<vtkPVXMLParser> parser;
  int result = parser->Parse(cori_pvsc);
  vtkPVXMLElement* root = parser->GetRootElement();
  pqServerConfiguration config(root);

  pqServerConnectReaction::connectToServerUsingConfiguration(config);
  return true;
}

// warn user when the server connection is about to expire
void pqACE3PRemoteParaViewBehavior::serverTimeoutIn5Mins()
{
  qtMessageDialog dialog(pqCoreUtilities::mainWidget());
  dialog.setIcon(QMessageBox::Warning);
  dialog.setWindowTitle("Remote Server Warning");
  dialog.setText("The remote server connection will timeout in 5 mimutes.");
  dialog.setAutoClose(true);
  dialog.setAutoCloseDelay(15);
  dialog.show();
}

// warn user when the server connection is about to expire
void pqACE3PRemoteParaViewBehavior::serverTimeoutIn1Min()
{
  qtMessageDialog dialog(pqCoreUtilities::mainWidget());
  dialog.setIcon(QMessageBox::Warning);
  dialog.setWindowTitle("Remote Server Warning");
  dialog.setText("The remote server connection will timeout in 1 mimute.");
  dialog.setAutoClose(true);
  dialog.setAutoCloseDelay(15);
  dialog.show();
}

// notify user when the server connection has expired
void pqACE3PRemoteParaViewBehavior::serverTimeout()
{
  qtMessageDialog dialog(pqCoreUtilities::mainWidget());
  dialog.setIcon(QMessageBox::Critical);
  dialog.setWindowTitle("Remote Server Disconnected");
  dialog.setText("The remote server connection has timed out.");
  dialog.show();
}
