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


/////////////////////// Local includes
#include <massxpert/gui/PolChemDefWnd.hpp>
#include <massxpert/gui/MainWindow.hpp>


namespace msXpSmassXpert
{


PolChemDefWnd::PolChemDefWnd(MainWindow *parent,
                             const QString &polChemDefFilePath)
  : AbstractMainTaskWindow{
      parent, "PolChemDefWnd", "massXpert: polymer chemistry definition"}
{
  if(!polChemDefFilePath.isEmpty())
    m_polChemDef.setFilePath(polChemDefFilePath);

  if(!initialize())
    {
      QMessageBox::warning(0,
                           QString("massXpert - %1").arg(m_wndTypeName),
                           "Failed to initialize the polymer "
                           "chemistry definition window.",
                           QMessageBox::Ok);
    }

  readSettings();

  show();
}


PolChemDefWnd::~PolChemDefWnd()
{
  // These values must have been set to 0 in the constructor !!!
  delete mpa_atomDefDlg;
  delete mpa_monomerDefDlg;
  delete mpa_modifDefDlg;
  delete mpa_cleaveSpecDefDlg;
  delete mpa_fragSpecDefDlg;
  delete mpa_crossLinkerDefDlg;
}


void
PolChemDefWnd::closeEvent(QCloseEvent *event)
{
  // We are asked to close the window even if it has unsaved data.
  if(m_forciblyClose || maybeSave())
    {
      writeSettings();
      event->accept();
      return;
    }
  else
    {
      event->ignore();
    }
}


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

  setAttribute(Qt::WA_DeleteOnClose);
  statusBar()->setSizeGripEnabled(true);

  m_ui.ionizeChargeSpinBox->setRange(-1000000000, 1000000000);
  m_ui.ionizeLevelSpinBox->setRange(0, 1000000000);


  ////// Connection of the SIGNALS and SLOTS //////

  // SINGULAR ENTITIES
  connect(m_ui.nameLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(nameEditFinished()));

  connect(m_ui.leftCapLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(leftCapEditFinished()));

  connect(m_ui.rightCapLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(rightCapEditFinished()));

  connect(m_ui.ionizeFormulaLineEdit,
          SIGNAL(editingFinished()),
          this,
          SLOT(ionizeFormulaEditFinished()));

  connect(m_ui.ionizeChargeSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(ionizeChargeChanged()));

  connect(m_ui.ionizeLevelSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(ionizeLevelChanged()));


  // PLURAL ENTITIES
  connect(m_ui.atomPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(atomPushButtonClicked()));

  connect(m_ui.monomerPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(monomerPushButtonClicked()));

  connect(m_ui.modifPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(modifPushButtonClicked()));

  connect(m_ui.crossLinkerPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(crossLinkerPushButtonClicked()));

  connect(m_ui.cleavagePushButton,
          SIGNAL(clicked()),
          this,
          SLOT(cleavagePushButtonClicked()));

  connect(m_ui.fragmentationPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(fragmentationPushButtonClicked()));


  // SAVE/SAVE AS
  connect(m_ui.savePushButton, SIGNAL(clicked()), this, SLOT(save()));

  connect(m_ui.saveAsPushButton, SIGNAL(clicked()), this, SLOT(saveAs()));


  // VALIDATION
  connect(m_ui.validatePushButton, SIGNAL(clicked()), this, SLOT(validate()));


  // We have to set the name with the [*] placeholder right now
  // because the functions below complain, otherwise.

  updateWindowTitle();

  if(!m_polChemDef.filePath().isEmpty())
    {
      if(!m_polChemDef.renderXmlPolChemDefFile())
        {
          QMessageBox::warning(
            this,
            tr("massXpert - Polymer Chemistry Definition"),
            tr("%1@%2\n"
               "Failed to render the polymer chemistry definition file.")
              .arg(__FILE__)
              .arg(__LINE__),
            QMessageBox::Ok);

          return false;
        }

      m_ui.nameLineEdit->setText(m_polChemDef.name());
    }

  if(!setSingularData())
    return false;

  if(!m_polChemDef.name().isEmpty())
    statusBar()->showMessage(tr("Correctly opened file."));

  setWindowModified(false);

  return true;
}


void
PolChemDefWnd::nameEditFinished()
{
  if(m_ui.nameLineEdit->text().isEmpty())
    statusBar()->showMessage(
      tr("Name of the polymer chemistry "
         "definition cannot be empty."));
  else
    {
      if(m_ui.nameLineEdit->isModified())
        {
          m_polChemDef.setName(m_ui.nameLineEdit->text());

          m_ui.nameLineEdit->setModified(true);

          setWindowModified(true);
        }
    }
}


