/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.filomace.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Qt includes
#include <QMessageBox>


/////////////////////// Local includes
#include "polChemDefWnd.hpp"
#include "application.hpp"


namespace massXpert
{

  PolChemDefWnd::PolChemDefWnd(const QString &filePath)
  {
    m_forciblyClose = false;
    m_noDelistWnd = false;

    // Init to 0 all the window pointers, because we'll need to check
    // the construction of the these windows.

    mpa_atomDefDlg = 0;
    mpa_monomerDefDlg = 0;
    mpa_modifDefDlg = 0;
    mpa_cleaveSpecDefDlg = 0;
    mpa_fragSpecDefDlg = 0;
    mpa_crossLinkerDefDlg = 0;

    if (!filePath.isEmpty())
      m_polChemDef.setFilePath(filePath);
  
    if (!initialize())
      {
	QMessageBox::warning(0, 
			      tr("massXpert - Polymer Chemistry Definition"),
			      tr("%1@%2\n"
				  "Failed to initialize the polymer "
				  "chemistry definition window.")
			      .arg(__FILE__)
			      .arg(__LINE__),
			      QMessageBox::Ok);
      }
  
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
    restoreGeometry(settings.value
		    ("pol_chem_def_wnd/geometry").toByteArray());
  
    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)
  {
    Application *application = static_cast<Application *>(qApp);

    // We are asked to close the window even if it has unsaved data.
    if (m_forciblyClose)
      {
	m_forciblyClose = false;
      
	// We have to delist the window.
	if(!m_noDelistWnd)
	  {
	    m_noDelistWnd = false;
	    int index = application->polChemDefWndList()->indexOf(this);
	  
	    application->polChemDefWndList()->takeAt(index);
	  }
      
	QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
	settings.setValue("pol_chem_def_wnd/geometry", saveGeometry());

	event->accept();
      
	return;
      }

    if (maybeSave())
      {
	// We are asked not to remove this window from the list of all
	// the polymer chemistry definition windows. This occurs when
	// all the windows are closed in a raw in the application
	// object.

	if(m_noDelistWnd)
	  {
	    m_noDelistWnd = false;
	  
	    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
	    settings.setValue("pol_chem_def_wnd/geometry", 
			       saveGeometry());

	    event->accept();
	  
	    return;
	  }
      
	// We must remove this window from the application's list of
	// such windows:

	int index = application->polChemDefWndList()->indexOf(this);
      
	application->polChemDefWndList()->takeAt(index);
  
	QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);

	settings.setValue("pol_chem_def_wnd/geometry", 
			   saveGeometry());
      
	event->accept();
      }
    else 
      {
	event->ignore();
      }
  }


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

    QPixmap pixmap(":/images/massxpert-icon-32.png");
    QIcon icon(pixmap);
    setWindowIcon(icon);

    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<Atom*> &atomList = m_polChemDef.atomList();

    Formula formula = 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<Atom*> &atomList = m_polChemDef.atomList();

    Formula formula = 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<Atom*> &atomList = m_polChemDef.atomList();

    Formula formula = 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()
  {
    Formula formula = m_polChemDef.leftCap();
    m_ui.leftCapLineEdit->setText(formula.text());
  
    formula = m_polChemDef.rightCap();
    m_ui.rightCapLineEdit->setText(formula.text());
  
    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<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);
	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);
	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);
	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);
	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);
	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);
	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<Atom*> &atomList = m_polChemDef.atomList();

    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;
      }

    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)
      {
	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 massXpert
