/* 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 <QMessageBox>
#include <QFileDialog>
#include <QApplication>


/////////////////////// Local includes
#include <massxpert/gui/MassSearchDlg.hpp>


namespace msXpSmassXpert
{

MassSearchDlg::MassSearchDlg(SequenceEditorWnd *editorWnd,
                             Polymer *polymer,
                             /* no PolChemDef **/
                             const QString &configSettingsFilePath,
                             const CalcOptions &calcOptions,
                             msXpSlibmass::IonizeRule *ionizeRule)
  : AbstractSeqEdWndDependentDlg(editorWnd,
                                 polymer,
                                 0, /*polChemDef*/
                                 configSettingsFilePath,
                                 "MassSearchDlg",
                                 "Mass search calculations"),
    m_calcOptions{calcOptions},
    mp_ionizeRule{ionizeRule}
{
  if(!mp_polymer)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(!mp_ionizeRule)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(!initialize())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
}


MassSearchDlg::~MassSearchDlg()
{
  delete mpa_resultsString;

  delete mpa_monoOligomerTableViewModel;

  delete mpa_monoProxyModel;

  delete mpa_avgOligomerTableViewModel;

  delete mpa_avgProxyModel;

  freeAllOligomerLists();
}


void
MassSearchDlg::readSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.oligomersSplitter->restoreState(
    settings.value("oligomersSplitter").toByteArray());
  m_ui.oligoDetailsSplitter->restoreState(
    settings.value("oligoDetailsSplitter").toByteArray());
  m_ui.tableViewsSplitter->restoreState(
    settings.value("tableViewsSplitter").toByteArray());
  settings.endGroup();
}


void
MassSearchDlg::writeSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);
  settings.beginGroup(m_wndTypeName);
  settings.setValue("geometry", saveGeometry());
  settings.setValue("oligomersSplitter", m_ui.oligomersSplitter->saveState());
  settings.setValue("oligoDetailsSplitter",
                    m_ui.oligoDetailsSplitter->saveState());
  settings.setValue("tableViewsSplitter", m_ui.tableViewsSplitter->saveState());
  settings.endGroup();
}


bool
MassSearchDlg::initialize()
{
  m_ui.setupUi(this);

  m_ui.delimiterLineEdit->setText("$");

  // Set the current whole/selected sequence data in the
  // m_calcOptions instance and in the corresponding widgets (true =
  // read from sequence editor window).
  updateSelectionData(true);

  m_monoTolerance = 0;
  m_avgTolerance  = 0;

  m_ionizeStart = 0;
  m_ionizeEnd   = 0;

  m_currentMass      = 0;
  m_foundOligosCount = 0;

  m_abort            = false;
  m_sequenceEmbedded = false;

  populateToleranceTypeComboBoxes();

  setupTableViews();

  m_ui.ionizationStartSpinBox->setRange(-1000000000, 1000000000);
  m_ui.ionizationStartSpinBox->setValue(1);

  m_ui.ionizationEndSpinBox->setRange(-1000000000, 1000000000);
  m_ui.ionizationEndSpinBox->setValue(1);

  m_ui.massSearchProgressBar->setRange(0, 100);
  m_ui.massSearchProgressBar->setValue(0);

  m_ui.avgToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));
  m_ui.monoToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.monoMassLineEdit->setText("1");
  m_ui.avgMassLineEdit->setText("1");


  // The tolerance when filtering mono/avg masses...
  QStringList stringList;

  stringList << tr("AMU") << tr("PCT") << tr("PPM");

  m_ui.monoFilterToleranceComboBox->insertItems(0, stringList);

  m_ui.monoFilterToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.avgFilterToleranceComboBox->insertItems(0, stringList);

  m_ui.avgFilterToleranceComboBox->setToolTip(
    tr("AMU: atom mass unit \n"
       "PCT: percent \n"
       "PPM: part per million"));

  m_ui.monoFilterToleranceLineEdit->setText("1");
  m_ui.avgFilterToleranceLineEdit->setText("1");

  monoFilterAct = new QAction(tr("Toggle Mono Filtering"), this);
  monoFilterAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M, Qt::Key_F));
  this->addAction(monoFilterAct);
  connect(
    monoFilterAct, SIGNAL(triggered()), this, SLOT(monoFilterOptionsToggled()));

  m_ui.monoFilteringOptionsGroupBox->addAction(monoFilterAct);

  avgFilterAct = new QAction(tr("Toggle Avg Filtering"), this);
  avgFilterAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A, Qt::Key_F));
  this->addAction(avgFilterAct);
  connect(
    avgFilterAct, SIGNAL(triggered()), this, SLOT(avgFilterOptionsToggled()));

  m_ui.avgFilteringOptionsGroupBox->addAction(avgFilterAct);

  // When the dialog box is created it is created with the groupbox
  // unchecked.
  m_ui.monoFilteringOptionsFrame->setVisible(false);
  m_ui.avgFilteringOptionsFrame->setVisible(false);

  // When the filtering group box will be opened, the focus will be on
  // the first widget of the groupbox:
  mp_monoFocusWidget = m_ui.monoFilterSearchedLineEdit;
  mp_avgFocusWidget  = m_ui.avgFilterSearchedLineEdit;

  // The results-exporting menus. ////////////////////////////////

  QStringList comboBoxItemList;

  comboBoxItemList << tr("To Clipboard") << tr("To File") << tr("Select File");

  m_ui.exportResultsComboBox->addItems(comboBoxItemList);

  connect(m_ui.exportResultsComboBox,
          SIGNAL(activated(int)),
          this,
          SLOT(exportResults(int)));

  mpa_resultsString = new QString();

  //////////////////////////////////// The results-exporting menus.

  readSettings();

  connect(m_ui.searchPushButton, SIGNAL(clicked()), this, SLOT(search()));

  connect(m_ui.abortPushButton, SIGNAL(clicked()), this, SLOT(abort()));

  connect(m_ui.updateCurrentSelectionDataPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(updateWholeSelectedSequenceData()));


  // MONO set of filtering widgets
  connect(m_ui.monoFilterSearchedLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterSearched()));

  connect(m_ui.monoFilterErrorLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterError()));

  connect(m_ui.monoFilterMonoMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterMonoMass()));

  connect(m_ui.monoFilterAvgMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(monoFilterAvgMass()));

  connect(m_ui.monoFilteringOptionsGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(monoFilterOptions(bool)));

  // AVG set of filtering widgets
  connect(m_ui.avgFilterSearchedLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterSearched()));

  connect(m_ui.avgFilterErrorLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterError()));

  connect(m_ui.avgFilterMonoMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterMonoMass()));

  connect(m_ui.avgFilterAvgMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(avgFilterAvgMass()));

  connect(m_ui.avgFilteringOptionsGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(avgFilterOptions(bool)));

  return true;
}


