/**
 * \file /core/condor_process/condorprocess.cpp
 * \date 12/11/2020
 * \author Thomas Renne
 * \brief handles condor jobs
 */

/*******************************************************************************
 * Copyright (c) 2017 Thomas Renne <thomas.renne@e.email>.
 *
 * This file is part of i2MassChroQ.
 *
 *     i2MassChroQ is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     i2MassChroQ is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with i2MassChroQ.  If not, see <http://www.gnu.org/licenses/>.
 *
 ******************************************************************************/
#include "condorprocess.h"
#include <QSettings>
#include <QThread>
#include <pappsomspp/core/pappsoexception.h>
#include <pappsomspp/core/exception/exceptioninterrupted.h>

CondorProcess::CondorProcess(MainWindow *p_main_window,
                             WorkMonitorInterface *p_monitor,
                             QString app_run)
{
  mp_main_window = p_main_window;
  mp_monitor     = p_monitor;
  m_app_run      = app_run;

  QSettings settings;
  m_condorSubmitCommand =
    settings.value("condor/submit", "/usr/bin/condor_submit").toString();
  m_condorQCommand =
    settings.value("condor/condor_q", "/usr/bin/condor_q").toString();
  m_condorRmCommand =
    settings.value("condor/condor_rm", "/usr/bin/condor_rm").toString();

  settings.setValue("condor/tmp_dir_autoremove",
                    settings.value("condor/tmp_dir_autoremove", true));
  settings.setValue("condor/submit",
                    settings.value("condor/submit", "/usr/bin/condor_submit"));
  settings.setValue("condor/condor_q",
                    settings.value("condor/condor_q", "/usr/bin/condor_q"));
  settings.setValue("condor/condor_rm",
                    settings.value("condor/condor_rm", "/usr/bin/condor_rm"));
}

CondorProcess::~CondorProcess()
{
  if(mp_tmpDir != nullptr)
    {
      delete mp_tmpDir;
    }
}

bool
CondorProcess::shouldIstop()
{
  if(mp_main_window->stopWorkerThread())
    {
      return true;
    }
  return false;
}

void
CondorProcess::prepareTemporaryDirectory()
{
  // /gorgone/pappso/tmp
  QSettings settings;
  QString condor_tmp_dir =
    QString("%1/i2masschroq")
      .arg(settings.value("condor/tmp_dir", "/tmp").toString());

  if(mp_tmpDir != nullptr)
    {
      delete mp_tmpDir;
      mp_tmpDir = nullptr;
    }
  mp_tmpDir = new QTemporaryDir(condor_tmp_dir);
  mp_tmpDir->setAutoRemove(
    settings.value("condor/tmp_dir_autoremove", true).toBool());

  if(!mp_tmpDir->isValid())
    {
      // dir.path() returns the unique directory path
      throw pappso::PappsoException(
        QObject::tr("problem creating condor temporary directory in %1\n")
          .arg(condor_tmp_dir));
    }
}

void
CondorProcess::parseCondorJobNumber(QString condor_job)
{
  // Submitting job(s)...
  // 3 job(s) submitted to cluster 3.
  QRegularExpression txt_submit(
    "([0-9]*) job\\(s\\) submitted to cluster ([0-9]*).");
  QRegularExpressionMatch match = txt_submit.match(condor_job);

  if(match.hasMatch())
    {
      m_condorClusterNumber = match.captured(2).toUInt();
      m_condorJobSize       = match.captured(1).toUInt();
      m_condoQueueParser.setCondorJobSize(m_condorJobSize);
    }
  else
    {
      throw pappso::PappsoException(
        QObject::tr("unable to find HTCondor job numbers in %1")
          .arg(condor_job));
    }
}

void
CondorProcess::surveyCondorJob()
{
  // condor is running job : we have to survey condor job using "condor_q -xml
  // m_condorClusterNumber"
  bool keep_running = true;
  while(keep_running)
    {
      QThread::msleep(m_condorStatusTimerMillisecond);
      getCondorJobState();
      displayCondorJobStatus();
      mp_monitor->message(
        QObject::tr(
          "%1 is running in condor cluster %2\n%3 on %4 jobs completed")
          .arg(m_app_run)
          .arg(m_condorClusterNumber)
          .arg(m_condorCompletedJobs)
          .arg(m_condorJobSize),
        m_condorCompletedJobs);

      if(m_condorCompletedJobs == m_condorJobSize)
        keep_running = false;

      if(shouldIstop())
        {
          keep_running = false;
          // condor_rm
          condorRemoveJob();
          throw pappso::ExceptionInterrupted(
            QObject::tr("HTCondor %1 jobs stopped by the user").arg(m_app_run));
        }
    }
}

