//=========================================================================
//  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/pqACE3PAutoStart.h"
#include "plugin/pqACE3PCloseBehavior.h"

#include "smtk/newt/qtNewtInterface.h"
#include "smtk/simulation/ace3p/Metadata.h"
#include "smtk/simulation/ace3p/Registrar.h"
#include "smtk/simulation/ace3p/qt/qtNerscFileItem.h"
#include "smtk/simulation/ace3p/qt/qtProjectRuntime.h"

// SMTK includes
#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/extension/qt/qtSMTKUtilities.h"
#include "smtk/project/Project.h"
#include "smtk/view/ResourcePhraseModel.h"

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

#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
#include <QList>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QTimer>
#include <QUrl>
#include <QWidget>

// Static accessor for builtin server
pqServer* pqACE3PAutoStart::s_builtinServer = nullptr;
pqServer* pqACE3PAutoStart::builtinServer()
{
  return s_builtinServer;
}

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;

// Generate QAction instances for help menu
QList<QAction*> createHelpMenuActions()
{
  QList<QAction*> actionList;

  QUrl docsURL("https://mb-ace3p.readthedocs.io/en/latest/");
  QAction* docsAction = new QAction("ModelBuilder getting started (website)")
    << pqSetName("actionDocs");
  new pqDesktopServicesReaction(docsURL, docsAction);
  actionList.push_back(docsAction);

  QUrl cwURL("https://confluence.slac.stanford.edu/display/AdvComp/Materials+for+CW18");
  QAction* cwAction = new QAction("SLAC Code Workshop materials (website)")
    << pqSetName("actionCWMaterials");
  new pqDesktopServicesReaction(cwURL, cwAction);
  actionList.push_back(cwAction);

  QUrl issueURL("https://gitlab.kitware.com/cmb/plugins/ace3p-extensions/-/issues");
  QAction* issueAction = new QAction("ModelBuilder issue tracker (website)")
    << pqSetName("actionIssue");
  new pqDesktopServicesReaction(issueURL, issueAction);
  actionList.push_back(issueAction);

  return actionList;
}

// Rebuild the cmb help menu
void rebuildHelpMenu(QMenu* menu)
{
  // Get the "About" action (which should be last)
  QList<QAction*> actionList = menu->actions();
  QAction* aboutAction = actionList.last();
  if (!aboutAction->text().startsWith("About"))
  {
    qWarning() << "Problem finding Help menu's About item (expected to be last).";
    return;
  }

  QAction* lastAction = aboutAction;
  int lastIndex = actionList.size() - 1;

  // Check if penultimate action is a separator
  if (actionList[lastIndex - 1]->isSeparator())
  {
    lastAction = actionList[lastIndex - 1];
    --lastIndex;
  }

  // Remove actions before lastIndex
  for (int i = 0; i < lastIndex; ++i)
  {
    menu->removeAction(actionList[i]);
  }

  // Insert new  menu items before lastAction
  auto newActionList = createHelpMenuActions();
  menu->insertActions(lastAction, newActionList);
}

// Replace the cmb-generated Help menu with an ace3p-specific one.
// This function uses QTimer to recursively wait until the Help menu
// has been configured by CMB.
void setHelpMenu()
{
  ++retryCount;
  QMainWindow* mainWindow = qobject_cast<QMainWindow*>(pqCoreUtilities::mainWidget());
  if (mainWindow == nullptr)
  {
    QTimer::singleShot(10, []() { setHelpMenu(); });
    return;
  }

  // Search for the Help menu
  QList<QAction*> menuBarActions = mainWindow->menuBar()->actions();
  QMenu* menu = nullptr;
  foreach (QAction* existingMenuAction, menuBarActions)
  {
    QString menuName = existingMenuAction->text();
    menuName.remove('&');
    if (menuName == "Help")
    {
      menu = existingMenuAction->menu();
      break;
    }
  }

  // Check if help menu instantiated
  if (menu == nullptr)
  {
    // qWarning() << "Help menu not found.";
    QTimer::singleShot(10, []() { setHelpMenu(); });
    return;
  }

  // Check if help menu is populated
  QList<QAction*> actionList = menu->actions();
  int actionListSize = actionList.size();
  if (actionListSize < 3)
  {
    QTimer::singleShot(10, []() { setHelpMenu(); });
    return;
  }

  // Can now replace contents
  rebuildHelpMenu(menu);
}

} // namespace

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

void pqACE3PAutoStart::startup()
{
// Set workflows folder for developer builds
#ifdef WORKFLOWS_SOURCE_DIR
  QDir workflowsDir(WORKFLOWS_SOURCE_DIR);
  if (workflowsDir.exists())
  {
    smtk::simulation::ace3p::Metadata::WORKFLOWS_DIRECTORY = WORKFLOWS_SOURCE_DIR;
#ifndef NDEBUG
    qDebug() << "Using Workflows directory" << WORKFLOWS_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);

    if (!server->isRemote())
    {
      s_builtinServer = server;
    }
  }

  // Listen for server connections
  auto smtkBehavior = pqSMTKBehavior::instance();
  QObject::connect(
    smtkBehavior,
    static_cast<void (pqSMTKBehavior::*)(pqSMTKWrapper*, pqServer*)>(
      &pqSMTKBehavior::addedManagerOnServer),
    this,
    &pqACE3PAutoStart::resourceManagerAdded);
  QObject::connect(
    smtkBehavior,
    static_cast<void (pqSMTKBehavior::*)(pqSMTKWrapper*, pqServer*)>(
      &pqSMTKBehavior::removingManagerFromServer),
    this,
    &pqACE3PAutoStart::resourceManagerRemoved);

  // Instantiate newt interface
  newt::qtNewtInterface::instance(pqCoreUtilities::mainWidget());

// ENABLE_ACE3P_UI_FEATURES set by cmake option
#ifdef ENABLE_ACE3P_UI_FEATURES
  // Replace help menu
  QTimer::singleShot(10, []() { setHelpMenu(); });
  pqAutoApplyReaction::setAutoApply(true);

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

      bool notCanceled = true;

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

      closeEvent->setAccepted(notCanceled);
    });
#endif

  // Since the loading order of smtk plugins is indeterminate, a spinning
  // function call is used here to customize the resource view.
  QTimer::singleShot(10, []() { setView(); });
}

void pqACE3PAutoStart::shutdown() {}

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

  if ((s_builtinServer == nullptr) && !server->isRemote())
  {
    s_builtinServer = server;
  }

  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;
  }

  smtk::simulation::ace3p::Registrar::registerTo(projManager);

  // Register custom item view for NERSC file browser
  qtSMTKUtilities::registerItemConstructor(
    "NERSCDirectory", smtk::simulation::ace3p::qtNerscFileItem::createItemWidget);

// ENABLE_ACE3P_UI_FEATURES set by cmake option
#ifdef ENABLE_ACE3P_UI_FEATURES
  // Update some default smtk settings
  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);
    } // if (ssIntProp)

    // 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)
#endif
}

void pqACE3PAutoStart::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::ace3p::Registrar::unregisterFrom(projManager);
  }
}
