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

// Local includes
#include "smtk/simulation/ace3p/Metadata.h"
#include "smtk/simulation/ace3p/operations/Create.h"
#include "smtk/simulation/ace3p/qt/qtProjectRuntime.h"
// #include "smtk/simulation/ace3p/utility/ModelUtils.h"

// SMTK
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DirectoryItem.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/VoidItem.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#include "smtk/extension/qt/qtOperationDialog.h"
#include "smtk/operation/Manager.h"
#include "smtk/operation/Operation.h"
#include "smtk/project/Project.h"

// ParaView
#include "pqActiveObjects.h"
#include "pqCoreUtilities.h"
#include "pqFileDialog.h"
#include "pqPresetDialog.h"
#include "pqServer.h"

// VTK
#include <vtk_jsoncpp.h> // for Json::Value

#include <QDebug>
#include <QFileInfo>
#include <QMessageBox>
#include <QPushButton>
#include <QSharedPointer>

#include <cassert>
#include <sstream>
#include <string>
#include <vector>

namespace
{
const std::string SurfacePaletteName = "Brewer Qualitative Dark2";
const std::string VolumePaletteName = "Brewer Qualitative Pastel2";

const int OP_SUCCEEDED = static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED);
}

class pqACE3PNewBehaviorInternals
{
public:
  pqACE3PNewBehaviorInternals()
  {
    // this->loadPalette(m_surfacePalette, SurfacePaletteName);
    // this->loadPalette(m_volumePalette, VolumePaletteName);
  }
  ~pqACE3PNewBehaviorInternals() = default;

  // void assignColors(smtk::model::ResourcePtr modelResource) const
  // {
  //   m_modelUtils.assignColors(modelResource, smtk::model::FACE, m_surfacePalette);
  //   m_modelUtils.assignColors(modelResource, smtk::model::VOLUME, m_volumePalette);
  // }

protected:
  // smtk::simulation::ace3p::ModelUtils m_modelUtils;
  std::vector<std::string> m_surfacePalette;
  std::vector<std::string> m_volumePalette;

  // void loadPalette(std::vector<std::string>& palette, const std::string& paletteName)
  // {
  //   // Grab color palettes from pqPresetDialog
  //   pqPresetDialog dialog;
  //   dialog.setCurrentPreset(paletteName.c_str());
  //
  //   const Json::Value preset = dialog.currentPreset();
  //   assert(preset.isMember("IndexedColors"));
  //
  //   palette.clear();
  //   const Json::Value& jsonColors(preset["IndexedColors"]);
  //   Json::ArrayIndex numColors = jsonColors.size() / 3;
  //   for (Json::ArrayIndex cc = 0; cc < numColors; ++cc)
  //   {
  //     std::ostringstream colorStr;
  //     colorStr << "#";
  //     for (int cm = 0; cm < 3; ++cm)
  //     {
  //       int val = static_cast<int>(jsonColors[3 * cc + cm].asDouble() * 255.0);
  //       colorStr << std::setfill('0') << std::setw(2) << std::hex << val;
  //     }
  //     palette.push_back(colorStr.str());
  //   }
  // }
};

//-----------------------------------------------------------------------------
pqACE3PNewReaction::pqACE3PNewReaction(QAction* parentObject)
  : Superclass(parentObject)
{
}

//-----------------------------------------------------------------------------
void pqACE3PNewReaction::onTriggered()
{
  pqACE3PNewBehavior::instance()->newProject();
}


//-----------------------------------------------------------------------------
static pqACE3PNewBehavior* g_instance = nullptr;

//-----------------------------------------------------------------------------
pqACE3PNewBehavior::pqACE3PNewBehavior(QObject* parent)
  : Superclass(parent)
{
  this->Internals = new pqACE3PNewBehaviorInternals();
}

//-----------------------------------------------------------------------------
pqACE3PNewBehavior* pqACE3PNewBehavior::instance(QObject* parent)
{
  if (!g_instance)
  {
    g_instance = new pqACE3PNewBehavior(parent);
  }

  if (g_instance->parent() == nullptr && parent)
  {
    g_instance->setParent(parent);
  }

  return g_instance;
}

//-----------------------------------------------------------------------------
pqACE3PNewBehavior::~pqACE3PNewBehavior()
{
  if (g_instance == this)
  {
    delete this->Internals;
    g_instance = nullptr;
  }

  QObject::disconnect(this);
}

