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


/////////////////////// Local includes
#include <massxpert/gui/FragSpecDefDlg.hpp>
#include <massxpert/nongui/PolChemDef.hpp>
#include <massxpert/gui/PolChemDefWnd.hpp>


namespace msXpSmassXpert
{

FragSpecDefDlg::FragSpecDefDlg(PolChemDef *polChemDef,
                               PolChemDefWnd *polChemDefWnd,
                               const QString &settingsFilePath)
  : AbstractPolChemDefDependentDlg{
      polChemDef,
      polChemDefWnd,
      settingsFilePath,
      "FragSpecDefDlg",
      "massXpert: Fragmentation specification definition"}
{
  mp_list = polChemDef->fragSpecListPtr();

  if(!initialize())
    qFatal(
      "Fatal error at %s@%d. Failed to initialize the %s window. Program "
      "aborted.",
      __FILE__,
      __LINE__,
      m_wndTypeName.toLatin1().data());
}


FragSpecDefDlg::~FragSpecDefDlg()
{
}


void
FragSpecDefDlg::closeEvent(QCloseEvent *event)
{
  // No real close, because we did not ask that
  // close==destruction. Thus we only hide the dialog remembering its
  // position and size.

  mp_polChemDefWnd->m_ui.fragmentationPushButton->setChecked(false);

  writeSettings();
}


void
FragSpecDefDlg::readSettings()
{
  QSettings settings(m_settingsFilePath, QSettings::IniFormat);

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


void
FragSpecDefDlg::writeSettings()
{

  QSettings settings(m_settingsFilePath, QSettings::IniFormat);

  settings.beginGroup(m_wndTypeName);
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.setValue("splitter", m_ui.splitter->saveState());
  settings.endGroup();
}


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

  // Set all the fragSpecs to the list widget.

  for(int iter = 0; iter < mp_list->size(); ++iter)
    {
      FragSpec *fragSpec = mp_list->at(iter);

      m_ui.fragSpecListWidget->addItem(fragSpec->name());
    }

  readSettings();

  // The combobox listing the available ends.

  QStringList endList;
  endList << "N/A"
          << "NE"
          << "LE"
          << "RE";

  m_ui.fragEndComboBox->addItems(endList);

  // Make the connections.

  connect(m_ui.addFragSpecPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(addFragSpecPushButtonClicked()));

  connect(m_ui.removeFragSpecPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeFragSpecPushButtonClicked()));

  connect(m_ui.moveUpFragSpecPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveUpFragSpecPushButtonClicked()));

  connect(m_ui.moveDownFragSpecPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveDownFragSpecPushButtonClicked()));

  connect(m_ui.addFragRulePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(addFragRulePushButtonClicked()));

  connect(m_ui.removeFragRulePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeFragRulePushButtonClicked()));

  connect(m_ui.moveUpFragRulePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveUpFragRulePushButtonClicked()));

  connect(m_ui.moveDownFragRulePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveDownFragRulePushButtonClicked()));

  connect(m_ui.applyFragSpecPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(applyFragSpecPushButtonClicked()));

  connect(m_ui.applyFragRulePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(applyFragRulePushButtonClicked()));

  connect(m_ui.validatePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(validatePushButtonClicked()));

  connect(m_ui.fragSpecListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(fragSpecListWidgetItemSelectionChanged()));

  connect(m_ui.fragRuleListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(fragRuleListWidgetItemSelectionChanged()));

  return true;
}


