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


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

#include <libmass/PeakShape.hpp>
#include <libmass/PeakCentroid.hpp>
#include <libmass/PeakShapeConfig.hpp>
#include <libmass/IsotopicPatternCalculator.hpp>
#include <massxpert/gui/SpectrumCalculationDlg.hpp>


namespace msXpSmassXpert
{

CleavageDlg::CleavageDlg(SequenceEditorWnd *editorWnd,
                         Polymer *polymer,
                         const PolChemDef *polChemDef,
                         const QString &configSettingsFilePath,
                         QByteArray hash,
                         const CalcOptions &calcOptions,
                         const msXpSlibmass::IonizeRule *ionizeRule)
  : AbstractSeqEdWndDependentDlg{editorWnd,
                                 polymer,
                                 const_cast<PolChemDef *>(polChemDef),
                                 configSettingsFilePath,
                                 "CleavageDlg",
                                 "Cleavage calculations"},
    m_polymerHash{hash},
    m_calcOptions{calcOptions},
    mp_ionizeRule{ionizeRule}
{
  if(!mp_polymer || !mp_polChemDef)
    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__);
}


CleavageDlg::~CleavageDlg()
{
  // Delete the oligomers in the list of such instances.

  while(!m_oligomerList.isEmpty())
    delete m_oligomerList.takeFirst();

  delete mpa_resultsString;

  delete mpa_oligomerTableViewModel;
  delete mpa_proxyModel;

  delete mpa_cleaver;

  writeSettings();
}

void
CleavageDlg::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.endGroup();
}


void
CleavageDlg::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());
  settings.endGroup();
}


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

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

  populateCleaveSpecListWidget();
  populateSelectedOligomerData();

  // Set pointers to 0 so that after the setupTableView call below
  // they'll get their proper value. We'll then be able to free all
  // that stuff in the destructor.
  mpa_cleaver                = 0;
  mpa_proxyModel             = 0;
  mpa_oligomerTableViewModel = 0;

  setupTableView();

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

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

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

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

  filterAct = new QAction(tr("Toggle Filtering"), this);
  filterAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_G));
  this->addAction(filterAct);
  connect(filterAct, SIGNAL(triggered()), this, SLOT(filterOptionsToggled()));

  m_ui.filteringOptionsGroupBox->addAction(filterAct);
  // When the dialog box is created it is created with the groupbox
  // unchecked.
  m_ui.filteringOptionsFrame->setVisible(false);

  // When the filtering group box will be opened, the focus will be
  // on the first widget of the groupbox:
  mp_focusWidget = m_ui.filterPartialLineEdit;

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

  QStringList comboBoxItemList;

  comboBoxItemList.insert(ExportResultsActions::EXPORT_TO_CLIPBOARD_OVERWRITE,
                          "Overwrite clipboard (Ctrl+O)");
  comboBoxItemList.insert(ExportResultsActions::EXPORT_TO_CLIPBOARD_APPEND,
                          "Append to clipboard (Ctrl+P)");
  comboBoxItemList.insert(ExportResultsActions::EXPORT_TO_FILE,
                          "Write to file (Ctrl+F)");
  comboBoxItemList.insert(ExportResultsActions::SELECT_FILE,
                          "Select file (Ctrl+S)");
  comboBoxItemList.insert(ExportResultsActions::CALCULATE_SPECTRUM,
                          "Calculate spectrum (Ctrl+C)");

  m_ui.exportResultsComboBox->addItems(comboBoxItemList);

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

  comboBoxItemList.clear();

  comboBoxItemList << "%e$%i$%l$%s$%m$%p\\n";
  m_ui.outputPatternComboBox->addItems(comboBoxItemList);

  connect(m_ui.outputPatternHelpPushButton,
          &QPushButton::clicked,
          this,
          &CleavageDlg::showOutputPatternDefinitionHelp);

  mpa_resultsString = new QString();

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

  readSettings();

  connect(m_ui.cleavePushButton, SIGNAL(clicked()), this, SLOT(cleave()));

  connect(m_ui.filterPartialLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(filterPartial()));

  connect(m_ui.filterMonoMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(filterMonoMass()));

  connect(m_ui.filterAvgMassLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(filterAvgMass()));

  connect(m_ui.filterChargeLineEdit,
          SIGNAL(returnPressed()),
          this,
          SLOT(filterCharge()));

  connect(m_ui.filteringOptionsGroupBox,
          SIGNAL(clicked(bool)),
          this,
          SLOT(filterOptions(bool)));

  show();

  return true;
}


