//=========================================================================
//  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.
//=========================================================================

// local includes
#include "plugin/pqWindTunnelAutoStart.h"
#include "plugin/pqWindTunnelContainerEngineSetup.h"

#include "plugin/pqWindTunnelCloseProjectBehavior.h"
#include "plugin/pqWindTunnelControlsView.h"
#include "smtk/simulation/windtunnel/Metadata.h"
#include "smtk/simulation/windtunnel/Registrar.h"
#include "smtk/simulation/windtunnel/qt/qtOpenFoamRunner.h"
#include "smtk/simulation/windtunnel/qt/qtSessionData.h"

// SMTK includes
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DirectoryItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKResourcePanel.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#include "smtk/extension/paraview/server/vtkSMTKSettings.h"
#include "smtk/operation/Manager.h"
#include "smtk/operation/Operation.h"
#include "smtk/project/Project.h"
#include "smtk/view/Manager.h"
#include "smtk/view/ResourcePhraseModel.h"
#include "smtk/view/ViewWidgetFactory.h"

// ParaView includes
#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqAutoApplyReaction.h"
#include "pqCoreConfiguration.h"
#include "pqCoreUtilities.h"
#include "pqMainWindowEventManager.h"
#include "pqServer.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMProperty.h"
#include "vtkSMProxy.h"
#include "vtkSMSessionProxyManager.h"

#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
#include <QDir>
#include <QList>
#include <QMainWindow>
#include <QTimer>
#include <QWidget>

namespace
{
// Configure resource panel to omit displaying projects
// Code is copied from smtk::project::AutoStart
void setView()
{
  for (QWidget* w : QApplication::topLevelWidgets())
  {
    QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(w);
    if (mainWindow)
    {
      pqSMTKResourcePanel* dock = mainWindow->findChild<pqSMTKResourcePanel*>();
      // If the dock is not there, just try it again.
      if (dock)
      {
        auto phraseModel = std::dynamic_pointer_cast<smtk::view::ResourcePhraseModel>(
          dock->resourceBrowser()->phraseModel());
        if (phraseModel)
        {
          phraseModel->setFilter([](const smtk::resource::Resource& resource) {
            return !resource.isOfType(smtk::common::typeName<smtk::project::Project>());
          });
        }
      }
      else
      {
        QTimer::singleShot(10, []() { setView(); });
      }
    }
  }
}

int retryCount = 0;
} // namespace

pqWindTunnelAutoStart::pqWindTunnelAutoStart(QObject* parent)
  : Superclass(parent)
{
}

void pqWindTunnelAutoStart::startup()
{
// Set workflows folder for developer builds
#ifdef WORKFLOWS_SOURCE_DIR
  QDir workflowsDir(WORKFLOWS_SOURCE_DIR);
  if (workflowsDir.exists())
  {
    smtk::simulation::windtunnel::Metadata::WORKFLOWS_DIRECTORY = WORKFLOWS_SOURCE_DIR;
#ifndef NDEBUG
    qDebug() << "Using Workflows directory" << WORKFLOWS_SOURCE_DIR;
#endif
  }
#endif

#ifdef OPERATIONS_SOURCE_DIR
  QDir operationsDir(OPERATIONS_SOURCE_DIR);
  if (operationsDir.exists())
  {
    smtk::simulation::windtunnel::Metadata::OPERATIONS_DIRECTORY = OPERATIONS_SOURCE_DIR;
#ifndef NDEBUG
    qDebug() << "Using Operations directory" << OPERATIONS_SOURCE_DIR;
#endif
  }
#endif

  // Check for current/active pqServer
  pqServer* server = pqActiveObjects::instance().activeServer();
  if (server != nullptr)
  {
    pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
    this->resourceManagerAdded(wrapper, server);
  }

  pqAutoApplyReaction::setAutoApply(true);

  // Connect to application close event
  QObject::connect(
    pqApplicationCore::instance()->getMainWindowEventManager(),
    &pqMainWindowEventManager::close,
    [](QCloseEvent* closeEvent) {
      auto runtime = smtk::simulation::windtunnel::qtSessionData::instance();
      if (!runtime)
      {
        closeEvent->accept();
        return;
      }

      bool notCanceled = true;

      auto project = runtime->project();
      if (project && !project->clean())
      {
        notCanceled = pqWindTunnelCloseProjectBehavior::instance()->closeProject();
      }

      closeEvent->setAccepted(notCanceled);
    });

  // Instantiate singletons
  smtk::simulation::windtunnel::qtSessionData::instance(this);
  auto* runner = smtk::simulation::windtunnel::qtOpenFoamRunner::instance(this);
  runner->useOpenFoamDocker(true);

  // Finish initialization after application is ready
  this->waitForApplication();
}

void pqWindTunnelAutoStart::shutdown() {}

