/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program 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.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QDebug>
#include <QThread>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QTimer>

/////////////////////// QCustomPlot


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "TaskMonitorCompositeWidget.hpp"
#include "ui_TaskMonitorCompositeWidget.h"

namespace MsXpS
{
namespace MineXpert
{

TaskMonitorCompositeWidget::TaskMonitorCompositeWidget(QWidget *parent_p)
  : QWidget(parent_p), m_ui(new ::Ui::TaskMonitorCompositeWidget)
{
  // By essence, the parent will be the task monitor window.

  if(parent_p == nullptr)
    qFatal() << "parent cannot be nullptr";


  setupWindow();
}

TaskMonitorCompositeWidget::TaskMonitorCompositeWidget(QWidget *parent_p,
                                                       const QColor &color)
  : QWidget(parent_p),
    m_color(color),
    m_ui(new ::Ui::TaskMonitorCompositeWidget)
{
  // By essence, the parent will be the task monitor window.

  if(parent_p == nullptr)
    qFatal() << "parent cannot be nullptr";

  setupWindow();
}

TaskMonitorCompositeWidget::~TaskMonitorCompositeWidget()
{
  // qDebug();
}

void
TaskMonitorCompositeWidget::setupWindow()
{
  m_ui->setupUi(this);

  connect(m_ui->cancelTaskPushButton,
          &QPushButton::clicked,
          this,
          &TaskMonitorCompositeWidget::cancelTask);
}

void
TaskMonitorCompositeWidget::setColor(const QColor &color)
{
  m_color = color;

  setAutoFillBackground(true);

  QPalette palette;
  palette.setColor(QPalette::Window, color);
  palette.setColor(QPalette::WindowText, color);

  m_ui->msRunIdLabel->setAutoFillBackground(true);
  m_ui->msRunIdLabel->setPalette(palette);
  m_ui->msRunIdLabel->setText("ms run id");
}

const QColor &
TaskMonitorCompositeWidget::getColor() const
{
  return m_color;
}

void
TaskMonitorCompositeWidget::makeMassDataIntegratorConnections(
  MassDataIntegrator *mass_data_integrator_p)
{

  // The integrator might want to set the min/max values of the progress bar
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setupProgressBarSignal,
          this,
          &TaskMonitorCompositeWidget::setupProgressBar);

  // The integrator might want to set the min value of the progress bar,
  // typically if it needs to restart a new looping computation.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setProgressBarMinValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMinValue);

  // The integrator informs by signals of the total number of spectra to be
  // loaded.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMaxValue);

  // The integrator informs by signals of the total number of spectra to be
  // loaded.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::incrementProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::incrementProgressBarMaxValue);

  // The integrator informs by signals of the number of added mass spectra.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setProgressBarCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarCurrentValue);

  // The integrator informs by signals of the number of added mass spectra.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::incrementProgressBarCurrentValueSignal,
          [this](double increment) {
            this->setProgressBarCurrentValue(m_currentValue += increment);
          });

  // The integrator informs by signals of the loading process with informative
  // text.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setTaskDescriptionTextSignal,
          this,
          &TaskMonitorCompositeWidget::setTaskDescriptionText);

  // The integrator informs by signals of the loading process with informative
  // text.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::setStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusText);

  // In some cases, we want to just append new informative text without erasing
  // the previous one.
  connect(mass_data_integrator_p,
          &MassDataIntegrator::appendStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::appendStatusText);

  connect(mass_data_integrator_p,
          &MassDataIntegrator::setStatusTextAndCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusTextAndCurrentValue);

  connect(
    mass_data_integrator_p,
    &MassDataIntegrator::incrementProgressBarCurrentValueAndSetStatusTextSignal,
    this,
    &TaskMonitorCompositeWidget::
      incrementProgressBarCurrentValueAndSetStatusText);

  connect(mass_data_integrator_p,
          &MassDataIntegrator::lockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::lock);

  connect(mass_data_integrator_p,
          &MassDataIntegrator::unlockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::unlock);
}

void
TaskMonitorCompositeWidget::makeMassDataIntegratorConnections(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p)
{

  // The integrator might want to set the min/max values of the progress bar
  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::setupProgressBarSignal,
    this,
    &TaskMonitorCompositeWidget::setupProgressBar);

  // The integrator might want to set the min value of the progress bar,
  // typically if it needs to restart a new looping computation.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            setProgressBarMinValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMinValue);

  // The integrator informs by signals of the total number of spectra to be
  // loaded.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            setProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMaxValue);

  // The integrator informs by signals of the total number of spectra to be
  // loaded.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            incrementProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::incrementProgressBarMaxValue);

  // The integrator informs by signals of the number of added mass spectra.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            setProgressBarCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarCurrentValue);

  // The integrator informs by signals of the number of added mass spectra.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            incrementProgressBarCurrentValueSignal,
          [this](double increment) {
            this->setProgressBarCurrentValue(m_currentValue += increment);
          });

  // The integrator informs by signals of the loading process with informative
  // text.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            setTaskDescriptionTextSignal,
          this,
          &TaskMonitorCompositeWidget::setTaskDescriptionText);

  // The integrator informs by signals of the loading process with informative
  // text.
  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::setStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusText);

  // In some cases, we want to just append new informative text without erasing
  // the previous one.
  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::appendStatusTextSignal,
    this,
    &TaskMonitorCompositeWidget::appendStatusText);

  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            setStatusTextAndCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusTextAndCurrentValue);

  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            incrementProgressBarCurrentValueAndSetStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::
            incrementProgressBarCurrentValueAndSetStatusText);

  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            lockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::lock);

  connect(mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::
            unlockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::unlock);
}