bool
CleavageDlg::populateSelectedOligomerData()
{
  CoordinateList coordList;
  Coordinates *coordinates = 0;

  if(!mp_editorWnd->mpa_editorGraphicsView->selectionIndices(&coordList))
    {
      // We get the selection indices corresponding to the virtual
      // selection, that is the sequence region encompassing the
      // first residue of the sequence up to the monomer left of
      // point.

      // In this case we want the cleavage to be performed on the
      // whole polymer sequence. Set its coordinates to the list of
      // coordinates.

      coordinates = new Coordinates(0, mp_polymer->size() - 1);

      // Set the newly created coordinates to the coordinates list
      // that we'll feed into the calculation options.

      coordList.setCoordinates(*coordinates);

      // We do not need the coordinates object anymore, delete it.
      delete coordinates;
      coordinates = 0;
    }

  if(coordList.size() > 1)
    {
      QMessageBox::information(this,
                               tr("massxpert - Polymer Cleavage"),
                               tr("Cleavage simulations with\n"
                                  "multi-region selection are not"
                                  "supported."),
                               QMessageBox::Ok);

      // Set the selection data as if all of the sequence was to be
      // cleaved.

      coordinates = new Coordinates(0, mp_polymer->size() - 1);

      // Set the newly created coordinates to the coordinates list
      // that we'll feed into the calculation options.

      coordList.setCoordinates(*coordinates);

      // We do not need the coordinates object anymore, delete it.
      delete coordinates;
      coordinates = 0;
    }

  // Now, if the checkbox asking for whole sequence cleavage is
  // checked, then we have to make the cleavage on the whole
  // sequence.
  if(m_ui.wholeSequenceCheckBox->isChecked())
    {
      // The cleavage is to be performed on the whole polymer
      // sequence.

      coordinates = new Coordinates(0, mp_polymer->size() - 1);

      // Set the newly created coordinates to the coordinates list
      // that we'll feed into the calculation options.

      coordList.setCoordinates(*coordinates);

      // We do not need the coordinates object anymore, delete it.
      delete coordinates;
      coordinates = 0;
    }

  m_calcOptions.setCoordinateList(coordList);

  coordinates = coordList.last();

  m_ui.oligomerStartLabel->setText(QString().setNum(coordinates->start() + 1));

  m_ui.oligomerEndLabel->setText(QString().setNum(coordinates->end() + 1));


  // We have to count the incomplete cross-links.

  const CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

  int crossLinkPartial = 0;

  for(int iter = 0; iter < crossLinkList.size(); ++iter)
    {
      CrossLink *crossLink = crossLinkList.at(iter);

      int ret = crossLink->encompassedBy(coordList);

      if(ret == CROSS_LINK_ENCOMPASSED_FULL)
        {
          // 		qDebug() << __FILE__ << __LINE__
          // 			  << "CrossLink at iter:" << iter
          // 			  << "is fully encompassed";
        }
      else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
        {
          // 		qDebug() << __FILE__ << __LINE__
          // 			  << "CrossLink at iter:" << iter
          // 			  << "is partially encompassed";

          ++crossLinkPartial;
        }
      else
        {
          // 		qDebug() << __FILE__ << __LINE__
          // 			  << "CrossLink at iter:" << iter
          // 			  << "is not encompassed at all";
        }
    }

  if(crossLinkPartial)
    {
      // Alert the user on the fact that the currently selected
      // region does not encompass all of the cross-linked material.

      QMessageBox::information(this,
                               tr("massXpert - Polymer Cleavage"),
                               tr("There are incomplete cross-links"),
                               QMessageBox::Ok);
    }

  // qDebug() << __FILE__ << __LINE__
  // << "Updated coordinates.";

  return true;
}


