/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <QSettings>
#include <QMenuBar>
#include <QMenu>
#include <QDebug>
#include <QDialog>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/msrun/private/timsmsrunreader.h>

/////////////////////// Local includes
#include "../nongui/MassDataIntegratorTask.hpp"
#include "Application.hpp"
#include "MsRunProbeInfoViewerDlg.hpp"
#include "ProgramWindow.hpp"
#include "ui_MsRunProbeInfoViewerDlg.h"

namespace MsXpS
{
namespace MineXpert
{


//! Construct an MsRunProbeInfoViewerDlg instance.
MsRunProbeInfoViewerDlg::MsRunProbeInfoViewerDlg(
  QWidget *parent,
  const QString &title,
  const QString &settingsTitle,
  const QString &description,
  const QString &vendor,
  pappso::MsRunIdCstSPtr ms_run_id_csp,
  MsRunProbeInfoSPtr ms_run_probe_info_sp)
  : QDialog(parent),
    mp_programWindow(static_cast<ProgramWindow *>(parent)),
    m_title(title),
    m_settingsTitle(settingsTitle),
    m_description(description),
    m_vendor(vendor),
    m_msRunIdCstSPtr(ms_run_id_csp),
    msp_msRunProbeInfo(ms_run_probe_info_sp),
    m_ui(new ::Ui::MsRunProbeInfoViewerDlg)
{
  if(parent == Q_NULLPTR)
    qFatal() << "Programming error.";

  if(ms_run_id_csp == nullptr)
    qFatal() << "Programming error.";

  m_ui->setupUi(this);

  if(!initialize())
    qFatal() << "Programming error.";
}

//! Destruct \c this MsRunProbeInfoViewerDlg instance.
MsRunProbeInfoViewerDlg::~MsRunProbeInfoViewerDlg()
{
  // qDebug();
  writeSettings();
}

//! Write the settings to as to restore the window geometry later.
void
MsRunProbeInfoViewerDlg::writeSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup(m_settingsTitle);

  settings.setValue("geometry", saveGeometry());
  // settings.setValue("splitter", m_ui->splitter->saveState());
  settings.setValue("RtTimeUnitSeconds", m_areRtTimeUnitSeconds);

  settings.endGroup();
}

//! Read the settings to as to restore the window geometry.
void
MsRunProbeInfoViewerDlg::readSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup(m_settingsTitle);

  restoreGeometry(settings.value("geometry").toByteArray());
  // m_ui->splitter->restoreState(settings.value("splitter").toByteArray());

  // By default we want retention times in seconds (this is in the Bruker data).
  m_areRtTimeUnitSeconds = settings.value("RtTimeUnitSeconds", true).toBool();

  m_ui->rtInSecondsRadioButton->setChecked(m_areRtTimeUnitSeconds);

  settings.endGroup();
}

//! Handle the close event.
void
MsRunProbeInfoViewerDlg::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  // qDebug();
  writeSettings();
  QDialog::reject();
}

ProgramWindow *
MsRunProbeInfoViewerDlg::getProgramWindow()
{
  return mp_programWindow;
}

