//=============================================================================
//  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 "cmb/cmbMainWindow.h"

#include "cmb/cmbMenuBuilder.h"
#include "cmb/cmbObjectFactory.h"
#include "cmb/cmbReaderFactory.h"
#include "cmb/cmbTestEventPlayer.h"
#include "cmb/cmbTestEventTranslator.h"
#include "cmb/ui_cmbFileMenu.h"
#include "cmb/ui_cmbMainWindow.h"

#include "smtk/common/VersionMacros.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKRenderResourceBehavior.h"

// Include layout spec
#include "cmbLayoutSpec.h"

#if defined(PV_COMPAT_59)
#include "vtkPVConfig.h"
#else
#include "vtkPVVersion.h"
#endif

#include "pqAlwaysConnectedBehavior.h"
#include "pqApplyBehavior.h"
#include "pqDefaultViewBehavior.h"
#ifdef PARAVIEW_USE_QTHELP
#include "pqHelpReaction.h"
#endif
#include "pqApplicationComponentsInit.h"
#include "pqApplicationCore.h"
#include "pqAutoLoadPluginXMLBehavior.h"
#include "pqDeleteReaction.h"
#include "pqInterfaceTracker.h"
#include "pqPVApplicationCore.h"
#include "pqParaViewBehaviors.h"
#include "pqParaViewMenuBuilders.h"
#include "pqPipelineSource.h"
#include "pqStandardViewFrameActionsImplementation.h"
#include "pqTestUtility.h"
#include "pqTimeManagerWidget.h"

#include "cmbStandardRecentlyUsedResourceLoaderImplementation.h"

#include <QAction>
#include <QDockWidget>
#include <QList>
#include <QToolBar>

#include "pqAxesToolbar.h"
#include "pqHelpReaction.h"
#include "pqLoadDataReaction.h"
#include "pqMainControlsToolbar.h"
#include "pqMainWindowEventManager.h"
#include "pqRepresentationToolbar.h"
#include "pqSetName.h"

#include "vtkPVPluginLoader.h"
#include "vtkProcessModule.h"
#include "vtkSMProxyManager.h"
#include "vtkSMSession.h"
#include "vtkSession.h"

#include <vtksys/SystemTools.hxx>

#ifdef CMB_ENABLE_PYTHONSHELL
#include "pqPythonDebugLeaksView.h"
#include "pqPythonShell.h"
using DebugLeaksViewType = pqPythonDebugLeaksView;
#else
#include "vtkQtDebugLeaksView.h"
using DebugLeaksViewType = vtkQtDebugLeaksView;
#endif

namespace
{
bool registerFactoryOverrides()
{
  cmbObjectFactory* factory = cmbObjectFactory::New();
  if (factory)
  {
    // vtkObjectFactory keeps a reference to the "factory",
    vtkObjectFactory::RegisterFactory(factory);
    factory->Delete();
    return true;
  }
  return false;
}
} // namespace

bool cmbMainWindow::registerFactoryOverrides = ::registerFactoryOverrides();

class cmbMainWindow::pqInternals : public Ui::pqClientMainWindow
{
};

//-----------------------------------------------------------------------------
static QToolBar* findToolBar(QMainWindow* mw, const QString& name)
{
  Q_FOREACH (QToolBar* bar, mw->findChildren<QToolBar*>())
  {
    if (bar->windowTitle() == name)
    {
      return bar;
    }
  }
  return nullptr;
}

static void hideToolBar(QMainWindow* mw, const QString& name)
{
  auto* tb = findToolBar(mw, name);
  if (tb)
  {
    tb->hide();
  }
}

static void showToolBar(QMainWindow* mw, const QString& name)
{
  auto* tb = findToolBar(mw, name);
  if (tb)
  {
    tb->show();
  }
}

static QAction* findAction(QToolBar* toolbar, const QString& name)
{
  if (toolbar)
  {
    Q_FOREACH (QAction* bar, toolbar->findChildren<QAction*>())
    {
      if (bar->objectName() == name)
      {
        return bar;
      }
    }
  }
  return nullptr;
}

static QAction* findActionByName(QMainWindow* mw, const QString& name)
{
  if (mw)
  {
    Q_FOREACH (QAction* act, mw->findChildren<QAction*>())
    {
      if (act->objectName() == name)
      {
        return act;
      }
    }
  }
  return nullptr;
}