void
CleavageDlg::populateCleaveSpecListWidget()
{
  PolChemDef *polChemDef = mp_editorWnd->polChemDef();
  Q_ASSERT(polChemDef);

  for(int iter = 0; iter < polChemDef->cleaveSpecList().size(); ++iter)
    {
      CleaveSpec *cleaveSpec = polChemDef->cleaveSpecList().at(iter);
      Q_ASSERT(cleaveSpec);

      m_ui.cleavageAgentListWidget->addItem(cleaveSpec->name());
    }

  return;
}

void
CleavageDlg::setupTableView()
{
  // Model stuff all thought for sorting.
  mpa_oligomerTableViewModel =
    new CleaveOligomerTableViewModel(&m_oligomerList, this);

  mpa_proxyModel = new CleaveOligomerTableViewSortProxyModel(this);
  mpa_proxyModel->setSourceModel(mpa_oligomerTableViewModel);
  mpa_proxyModel->setFilterKeyColumn(-1);

  m_ui.oligomerTableView->setModel(mpa_proxyModel);
  m_ui.oligomerTableView->setParentDlg(this);
  m_ui.oligomerTableView->setOligomerList(&m_oligomerList);
  mpa_oligomerTableViewModel->setTableView(m_ui.oligomerTableView);
}


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

QByteArray
CleavageDlg::polymerHash() const
{
  return m_polymerHash;
}


void
CleavageDlg::cleave()
{
  // What's the cleavage agent ?
  int value              = m_ui.cleavageAgentListWidget->currentRow();
  CleaveSpec *cleaveSpec = mp_polChemDef->cleaveSpecList().at(value);

  // What's the asked level of partial cleavages?
  value = m_ui.partialCleavageSpinBox->value();

  CleaveOptions cleaveOptions(*cleaveSpec, value, false);

  // Set the ionization levels to the local cleavage options object.
  int valueStart = m_ui.ionizeLevelStartSpinBox->value();
  int valueEnd   = m_ui.ionizeLevelEndSpinBox->value();

  cleaveOptions.setIonizeLevels(valueStart, valueEnd);

  // The list in which we'll store all the allocated oligomers.
  OligomerList tempOligomerList;

  delete mpa_cleaver;
  mpa_cleaver = 0;

  // Update the mass calculation engine's configuration data
  // directly from the sequence editor window.
  m_calcOptions = mp_editorWnd->calcOptions();

  // Update the selection data from the sequence editor window.
  if(!populateSelectedOligomerData())
    return;

  mpa_cleaver = new Cleaver(
    mp_polymer, mp_polChemDef, cleaveOptions, m_calcOptions, *mp_ionizeRule);

  mpa_cleaver->setOligomerList(&tempOligomerList);

  if(!mpa_cleaver->cleave())
    {
      delete mpa_cleaver;

      QMessageBox::critical(this,
                            tr("massXpert - Polymer Cleavage"),
                            tr("Failed to perform cleavage."),
                            QMessageBox::Ok);
      return;
    }

  // At this point we have a brand new set of oligomers in the local
  // list of oligomers (tempOligomerList). We either stack these on top
  // of the previous ones, or we replace the previous ones with the
  // new ones.  Are we stacking new oligomers on top of the old
  // ones?

  if(!m_ui.stackOligomersCheckBox->isChecked())
    {
      // We are going to remove *all* the previous oligomers.
      mpa_oligomerTableViewModel->removeOligomers(0, -1);
    }

  // At this point we can set up the data to the treeview model.

  int initialOligomers = tempOligomerList.size();

  // qDebug() << __FILE__ << __LINE__
  //          << "initialOligomers:" << initialOligomers;

  // We are *transferring* the oligomers from tempOligomerList to
  // the list of m_oligomerList list oligomers of which there is a
  // pointer in the model : we are not duplicating the
  // oligomers. When the transfer from tempOligomerList to
  // m_oligomerList _via_ the model will have been done,
  // tempOligomerList will be empty and m_oligomerList will hold
  // them all, with the model having been made aware of that with
  // the beginInsertRows/endInsertRows statement pair.
  int addedOligomers =
    mpa_oligomerTableViewModel->addOligomers(tempOligomerList);

  // qDebug() << __FILE__ << __LINE__
  //          << "addedOligomers:" << addedOligomers;

  if(initialOligomers != addedOligomers)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // Now that we have *transferred* (not copied) all the oligomers
  // in the model, we can clear the tempOligomerList without freeing the
  // instances it currently contain...
  tempOligomerList.clear();

  // Set focus to the treeView.
  m_ui.oligomerTableView->setFocus();

  QString title;

  int oligomerCount = mpa_oligomerTableViewModel->rowCount();

  if(!oligomerCount)
    title = tr("Oligomers (empty list)");
  else if(oligomerCount == 1)
    title = tr("Oligomers (one item)");
  else
    title = tr("Oligomers (%1 items)").arg(oligomerCount);

  m_ui.oligomerGroupBox->setTitle(title);

  // Update the cleavage details so that we know how the oligos were
  // computed.
  updateCleavageDetails(m_calcOptions);
}


