/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006-2013 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 <QCloseEvent>
#include <QDebug>


/////////////////////// Local includes
#include "mzLabMatchBasedActionsDlg.hpp"
#include "mzLabWnd.hpp"
#include "mzLabInputOligomerTableViewDlg.hpp"
#include "mzLabOutputOligomerTableViewDlg.hpp"
#include "application.hpp"
#include "oligomerPair.hpp"
#include "fragmentOligomer.hpp"


namespace massXpert
{

  MzLabMatchBasedActionsDlg::MzLabMatchBasedActionsDlg
  (QWidget *parent) : QDialog(parent)
  {
    Q_ASSERT(parent);

    mp_mzLabWnd = static_cast<MzLabWnd *>(parent);

    m_ui.setupUi(this);

    setWindowTitle(tr("massXpert: mz Lab - Match-based Actions"));
    
    // The tolerance when filtering mono/avg masses...
    QStringList stringList;
  
    stringList << tr("AMU") << tr("PCT") << tr("PPM");
  
    m_ui.toleranceComboBox->insertItems(0, stringList);
  
    m_ui.toleranceComboBox->setCurrentIndex(0);
    
    m_ui.toleranceComboBox->setToolTip(tr("AMU: atom mass unit \n"
					    "PCT: percent \n"
					    "PPM: part per million"));

    m_ui.toleranceLineEdit->setText("1");

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

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

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

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

    settings.endGroup();
  }
  

  MzLabMatchBasedActionsDlg::~MzLabMatchBasedActionsDlg()
  {
  }


  void
  MzLabMatchBasedActionsDlg::closeEvent(QCloseEvent *event)
  {
    QSettings settings 
     (static_cast<Application *>(qApp)->configSettingsFilePath(), 
       QSettings::IniFormat);
    
    settings.beginGroup("mz_lab_wnd_match_based_actions");
    
    settings.setValue("geometry", saveGeometry());
    
    settings.endGroup();

    QDialog::closeEvent(event);
  }

  double
  MzLabMatchBasedActionsDlg::calculateTolerance(double value)
  {
    int index = m_ui.toleranceComboBox->currentIndex();
    QString text = m_ui.toleranceLineEdit->text();
    
    bool ok = false;
    double nominal = qAbs(text.toDouble(&ok));
    
    if (!nominal && !ok)
      return -1;
  
    if (index == 0)
      {
	// MXT_MASS_TOLERANCE_AMU
  
	return nominal;
      }
    else if (index == 1)
      {
	// MXT_MASS_TOLERANCE_PCT

	return(nominal *(value / 100));
      }
    else if (index == 2)
      {
	// MXT_MASS_TOLERANCE_PPM

	return(nominal *(value / 1000000));
      }
    else
      Q_ASSERT(0);
    
    return -1;
  }