void
CondorProcess::getCondorJobState()
{
  QStringList arguments;
  arguments << "-xml" << QString("%1").arg(m_condorClusterNumber);
  QString condor_q_output;

  try
    {
      QProcess condor_q_process;
      qDebug() << " command " << m_condorQCommand << " " << arguments.join(" ");
      condor_q_process.start(m_condorQCommand, arguments);

      if(!condor_q_process.waitForStarted())
        {
          QString err = QObject::tr(
                          "Could not start condor_q process "
                          "'%1' with arguments '%2': %3")
                          .arg(m_condorQCommand)
                          .arg(arguments.join(QStringLiteral(" ")))
                          .arg(condor_q_process.errorString());
          throw pappso::PappsoException(err);
        }

      if(!condor_q_process.waitForFinished(m_maxTimeMs))
        {
          throw pappso::PappsoException(
            QObject::tr("HTCondor condor_q process failed to finish"));
        }

      QString condor_error = condor_q_process.readAllStandardError();
      if(!condor_error.isEmpty())
        {
          qDebug() << " readAllStandardError " << condor_error;
          throw pappso::PappsoException(
            QObject::tr("HTCondor condor_q process failed :\n%1")
              .arg(condor_error));
        }

      condor_q_output = condor_q_process.readAllStandardOutput();
      if(condor_q_output.isEmpty())
        {
          throw pappso::PappsoException(
            QObject::tr("HTCondor condor_q process failed :\nThe output of the "
                        "command %1 is empty")
              .arg(m_condorQCommand));
        }
    }
  catch(pappso::PappsoException &exception_pappso)
    {
      throw pappso::PappsoException(
        QObject::tr("error watching condor status :\n%1")
          .arg(exception_pappso.qwhat()));
    }
  catch(std::exception &exception_std)
    {
      throw pappso::PappsoException(
        QObject::tr("error watching condor status (std::exception):\n%1")
          .arg(exception_std.what()));
    }
  // Submitting job(s).\n1 job(s) submitted to cluster 29.\n
  parseCondorQueue(condor_q_output);
}

void
CondorProcess::condorRemoveJob()
{
  QStringList arguments;
  arguments << QString("%1").arg(m_condorClusterNumber);
  QString condor_rm_output;

  try
    {
      QProcess condor_rm_process;
      qDebug() << m_condorRmCommand << " " << arguments.join(" ");
      condor_rm_process.start(m_condorRmCommand, arguments);

      if(!condor_rm_process.waitForStarted())
        {
          QString err = QObject::tr(
                          "Could not start condor_rm process "
                          "'%1' with arguments '%2': %3")
                          .arg(m_condorQCommand)
                          .arg(arguments.join(QStringLiteral(" ")))
                          .arg(condor_rm_process.errorString());
          throw pappso::PappsoException(err);
        }

      if(!condor_rm_process.waitForFinished(m_maxTimeMs))
        {
          throw pappso::PappsoException(
            QObject::tr("HTCondor condor_rm process failed to finish"));
        }

      QString condor_rm_error = condor_rm_process.readAllStandardError();
      if(!condor_rm_error.isEmpty())
        {
          qDebug() << condor_rm_error;
          throw pappso::PappsoException(
            QObject::tr("HTCondor condor_q process failed :\n%1")
              .arg(condor_rm_error));
        }

      condor_rm_output = condor_rm_process.readAllStandardOutput();
    }
  catch(pappso::PappsoException &exception_pappso)
    {
      throw pappso::PappsoException(
        QObject::tr("error removing condor jobs :\n%1")
          .arg(exception_pappso.qwhat()));
    }
  catch(std::exception &exception_std)
    {
      throw pappso::PappsoException(
        QObject::tr("error removing condor jobs (std::exception):\n%1")
          .arg(exception_std.what()));
    }
  if(condor_rm_output.isEmpty())
    {
      throw pappso::PappsoException(
        QObject::tr("HTCondor condor_rm process failed :The output of the "
                    "command %1 is empty")
          .arg(m_condorRmCommand));
    }

  qDebug() << " condor_rm command job done on " << condor_rm_output;
}

void
CondorProcess::parseCondorQueue(QString &condor_q_xml)
{
  try
    {

      if(m_condoQueueParser.read(condor_q_xml))
        {
          m_condorCompletedJobs =
            m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Completed);
        }
      else
        {
          throw pappso::PappsoException(
            QObject::tr("Error reading condor_q xml string :\n %1\n%2")
              .arg(m_condoQueueParser.errorString())
              .arg(condor_q_xml));
        }
    }
  catch(pappso::PappsoException &exception_pappso)
    {
      throw pappso::PappsoException(
        QObject::tr("error parsing condor queue :\n%1")
          .arg(exception_pappso.qwhat()));
    }
  catch(std::exception &exception_std)
    {
      throw pappso::PappsoException(
        QObject::tr("error parsing condor queue (std::exception):\n%1")
          .arg(exception_std.what()));
    }
}

std::size_t
CondorProcess::getCondorJobSize() const
{
  return m_condorJobSize;
}

void
CondorProcess::displayCondorJobStatus() const
{
  QString status_message =
    QString(
      "%1 unexpanded jobs\n%2 idle jobs\n%3 running jobs\n%4 removed "
      "jobs\n%5 completed jobs\n%6 held jobs\n%7 submission errors")
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Unexpanded))
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Idle))
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Running))
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Removed))
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Completed))
      .arg(m_condoQueueParser.countCondorJobStatus(CondorJobStatus::Held))
      .arg(m_condoQueueParser.countCondorJobStatus(
        CondorJobStatus::Submission_err));

  mp_monitor->setText(status_message);
}
