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

   http://www.massxpert.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>
#include<QFileDialog>

/////////////////////// Local includes
#include "compositionsDlg.hpp"
#include "application.hpp"
#include "coordinates.hpp"

namespace massXpert
{

  CompositionsDlg::CompositionsDlg(QWidget *parent,
				    Polymer *polymer,
				    CalcOptions *calcOptions,
				    IonizeRule *ionizeRule)
    : QDialog(parent),
      mp_polymer(polymer), 
      mp_calcOptions(calcOptions),
      mp_ionizeRule(ionizeRule)
  {
    Q_ASSERT(parent);
    Q_ASSERT(mp_polymer && mp_calcOptions && mp_ionizeRule);
  
    m_ui.setupUi(this);
  
    mp_editorWnd = static_cast<SequenceEditorWnd *>(parent);
    
    updateSelectionData();
        
    setupTreeView();

    // The results-exporting menus. ////////////////////////////////

    QStringList comboBoxItemList;

    comboBoxItemList 
      << tr("To Clipboard") 
      << tr("To File")
      << tr("Select File");
  
    m_ui.exportResultsComboBox->addItems(comboBoxItemList);
  
    connect(m_ui.exportResultsComboBox,
	     SIGNAL(activated(int)),
	     this,
	     SLOT(exportResults(int)));

    mpa_resultsString = new QString();
  
    //////////////////////////////////// The results-exporting menus.
  

    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
  
    settings.beginGroup("compositions_dlg");

    restoreGeometry(settings.value("geometry").toByteArray());

    m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
  
    settings.endGroup();


    connect(m_ui.updateSelectionDataPushButton,
	     SIGNAL(clicked()),
	     this,
	     SLOT(updateSelectionData()));

    connect(m_ui.monomericPushButton,
	     SIGNAL(clicked()),
	     this,
	     SLOT(monomericComposition()));

    connect(m_ui.elementalPushButton, 
	     SIGNAL(clicked()),
	     this, 
	     SLOT(elementalComposition()));
  }


  CompositionsDlg::~CompositionsDlg()
  {
    delete mpa_compositionTreeViewModel;
  
    delete mpa_compositionProxyModel;

    delete mpa_resultsString;
    
    freeMonomerList();
  }


  void 
  CompositionsDlg::closeEvent(QCloseEvent *event)
  {
    if (event)
      printf("%s", "");
  
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
  
    settings.beginGroup("compositions_dlg");

    settings.setValue("geometry", saveGeometry());

    settings.setValue("splitter", m_ui.splitter->saveState());
  
    settings.endGroup();
  }


  SequenceEditorWnd *
  CompositionsDlg::editorWnd()
  {
    return mp_editorWnd;
  }


  void
  CompositionsDlg::updateSelectionData()
  {
    // The selection might exist as a list of region selections.
    
    if (mp_editorWnd->mpa_editorGraphicsView->
	selectionIndices(&m_coordinateList))
      {
	m_ui.selectionCoordinatesLineEdit->
	  setText(m_coordinateList.positionsAsText());
	
	m_ui.selectedSequenceRadioButton->setChecked(true);
      }
    else
      {
	m_ui.selectionCoordinatesLineEdit->setText("");
	
	m_ui.wholeSequenceRadioButton->setChecked(true);
      }
    
    m_ui.monomericPushButton->setFocus();
  }
  