void
TaskMonitorCompositeWidget::makeMassSpecDataFileLoaderConnections(
  MassSpecDataFileLoader *mass_spec_data_file_loader_p)
{


  // The integrator might want to set the min/max values of the progress bar
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setupProgressBarSignal,
          this,
          &TaskMonitorCompositeWidget::setupProgressBar);

  // The loader might want to set the min value of the progress bar.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setProgressBarMinValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMinValue);

  // The loader informs by signals of the total number of spectra to be loaded.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::spectrumListSizeSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMaxValue);

  // The loader informs by signals of the total number of spectra to be loaded.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMaxValue);

  // The loader informs by signals of the number of added mass spectra.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setProgressBarCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarCurrentValue);

  // The loader informs by signals of the loading process with informative text.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusText);

  // In some cases, we want to just append new informative text without erasing
  // the previous one.
  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::appendStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::appendStatusText);

  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::setStatusTextAndCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusTextAndCurrentValue);

  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::lockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::lock);

  connect(mass_spec_data_file_loader_p,
          &MassSpecDataFileLoader::unlockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::unlock);
}

void
TaskMonitorCompositeWidget::makeMsRunProberConnections(
  MsRunProber *ms_run_prober_p)
{

  // The integrator might want to set the min/max values of the progress bar
  connect(ms_run_prober_p,
          &MsRunProber::setupProgressBarSignal,
          this,
          &TaskMonitorCompositeWidget::setupProgressBar);

  // The loader might want to set the min value of the progress bar.
  connect(ms_run_prober_p,
          &MsRunProber::setProgressBarMinValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMinValue);

  // The loader informs by signals of the total number of spectra to be loaded.
  connect(ms_run_prober_p,
          &MsRunProber::setProgressBarMaxValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarMaxValue);

  // The loader informs by signals of the number of added mass spectra.
  connect(ms_run_prober_p,
          &MsRunProber::setProgressBarCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setProgressBarCurrentValue);

  // The loader informs by signals of the loading process with informative text.
  connect(ms_run_prober_p,
          &MsRunProber::setStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusText);

  // In some cases, we want to just append new informative text without erasing
  // the previous one.
  connect(ms_run_prober_p,
          &MsRunProber::appendStatusTextSignal,
          this,
          &TaskMonitorCompositeWidget::appendStatusText);

  connect(ms_run_prober_p,
          &MsRunProber::setStatusTextAndCurrentValueSignal,
          this,
          &TaskMonitorCompositeWidget::setStatusTextAndCurrentValue);

  connect(ms_run_prober_p,
          &MsRunProber::lockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::lock);

  connect(ms_run_prober_p,
          &MsRunProber::unlockTaskMonitorCompositeWidgetSignal,
          this,
          &TaskMonitorCompositeWidget::unlock);
}

void
TaskMonitorCompositeWidget::setTimeLag(double time_lag)
{
  m_timeLag = time_lag;
}

double
TaskMonitorCompositeWidget::getTimeLag() const
{
  return m_timeLag;
}

bool
TaskMonitorCompositeWidget::shouldUpdate()

{

  bool result = true;

  // #if 0
  std::chrono::time_point<std::chrono::system_clock> now =
    std::chrono::system_clock::now();

  auto time_lag = std::chrono::duration_cast<std::chrono::milliseconds>(
    now - m_lastTimePoint);

  if(time_lag.count() >= m_timeLag)
    {
      m_lastTimePoint = now;
      result          = true;
    }

  // qDebug() << "Returning:" << result;
  // #endif

  return result;
}

/****************************** Slots *****************************/
/****************************** Slots *****************************/

void
TaskMonitorCompositeWidget::cancelTask()
{
  // qDebug() << "cancellation asked, emitting cancelTaskSignal";

  // Relay the signal.
  emit cancelTaskSignal();

  appendStatusText(" - operation cancelled by the user.");
  m_isLocked = true;

  // No need for any QTimer-based auto-delete later because this signal is
  // trapped somewhere else and eventually the taskFinished() function will be
  // called, and the deletion of *this widget will be triggered. Otherwise
  // doulbe free of same pointer and crash.
}

void
TaskMonitorCompositeWidget::taskFinished()
{
  // qDebug();

  appendStatusText(" - done.");

  // Lock the widget because we want the user to see the "- done" string.
  m_isLocked = true;

  // Program the auto destruction of this.

  QTimer::singleShot(1500, [this]() {
    this->deleteLater();
  });
}