bool
MassSearchDlg::populateToleranceTypeComboBoxes()
{
  QStringList stringList;

  stringList << tr("AMU") << tr("PCT") << tr("PPM");

  m_ui.monoToleranceComboBox->insertItems(0, stringList);
  m_ui.avgToleranceComboBox->insertItems(0, stringList);

  return true;
}

void
MassSearchDlg::updateWholeSelectedSequenceData()
{
  updateSelectionData(true);
}


bool
MassSearchDlg::updateSelectionData(bool fromSequenceEditor)
{
  // There are two situations:

  // 1. This function is called from the constructor to setup data
  // in the whole/selected sequence groupBox widget. It gets the
  // info directly from the sequence editor window
  // (fromSequenceEditor = true). By default the param is true,
  // because this is expected for when this slot is called upon
  // clicking on the Update button.

  // 2. This function is called only to set the whole/selected
  // sequence data in the calculation options instance. The data are
  // read directly from the widgets and the sequence editor window
  // is not probed.

  if(!fromSequenceEditor)
    {
      // Only update that whole/selected sequence data in the
      // m_calcOptions from the widgettry and not from the
      // sequence. Because the user cannot modify the selection text
      // in the line edit widget, all we need to know is: whole or
      // selected sequence.

      if(m_ui.wholeSequenceRadioButton->isChecked())
        {
          // Set the "selection" to be the whole sequence.

          Coordinates coordinates(0, mp_polymer->size() - 1);
          m_calcOptions.setCoordinateList(coordinates);

          m_ui.wholeSequenceRadioButton->setChecked(true);

          return true;
        }
      // Else, we do not need to do anything. The selection is
      // updated by clicking onto the Update button.

      return true;
    }

  // At this point, we are asked to update the whole/selected
  // sequence data directly from the sequence editor window. So get
  // all these coordinates !

  CoordinateList coordList;

  bool res = mp_editorWnd->mpa_editorGraphicsView->selectionIndices(&coordList);

  if(res)
    {
      // If there are more than one region selection or if there is
      // one region selection but spanning more than one monomer,
      // then set the target to be "current selected sequence".

      if(coordList.size() > 1)
        {
          // Apparently there are multiple regions selected.

          m_ui.currentSelectionLineEdit->setText(coordList.positionsAsText());

          m_calcOptions.setCoordinateList(coordList);
        }
      else
        {
          Coordinates *coordinates = coordList.first();

          if(coordinates->start() == coordinates->end())
            {
              // Construct a string with both the monomer code and
              // its position in the sequence.

              Polymer *polymer = mp_editorWnd->polymer();
              const Monomer *monomer =
                polymer->monomerList().at(coordinates->start());

              QString text = tr("%1 at pos. %2")
                               .arg(monomer->code())
                               .arg(coordinates->start() + 1);

              m_ui.currentSelectionLineEdit->setText(text);
            }
          else
            {
              m_ui.currentSelectionLineEdit->setText(
                coordList.positionsAsText());
            }

          m_calcOptions.setCoordinateList(*coordinates);
        }

      m_ui.currentSelectionRadioButton->setChecked(true);
    }
  else
    {
      // There is no selection, set the "selection" to be the
      // whole sequence.

      Coordinates coordinates(0, mp_polymer->size() - 1);
      m_calcOptions.setCoordinateList(coordinates);

      m_ui.wholeSequenceRadioButton->setChecked(true);
    }

  // Return if there was a selection(multiple-region or not) or
  // not.
  return res;
}


