/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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>


/////////////////////// Local includes
#include <minexpert/gui/MzIntegrationParamsWnd.hpp>
#include <globals/globals.hpp>


namespace msXpSmineXpert
{

MzIntegrationParamsWnd::MzIntegrationParamsWnd(QWidget *parent)
  : QMainWindow{parent}
{
  if(parent == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer to the parent window cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  m_ui.setupUi(this);

  initialize();
}


MzIntegrationParamsWnd::~MzIntegrationParamsWnd()
{
  writeSettings();
}


void
MzIntegrationParamsWnd::closeEvent(QCloseEvent *event)
{
  writeSettings();
  event->accept();
}


void
MzIntegrationParamsWnd::show()
{
  QMainWindow::show();
  raise();
  activateWindow();
}


void
MzIntegrationParamsWnd::readSettings()
{
  QSettings settings;
  settings.beginGroup("MzIntegrationParamsWnd");

  // Restore the geometry of the window.
  restoreGeometry(settings.value("geometry").toByteArray());

  bool wasVisible = settings.value("visible").toBool();
  setVisible(wasVisible);

  settings.endGroup();
}


void
MzIntegrationParamsWnd::writeSettings()
{
  QSettings settings;
  settings.beginGroup("MzIntegrationParamsWnd");

  settings.setValue("geometry", saveGeometry());
  settings.setValue("visible", isVisible());

  settings.endGroup();
}


void
MzIntegrationParamsWnd::initialize()
{
  readSettings();

  /********* Integration parameters **********/

  QStringList typeList;

  // Fill in the mass tolerance type strings in the combo box.
  QList<int> keys = msXpS::massToleranceTypeMap.keys();

  for(int iter = 0; iter < keys.size(); ++iter)
    {
      int key = keys.at(iter);

      if(key == msXpS::MassToleranceType::MASS_TOLERANCE_NONE)
        continue;

      if(key == msXpS::MassToleranceType::MASS_TOLERANCE_LAST)
        continue;

      m_ui.typeComboBox->insertItem(
        iter, msXpS::massToleranceTypeMap.value(key), key);
    }


  // Connect all radio buttons to the same slot, we'll then check which is
  // checked.
  connect(m_ui.noBinningRadioButton,
          &QRadioButton::toggled,
          this,
          &MzIntegrationParamsWnd::binningRadioButtonToggled);

  connect(m_ui.dataBasedBinningRadioButton,
          &QRadioButton::toggled,
          this,
          &MzIntegrationParamsWnd::binningRadioButtonToggled);

  connect(m_ui.arbitraryBinningRadioButton,
          &QRadioButton::toggled,
          this,
          &MzIntegrationParamsWnd::binningRadioButtonToggled);

  connect(
    m_ui.typeComboBox,
    static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
    this,
    static_cast<void (MzIntegrationParamsWnd::*)(int)>(
      &MzIntegrationParamsWnd::binSizeTypeItemChanged));

  connect(m_ui.valueDoubleSpinBox,
          SIGNAL(valueChanged(double)),
          this,
          SLOT(binSizeValueChanged(double)));

  connect(m_ui.applyMzShiftCheckBox,
          &QCheckBox::stateChanged,
          this,
          &MzIntegrationParamsWnd::applyMzShiftCheckBoxStateChanged);

  connect(m_ui.removeZeroValueCheckBox,
          &QCheckBox::stateChanged,
          this,
          &MzIntegrationParamsWnd::removeZeroValueCheckBoxStateChanged);

  m_mzIntegrationParams.m_binningType = msXpS::BinningType::BINNING_TYPE_NONE;
  m_ui.noBinningRadioButton->setChecked(true);

  /********* Savitzky-Golay parameters **********/

  // First, we do not want the savgol to be applied by default:
  m_ui.savitzkyGolayGroupBox->setChecked(false);

  // However, set the default values in it.

  m_ui.nlSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.nL);
  m_ui.nrSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.nR);
  m_ui.mSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.m);
  m_ui.ldSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.lD);

  connect(m_ui.savitzkyGolayGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(savitzkyGolayGroupBoxClicked(bool)));

  connect(
    m_ui.nlSpinBox, SIGNAL(valueChanged(int)), this, SLOT(sgValueChanged(int)));

  connect(
    m_ui.nrSpinBox, SIGNAL(valueChanged(int)), this, SLOT(sgValueChanged(int)));

  connect(
    m_ui.mSpinBox, SIGNAL(valueChanged(int)), this, SLOT(sgValueChanged(int)));

  connect(
    m_ui.ldSpinBox, SIGNAL(valueChanged(int)), this, SLOT(sgValueChanged(int)));
}