//! Initialize the window.
bool
MsRunProbeInfoViewerDlg::initialize()
{
  setWindowTitle(QString("MineXpert3 - %1").arg(m_title));
  m_ui->descriptionLabel->setText(m_description);
  m_ui->vendorLabel->setText(m_vendor);

  setAttribute(Qt::WA_DeleteOnClose);

  // The default window icon.
  QString pixmap_file_name = ":/images/icons/32x32/minexpert3.png";
  QPixmap icon_pixmap(pixmap_file_name);
  QIcon icon(icon_pixmap);
  setWindowIcon(icon);

  // Will read the retention time unit. By default, minutes.
  readSettings();

  /****************** The widgetry  ******************/

  // Now that we have read the settings, check the right radio button.
  m_ui->rtInSecondsRadioButton->setChecked(m_areRtTimeUnitSeconds);
  m_ui->rtInMinutesRadioButton->setChecked(!m_areRtTimeUnitSeconds);

  m_ui->ms1SpectraCountSpinBox->setValue(msp_msRunProbeInfo->ms1SpectraCount);
  m_ui->ms2SpectraCountSpinBox->setValue(msp_msRunProbeInfo->ms2SpectraCount);

  mp_plotWidget = new pappso::TicXicChromTracePlotWidget(
    this, "Retention time", "Intensity (a.u.)");

  m_ui->qcpTracePlotWidgetHorizontalLayout->addWidget(mp_plotWidget);

  // Set the widest range possible for retention times
  m_ui->rtRangeBeginDoubleSpinBox->setRange(std::numeric_limits<double>::min(),
                                            std::numeric_limits<double>::max());

  connect(
    m_ui->rtRangeBeginDoubleSpinBox, &QDoubleSpinBox::editingFinished, [&]() {
      mp_plotWidget->xAxis->setRange(
        QCPRange(m_ui->rtRangeBeginDoubleSpinBox->value(),
                 mp_plotWidget->xAxis->range().upper));

      // setRange orders lower and upper in  the right  ascending order. If the
      // user enters an upper value < lower value, then setRange fixes this.
      // This is why we take advantage of this and call updateRtRangeSpinBoxes.
      updateRtRangeSpinBoxes();
      mp_plotWidget->replot();
    });

  m_ui->rtRangeEndDoubleSpinBox->setRange(std::numeric_limits<double>::min(),
                                          std::numeric_limits<double>::max());

  connect(
    m_ui->rtRangeEndDoubleSpinBox, &QDoubleSpinBox::editingFinished, [&]() {
      mp_plotWidget->xAxis->setRange(
        QCPRange(mp_plotWidget->xAxis->range().lower,
                 m_ui->rtRangeEndDoubleSpinBox->value()));

      // setRange orders lower and upper in  the right  ascending order. If the
      // user enters an upper value < lower value, then setRange fixes this.
      // This is why we take advantage of this and call updateRtRangeSpinBoxes.
      updateRtRangeSpinBoxes();
      mp_plotWidget->replot();
    });

  connect(m_ui->rtInSecondsRadioButton, &QRadioButton::clicked, [&]() {
    if(!m_areRtTimeUnitSeconds)
      {
        // We are converting the data to seconds.
        convertTicChromatogramRetTimeUnit();
        mp_plotWidget->replot();
        m_areRtTimeUnitSeconds = true;
      }
  });

  connect(m_ui->rtInMinutesRadioButton, &QRadioButton::clicked, [&]() {
    if(m_areRtTimeUnitSeconds)
      {
        // We are converting the data to minutes.
        convertTicChromatogramRetTimeUnit();
        mp_plotWidget->replot();
        m_areRtTimeUnitSeconds = false;
      }
  });

  connect(mp_plotWidget,
          &pappso::TicXicChromTracePlotWidget::plotRangesChangedSignal,
          [&]() {
            m_ui->rtRangeBeginDoubleSpinBox->setValue(
              mp_plotWidget->xAxis->range().lower);
            m_ui->rtRangeEndDoubleSpinBox->setValue(
              mp_plotWidget->xAxis->range().upper);

            // qDebug("New x axis range: %f.5--%f.5",
            //        mp_plotWidget->xAxis->range().lower,
            //        mp_plotWidget->xAxis->range().upper);
          });

  connect(m_ui->loadDataPushButton, &QPushButton::clicked, [&]() {
    // qDebug("Clicked to load data.");

    if(!validateData())
      return;
    else
      QDialog::accept();
  });

  connect(m_ui->cancelPushButton, &QPushButton::clicked, [&]() {
    // qDebug("Clicked to cancel.");
    QDialog::reject();
  });

  connect(m_ui->imOneOverKoGroupBox, &QGroupBox::clicked, [&](bool checked) {
    if(checked)
      m_ui->imScansGroupBox->setChecked(!checked);
  });
  connect(m_ui->imScansGroupBox, &QGroupBox::clicked, [&](bool checked) {
    if(checked)
      m_ui->imOneOverKoGroupBox->setChecked(!checked);
  });

  // Now get the TIC chromatogram out of the file in question.

  pappso::TimsMsRunReader tims_ms_run_reader =
    pappso::TimsMsRunReader(m_msRunIdCstSPtr);

  pappso::Trace tic_chrom_trace = tims_ms_run_reader.getTicChromatogram();

  // Note that the TIC chromatogram from the Bruker timsTOF data files has time
  // unit seconds, not minutes.

  if(!m_areRtTimeUnitSeconds)
    {
      // The user wants unit to be min, so divide the seconds from the Bruker
      // data file by 60.
      std::for_each(tic_chrom_trace.begin(),
                    tic_chrom_trace.end(),
                    [](pappso::DataPoint &dp) {
                      dp.x /= 60;
                    });
    }

  mp_plotWidget->addTrace(tic_chrom_trace, QColor(180, 0, 0));

  updateRtRangeSpinBoxes();

  // Now all the other data.

  m_ui->imOneOverKoRangeBeginDoubleSpinBox->setValue(
    msp_msRunProbeInfo->ionMobilityOneOverKoBegin);
  m_ui->imOneOverKoRangeEndDoubleSpinBox->setValue(
    msp_msRunProbeInfo->ionMobilityOneOverKoEnd);

  m_ui->imScansRangeBeginSpinBox->setValue(
    msp_msRunProbeInfo->ionMobilityScansBegin);
  m_ui->imScansRangeEndSpinBox->setValue(
    msp_msRunProbeInfo->ionMobilityScansEnd);

  m_ui->mzRangeBeginDoubleSpinBox->setValue(msp_msRunProbeInfo->mzBegin);
  m_ui->mzRangeEndDoubleSpinBox->setValue(msp_msRunProbeInfo->mzEnd);

  // It is necessary that msp_msRunProbeInfo->mzBinCount be non-naught.
  if(msp_msRunProbeInfo->mzBinCount)
    {
      m_ui->binCountSpinBox->setValue(msp_msRunProbeInfo->mzBinCount);

      // The bin size is nothing else than the whole m/z range divided by the
      // number of bins in the whole acquisition.
      double mz_bin_size =
        (msp_msRunProbeInfo->mzEnd - msp_msRunProbeInfo->mzBegin) /
        static_cast<double>(msp_msRunProbeInfo->mzBinCount);

      m_ui->mzBinSizeDoubleSpinBox->setValue(mz_bin_size);
    }
  else
    m_ui->mzBinSizeDoubleSpinBox->setValue(0);

  // At this point we need to configure the behaviour of the m/z bin radiobutton
  // widgets

  connect(
    // The user can define a merge value by specifying the size of the bin. If,
    // for example, the bin size over the whole m/z range is 0.005, then, if the
    // user sets 0.05, that would mean that the user wants to merge 10 bins into
    // a single bin, that is make a bin out of 10 bins in the original data set.
    m_ui->mzMergeBinsByMzDoubleSpinBox,
    &QDoubleSpinBox::editingFinished,
    [&]() {
      // qDebug();
      double mz_window = m_ui->mzMergeBinsByMzDoubleSpinBox->value();
      m_ui->mzMergeBinsByMzRadioButton->setChecked(true);

      if(msp_msRunProbeInfo->mzBinCount)
        {
          // We need to compute this value because we are in a lambda.
          double mz_bin_size =
            (msp_msRunProbeInfo->mzEnd - msp_msRunProbeInfo->mzBegin) /
            static_cast<double>(msp_msRunProbeInfo->mzBinCount);

          // Compute the equivalent TOF indices window (that is, the
          // number of bins that correspond to the size of m/z window)
          uint mz_bin_count = mz_window / mz_bin_size;
          m_ui->mzMergeBinsByCountSpinBox->setValue(mz_bin_count);
        }
    });

  connect(
    m_ui->mzMergeBinsByCountSpinBox, &QDoubleSpinBox::editingFinished, [&]() {
      // qDebug();
      uint mz_bin_count = m_ui->mzMergeBinsByCountSpinBox->value();
      m_ui->mzMergeBinsByCountRadioButton->setChecked(true);

      if(msp_msRunProbeInfo->mzBinCount)
        {
          // We need to compute this value because we are in a lambda.
          double mz_bin_size =
            (msp_msRunProbeInfo->mzEnd - msp_msRunProbeInfo->mzBegin) /
            static_cast<double>(msp_msRunProbeInfo->mzBinCount);

          // Compute the equivalent m/z window (that is, the
          // size of the m/z bin in which m/z values will be packed)
          double mz_window = mz_bin_count * mz_bin_size;
          m_ui->mzMergeBinsByMzDoubleSpinBox->setValue(mz_window);
        }
    });

  // Now that we have configured the way the m/z-related widgets behave, we can
  // set the default m/z window to the m/z bin size deduced by looking at the
  // sample spectrum.

  if(msp_msRunProbeInfo->mzBinCount)
    {
      double mz_bin_size =
        (msp_msRunProbeInfo->mzEnd - msp_msRunProbeInfo->mzBegin) /
        static_cast<double>(msp_msRunProbeInfo->mzBinCount);

      m_ui->mzMergeBinsByMzDoubleSpinBox->setValue(mz_bin_size);
      // qDebug("Set m_ui->mzMergeBinsByMzDoubleSpinBox to %f.5", mz_bin_size);
    }

  m_ui->renderMzRangeGroupBox->setChecked(false);
  m_ui->mergeMzBinsGroupBox->setChecked(false);

  return true;
}

