/* 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
 */


/////////////////////// Qt includes
#include <QObject>
#include <QDebug>
#include <QWidget>
#include <QMessageBox>
#include <QSettings>
#include <QLineEdit>
#include <QCloseEvent>


/////////////////////// libXpertMassCore includes


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>


/////////////////////// Local includes
#include "Application.hpp"
#include "MzIntegrationParamsDlg.hpp"
#include "ui_MzIntegrationParamsDlg.h"

namespace MsXpS
{
namespace MineXpert
{

MzIntegrationParamsDlg::MzIntegrationParamsDlg(QWidget *parent)
  : QDialog(parent),
    mp_mzIntegrationParams(new pappso::MzIntegrationParams(/*parent*/ this)),
    m_ui(new ::Ui::MzIntegrationParamsDlg)
{
  if(parent == nullptr)
    qFatal() << "Programming error.";

  m_ui->setupUi(this);

  // Get some stored settings before setting up the widget.
  readSettings();
  setupWidget();
}

MzIntegrationParamsDlg::MzIntegrationParamsDlg(
  QWidget *parent,
  const pappso::MzIntegrationParams &mz_integration_params,
  const QColor &color)
  : QDialog(parent),
    mp_mzIntegrationParams(mz_integration_params.clone(/*parent*/ this)),
    m_color(color),
    m_ui(new ::Ui::MzIntegrationParamsDlg)
{
  if(parent == nullptr)
    qFatal() << "Programming error.";

  qDebug().noquote() << "The mz integration params at dialog construction:\n"
                     << mp_mzIntegrationParams->toString();

  if(!m_color.isValid())
    qFatal() << "The color is not valid.";

  m_ui->setupUi(this);

  setupWidget();
}

MzIntegrationParamsDlg::~MzIntegrationParamsDlg()
{
  // qDebug();
  writeSettings();
}

void
MzIntegrationParamsDlg::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  // qDebug();

  writeSettings();

  emit mzIntegrationParamsDlgShouldBeDestroyedSignal();
}

//! Write the settings to as to restore the window geometry later.
void
MzIntegrationParamsDlg::writeSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  qDebug() << "Saving window geometry.";

  libXpertMassGui::WindowGeometryManager::save(
    dynamic_cast<const QWidget *>(this), "MzIntegrationParamsDlg", settings);


  settings.beginGroup("MzIntegrationParamsDlg");

  // Awaiting settings...

  settings.endGroup();
}

//! Read the settings to as to restore the window geometry.
void
MzIntegrationParamsDlg::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  qDebug() << "Restoring window geometry.";

  libXpertMassGui::WindowGeometryManager::restore(this, "MzIntegrationParamsDlg", settings);

  settings.beginGroup("MzIntegrationParamsDlg");

  // Awaiting settings...

  settings.endGroup();
}