void
MzIntegrationParamsWnd::updateMzIntegrationParams(MzIntegrationParams params,
                                                  const QColor &color)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The params data from the plot widget via the tic chrom wnd:" <<
  //params.asText();

  m_mzIntegrationParams = params;

  // Now update all the values in the different controls.

  m_ui.noBinningRadioButton->setChecked(params.m_binningType ==
                                        msXpS::BinningType::BINNING_TYPE_NONE);
  m_ui.arbitraryBinningRadioButton->setChecked(
    params.m_binningType == msXpS::BinningType::BINNING_TYPE_ARBITRARY);
  m_ui.dataBasedBinningRadioButton->setChecked(
    params.m_binningType == msXpS::BinningType::BINNING_TYPE_DATA_BASED);

  m_ui.valueDoubleSpinBox->setValue(params.m_binSize);
  m_ui.typeComboBox->setCurrentText(
    msXpS::massToleranceTypeMap.value(params.m_binSizeType));
  m_ui.applyMzShiftCheckBox->setChecked(params.m_applyMzShift ? true : false);
  m_ui.removeZeroValueCheckBox->setCheckState(
    params.m_removeZeroValDataPoints ? Qt::Checked : Qt::Unchecked);

  m_ui.savitzkyGolayGroupBox->setChecked(
    m_mzIntegrationParams.m_applySavGolFilter);

  m_ui.nlSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.nL);
  m_ui.nrSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.nR);
  m_ui.mSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.m);
  m_ui.ldSpinBox->setValue(m_mzIntegrationParams.m_savGolParams.lD);

  // Finally, we need to set the color to the flag horizontal line

  QPalette pal = palette();
  pal.setColor(QPalette::Background, color);
  m_ui.topHorLine->setAutoFillBackground(true);
  m_ui.topHorLine->setPalette(pal);
}


void
MzIntegrationParamsWnd::binningRadioButtonToggled(bool checked)
{
  QRadioButton *radioButton = static_cast<QRadioButton *>(QObject::sender());

  if(radioButton == m_ui.noBinningRadioButton)
    {
      if(checked == true)
        m_mzIntegrationParams.m_binningType =
          msXpS::BinningType::BINNING_TYPE_NONE;
    }
  else if(radioButton == m_ui.arbitraryBinningRadioButton)
    {
      if(checked == true)
        m_mzIntegrationParams.m_binningType =
          msXpS::BinningType::BINNING_TYPE_ARBITRARY;
    }
  else if(radioButton == m_ui.dataBasedBinningRadioButton)
    {
      if(checked == true)
        m_mzIntegrationParams.m_binningType =
          msXpS::BinningType::BINNING_TYPE_DATA_BASED;
    }
  else
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "This line cannot be reached."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::binSizeTypeItemChanged(int index)
{
  m_mzIntegrationParams.m_binSizeType = static_cast<msXpS::MassToleranceType>(
    m_ui.typeComboBox->itemData(index).toInt());

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::binSizeValueChanged(double value)
{
  m_mzIntegrationParams.m_binSize = value;

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::applyMzShiftCheckBoxStateChanged(int state)
{
  m_mzIntegrationParams.m_applyMzShift = m_ui.applyMzShiftCheckBox->isChecked();

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::removeZeroValueCheckBoxStateChanged(int state)
{
  m_mzIntegrationParams.m_removeZeroValDataPoints =
    m_ui.removeZeroValueCheckBox->isChecked();

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::savitzkyGolayGroupBoxClicked(bool checked)
{
  m_mzIntegrationParams.m_applySavGolFilter = checked;

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::sgValueChanged(int value)
{
  QSpinBox *spinBox = static_cast<QSpinBox *>(QObject::sender());

  if(spinBox == m_ui.nlSpinBox)
    m_mzIntegrationParams.m_savGolParams.nL = spinBox->value();
  else if(spinBox == m_ui.nrSpinBox)
    m_mzIntegrationParams.m_savGolParams.nR = spinBox->value();
  else if(spinBox == m_ui.mSpinBox)
    m_mzIntegrationParams.m_savGolParams.m = spinBox->value();
  else if(spinBox == m_ui.ldSpinBox)
    m_mzIntegrationParams.m_savGolParams.lD = spinBox->value();
  else
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "This line cannot be reached."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


QString
MzIntegrationParamsWnd::paramsAsText() const
{
  return m_mzIntegrationParams.asText();
}


void
MzIntegrationParamsWnd::setBinningType(msXpS::BinningType binningType)
{
  if(binningType == msXpS::BinningType::BINNING_TYPE_NONE)
    m_ui.noBinningRadioButton->setChecked(true);
  else if(binningType == msXpS::BinningType::BINNING_TYPE_DATA_BASED)
    m_ui.dataBasedBinningRadioButton->setChecked(true);
  else if(binningType == msXpS::BinningType::BINNING_TYPE_ARBITRARY)
    m_ui.arbitraryBinningRadioButton->setChecked(true);
  else
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Cannot reach this line."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);
}


void
MzIntegrationParamsWnd::setBinSize(double value)
{
  m_ui.valueDoubleSpinBox->setValue(value);
  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::setBinSizeType(msXpS::MassToleranceType type)
{
  m_ui.typeComboBox->setCurrentText(msXpS::massToleranceTypeMap.value(type));
  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}

void
MzIntegrationParamsWnd::setApplyMzShift(bool apply)
{
  m_ui.applyMzShiftCheckBox->setChecked(apply);
  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


void
MzIntegrationParamsWnd::setRemoveZeroValDataPoints(bool apply)
{
  m_ui.removeZeroValueCheckBox->setChecked(apply);
  emit mzIntegrationParamsChangedSignal(m_mzIntegrationParams);
}


} // namespace msXpSmineXpert