static void hideAction(QToolBar* tb, const QString& name)
{
  auto* act = findAction(tb, name);
  if (act)
  {
    act->setVisible(false);
  }
}

static void showAction(QToolBar* tb, const QString& name)
{
  auto* act = findAction(tb, name);
  if (act)
  {
    act->setVisible(true);
  }
}

static void hideAction(QAction* act)
{
  if (act)
  {
    act->setVisible(false);
  }
}

static void showAction(QAction* act)
{
  if (act)
  {
    act->setVisible(true);
  }
}

//-----------------------------------------------------------------------------
cmbMainWindow::cmbMainWindow()
{
#if SMTK_VERSION_NUMBER > 2401100
  // When operations create SMTK resources, force creation of a pqPipelineSource
  // corresponding to each resource (instead of only creating pipelines for
  // resources that have renderable geometry).
  // Until classes such as (including, but not limited to) pqSMTKAttributePanel,
  // pqSMTKCloseResourceBehavior, pqSMTKSaveResourceBehavior, and
  // pqSMTKSaveOnCloseResourceBehavior are refactored to use SMTK selections,
  // this setting allows many behaviors to work as expected.
  pqSMTKRenderResourceBehavior::setAlwaysCreatePipelines(true);
#endif

  // 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();
  }
  // Initialize ParaView icon resources, so we can use them in our menus.
  pqApplicationComponentsInit();

#ifdef CMB_ENABLE_MULTISERVERS
  vtkProcessModule::GetProcessModule()->MultipleSessionsSupportOn();
#endif

  this->setTabPosition(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea, QTabWidget::North);
  this->Internals = new pqInternals();
  this->Internals->setupUi(this);

#ifdef CMB_ENABLE_PYTHONSHELL
  pqPythonShell* shell = new pqPythonShell(this);
  shell->setObjectName("pythonShell");
  this->Internals->pythonShellDock->setWidget(shell);
  if (leaksView)
  {
    leaksView->setShell(shell);
  }
#endif

  // Allow multiple panels to dock side-by-side, which is
  // handy for the attribute and resource-tree panels:
  this->setDockNestingEnabled(true);

  // Setup default GUI layout

  // 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);

// Populate application menus with actions.
// This must occur now, so pqParaViewMenuBuilders can watch for default PV xmls to
// be loaded by pvInitializer.Initialize()
#if 1
  //pqParaViewMenuBuilders::buildFileMenu(*this->Internals->menu_File);
  cmbMenuBuilder::buildFileMenu(*this->Internals->menu_File);
#else
  QList<QAction*> qa = this->Internals->menu_File->actions();
  QAction* mqa = qa.at(0);
  new pqLoadDataReaction(mqa);
#endif

  pqParaViewMenuBuilders::buildEditMenu(*this->Internals->menu_Edit);

  // 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 Catalyst menu.
  pqParaViewMenuBuilders::buildCatalystMenu(*this->Internals->menu_Catalyst);

  // Hide sources, filters, extract generators, and catalyst menus by default.
  this->Internals->menuSources->menuAction()->setVisible(false);
  this->Internals->menuFilters->menuAction()->setVisible(false);
  this->Internals->menuExtractors->menuAction()->setVisible(false);
  this->Internals->menu_Catalyst->menuAction()->setVisible(false);
  this->Internals->menuSources->setEnabled(false);
  this->Internals->menuFilters->setEnabled(false);
  this->Internals->menuExtractors->setEnabled(false);
  this->Internals->menu_Catalyst->setEnabled(false);

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

  auto* pvapp = pqPVApplicationCore::instance();
  QObject::connect(pvapp, SIGNAL(clientEnvironmentDone()), this, SLOT(postParaViewSetup()));
}

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