void
CleavageDlg::updateCleavageDetails(const CalcOptions &calcOptions)
{
  if(calcOptions.capping() & CAP_LEFT)
    m_ui.leftCapCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.leftCapCheckBox->setCheckState(Qt::Unchecked);

  if(calcOptions.capping() & CAP_RIGHT)
    m_ui.rightCapCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.rightCapCheckBox->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);

  if(calcOptions.monomerEntities() & MONOMER_CHEMENT_MODIF)
    m_ui.modificationsCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.modificationsCheckBox->setCheckState(Qt::Unchecked);

  if(calcOptions.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
    m_ui.crossLinksCheckBox->setCheckState(Qt::Checked);
  else
    m_ui.crossLinksCheckBox->setCheckState(Qt::Unchecked);
}


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

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


bool
CleavageDlg::calculateTolerance(double mass)
{
  // Get the tolerance that is in its lineEdit.

  QString text     = m_ui.toleranceLineEdit->text();
  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_tolerance = 0;
    }

  // What's the item currently selected in the comboBox?
  int index = m_ui.toleranceComboBox->currentIndex();

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

  return true;
}

QString
CleavageDlg::lastUsedCleavageAgentName() const
{
  if(mpa_cleaver != nullptr)
    return mpa_cleaver->cleaveAgentName();
  else
    return QString("");
}

void
CleavageDlg::filterOptions(bool checked)
{
  if(!checked)
    {
      mpa_proxyModel->setFilterKeyColumn(-1);

      mpa_proxyModel->applyNewFilter();

      m_ui.filteringOptionsFrame->setVisible(false);
    }
  else
    {
      m_ui.filteringOptionsFrame->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_focusWidget->setFocus();
    }
}


void
CleavageDlg::filterOptionsToggled()
{
  bool isChecked = m_ui.filteringOptionsGroupBox->isChecked();

  m_ui.filteringOptionsGroupBox->setChecked(!isChecked);
  filterOptions(!isChecked);
}


void
CleavageDlg::filterPartial()
{
  // First off, we have to get the partial that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a int.

  bool ok     = false;
  int partial = text.toInt(&ok);

  if(!partial && !ok)
    return;

  mpa_proxyModel->setPartialFilter(partial);
  mpa_proxyModel->setFilterKeyColumn(0);
  mpa_proxyModel->applyNewFilter();

  mp_focusWidget = m_ui.filterPartialLineEdit;
}


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

  QString text = m_ui.filterMonoMassLineEdit->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(!calculateTolerance(mass))
    return;

  mpa_proxyModel->setMonoFilter(mass);
  mpa_proxyModel->setTolerance(m_tolerance);

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

  mp_focusWidget = m_ui.filterMonoMassLineEdit;
}


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

  QString text = m_ui.filterAvgMassLineEdit->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(!calculateTolerance(mass))
    return;

  mpa_proxyModel->setAvgFilter(mass);
  mpa_proxyModel->setTolerance(m_tolerance);

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

  mp_focusWidget = m_ui.filterAvgMassLineEdit;
}