void
FragSpecDefDlg::addFragSpecPushButtonClicked()
{
  // We are asked to add a new fragSpec. We'll add it right after the
  // current item.

  // Returns -1 if the list is empty.
  int index = m_ui.fragSpecListWidget->currentRow();

  FragSpec *newFragSpec = new FragSpec(mp_polChemDef,
                                       tr("Type Spec Name"),
                                       tr("Type Spec Formula"),
                                       FRAG_END_NONE);

  mp_list->insert(index, newFragSpec);
  m_ui.fragSpecListWidget->insertItem(index, newFragSpec->name());

  setModified();

  // Needed so that the setCurrentRow() call below actually set the
  // current row!
  if(index <= 0)
    index = 0;

  m_ui.fragSpecListWidget->setCurrentRow(index);

  // Erase fragRule data that might be left over by precedent current
  // fragSpec.
  updateFragRuleDetails(0);

  // Set the focus to the lineEdit that holds the name of the fragSpec.
  m_ui.fragSpecNameLineEdit->setFocus();
  m_ui.fragSpecNameLineEdit->selectAll();
}


void
FragSpecDefDlg::removeFragSpecPushButtonClicked()
{
  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current fragSpec.
  int index = m_ui.fragSpecListWidget->currentRow();

  QListWidgetItem *item = m_ui.fragSpecListWidget->takeItem(index);
  delete item;

  FragSpec *fragSpec = mp_list->takeAt(index);
  Q_ASSERT(fragSpec);
  delete fragSpec;

  setModified();

  // If there are remaining items, we want to set the next item the
  // currentItem. If not, then, the currentItem should be the one
  // preceding the fragSpec that we removed.

  if(m_ui.fragSpecListWidget->count() >= index + 1)
    {
      m_ui.fragSpecListWidget->setCurrentRow(index);
      fragSpecListWidgetItemSelectionChanged();
    }

  // If there are no more items in the fragSpec list, remove all the items
  // from the fragRuleList.

  if(!m_ui.fragSpecListWidget->count())
    {
      m_ui.fragRuleListWidget->clear();
      clearAllDetails();
    }
}


void
FragSpecDefDlg::moveUpFragSpecPushButtonClicked()
{
  // Move the current row to one index less.

  // If no fragSpec is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the fragSpec and the fragSpec itself.
  int index = m_ui.fragSpecListWidget->currentRow();

  // If the item is already at top of list, do nothing.
  if(!index)
    return;

  mp_list->move(index, index - 1);

  QListWidgetItem *item = m_ui.fragSpecListWidget->takeItem(index);

  m_ui.fragSpecListWidget->insertItem(index - 1, item);
  m_ui.fragSpecListWidget->setCurrentRow(index - 1);
  fragSpecListWidgetItemSelectionChanged();

  setModified();
}


void
FragSpecDefDlg::moveDownFragSpecPushButtonClicked()
{
  // Move the current row to one index less.

  // If no fragSpec is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the fragSpec and the fragSpec itself.
  int index = m_ui.fragSpecListWidget->currentRow();

  // If the item is already at bottom of list, do nothing.
  if(index == m_ui.fragSpecListWidget->count() - 1)
    return;

  mp_list->move(index, index + 1);

  QListWidgetItem *item = m_ui.fragSpecListWidget->takeItem(index);
  m_ui.fragSpecListWidget->insertItem(index + 1, item);
  m_ui.fragSpecListWidget->setCurrentRow(index + 1);
  fragSpecListWidgetItemSelectionChanged();

  setModified();
}


void
FragSpecDefDlg::addFragRulePushButtonClicked()
{
  // We are asked to add a new fragRule. We'll add it right after the
  // current item. Note however, that one fragSpec has to be selected.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    {
      QMessageBox::information(this,
                               tr("massXpert - Fragmentation definition"),
                               tr("Please, select a fragmentation first."),
                               QMessageBox::Ok);
      return;
    }

  // Get the index of the current fragSpec so that we know to which fragSpec
  // we'll add the fragRule.
  int index = m_ui.fragSpecListWidget->currentRow();

  // What's the actual fragSpec?
  FragSpec *fragSpec = mp_list->at(index);
  Q_ASSERT(fragSpec);

  // Allocate the new fragRule.
  FragRule *newFragRule = new FragRule(mp_polChemDef, tr("Type Rule Name"));

  // Get the row index of the current fragRule item. Returns -1 if the
  // list is empty.
  index = m_ui.fragRuleListWidget->currentRow();

  m_ui.fragRuleListWidget->insertItem(index, newFragRule->name());

  // Needed so that the setCurrentRow() call below actually set the
  // current row!
  if(index <= 0)
    index = 0;

  fragSpec->ruleList().insert(index, newFragRule);

  m_ui.fragRuleListWidget->setCurrentRow(index);

  setModified();

  // Set the focus to the lineEdit that holds the mass of the fragRule.
  m_ui.fragRuleNameLineEdit->setFocus();
  m_ui.fragRuleNameLineEdit->selectAll();
}