bool
MassSearchDlg::checkTolerance(int type)
{
  double nominal = 0;
  bool ok        = false;

  QString text;

  if(type & msXpS::MassType::MASS_MONO)
    {
      text = m_ui.monoMassLineEdit->text();
    }
  else if(type & msXpS::MassType::MASS_AVG)
    {
      text = m_ui.avgMassLineEdit->text();
    }
  else
    Q_ASSERT(0);

  if(text.isEmpty())
    return false;

  nominal = text.toDouble(&ok);

  if(!nominal && !ok)
    return false;

  return true;
}

bool
MassSearchDlg::calculateTolerance(double value, int type)
{
  int index      = 0;
  double nominal = 0;

  bool ok = false;

  QString text;


  if(type & msXpS::MassType::MASS_MONO)
    {
      index = m_ui.monoToleranceComboBox->currentIndex();
      text  = m_ui.monoMassLineEdit->text();
    }
  else if(type & msXpS::MassType::MASS_AVG)
    {
      index = m_ui.avgToleranceComboBox->currentIndex();
      text  = m_ui.avgMassLineEdit->text();
    }
  else
    Q_ASSERT(0);


  nominal = text.toDouble(&ok);

  if(!nominal && !ok)
    return false;

  if(index == 0)
    {
      // MASS_TOLERANCE_AMU

      type &msXpS::MassType::MASS_MONO ? m_monoTolerance = nominal
                                       : m_avgTolerance  = nominal;

      return true;
    }
  else if(index == 1)
    {
      // MASS_TOLERANCE_PCT

      type &msXpS::MassType::MASS_MONO
        ? m_monoTolerance = nominal * (value / 100)
        : m_avgTolerance  = nominal * (value / 100);

      return true;
    }
  else if(index == 2)
    {
      // MASS_TOLERANCE_PPM

      type &msXpS::MassType::MASS_MONO
        ? m_monoTolerance = nominal * (value / 1000000)
        : m_avgTolerance  = nominal * (value / 1000000);

      return true;
    }
  else
    Q_ASSERT(0);

  return false;
}


bool
MassSearchDlg::parseMassText(int type)
{
  Q_ASSERT(type & msXpS::MassType::MASS_MONO ||
           type & msXpS::MassType::MASS_AVG);

  QString text;

  if(type & msXpS::MassType::MASS_MONO)
    text = m_ui.monoMassTextEdit->toPlainText();
  else if(type & msXpS::MassType::MASS_AVG)
    text = m_ui.avgMassTextEdit->toPlainText();
  else
    Q_ASSERT(0);

  QStringList splitList = text.split(QRegExp("\\s+"), QString::SkipEmptyParts);

  for(int iter = 0; iter < splitList.size(); ++iter)
    {
      bool ok = false;

      double value = splitList.at(iter).toDouble(&ok);

      if(!value && !ok)
        {
          qDebug() << "Error in mass list";

          return false;
        }

      if(type & msXpS::MassType::MASS_MONO)
        m_monoMassesList.append(value);
      else
        m_avgMassesList.append(value);
    }

  return true;
}


