/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <QInputDialog>
#include <MsXpS/libXpertMassCore/PolChemDefEntity.hpp>

/////////////////////// Local includes
#include "MzLabWnd.hpp"
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "MzLabInputOligomerTableViewDlg.hpp"
#include "MzLabOutputOligomerTableViewDlg.hpp"
#include "MzLabFormulaBasedActionsDlg.hpp"
#include "MzLabMassBasedActionsDlg.hpp"
#include "MzLabMatchBasedActionsDlg.hpp"


namespace MsXpS
{

namespace MassXpert
{


MzLabWnd::MzLabWnd(ProgramWindow *parent,
                   const QString &polChemDefFilePath,
                   const QString &application_name,
                   const QString &description)
  : AbstractMainTaskWindow{parent, "MzLabWnd", application_name, description}
{
  m_forciblyClose = false;

  if(polChemDefFilePath.isEmpty())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Polymer chemistry definition filepath empty."),
        QMessageBox::Ok);

      return;
    }

  // Start by creating the polymer chemistry definition.

  msp_polChemDef = std::make_shared<libXpertMassCore::PolChemDef>();

  msp_polChemDef->setXmlDataFilePath(polChemDefFilePath);

  if(!initialize())
    {
      QMessageBox::warning(
        0,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("Failed to initialize the mz lab window."),
        QMessageBox::Ok);
    }
}


MzLabWnd::~MzLabWnd()
{
  while(m_dlgList.size())
    delete m_dlgList.takeFirst();
}


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

  if(maybeSave())
    {
      writeSettings();
      event->accept();
      return;
    }
  else
    {
      event->ignore();
    }
}


void
MzLabWnd::readSettings()
{
  QSettings settings(dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup("MzLabWnd");
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.endGroup();
}


void
MzLabWnd::writeSettings()
{
  QSettings settings(dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup("MzLabWnd");
  settings.setValue("geometry", saveGeometry());
  settings.endGroup();
}


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

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

  // qDebug() << "PolChemDef: " << msp_polChemDef->filePath();

  // Craft the base name of the full file path
  // so that we can inform the user in the title bar of the window.
  QString file_name =
    QFileInfo(msp_polChemDef->getXmlDataFilePath()).baseName();

  setWindowTitle(QString("%1 - %2 %3[*]")
                   .arg(m_applicationName)
                   .arg(m_windowDescription)
                   .arg(file_name));

  readSettings();

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

  connect(
    m_ui.newInputListPushButton, SIGNAL(clicked()), this, SLOT(newInputList()));

  connect(m_ui.catalogue1ListWidget,
          SIGNAL(itemClicked(QListWidgetItem *)),
          this,
          SLOT(inputListWidgetItemClicked(QListWidgetItem *)));

  connect(m_ui.catalogue2ListWidget,
          SIGNAL(itemClicked(QListWidgetItem *)),
          this,
          SLOT(inputListWidgetItemClicked(QListWidgetItem *)));

  connect(m_ui.deleteInputListPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(deleteInputListItem()));

  // We need to update m_ionizer each time one of the values is
  // changed.

  connect(m_ui.ionizationChargeSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(ionizationChargeChanged(int)));

  connect(m_ui.ionizationLevelSpinBox,
          SIGNAL(valueChanged(int)),
          this,
          SLOT(ionizationLevelChanged(int)));

  connect(m_ui.ionizationFormulaLineEdit,
          SIGNAL(textChanged(const QString &)),
          this,
          SLOT(ionizationFormulaChanged(const QString &)));

  // The dialog actions that might be triggered within the lab are
  // called _via_ as set of buttons:

  connect(m_ui.formulaBasedActionsPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(formulaBasedActionsPushButton()));

  connect(m_ui.massBasedActionsPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(massBasedActionsPushButton()));

  connect(m_ui.matchBasedActionsPushButton,
          SIGNAL(clicked()),
          this,
          SLOT(matchBasedActionsPushButton()));


  if(!libXpertMassCore::PolChemDef::renderXmlPolChemDefFile(msp_polChemDef))
    return false;

  m_ui.ionizationChargeSpinBox->setRange(1, 1000000000);
  m_ui.ionizationLevelSpinBox->setRange(0, 1000000000);

  m_ionizer = msp_polChemDef->ionizer();

  m_ui.ionizationFormulaLineEdit->setText(m_ionizer.formula());
  m_ui.ionizationChargeSpinBox->setValue(m_ionizer.charge());
  m_ui.ionizationLevelSpinBox->setValue(m_ionizer.level());

  show();

  return true;
}


libXpertMassCore::PolChemDefCstSPtr
MzLabWnd::polChemDefCstSPtr()
{
  return msp_polChemDef;
}


libXpertMassCore::PolChemDefSPtr
MzLabWnd::pol_chem_def_sp()
{
  return msp_polChemDef;
}


const libXpertMassCore::Ionizer &
MzLabWnd::ionizer() const
{
  return m_ionizer;
}


void
MzLabWnd::updateWindowTitle()
{
  setWindowTitle(QString("%1 - %2 %3[*]")
                   .arg(m_applicationName)
                   .arg(m_windowDescription)
                   .arg(msp_polChemDef->name()));
}


void
MzLabWnd::ionizationChargeChanged(int value)
{
  m_ionizer.setCharge(value);
}


void
MzLabWnd::ionizationLevelChanged(int value)
{
  m_ionizer.setLevel(value);
}

void
MzLabWnd::ionizationFormulaChanged(const QString &text)
{
  libXpertMassCore::Formula formula(text);

  libXpertMassCore::IsotopicDataCstSPtr isotopic_data_csp =
    msp_polChemDef->getIsotopicDataCstSPtr();

  if(!formula.validate(isotopic_data_csp))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "The formula '%3' is not valid.")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(formula.toString()));

      m_ui.ionizationFormulaLineEdit->setText("");

      return;
    }

  m_ionizer.setFormula(text);
}