void cmbMainWindow::postParaViewSetup()
{
  // Enable help from the properties panel.
  QObject::connect(
    this->Internals->proxyTabWidget, &pqPropertiesPanel::helpRequested, &showHelpForProxy);

// Populate toolbars
#if 1
  pqParaViewMenuBuilders::buildToolbars(*this);
  // Hide toolbars and actions that are only used during post-processing.
  // The post-processing-mode plugin will make them visible when in post-processing mode.
  hideToolBar(this, "VCR Controls");
  hideToolBar(this, "Current Time Controls");
  hideAction(findToolBar(this, "Main Controls"), "actionQuery");
  hideAction(findToolBar(this, "Main Controls"), "actionSaveCatalystState");
  hideAction(findActionByName(this, "actionFileLoadServerState"));
  hideAction(findActionByName(this, "actionFileSaveServerState"));
  hideAction(findActionByName(this, "actionFileSaveCatalystState"));
  hideAction(findActionByName(this, "actionFileSaveExtracts"));

#else
  QToolBar* mainToolBar = new pqMainControlsToolbar(this) << pqSetName("MainControlsToolbar");
  mainToolBar->layout()->setSpacing(0);
  this->addToolBar(Qt::TopToolBarArea, mainToolBar);

  QToolBar* reprToolbar = new pqRepresentationToolbar(this) << pqSetName("representationToolbar");
  reprToolbar->layout()->setSpacing(0);
  this->addToolBar(Qt::TopToolBarArea, reprToolbar);

  QToolBar* axesToolbar = new pqAxesToolbar(this) << pqSetName("axesToolbar");
  axesToolbar->layout()->setSpacing(0);
  this->addToolBar(Qt::TopToolBarArea, axesToolbar);
#endif

  // 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.
  cmbMenuBuilder::buildHelpMenu(*this->Internals->menu_Help);

#if 1
  // Final step, define application behaviors. Since we want some paraview behaviors
  // we can use static method to configure the pqParaViewBehaviors and select
  // only the components we want
  pqParaViewBehaviors::setEnableStandardPropertyWidgets(true);
  pqParaViewBehaviors::setEnableStandardRecentlyUsedResourceLoader(false);
  pqParaViewBehaviors::setEnableDataTimeStepBehavior(false);
  pqParaViewBehaviors::setEnableSpreadSheetVisibilityBehavior(false);
  pqParaViewBehaviors::setEnablePipelineContextMenuBehavior(true);

  // Disable object picking to match ParaView, aeva by default.
  // When enabled, single click selects or
  // de-selects pipeline objects, and therefore smtk entities, losing the
  // current selection.
  bool objectPicking = false;
#ifdef CMB_ENABLE_OBJECTPICKING
  objectPicking = true;
#endif
  pqParaViewBehaviors::setEnableObjectPickingBehavior(objectPicking);

  pqParaViewBehaviors::setEnableUndoRedoBehavior(false);
  pqParaViewBehaviors::setEnableCrashRecoveryBehavior(false);
  pqParaViewBehaviors::setEnablePluginDockWidgetsBehavior(true);
  pqParaViewBehaviors::setEnableVerifyRequiredPluginBehavior(true);
  pqParaViewBehaviors::setEnablePluginActionGroupBehavior(true);
  pqParaViewBehaviors::setEnableCommandLineOptionsBehavior(true);
  pqParaViewBehaviors::setEnablePersistentMainWindowStateBehavior(true);
  pqParaViewBehaviors::setEnableCollaborationBehavior(false);
  pqParaViewBehaviors::setEnableViewStreamingBehavior(false);
  pqParaViewBehaviors::setEnablePluginSettingsBehavior(true);
  pqParaViewBehaviors::setEnableQuickLaunchShortcuts(false);
  pqParaViewBehaviors::setEnableLockPanelsBehavior(true);

  // This is actually useless, as they are activated by default
  pqParaViewBehaviors::setEnableStandardViewFrameActions(
    true); // ... but we need block-select when repr is SMTK model
  pqParaViewBehaviors::setEnableDefaultViewBehavior(true);
  pqParaViewBehaviors::setEnableAlwaysConnectedBehavior(true);
  pqParaViewBehaviors::setEnableAutoLoadPluginXMLBehavior(true);
  pqParaViewBehaviors::setEnableApplyBehavior(true);
  new pqParaViewBehaviors(this, this);

  // register our override of
  pqInterfaceTracker* pgm = pqApplicationCore::instance()->interfaceTracker();
  pgm->addInterface(new cmbStandardRecentlyUsedResourceLoaderImplementation(pgm));

#else
  // Or do everything manually, not recommended

  // Register standard types of view-frame actions.
  // Needed for Default View Behavior
  pqInterfaceTracker* pgm = pqApplicationCore::instance()->interfaceTracker();
  pgm->addInterface(new pqStandardViewFrameActionsImplementation(pgm));

  // Create behaviors
  new pqDefaultViewBehavior(this);
  new pqAlwaysConnectedBehavior(this);
  new pqAutoLoadPluginXMLBehavior(this);
  pqApplyBehavior* applyBehavior = new pqApplyBehavior(this);

  // Register panels
  Q_FOREACH (pqPropertiesPanel* ppanel, this->findChildren<pqPropertiesPanel*>())
  {
    applyBehavior->registerPanel(ppanel);
  }
#endif

  cmbLayoutSpec layoutSpec;
  QList<QDockWidget*> all_docks = this->findChildren<QDockWidget*>();
  Q_FOREACH (QDockWidget* dw, all_docks)
  {
    layoutSpec.layoutDockWidget(this, dw);
  }

  // Enable delete from the properties panel.
  auto* actionDel = new QAction(this);
  auto* reactionDel = new pqDeleteReaction(actionDel);
  QObject::connect(
    this->Internals->proxyTabWidget,
    &pqPropertiesPanel::deleteRequested,
    reactionDel,
    &pqDeleteReaction::deleteSource);

  hideToolBar(this, "&Measurement Tools");

  // Set up but hide the pipeline browser dock-widget by default.
  pqParaViewMenuBuilders::buildPipelineBrowserContextMenu(
    *this->Internals->pipelineBrowser->contextMenu());

  // Turn off post-processing functionality by default.
  // NB: This is fragile! The timer below is set for 20ms
  //     because pqPersistentMainWindowStateBehavior::restoreState
  //     is scheduled to occur at 10ms after the event loop starts.
  //     If ParaView changes, then this timer must change.
  QTimer::singleShot(20, this, SLOT(prepare()));

  // Enable the colormap editor.
  this->Internals->colorMapEditorDock->hide();
  pqApplicationCore::instance()->registerManager(
    "COLOR_EDITOR_PANEL", this->Internals->colorMapEditorDock);

  cmbMainWindow::testSetup();
}

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