void pqWindTunnelAutoStart::resourceManagerAdded(pqSMTKWrapper* wrapper, pqServer* server)
{
  if (!wrapper || !server)
  {
    return;
  }

  smtk::resource::ManagerPtr resManager = wrapper->smtkResourceManager();
  smtk::view::ManagerPtr viewManager = wrapper->smtkViewManager();
  smtk::operation::ManagerPtr opManager = wrapper->smtkOperationManager();
  smtk::project::ManagerPtr projManager = wrapper->smtkProjectManager();
  if (!resManager || !viewManager || !opManager || !projManager)
  {
    return;
  }

  // Register custom view (mini control panel)
  viewManager->viewWidgetFactory().registerType<pqWindTunnelControlsView>();
  viewManager->viewWidgetFactory().addAlias<pqWindTunnelControlsView>("WindTunnelControls");

  auto* sessionData = smtk::simulation::windtunnel::qtSessionData::instance();
  sessionData->setOperationManager(opManager);
  sessionData->setViewManager(viewManager);
}

void pqWindTunnelAutoStart::resourceManagerRemoved(pqSMTKWrapper* wrapper, pqServer* server)
{
  if (!wrapper || !server)
  {
    return;
  }

  smtk::view::ManagerPtr viewManager = wrapper->smtkViewManager();
  if (viewManager != nullptr)
  {
  }

  smtk::project::ManagerPtr projManager = wrapper->smtkProjectManager();
  if (projManager != nullptr)
  {
    smtk::simulation::windtunnel::Registrar::unregisterFrom(projManager);
  }
}

void pqWindTunnelAutoStart::waitForApplication()
{
  // Wait for main widget
  QWidget* mainWidget = pqCoreUtilities::mainWidget();
  if (mainWidget == nullptr)
  {
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::waitForApplication);
    return;
  }

  // Wait for plugin location
  std::string workflowsDir = smtk::simulation::windtunnel::Metadata::WORKFLOWS_DIRECTORY;
  std::string operationsDir = smtk::simulation::windtunnel::Metadata::OPERATIONS_DIRECTORY;
  if (workflowsDir.empty() || operationsDir.empty())
  {
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::waitForApplication);
    return;
  }

  // Wait for pqServer
  pqServer* server = pqActiveObjects::instance().activeServer();
  if (server == nullptr)
  {
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::waitForApplication);
    return;
  }

  // Wait for SMTK settings
  vtkSMProxy* smtkProxy = server->proxyManager()->GetProxy("settings", "SMTKSettings");
  if (smtkProxy == nullptr)
  {
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::waitForApplication);
    return;
  }

  // Wait for Wind Tunnel Settings
  vtkSMProxy* windTunnelProxy = server->proxyManager()->GetProxy("settings", "WindTunnelSettings");
  if (windTunnelProxy == nullptr)
  {
    qDebug() << "Waiting for WindTunnelSettings";
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::waitForApplication);
    return;
  }

  // Can now start plugin initialization
  this->initializeSettings(server);

  // Update singletons
  smtk::simulation::windtunnel::qtSessionData::instance()->setMainWidget(mainWidget);
  smtk::simulation::windtunnel::qtOpenFoamRunner::instance()->setParentWidget(mainWidget);

  // This is hack/workaround to enable package testing in cmb-superbuild.
  // If any test scripts are specified, skip the user-interface dialogs to configure
  // the container engine.
  auto* coreConfig = pqCoreConfiguration::instance();
  if (coreConfig == nullptr)
  {
    qWarning() << "pqCoreConfiguration instance is null";
  }
  else if (coreConfig->testScriptCount() > 0)
  {
    qInfo() << "Test script count is" << coreConfig->testScriptCount()
            << "==> skipping container engine setup.";

    m_containerSetup = new pqWindTunnelContainerEngineSetup(mainWidget);
    this->registerOperations();
    return;
  }

  qDebug() << "Start container engine setup";
  this->setupContainerEngine();
}

void pqWindTunnelAutoStart::setupContainerEngine()
{
  QWidget* mainWidget = smtk::simulation::windtunnel::qtSessionData::instance()->mainWidget();

  if (m_containerSetup == nullptr)
  {
    m_containerSetup = new pqWindTunnelContainerEngineSetup(mainWidget);
    QObject::connect(
      m_containerSetup,
      &pqWindTunnelContainerEngineSetup::finished,
      this,
      &pqWindTunnelAutoStart::registerOperations);
  }

  if (m_containerSetup->status() == pqWindTunnelContainerEngineSetup::Uninitialized)
  {
    m_containerSetup->start();
    QTimer::singleShot(103, this, &pqWindTunnelAutoStart::setupContainerEngine);
    return;
  }
  else if (m_containerSetup->status() == pqWindTunnelContainerEngineSetup::Pending)
  {
    // qDebug() << "Waiting for container engine status";
    QTimer::singleShot(103, this, &pqWindTunnelAutoStart::setupContainerEngine);
    return;
  }
  else if (m_containerSetup->status() == pqWindTunnelContainerEngineSetup::Error)
  {
    QString msg = "There was an error during container engine configuration."
                  " The application is not able to run OpenFOAM applications.";
    QMessageBox::critical(mainWidget, "Internal Error", msg);
    smtk::simulation::windtunnel::qtSessionData::instance()->setContainerEngine("");
  }
  // qDebug() << "Container Status" << m_containerSetup->statusAsString();
}