bool
MassSearchDlg::searchMass(double value,
                          const Coordinates &coordinates,
                          int type)
{
  //    CalcOptions localCalcOptions(*mp_calcOptions);
  msXpSlibmass::IonizeRule localIonizeRule(*mp_ionizeRule);

  int count = 0;
  int cycle = 0;

  Q_ASSERT(type & msXpS::MassType::MASS_MONO ||
           type & msXpS::MassType::MASS_AVG);

  int startIndex = coordinates.start();
  int endIndex   = coordinates.end();

  double maxValue = type & msXpS::MassType::MASS_MONO ? value + m_monoTolerance
                                                      : value + m_avgTolerance;

  double minValue = type & msXpS::MassType::MASS_MONO ? value - m_monoTolerance
                                                      : value - m_avgTolerance;

  if(minValue < 0)
    minValue = 0;

  m_currentMass = value;
  updateProgressDetails(type);

  for(int iter = startIndex; iter < endIndex + 1; ++iter)
    {
      if(m_abort)
        return true;

      for(int jter = 0; jter < endIndex + 1 - iter; ++jter)
        {
          // Note that the ionization starts from a low value and goes
          // to a higher value, that means that while this loop runs
          // for a given oligomer, the m/z ratio goes diminishing.

          for(int kter = m_ionizeStart; kter < m_ionizeEnd + 1; ++kter, ++cycle)
            {
              ++m_testedOligosCount;

              if(cycle == 200)
                {
                  cycle = 0;
                  updateProgressDetails(type);
                }

              localIonizeRule.setLevel(kter);

              Oligomer oligomerTest(mp_polymer,
                                    "NOT_SET" /*name*/,
                                    QString("") /*description*/,
                                    false /*modified*/,
                                    msXpSlibmass::Ponderable(),
                                    localIonizeRule,
                                    CalcOptions(),
                                    false,
                                    iter,
                                    iter + jter);

              oligomerTest.calculateMasses(&m_calcOptions);

              double mz = type & msXpS::MassType::MASS_MONO
                            ? oligomerTest.mono()
                            : oligomerTest.avg();

              if(mz < minValue)
                // Oligomer's m/z already too small. Break
                // immediately as this cannot get any better by
                // continuing to increase the ionization level.
                break;

              if(mz > maxValue)
                // Oligomer's m/z is still too big. Continue
                // incrementing the ionization level, so that its
                // m/z will decrease...
                continue;


              // At this point the oligomer has correct masses.

              Oligomer *oligomerNew = new Oligomer(oligomerTest);

              ++count;

              int totalCharge =
                localIonizeRule.charge() * localIonizeRule.level();

              QString name =
                QString("%1-z%2#%3").arg(value).arg(totalCharge).arg(count);

              oligomerNew->setName(name);

              // Set the searched mz in a prop.

              DoubleProp *prop = new DoubleProp("SEARCHED_MZ", value);

              oligomerNew->appendProp(prop);

              // Set the error mz in a prop.

              double error = mz - value;

              prop = new DoubleProp("ERROR_MZ", error);

              oligomerNew->appendProp(prop);

              type &msXpS::MassType::MASS_MONO
                ? mpa_monoOligomerTableViewModel->addOligomer(oligomerNew)
                : mpa_avgOligomerTableViewModel->addOligomer(oligomerNew);

              ++m_foundOligosCount;

              updateProgressDetails(type, false, oligomerNew);
            }
          // End of
          // for (int kter = m_ionizeStart; kter < m_ionizeEnd + 1; ++kter)
        }
      // End of
      // for (int jter = 0; jter < endIndex + 1 - iter; ++jter)
    }
  // End of
  // for (int iter = startIndex; iter < endIndex + 1; ++iter)

  return true;
}


bool
MassSearchDlg::searchMasses(int type)
{
  if(!mp_polymer->size())
    return false;

  const CoordinateList &coordinateList = m_calcOptions.coordinateList();

  if(type & msXpS::MassType::MASS_MONO)
    {
      for(int iter = 0; iter < m_monoMassesList.size(); ++iter)
        {
          double value = m_monoMassesList.at(iter);

          if(!calculateTolerance(value, msXpS::MassType::MASS_MONO))
            return false;

          for(int iter = 0; iter < coordinateList.size(); ++iter)
            {
              if(!searchMass(value,
                             *(coordinateList.at(iter)),
                             msXpS::MassType::MASS_MONO))
                return false;

              m_ui.massSearchProgressBar->setValue(++m_progressValue);

              if(m_abort)
                return true;
            }
        }
    }
  else if(type & msXpS::MassType::MASS_AVG)
    {
      for(int iter = 0; iter < m_avgMassesList.size(); ++iter)
        {
          double value = m_avgMassesList.at(iter);

          if(!calculateTolerance(value, msXpS::MassType::MASS_AVG))
            return false;

          for(int iter = 0; iter < coordinateList.size(); ++iter)
            {
              if(!searchMass(value,
                             *(coordinateList.at(iter)),
                             msXpS::MassType::MASS_AVG))
                return false;

              m_ui.massSearchProgressBar->setValue(++m_progressValue);

              if(m_abort)
                return true;
            }
        }
    }
  else
    Q_ASSERT(0);

  return true;
}