void
PolChemDefWnd::leftCapEditFinished()
{
  if(!m_ui.leftCapLineEdit->isModified())
    return;

  if(!checkAtomList())
    {
      return;
    }

  const QList<msXpSlibmass::Atom *> &atomList = m_polChemDef.atomList();

  msXpSlibmass::Formula formula =
    msXpSlibmass::Formula(m_ui.leftCapLineEdit->text());

  if(!formula.validate(atomList))
    {
      statusBar()->showMessage(
        tr("Formula(%1) is invalid.").arg(formula.text()));

      m_ui.leftCapLineEdit->setText(m_polChemDef.leftCap().text());
    }
  else
    {
      m_polChemDef.setLeftCap(formula);

      m_ui.leftCapLineEdit->setModified(false);

      setWindowModified(true);
    }

  return;
}


void
PolChemDefWnd::rightCapEditFinished()
{
  if(!m_ui.rightCapLineEdit->isModified())
    return;

  if(!checkAtomList())
    {
      return;
    }

  const QList<msXpSlibmass::Atom *> &atomList = m_polChemDef.atomList();

  msXpSlibmass::Formula formula =
    msXpSlibmass::Formula(m_ui.rightCapLineEdit->text());

  if(!formula.validate(atomList))
    {
      statusBar()->showMessage(
        tr("Formula(%1) is invalid.").arg(formula.text()));

      m_ui.rightCapLineEdit->setText(m_polChemDef.rightCap().text());
    }
  else
    {
      m_polChemDef.setRightCap(formula);

      m_ui.rightCapLineEdit->setModified(false);

      setWindowModified(true);
    }

  return;
}


void
PolChemDefWnd::ionizeFormulaEditFinished()
{
  if(!m_ui.ionizeFormulaLineEdit->isModified())
    return;

  if(!checkAtomList())
    return;

  const QList<msXpSlibmass::Atom *> &atomList = m_polChemDef.atomList();

  msXpSlibmass::Formula formula =
    msXpSlibmass::Formula(m_ui.ionizeFormulaLineEdit->text());

  if(!formula.validate(atomList))
    {
      statusBar()->showMessage(
        tr("Formula(%1) is invalid.").arg(formula.text()));

      m_ui.ionizeFormulaLineEdit->setText(m_polChemDef.ionizeRule().formula());
    }
  else
    {
      m_polChemDef.ionizeRulePtr()->setFormula(formula.text());

      m_ui.ionizeFormulaLineEdit->setModified(false);

      setWindowModified(true);
    }

  return;
}


void
PolChemDefWnd::ionizeChargeChanged()
{
  int charge = m_ui.ionizeChargeSpinBox->value();

  m_polChemDef.ionizeRulePtr()->setCharge(charge);

  setWindowModified(true);

  return;
}


void
PolChemDefWnd::ionizeLevelChanged()
{
  int level = m_ui.ionizeLevelSpinBox->value();

  m_polChemDef.ionizeRulePtr()->setLevel(level);

  setWindowModified(true);

  return;
}


bool
PolChemDefWnd::setSingularData()
{
  msXpSlibmass::Formula formula = m_polChemDef.leftCap();
  m_ui.leftCapLineEdit->setText(formula.text());

  formula = m_polChemDef.rightCap();
  m_ui.rightCapLineEdit->setText(formula.text());

  msXpSlibmass::IonizeRule ionizeRule = m_polChemDef.ionizeRule();
  formula                             = ionizeRule.formula();
  m_ui.ionizeFormulaLineEdit->setText(formula.text());

  m_ui.ionizeChargeSpinBox->setValue(ionizeRule.charge());
  m_ui.ionizeLevelSpinBox->setValue(ionizeRule.level());

  return true;
}


bool
PolChemDefWnd::checkAtomList()
{
  const QList<msXpSlibmass::Atom *> &atomList = m_polChemDef.atomList();

  if(atomList.size() < 1)
    {
      QMessageBox::warning(this,
                           tr("massXpert - Polymer Chemistry Definition"),
                           tr("Edit the atom definition first."),
                           QMessageBox::Ok);

      return false;
    }

  return true;
}


void
PolChemDefWnd::updateWindowTitle()
{
  QString shownName = tr("Untitled");

  if(!m_polChemDef.filePath().isEmpty())
    {
      shownName = QFileInfo(m_polChemDef.filePath()).fileName();
    }

  setWindowTitle(tr("%1 %2[*]")
                   .arg(tr("massXpert - Polymer Chemistry Definition:"))
                   .arg(shownName));
}


void
PolChemDefWnd::atomPushButtonClicked()
{
  // Check if the atom definition window was constructed already. If
  // not we construct it.

  if(!mpa_atomDefDlg)
    {
      mpa_atomDefDlg = new AtomDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_atomDefDlg->show();
      m_ui.atomPushButton->setChecked(true);

      return;
    }

  if(mpa_atomDefDlg->isVisible())
    {
      mpa_atomDefDlg->hide();
      m_ui.atomPushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_atomDefDlg->show();
      m_ui.atomPushButton->setChecked(true);

      return;
    }
}