void
TaskMonitorCompositeWidget::setupProgressBar(int min_value, int max_value)
{
  if(!m_isLocked)
    {
      m_ui->progressBar->setMinimum(min_value);
      m_currentValue = min_value;
      m_ui->progressBar->setMaximum(max_value);
    }
}

void
TaskMonitorCompositeWidget::setProgressBarMinValue(int value)
{
  if(!m_isLocked)
    {
      m_ui->progressBar->setMinimum(value);
      m_currentValue = value;
    }
}

void
TaskMonitorCompositeWidget::setProgressBarMaxValue(int value)
{
  if(!m_isLocked)
    {
      // qDebug() << "setting max value:" << value;

      m_ui->progressBar->setMaximum(value);
    }
}

void
TaskMonitorCompositeWidget::incrementProgressBarMaxValue(int increment)
{
  int new_value = m_ui->progressBar->maximum() + increment;

  // qDebug() << "incrementing max value to:" << new_value;

  m_ui->progressBar->setMaximum(new_value);
}

void
TaskMonitorCompositeWidget::setProgressBarCurrentValue(int value)
{
  if(!m_isLocked && shouldUpdate())
    {
      m_ui->progressBar->setValue(value);
      m_currentValue = value;
    }
}

void
TaskMonitorCompositeWidget::setMsRunIdText(QString text)
{
  if(!m_isLocked)
    m_ui->msRunIdLabel->setText(text);
}

QString
TaskMonitorCompositeWidget::getMsRunIdText()
{
  return m_ui->msRunIdLabel->text();
}

void
TaskMonitorCompositeWidget::setTaskDescriptionText(QString text)
{
  if(!m_isLocked)
    m_ui->taskDescriptionLineEdit->setText(text);
}

void
TaskMonitorCompositeWidget::clearTaskDescriptionText()
{
  if(!m_isLocked)
    m_ui->taskDescriptionLineEdit->setText("");
}

QString
TaskMonitorCompositeWidget::getTaskDescriptionText()
{
  return m_ui->taskDescriptionLineEdit->text();
}

void
TaskMonitorCompositeWidget::setStatusText(QString text)
{
  // qDebug() << "m_isLocked:" << m_isLocked << "shouldUpdate:" <<
  // shouldUpdate();

  if(!m_isLocked && shouldUpdate())
    m_ui->statusLineEdit->setText(text);
}

void
TaskMonitorCompositeWidget::appendStatusText(QString text)
{
  if(!m_isLocked && shouldUpdate())
    m_ui->statusLineEdit->setText(m_ui->statusLineEdit->text() + text);
}

QString
TaskMonitorCompositeWidget::getStatusText()
{
  return m_ui->statusLineEdit->text();
}

void
TaskMonitorCompositeWidget::clearStatusText()
{
  if(!m_isLocked && shouldUpdate())
    m_ui->statusLineEdit->setText("");
}

void
TaskMonitorCompositeWidget::setStatusTextAndCurrentValue(QString text,
                                                         int value)
{
  if(!m_isLocked && shouldUpdate())
    {
      m_ui->statusLineEdit->setText(text);
      m_ui->progressBar->setValue(value);
      m_currentValue = value;
    }
}

void
TaskMonitorCompositeWidget::incrementProgressBarCurrentValue(int increment)
{
  if(!m_isLocked && shouldUpdate())
    {
      m_currentValue += increment;
      m_ui->progressBar->setValue(m_currentValue);
    }
}

void
TaskMonitorCompositeWidget::incrementProgressBarCurrentValueAndSetStatusText(
  int increment, QString text)
{
  // qDebug() << "current thread:" << QThread::currentThread()
  //<< "current value:" << m_currentValue;

  if(!m_isLocked)
    {

      // We have to increment the value, even if we should not update the text
      // string, because otherwise we miss all the increment calls between two
      // updates.

      m_currentValue += increment;

      if(shouldUpdate())
        {
          m_ui->progressBar->setValue(m_currentValue);
          QString new_text = craftStringIfFormattedText(text);
          m_ui->statusLineEdit->setText(new_text);

          // qDebug() << "current thread:" << QThread::currentThread()
          //<< "after increment current value:" << m_currentValue
          //<< "new text:" << new_text;
        }
    }
}

void
TaskMonitorCompositeWidget::lock()
{
  m_isLocked = true;
}

void
TaskMonitorCompositeWidget::unlock()
{
  m_isLocked = false;
}

bool
TaskMonitorCompositeWidget::isLocked() const
{
  return m_isLocked;
}

QString
TaskMonitorCompositeWidget::craftStringIfFormattedText(
  const QString &text) const
{
  // If text contains a format like "%c", then we need to craft a text with
  // it.

  if(!text.contains("%c"))
    {
      // qDebug() << "text:" << text << "has no format substring.";
      return text;
    }

  QStringList string_list = text.split("%c", Qt::KeepEmptyParts);

  QString new_text;

  for(int iter = 0; iter < string_list.size(); ++iter)
    {
      new_text += string_list.at(iter);

      if(iter < string_list.size() - 1)
        new_text += QString("%1 ").arg(m_currentValue);
    }

  // qDebug() << "new_text:" << new_text;

  return new_text;
}


} // namespace MineXpert

} // namespace MsXpS
