//=============================================================================
//  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 "aeva/mbMainWindow.h"

#include "aeva/mbMenuBuilder.h"
#include "aeva/mbObjectFactory.h"
#include "aeva/mbReaderFactory.h"
#include "aeva/mbTestEventPlayer.h"
#include "aeva/mbTestEventTranslator.h"
#include "aeva/ui_mbFileMenu.h"
#include "aeva/ui_mbMainWindow.h"

#include "vtkPVConfig.h"

#include "pqAlwaysConnectedBehavior.h"
#include "pqApplyBehavior.h"
#include "pqDefaultViewBehavior.h"
#ifdef PARAVIEW_USE_QTHELP
#include "pqHelpReaction.h"
#endif
#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 "pqProxy.h"
#include "pqStandardViewFrameActionsImplementation.h"
#include "pqTestUtility.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 "vtkSMProxyManager.h"

#include <vtksys/SystemTools.hxx>

#ifdef AEVA_USE_PYTHON
#include "pvpythonmodules.h"
#endif

#ifdef AEVA_USE_PYTHON
#include "pqExportInspector.h"
#include "pqPythonDebugLeaksView.h"
#include "pqPythonShell.h"
typedef pqPythonDebugLeaksView DebugLeaksViewType;
#else
#include "vtkQtDebugLeaksView.h"
typedef vtkQtDebugLeaksView DebugLeaksViewType;
#endif

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

bool mbMainWindow::registerFactoryOverrides = ::registerFactoryOverrides();

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

//-----------------------------------------------------------------------------
static QToolBar* findToolBar(QMainWindow* mw, const QString& name)
{
  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)
  {
    foreach (QAction* bar, toolbar->findChildren<QAction*>())
    {
      if (bar->text() == name)
      {
        return bar;
      }
    }
  }
  return nullptr;
}

static QAction* findActionByName(QMainWindow* mw, const QString& name)
{
  if (mw)
  {
    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);
  }
}

//-----------------------------------------------------------------------------
mbMainWindow::mbMainWindow()
{
  // 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();
  }

#ifdef AEVA_USE_PYTHON
  pvpythonmodules_load();
#endif

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

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

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

  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 mbMainWindow::postParaViewSetup()
{
  // Enable help from the properties panel.
  QObject::connect(
    this->Internals->proxyTabWidget, &pqPropertiesPanel::helpRequested, &showHelpForProxy);

// Populate application menus with actions.
#if 1
  //pqParaViewMenuBuilders::buildFileMenu(*this->Internals->menu_File);
  mbMenuBuilder::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);

  // Hide sources and filters menus by default.
  this->Internals->menuSources->menuAction()->setVisible(false);
  this->Internals->menuFilters->menuAction()->setVisible(false);
  this->Internals->menuSources->setEnabled(false);
  this->Internals->menuFilters->setEnabled(false);

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

// 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"), "Find data...");
  hideAction(findActionByName(this, "actionFileLoadServerState"));
  hideAction(findActionByName(this, "actionFileSaveServerState"));

#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.
  mbMenuBuilder::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(true);
  pqParaViewBehaviors::setEnableDataTimeStepBehavior(false);
  pqParaViewBehaviors::setEnableSpreadSheetVisibilityBehavior(false);
  pqParaViewBehaviors::setEnablePipelineContextMenuBehavior(true);
  pqParaViewBehaviors::setEnableObjectPickingBehavior(false);
  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);

#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
  foreach (pqPropertiesPanel* ppanel, this->findChildren<pqPropertiesPanel*>())
  {
    applyBehavior->registerPanel(ppanel);
  }
#endif

  QList<QDockWidget*> all_docks = this->findChildren<QDockWidget*>();
  QDockWidget* attDW = nullptr;
  QDockWidget* infoDW = nullptr;
  QDockWidget* propDW = nullptr;
  foreach (QDockWidget* dw, all_docks)
  {
    if (dw->objectName() == "pqSMTKAttributePanel")
    {
      dw->setVisible(true);
      this->resizeDocks({ dw }, { 200 }, Qt::Horizontal);
      attDW = dw;
    }
    else if (dw->objectName() == "pqSMTKResourcePanel")
    {
      // move to the right side.
      removeDockWidget(dw);
      addDockWidget(Qt::RightDockWidgetArea, dw);
      dw->setVisible(true);
    }
    else if (dw->objectName() == "pqSMTKOperationPanel")
    {
      // move to the right side.
      removeDockWidget(dw);
      addDockWidget(Qt::RightDockWidgetArea, dw);
      dw->setVisible(false);
    }
    else if (dw->objectName() == "proxyTabDock")
    {
      dw->setVisible(true);
      propDW = dw;
    }
    else if (dw->objectName() == "informationDock")
    {
      dw->setVisible(true);
      infoDW = dw;
    }
    else if (dw->objectName() == "outputWidgetDock")
    {
      dw->setVisible(true);
      this->resizeDocks({ dw }, { 200 }, Qt::Vertical);
    }
    else
    {
      std::cerr << dw->objectName().toStdString() << "\n";
      dw->setVisible(false);
    }
  }

  if ((attDW != nullptr) && (propDW != nullptr))
  {
    // this->tabifyDockWidget(propDW, attDW);
    this->tabifyDockWidget(attDW, propDW);
    propDW->setVisible(false);
  }
  if ((attDW != nullptr) && (infoDW != nullptr))
  {
    // this->tabifyDockWidget(propDW, attDW);
    this->tabifyDockWidget(attDW, infoDW);
    infoDW->setVisible(false);
  }

  // Turn on auto-apply by default. This over-rides the preference.
  auto* autoApplyAction = findActionByName(this, "actionAutoApply");
  if (!autoApplyAction->isChecked())
  {
    autoApplyAction->activate(QAction::Trigger);
  }

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

  // Prepare sources and filters from XML
  // Load the filter and source XML configurations like
  // ParaView's branded_paraview_initializer.cxx.in would:
  auto* pvapp = pqPVApplicationCore::instance();
  QString root(":/aevaCMB/Configuration");
  QDir dir(root);
  QStringList files = dir.entryList(QDir::Files);
  foreach (QString file, files)
  {
    pvapp->loadConfiguration(root + QString("/") + file);
  }
  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);

  mbMainWindow::testSetup();
}

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

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

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

//-----------------------------------------------------------------------------
void mbMainWindow::togglePostProcessingMode(bool enablePostProcessing)
{
  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->menuSources->setEnabled(true);
    this->Internals->menuFilters->setEnabled(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"), "Find data...");
    showAction(findActionByName(this, "actionFileLoadServerState"));
    showAction(findActionByName(this, "actionFileSaveServerState"));
  }
  else
  {
    this->Internals->menuSources->menuAction()->setVisible(false);
    this->Internals->menuFilters->menuAction()->setVisible(false);
    this->Internals->menuSources->setEnabled(false);
    this->Internals->menuFilters->setEnabled(false);

    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"), "Find data...");
    hideAction(findActionByName(this, "actionFileLoadServerState"));
    hideAction(findActionByName(this, "actionFileSaveServerState"));
  }

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

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

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

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

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

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