  bool
  CompositionsDlg::fetchValidateInputData()
  {
    if (!m_ui.selectedSequenceRadioButton->isChecked())
      {
	m_coordinateList.setCoordinates(Coordinates(0, 
						      mp_polymer->size() - 1));
      }
    
    // Make sure the sequence still has a number of residues
    // compatible with the current m_coordinateList. this because the
    // sequence might be edited between opening this dialog window and
    // actually using it.

    int polymerSize = mp_polymer->size();
    
    for (int iter = 0; iter < m_coordinateList.size(); ++iter)
      {
	// New coordinates instance we are iterating into.
	Coordinates *coordinates = m_coordinateList.at(iter);
		
	if(coordinates->start() >= polymerSize ||
	    coordinates->end() >= polymerSize)
	  {
	    QMessageBox::warning(0, 
				  tr("massXpert - Compositions"),
				  tr("Selection data are no more valid.\n"
				      "Please update these data."),
				  QMessageBox::Ok);
	    return false;
	  }
      }
    
    // We also want to know if the cross-links should be taken into account.
    
    if (mp_calcOptions->monomerEntities() & MXT_MONOMER_CHEMENT_CROSS_LINK)
      {
	// If the whole sequence is dealt with, then by definition all
	// the cross-links are encompassed by the sequence.
	
	if(!m_ui.selectedSequenceRadioButton->isChecked())
	  {
	    m_ui.incompleteCrossLinkWarningLabel->setText 
	     (tr("Incomplete cross-links: %1").
	       arg(0));
	  }
	else
	  {
	    // We have to count the incomplete cross-links.

	    const CrossLinkList &crossLinkList = mp_polymer->crossLinkList();
	  
	    int crossLinkPartial = 0;
	  
	    for (int iter = 0; iter < crossLinkList.size(); ++iter)
	      {
		CrossLink *crossLink = crossLinkList.at(iter);
	  
		int ret = 
		  crossLink->encompassedBy(m_coordinateList);
	  
		if(ret == MXP_CROSS_LINK_ENCOMPASSED_FULL)
		  {
		    // 		qDebug() << __FILE__ << __LINE__
		    // 			  << "CrossLink at iter:" << iter
		    // 			  << "is fully encompassed";
		  }
		else if (ret == MXP_CROSS_LINK_ENCOMPASSED_PARTIAL)
		  {
		    // 		qDebug() << __FILE__ << __LINE__
		    // 			  << "CrossLink at iter:" << iter
		    // 			  << "is partially encompassed";
	      
		    ++crossLinkPartial;
		  }
		else
		  {
		    // 		qDebug() << __FILE__ << __LINE__
		    // 			  << "CrossLink at iter:" << iter
		    // 			  << "is not encompassed at all";
		  }
	      }

	    m_ui.incompleteCrossLinkWarningLabel->setText 
	     (tr("Incomplete cross-links: %1").
	       arg(crossLinkPartial));
	  }
      }
    else
      {
	m_ui.incompleteCrossLinkWarningLabel->
	  setText(tr("Not accounting for cross-links"));
      }
    
    return true;
  }


  void 
  CompositionsDlg::monomericComposition()
  {
    if (!fetchValidateInputData())
      {
	QMessageBox::warning(0, 
			      tr("massXpert - Compositions"),
			      tr("Failed validating input data."),
			      QMessageBox::Ok);
	return;
      }

    Monomer *fakeMonomer = 0;
  
    // Empty the treeview. This will also remove all the monomer items
    // in the m_monomerList.
    mpa_compositionTreeViewModel->removeAll();
  
    setCursor(Qt::WaitCursor);
  
    // Iterate in the sequence and count all the occurrences of the
    // different monomer that comprise the sequence.

    if (mpa_compositionTreeViewModel)
      {
	delete mpa_compositionTreeViewModel;
	mpa_compositionTreeViewModel = 0;
      }
  
    if (mpa_compositionProxyModel)
      {
	delete mpa_compositionProxyModel;
	mpa_compositionProxyModel = 0;
      }

    for (int iter = 0; iter < m_coordinateList.size(); ++iter)
      {
	// New coordinates instance we are iterating into.
	Coordinates *coordinates = m_coordinateList.at(iter);
	
	for(int jter = coordinates->start() ; 
	     jter < coordinates->end() + 1; ++jter)
	  {
	    bool processed = false;

	    const Monomer *iterMonomer = mp_polymer->at(jter);
	    Q_ASSERT(iterMonomer);
      
	    // Check if monomer by same name is not already in our list of
	    // fake monomers.

	    for (int kter = 0; kter < m_monomerList.size(); ++kter)
	      {
		fakeMonomer = m_monomerList.at(kter);
		Q_ASSERT(fakeMonomer);
	  
		if(iterMonomer->name() == fakeMonomer->name())
		  {
		    Prop *prop = fakeMonomer->prop("MONOMER_COUNT");
		
		    // The fake monomer MUST have a count prop !
		    Q_ASSERT(prop);
	      
		    int *count = 
		      new int(*static_cast<const int *>(prop->data()));

		    ++(*count);
		    prop->setData(count);
	      	      
		    if (iterMonomer->isModified())
		      {
			// The monomer in the sequence is modified. Let the
			// fake monomer know it.
			Prop *prop = fakeMonomer->prop("MODIF_COUNT");
		  
			// The fake monomer might not have a modif prop.
			if(prop)
			  {
			    int *count = 
			      new int(*static_cast<const int *>(prop->data()));
			    ++(*count);
			    prop->setData(count);
			  }
			else
			  {
			    IntProp *prop = new IntProp("MODIF_COUNT", 1);
			    fakeMonomer->appendProp(prop);
			  }
		      }
	      
		    processed = true;
	      
		    break;
		  }
	      }
	    // End of 
	    // for (int kter = 0; kter < m_monomerList.size(); ++kter)
	      
	    // Did we find a fake monomer, thus processing the currently
	    // iterated sequence monomer or not ? If not we still have to do
	    // all the work.
      
	    if (!processed)
	      {
		fakeMonomer = iterMonomer->clone();
	  
		IntProp *prop = new IntProp("MONOMER_COUNT", 1);
		fakeMonomer->appendProp(prop);
	  
		if(iterMonomer->isModified())
		  {
		    // The monomer in the sequence is modified. Let the
		    // fake monomer know it.
		      
		    IntProp *prop = new IntProp("MODIF_COUNT", 1);
		    fakeMonomer->appendProp(prop);
		  }
	  
		m_monomerList.append(fakeMonomer);
	      }
	  }
	// End of
	// for (int iter = startIndex ; iter < endIndex + 1; ++iter)

	setupTreeView();
  
	prepareResultsTxtString(MXP_TARGET_MONOMERIC);
  
	setCursor(Qt::ArrowCursor);
      }
  }
  