void
MzLabWnd::formulaBasedActionsPushButton()
{
  MzLabFormulaBasedActionsDlg *dlg = new MzLabFormulaBasedActionsDlg(
    this, dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath());
  dlg->show();
}


void
MzLabWnd::massBasedActionsPushButton()
{
  MzLabMassBasedActionsDlg *dlg = new MzLabMassBasedActionsDlg(
    this, dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath());
  dlg->show();
}


void
MzLabWnd::matchBasedActionsPushButton()
{
  MzLabMatchBasedActionsDlg *dlg = new MzLabMatchBasedActionsDlg(
    this, dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath());
  dlg->show();
}


bool
MzLabWnd::inputListDlg(MzLabInputOligomerTableViewDlg **dlg)
{
  QListWidgetItem *item = 0;

  // Which list of lists is selected ?

  if(m_ui.catalogue1RadioButton->isChecked())
    item = m_ui.catalogue1ListWidget->currentItem();
  else
    item = m_ui.catalogue2ListWidget->currentItem();

  if(!item)
    return false;

  // Get the name of the dialog window represented by the item and
  // find the dialog window itself.

  QString text = item->text();

  MzLabInputOligomerTableViewDlg *dialog = findDlg(text);
  Q_ASSERT(dialog);

  *dlg = dialog;

  return true;
}


bool
MzLabWnd::inputListsDlg(MzLabInputOligomerTableViewDlg **dlg1,
                        MzLabInputOligomerTableViewDlg **dlg2)
{
  QListWidgetItem *item1 = 0;
  QListWidgetItem *item2 = 0;

  // Which lists of lists is selected ?

  item1 = m_ui.catalogue1ListWidget->currentItem();
  item2 = m_ui.catalogue2ListWidget->currentItem();

  if(!item1 || !item2)
    return false;

  // Get the name of the dialog window represented by the two items
  // and find the corresponding dialog windows.

  QString text = item1->text();

  MzLabInputOligomerTableViewDlg *dialog = findDlg(text);
  Q_ASSERT(dialog);

  *dlg1 = dialog;

  text = item2->text();

  dialog = 0;

  dialog = findDlg(text);
  Q_ASSERT(dialog);

  *dlg2 = dialog;

  return true;
}


MzLabInputOligomerTableViewDlg *
MzLabWnd::inputListDlg()
{
  MzLabInputOligomerTableViewDlg *dlg = 0;

  if(!inputListDlg(&dlg))
    {
      return 0;
    }

  return dlg;
}


bool
MzLabWnd::inPlaceCalculation()
{
  bool inPlace = m_ui.inPlaceCalculationCheckBox->isChecked();
  return inPlace;
}


MzLabInputOligomerTableViewDlg *
MzLabWnd::newInputList()
{
  return newInputList(QString(), libXpertMassCore::Enums::MassType::MASS_NONE);
}