void
FragSpecDefDlg::removeFragRulePushButtonClicked()
{
  QList<QListWidgetItem *> selectedList =
    m_ui.fragRuleListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;


  // Get the index of the current fragSpec so that we know from
  // which fragSpec we'll remove the fragRule.
  int index = m_ui.fragSpecListWidget->currentRow();

  FragSpec *fragSpec = mp_list->at(index);
  Q_ASSERT(fragSpec);

  // Get the index of the current fragRule.
  index = m_ui.fragRuleListWidget->currentRow();

  // First remove the item from the listwidget because that will have
  // fragRuleListWidgetItemSelectionChanged() triggered and we have to
  // have the item in the fragRule list in the fragSpec! Otherwise a crash
  // occurs.
  QListWidgetItem *item = m_ui.fragRuleListWidget->takeItem(index);
  delete item;

  // Remove the fragRule from the fragSpec proper.

  QList<FragRule *> fragRuleList = fragSpec->ruleList();

  FragRule *fragRule = fragRuleList.at(index);

  fragSpec->ruleList().removeAt(index);
  delete fragRule;

  // If there are remaining items, we want to set the next item the
  // currentItem. If not, then, the currentItem should be the one
  // preceding the fragSpec that we removed.

  if(m_ui.fragRuleListWidget->count() >= index + 1)
    {
      m_ui.fragRuleListWidget->setCurrentRow(index);
      fragRuleListWidgetItemSelectionChanged();
    }

  // If there are no more items in the fragRule list, remove all the
  // details.

  if(!m_ui.fragRuleListWidget->count())
    {
      updateFragRuleDetails(0);
    }
  else
    {
    }

  setModified();
}


void
FragSpecDefDlg::moveUpFragRulePushButtonClicked()
{
  // Move the current row to one index less.

  // If no fragRule is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragRuleListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the fragSpec to which the fragRule belongs.
  int index          = m_ui.fragSpecListWidget->currentRow();
  FragSpec *fragSpec = mp_list->at(index);

  // Get the index of the current fragRule item.
  index = m_ui.fragRuleListWidget->currentRow();

  // If the item is already at top of list, do nothing.
  if(!index)
    return;

  // Get the fragRule itself from the fragSpec.
  FragRule *fragRule = fragSpec->ruleList().at(index);

  fragSpec->ruleList().removeAt(index);
  fragSpec->ruleList().insert(index - 1, fragRule);

  QListWidgetItem *item = m_ui.fragRuleListWidget->takeItem(index);
  m_ui.fragRuleListWidget->insertItem(index - 1, item);
  m_ui.fragRuleListWidget->setCurrentRow(index - 1);
  fragRuleListWidgetItemSelectionChanged();

  setModified();
}


