//=========================================================================
//  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 "smtk/simulation/ace3p/qt/qtJobsWidget.h"
#include "smtk/simulation/ace3p/qt/qtJobsModel.h"
#include "smtk/simulation/ace3p/qt/ui_qtJobsWidget.h"

#include "smtk/cumulus/jobspanel/cumulusproxy.h"
#include "smtk/cumulus/jobspanel/job.h"
#include "smtk/simulation/ace3p/Project.h"
#include "smtk/simulation/ace3p/qt/qtProjectRuntime.h"

#include <iostream>

#include <QAction>
#include <QClipboard>
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QVariant>

namespace smtk
{
namespace simulation
{
namespace ace3p
{

qtJobsWidget::qtJobsWidget(QWidget* parentWidget)
  : QWidget(parentWidget)
  , m_jobs_model(new qtJobsModel(this))
  , ui(new Ui::qtJobsWidget)
{
  // initialize the UI
  this->ui->setupUi(this);

  // setup jobs table
  this->ui->m_jobsTable->setModel(m_jobs_model);
  this->ui->m_jobsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
  this->ui->m_jobsTable->setSelectionMode(QAbstractItemView::SingleSelection);
  this->ui->m_jobsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
  this->ui->m_jobsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

  // we use the JobsModel to fill the details panel, but we only want a few fields in the table
  for (int i = 4; i < m_jobs_model->columnCount(); ++i)
  {
    this->ui->m_jobsTable->hideColumn(i);
  }

  // hide the jobs details panel when first opened
  this->ui->m_JobsDetails->hide();

  // connect selection model to details section
  auto selection = this->ui->m_jobsTable->selectionModel();
  QObject::connect(
    selection,
    &QItemSelectionModel::selectionChanged,
    this,
    &qtJobsWidget::toggleDetailsVisibility);

  // connect job name field to job model setData
  QObject::connect(
    this->ui->m_job_name_field, &QLineEdit::textEdited, this, &qtJobsWidget::jobNameChanged);

  QObject::connect(
    this->ui->m_notes_field, &QPlainTextEdit::textChanged, this, &qtJobsWidget::notesChanged);

  // Connect polling widgets to qtJobsModel
  QObject::connect(
    this->ui->m_updateStatusButton,
    &QPushButton::clicked,
    m_jobs_model,
    &qtJobsModel::updateStatus);

  QObject::connect(
    this->ui->m_enablePollingCheckBox,
    &QCheckBox::stateChanged,
    m_jobs_model,
    &qtJobsModel::enablePolling);

  QObject::connect(m_jobs_model, &qtJobsModel::pollingStateChanged, [this](bool polling) {
    QString text = polling ? "ON" : "Off";
    this->ui->m_pollingStateLabel->setText(text);
  });

  QObject::connect(
    this->ui->m_download_remote_dir, &QPushButton::clicked, this, &qtJobsWidget::downloadJob);

  QObject::connect(
    this->ui->m_remote_dir_button,
    &QPushButton::clicked,
    this,
    &qtJobsWidget::onNavigateRemoteClicked);

  QObject::connect(
    this->ui->m_input_dir_button,
    &QPushButton::clicked,
    this,
    &qtJobsWidget::onNavigateInputClicked);

  QObject::connect(
    this->ui->m_load_job_button, &QPushButton::clicked, this, &qtJobsWidget::onLoadJobClicked);

  // connect copy remote dir button to system clipboard
  QObject::connect(this->ui->m_copy_remote_path, &QPushButton::clicked, [this]() {
    QString path = this->ui->m_remote_dir->text();
    auto clipboard = QGuiApplication::clipboard();
    clipboard->setText(path);
    qInfo() << "Remote Directory path copied.";
  });

  // connect copy download dir button to system clipboard
  QObject::connect(this->ui->m_copy_download_path, &QPushButton::clicked, [this]() {
    QString path = this->ui->m_local_dir->text();
    auto clipboard = QGuiApplication::clipboard();
    clipboard->setText(path);
    qInfo() << "Download Directory path copied.";
  });

  // connect copy input dir button to system clipboard
  QObject::connect(this->ui->m_copy_input_path, &QPushButton::clicked, [this]() {
    QString path = this->ui->m_input_dir->text();
    auto clipboard = QGuiApplication::clipboard();
    clipboard->setText(path);
    qInfo() << "Remote Input Directory path copied.";
  });

  // Initialize polling widgets and tool buttons
  this->enablePollingWidgets();
  QSharedPointer<cumulus::CumulusProxy> cumulus = cumulus::CumulusProxy::instance();
  if (cumulus->isAuthenticated())
  {
    this->enableJobToolButtons();
  }
  else
  {
    this->enableJobToolButtons(false);
    QObject::connect(cumulus.get(), &cumulus::CumulusProxy::authenticationFinished, [this]() {
      this->enablePollingWidgets();

      QSharedPointer<cumulus::CumulusProxy> cumulus = cumulus::CumulusProxy::instance();
      bool enabled = cumulus->isAuthenticated();
      this->enableJobToolButtons(enabled);
    });
  }

  // Listen for job-downloaded signals
  QObject::connect(
    cumulus.get(),
    &cumulus::CumulusProxy::jobDownloaded,
    [this](cumulus::Job job, const QString& path) {
      QString jobId = job.id();
      this->onJobDownloaded(jobId);
    });
}

void qtJobsWidget::setProject(smtk::project::ProjectPtr project)
{
  if (project)
  {
    m_jobs_model->populateJobs(project);
    this->enablePollingWidgets();
  }
}

void qtJobsWidget::onJobAdded(const QString& cumulusJobId)
{
  m_jobs_model->onJobAdded(cumulusJobId);
  this->enablePollingWidgets();
  // Future: add argument to enableNERSCWidgets() that new job was added.
  // In that case we can presume that user is logged in and there is at least one job.
}

void qtJobsWidget::toggleDetailsVisibility(
  const QItemSelection& selected,
  const QItemSelection& deselected)
{
  // get the list of selected items
  auto selectedList = selected.indexes();

  // if the selection is empty, hide the details panel and return
  if (!selectedList.size())
  {
    this->ui->m_JobsDetails->hide();
    return;
  }

  // fill in all the fields in the details panel
  const int job = selectedList[0].row();
  QModelIndex index;
  QVariant fieldData;

  index = m_jobs_model->index(job, qtJobsModel::Machine);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_machine->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::Nodes);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_nodes->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::Status);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  QString statusEntry = fieldData.toString();
  this->ui->m_job_status->setText(statusEntry);