void
MassSearchDlg::search()
{
  // Update the options from the parent window.
  m_calcOptions = mp_editorWnd->calcOptions();

  // Set the current whole/selected sequence data in the
  // m_calcOptions instance (false = no read from sequence editor
  // window).
  updateSelectionData(false);

  updateIonizationData();

  // Empty the treeviews and the lists of oligomers.
  mpa_monoOligomerTableViewModel->removeOligomers(0, -1);
  mpa_avgOligomerTableViewModel->removeOligomers(0, -1);

  emptyAllMassLists();

  m_abort = false;

  if(!parseMassText(msXpS::MassType::MASS_MONO))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Mass Search"),
                           tr("%1@%2\n"
                              "Failed to parse the masses to search for MONO.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return;
    }

  if(!parseMassText(msXpS::MassType::MASS_AVG))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Mass Search"),
                           tr("%1@%2\n"
                              "Failed to parse the masses to search for AVG.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      return;
    }

  if(m_monoMassesList.size())
    {
      if(!checkTolerance(msXpS::MassType::MASS_MONO))
        {
          QMessageBox::information(this,
                                   tr("massxpert - Mass Search"),
                                   tr("Enter a valid MONO tolerance value"),
                                   QMessageBox::Ok);
          return;
        }
    }

  if(m_avgMassesList.size())
    {
      if(!checkTolerance(msXpS::MassType::MASS_AVG))
        {
          QMessageBox::information(this,
                                   tr("massxpert - Mass Search"),
                                   tr("Enter a valid AVG tolerance value"),
                                   QMessageBox::Ok);
          return;
        }
    }

  setCursor(Qt::WaitCursor);

  m_testedOligosCount = 0;
  m_foundOligosCount  = 0;

  int massNumber  = m_monoMassesList.size() + m_avgMassesList.size();
  m_progressValue = 0;
  m_ui.massSearchProgressBar->setRange(0, massNumber);

  updateProgressDetails(msXpS::MassType::MASS_MONO, true);
  updateProgressDetails(msXpS::MassType::MASS_AVG, true);

  if(!searchMasses(msXpS::MassType::MASS_MONO))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Mass Search"),
                           tr("%1@%2\n"
                              "Failed to search for the MONO masses.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      setCursor(Qt::ArrowCursor);
      updateMassSearchDetails(m_calcOptions);

      return;
    }

  if(!searchMasses(msXpS::MassType::MASS_AVG))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Mass Search"),
                           tr("%1@%2\n"
                              "Failed to search for the AVG masses.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      setCursor(Qt::ArrowCursor);
      updateMassSearchDetails(m_calcOptions);

      return;
    }

  setCursor(Qt::ArrowCursor);

  updateMassSearchDetails(m_calcOptions);
}


void
MassSearchDlg::setupTableViews()
{
  // Model stuff all thought for sorting.
  mpa_monoOligomerTableViewModel =
    new MassSearchOligomerTableViewModel(&m_monoOligomerList, this);

  mpa_avgOligomerTableViewModel =
    new MassSearchOligomerTableViewModel(&m_avgOligomerList, this);

  mpa_monoProxyModel = new MassSearchOligomerTableViewSortProxyModel(this);
  mpa_monoProxyModel->setSourceModel(mpa_monoOligomerTableViewModel);
  mpa_monoProxyModel->setFilterKeyColumn(-1);

  mpa_avgProxyModel = new MassSearchOligomerTableViewSortProxyModel(this);
  mpa_avgProxyModel->setSourceModel(mpa_avgOligomerTableViewModel);
  mpa_avgProxyModel->setFilterKeyColumn(-1);

  m_ui.monoOligomerTableView->setModel(mpa_monoProxyModel);
  m_ui.monoOligomerTableView->setParentDlg(this);
  m_ui.monoOligomerTableView->setOligomerList(&m_monoOligomerList);
  mpa_monoOligomerTableViewModel->setTableView(m_ui.monoOligomerTableView);

  m_ui.avgOligomerTableView->setModel(mpa_avgProxyModel);
  m_ui.avgOligomerTableView->setParentDlg(this);
  m_ui.avgOligomerTableView->setOligomerList(&m_avgOligomerList);
  mpa_avgOligomerTableViewModel->setTableView(m_ui.avgOligomerTableView);
}


void
MassSearchDlg::updateIonizationData()
{
  m_ionizeStart = m_ui.ionizationStartSpinBox->value();
  m_ionizeEnd   = m_ui.ionizationEndSpinBox->value();

  if(m_ionizeStart > m_ionizeEnd)
    {
      int temp      = m_ionizeStart;
      m_ionizeStart = m_ionizeEnd;
      m_ionizeEnd   = temp;
    }
}