  void 
  CompositionsDlg::elementalComposition()
  {
    // For each monomer in the sequence, get the formula and account it.

    if (!fetchValidateInputData())
      {
	QMessageBox::warning(0, 
			      tr("massXpert - Compositions"),
			      tr("Failed validating input data."),
			      QMessageBox::Ok);
	return;
      }

    QString composition = mp_polymer->elementalComposition(*mp_ionizeRule,
                                                           m_coordinateList,
                                                           *mp_calcOptions);
        m_ui.elementalCompositionLineEdit->
      setText(composition);

    prepareResultsTxtString(MXP_TARGET_ELEMENTAL);

    setCursor(Qt::ArrowCursor);

  }


  void
  CompositionsDlg::setupTreeView()
  {
    // Model stuff all thought for sorting.
    mpa_compositionTreeViewModel = 
      new CompositionTreeViewModel(&m_monomerList, this);

    mpa_compositionProxyModel = new CompositionTreeViewSortProxyModel(this);
    mpa_compositionProxyModel->setSourceModel(mpa_compositionTreeViewModel);

    m_ui.compositionTreeView->setModel(mpa_compositionProxyModel);
    m_ui.compositionTreeView->setParentDlg(this);
    mpa_compositionTreeViewModel->setTreeView(m_ui.compositionTreeView);
  }


  void 
  CompositionsDlg::freeMonomerList()
  {
    while(!m_monomerList.isEmpty())
      delete m_monomerList.takeFirst();
  }


  // The results-exporting functions. ////////////////////////////////
  // The results-exporting functions. ////////////////////////////////
  // The results-exporting functions. ////////////////////////////////
  void
  CompositionsDlg::exportResults(int index)
  {
    // Remember that we had set up the combobox with the following strings:
    // << tr("To &Clipboard") 
    // << tr("To &File")
    // << tr("&Select File");

    if (index == 0)
      {
	exportResultsClipboard();
      }
    else if (index == 1)
      {
	exportResultsFile();
      }
    else if (index == 2)
      {
	selectResultsFile();
      }
    else 
      Q_ASSERT(0);
  
  }