void
PolChemDefWnd::monomerPushButtonClicked()
{
  // Check if the monomer definition window was constructed
  // already. If not we construct it.

  if(!mpa_monomerDefDlg)
    {
      mpa_monomerDefDlg = new MonomerDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_monomerDefDlg->show();
      m_ui.monomerPushButton->setChecked(true);

      return;
    }

  if(mpa_monomerDefDlg->isVisible())
    {
      mpa_monomerDefDlg->hide();
      m_ui.monomerPushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_monomerDefDlg->show();
      m_ui.monomerPushButton->setChecked(true);

      return;
    }
}


void
PolChemDefWnd::modifPushButtonClicked()
{
  // Check if the modif definition window was constructed
  // already. If not we construct it.

  if(!mpa_modifDefDlg)
    {
      mpa_modifDefDlg = new ModifDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_modifDefDlg->show();
      m_ui.modifPushButton->setChecked(true);

      return;
    }

  if(mpa_modifDefDlg->isVisible())
    {
      mpa_modifDefDlg->hide();
      m_ui.modifPushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_modifDefDlg->show();
      m_ui.monomerPushButton->setChecked(true);

      return;
    }
}


void
PolChemDefWnd::crossLinkerPushButtonClicked()
{
  // Check if the crossLinker definition window was constructed
  // already. If not we construct it.

  if(!mpa_crossLinkerDefDlg)
    {
      mpa_crossLinkerDefDlg = new CrossLinkerDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_crossLinkerDefDlg->show();
      m_ui.crossLinkerPushButton->setChecked(true);

      return;
    }

  if(mpa_crossLinkerDefDlg->isVisible())
    {
      mpa_crossLinkerDefDlg->hide();
      m_ui.crossLinkerPushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_crossLinkerDefDlg->show();
      m_ui.crossLinkerPushButton->setChecked(true);

      return;
    }
}


void
PolChemDefWnd::cleavagePushButtonClicked()
{
  // Check if the cleaveSpec definition window was constructed already. If
  // not we construct it.

  if(!mpa_cleaveSpecDefDlg)
    {
      mpa_cleaveSpecDefDlg = new CleaveSpecDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_cleaveSpecDefDlg->show();
      m_ui.cleavagePushButton->setChecked(true);

      return;
    }

  if(mpa_cleaveSpecDefDlg->isVisible())
    {
      mpa_cleaveSpecDefDlg->hide();
      m_ui.cleavagePushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_cleaveSpecDefDlg->show();
      m_ui.cleavagePushButton->setChecked(true);

      return;
    }
}


void
PolChemDefWnd::fragmentationPushButtonClicked()
{
  // Check if the fragSpec definition window was constructed already. If
  // not we construct it.

  if(!mpa_fragSpecDefDlg)
    {
      mpa_fragSpecDefDlg = new FragSpecDefDlg(
        &m_polChemDef, this, mp_parentWnd->configSettingsFilePath());
      mpa_fragSpecDefDlg->show();
      m_ui.fragmentationPushButton->setChecked(true);

      return;
    }

  if(mpa_fragSpecDefDlg->isVisible())
    {
      mpa_fragSpecDefDlg->hide();
      m_ui.fragmentationPushButton->setChecked(false);

      return;
    }
  else
    {
      mpa_fragSpecDefDlg->show();
      m_ui.fragmentationPushButton->setChecked(true);

      return;
    }
}