//-----------------------------------------------------------------------------
void pqACE3PNewBehavior::newProject()
{
  // Todo abort if qtProjectRuntime project is already set.
  // Access the active server
  pqServer* server = pqActiveObjects::instance().activeServer();
  pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
  auto opManager = wrapper->smtkOperationManager();

  // Instantiate the Create operator
  auto createOp = opManager->create<smtk::simulation::ace3p::Create>();
  assert(createOp != nullptr);

  // Initialize confirmation pop-up dialog in case the selected directory is non-empty
  QSharedPointer<QMessageBox> confirmDialog =
    QSharedPointer<QMessageBox>(new QMessageBox(pqCoreUtilities::mainWidget()));
  confirmDialog->setWindowTitle("OK to Delete Directory Contents?");
  confirmDialog->setText("The contents of this directory will be deleted. Continue?");
  const char* info =
    "You have selected an existing directory that contains one or more files or subdirectories."
    " Before creating the new project, the current contents of that directory will be deleted "
    "permanently."
    " Click \"Continue\" if you wish to delete the current contents and create a new project.\n";
  confirmDialog->setInformativeText(info);
  auto yesButton = confirmDialog->addButton("Continue", QMessageBox::AcceptRole);
  auto retryButton = confirmDialog->addButton("Change Directory", QMessageBox::ResetRole);
  auto noButton = confirmDialog->addButton("Cancel", QMessageBox::RejectRole);
  confirmDialog->setDefaultButton(QMessageBox::No);

  // Todo get optional start directory from settings
  QString workspaceFolder = QDir::currentPath();

  std::unique_ptr<pqFileDialog> fileDialog;
  const QString fileDialogTitle("Select or Create New Project Directory");
  while (true)
  {
    // Must reinitialize file dialog each iteration in order to clear selected files
    fileDialog.reset();
    fileDialog = std::unique_ptr<pqFileDialog>(
      new pqFileDialog(server, pqCoreUtilities::mainWidget(), fileDialogTitle, workspaceFolder));
    fileDialog->setFileMode(pqFileDialog::Directory);

    if (fileDialog->exec() != QDialog::Accepted)
    {
      return;
    }
    auto fileName = fileDialog->getSelectedFiles()[0];

    // Check if selected directory exists (and is non-empty) or not
    QDir filePath(fileName);
    // Clear the directory if user confirms so
    if (filePath.exists() && !filePath.isEmpty())
    {
      QString text;
      QTextStream qs(&text);
      qs << "The contents of directory \"" << filePath.dirName() << "\" will be deleted. Continue?";
      confirmDialog->setText(text);

      confirmDialog->exec();
      if (confirmDialog->clickedButton() == yesButton)
      {
        qDebug() << "Deleting contents of" << filePath.dirName();
        if (!filePath.removeRecursively())
        {
          QMessageBox::warning(pqCoreUtilities::mainWidget(), "Failed to delete directory contents",
            "The application could not delete the directory contents.", QMessageBox::Close);
          return;
        }
        if (!filePath.mkdir(filePath.absolutePath()))
        {
          QMessageBox::warning(pqCoreUtilities::mainWidget(), "Failed to create directory",
            "The application could not create the directory.", QMessageBox::Close);
          return;
        }
      }
      else if (confirmDialog->clickedButton() == retryButton)
      {
        fileDialog->selectFile(workspaceFolder);
        continue;
      }
      else // (noButton)
      {
        return;
      }
    } // if

    // Get the separator ('/' for Unix and '\' for Windows)
    const QChar separator = filePath.separator();
    // Assign the parent and subdirectories
    // QString projectFolder = fileName.section(separator, -1);
    workspaceFolder = fileName.section(separator, 0, -2); // for next loop

    auto folderItem = createOp->parameters()->findDirectory("location");
    assert(folderItem);
    folderItem->setValue(0, filePath.absolutePath().toStdString());
    createOp->parameters()->findVoid("overwrite")->setIsEnabled(true);
    break;
  } // while

  // For development, set the optional "simulation-template" item to the default path
  QDir workflowsDirectory(smtk::simulation::ace3p::Metadata::WORKFLOWS_DIRECTORY.c_str());
  std::string sbtLocation = workflowsDirectory.absoluteFilePath("ACE3P.sbt").toStdString();
  auto sbtItem = createOp->parameters()->findFile("simulation-template");
  sbtItem->setValue(sbtLocation);

  QSharedPointer<smtk::extension::qtOperationDialog> dialog =
    QSharedPointer<smtk::extension::qtOperationDialog>(
      new smtk::extension::qtOperationDialog(createOp, wrapper->smtkResourceManager(),
        wrapper->smtkViewManager(), pqCoreUtilities::mainWidget()));
  dialog->setObjectName("NewProjectDialog");
  dialog->setWindowTitle("Specify New Project");
  QObject::connect(dialog.get(), &smtk::extension::qtOperationDialog::operationExecuted,
    [this](const smtk::operation::Operation::Result& result) {
      int outcome = result->findInt("outcome")->value();
      if (outcome == OP_SUCCEEDED)
      {
        smtk::attribute::ResourceItemPtr projectItem = result->findResource("resource");
        auto resource = projectItem->value();
        assert(resource != nullptr);

        auto project = std::dynamic_pointer_cast<smtk::project::Project>(resource);
        assert(project != nullptr);

        smtk::simulation::ace3p::qtProjectRuntime::instance()->setProject(project);
        emit this->projectCreated(project);
        return;
      }

      // Todo popup error message

    });
  dialog->exec();
} // newProject()