void
MzIntegrationParamsDlg::setupWidget()
{
  // qDebug() << "Setting up the dialog with these mz integration params:"
  // << m_mzIntegrationParams.toString();

  // Set the right color to the frame to help the user determine for which
  // graph the parameters were initialized.
  // qDebug() << "The frame color is:" << m_color;

  QString style =
    QString("#mainFrame {border: 2px solid %1;}").arg(m_color.name());
  m_ui->mainFrame->setStyleSheet(style);

  // Prepare the exclusion logic of the radio button widgets.

  // One important thing is that whenever the data-based binning radio button is
  // clicked, it won't be possible to store the settings as default. Indeed,
  // data-based binning is rare, and is used when combinining mass data into an
  // existing graph already plotted inside a plot widget.

  connect(m_ui->dataBasedBinningRadioButton, &QRadioButton::clicked, [this]() {
    m_ui->setAsDefaultParamsCheckBox->setChecked(false);
  });

  connect(m_ui->setAsDefaultParamsCheckBox,
          &QCheckBox::checkStateChanged,
          [this](Qt::CheckState check_state) {
            if(check_state == Qt::CheckState::Checked)
              m_ui->dataBasedBinningRadioButton->setChecked(false);
          });

  // The precision widget required to specify the logic with which the bins
  // are calculated.

  // The precision widget that is used to set the bin size (Dalton|Res|Ppm) must
  // be made active when the arbitrary binning radio button is clicked but not
  // otherwise.

  connect(m_ui->arbitraryBinningRadioButton,
          &QRadioButton::toggled,
          [this](bool checked) {
            mp_binSizeModelWidget->setEnabled(checked);
          });

  mp_binSizeModelWidget = new pappso::PrecisionWidget(this);
  m_ui->precisionWidgetHorizontalLayout->addWidget(mp_binSizeModelWidget);
  mp_binSizeModelWidget->setToolTip("Set the size of the m/z bins");
  mp_binSizeModelWidget->setPrecision(
    mp_mzIntegrationParams->getBinSizeModel());

  connect(mp_binSizeModelWidget,
          &pappso::PrecisionWidget::precisionChanged,
          [this](pappso::PrecisionPtr precision_p) {
            mp_binSizeModel = precision_p;
            m_ui->arbitraryBinningRadioButton->setChecked(true);
          });

  connect(m_ui->binCreationStrategyGroupBox,
          &QGroupBox::toggled,
          this,
          &MzIntegrationParamsDlg::binCreationStrategyGroupBoxToggled);

  // Now, if any kind of binning is required, just activate the
  // groupbox and the connected signal will make the work.

  if(mp_mzIntegrationParams->getBinningType() ==
     pappso::MzIntegrationParams::BinningType::NONE)
    {
      // qDebug() << "The MzIntegrationParams binning type is NONE.";

      m_ui->binCreationStrategyGroupBox->setChecked(false);
      // The connected slot will handle all the setting up of the
      // widgets.
    }
  else
    {
      m_ui->binCreationStrategyGroupBox->setChecked(true);

      if(mp_mzIntegrationParams->getBinningType() ==
         pappso::MzIntegrationParams::BinningType::DATA_BASED)
        {
          m_ui->dataBasedBinningRadioButton->setChecked(true);
          qDebug() << "The MzIntegrationParams binning type is DATA_BASED.";
        }
      else if(mp_mzIntegrationParams->getBinningType() ==
              pappso::MzIntegrationParams::BinningType::ARBITRARY)
        {
          m_ui->arbitraryBinningRadioButton->setChecked(true);
          qDebug() << "The MzIntegrationParams binning type is ARBITRARY.";
        }
      else
        qFatal() << "Programming error. Ran out of integration methods...";
    }

  // If the binCreationStrategyGroupBox is not checked, then no binning
  // is required.

  // We have a member MzIntegrationParams object that we need to
  // represent in the various widgets of this dialog window.

  m_ui->decimalPlacesSpinBox->setValue(
    mp_mzIntegrationParams->getDecimalPlaces());

  m_ui->removeZeroValueCheckBox->setCheckState(
    mp_mzIntegrationParams->isRemoveZeroValDataPoints() ? Qt::Checked
                                                        : Qt::Unchecked);
  connect(m_ui->applyPushButton,
          &QPushButton::clicked,
          this,
          &MzIntegrationParamsDlg::applyPushButtonClicked);

  m_ui->binSizeDivisorSpinBox->setValue(
    mp_mzIntegrationParams->getBinSizeDivisor());

  readSettings();
}

void
MzIntegrationParamsDlg::binCreationStrategyGroupBoxToggled(bool checked)
{
  qDebug() << "The bin creation strategy group box is checked or not?"
           << checked;

  if(checked)
    {
      // The user is willing to configure bin creation.
      // Set the values as we have them in the member MzIntegrationParams.

      if(mp_mzIntegrationParams->getBinningType() ==
         pappso::MzIntegrationParams::BinningType::DATA_BASED)
        {
          m_ui->dataBasedBinningRadioButton->setChecked(true);
          // When data based binning is selected, that can never become
          // a default. See the connect lambda above.
        }
      else if(mp_mzIntegrationParams->getBinningType() ==
              pappso::MzIntegrationParams::BinningType::ARBITRARY)
        {
          m_ui->arbitraryBinningRadioButton->setChecked(true);
        }
      else
        {
          // Cannot say qFatal() here because at this stage,
          // mp_mzIntegrationParams is still set to ::NONE.

          // Then, by default,
          qDebug() << "Setting the default binning logic: arbitrary.";
          m_ui->arbitraryBinningRadioButton->setChecked(true);
        }
    }
}

QString
MzIntegrationParamsDlg::toString() const
{
  return mp_mzIntegrationParams->toString();
}

