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

#include "ParaViewMainWindow.h"
#include "ui_ParaViewMainWindow.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqDeleteReaction.h"
#include "pqMainWindowEventManager.h"
#include "pqParaViewBehaviors.h"
#include "pqParaViewMenuBuilders.h"
#include "pqSaveStateReaction.h"
#include "pqSettings.h"
#include "pqTimer.h"
#include "pqWelcomeDialog.h"

#include "vtkCommand.h"
#include "vtkPVGeneralSettings.h"
#include "vtkRemotingCoreConfiguration.h"
#include "vtkSMSettings.h"
#include "vtksys/SystemTools.hxx"

#include "pqQtConfig.h"
#ifdef PARAVIEW_USE_QTHELP
#include "pqHelpReaction.h"
#endif

#include <QDragEnterEvent>
#include <QMessageBox>
#include <QTextCodec>
#include <QtDebug>

#if PARAVIEW_ENABLE_EMBEDDED_DOCUMENTATION
#include "ParaViewDocumentationInitializer.h"
#endif

#if PARAVIEW_USE_MATERIALEDITOR
#include "pqMaterialEditor.h"
#endif

#if PARAVIEW_USE_PYTHON
#include "pqPythonDebugLeaksView.h"
#include "pqPythonShell.h"
typedef pqPythonDebugLeaksView DebugLeaksViewType;
#else
#include "vtkQtDebugLeaksView.h"
typedef vtkQtDebugLeaksView DebugLeaksViewType;
#endif

class ParaViewMainWindow::pqInternals : public Ui::pqClientMainWindow
{
public:
  bool FirstShow;
  int CurrentGUIFontSize;
  QFont DefaultApplicationFont; // will be initialized to default app font in constructor.
  pqTimer UpdateFontSizeTimer;
  pqInternals()
    : FirstShow(true)
    , CurrentGUIFontSize(0)
  {
    this->UpdateFontSizeTimer.setInterval(0);
    this->UpdateFontSizeTimer.setSingleShot(true);
  }
};

