/* 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 <QtGui>
#include <QtGlobal>
#include <QMessageBox>
#include <QInputDialog>

/////////////////////// Local includes
#include <massxpert/gui/MzLabWnd.hpp>
#include <massxpert/gui/MainWindow.hpp>
#include <massxpert/gui/MzLabInputOligomerTableViewDlg.hpp>
#include <massxpert/gui/MzLabOutputOligomerTableViewDlg.hpp>
#include <massxpert/gui/MzLabFormulaBasedActionsDlg.hpp>
#include <massxpert/gui/MzLabMassBasedActionsDlg.hpp>
#include <massxpert/gui/MzLabMatchBasedActionsDlg.hpp>


namespace msXpSmassXpert
{

MzLabWnd::MzLabWnd(MainWindow *parent, const QString &polChemDefFilePath)
  : AbstractMainTaskWindow{parent, "MzLabWnd", "massXpert: m/z lab"}
{
  m_forciblyClose = false;

  if(polChemDefFilePath.isEmpty())
    {
      QMessageBox::warning(0,
                           tr("massXpert - mz Lab"),
                           tr("Polymer chemistry definition filepath empty."),
                           QMessageBox::Ok);

      return;
    }

  m_polChemDef.setFilePath(polChemDefFilePath);

  if(!initialize())
    {
      QMessageBox::warning(0,
                           tr("massXpert - mz Lab"),
                           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(mp_parentWnd->configSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup("MzLabWnd");
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.endGroup();
}


void
MzLabWnd::writeSettings()
{
  QSettings settings(mp_parentWnd->configSettingsFilePath(),
                     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);

  setWindowTitle(
    tr("%1 %2[*]").arg(tr("massXpert: mz Lab - ")).arg(m_polChemDef.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_ionizeRule 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(!m_polChemDef.renderXmlPolChemDefFile())
    return false;

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

  m_ionizeRule = m_polChemDef.ionizeRule();

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

  show();

  return true;
}


PolChemDef *
MzLabWnd::polChemDef()
{
  return &m_polChemDef;
}


const msXpSlibmass::IonizeRule &
MzLabWnd::ionizeRule() const
{
  return m_ionizeRule;
}


void
MzLabWnd::updateWindowTitle()
{
  setWindowTitle(
    tr("%1 %2[*]").arg(tr("massXpert: mz Lab - ")).arg(m_polChemDef.name()));
}


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


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

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

  if(!formula.validate(m_polChemDef.atomList()))
    {
      QMessageBox::warning(this,
                           tr("massXpert: mz Lab"),
                           tr("%1@%2\n"
                              "The formula '%3' is not valid.")
                             .arg(__FILE__)
                             .arg(__LINE__)
                             .arg(formula.text()));

      m_ui.ionizationFormulaLineEdit->setText("");

      return;
    }

  m_ionizeRule.setFormula(text);
}


void
MzLabWnd::formulaBasedActionsPushButton()
{
  MzLabFormulaBasedActionsDlg *dlg = new MzLabFormulaBasedActionsDlg(
    this, mp_parentWnd->configSettingsFilePath());
  dlg->show();
}


void
MzLabWnd::massBasedActionsPushButton()
{
  MzLabMassBasedActionsDlg *dlg =
    new MzLabMassBasedActionsDlg(this, mp_parentWnd->configSettingsFilePath());
  dlg->show();
}


void
MzLabWnd::matchBasedActionsPushButton()
{
  MzLabMatchBasedActionsDlg *dlg =
    new MzLabMatchBasedActionsDlg(this, mp_parentWnd->configSettingsFilePath());
  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(), msXpS::MassType::MASS_NONE);
}

MzLabInputOligomerTableViewDlg *
MzLabWnd::newInputList(QString name, msXpS::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,
                           tr("massXpert: mz Lab"),
                           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,
                                 tr("massXpert - mz Lab"),
                                 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 msXpSmassXpert