void
FragSpecDefDlg::moveDownFragRulePushButtonClicked()
{
  // Move the current row to one index less.

  // If no fragRule is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragRuleListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the fragSpec to which the fragRule belongs.
  int index          = m_ui.fragSpecListWidget->currentRow();
  FragSpec *fragSpec = mp_list->at(index);

  // Get the index of the current fragRule item.
  index = m_ui.fragRuleListWidget->currentRow();

  // If the item is already at top of list, do nothing.
  if(index == m_ui.fragRuleListWidget->count() - 1)
    return;

  // Get the fragRule itself from the fragSpec.
  FragRule *fragRule = fragSpec->ruleList().at(index);

  fragSpec->ruleList().removeAt(index);
  fragSpec->ruleList().insert(index + 1, fragRule);

  QListWidgetItem *item = m_ui.fragRuleListWidget->takeItem(index);
  m_ui.fragRuleListWidget->insertItem(index + 1, item);
  m_ui.fragRuleListWidget->setCurrentRow(index + 1);
  fragRuleListWidgetItemSelectionChanged();

  setModified();
}


void
FragSpecDefDlg::applyFragSpecPushButtonClicked()
{
  // We are asked to apply the data for the fragSpec.

  // If no fragSpec is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current fragSpec item.
  int index = m_ui.fragSpecListWidget->currentRow();

  FragSpec *fragSpec = mp_list->at(index);

  // We do not want more than one fragSpec by the same name.

  QString editName    = m_ui.fragSpecNameLineEdit->text();
  QString editFormula = m_ui.fragSpecFormulaLineEdit->text();

  // If a fragSpec is found in the list with the same name, and that
  // fragSpec is not the one that is current in the fragSpec list,
  // then we are making a double entry, which is not allowed.

  int nameRes = FragSpec::isNameInList(editName, *mp_list);
  if(nameRes != -1 && nameRes != index)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           tr("A fragmentation with same name "
                              "exists already."),
                           QMessageBox::Ok);
      return;
    }

  QString fragEndString = m_ui.fragEndComboBox->currentText();
  MxtFragEnd fragEnd;

  if(fragEndString == "NE")
    fragEnd = FRAG_END_NONE;
  else if(fragEndString == "BE")
    fragEnd = FRAG_END_BOTH;
  else if(fragEndString == "LE")
    fragEnd = FRAG_END_LEFT;
  else if(fragEndString == "RE")
    fragEnd = FRAG_END_RIGHT;
  else
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           tr("The fragmentation end is not correct. "
                              "Choose either 'NE' or 'LE' or 'RE'."),
                           QMessageBox::Ok);
      return;
    }

  // At this point, validate the formula:

  msXpSlibmass::Formula formula(editFormula);

  if(!formula.validate(mp_polChemDef->atomList()))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           tr("The formula failed to validate."),
                           QMessageBox::Ok);
      return;
    }

  // At this point, check if the fragmented monomer should
  // contribute something to the mass of the fragment (usually one
  // would expect that the contribution be negative, that is that
  // the monomer gets decomposed somehow). This integer value
  // indicates if this monomer's mass should be added (positive
  // integer) or removed (negative integer) and how may times it
  // should thus be. The user is required to state what part of the
  // monomer structure should be readded.

  // The rationale is that we can easily remove the mass of the
  // whole monomer, which we know, of course. However, since full
  // removal of a monomer mass does not make sense, the user should
  // indicate which part of the monomer should be readded. This
  // mechanism is typically used when making definitions of
  // fragmentations which involve lateral chain decomposition (the
  // base in DNA, the side chain for proteins and other chemical
  // groups for sugars).

  // For example, let's imagine, we are dealing with the
  // decomposition of guanine in a DNA oligonucleotide. We want to
  // define the fragmentation pattern 'a' with full decomposition of
  // the base.

  // First off, the monomer of a DNA is constituted of two parts :

  // 1. The skeleton, which does not change when the nucleotide
  // changes (that is which is constant which ever the base is at
  // any given monomer position), this is the sugar plus the
  // phosphate (C5H8O5P);

  // 2. The base, which changes each time a monomer changes (that's
  // the lateral chain of the monomer). Adenine has formula C5H4N5,
  // for example.

  // Now, if we wanted to define the fragmentation a-B (that is a
  // with base decomposition), we would define -O (that is the
  // normal a fragmentation formula), +OH (as the left cap) and we
  // would have to set two more parameters in order to remove the
  // mass of the nitrogenous base (lateral chain).

  // 1. Ask that the mass of the whole monomer be removed (set
  // monomerContrib below to -1). But that would be removing too
  // much stuff. We should now readd the mass of the skeleton, so
  // that the net mass loss corresponds to decomposing only the base
  // (adenine, for example).

  // 2. Ask that the mass of the skeleton is readded. This is done
  // by appending +C5H8O5P to the -O formula of the normal a
  // fragmentation pattern.

  // Finally, if we had to perform something similar wity protein,
  // we would be having the same mechanism, unless the skeleton part
  // of a proteinaceous residual chain is CH.


  int monomerContrib = m_ui.monomerContributionSpinBox->value();

  // Now that all data were validated, we can set them all.

  fragSpec->setName(editName);
  fragSpec->setFragEnd(fragEnd);
  fragSpec->setFormula(editFormula);
  fragSpec->setComment(m_ui.fragSpecCommentLineEdit->text());
  fragSpec->setMonomerContribution(monomerContrib);


  // Update the list widget item.

  QListWidgetItem *item = m_ui.fragSpecListWidget->currentItem();
  item->setData(Qt::DisplayRole, fragSpec->name());

  setModified();
}