void
MsRunProbeInfoViewerDlg::show()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup(m_settingsTitle);

  restoreGeometry(settings.value(m_settingsTitle + "_geometry").toByteArray());

  settings.endGroup();

  QDialog::show();
}

void
MsRunProbeInfoViewerDlg::showWindow()
{
  activateWindow();
  raise();
  show();
}

void
MsRunProbeInfoViewerDlg::convertTicChromatogramRetTimeUnit()
{
  QSharedPointer<QCPGraphDataContainer> graph_data_container_p =
    mp_plotWidget->graph(0)->data();

  QCPGraphDataContainer::iterator iter     = graph_data_container_p->begin();
  QCPGraphDataContainer::iterator iter_end = graph_data_container_p->end();
  QCPRange prev_range(mp_plotWidget->xAxis->range());
  QCPRange new_range;

  if(m_areRtTimeUnitSeconds)
    {
      // Convert to minutes.

      while(iter != iter_end)
        {
          iter->key /= 60;
          ++iter;
        }
      mp_plotWidget->xAxis->setRange(
        QCPRange(prev_range.lower / 60, prev_range.upper / 60));
    }
  else
    {
      // Convert to seconds

      while(iter != iter_end)
        {
          iter->key *= 60;
          ++iter;
        }
      mp_plotWidget->xAxis->setRange(
        QCPRange(prev_range.lower * 60, prev_range.upper * 60));
    }

  updateRtRangeSpinBoxes();
}