  void 
  MzLabMatchBasedActionsDlg::fillMolecularMassList
  (const OligomerList &oligomerList,
   QList<double> *listM, MassType &massType)
  {
    Q_ASSERT(listM);

    double mass = 0;
    
    for (int iter = 0; iter < oligomerList.size(); ++iter)
      {
	Oligomer *oligomer = oligomerList.at(iter);
	
	FragmentOligomer *fragOligomer =
	  dynamic_cast<FragmentOligomer *>(oligomer);
	
	if(fragOligomer)
	  {
	    // Depending on the massType, select the proper molecular
	    // mass. Note that it is not possible that the mass be < 0.
	    mass = fragOligomer->molecularMass(massType);
	  }
	else
	  mass = oligomer->molecularMass(massType);
	
	if(mass < 0)
	  qFatal("Fatal error at %s@%d. Program aborted. Mass was:%.5f",
		  __FILE__, __LINE__, mass);
	
	listM->append(mass);
      }
  }
  

  
  void
  MzLabMatchBasedActionsDlg::performMatchWorkPushButton()
  {
    // We have to get the name of two lists. The first list's item
    // should be matched to the second list's items. The user might ask
    // that the second list's items be modified to match the first
    // list's ones.

    MzLabInputOligomerTableViewDlg *dlg1 = 0;
    MzLabInputOligomerTableViewDlg *dlg2 = 0;

    if (!mp_mzLabWnd->inputListsDlg(&dlg1, &dlg2))    
      {
	QMessageBox::warning(this,
			      tr("massXpert: mz Lab"),
			      tr("This feature performs matches between "
				  "two input lists.\n"
				  "Please, select two input lists first."),
			      QMessageBox::Ok);
	return;
      }


    // The general idea is to first create for each m/z--z list a
    // corresponding list of M values(that is to first deionize the
    // m/z into M). Next, the comparison will be performed on the M
    // lists. This will ensure that all peaks are matched even if
    // simulation peaks are for z=1 and z=2, for example, and the
    // measured m/z has peaks with z=3. 

    // The m/z--z lists and the M lists will retain a complete
    // correlation, that is the order of their components will not be
    // modified, and the their number neither. Which means that, for
    // example, the item in m/z list 1 [index 0] will correspond to
    // the item in M list [index 0].

    // The comparison of M list 1 with M list 2 will possibly yield
    // matches which will be reported in an output list of which the
    // items will retain links to the initial m/z lists 1 and 2. This
    // way, it will be possible to trace back the matched m/z items
    // for a given match.

    // The dlg1 holds the list that has the measured data(that is
    // factual m/z values that cannot be changed in any way). The dlg2
    // holds the theoretical m/z values that might undergo chemical
    // modifications in order to seek matches in the dlg1 list.

    // The two lists might not have the same item count.

    MassType massType1 = dlg1->massType();
    MassType massType2 = dlg2->massType();

    if (massType1 != massType2)
      {
	int ret = 
	  QMessageBox::question(this, tr("massXpert - mz Lab"),
				tr("The lists to match are not of the same "
				   "mass type.\n\nContinue?"),
				QMessageBox::Yes | QMessageBox::No);
       
	if(ret == QMessageBox::No)
	  return;
      }


    // For clearness...
    MassType massType = massType1;

    //    qDebug() << __FILE__ << __LINE__
    //             << "massType:" << massType;

    // Allocate first the oligomer shell that we'll use later.
    OligomerPair *oligomerPair = 0;

    // What kind of match are we willing?
    bool trueMatches = m_ui.trueMatchesCheckBox->isChecked();
    // qDebug() << __FILE__ << __LINE__ 
    //          << "trueMatches:" << trueMatches;
    
    bool falseMatches = m_ui.falseMatchesCheckBox->isChecked();
    // qDebug() << __FILE__ << __LINE__ 
    //          << "falseMatches:" << falseMatches;

    // Create the two lists that will hold the double values.

    // First list.

    const OligomerList &listMz1 = dlg1->oligomerList();
    QList<double> listM1;
    fillMolecularMassList(listMz1, &listM1, massType);
    Q_ASSERT(listMz1.size() == listM1.size());

    // Second list.

    const OligomerList &listMz2 = dlg2->oligomerList();
    QList<double> listM2;
    fillMolecularMassList(listMz2, &listM2, massType);
    Q_ASSERT(listMz2.size() == listM2.size());

    // Allocate the new list of OligomerPair instances into which
    // we'll append OligomerPairs instances corresponding to
    // successful matches between oligomers.

    QList <OligomerPair *> *oligomerPairList = 
      new QList <OligomerPair *>();

    // At this point we have the two lists with molecular masses. We
    // can start doing the matches.
        
    for (int iter = 0; iter < listM1.size(); ++iter)
      {
	double mass1 =  listM1.at(iter);

        double tolerance = calculateTolerance(mass1);
        
        double lowerBorder = mass1 - tolerance;
        double upperBorder = mass1 + tolerance;
        
        bool foundMatch = false;
        
        for(int jter = 0; jter < listM2.size(); ++jter)
	  {
	    double mass2 =  listM2.at(jter);
            
            if (mass2 >= lowerBorder &&
		mass2 <= upperBorder)
	      {
                // qDebug() << __FILE__ << __LINE__
                //          << "M1 at index:" << iter 
                //          << "matches"
                //          << "M2 at index:" << jter; 
                
		// At this point we can create a new OligomerPair.
                if(trueMatches)
                  {
                    oligomerPair =
                      new OligomerPair(listMz1.at(iter), listMz2.at(jter),
                                       massType,
                                       qAbs(mass2 - mass1), 
                                       true /* isMatching */,
                                       "NOT_SET");

                    oligomerPairList->append(oligomerPair);
                  }
                
                // Whatever asked by the user (trueMatches or not), we
                // must tell the following code that a match was
                // found.
                foundMatch = true;
              }
          }
        
        // Now that we have finished dealing with mass1, let's check
        // if a match was found. If not and the non-matches are
        // requested, store a locally allocated oligomerPair.

        if(!foundMatch && falseMatches)
          {
            // Allocate a duplicate oligomer identical to the oligomer
            // that had no match in the listMz2.
            Oligomer *oligomer = new Oligomer(*listMz1.at(iter));

            oligomerPair = 
              new OligomerPair(listMz1.at(iter), oligomer,
                               massType,
                               0, 
                               false /* isMatching */,
                               "NOT_SET");
            
            oligomerPairList->append(oligomerPair);
          }
      }
    // End of
    // for (int iter = 0; iter < listM1.size(); ++iter)
    
    
    // qDebug() << __FILE__ << __LINE__
    //          << "Appended oligomer pair:"
    //          << oligomerPair->oligomer1()->name()
    //          << oligomerPair->oligomer2()->name()
    //          << "checksum:" << oligomerPair->oligomer1()->
    //   polymer()->checksum();
    
    //    qDebug() << __FILE__ << __LINE__
    //             << "Found pairs:" << oligomerPairList->size();
    
    // Allocate the new dialog, we pass 
  
    QString dialogName = tr("%1 vs %2")
                .arg(dlg1->name())
      .arg(dlg2->name());
    
    // Ownership of oligomerPairList is going to be transferred to the
    // dialog being created (note that to the dialog and not the table
    // view model in the dialog's table view).

    MzLabOutputOligomerTableViewDlg *dlg =
      new MzLabOutputOligomerTableViewDlg(this, oligomerPairList, 
                                          massType1, dlg1, dlg2, 
                                          dlg1->name(), dlg2->name());
    dlg->show();
  }


 void
  MzLabMatchBasedActionsDlg::helpPushButton()
  {
    // Explain to the user what's the point of having matches
    // performed.

    QString msg(tr("To perform a match operation one must have\n"
                   "two lists of (m/z,z) pairs already available.\n\n"
                   "One list must be selected in the Catalogue 1\n"
                   "and the other list must be selected in the Catalogue 2.\n\n"
                   "There are two kinds of \"match work\" operations : \n"
                   "One that aims at finding matches between (m/z,z) pairs\n"
                   "in each of the two lists and one that aims at finding\n"
                   "(m/z,z) pairs that have no match in the other list.\n\n"
                   "How are match (or non-match) works performed ?\n"
                   "One match work is performed by searching in\n"
                   "the first list (Catalogue 1) all the (m/z,z) pairs \n"
                   "that are also in the second list (Catalogue 2).\n\n"
                   "One non-match work is performed by searching in the\n"
                   "first list (Catalogue 1) all the (m/z,z) pairs that are\n"
                   "not found in the second list (Catalogue 2).\n"));
    
    QMessageBox::warning(this,
                         tr("massXpert: mz Lab"),
                         msg,
                         QMessageBox::Ok);
  }


} // namespace massXpert