void
MassSearchDlg::updateProgressDetails(int type, bool reset, Oligomer *oligomer)
{
  if(reset)
    {
      m_ui.lastOligoNameLineEdit->setText("");
      m_ui.lastOligoCoordinatesLineEdit->setText("");
      m_ui.lastOligoMonoMassLineEdit->setText("");
      m_ui.lastOligoAvgMassLineEdit->setText("");

      m_ui.currentMassLineEdit->setText("");

      m_ui.lastOligoMassTypeLineEdit->setText("");

      m_ui.testedCountLabel->setText(QString().setNum(0));
      m_ui.foundCountLabel->setText(QString().setNum(0));

      m_ui.massSearchProgressBar->setValue(0);

      QApplication::processEvents();

      return;
    }

  if(oligomer)
    {
      m_ui.lastOligoNameLineEdit->setText(oligomer->name());
      QString coordinates(tr("[ %1 - %2 ]")
                            .arg(oligomer->startIndex() + 1)
                            .arg(oligomer->endIndex() + 1));

      m_ui.lastOligoCoordinatesLineEdit->setText(coordinates);

      QString mass = oligomer->monoString(msXpS::OLIGOMER_DEC_PLACES);
      m_ui.lastOligoMonoMassLineEdit->setText(mass);

      mass = oligomer->avgString(msXpS::OLIGOMER_DEC_PLACES);
      m_ui.lastOligoAvgMassLineEdit->setText(mass);
    }

  m_ui.currentMassLineEdit->setText(QString().setNum(m_currentMass, 'g', 10));

  type &msXpS::MassType::MASS_MONO
    ? m_ui.lastOligoMassTypeLineEdit->setText(tr("MONO"))
    : m_ui.lastOligoMassTypeLineEdit->setText(tr("AVG"));

  m_ui.testedCountLabel->setText(QString().setNum(m_testedOligosCount));
  m_ui.foundCountLabel->setText(QString().setNum(m_foundOligosCount));

  m_ui.massSearchProgressBar->setValue(m_progressValue);

  QApplication::processEvents();
}


void
MassSearchDlg::updateMassSearchDetails(const CalcOptions &calcOptions)
{
  if(calcOptions.monomerEntities() & MONOMER_CHEMENT_MODIF)
    m_ui.modificationsCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.modificationsCheckBox->setCheckState(Qt::Unchecked);

  if(calcOptions.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF)
    m_ui.leftModifCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.leftModifCheckBox->setCheckState(Qt::Unchecked);

  if(calcOptions.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF)
    m_ui.rightModifCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.rightModifCheckBox->setCheckState(Qt::Unchecked);
}


void
MassSearchDlg::updateOligomerSequence(QString *text)
{
  Q_ASSERT(text);

  m_ui.oligomerSequenceTextEdit->clear();
  m_ui.oligomerSequenceTextEdit->append(*text);
}


void
MassSearchDlg::freeAllOligomerLists()
{
  while(!m_monoOligomerList.isEmpty())
    delete m_monoOligomerList.takeFirst();

  while(!m_avgOligomerList.isEmpty())
    delete m_avgOligomerList.takeFirst();
}


void
MassSearchDlg::emptyAllMassLists()
{
  while(!m_monoMassesList.isEmpty())
    m_monoMassesList.removeFirst();

  while(!m_avgMassesList.isEmpty())
    m_avgMassesList.removeFirst();
}


void
MassSearchDlg::abort()
{
  m_abort = true;
}


bool
MassSearchDlg::calculateFilterTolerance(double mass, int massType)
{
  // Get the tolerance that is in its lineEdit.
  QString text;

  if(massType == msXpS::MassType::MASS_MONO)
    text = m_ui.monoFilterToleranceLineEdit->text();
  else if(massType == msXpS::MassType::MASS_AVG)
    text = m_ui.avgFilterToleranceLineEdit->text();
  else
    Q_ASSERT(0);

  double tolerance = 0;
  bool ok          = false;

  if(!text.isEmpty())
    {
      // Convert the string to a double.
      ok        = false;
      tolerance = text.toDouble(&ok);

      if(!tolerance && !ok)
        return false;
    }
  else
    {
      m_filterTolerance = 0;
    }

  // What's the item currently selected in the comboBox?
  int index = 0;

  if(massType == msXpS::MassType::MASS_MONO)
    index = m_ui.monoFilterToleranceComboBox->currentIndex();
  else if(massType == msXpS::MassType::MASS_AVG)
    index = m_ui.avgFilterToleranceComboBox->currentIndex();
  else
    Q_ASSERT(0);

  if(index == 0)
    {
      // MASS_TOLERANCE_AMU
      m_filterTolerance = tolerance;
    }
  else if(index == 1)
    {
      // MASS_TOLERANCE_PCT
      m_filterTolerance = (tolerance / 100) * mass;
    }
  else if(index == 2)
    {
      // MASS_TOLERANCE_PPM
      m_filterTolerance = (tolerance / 1000000) * mass;
    }
  else
    Q_ASSERT(0);

  return true;
}


