
//=========================================================================
//  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/qtNewtJobTracker.h"
#include "smtk/newt/qtNewtInterface.h"
#include "smtk/simulation/ace3p/JobsManifest.h"

#include "QtGlobal"
#include <QDebug>
#include <QNetworkReply>
#include <QSet>

#include "nlohmann/json.hpp"

#ifndef NDEBUG
#include <iostream>
#endif

namespace
{
const QString MACHINE("cori");
// Macro for printing messages to stdout for debug builds only
#ifndef NDEBUG
#define DebugMessageMacro(msg)                                                                     \
  do                                                                                               \
  {                                                                                                \
    std::cout << __FILE__ << ":" << __LINE__ << " " << msg << std::endl;                           \
  } while (0)
#else
#define DebugMessageMacro(msg)
#endif

QSet<QString> RunningState = { "ca", "cg", "r", "s", "to" };
QSet<QString> ErrorState = { "f", "nf" };
QSet<QString> CompleteState = { "cd", "pr" };
QSet<QString> QueuedState = { "cf", "pd" };

QString slurmState2Status(const QString& slurmState)
{
  QString ss = slurmState.toLower();
  if (CompleteState.contains(ss))
  {
    return "complete";
  }
  else if (RunningState.contains(ss))
  {
    return "running";
  }
  else if (QueuedState.contains(ss))
  {
    return "queued";
  }
  else if (ErrorState.contains(ss))
  {
    return "error";
  }
  // (else)
  return "unrecognized: " + slurmState;
}

} // namespace

namespace smtk
{
namespace simulation
{
namespace ace3p
{
//-----------------------------------------------------------------------------
qtNewtJobTracker::qtNewtJobTracker(QObject* parent)
  : Superclass(parent)
  , m_newt(newt::qtNewtInterface::instance())
{
  if (this->m_newt->isLoggedIn())
  {
    this->onLogin();
  }
  else
  {
    QObject::connect(
      this->m_newt, &newt::qtNewtInterface::loginComplete, this, &qtNewtJobTracker::onLogin);
  }

  m_internalTimer->m_timer->callOnTimeout(this, &qtNewtJobTracker::onTimerEvent);
}

void qtNewtJobTracker::onLogin()
{
  this->setNextPoll(true);
}

bool qtNewtJobTracker::pollOnce()
{
  if (!this->m_newt->isLoggedIn())
  {
    qWarning() << "Cannot poll jobs because not signed into NERSC.";
    return false;
  }

  return qtAbstractJobTracker::pollOnce();
}

bool qtNewtJobTracker::setNextPoll(bool pollNow)
{
  if (!this->m_newt->isLoggedIn())
  {
    return false;
  }

  return qtAbstractJobTracker::setNextPoll(pollNow);
}

void qtNewtJobTracker::requestJob(int index)
{
  m_internalTimer->m_busy = true;

  QString jobId = this->m_jobList[index];
  m_internalTimer->m_pollingIndex = index;
  QNetworkReply* networkReply = m_newt->requestJob(MACHINE, jobId);
  QObject::connect(networkReply, &QNetworkReply::finished, this, &qtNewtJobTracker::onNewtReply);
}

void qtNewtJobTracker::onNewtReply()
{
  // Check that instance hasn't been cleared
  if (!m_internalTimer->m_busy)
  {
    return;
  }

  auto networkReply = qobject_cast<QNetworkReply*>(this->sender());
  assert(networkReply != nullptr);
  QByteArray bytes = networkReply->readAll();
  nlohmann::json jResponse;
  try
  {
    jResponse = nlohmann::json::parse(bytes.constData());
  }
  catch (std::exception const& /* ex */)
  {
    QString errMessage;
    QTextStream qs(&errMessage);
    qs << "Error parsing Newt reply: " << bytes.constData();
    qWarning() << __FILE__ << __LINE__ << errMessage;
    emit error(errMessage);
    networkReply->deleteLater();
    return;
  }

  if (networkReply->error() && !jResponse.is_object())
  {
    // if the network errored our, and the json output is not an object
    // then we probably have a real error
    qWarning() << __FILE__ << __LINE__ << ":" << networkReply->errorString();
    emit error(networkReply->errorString());
    m_internalTimer->m_busy = false;
  }
  else if (!networkReply->error() && !jResponse.is_object())
  {
    // no error, but the
  }
  else
  {
    QString status, jobId;
    qint64 startTimeStamp = 0L;

    // Newt gives us elapsed time, not start timestamp.
    // so we subtract runtime from current time and live with the small error
    auto iter = jResponse.find("timeuse");
    if (iter != jResponse.end())
    {
      const qint64 timeuse = static_cast<qint64>(std::stod((*iter).get<std::string>()));
      startTimeStamp = QDateTime::currentSecsSinceEpoch() - timeuse;
    }

    iter = jResponse.find("status");
    if (iter != jResponse.end())
    {
      QString slurmState = (*iter).get<std::string>().c_str();
      status = slurmState2Status(slurmState);
    }

    iter = jResponse.find("jobid");
    if (iter != jResponse.end())
    {
      jobId = (*iter).get<std::string>().c_str();
      // emit job information
      emit this->jobStatus(jobId, status, jobId, startTimeStamp);
    }

    // Check if job status is a final state, remove from the list.
    if (JobsManifest::isJobFinished(status.toStdString()))
    {
      this->m_jobList.removeAt(m_internalTimer->m_pollingIndex);
    }

    // Check for more jobs
    m_internalTimer->m_pollingIndex -= 1;
    if (m_internalTimer->m_pollingIndex >= 0)
    {
      // Schedule next request
      this->requestJob(m_internalTimer->m_pollingIndex);
    }
    else
    {
      // Schedule next polling interval
      this->setNextPoll();
    }
  }
  networkReply->deleteLater();
}

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