//-----------------------------------------------------------------------------
ParaViewMainWindow::ParaViewMainWindow()
{
  // the debug leaks view should be constructed as early as possible
  // so that it can monitor vtk objects created at application startup.
  DebugLeaksViewType* leaksView = nullptr;
  if (vtksys::SystemTools::GetEnv("PV_DEBUG_LEAKS_VIEW"))
  {
    leaksView = new DebugLeaksViewType(this);
    leaksView->setWindowFlags(Qt::Window);
    leaksView->show();
  }

#if PARAVIEW_USE_PYTHON
  pvpythonmodules_load();
#endif

#if PARAVIEW_ENABLE_EMBEDDED_DOCUMENTATION
  // init the ParaView embedded documentation.
  paraview_documentation_initialize();
#endif

  this->Internals = new pqInternals();
  this->Internals->setupUi(this);
  this->Internals->outputWidgetDock->hide();
  this->Internals->pythonShellDock->hide();
  this->Internals->materialEditorDock->hide();
#if PARAVIEW_USE_PYTHON
  pqPythonShell* shell = new pqPythonShell(this);
  shell->setObjectName("pythonShell");
  this->Internals->pythonShellDock->setWidget(shell);
  if (leaksView)
  {
    leaksView->setShell(shell);
  }
#endif

#if PARAVIEW_USE_MATERIALEDITOR
  pqMaterialEditor* materialEditor =
    new pqMaterialEditor(this, this->Internals->materialEditorDock);
  materialEditor->setObjectName("materialEditorPanel");
  this->Internals->materialEditorDock->setWidget(materialEditor);
#endif

  // show output widget if we received an error message.
  this->connect(this->Internals->outputWidget, SIGNAL(messageDisplayed(const QString&, int)),
    SLOT(handleMessage(const QString&, int)));

  // Setup default GUI layout.
  this->setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::North);

  // Set up the dock window corners to give the vertical docks more room.
  this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
  this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

  this->tabifyDockWidget(this->Internals->colorMapEditorDock, this->Internals->memoryInspectorDock);
  this->tabifyDockWidget(
    this->Internals->colorMapEditorDock, this->Internals->comparativePanelDock);
  this->tabifyDockWidget(
    this->Internals->colorMapEditorDock, this->Internals->collaborationPanelDock);
  this->tabifyDockWidget(this->Internals->colorMapEditorDock, this->Internals->lightInspectorDock);
  this->tabifyDockWidget(this->Internals->colorMapEditorDock, this->Internals->findDataDock);
  this->tabifyDockWidget(
    this->Internals->colorMapEditorDock, this->Internals->multiBlockInspectorDock);

  this->Internals->findDataDock->hide();
  this->Internals->statisticsDock->hide();
  this->Internals->comparativePanelDock->hide();
  this->Internals->collaborationPanelDock->hide();
  this->Internals->memoryInspectorDock->hide();
  this->Internals->multiBlockInspectorDock->hide();
  this->Internals->colorMapEditorDock->hide();
  this->Internals->timeManagerDock->hide();
  this->Internals->lightInspectorDock->hide();
  this->Internals->selectionEditorDock->hide();

  this->tabifyDockWidget(this->Internals->timeManagerDock, this->Internals->statisticsDock);
  this->tabifyDockWidget(this->Internals->timeManagerDock, this->Internals->outputWidgetDock);
  this->tabifyDockWidget(this->Internals->timeManagerDock, this->Internals->pythonShellDock);

  // setup properties dock
  this->tabifyDockWidget(this->Internals->propertiesDock, this->Internals->viewPropertiesDock);
  this->tabifyDockWidget(this->Internals->propertiesDock, this->Internals->displayPropertiesDock);
  this->tabifyDockWidget(this->Internals->propertiesDock, this->Internals->informationDock);

  vtkSMSettings* settings = vtkSMSettings::GetInstance();

  int propertiesPanelMode = settings->GetSettingAsInt(
    ".settings.GeneralSettings.PropertiesPanelMode", vtkPVGeneralSettings::ALL_IN_ONE);
  switch (propertiesPanelMode)
  {
    case vtkPVGeneralSettings::SEPARATE_DISPLAY_PROPERTIES:
      delete this->Internals->viewPropertiesPanel;
      delete this->Internals->viewPropertiesDock;
      this->Internals->viewPropertiesPanel = nullptr;
      this->Internals->viewPropertiesDock = nullptr;

      this->Internals->propertiesPanel->setPanelMode(
        pqPropertiesPanel::SOURCE_PROPERTIES | pqPropertiesPanel::VIEW_PROPERTIES);
      break;

    case vtkPVGeneralSettings::SEPARATE_VIEW_PROPERTIES:
      delete this->Internals->displayPropertiesPanel;
      delete this->Internals->displayPropertiesDock;
      this->Internals->displayPropertiesPanel = nullptr;
      this->Internals->displayPropertiesDock = nullptr;

      this->Internals->propertiesPanel->setPanelMode(
        pqPropertiesPanel::SOURCE_PROPERTIES | pqPropertiesPanel::DISPLAY_PROPERTIES);
      break;

    case vtkPVGeneralSettings::ALL_SEPARATE:
      this->Internals->propertiesPanel->setPanelMode(pqPropertiesPanel::SOURCE_PROPERTIES);
      break;

    case vtkPVGeneralSettings::ALL_IN_ONE:
    default:
      delete this->Internals->viewPropertiesPanel;
      delete this->Internals->viewPropertiesDock;
      this->Internals->viewPropertiesPanel = nullptr;
      this->Internals->viewPropertiesDock = nullptr;

      delete this->Internals->displayPropertiesPanel;
      delete this->Internals->displayPropertiesDock;
      this->Internals->displayPropertiesPanel = nullptr;
      this->Internals->displayPropertiesDock = nullptr;
      break;
  }

  // update UI when font size changes.
  vtkPVGeneralSettings* gsSettings = vtkPVGeneralSettings::GetInstance();
  pqCoreUtilities::connect(
    gsSettings, vtkCommand::ModifiedEvent, &this->Internals->UpdateFontSizeTimer, SLOT(start()));
  this->connect(&this->Internals->UpdateFontSizeTimer, SIGNAL(timeout()), SLOT(updateFontSize()));

  this->Internals->propertiesDock->show();
  this->Internals->propertiesDock->raise();

  // Enable help from the properties panel.
  QObject::connect(this->Internals->propertiesPanel,
    SIGNAL(helpRequested(const QString&, const QString&)), this,
    SLOT(showHelpForProxy(const QString&, const QString&)));

  /// hook delete to pqDeleteReaction.
  QAction* tempDeleteAction = new QAction(this);
  pqDeleteReaction* handler = new pqDeleteReaction(tempDeleteAction);
  handler->connect(this->Internals->propertiesPanel, SIGNAL(deleteRequested(pqProxy*)),
    SLOT(deleteSource(pqProxy*)));

  // setup color editor
  /// Provide access to the color-editor panel for the application.
  pqApplicationCore::instance()->registerManager(
    "COLOR_EDITOR_PANEL", this->Internals->colorMapEditorDock);

  // Provide access to the find data panel for the application.
  pqApplicationCore::instance()->registerManager("FIND_DATA_PANEL", this->Internals->findDataDock);

  // Populate application menus with actions.
  pqParaViewMenuBuilders::buildFileMenu(*this->Internals->menu_File);
  pqParaViewMenuBuilders::buildEditMenu(
    *this->Internals->menu_Edit, this->Internals->propertiesPanel);

  // Populate sources menu.
  pqParaViewMenuBuilders::buildSourcesMenu(*this->Internals->menuSources, this);

  // Populate filters menu.
  pqParaViewMenuBuilders::buildFiltersMenu(*this->Internals->menuFilters, this);

  // Populate extractors menu.
  pqParaViewMenuBuilders::buildExtractorsMenu(*this->Internals->menuExtractors, this);

  // Populate Tools menu.
  pqParaViewMenuBuilders::buildToolsMenu(*this->Internals->menuTools);

  // Populate Catalyst menu.
  pqParaViewMenuBuilders::buildCatalystMenu(*this->Internals->menu_Catalyst);

  // setup the context menu for the pipeline browser.
  pqParaViewMenuBuilders::buildPipelineBrowserContextMenu(
    *this->Internals->pipelineBrowser->contextMenu());

  pqParaViewMenuBuilders::buildToolbars(*this);

  // Setup the View menu. This must be setup after all toolbars and dockwidgets
  // have been created.
  pqParaViewMenuBuilders::buildViewMenu(*this->Internals->menu_View, *this);

  // Setup the menu to show macros.
  pqParaViewMenuBuilders::buildMacrosMenu(*this->Internals->menu_Macros);

  // Setup the help menu.
  pqParaViewMenuBuilders::buildHelpMenu(*this->Internals->menu_Help);

  // Final step, define application behaviors. Since we want all ParaView
  // behaviors, we use this convenience method.

  // UsageLoggingBehavior needs to explicitly enabled for ParaView since it's
  // disabled by default.
  pqParaViewBehaviors::setEnableUsageLoggingBehavior(true);
  new pqParaViewBehaviors(this, this);
}

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