int
PolChemDefWnd::validate()
{
  QStringList errorList;

  int errorCount = 0;

  if(!checkAtomList())
    return -1;

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

  if(name.isEmpty())
    {
      errorList << QString(tr("Name cannot be empty."));
      ++errorCount;
    }

  const QList<msXpSlibmass::Atom *> &atomList = m_polChemDef.atomList();

  msXpSlibmass::Formula formula = m_polChemDef.leftCap();
  if(!formula.validate(atomList))
    {
      errorList << QString(tr("Validation of left cap failed."));
      ++errorCount;
    }

  formula = m_polChemDef.rightCap();
  if(!formula.validate(atomList))
    {
      errorList << QString(tr("Validation of right cap failed."));
      ++errorCount;
    }

  msXpSlibmass::IonizeRule ionizeRule = m_polChemDef.ionizeRule();
  if(!ionizeRule.validate(atomList))
    {
      errorList << QString(tr("Validation of ionization rule failed."));
      ++errorCount;
    }

  for(int iter = 0; iter < m_polChemDef.atomList().size(); ++iter)
    {
      msXpSlibmass::Atom *atom = m_polChemDef.atomList().at(iter);

      errorCount += !atom->validate();
    }

  for(int iter = 0; iter < m_polChemDef.monomerList().size(); ++iter)
    {
      Monomer *monomer = m_polChemDef.monomerList().at(iter);

      errorCount += !monomer->validate();
    }

  for(int iter = 0; iter < m_polChemDef.modifList().size(); ++iter)
    {
      Modif *modif = m_polChemDef.modifList().at(iter);

      errorCount += !modif->validate();
    }

  for(int iter = 0; iter < m_polChemDef.crossLinkerList().size(); ++iter)
    {
      CrossLinker *crossLinker = m_polChemDef.crossLinkerList().at(iter);

      errorCount += !crossLinker->validate();
    }

  for(int iter = 0; iter < m_polChemDef.cleaveSpecList().size(); ++iter)
    {
      CleaveSpec *cleaveSpec = m_polChemDef.cleaveSpecList().at(iter);

      errorCount += !cleaveSpec->validate();
    }

  for(int iter = 0; iter < m_polChemDef.fragSpecList().size(); ++iter)
    {
      FragSpec *fragSpec = m_polChemDef.fragSpecList().at(iter);

      errorCount += !fragSpec->validate();
    }

  if(errorCount > 0)
    {
      QString errorMessage = tr("Total number of errors: %1\n").arg(errorCount);

      errorMessage += errorList.join(QString(""));

      QMessageBox::warning(this,
                           tr("massXpert - Polymer Chemistry Definition"),
                           errorMessage,
                           QMessageBox::Ok);
    }
  else
    statusBar()->showMessage(
      tr("Polymer chemistry definition "
         "data validation succeeded."));

  return errorCount;
}


bool
PolChemDefWnd::maybeSave()
{
  // Returns true if we can continue(either saved ok or discard). If
  // save failed or cancel we return false to indicate to the caller
  // that something is wrong.

  if(isWindowModified())
    {
      QMessageBox::StandardButton ret;
      ret = QMessageBox::warning(this,
                                 tr("massXpert - Polymer Chemistry Definition"),
                                 tr("The document has been modified.\n"
                                    "Do you want to save your changes?"),
                                 QMessageBox::Save | QMessageBox::Discard |
                                   QMessageBox::Cancel);

      if(ret == QMessageBox::Save)
        {
          // We want to save the file. If the file has no existing
          // file associate the save as function will be called
          // automatically.
          return save();
        }
      else if(ret == QMessageBox::Discard)
        return true;
      else if(ret == QMessageBox::Cancel)
        return false;
    }

  return true;
}


bool
PolChemDefWnd::save()
{
  int ret = 0;

  // We must save to an xml file. It might be that the definition is
  // totally new, in which case the filePath() call will return
  // something invalid as a QFile object. In that case we ask the
  // saveAs() to do the job.

  // We only save if the validation is ok.
  ret += validate();

  if(ret)
    {
      // Errors encountered.

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, validate the definition "
        "prior to saving to file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }

  if(!QFile::exists(m_polChemDef.filePath()))
    return saveAs();

  if(!m_polChemDef.writeXmlFile())
    {
      statusBar()->showMessage(tr("File save failed."), 3000);

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, check that you have "
        "write permissions on the file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }

  statusBar()->showMessage(tr("File save succeeded."), 3000);

  updateWindowTitle();

  setWindowModified(false);

  return true;
}


bool
PolChemDefWnd::saveAs()
{
  int ret = 0;

  // We must save the new contents of the polymer chemistry definition
  // to an xml file that is not the file from which this definition
  // might have been saved.

  // We only save if the validation is ok.
  ret += validate();

  if(ret != 0)
    {
      // Errors encountered.

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, validate the definition "
        "prior to saving to file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }


  QString filePath = QFileDialog::getSaveFileName(this,
                                                  tr("Save Polymer Chemistry "
                                                     "Definition File"),
                                                  QDir::homePath(),
                                                  tr("XML files(*.xml *.XML)"));

  if(filePath.isEmpty())
    {
      statusBar()->showMessage(tr("File path is empty"), 3000);
      return false;
    }

  m_polChemDef.setFilePath(filePath);

  if(!m_polChemDef.writeXmlFile())
    {
      statusBar()->showMessage(tr("File save failed."), 3000);

      QMessageBox msgBox;

      msgBox.setText("Failed to save the document.");
      msgBox.setInformativeText(
        "Please, check that you have "
        "write permissions on the file.");
      msgBox.setStandardButtons(QMessageBox::Ok);

      msgBox.exec();

      return false;
    }

  statusBar()->showMessage(tr("File save succeeded."), 3000);

  updateWindowTitle();

  setWindowModified(false);

  return true;
}

} // namespace msXpSmassXpert