void
MassSearchDlg::monoFilterOptions(bool checked)
{
  if(!checked)
    {
      mpa_monoProxyModel->setFilterKeyColumn(-1);

      mpa_monoProxyModel->applyNewFilter();

      m_ui.monoFilteringOptionsFrame->setVisible(false);
    }
  else
    {
      m_ui.monoFilteringOptionsFrame->setVisible(true);

      // In this case, set focus to the last focused widget in the
      // groupbox or the first widget in the groubox if this is the
      // first time the filtering is used.
      mp_monoFocusWidget->setFocus();
    }
}


void
MassSearchDlg::monoFilterOptionsToggled()
{
  bool isChecked = m_ui.monoFilteringOptionsGroupBox->isChecked();

  m_ui.monoFilteringOptionsGroupBox->setChecked(!isChecked);
  monoFilterOptions(!isChecked);
}


void
MassSearchDlg::avgFilterOptions(bool checked)
{
  if(!checked)
    {
      mpa_avgProxyModel->setFilterKeyColumn(-1);

      mpa_avgProxyModel->applyNewFilter();

      m_ui.avgFilteringOptionsFrame->setVisible(false);
    }
  else
    {
      m_ui.avgFilteringOptionsFrame->setVisible(true);

      // In this case, set focus to the last focused widget in the
      // groupbox or the first widget in the groubox if this is the
      // first time the filtering is used.
      mp_avgFocusWidget->setFocus();
    }
}


void
MassSearchDlg::avgFilterOptionsToggled()
{
  bool isChecked = m_ui.avgFilteringOptionsGroupBox->isChecked();

  m_ui.avgFilteringOptionsGroupBox->setChecked(!isChecked);
  avgFilterOptions(!isChecked);
}


void
MassSearchDlg::monoFilterSearched()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.monoFilterSearchedLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  mpa_monoProxyModel->setSearchedFilter(mass);

  mpa_monoProxyModel->setFilterKeyColumn(0);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterSearchedLineEdit;
}


void
MassSearchDlg::avgFilterSearched()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.avgFilterSearchedLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  mpa_avgProxyModel->setSearchedFilter(mass);

  mpa_avgProxyModel->setFilterKeyColumn(0);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterSearchedLineEdit;
}


void
MassSearchDlg::monoFilterError()
{
  // First off, we have to get the mass error that is in the lineEdit.

  QString text = m_ui.monoFilterErrorLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_MONO))
    return;

  mpa_monoProxyModel->setErrorFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(3);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterErrorLineEdit;
}


void
MassSearchDlg::avgFilterError()
{
  // First off, we have to get the mass error that is in the lineEdit.

  QString text = m_ui.avgFilterErrorLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_AVG))
    return;

  mpa_avgProxyModel->setErrorFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(3);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterErrorLineEdit;
}


void
MassSearchDlg::monoFilterMonoMass()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.monoFilterMonoMassLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_MONO))
    return;

  mpa_monoProxyModel->setMonoFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(4);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterMonoMassLineEdit;
}


void
MassSearchDlg::avgFilterMonoMass()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.avgFilterMonoMassLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_AVG))
    return;

  mpa_avgProxyModel->setMonoFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(4);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterMonoMassLineEdit;
}


void
MassSearchDlg::monoFilterAvgMass()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.monoFilterAvgMassLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_MONO))
    return;

  mpa_monoProxyModel->setAvgFilter(mass);
  mpa_monoProxyModel->setTolerance(m_filterTolerance);

  mpa_monoProxyModel->setFilterKeyColumn(5);
  mpa_monoProxyModel->applyNewFilter();

  mp_monoFocusWidget = m_ui.monoFilterAvgMassLineEdit;
}