void
FragSpecDefDlg::applyFragRulePushButtonClicked()
{
  // We are asked to apply the data for the fragRule.

  // If no fragRule is selected, just return.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragRuleListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the fragSpec to which the fragRule belongs.
  int index          = m_ui.fragSpecListWidget->currentRow();
  FragSpec *fragSpec = mp_list->at(index);

  // Get the index of the current fragRule item.
  index = m_ui.fragRuleListWidget->currentRow();

  // Get the fragRule itself from the fragSpec.
  FragRule *fragRule = fragSpec->ruleList().at(index);

  QString editName    = m_ui.fragRuleNameLineEdit->text();
  QString editFormula = m_ui.fragRuleFormulaLineEdit->text();

  QString prevCode = m_ui.prevCodeLineEdit->text();
  QString currCode = m_ui.currCodeLineEdit->text();
  QString nextCode = m_ui.nextCodeLineEdit->text();

  // If a fragRule is found in the list with the same name, and that
  // fragRule is not the one that is current in the fragRule list,
  // then we are making a double entry, which is not allowed.

  int nameRes = FragRule::isNameInList(editName, fragSpec->ruleList());
  if(nameRes != -1 && nameRes != index)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           tr("A fragmentation rule with same name "
                              "exists already."),
                           QMessageBox::Ok);
      return;
    }

  if(!prevCode.isEmpty())
    {
      const QList<Monomer *> &monomerList = mp_polChemDef->monomerList();

      if(Monomer::isCodeInList(prevCode, monomerList) == -1)
        {
          QMessageBox::warning(this,
                               tr("massXpert - Fragmentation definition"),
                               tr("The previous code is not known."),
                               QMessageBox::Ok);
          return;
        }
    }

  if(!currCode.isEmpty())
    {
      const QList<Monomer *> &monomerList = mp_polChemDef->monomerList();

      if(Monomer::isCodeInList(currCode, monomerList) == -1)
        {
          QMessageBox::warning(this,
                               tr("massXpert - Fragmentation definition"),
                               tr("The current code is not known."),
                               QMessageBox::Ok);
          return;
        }
    }

  if(!nextCode.isEmpty())
    {
      const QList<Monomer *> &monomerList = mp_polChemDef->monomerList();

      if(Monomer::isCodeInList(nextCode, monomerList) == -1)
        {
          QMessageBox::warning(this,
                               tr("massXpert - Fragmentation definition"),
                               tr("The next code is not known."),
                               QMessageBox::Ok);
          return;
        }
    }

  msXpSlibmass::Formula formula(editFormula);

  if(!formula.validate(mp_polChemDef->atomList()))
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           tr("The formula failed to validate."),
                           QMessageBox::Ok);
      return;
    }


  fragRule->setName(editName);
  fragRule->setFormula(editFormula);

  fragRule->setPrevCode(prevCode);
  fragRule->setCurrCode(currCode);
  fragRule->setNextCode(nextCode);

  fragRule->setComment(m_ui.fragRuleCommentLineEdit->text());

  // Update the list widget item.

  QListWidgetItem *item = m_ui.fragRuleListWidget->currentItem();
  item->setData(Qt::DisplayRole, fragRule->name());

  setModified();
}


