/* 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 <QDebug>
#include <QHeaderView>
#include <QMouseEvent>
#include <QMessageBox>


/////////////////////// Local includes
#include "mzLabOutputOligomerTableView.hpp"
#include "mzLabOutputOligomerTableViewModel.hpp"
#include "mzLabOutputOligomerTableViewSortProxyModel.hpp"

#include "application.hpp"



namespace massXpert
{

  MzLabOutputOligomerTableView::MzLabOutputOligomerTableView(QWidget *parent)
    : QTableView(parent)
  {

    setAlternatingRowColors(true);
  
    setSortingEnabled(true);

    QHeaderView *headerView = horizontalHeader();
    headerView->setClickable(true);
    headerView->setMovable(true);

    connect(this,
	     SIGNAL(activated(const QModelIndex &)),
	     this,
	     SLOT(itemActivated(const QModelIndex &)));  
  }


  MzLabOutputOligomerTableView::~MzLabOutputOligomerTableView()
  {
  }


  void 
  MzLabOutputOligomerTableView::setOligomerPairList(QList <OligomerPair *> *oligomerPairList)
  {
    mp_oligomerPairList = oligomerPairList;
  }
  
  
  const QList <OligomerPair *> *
  MzLabOutputOligomerTableView::oligomerPairList()
  {
    return mp_oligomerPairList;
  }
  

    void 
  MzLabOutputOligomerTableView::setParentDlg(MzLabOutputOligomerTableViewDlg *dlg)
  {
    Q_ASSERT(dlg);
    mp_parentDlg = dlg;
  }

  
  MzLabOutputOligomerTableViewDlg *
  MzLabOutputOligomerTableView::parentDlg()
  {
    return mp_parentDlg;
  }
  

  void 
  MzLabOutputOligomerTableView::setMzLabWnd(MzLabWnd *wnd)
  {
    Q_ASSERT(wnd);
    
    mp_mzLabWnd = wnd;
  }


  MzLabWnd *
  MzLabOutputOligomerTableView::mzLabWnd()
  {
    return mp_mzLabWnd;
  }
  

  void 
  MzLabOutputOligomerTableView::setSourceModel
  (MzLabOutputOligomerTableViewModel *model)
  {
    mp_sourceModel = model;
  }
  

  MzLabOutputOligomerTableViewModel *
  MzLabOutputOligomerTableView::sourceModel()
  {
    return mp_sourceModel;
  }
  

  
  QString * 
  MzLabOutputOligomerTableView::selectedDataAsPlainText()
  {
    // Provide a most simple list of (m/z,z) pairs like 1021.21 2 for
    // all the selected items in the table view.

    QString *resultString = new QString();
    
    // We first have to get the selection model for the proxy model.

    QItemSelectionModel *selModel = selectionModel();

    MzLabOutputOligomerTableViewModel* modelSource = sourceModel();
    
    // Now get the selection ranges.
    
    QItemSelection proxyItemSelection = selModel->selection();

    QSortFilterProxyModel *sortModel = 
      static_cast<QSortFilterProxyModel *>(model());
    
    QItemSelection sourceItemSelection = 
      sortModel->mapSelectionToSource(proxyItemSelection);

    QModelIndexList modelIndexList = sourceItemSelection.indexes();

    int modelIndexListSize = modelIndexList.size();
    
    // Attention, if we select one single row, our modelIndexList will
    // be of size 5, because in one single row there are five cells:
    // each cell for each column, and there are 5 columns. Thus, when
    // we iterate in the modelIndexList, we'll have to take care of
    // this and make sure we are not putting each selected row's data
    // more than one time. For this, we make sure we are not handling
    // the same row twice or more, by storing the processed rows in a
    // list of integers and by checking for existence of that row each
    // time a new index is processed.
    
    QList<int> processedRowList;
    
    resultString->append("m/z 1 | z 1 | m/z 2 | z 2 | Error | Match\n\n");
    
    for (int iter = 0; iter < modelIndexListSize; ++iter)
      {
	QModelIndex index = modelIndexList.at(iter);
	
	Q_ASSERT(index.isValid());

        // Get to know what's the row of the index.

        int row = index.row();
        
        if(processedRowList.contains(row))
          continue;
        else
          processedRowList.append(row);
        
        // Get the m/z and the z values through the use of the model's
        // data function, which expects an index, which we create
        // using the model's index() function below.

        // The m/z 1.
        QModelIndex itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_1_MASS_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString massString1 =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        // The z 1.
        itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_1_CHARGE_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString chargeString1 =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        
        // The m/z 2.
        itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_2_MASS_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString massString2 =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        // The z 2.
        itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_2_CHARGE_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString chargeString2 =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        // The error.
        itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_ERROR_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString errorString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        // The match.
        itemModelIndex = 
          modelSource->index(row, MZ_LAB_OUTPUT_OLIGO_MATCH_COLUMN);
        
        Q_ASSERT(itemModelIndex.isValid());
        
        QString matchString =
          modelSource->data(itemModelIndex, Qt::DisplayRole).toString();
        
        
        resultString->append(QString("%1 %2 %3 %4 %5 %6\n")
                             .arg(massString1)
                             .arg(chargeString1)
                             .arg(massString2)
                             .arg(chargeString2)
                             .arg(errorString)
                             .arg(matchString));
      }
    
    return resultString;
  }
  
  void 
  MzLabOutputOligomerTableView::itemActivated(const QModelIndex &index)
  {
    // It might be possible that both the oligomer1 and oligomer2 come
    // from data computed theoretically starting from a polymer
    // sequence opened in an editor window. We thus ask each of these
    // two oligomers for existence of a "SEQUENCE_EDITOR_WND" Prop
    // instance.

    if (!index.isValid())
      return;

    MzLabOutputOligomerTableViewSortProxyModel *sortModel = 
      static_cast<MzLabOutputOligomerTableViewSortProxyModel *>(model());
    
    QModelIndex sourceIndex = sortModel->mapToSource(index);

    int row = sourceIndex.row();

    OligomerPair *oligomerPair = mp_oligomerPairList->at(row);

    // Which are the oligomers of the pair?

    const Oligomer *oligomer1 = oligomerPair->oligomer1();
    //     qDebug() << __FILE__ << __LINE__
    // 	     << oligomer1->name();

    const Oligomer *oligomer2 = oligomerPair->oligomer2();
    
    //     qDebug() << __FILE__ << __LINE__
    // 	     << oligomer2->name();
    
    // Get the Polymer instance pointers for both oligomers.

    const Polymer *polymer1 = oligomer1->polymer();
    const Polymer *polymer2 = oligomer2->polymer();
    
    // If none of the oligomers have a pointer to a polymer, that
    // might mean that the data have been generated using a drag and
    // drop from textual data.

    if (!polymer1 && !polymer2)
      {
	return;
      }
    
    // Now, try in turn for oligomer1 and oligomer2 to check if we
    // still have a connection with their respective sequence editor
    // window. If so, then select the corresponding sequence in it.

    // oligomer1
    // =========

    if(polymer1)
      provideEditorWindowFeedback(oligomer1);
    
    // oligomer2
    // =========

    // We only handle oligomer2 if the polymer from which it comes is
    // different from oligomer1.

    if(polymer2 && (polymer1 != polymer2))
      provideEditorWindowFeedback(oligomer2);
  }
  
  void
  MzLabOutputOligomerTableView::provideEditorWindowFeedback
  (const Oligomer *oligomer)
  {
    if(!oligomer)
      qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
    
    const Polymer *polymer = oligomer->polymer();

    // Get the sequence editor window pointer out of the oligomer,
    // if it has one in the form of a NoDeletePointerProp.
        
    Prop *prop = 
      const_cast<Oligomer *>(oligomer)->prop("SEQUENCE_EDITOR_WND");
    
    if (!prop)
      {
        // 	qDebug() << __FILE__ << __LINE__;
        
        return;
      }
    
    SequenceEditorWnd *editorWnd = 
      static_cast<SequenceEditorWnd *>(prop->data());
    
    if (!editorWnd)
      return;
    
    CoordinateList *coordinateList = 
      static_cast<CoordinateList *>(const_cast<Oligomer *>(oligomer));
    
    // Remove the previous selection, so that we can start fresh.
    editorWnd->mpa_editorGraphicsView->resetSelection();
  
    for (int iter = 0; iter < coordinateList->size(); ++iter)
      {
	Coordinates *coordinates = coordinateList->at(iter);
	
	int start = coordinates->start();
	int end = coordinates->end();

        // qDebug() << __FILE__ << __LINE__
        //          << "oligomer:" << oligomer
        //          << "polymer:" << polymer
        //          << "start:" << start << "end:" << end;
        
	if(start >= polymer->size() ||
           end >= polymer->size())
	  {
	    QMessageBox::warning(this,
                                 tr("massXpert - mz Lab"),
                                 tr("%1@%2\n"
                                    "The monomer indices do not correspond "
                                    "to a valid polymer sequence range.\n"
                                    "Avoid modifying the sequence while "
                                    "working with oligomers.")
                                 .arg(__FILE__)
                                 .arg(__LINE__),
                                 QMessageBox::Ok);
	    
	    return;
	  }
	
	editorWnd->mpa_editorGraphicsView->setSelection(*coordinates, 
                                                        true, false);
      }
    
    editorWnd->updateSelectedSequenceMasses();  
  }

  int
  MzLabOutputOligomerTableView::duplicateSelectedOligomers
  (OligomerList *oligomerList)
  {
    if(!oligomerList)
      qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

    int count = 0;
    
    // Determine what's the selected material, that is, we want to
    // know which column is selected.

    // For each selected oligomer, duplicate it and append to the list
    // passed as argument.

    // We first have to get the selection model for the proxy model.

    QItemSelectionModel *selModel = selectionModel();

    // Now get the selection ranges.
    
    QItemSelection proxyItemSelection = selModel->selection();

    QSortFilterProxyModel *sortModel = 
      static_cast<QSortFilterProxyModel *>(model());
    
    QItemSelection sourceItemSelection = 
      sortModel->mapSelectionToSource(proxyItemSelection);

    QModelIndexList modelIndexList = sourceItemSelection.indexes();

    int modelIndexListSize = modelIndexList.size();
    
    // If nothing is selected, then return 0.
    if(!modelIndexListSize)
      return count;
    
    // Attention, if we select one single row, our modelIndexList will
    // be of size 6, because in one single row there are six cells:
    // each cell for each column. Thus, when we iterate in the
    // modelIndexList, we'll have to take care of this and make sure
    // we are not putting each selected row's oligomer that many
    // times. For this, we make sure we are not handling the same row
    // twice or more, by storing the processed rows in a list of
    // integers and by checking for existence of that row each time a
    // new index is processed.
    
    QList<int> processedRowList;
    
    
    // In *this* dialog, there are two sets of columns : m/z 1, z 1,
    // m/z 2, z 2. Now, we have to know what's the column currently
    // selected so that we know what oligomers we want to deal with
    // from the oligomerPair set : oligomer1 (m/z 1, z 1) or oligomer2
    // (m/z 2, z 2).
    
    // Get the first index, just to know which is the column.

    QModelIndex oligomerIndex = modelIndexList.at(0);
    Q_ASSERT(oligomerIndex.isValid());
    
    int column = oligomerIndex.column();
    int oligomerSet = -1;
    
    if(column == MZ_LAB_OUTPUT_OLIGO_1_MASS_COLUMN ||
       column == MZ_LAB_OUTPUT_OLIGO_1_CHARGE_COLUMN)
      oligomerSet = 1;
    else if (column == MZ_LAB_OUTPUT_OLIGO_2_MASS_COLUMN ||
             column == MZ_LAB_OUTPUT_OLIGO_2_CHARGE_COLUMN)
      oligomerSet = 2;
    else
      return count;
    
    // We now know if we have to deal with oligomers 1 or oligomers 2
    // in the OligomerPair list.

    for (int iter = 0; iter < modelIndexListSize; ++iter)
      {
	oligomerIndex = modelIndexList.at(iter);
	
	Q_ASSERT(oligomerIndex.isValid());
        
        // Get to know what's the row of the index, so that we can get
        // to the oligomer.

        int row = oligomerIndex.row();

        if(processedRowList.contains(row))
          continue;
        else
          processedRowList.append(row);

        const Oligomer *iterOligomer;
        
        if(oligomerSet == 1)
          iterOligomer = mp_oligomerPairList->at(iter)->oligomer1();
        else
          iterOligomer = mp_oligomerPairList->at(iter)->oligomer2();
        
        // Now perform the duplication.
        Oligomer *newOligomer = new Oligomer(*iterOligomer);
        
	oligomerList->append(newOligomer);
	
	++count;
      }
    
    return count;
  }
                                                  
  

} // namespace massXpert