void
CleavageDlg::filterCharge()
{
  // First off, we have to get the charge that is in the lineEdit.

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

  if(text.isEmpty())
    return;

  // Convert the string to a int.

  bool ok    = false;
  int charge = text.toInt(&ok);

  if(!charge && !ok)
    return;

  mpa_proxyModel->setChargeFilter(charge);
  mpa_proxyModel->setFilterKeyColumn(5);
  mpa_proxyModel->applyNewFilter();

  mp_focusWidget = m_ui.filterChargeLineEdit;
}


void
CleavageDlg::keyPressEvent(QKeyEvent *event)
{
  int modifiers = QGuiApplication::keyboardModifiers();

  if(event->key() == Qt::Key_P && modifiers & Qt::ControlModifier)
    {
      // The user wants to append the selected oligomers to the
      // clipboard.

      // qDebug() << __FILE__ << __LINE__
      // << "Ctrl-P";

      m_ui.exportResultsComboBox->setCurrentIndex(
        ExportResultsActions::EXPORT_TO_CLIPBOARD_APPEND);
      m_ui.exportResultsComboBox->activated(
        ExportResultsActions::EXPORT_TO_CLIPBOARD_APPEND);
      event->accept();
      return;
    }

  if(event->key() == Qt::Key_O && modifiers & Qt::ControlModifier)
    {
      // The user wants to overwrite the selected oligomers to the clipboard.

      // qDebug() << __FILE__ << __LINE__
      // << "Ctrl-O";

      m_ui.exportResultsComboBox->setCurrentIndex(
        ExportResultsActions::EXPORT_TO_CLIPBOARD_OVERWRITE);
      m_ui.exportResultsComboBox->activated(
        ExportResultsActions::EXPORT_TO_CLIPBOARD_OVERWRITE);

      event->accept();
      return;
    }

  if(event->key() == Qt::Key_F && modifiers & Qt::ControlModifier)
    {
      // The user wants to write the selected oligomers to file.

      // qDebug() << __FILE__ << __LINE__
      // << "Ctrl-F";

      m_ui.exportResultsComboBox->setCurrentIndex(
        ExportResultsActions::EXPORT_TO_FILE);
      m_ui.exportResultsComboBox->activated(
        ExportResultsActions::EXPORT_TO_FILE);
      event->accept();
      return;
    }

  if(event->key() == Qt::Key_S && modifiers & Qt::ControlModifier)
    {
      // The user wants to select the file in which to write the results.

      // qDebug() << __FILE__ << __LINE__
      // << "Ctrl-S";

      m_ui.exportResultsComboBox->setCurrentIndex(
        ExportResultsActions::SELECT_FILE);
      m_ui.exportResultsComboBox->activated(ExportResultsActions::SELECT_FILE);
      event->accept();
      return;
    }

  if(event->key() == Qt::Key_C && modifiers & Qt::ControlModifier)
    {
      // The user wants to calculate a spectrum with the peptides.

      // qDebug() << __FILE__ << __LINE__
      // << "Ctrl-C";

      m_ui.exportResultsComboBox->setCurrentIndex(
        ExportResultsActions::CALCULATE_SPECTRUM);
      m_ui.exportResultsComboBox->activated(
        ExportResultsActions::CALCULATE_SPECTRUM);
      event->accept();
      return;
    }
}


// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
// The results-exporting functions. ////////////////////////////////
void
CleavageDlg::showOutputPatternDefinitionHelp()
{
  QMessageBox msgBox;
  QString msg(
    "The following special variables are available to craft a formatting "
    "string\n\n");
  msg += "%f : filename\n";
  msg += "%i : sequence name (identity)\n";
  msg += "%n : oligomer name\n";
  msg += "%s : oligomer sequence\n";
  msg +=
    "%l : elided sequence, like \"LEFT...RIGH\" (first 4 left...4 last "
    "right)\n";
  msg += "%c : oligomer coordinates\n";
  msg += "%o : 0 if oligomer is not modified, 1 if it is\n";
  msg += "%p : partial cleavage\n";
  msg += "%e : cleavage agent name\n";
  msg += "%m : mono m/z\n";
  msg += "%a : avg m/z\n";
  msg += "%z : charge\n";

  msg += "\nNote that any text can be interspersed in the format string.\n";
  msg +=
    "If a '\\' is required in the output, it must be escaped like this: "
    "'\\\\'\n";
  msg +=
    "If a '%' is required in the output, it must be escaped like this: '\\%'\n";
  msg +=
    "If a newline (carriage return) is required in the output, write '\\n'\n";

  msg += "\nExample:\n\n";
  msg += "%e$%n$%s%m$%p\\n\n";
  msg += "will yield the following string:\n";
  msg += "Trypsin$0#20#z=1$SEQENCE$12458.2354$0\n";

  msgBox.setText(msg);

  msgBox.exec();
}


void
CleavageDlg::exportResults(int index)
{
  // Remember that we had set up the combobox with the following strings:
  // << tr("To Clipboard (overwrite)")
  // << tr("To Clipboard (append)")
  // << tr("To File")
  // << tr("Select File");

  if(index == 0)
    {
      exportResultsToClipboard(false /*!append*/);
    }
  else if(index == 1)
    {
      exportResultsToClipboard(true /*append*/);
    }
  else if(index == 2)
    {
      exportResultsFile();
    }
  else if(index == 3)
    {
      selectResultsFile();
    }
  else if(index == 4)
    {
      calculateSpectrum();
    }
  else
    Q_ASSERT(0);
}


void
CleavageDlg::prepareResultsTxtString(bool append)
{
  // 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;
  bool noHeader            = false;
  msXpS::MassType massType = msXpS::MassType::MASS_NONE;

  forXpertMiner = m_ui.forXpertMinerCheckBox->isChecked();
  noHeader      = m_ui.noHeaderCheckBox->isChecked();

  if(!noHeader && !forXpertMiner && !append)
    {
      *mpa_resultsString += QString(
                              "# \n"
                              "# ---------------------------\n"
                              "# Polymer sequence cleavage: %1\n"
                              "# ---------------------------\n")
                              .arg(mp_polymer->name());
    }
  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;
    }

  bool withSequence = m_ui.withSequenceCheckBox->isChecked();

  QString pattern = m_ui.outputPatternComboBox->currentText();
  if(pattern == "Output pattern")
    pattern.clear();

  QString *text = m_ui.oligomerTableView->selectedOligomersAsPlainText(
    pattern, delimiter, withSequence, forXpertMiner, massType);
  *mpa_resultsString += *text;

  delete text;
}


bool
CleavageDlg::exportResultsToClipboard(bool append)
{
  // Below, if append is true, the prepared string will not start with the
  // usual stanza.
  prepareResultsTxtString(append);

  QClipboard *clipboard = QApplication::clipboard();

  // Depending on the fact that we are appending or not, we need to first
  // get the text from the clipboard or not.
  if(append)
    {
      QString clipboardText = clipboard->text(QClipboard::Clipboard);
      clipboardText.append(*mpa_resultsString);
      clipboard->setText(clipboardText, QClipboard::Clipboard);
    }
  else
    {
      clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);
    }

  return true;
}


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

  QFile file(m_resultsFilePath);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Append))
    {
      QMessageBox::information(this,
                               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
CleavageDlg::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;
}


bool
CleavageDlg::calculateSpectrum()
{
  SpectrumCalculationDlg *dlg =
    new SpectrumCalculationDlg(this,
                               m_configSettingsFilePath,
                               mp_polChemDef->atomList(),
                               SPECTRUM_CALCULATION_MODE_SPECTRUM);

  // We now have to prepare the land for the calculations.

  dlg->setOligomerList(&m_oligomerList);

  // We really need that the dialog be set modal because we want to
  // be able to access all the atom and oligomer data freely and
  // durably.
  dlg->setModal(true);

  dlg->show();

  return true;
}

//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.
//////////////////////////////////// The results-exporting functions.

} // namespace msXpSmassXpert