void
MsRunProbeInfoViewerDlg::updateRtRangeSpinBoxes()
{
  m_ui->rtRangeBeginDoubleSpinBox->setValue(
    mp_plotWidget->xAxis->range().lower);
  m_ui->rtRangeEndDoubleSpinBox->setValue(mp_plotWidget->xAxis->range().upper);
}

bool
MsRunProbeInfoViewerDlg::validateData()
{
  if(m_ui->retentionTimeRangeGroupBox->isChecked())
    {
      if(m_ui->rtInSecondsRadioButton->isChecked())
        {
          msp_msRunProbeInfo->retentionTimeBeginInSeconds =
            m_ui->rtRangeBeginDoubleSpinBox->value();
          msp_msRunProbeInfo->retentionTimeEndInSeconds =
            m_ui->rtRangeEndDoubleSpinBox->value();
        }
      else
        {
          msp_msRunProbeInfo->retentionTimeBeginInSeconds =
            m_ui->rtRangeBeginDoubleSpinBox->value() * 60;
          msp_msRunProbeInfo->retentionTimeEndInSeconds =
            m_ui->rtRangeEndDoubleSpinBox->value() * 60;
        }
    }
  else
    {
      msp_msRunProbeInfo->retentionTimeBeginInSeconds = -1;
      msp_msRunProbeInfo->retentionTimeEndInSeconds   = -1;
    }

  // By default no IM merge unit, that is, ion mobility should
  // not be a criterion to select data subsets to be loaded.
  msp_msRunProbeInfo->ionMobilityUnit = IonMobilityUnit::NONE;

  // By default, do not flatten frames, that is, we do not merge IMS scans.
  msp_msRunProbeInfo->ionMobilityScansMergeRequired = false;

  if(m_ui->ionMobilityGroupBox->isChecked())
    {
      // Is the user asking to restrict the IM data to some range?
      if(m_ui->imOneOverKoGroupBox->isChecked())
        {
          msp_msRunProbeInfo->ionMobilityUnit = IonMobilityUnit::ONE_OVER_KO;
          msp_msRunProbeInfo->ionMobilityOneOverKoBegin =
            m_ui->imOneOverKoRangeBeginDoubleSpinBox->value();
          msp_msRunProbeInfo->ionMobilityOneOverKoEnd =
            m_ui->imOneOverKoRangeEndDoubleSpinBox->value();
        }
      else if(m_ui->imScansGroupBox->isChecked())
        {
          msp_msRunProbeInfo->ionMobilityUnit = IonMobilityUnit::SCAN;
          msp_msRunProbeInfo->ionMobilityScansBegin =
            m_ui->imScansRangeBeginSpinBox->value();
          msp_msRunProbeInfo->ionMobilityScansEnd =
            m_ui->imScansRangeEndSpinBox->value();
        }

      // Whatever the IM data restriction above, it is still possible, inside
      // the restricted IM data range to merge IM data, either by scan count
      // (every x scans, merge them together) or by 1/K0 window (each time a set
      // of IM scans span that 1/K0 window, merge them together).

      if(m_ui->mergeImScansCheckBox->isChecked())
        {
          msp_msRunProbeInfo->ionMobilityScansMergeRequired =
            m_ui->mergeImScansCheckBox->isChecked();
        }
    }

  QString ms_levels_to_render = m_ui->msLevelsLineEdit->text();

  bool ok = false;

  if(!ms_levels_to_render.isEmpty())
    {
      // qDebug() << "The MS levels line edit widget contains this text:"
      //          << ms_levels_to_render;

      QStringList ms_levels_string_list =
        ms_levels_to_render.split(",", Qt::SkipEmptyParts);

      msp_msRunProbeInfo->msLevels.clear();

      for(int iter = 0; iter < ms_levels_string_list.size(); ++iter)
        {
          QString ms_level_string = ms_levels_string_list.at(iter).trimmed();

          std::size_t ms_level = ms_level_string.toUInt(&ok);

          if(!ok)
            {
              QMessageBox::warning(this,
                                   "Mass spectrometry file loading preview",
                                   "The MS levels failed to validate fully.");
              return false;
            }


          msp_msRunProbeInfo->msLevels.push_back(ms_level);
        }
    }
  else
    {
      msp_msRunProbeInfo->msLevels.clear();
    }

  if(m_ui->renderMzRangeGroupBox->isChecked())
    {
      msp_msRunProbeInfo->mzBegin = m_ui->mzRangeBeginDoubleSpinBox->value();
      msp_msRunProbeInfo->mzEnd   = m_ui->mzRangeEndDoubleSpinBox->value();
    }
  else
    {
      msp_msRunProbeInfo->mzBegin = -1;
      msp_msRunProbeInfo->mzEnd   = -1;
    }

  if(m_ui->mergeMzBinsGroupBox->isChecked())
    {
      // qDebug("The merge m/z bins is asked for.");

      // Since the m/z window is computed automatically (connection in the
      // initialize) just read the mz window.
      msp_msRunProbeInfo->mzBinsMergeWindow =
        m_ui->mzMergeBinsByCountSpinBox->value();

      // qDebug("The mz merge window was set to a bin window of bin count: %d",
      //        msp_msRunProbeInfo->mzBinsMergeWindow);
    }

  return true;
}

} // namespace MineXpert

} // namespace MsXpS