  index = m_jobs_model->index(job, qtJobsModel::AnalysisType);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_analysis_type->setText(fieldData.toString());

  if (fieldData.toString() == "ACDTool")
  {
    this->ui->m_input_dir->show();
    this->ui->m_input_dir_button->show();
    this->ui->m_input_dir_label->show();
    this->ui->m_copy_input_path->show();
  }
  else
  {
    this->ui->m_input_dir->hide();
    this->ui->m_input_dir_button->hide();
    this->ui->m_input_dir_label->hide();
    this->ui->m_copy_input_path->hide();
  }

  // check if we should enable the SLACTools loader button
  bool enableSTButton = ((fieldData.toString() == "Omega3P") || (fieldData.toString() == "S3P") ||
                         (fieldData.toString() == "T3P")) &&
    (statusEntry == "downloaded");
  this->ui->m_load_job_button->setEnabled(enableSTButton);

  index = m_jobs_model->index(job, qtJobsModel::Notes);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_notes_field->setPlainText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::RemoteDir);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_remote_dir->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::LocalDir);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_local_dir->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::InputDir);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_input_dir->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::Processes);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_processes->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::SlurmID);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_slurm_id->setText(fieldData.toString());

  index = m_jobs_model->index(job, qtJobsModel::JobName);
  fieldData = m_jobs_model->data(index, Qt::DisplayRole);
  this->ui->m_job_name_field->setText(fieldData.toString());

  this->ui->m_JobsDetails->show();
}

void qtJobsWidget::jobNameChanged(const QString& text)
{
  // get index of current job
  auto selection = this->ui->m_jobsTable->selectionModel();
  QModelIndexList rowList = selection->selectedRows(qtJobsModel::JobName);
  this->m_jobs_model->setData(rowList[0], text);
}

void qtJobsWidget::notesChanged()
{
  // get index of current job
  auto selection = this->ui->m_jobsTable->selectionModel();
  QModelIndexList rowList = selection->selectedRows(qtJobsModel::Notes);
  this->m_jobs_model->setData(rowList[0], this->ui->m_notes_field->toPlainText());
}

void qtJobsWidget::pollingCheckBoxStateChanged(bool checked)
{
  m_jobs_model->enablePolling(checked);
  this->ui->m_updateStatusButton->setEnabled(!checked);
}

bool qtJobsWidget::enablePollingWidgets()
{
  bool enable = false;

  // Enable widgets if model has data and user logged into cumulus
  if (m_jobs_model->rowCount() > 0)
  {
    // Check if user is signed in
    QSharedPointer<cumulus::CumulusProxy> cumulus = cumulus::CumulusProxy::instance();
    enable = cumulus->isAuthenticated();
  }

  // Set polling widget states
  this->ui->m_enablePollingCheckBox->setEnabled(enable);
  this->ui->m_pollingStateLabel->setEnabled(enable);
  this->ui->m_updateStatusButton->setEnabled(
    enable && !this->ui->m_enablePollingCheckBox->isChecked());
  if (!enable)
  {
    this->ui->m_pollingStateLabel->setText("Off");
  }

  return enable;
}