bool
FragSpecDefDlg::validatePushButtonClicked()
{
  QStringList errorList;

  // All we have to do is validate the fragSpec definition. For that we'll
  // go in the listwidget items one after the other and make sure that
  // everything is fine and that colinearity is perfect between the
  // fragSpec list and the listwidget.

  int itemCount = m_ui.fragSpecListWidget->count();

  if(itemCount != mp_list->size())
    {
      errorList << QString(
        tr("\nThe number of fragmentations in "
           "the list widget \n"
           "and in the list of fragmentations "
           "is not identical.\n"));

      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           errorList.join("\n"),
                           QMessageBox::Ok);
      return false;
    }

  for(int iter = 0; iter < mp_list->size(); ++iter)
    {
      QListWidgetItem *item = m_ui.fragSpecListWidget->item(iter);

      FragSpec *fragSpec = mp_list->at(iter);

      if(item->text() != fragSpec->name())
        errorList << QString(tr("\nFragmentation at index %1 has not "
                                "the same\n"
                                "name as the list widget item at the\n"
                                "same index.\n")
                               .arg(iter));

      if(!fragSpec->validate())
        errorList << QString(tr("\nFragmentation at index %1 failed "
                                "to validate.\n")
                               .arg(iter));
    }

  if(errorList.size())
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           errorList.join("\n"),
                           QMessageBox::Ok);
      return false;
    }
  else
    {
      QMessageBox::warning(this,
                           tr("massXpert - Fragmentation definition"),
                           ("Validation: success\n"),
                           QMessageBox::Ok);
    }

  return true;
}


void
FragSpecDefDlg::fragSpecListWidgetItemSelectionChanged()
{
  // The fragSpec item has changed. Empty the fragRule list and update its
  // contents. Update the details for the fragSpec.

  // The list is a single-item-selection list.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragSpecListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current fragSpec.
  int index = m_ui.fragSpecListWidget->currentRow();

  FragSpec *fragSpec = mp_list->at(index);
  Q_ASSERT(fragSpec);

  // Set the data of the fragSpec to their respective widgets.
  updateFragSpecDetails(fragSpec);

  // The list of fragRules
  m_ui.fragRuleListWidget->clear();

  for(int iter = 0; iter < fragSpec->ruleList().size(); ++iter)
    {
      FragRule *fragRule = fragSpec->ruleList().at(iter);

      m_ui.fragRuleListWidget->addItem(fragRule->name());
    }

  if(!m_ui.fragRuleListWidget->count())
    updateFragRuleDetails(0);
  else
    {
      // And now select the first row in the fragRule list widget.
      m_ui.fragRuleListWidget->setCurrentRow(0);
    }
}


void
FragSpecDefDlg::fragRuleListWidgetItemSelectionChanged()
{
  // The fragRule item has changed. Update the details for the fragRule.

  // The list is a single-item-selection list.

  QList<QListWidgetItem *> selectedList =
    m_ui.fragRuleListWidget->selectedItems();

  if(selectedList.size() != 1)
    return;

  // Get the index of the current fragSpec.
  int index = m_ui.fragSpecListWidget->currentRow();

  // Find the fragRule object in the list of fragRules.
  FragSpec *fragSpec = mp_list->at(index);
  Q_ASSERT(fragSpec);

  // Get the index of the current fragRule.
  index = m_ui.fragRuleListWidget->currentRow();

  // Get the fragRule that is currently selected from the fragSpec's list
  // of fragRules.
  FragRule *fragRule = fragSpec->ruleList().at(index);
  Q_ASSERT(fragRule);

  // Set the data of the fragRule to their respective widgets.
  updateFragRuleDetails(fragRule);
}