void
MassSearchDlg::avgFilterAvgMass()
{
  // First off, we have to get the mass that is in the lineEdit.

  QString text = m_ui.avgFilterAvgMassLineEdit->text();

  if(text.isEmpty())
    return;

  // Convert the string to a double.
  bool ok     = false;
  double mass = text.toDouble(&ok);

  if(!mass && !ok)
    return;

  // At this point, depending on the item that is currently selected
  // in the comboBox, we'll have to actually compute the tolerance.

  if(!calculateFilterTolerance(mass, msXpS::MassType::MASS_AVG))
    return;

  mpa_avgProxyModel->setAvgFilter(mass);
  mpa_avgProxyModel->setTolerance(m_filterTolerance);

  mpa_avgProxyModel->setFilterKeyColumn(5);
  mpa_avgProxyModel->applyNewFilter();

  mp_avgFocusWidget = m_ui.avgFilterAvgMassLineEdit;
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
MassSearchDlg::exportResults(int index)
{
  // Remember that we had set up the combobox with the following strings:
  // << tr("To &Clipboard")
  // << tr("To &File")
  // << tr("&Select File");

  if(index == 0)
    {
      exportResultsClipboard();
    }
  else if(index == 1)
    {
      exportResultsFile();
    }
  else if(index == 2)
    {
      selectResultsFile();
    }
  else
    Q_ASSERT(0);
}


void
MassSearchDlg::prepareResultsTxtString()
{
  // Set the delimiter to the char/string that is in the
  // corresponding text line edit widget.

  QString delimiter = m_ui.delimiterLineEdit->text();
  // If delimiter is empty, the function that will prepare the
  // string will put the default character, that is '$'.

  mpa_resultsString->clear();

  // We only put the header info if the user does not want to have
  // the data formatted for XpertMiner, which only can get the data
  // in the format :
  //
  // mass <delim> charge <delim> name <delim> coords
  //
  // Note that name and coords are optional.
  bool forXpertMiner       = false;
  msXpS::MassType massType = msXpS::MassType::MASS_NONE;

  forXpertMiner = m_ui.forXpertMinerCheckBox->isChecked();

  if(!forXpertMiner)
    {
      *mpa_resultsString += QObject::tr(
        "# \n"
        "# ---------------------------\n"
        "# Mass Search: \n"
        "# ---------------------------\n");
    }
  else
    {
      // Then, we should ask whether the masses to be exported are
      // the mono or avg masses.

      if(m_ui.monoRadioButton->isChecked())
        massType = msXpS::MassType::MASS_MONO;
      else
        massType = msXpS::MassType::MASS_AVG;
    }


  // Should the oligomers have their sequence displayed?
  bool withSequence = m_ui.withSequenceCheckBox->isChecked();

  QString masses;

  // We only show the list of masses if we are exporting not for
  // XpertMiner.

  if(!forXpertMiner)
    {
      // Get the mono masses searched:
      masses = m_ui.monoMassTextEdit->toPlainText();
      if(!masses.isEmpty())
        {
          *mpa_resultsString += QObject::tr(
                                  "\nSearched mono masses:\n"
                                  "%1\n")
                                  .arg(masses);
        }
    }

  QString *text = 0;

  // We export mono data only in two situations: when the data is
  // for XpertMiner and the user has selected the Mono radio button
  // or when the data is not for XpertMiner.

  if((!forXpertMiner) || (forXpertMiner && m_ui.monoRadioButton->isChecked()))
    {
      // The mono treeview:

      text = m_ui.monoOligomerTableView->selectedOligomersAsPlainText(
        delimiter, withSequence, forXpertMiner, massType);

      *mpa_resultsString += *text;

      delete text;
    }

  if(!forXpertMiner)
    {
      // Get the avg masses searched:
      masses = m_ui.avgMassTextEdit->toPlainText();
      if(!masses.isEmpty())
        {
          *mpa_resultsString += QObject::tr(
                                  "\nSearched average masses:\n"
                                  "%1\n")
                                  .arg(masses);
        }
    }


  // We export mono data only in two situations: when the data is
  // for XpertMiner and the user has selected the Mono radio button
  // or when the data is not for XpertMiner.

  if((!forXpertMiner) || (forXpertMiner && m_ui.avgRadioButton->isChecked()))
    {
      // The avg treeview:

      text = m_ui.avgOligomerTableView->selectedOligomersAsPlainText(
        delimiter, withSequence, forXpertMiner, massType);

      *mpa_resultsString += *text;

      delete text;
    }
}


bool
MassSearchDlg::exportResultsClipboard()
{
  prepareResultsTxtString();

  QClipboard *clipboard = QApplication::clipboard();

  clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);

  return true;
}


bool
MassSearchDlg::exportResultsFile()
{
  if(m_resultsFilePath.isEmpty())
    {
      if(!selectResultsFile())
        return false;
    }

  QFile file(m_resultsFilePath);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      QMessageBox::information(0,
                               tr("massXpert - Export Data"),
                               tr("Failed to open file in append mode."),
                               QMessageBox::Ok);
      return false;
    }

  QTextStream stream(&file);
  stream.setCodec("UTF-8");

  prepareResultsTxtString();

  stream << *mpa_resultsString;

  file.close();

  return true;
}


bool
MassSearchDlg::selectResultsFile()
{
  m_resultsFilePath =
    QFileDialog::getSaveFileName(this,
                                 tr("Select File To Export Data To"),
                                 QDir::homePath(),
                                 tr("Data files(*.dat *.DAT)"));

  if(m_resultsFilePath.isEmpty())
    return false;

  return true;
}
//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.

} // namespace msXpSmassXpert
