/* 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 "AtomDefDlg.hpp"
#include "../nongui/PolChemDef.hpp"
#include "PolChemDefWnd.hpp"
#include "../nongui/Isotope.hpp"


namespace msxps
{

	namespace massxpert
	{



AtomDefDlg::AtomDefDlg(PolChemDef *polChemDef,
                       PolChemDefWnd *polChemDefWnd,
                       const QString &settingsFilePath)
  : AbstractPolChemDefDependentDlg{polChemDef,
                                   polChemDefWnd,
                                   settingsFilePath,
                                   "AtomDefDlg",
                                   "massXpert: Atom definition"}
{
  mp_list = mp_polChemDef->atomListPtr();

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


AtomDefDlg::~AtomDefDlg()
{
}


void
AtomDefDlg::closeEvent([[maybe_unused]] 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.atomPushButton->setChecked(false);

  writeSettings();
}


void
AtomDefDlg::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
AtomDefDlg::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
AtomDefDlg::initialize()
{
  m_ui.setupUi(this);

  // Set all the atoms to the list widget.

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

      m_ui.atomListWidget->addItem(atom->name());
    }

  readSettings();

  // Validator stuff for the atom symbol lineedit.
  QRegExp symbolRegExp("[A-Z][a-z]*");
  QValidator *validator = new QRegExpValidator(symbolRegExp, this);

  m_ui.symbolLineEdit->setValidator(validator);

  // Make the connections.

  connect(m_ui.addAtomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(addAtomPushButtonClicked()));

  connect(m_ui.removeAtomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeAtomPushButtonClicked()));

  connect(m_ui.moveUpAtomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveUpAtomPushButtonClicked()));

  connect(m_ui.moveDownAtomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveDownAtomPushButtonClicked()));

  connect(m_ui.addIsotopePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(addIsotopePushButtonClicked()));

  connect(m_ui.removeIsotopePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(removeIsotopePushButtonClicked()));

  connect(m_ui.moveUpIsotopePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveUpIsotopePushButtonClicked()));

  connect(m_ui.moveDownIsotopePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(moveDownIsotopePushButtonClicked()));

  connect(m_ui.applyAtomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(applyAtomPushButtonClicked()));

  connect(m_ui.applyIsotopePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(applyIsotopePushButtonClicked()));

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

  connect(m_ui.atomListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(atomListWidgetItemSelectionChanged()));

  connect(m_ui.isotopeListWidget,
          SIGNAL(itemSelectionChanged()),
          this,
          SLOT(isotopeListWidgetItemSelectionChanged()));

  return true;
}


void
AtomDefDlg::addAtomPushButtonClicked()
{
  // We are asked to add a new atom. We'll add it right after the
  // current item.

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

  Atom *newAtom =
    new Atom(tr("Type Name"), tr("Type Symbol"));

  mp_list->insert(index, newAtom);
  m_ui.atomListWidget->insertItem(index, newAtom->name());

  setModified();

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

  m_ui.atomListWidget->setCurrentRow(index);

  // Erase isotope data that might be left over by precedent current
  // atom.
  updateIsotopeDetails(0);

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


void
AtomDefDlg::removeAtomPushButtonClicked()
{
  QList<QListWidgetItem *> selectedList = m_ui.atomListWidget->selectedItems();

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

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

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

  Atom *atom = mp_list->takeAt(index);
  Q_ASSERT(atom);
  delete atom;

  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 atom that we removed.

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

  // If there are no more items in the atom list, remove all the items
  // from the isotopeList.

  if(!m_ui.atomListWidget->count())
    {
      m_ui.isotopeListWidget->clear();
      clearAllDetails();
    }
}


void
AtomDefDlg::moveUpAtomPushButtonClicked()
{
  // Move the current row to one index less.

  // If no atom is selected, just return.

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

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

  // Get the index of the atom and the atom itself.
  int index = m_ui.atomListWidget->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.atomListWidget->takeItem(index);

  m_ui.atomListWidget->insertItem(index - 1, item);
  m_ui.atomListWidget->setCurrentRow(index - 1);
  atomListWidgetItemSelectionChanged();

  setModified();
}


void
AtomDefDlg::moveDownAtomPushButtonClicked()
{
  // Move the current row to one index less.

  // If no atom is selected, just return.

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

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

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

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

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

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

  setModified();
}


void
AtomDefDlg::addIsotopePushButtonClicked()
{
  // We are asked to add a new isotope. We'll add it right after the
  // current item. Note however, that one atom has to be selected.

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

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

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

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

  // Allocate the new isotope.
  Isotope *newIsotope = new Isotope();

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

  m_ui.isotopeListWidget->insertItem(
    index, newIsotope->massString(ATOM_DEC_PLACES));

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

  atom->insertIsotopeAt(index, newIsotope);

  m_ui.isotopeListWidget->setCurrentRow(index);

  setModified();

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


void
AtomDefDlg::removeIsotopePushButtonClicked()
{
  QList<QListWidgetItem *> selectedList =
    m_ui.isotopeListWidget->selectedItems();

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


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

  Atom *atom = mp_list->at(index);
  Q_ASSERT(atom);

  // Get the index of the current isotope.
  index = m_ui.isotopeListWidget->currentRow();

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

  // Remove the isotope from the atom proper.

  QList<Isotope *> atomIsotopeList = atom->isotopeList();

  Isotope *isotope = atomIsotopeList.at(index);

  atom->removeIsotopeAt(index);
  delete isotope;

  // 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 atom that we removed.

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

  // We have changed the isotopic constitution of the chemical
  // element, we have to recompute the mono/avg masses.

  atom->calculateMasses();

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

  if(!m_ui.isotopeListWidget->count())
    {
      updateAtomMassDetails(0);
      updateIsotopeDetails(0);
    }
  else
    {
      updateAtomMassDetails(atom);
    }

  setModified();
}


void
AtomDefDlg::moveUpIsotopePushButtonClicked()
{
  // Move the current row to one index less.

  // If no isotope is selected, just return.

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

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

  // Get the atom to which the isotope belongs.
  int index                = m_ui.atomListWidget->currentRow();
  Atom *atom = mp_list->at(index);

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

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

  // Get the isotope itself from the atom.
  Isotope *isotope = atom->isotopeList().at(index);

  atom->removeIsotopeAt(index);
  atom->insertIsotopeAt(index - 1, isotope);

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

  setModified();
}


void
AtomDefDlg::moveDownIsotopePushButtonClicked()
{
  // Move the current row to one index less.

  // If no isotope is selected, just return.

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

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

  // Get the atom to which the isotope belongs.
  int index                = m_ui.atomListWidget->currentRow();
  Atom *atom = mp_list->at(index);

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

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

  // Get the isotope itself from the atom.
  Isotope *isotope = atom->isotopeList().at(index);

  atom->removeIsotopeAt(index);
  atom->insertIsotopeAt(index + 1, isotope);

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

  setModified();
}


void
AtomDefDlg::applyAtomPushButtonClicked()
{
  // We are asked to apply the data for the atom.

  // If no atom is selected, just return.

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

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

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

  Atom *atom = mp_list->at(index);

  // We do not want more than one atom by the same name or the same
  // symbol.

  QString editName = m_ui.nameLineEdit->text();

  // The syntax [A-Z][a-z]* is automatically validated using the
  // validator(see initialize()).
  QString editSymbol = m_ui.symbolLineEdit->text();

  // If an atom is found in the list with any of these two strings,
  // and that atom is not the one that is current in the atom list,
  // then we are making a double entry, which is not allowed.

  int nameRes = Atom::nameIndex(editName, *mp_list);
  if(nameRes != -1 && nameRes != index)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Atom definition"),
                           tr("An atom with same name exists already."),
                           QMessageBox::Ok);
      return;
    }

  int symbolRes = Atom::symbolIndex(editSymbol, *mp_list);
  if(symbolRes != -1 && symbolRes != index)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Atom definition"),
                           tr("An atom with same symbol exists already."),
                           QMessageBox::Ok);
      return;
    }

  atom->setName(editName);
  atom->setSymbol(editSymbol);

  // Update the list widget item.

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

  setModified();
}


void
AtomDefDlg::applyIsotopePushButtonClicked()
{
  // We are asked to apply the data for the isotope.

  // If no isotope is selected, just return.

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

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

  // Get the atom to which the isotope belongs.
  int index                = m_ui.atomListWidget->currentRow();
  Atom *atom = mp_list->at(index);

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

  // Get the isotope itself from the atom.
  Isotope *isotope = atom->isotopeList().at(index);

  QString editMass      = m_ui.isotopeMassLineEdit->text();
  QString editAbundance = m_ui.isotopeAbundanceLineEdit->text();

  bool ok           = false;
  double doubleMass = editMass.toDouble(&ok);

  if(doubleMass == 0.0 && !ok)
    {
      QMessageBox::warning(
        this,
        tr("massXpert - Atom definition"),
        tr("Failed to convert %1 to a double.").arg(editMass),
        QMessageBox::Ok);
      return;
    }

  ok                     = false;
  double doubleAbundance = editAbundance.toDouble(&ok);

  if(doubleAbundance == 0.0 && !ok)
    {
      QMessageBox::warning(
        this,
        tr("massXpert - Atom definition"),
        tr("Failed to convert %1 to a double.").arg(editAbundance),
        QMessageBox::Ok);
      return;
    }

  isotope->setMass(doubleMass);
  isotope->setAbundance(doubleAbundance);

  // Update the list widget item.

  QListWidgetItem *item = m_ui.isotopeListWidget->currentItem();
  QString mass          = isotope->massString(ATOM_DEC_PLACES);
  item->setData(Qt::DisplayRole, mass);

  atom->calculateMasses();

  updateAtomMassDetails(atom);

  setModified();
}


bool
AtomDefDlg::validatePushButtonClicked()
{
  QStringList errorList;

  // All we have to do is validate the atom 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
  // atom list and the listwidget.

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

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

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

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

      Atom *atom = mp_list->at(iter);

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

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

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

  return true;
}


void
AtomDefDlg::atomListWidgetItemSelectionChanged()
{
  // The atom item has changed. Empty the isotope list and update its
  // contents. Update the details for the atom.

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

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

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

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

  Atom *atom = mp_list->at(index);
  Q_ASSERT(atom);

  // Set the data of the atom to their respective widgets.
  updateAtomIdentityDetails(atom);
  updateAtomMassDetails(atom);

  // The list of isotopes
  m_ui.isotopeListWidget->clear();

  for(int iter = 0; iter < atom->isotopeList().size(); ++iter)
    {
      Isotope *isotope = atom->isotopeList().at(iter);

      m_ui.isotopeListWidget->addItem(
        isotope->massString(ATOM_DEC_PLACES));
    }

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


void
AtomDefDlg::isotopeListWidgetItemSelectionChanged()
{
  // The isotope item has changed. Update the details for the isotope.

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

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

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

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

  // Find the isotope object in the list of isotopes.
  Atom *atom = mp_list->at(index);
  Q_ASSERT(atom);

  // Get the index of the current isotope.
  index = m_ui.isotopeListWidget->currentRow();

  // Get the isotope that is currently selected from the atom's list
  // of isotopes.
  Isotope *isotope = atom->isotopeList().at(index);
  Q_ASSERT(isotope);

  // Set the data of the isotope to their respective widgets.
  updateIsotopeDetails(isotope);
}


void
AtomDefDlg::updateAtomIdentityDetails(Atom *atom)
{
  if(atom)
    {
      m_ui.nameLineEdit->setText(atom->name());
      m_ui.symbolLineEdit->setText(atom->symbol());
    }
  else
    {
      m_ui.nameLineEdit->setText("");
      m_ui.symbolLineEdit->setText("");
    }
}


void
AtomDefDlg::updateAtomMassDetails(Atom *atom)
{
  if(atom)
    {
      m_ui.monoMassLineEdit->setText(atom->monoString(ATOM_DEC_PLACES));
      m_ui.avgMassLineEdit->setText(atom->avgString(ATOM_DEC_PLACES));
    }
  else
    {
      m_ui.monoMassLineEdit->setText("");
      m_ui.avgMassLineEdit->setText("");
    }
}


void
AtomDefDlg::updateIsotopeDetails(Isotope *isotope)
{
  if(isotope)
    {
      m_ui.isotopeMassLineEdit->setText(
        isotope->massString(ATOM_DEC_PLACES));
      m_ui.isotopeAbundanceLineEdit->setText(
        isotope->abundanceString(ATOM_DEC_PLACES));
    }
  else
    {
      m_ui.isotopeMassLineEdit->setText("");
      m_ui.isotopeAbundanceLineEdit->setText("");
    }
}


void
AtomDefDlg::clearAllDetails()
{
  m_ui.nameLineEdit->setText("");
  m_ui.symbolLineEdit->setText("");

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

  m_ui.isotopeMassLineEdit->setText("");
  m_ui.isotopeAbundanceLineEdit->setText("");
}


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

} // namespace massxpert

} // namespace msxps
