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

// Client side
#include "pqActiveObjects.h"
#include "pqCoreUtilities.h"
#include "pqServer.h"
#include "vtkSMProperty.h"
#include "vtkSMProxy.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMStringVectorProperty.h"

// Qt
#include <QAction>
#include <QDebug>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QIcon>
#include <QMessageBox>
#include <QProcess>
#include <QStringList>
#include <QWidget>

pqCubitToolBar::pqCubitToolBar(QWidget* parent)
  : Superclass("CUBIT Application", parent)
  , m_cubitPID(0)
{
  this->setObjectName("CUBIT Executable");

  QAction* launchAction = this->addAction(
    QIcon(":/icons/toolbar/cubit02.svg"), "Launch CUBIT", this, &pqCubitToolBar::onLaunch);
}

pqCubitToolBar::~pqCubitToolBar() = default;

void pqCubitToolBar::onLaunch()
{
  QWidget* mainWidget = pqCoreUtilities::mainWidget();

  // Have we been here before?
  if (m_cubitPID != 0)
  {
    QString text = "You have already started CUBIT from modelbuilder before."
                   " Do you want to continue anyway?";
    int choice = QMessageBox::question(mainWidget, "CUBIT Already Running?", text);
    if (choice != QMessageBox::Yes)
    {
      return;
    }
  }

  QString cubitPath;
  if (!this->getCubitPath(cubitPath))
  {
    return;
  }

  QString workspacePath;
  if (!this->getWorkspacePath(workspacePath))
  {
    return;
  }

  // Make sure workspace/cubit folder exists
  QDir workspaceDir(workspacePath);
  QDir workingDir(workspaceDir);
  if (!workingDir.cd("cubit"))
  {
    if (!workspaceDir.mkdir("cubit"))
    {
      QMessageBox::warning(
        mainWidget,
        "Cannot Create working Directory",
        "Error creating working directory " + workingDir.canonicalPath());
      return;
    }

    workingDir.setPath(workspaceDir.path());
    workingDir.cd("cubit");
  }

  // Start cubit as a detached process
  bool success = this->runCubit(cubitPath, workingDir.canonicalPath());
  if (!success)
  {
    QMessageBox::warning(
      mainWidget, "CUBIT LAUNCH FAILED", "The system was unable to launch CUBIT.");
    return;
  }
}

void pqCubitToolBar::askUserForPath(
  const QString& headline,
  const QString& details,
  QString& result) const
{
  QWidget* mainWidget = pqCoreUtilities::mainWidget();

  QMessageBox msgBox(mainWidget);
  msgBox.setText(headline);
  msgBox.setInformativeText(details);
  msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
  msgBox.setDefaultButton(QMessageBox::Yes);

  int msgRet = msgBox.exec();
  if (msgRet != QMessageBox::Yes)
  {
    result.clear();
    return;
  }

  QFileDialog fileDialog(mainWidget, "CUBIT executable");
  fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
  fileDialog.setFileMode(QFileDialog::ExistingFile);
  bool fileRet = fileDialog.exec();
  if (fileRet != QDialog::Accepted)
  {
    result.clear();
  }
  else
  {
    result = fileDialog.selectedFiles().at(0);
  }
}

bool pqCubitToolBar::checkCubitPath(const QString& path, QString& reason) const
{
  QFileInfo fileInfo(path);
  if (!fileInfo.exists())
  {
    reason = "File was not found at " + path + ".";
    return false;
  }

  if (!fileInfo.isExecutable())
  {
    reason = "The file is not executable: " + path + ".";
    return false;
  }

  if (!fileInfo.permission(QFileDevice::ExeUser))
  {
    reason = "The current user does not have permission to run " + path + ".";
    return false;
  }

  return true;
}

