/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.


   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Qt includes
#include<QMessageBox>
#include<QFileDialog>

/////////////////////// Local includes
#include "massSearchDlg.hpp"
#include "application.hpp"


namespace massXpert
{

  MassSearchDlg::MassSearchDlg(QWidget *parent,
                               Polymer *polymer,
                               const CalcOptions &calcOptions,
                               IonizeRule *ionizeRule)
    : QDialog(parent),
      mp_polymer(polymer),
      m_calcOptions(calcOptions),
      mp_ionizeRule(ionizeRule)
  {
    Q_ASSERT(parent);
    Q_ASSERT(mp_polymer && mp_ionizeRule);

    m_ui.setupUi(this);

    mp_editorWnd = static_cast<SequenceEditorWnd *>(parent);

    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.


    QSettings settings
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("mass_search_dlg");

    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();


    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)));
  }


  MassSearchDlg::~MassSearchDlg()
  {
    delete mpa_resultsString;

    delete mpa_monoOligomerTableViewModel;

    delete mpa_monoProxyModel;

    delete mpa_avgOligomerTableViewModel;

    delete mpa_avgProxyModel;

    freeAllOligomerLists();
  }


  void
  MassSearchDlg::closeEvent(QCloseEvent *event)
  {
    if (event)
      printf("%s", "");

    QSettings settings
      (static_cast<Application *>(qApp)->configSettingsFilePath(),
       QSettings::IniFormat);

    settings.beginGroup("mass_search_dlg");

    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();
  }


  SequenceEditorWnd *
  MassSearchDlg::editorWnd()
  {
    return mp_editorWnd;
  }


  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 & MXT_MASS_MONO)
      {
        text = m_ui.monoMassLineEdit->text();
      }
    else if (type & MXT_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 & MXT_MASS_MONO)
      {
        index = m_ui.monoToleranceComboBox->currentIndex();
        text = m_ui.monoMassLineEdit->text();
      }
    else if (type & MXT_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)
      {
        // MXT_MASS_TOLERANCE_AMU

        type & MXT_MASS_MONO ?
          m_monoTolerance = nominal :
          m_avgTolerance = nominal;

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

        type & MXT_MASS_MONO ?
          m_monoTolerance = nominal *(value / 100) :
          m_avgTolerance = nominal *(value / 100);

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

        type & MXT_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)
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    Q_ASSERT(type & MXT_MASS_MONO || type & MXT_MASS_AVG);

    QString text;

    if (type & MXT_MASS_MONO)
      text = m_ui.monoMassTextEdit->toPlainText();
    else if (type & MXT_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 = locale.toDouble(splitList.at(iter), &ok);

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

            return false;
          }

        if(type & MXT_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);
    IonizeRule localIonizeRule(*mp_ionizeRule);

    int count = 0;
    int cycle = 0;

    Q_ASSERT(type & MXT_MASS_MONO || type & MXT_MASS_AVG);

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

    double maxValue = type & MXT_MASS_MONO ?
      value + m_monoTolerance :
      value + m_avgTolerance;

    double minValue = type & MXT_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", Ponderable(),
                                      localIonizeRule, CalcOptions(),false,
                                      iter, iter + jter);

                oligomerTest.calculateMasses(&m_calcOptions);

                double mz = type & MXT_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 & MXT_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 & MXT_MASS_MONO)
      {
        for(int iter = 0; iter < m_monoMassesList.size(); ++iter)
          {
            double value = m_monoMassesList.at(iter);

            if (!calculateTolerance(value, MXT_MASS_MONO))
              return false;

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

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

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

            if (!calculateTolerance(value, MXT_MASS_AVG))
              return false;

            for (int iter = 0; iter < coordinateList.size(); ++iter)
              {
                if(!searchMass(value, *(coordinateList.at(iter)),
                               MXT_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(MXT_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(MXT_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(MXT_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(MXT_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(MXT_MASS_MONO, true);
    updateProgressDetails(MXT_MASS_AVG, true);

    Application *application = static_cast<Application *>(qApp);

    if (!searchMasses(MXT_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);
        application->beep();

        return;
      }

    if (!searchMasses(MXT_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);
        application->beep();

        return;
      }

    setCursor(Qt::ArrowCursor);

    updateMassSearchDetails(m_calcOptions);

    application->beep();
  }


  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)
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    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);

        application->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->mono(locale);
        m_ui.lastOligoMonoMassLineEdit->setText(mass);

        mass = oligomer->avg(locale);
        m_ui.lastOligoAvgMassLineEdit->setText(mass);
      }

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

    type & MXT_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);

    application->processEvents();
  }


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

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

    if (calcOptions.polymerEntities() & MXT_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 == MXT_MASS_MONO)
      text = m_ui.monoFilterToleranceLineEdit->text();
    else if (massType == MXT_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.
        Application *application = static_cast<Application *>(qApp);
        QLocale locale = application->locale();

        ok = false;
        tolerance = locale.toDouble(text, &ok);

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

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

    if (massType == MXT_MASS_MONO)
      index = m_ui.monoFilterToleranceComboBox->currentIndex();
    else if (massType == MXT_MASS_AVG)
      index = m_ui.avgFilterToleranceComboBox->currentIndex();
    else
      Q_ASSERT(0);

    if (index == 0)
      {
        // MXT_MASS_TOLERANCE_AMU
        m_filterTolerance = tolerance;
      }
    else if (index == 1)
      {
        // MXT_MASS_TOLERANCE_PCT
        m_filterTolerance =(tolerance / 100) * mass;
      }
    else if (index == 2)
      {
        // MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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.
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    bool ok = false;
    double mass = locale.toDouble(text, &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, MXT_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;
    MassType massType = MXT_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 = MXT_MASS_MONO;
        else
          massType = MXT_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 massXpert