  void
  CompositionsDlg::prepareResultsTxtString(int target)
  {
    mpa_resultsString->clear();
  
    *mpa_resultsString += QObject::tr("# \n"
                                      "# -------------\n"
                                      "# Compositions: \n"
                                      "# -------------\n");
    
    if (target == MXP_TARGET_ELEMENTAL)
      {
	*mpa_resultsString += QObject::tr("\nIonization rule:\n");

	*mpa_resultsString += QObject::tr("Formula: %1 - ")
	  .arg(mp_ionizeRule->formula());
  
	*mpa_resultsString += QObject::tr("Charge: %1 - ")
	  .arg(mp_ionizeRule->charge());
  
	*mpa_resultsString += QObject::tr("Level: %1\n")
	  .arg(mp_ionizeRule->level());
  
  
	*mpa_resultsString += QObject::tr("\nCalculation options:\n");
  
      
	bool withEntities =(mp_calcOptions->monomerEntities() &
			 MXT_MONOMER_CHEMENT_MODIF);

	// We want a delimited sequence with indication of the
	// different sequence regions for which the composition was
	// determined, thus the true below.
	
	QString *sequence = 
	  mp_polymer->monomerText(m_coordinateList, withEntities, true);
	
	*mpa_resultsString += *sequence;
	
	delete sequence;
      
	if(withEntities)
	  *mpa_resultsString += QObject::tr("Account monomer modifs: yes\n");
	else
	  *mpa_resultsString += QObject::tr("Account monomer modifs: no\n");
	
	// Left end and right end modifs
	withEntities =(mp_calcOptions->polymerEntities() &
			MXT_POLYMER_CHEMENT_LEFT_END_MODIF ||
			mp_calcOptions->polymerEntities() &
			MXT_POLYMER_CHEMENT_RIGHT_END_MODIF);
	
	if(!withEntities)
	  {
	    *mpa_resultsString += QObject::tr("Account ends' modifs: no\n");
	  }
	else
	  {
	    *mpa_resultsString += QObject::tr("Account ends' modifs: yes - ");
	  
	    // Left end modif
	    withEntities =(mp_calcOptions->polymerEntities() &
			    MXT_POLYMER_CHEMENT_LEFT_END_MODIF);
	    if (withEntities)
	      {
		*mpa_resultsString += QObject::tr("Left end modif: %1 - ")
		  .arg(mp_polymer->leftEndModif().name());
	      }
	  
	    // Right end modif
	    withEntities =(mp_calcOptions->polymerEntities() &
			    MXT_POLYMER_CHEMENT_RIGHT_END_MODIF);
	    if (withEntities)
	      {
		*mpa_resultsString += QObject::tr("Right end modif: %1")
		  .arg(mp_polymer->leftEndModif().name());
	      }
	  }
	
	*mpa_resultsString += QObject::tr("\n\nElemental composition: %1")
	  .arg(m_ui.elementalCompositionLineEdit->text());
      }
    else if (target == MXP_TARGET_MONOMERIC)
      {
	CompositionTreeViewModel *model = 
	  static_cast<CompositionTreeViewModel *> 
	 (m_ui.compositionTreeView->model());
	Q_ASSERT(model);
	
	int rowCount = model->rowCount();
	//   qDebug() << __FILE__ << __LINE__ << "rowCount" << rowCount;
	if(!rowCount)
	  return;
      
	QString composString;
      
	for(int iter = 0; iter < rowCount; ++iter)
	  {
	    QModelIndex currentIndex = model->index(iter,
						     COMPOSITION_NAME_COLUMN,
						     QModelIndex());
	    Q_ASSERT(currentIndex.isValid());
	  
	    composString += QObject::tr("%1 - ").
	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
	    

	    currentIndex = model->index(iter,
					 COMPOSITION_CODE_COLUMN,
					 QModelIndex());
	    Q_ASSERT(currentIndex.isValid());
	  
	    composString += QObject::tr("%1 - ").
	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
	  
	  
	    currentIndex = model->index(iter,
					 COMPOSITION_MODIF_COLUMN,
					 QModelIndex());
	    Q_ASSERT(currentIndex.isValid());
	  
	    composString += QObject::tr("Modified ?: %1 - ").
	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
	  
	  
	    currentIndex = model->index(iter,
					 COMPOSITION_COUNT_COLUMN,
					 QModelIndex());
	    Q_ASSERT(currentIndex.isValid());
	  
	    composString += QObject::tr("Count: %1.\n").
	      arg(model->data(currentIndex, Qt::DisplayRole).toString());
	  }
	*mpa_resultsString += composString;
      }
    else
      Q_ASSERT(0);
  }


  bool 
  CompositionsDlg::exportResultsClipboard()
  {
    QClipboard *clipboard = QApplication::clipboard();

    clipboard->setText(*mpa_resultsString, QClipboard::Clipboard);
  
    return true;
  }


  bool 
  CompositionsDlg::exportResultsFile()
  {
    if (m_resultsFilePath.isEmpty())
      {
	if(!selectResultsFile())
	  return false;
      }
  
    QFile file(m_resultsFilePath);
  
    if (!file.open(QIODevice::WriteOnly | QIODevice::Append))
      {
	QMessageBox::information(0, 
				  tr("massXpert - Export Data"),
				  tr("Failed to open file in append mode."),
				  QMessageBox::Ok);
	return false;
      }
  
    QTextStream stream(&file);
    stream.setCodec("UTF-8");

    stream << *mpa_resultsString;
  
    file.close();

    return true;
  }


  bool 
  CompositionsDlg::selectResultsFile()
  {
    m_resultsFilePath = 
      QFileDialog::getSaveFileName(this, tr("Select file to export data to"),
				    QDir::homePath(),
				    tr("Data files(*.dat *.DAT)"));
  
    if (m_resultsFilePath.isEmpty())
      return false;

    return true;
  }
  //////////////////////////////////// The results-exporting functions.
  //////////////////////////////////// The results-exporting functions.
  //////////////////////////////////// The results-exporting functions.

} // namespace massXpert