void pqWindTunnelAutoStart::registerOperations()
{
  pqServer* server = pqActiveObjects::instance().activeServer();
  auto smtkBehavior = pqSMTKBehavior::instance();
  auto wrapper = smtkBehavior->resourceManagerForServer();
  if (wrapper == nullptr)
  {
    QTimer::singleShot(11, this, &pqWindTunnelAutoStart::registerOperations);
  }
  this->resourceManagerAdded(wrapper, server);

  smtk::operation::ManagerPtr opManager = wrapper->smtkOperationManager();
  smtk::project::ManagerPtr projManager = wrapper->smtkProjectManager();

  // Register operations
  smtk::simulation::windtunnel::Registrar::registerTo(projManager);

  // Run SetupPaths operation
  std::string setupOpName = "setup_paths.SetupPaths";
  auto setupOp = opManager->create(setupOpName);
  if (setupOp == nullptr)
  {
    qCritical() << "Internal Error: Unable to create" << setupOpName.c_str() << "operation";
    return;
  }
  setupOp->parameters()
    ->findDirectory("workflows_folder")
    ->setValue(smtk::simulation::windtunnel::Metadata::WORKFLOWS_DIRECTORY);
  setupOp->parameters()
    ->findDirectory("operations_folder")
    ->setValue(smtk::simulation::windtunnel::Metadata::OPERATIONS_DIRECTORY);

  // Set container engine
  auto ceStatus = m_containerSetup->status();
  if (ceStatus == pqWindTunnelContainerEngineSetup::Ready)
  {
    std::string containerEnginePath = m_containerSetup->containerEnginePath().toStdString();
    auto engineItem = setupOp->parameters()->findString("container_engine");
    engineItem->setIsEnabled(true);
    engineItem->setValue(containerEnginePath);
  }
  else
  {
    qWarning() << "Container engine status" << m_containerSetup->statusAsString();
  }

  auto result = setupOp->operate();
  int outcome = result->findInt("outcome")->value();
  if (outcome != static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    qCritical() << "Error setting up operation paths, outcome" << outcome;
    return;
  }

  auto* sessionData = smtk::simulation::windtunnel::qtSessionData::instance();

  std::string uid = result->findString("uid")->value();
  QString QUID = QString::fromStdString(uid);
  sessionData->setDockerUID(QUID);

  std::string gid = result->findString("gid")->value();
  QString QGID = QString::fromStdString(gid);
  sessionData->setDockerGID(QGID);

  // Setup connections for add/remote resource manager
  QObject::connect(
    smtkBehavior,
    static_cast<void (pqSMTKBehavior::*)(pqSMTKWrapper*, pqServer*)>(
      &pqSMTKBehavior::addedManagerOnServer),
    this,
    &pqWindTunnelAutoStart::resourceManagerAdded);
  QObject::connect(
    smtkBehavior,
    static_cast<void (pqSMTKBehavior::*)(pqSMTKWrapper*, pqServer*)>(
      &pqSMTKBehavior::removingManagerFromServer),
    this,
    &pqWindTunnelAutoStart::resourceManagerRemoved);

  qInfo() << "Wind Tunnel plugin initialized.";
}

void pqWindTunnelAutoStart::initializeSettings(pqServer* server)
{
  vtkSMProxy* proxy = server->proxyManager()->GetProxy("settings", "SMTKSettings");
  if (proxy)
  {
    // Disable the smtk save-on-close dialog (superseded in our close behavior)
    vtkSMProperty* ssProp = proxy->GetProperty("ShowSaveResourceOnClose");
    vtkSMIntVectorProperty* ssIntProp = vtkSMIntVectorProperty::SafeDownCast(ssProp);
    if (ssIntProp != nullptr)
    {
      ssIntProp->SetElement(0, vtkSMTKSettings::DontShowAndDiscard);
    }
    else
    {
      qWarning() << "Property not found: ShowSaveResourceOnClose";
    }

    // Enable highlight on hover
    vtkSMProperty* hhProp = proxy->GetProperty("HighlightOnHover");
    vtkSMIntVectorProperty* hhIntProp = vtkSMIntVectorProperty::SafeDownCast(hhProp);
    if (hhIntProp != nullptr)
    {
      hhIntProp->SetElement(0, 1);
    } // if (ssIntProp)

    proxy->UpdateVTKObjects();
  } // if (proxy)
  else
  {
    qWarning() << "Did not find SMTKSettings";
  }
}