void qtJobsWidget::enableJobToolButtons(bool enable)
{
  // set shortcut button states
  this->ui->m_input_dir_button->setEnabled(enable);
  this->ui->m_download_remote_dir->setEnabled(enable);
  this->ui->m_remote_dir_button->setEnabled(enable);
}

void qtJobsWidget::onProjectClosed()
{
  this->m_jobs_model->clearJobs();
  this->ui->m_JobsDetails->hide();
  this->enablePollingWidgets();
}

void qtJobsWidget::onNavigateRemoteClicked()
{
  QString dir = this->ui->m_remote_dir->text();
  emit requestNavigateDir(dir);
}

void qtJobsWidget::onNavigateInputClicked()
{
  QString dir = this->ui->m_input_dir->text();
  emit requestNavigateDir(dir);
}
void qtJobsWidget::onLoadJobClicked()
{
  QString dir = this->ui->m_local_dir->text();
  emit requestLoadJob(dir);
}

void qtJobsWidget::downloadJob()
{
  auto selection = this->ui->m_jobsTable->selectionModel();
  int jobInd = selection->selectedIndexes()[0].row();

  auto index = m_jobs_model->index(jobInd, qtJobsModel::CumulusID);
  QString cumulus_id = m_jobs_model->data(index).toString();

  index = m_jobs_model->index(jobInd, qtJobsModel::JobName);
  QString name = m_jobs_model->data(index).toString();

  index = m_jobs_model->index(jobInd, qtJobsModel::Status);
  QString status = m_jobs_model->data(index).toString();

  index = m_jobs_model->index(jobInd, qtJobsModel::CumulusFolderID);
  QString remote_dir = m_jobs_model->data(index).toString();

  index = m_jobs_model->index(jobInd, qtJobsModel::Machine);
  QString machine = m_jobs_model->data(index).toString();

  index = m_jobs_model->index(jobInd, qtJobsModel::SlurmID);
  QString slurm_id = m_jobs_model->data(index).toString();

  cumulus::Job cumulusJob(cumulus_id, name, status, { remote_dir }, machine, slurm_id);
  qInfo() << "downloadJob " << remote_dir;

  // Setup local folder for download
  index = m_jobs_model->index(jobInd, qtJobsModel::LocalDir);
  QString local_path = m_jobs_model->data(index, Qt::DisplayRole).toString();
  QString download_path = local_path + "/download";
  QDir dir(download_path);

  // check if the job has been downloaded. present a popup if so
  if (dir.exists())
  {
    QMessageBox msgBox;
    msgBox.setText("This job has already been downloaded.");
    msgBox.setInformativeText(
      "Do you want to download this job? This may overwrite existing files.");
    QPushButton* continueButton = msgBox.addButton(tr("Continue"), QMessageBox::AcceptRole);
    msgBox.setDefaultButton(continueButton);
    QPushButton* cancelButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole);

    msgBox.exec();

    if (msgBox.clickedButton() == cancelButton)
    {
      qInfo() << "Job download canceled.";
      return;
    }
  }

  if (!dir.mkpath(download_path))
  {
    qWarning() << "Failed to create directory" << download_path;
    return;
  }
  dir.setPath(download_path);

  // Request download
  auto cumulusProxy = cumulus::CumulusProxy::instance();
  cumulusProxy->downloadJob(download_path, cumulusJob);
}

void qtJobsWidget::onJobDownloaded(const QString& jobId)
{
  auto baseProject = qtProjectRuntime::instance()->project();
  auto project = std::dynamic_pointer_cast<Project>(baseProject);
  if (project == nullptr)
  {
    qWarning() << "No project loaded for download job" << jobId;
    return;
  }

  const JobsManifest& manifest = project->jobsManifest();
  int index = manifest.getIndexByCumulusID(jobId.toStdString());
  if (index < 0)
  {
    qWarning() << "Job" << jobId.toStdString().c_str() << "not found in project"
               << project->name().c_str();
    return;
  }

  // Update jobs manifest
  project->setJobRecordField(index, "status", "downloaded");
  project->writeJobsManifest();

  // Check if the job is currently selected
  auto selection = this->ui->m_jobsTable->selectionModel();
  QModelIndexList rowList = selection->selectedRows(qtJobsModel::CumulusID);
  if (rowList.isEmpty())
  {
    return;
  }
  QString selectedId = rowList[0].data().toString();
  if (selectedId != jobId)
  {
    return;
  }

  // notify model to update
  QModelIndex qIndex = this->m_jobs_model->index(index, qtJobsModel::Status);
  this->ui->m_jobsTable->update(qIndex);

  // Check analysis to see if we should enable SLACTools button
  std::string analysis;
  manifest.getField(index, "analysis", analysis);
  bool enableSTButton = ((analysis == "Omega3P") || (analysis == "S3P") || (analysis == "T3P"));
  this->ui->m_load_job_button->setEnabled(enableSTButton);
}

} // namespace ace3p
} // namespace simulation
} // namespace smtk