void
FragSpecDefDlg::updateFragSpecDetails(FragSpec *fragSpec)
{
  if(fragSpec)
    {
      m_ui.fragSpecNameLineEdit->setText(fragSpec->name());
      m_ui.fragSpecFormulaLineEdit->setText(fragSpec->formula());

      int index = 0;

      if(fragSpec->fragEnd() == FRAG_END_NONE)
        index = m_ui.fragEndComboBox->findText("NE");
      else if(fragSpec->fragEnd() == FRAG_END_LEFT)
        index = m_ui.fragEndComboBox->findText("LE");
      else if(fragSpec->fragEnd() == FRAG_END_RIGHT)
        index = m_ui.fragEndComboBox->findText("RE");
      m_ui.fragEndComboBox->setCurrentIndex(index);
      m_ui.monomerContributionSpinBox->setValue(
        fragSpec->monomerContribution());
      m_ui.fragSpecCommentLineEdit->setText(fragSpec->comment());
    }
  else
    {
      m_ui.fragSpecNameLineEdit->setText("");
      m_ui.fragSpecFormulaLineEdit->setText("");
      int index = m_ui.fragEndComboBox->findText("N/A");
      m_ui.fragEndComboBox->setCurrentIndex(index);
      m_ui.monomerContributionSpinBox->setValue(0);
      m_ui.fragSpecCommentLineEdit->setText("");
    }
}


void
FragSpecDefDlg::updateFragRuleDetails(FragRule *fragRule)
{
  if(fragRule)
    {
      m_ui.fragRuleNameLineEdit->setText(fragRule->name());
      m_ui.fragRuleFormulaLineEdit->setText(fragRule->formula());

      m_ui.prevCodeLineEdit->setText(fragRule->prevCode());
      m_ui.currCodeLineEdit->setText(fragRule->currCode());
      m_ui.nextCodeLineEdit->setText(fragRule->nextCode());

      m_ui.fragRuleCommentLineEdit->setText(fragRule->comment());
    }
  else
    {
      m_ui.fragRuleNameLineEdit->setText("");
      m_ui.fragRuleFormulaLineEdit->setText("");

      m_ui.prevCodeLineEdit->setText("");
      m_ui.currCodeLineEdit->setText("");
      m_ui.nextCodeLineEdit->setText("");

      m_ui.fragRuleCommentLineEdit->setText("");
    }
}


void
FragSpecDefDlg::clearAllDetails()
{
  m_ui.fragSpecNameLineEdit->setText("");
  m_ui.fragSpecFormulaLineEdit->setText("");
  m_ui.monomerContributionSpinBox->setValue(0);
  int index = m_ui.fragEndComboBox->findText("N/A");
  m_ui.fragEndComboBox->setCurrentIndex(index);
  m_ui.monomerContributionSpinBox->setValue(0);
  m_ui.fragSpecCommentLineEdit->setText("");

  m_ui.fragRuleNameLineEdit->setText("");
  m_ui.fragRuleFormulaLineEdit->setText("");

  m_ui.prevCodeLineEdit->setText("");
  m_ui.currCodeLineEdit->setText("");
  m_ui.nextCodeLineEdit->setText("");

  m_ui.fragRuleCommentLineEdit->setText("");
}


// VALIDATION
bool
FragSpecDefDlg::validate()
{
  return validatePushButtonClicked();
}

} // namespace msXpSmassXpert