//-----------------------------------------------------------------------------
void ParaViewMainWindow::showHelpForProxy(const QString& groupname, const QString& proxyname)
{
#ifdef PARAVIEW_USE_QTHELP
  pqHelpReaction::showProxyHelp(groupname, proxyname);
#endif
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::dragEnterEvent(QDragEnterEvent* evt)
{
  pqApplicationCore::instance()->getMainWindowEventManager()->dragEnterEvent(evt);
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::dropEvent(QDropEvent* evt)
{
  pqApplicationCore::instance()->getMainWindowEventManager()->dropEvent(evt);
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::showEvent(QShowEvent* evt)
{
  this->Superclass::showEvent(evt);

  if (this->Internals->FirstShow)
  {
    this->Internals->FirstShow = false;
    if (!vtkRemotingCoreConfiguration::GetInstance()->GetDisableRegistry())
    {
      auto core = pqApplicationCore::instance();
      if (core->settings()->value("GeneralSettings.ShowWelcomeDialog", true).toBool())
      {
        pqTimer::singleShot(1000, this, SLOT(showWelcomeDialog()));
      }

      this->updateFontSize();
    }
  }

  pqApplicationCore::instance()->getMainWindowEventManager()->showEvent(evt);
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::closeEvent(QCloseEvent* evt)
{
  pqApplicationCore::instance()->getMainWindowEventManager()->closeEvent(evt);
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::showWelcomeDialog()
{
  pqWelcomeDialog dialog(this);
  dialog.exec();
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::updateFontSize()
{
  auto& internals = *this->Internals;
  vtkPVGeneralSettings* gsSettings = vtkPVGeneralSettings::GetInstance();
  int fontSize = internals.DefaultApplicationFont.pointSize();
  if (gsSettings->GetGUIOverrideFont() && gsSettings->GetGUIFontSize() > 0)
  {
    fontSize = gsSettings->GetGUIFontSize();
  }
  if (fontSize <= 0)
  {
    qDebug() << "Ignoring invalid font size: " << fontSize;
    return;
  }

  if (internals.CurrentGUIFontSize != fontSize)
  {
    QFont newFont = this->font();
    newFont.setPointSize(fontSize);
    this->setFont(newFont);
    this->Internals->CurrentGUIFontSize = fontSize;
  }

// Console font size
#if PARAVIEW_USE_PYTHON
  pqPythonShell* shell = qobject_cast<pqPythonShell*>(this->Internals->pythonShellDock->widget());
  shell->setFontSize(fontSize);
#endif
  pqOutputWidget* outputWidget =
    qobject_cast<pqOutputWidget*>(this->Internals->outputWidgetDock->widget());
  outputWidget->setFontSize(fontSize);
}

//-----------------------------------------------------------------------------
void ParaViewMainWindow::handleMessage(const QString&, int type)
{
  QDockWidget* dock = this->Internals->outputWidgetDock;
  pqOutputWidget* outputWidget = qobject_cast<pqOutputWidget*>(dock->widget());
  if (dock->isFloating() && !outputWidget->shouldOpenForNewMessages())
  {
    return;
  }
  if (!dock->isVisible() && (type == QtCriticalMsg || type == QtFatalMsg || type == QtWarningMsg))
  {
    // if dock is not visible, we always pop it up as a floating dialog. This
    // avoids causing re-renders which may cause more errors and more confusion.
    QRect rectApp = this->geometry();

    QRect rectDock(
      QPoint(0, 0), QSize(static_cast<int>(rectApp.width() * 0.4), dock->sizeHint().height()));
    rectDock.moveCenter(
      QPoint(rectApp.center().x(), rectApp.bottom() - dock->sizeHint().height() / 2));
    dock->setFloating(true);
    dock->setGeometry(rectDock);
    dock->show();
  }
  if (dock->isVisible())
  {
    dock->raise();
  }
}