QMenu* cmbMainWindow::createPopupMenu()
{
  QMenu* menu = this->QMainWindow::createPopupMenu();
  menu->setObjectName("toolbarDockWidgetMenu");
  return menu;
}

//-----------------------------------------------------------------------------
void cmbMainWindow::prepare()
{
  this->togglePostProcessingMode(false);
}

//-----------------------------------------------------------------------------
void cmbMainWindow::togglePostProcessingMode(bool enablePostProcessing)
{
  auto* pipelineAnimationDock = this->Internals->animationViewDock;
  auto* pipelineAnimationAction = pipelineAnimationDock->toggleViewAction();

  auto* pipelineBrowserDock = this->Internals->pipelineBrowserDock;
  auto* pipelineBrowserAction = pipelineBrowserDock->toggleViewAction();

  auto* multiblockInspectorDock = this->Internals->multiBlockInspectorDock;
  auto* multiblockInspectorAction = multiblockInspectorDock->toggleViewAction();

  // Whenever we enable postprocessing mode, we show the pipeline browser dock.
  // We only want to show the multiblock inspector dock if the user selects it,
  // however. We therefore store the state of the multiblock inspector whenever
  // postprocessing is disabled, and we assign it when it is enabled.
  static bool showMultiblockInspectorInPostProcessing = false;

  if (enablePostProcessing)
  {
    this->Internals->menuSources->menuAction()->setVisible(true);
    this->Internals->menuFilters->menuAction()->setVisible(true);
    this->Internals->menuExtractors->menuAction()->setVisible(true);
    this->Internals->menu_Catalyst->menuAction()->setVisible(true);
    this->Internals->menuSources->setEnabled(true);
    this->Internals->menuFilters->setEnabled(true);
    this->Internals->menuExtractors->setEnabled(true);
    this->Internals->menu_Catalyst->setEnabled(true);

    pipelineAnimationAction->setEnabled(true);
    pipelineAnimationAction->setVisible(true);

    pipelineBrowserAction->setEnabled(true);
    pipelineBrowserAction->setVisible(true);
    pipelineBrowserAction->setChecked(true);
    pipelineBrowserDock->show();

    multiblockInspectorAction->setEnabled(true);
    multiblockInspectorAction->setVisible(true);
    multiblockInspectorAction->setChecked(showMultiblockInspectorInPostProcessing);
    if (showMultiblockInspectorInPostProcessing)
    {
      multiblockInspectorDock->show();
    }

    showToolBar(this, "&Common");
    showToolBar(this, "&Data Analysis");
    showToolBar(this, "VCR Controls");
    showToolBar(this, "Current Time Controls");
    showToolBar(this, "Active Variable Controls");

    showAction(findToolBar(this, "Main Controls"), "actionQuery");
    showAction(findToolBar(this, "Main Controls"), "actionSaveCatalystState");
    showAction(findActionByName(this, "actionFileLoadServerState"));
    showAction(findActionByName(this, "actionFileSaveServerState"));
    showAction(findActionByName(this, "actionFileSaveAnimation"));
    showAction(findActionByName(this, "actionFileSaveCatalystState"));
    showAction(findActionByName(this, "actionFileSaveExtracts"));
  }
  else
  {
    this->Internals->menuSources->menuAction()->setVisible(false);
    this->Internals->menuFilters->menuAction()->setVisible(false);
    this->Internals->menuExtractors->menuAction()->setVisible(false);
    this->Internals->menu_Catalyst->menuAction()->setVisible(false);
    this->Internals->menuSources->setEnabled(false);
    this->Internals->menuFilters->setEnabled(false);
    this->Internals->menuExtractors->setEnabled(false);
    this->Internals->menu_Catalyst->setEnabled(false);

    pipelineAnimationAction->setEnabled(false);
    pipelineAnimationAction->setVisible(false);
    pipelineAnimationAction->setChecked(false);
    pipelineAnimationDock->hide();

    pipelineBrowserAction->setEnabled(false);
    pipelineBrowserAction->setVisible(false);
    pipelineBrowserAction->setChecked(false);
    pipelineBrowserDock->hide();

    multiblockInspectorAction->setEnabled(false);
    multiblockInspectorAction->setVisible(false);
    showMultiblockInspectorInPostProcessing = multiblockInspectorAction->isChecked();
    multiblockInspectorAction->setChecked(false);
    multiblockInspectorDock->hide();

    hideToolBar(this, "&Common");
    hideToolBar(this, "&Data Analysis");
    hideToolBar(this, "VCR Controls");
    hideToolBar(this, "Current Time Controls");
    hideToolBar(this, "Active Variable Controls");

    hideAction(findToolBar(this, "Main Controls"), "actionQuery");
    hideAction(findToolBar(this, "Main Controls"), "actionSaveCatalystState");
    hideAction(findActionByName(this, "actionFileLoadServerState"));
    hideAction(findActionByName(this, "actionFileSaveServerState"));
    hideAction(findActionByName(this, "actionFileSaveAnimation"));
    hideAction(findActionByName(this, "actionFileSaveCatalystState"));
    hideAction(findActionByName(this, "actionFileSaveExtracts"));
  }

  auto* readerFactory =
    dynamic_cast<cmbReaderFactory*>(vtkSMProxyManager::GetProxyManager()->GetReaderFactory());
  if (readerFactory)
  {
    readerFactory->SetPostProcessingMode(enablePostProcessing);
  }
}

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

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

//-----------------------------------------------------------------------------
void cmbMainWindow::showEvent(QShowEvent* evt)
{
  pqApplicationCore::instance()->getMainWindowEventManager()->showEvent(evt);
}

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

//-----------------------------------------------------------------------------
void cmbMainWindow::testSetup()
{
  pqApplicationCore* const core = pqApplicationCore::instance();
  // When recording tests, translate some Qt events before writing to disk:
  core->testUtility()->eventTranslator()->addWidgetEventTranslator(
    new cmbTestEventTranslator(core->testUtility()));
  // When playing tests, translate some Qt events before firing them:
  core->testUtility()->eventPlayer()->addWidgetEventPlayer(
    new cmbTestEventPlayer(core->testUtility()));
}