bool pqCubitToolBar::getCubitPath(QString& usePath) const
{
  // Get the CUBIT path from application settings
  QWidget* mainWidget = pqCoreUtilities::mainWidget();
  pqServer* server = pqActiveObjects::instance().activeServer();
  vtkSMProxy* proxy = server->proxyManager()->GetProxy("settings", "ACE3PSettings");
  if (!proxy)
  {
    QMessageBox::warning(
      mainWidget, "Internal Error", "Internal Error: Settings proxy for ACE3P not found.");
    return false;
  }

  vtkSMProperty* cubitProp = proxy->GetProperty("CubitPath");
  auto* cubitStringProp = vtkSMStringVectorProperty::SafeDownCast(cubitProp);
  if (!cubitStringProp)
  {
    QMessageBox::critical(
      mainWidget, "Internal Error", "Internal Error: CubitPath not found in ACE3P settings.");
    return false;
  }
  std::string cubitPath = cubitStringProp->GetElement(0);

  // Make sure the path is set to a valid executable
  bool done = false;
  QString path = QString::fromStdString(cubitPath);
  while (!done)
  {
    QString headline;
    QString details;

    if (path.isEmpty())
    {
      headline = "The application does not know where CUBIT is on your system.";
      details = "Do you want to set it now?";
      this->askUserForPath(headline, details, path);
      if (path.isEmpty())
      {
        break;
      }
    } // if (path empty)

    // Check path
    QString reason;
    if (this->checkCubitPath(path, reason))
    {
      done = true;
      break;
    }

    // (else)
    this->askUserForPath(reason, "Do you want to try again?", path);
    if (path.isEmpty())
    {
      break;
    }
  } // while

  if (path.isEmpty())
  {
    return false;
  }

  // (else)
  usePath = path;
  cubitPath = path.toStdString();

  // Update settings
  cubitStringProp->SetElement(0, cubitPath.c_str());
  proxy->UpdateVTKObjects();
  return true;
}

bool pqCubitToolBar::getWorkspacePath(QString& usePath) const
{
  // Get the Workspace path from application settings
  QWidget* mainWidget = pqCoreUtilities::mainWidget();
  pqServer* server = pqActiveObjects::instance().activeServer();
  vtkSMProxy* proxy = server->proxyManager()->GetProxy("settings", "SMTKSettings");
  if (!proxy)
  {
    QMessageBox::critical(
      mainWidget, "Internal Error", "Internal Error: Settings proxy for SMTK not found.");
    return false;
  }

  vtkSMProperty* folderProp = proxy->GetProperty("ProjectsRootFolder");
  auto* folderStringProp = vtkSMStringVectorProperty::SafeDownCast(folderProp);
  if (!folderStringProp)
  {
    QMessageBox::critical(
      mainWidget,
      "Internal Error",
      "Internal Error: ProjectsRootFolder not found in SMTK settings.");
    return false;
  }
  std::string folderPath = folderStringProp->GetElement(0);
  QString path = QString::fromStdString(folderPath);

  if (path.isEmpty())
  {
    QString headline = "The projects workspace folder has not been specified.";
    QString details = "This setting is required before you can run CUBIT."
                      " Do you want to set it now?";
    this->askUserForPath(headline, details, path);
    if (path.isEmpty())
    {
      return false;
    }
  } // if (path empty)

  // Check that path exists
  QDir projectsFolder(path);
  if (!projectsFolder.exists() && !projectsFolder.mkpath(path))
  {
    QString details = "The system could not create a folder at " + path;
    QMessageBox::warning(mainWidget, "Unable to create projects folder", details);
    return false;
  }

  // (else)
  usePath = path;

  // Update settings
  folderStringProp->SetElement(0, path.toStdString().c_str());
  proxy->UpdateVTKObjects();
  return true;
}

bool pqCubitToolBar::runCubit(const QString& path, const QString& workingDir)
{
  QProcess process;
  process.setProgram(path);
  QStringList args;
  args << "-workingdir" << workingDir;
  process.setArguments(args);
  process.setWorkingDirectory(workingDir); // backup
  qint64 pid;
  qInfo() << "Starting" << path;
  bool success = process.startDetached(&pid);
  if (success)
  {
    qInfo() << "Started" << path << "as process" << pid;
    qInfo() << "Working directory" << workingDir;
    m_cubitPID = pid;
  }
  return success;
}