MzLabInputOligomerTableViewDlg *
MzLabWnd::newInputList(QString name, libXpertMassCore::Enums::MassType massType)
{
  // Ask that the user give a name to the list.

  bool ok;

  QString localName = name;

  if(localName.isEmpty())
    {
      // Each dialog must have a name. So ask for one.
      localName = QInputDialog::getText(this,
                                        tr("Give a name to the new input list"),
                                        tr("List name:"),
                                        QLineEdit::Normal,
                                        tr("New input list name"),
                                        &ok);

      if(localName.isEmpty() || !ok)
        return 0;
    }
  else
    {
      // The name is already set, only ask the user to confirm it.

      localName =
        QInputDialog::getText(this,
                              tr("Confirm the name of the new input list"),
                              tr("List name:"),
                              QLineEdit::Normal,
                              localName,
                              &ok);

      if(localName.isEmpty() || !ok)
        return 0;
    }


  // Make sure the name is not already taken.
  if(findDlg(localName))
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("%1@%2\n"
           "The '%3' name is already taken.")
          .arg(__FILE__)
          .arg(__LINE__)
          .arg(localName),
        QMessageBox::Ok);
      return 0;
    }

  MzLabInputOligomerTableViewDlg *dlg =
    new MzLabInputOligomerTableViewDlg(this, localName, massType);

  dlg->show();

  // Now we can add the item to the lists of input...

  m_ui.catalogue1ListWidget->addItem(localName);
  m_ui.catalogue2ListWidget->addItem(localName);

  // Finally we can add the list to the list of dialog windows.

  m_dlgList.append(dlg);

  setWindowModified(true);

  return dlg;
}


MzLabInputOligomerTableViewDlg *
MzLabWnd::findDlg(const QString &name)
{
  for(int iter = 0; iter < m_dlgList.size(); ++iter)
    {
      MzLabInputOligomerTableViewDlg *dlg = m_dlgList.at(iter);

      if(dlg->name() == name)
        return dlg;
    }

  return 0;
}


void
MzLabWnd::inputListWidgetItemClicked(QListWidgetItem *item)
{
  QString text = item->text();

  MzLabInputOligomerTableViewDlg *dlg = findDlg(text);
  Q_ASSERT(dlg);

  dlg->show();
}


void
MzLabWnd::deleteInputListItem()
{
  // Do this for input 1 list only.

  QListWidgetItem *item = m_ui.catalogue1ListWidget->currentItem();
  if(!item)
    return;

  // Get the name of the dialog window represented by the item and
  // find the dialog window itself.

  QString text = item->text();

  // Destroy the dialog window and update the 2 lists contents.
  destroyDlg(text);

  // If the last dialog window is destroyed, then the lab is not
  // modified anymore.
  if(!m_dlgList.size())
    setWindowModified(false);
}


void
MzLabWnd::destroyDlg(const QString &name)
{
  MzLabInputOligomerTableViewDlg *dlg = findDlg(name);
  Q_ASSERT(dlg);

  // Remove the dialog window from the list of dialog windows and
  // delete it.
#ifdef DEBUG
  int result = m_dlgList.removeAll(dlg);
  Q_ASSERT(result);
#else
  m_dlgList.removeAll(dlg);
#endif
  delete(dlg);

  // Update the listWidgets(1 and 2) corresponding items.

  QList<QListWidgetItem *> list =
    m_ui.catalogue1ListWidget->findItems(name, Qt::MatchExactly);
  Q_ASSERT(list.size() == 1);

  QListWidgetItem *item = list.first();

  m_ui.catalogue1ListWidget->takeItem(m_ui.catalogue1ListWidget->row(item));

  if(item)
    delete(item);

  list = m_ui.catalogue2ListWidget->findItems(name, Qt::MatchExactly);
  Q_ASSERT(list.size() == 1);

  item = list.first();

  m_ui.catalogue2ListWidget->takeItem(m_ui.catalogue2ListWidget->row(item));

  if(item)
    delete(item);
}


bool
MzLabWnd::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,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        tr("The mz Lab has been modified.\n"
           "OK to exit?"),
        QMessageBox::Yes | QMessageBox::No);

      if(ret == QMessageBox::Yes)
        return true;
      else
        return false;
    }

  return true;
}


} // namespace MassXpert

} // namespace MsXpS