void
MzIntegrationParamsDlg::setBinningType(
  pappso::MzIntegrationParams::BinningType binningType)
{
  if(binningType == pappso::MzIntegrationParams::BinningType::NONE)
    {
      m_ui->binCreationStrategyGroupBox->setChecked(false);
    }

  else if(binningType == pappso::MzIntegrationParams::BinningType::ARBITRARY)
    {
      m_ui->binCreationStrategyGroupBox->setChecked(true);
      m_ui->arbitraryBinningRadioButton->setChecked(true);
    }
  else if(binningType == pappso::MzIntegrationParams::BinningType::DATA_BASED)
    {
      m_ui->binCreationStrategyGroupBox->setChecked(true);
      m_ui->dataBasedBinningRadioButton->setChecked(true);
    }
  else
    qFatal() << "Programming error.";
}

void
MzIntegrationParamsDlg::setBinSizePrecisionPtr(
  pappso::PrecisionPtr bin_size_precsion_p)
{
  mp_binSizeModelWidget->setPrecision(bin_size_precsion_p);
}

void
MzIntegrationParamsDlg::setRemoveZeroValDataPoints(bool apply)
{
  m_ui->removeZeroValueCheckBox->setChecked(apply);
}

void
MzIntegrationParamsDlg::setDecimalPlaces(int decimal_places)
{
  m_ui->decimalPlacesSpinBox->setValue(decimal_places);
}

void
MzIntegrationParamsDlg::setBinSizeDivisor(int divisor)
{
  Q_ASSERT(divisor > 0);

  m_ui->binSizeDivisorSpinBox->setValue(divisor);
}

void
MzIntegrationParamsDlg::keyPressEvent(QKeyEvent *event)
{
  // If Ctrl return, apply and close.

  if(event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier)
    {
      applyPushButtonClicked();

      close();
    }
}

void
MzIntegrationParamsDlg::show()
{
  readSettings();

  QDialog::show();
}

void
MzIntegrationParamsDlg::applyPushButtonClicked()
{
  // qDebug();

  // We need to check all the widgets, extract their information and then
  // craft a pappso::MzIntegrationParams instance that we'll pass to any
  // widget (the trace plot composite widget) that might be listening. We make
  // a new instance by copying the member instance because that member
  // instance has data that are not configured in this dialog. We would loose
  // those data if we created a default mz integration params instance.

  pappso::MzIntegrationParams mz_integration_params;
  mz_integration_params.initialize(*mp_mzIntegrationParams);

  // qDebug().noquote() << "Before reading all the widgets' values::"
  //                    << mz_integration_params.toString(0, " ");

  if(!m_ui->binCreationStrategyGroupBox->isChecked())
    {
      mz_integration_params.setBinningType(
        pappso::MzIntegrationParams::BinningType::NONE);
    }
  else if(m_ui->arbitraryBinningRadioButton->isChecked())
    {
      mz_integration_params.setBinningType(
        pappso::MzIntegrationParams::BinningType::ARBITRARY);
    }
  else if(m_ui->dataBasedBinningRadioButton->isChecked())
    {
      mz_integration_params.setBinningType(
        pappso::MzIntegrationParams::BinningType::DATA_BASED);
    }
  else
    qFatal() << "Programming error.";

  mz_integration_params.setBinSizeModel(mp_binSizeModel);

  mz_integration_params.setDecimalPlaces(m_ui->decimalPlacesSpinBox->value());

  mz_integration_params.setBinSizeDivisor(m_ui->binSizeDivisorSpinBox->value());

  mz_integration_params.setRemoveZeroValDataPoints(
    m_ui->removeZeroValueCheckBox->isChecked());

  // Finally set back to the member datum the elaborated params.
  mp_mzIntegrationParams->initialize(mz_integration_params);

  // qDebug().noquote() << "MzIntegrationParamsDlg: the newly set parameters:"
  //                    << mp_mzIntegrationParams->toString(0, " ");

  // Now handle the saving

  if(m_ui->setAsDefaultParamsCheckBox->isChecked())
    {
      dynamic_cast<Application *>(qApp)->saveMzIntegrationParamsToSettings(
        *mp_mzIntegrationParams);
    }
  else
    {
      dynamic_cast<Application *>(qApp)->eraseMzIntegrationParamsFromSettings();
    }

  emit mzIntegrationParamsChangedSignal(*mp_mzIntegrationParams);
}


} // namespace MineXpert
} // namespace MsXpS
