/* 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 <QMouseEvent>


/////////////////////// Local includes
#include "globals.hpp"
#include "massSearchOligomerTableView.hpp"
#include "massSearchOligomerTableViewSortProxyModel.hpp"

#include "oligomer.hpp"

#include "massSearchDlg.hpp"

#include "application.hpp"

#include "massSearchOligomerTableViewMimeData.hpp"


namespace massXpert
{

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

    setAlternatingRowColors(true);

    setSortingEnabled(true);
    setDragEnabled(true);

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

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

    ////// Create the actions for the contextual menu.

    // Copy Mono
    copyMonoAct = new QAction(tr("Copy Mono To Clipboard"), this);
    copyMonoAct->setStatusTip(tr("Copies the monoisotopic mass list "
                                 "to the clipboard"));
    connect(copyMonoAct, SIGNAL(triggered()), this, SLOT(copyMono()));

    // Copy Avg
    copyAvgAct = new QAction(tr("Copy Avg To Clipboard"), this);
    copyMonoAct->setStatusTip(tr("Copies the average mass list "
                                 "to the clipboard"));
    connect(copyAvgAct, SIGNAL(triggered()), this, SLOT(copyAvg()));

    // And now create the contextual menu and add the actions to it.
    contextMenu = new QMenu(tr("Copy Mass List"), this);
    contextMenu->addAction(copyMonoAct);
    contextMenu->addAction(copyAvgAct);
  }


  MassSearchOligomerTableView::~MassSearchOligomerTableView()
  {

  }


  void
  MassSearchOligomerTableView::setOligomerList(OligomerList *oligomerList)
  {
    mp_oligomerList = oligomerList;
  }


  const OligomerList *
  MassSearchOligomerTableView::oligomerList()
  {
    return mp_oligomerList;
  }


  void
  MassSearchOligomerTableView::setParentDlg(MassSearchDlg *dlg)
  {
    Q_ASSERT(dlg);
    mp_parentDlg = dlg;
  }


  MassSearchDlg *
  MassSearchOligomerTableView::parentDlg()
  {
    return mp_parentDlg;
  }

  int
  MassSearchOligomerTableView::selectedOligomers(OligomerList *oligomerList,
                                                 int index) const
  {
    if(!oligomerList)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    int count = 0;

    int localIndex = 0;

    // How many oligomers are there in the list passed as argument?
    int oligomerCount = oligomerList->size();

    if(index > oligomerCount)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    // If index is -1 , then we are asked to append the oligomers to
    // the list.
    if(index == -1)
      localIndex = oligomerList->size();

    // 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();

    // Attention, if we select one single row, our modelIndexList will
    // be of size 7, because in one single row there are seven cells:
    // each cell for each column, and there are 7 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
    // oligomer sevent 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;

    for (int iter = 0; iter < modelIndexListSize; ++iter)
      {
        QModelIndex 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);

        Oligomer *oligomer = mp_oligomerList->at(row);

        Oligomer *newOligomer = new Oligomer(*oligomer);

        // Create a NoDeletePointerProp, which might be used later by
        // the user of the list of oligomers to highlight regions in
        // the sequence editor.

        NoDeletePointerProp *prop =
          new NoDeletePointerProp("SEQUENCE_EDITOR_WND",
                                  static_cast<void *>
                                  (mp_parentDlg->editorWnd()));

        newOligomer->appendProp(prop);

        oligomerList->insert(localIndex, newOligomer);

        ++localIndex;
        ++count;
      }

    return count;
  }


  QString *
  MassSearchOligomerTableView::selectedOligomersAsPlainText(QString delimiter,
                                                            bool withSequence,
                                                            bool forXpertMiner,
                                                            MassType massType) const
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    // Let's get all the currently selected oligomers in one list.

    OligomerList oligomerList;

    // Append the selected oligomers to the empty list.
    int appendedOligomerCount = selectedOligomers(&oligomerList, -1);

    // Sanity check
    if(appendedOligomerCount != oligomerList.size())
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    // Allocate a string in which we describe all the selected items.

    QString *text = new QString();
    

    // For export to XpertMiner, we only want the masses asked for:
    // MXT_MASS_MONO or MXT_MASS_AVG. Also, we want the format to be :

    // mass <delim> charge <delim> name <delim> coords

    if(!forXpertMiner)
      {
        // The header of the column set output only if the data are
        // not for XpertMiner.
        *text += QObject::tr("\nSearched mass \t : Name \t :"
                             "Coordinates \t : Error \t :"
                             "Mono \t : Avg \t : Modif\n\n");
      }
    
    while(!oligomerList.isEmpty())
      {
        Oligomer *oligomer = oligomerList.takeFirst();

        // Get the searched mz out of the Prop object.

        Prop *prop = oligomer->prop("SEARCHED_MZ");
        Q_ASSERT(prop);

        double mzSearchedDouble =
          *static_cast<const double *>(prop->data());

        QString mzSearched =
          locale.toString(mzSearchedDouble, 'f', MXP_OLIGOMER_DEC_PLACES);

        prop = 0;
        prop = oligomer->prop("ERROR_MZ");
        Q_ASSERT(prop);

        double mzErrorDouble =
          *static_cast<const double *>(prop->data());

        QString mzError =
          locale.toString(mzErrorDouble, 'f', MXP_OLIGOMER_DEC_PLACES);

        if(!forXpertMiner)
          *text += QString("\nSearched: %1%2")
            .arg(mzSearched)
            .arg(delimiter);
        
        if(forXpertMiner && massType == MXT_MASS_AVG)
          {
          }
        else
          {
            *text +=  QString("%1%2")
              .arg(oligomer->mono(locale, MXP_OLIGOMER_DEC_PLACES))
              .arg(delimiter);
          }

        if(forXpertMiner && massType == MXT_MASS_MONO)
          {
          }
        else
          {
            *text +=  QString("%1%2")
              .arg(oligomer->avg(locale, MXP_OLIGOMER_DEC_PLACES))
              .arg(delimiter);
          }
        
        *text +=  QString("%1%2")
          .arg(oligomer->charge())
          .arg(delimiter);

        *text +=  QString("%1%2")
          .arg(oligomer->name())
          .arg(delimiter);

        *text +=  QString("%1%2")
          .arg(static_cast<CoordinateList *>(oligomer)->positionsAsText())
          .arg(delimiter);

        if(!forXpertMiner)
          *text +=  QString("%1%2")
            .arg(oligomer->isModified())
            .arg(delimiter);
        
        // We cannot export the sequence if data are for XpertMiner
        if(!forXpertMiner && withSequence)
          {
            QString *sequence = oligomer->monomerText();

            *text += QString("\n%1")
              .arg(*sequence);

            delete sequence;
          }

        // Terminate the stanza
        *text +=  QString("\n");
        
        // We can now delete the allocated oligomer, since we do not
        // need it anymore.
        delete oligomer;
      }

    // Terminate the string with a new line.
    *text += QString("\n");

    return text;
  }


  void
  MassSearchOligomerTableView::mousePressEvent(QMouseEvent *mouseEvent)
  {
    if (mouseEvent->buttons() & Qt::LeftButton)
      {
        m_dragStartPos = mouseEvent->pos();
      }
    else if (mouseEvent->buttons() & Qt::RightButton)
      {
        contextMenu->popup(mouseEvent->globalPos());
        return;
      }

    QTableView::mousePressEvent(mouseEvent);
  }


  void
  MassSearchOligomerTableView::mouseMoveEvent(QMouseEvent *mouseEvent)
  {
    if (mouseEvent->buttons() & Qt::LeftButton)
      {
        int distance =
          (mouseEvent->pos() - m_dragStartPos).manhattanLength();

        if(distance >= QApplication::startDragDistance())
          {
            startDrag();
            return;
          }
      }

    QTableView::mousePressEvent(mouseEvent);
  }


  void
  MassSearchOligomerTableView::startDrag()
  {
    MassSearchOligomerTableViewMimeData *mimeData =
      new MassSearchOligomerTableViewMimeData(this,
                                              mp_parentDlg->editorWnd(),
                                              mp_parentDlg);

    QDrag *drag = new QDrag(this);
    drag->setMimeData(mimeData);
    //    drag->setPixmap(QPixmap(":/images/greenled.png"));
    drag->start(Qt::CopyAction);
  }


  void
  MassSearchOligomerTableView::currentChanged(const QModelIndex &current,
                                              const QModelIndex &previous)
  {
    if (!current.isValid())
      return;

    MassSearchOligomerTableViewSortProxyModel *sortModel =
      static_cast<MassSearchOligomerTableViewSortProxyModel *>(model());

    QModelIndex sourceIndex = sortModel->mapToSource(current);

    int row = sourceIndex.row();

    // Get to the list of oligomers that is referenced in this
    // tableView (that list actually belongs to the MassSearchDlg
    // instance.

    Oligomer *oligomer = mp_oligomerList->at(row);

    // If the oligomers obtained with the cleavage are old and the
    // sequence has been changed since the cleavage, then the
    // oligomers might point to a sequence element that is no more. We
    // want to avoid such kind of errors.

    if (oligomer->startIndex() >= oligomer->polymer()->size() ||
        oligomer->endIndex() >= oligomer->polymer()->size())
      {
        QMessageBox::warning(this,
                             tr("massXpert - Mass Search"),
                             tr("%1@%2\n"
                                "The monomer indices do not correspond "
                                "to a valid polymer sequence range.\n"
                                "Avoid modifying the sequence while "
                                "working with mass searches.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                             QMessageBox::Ok);

        return;
      }

    // Old version, check if it needs to be so, or if the version
    // below is fine.

    // QString *text =
    //   oligomer->polymer()->monomerText(oligomer->startIndex(),
    //                                    oligomer->endIndex(), true);

    QString *text = oligomer->monomerText();

    // We are getting text for an oligomer; it cannot be empty,
    // because that would mean the oligomer has no monomers. In that
    // case it is not conceivable that the oligomer be in the mass
    // search product list.

    Q_ASSERT(!text->isEmpty());

    mp_parentDlg->updateOligomerSequence(text);

    delete text;

    QTableView::currentChanged(sourceIndex, previous);
  }


  void
  MassSearchOligomerTableView::itemActivated(const QModelIndex &index)
  {
    if (!index.isValid())
      return;

    MassSearchOligomerTableViewSortProxyModel *sortModel =
      static_cast<MassSearchOligomerTableViewSortProxyModel *>(model());

    QModelIndex sourceIndex = sortModel->mapToSource(index);

    int row = sourceIndex.row();

    // Get to the list of oligomers that is referenced in this
    // tableView (that list actually belongs to the CleavageDlg
    // instance.

    Oligomer *oligomer = mp_oligomerList->at(row);

    if (oligomer->startIndex() >= oligomer->polymer()->size() ||
        oligomer->endIndex() >= oligomer->polymer()->size())
      {
        QMessageBox::warning(this,
                             tr("massXpert - Mass Search"),
                             tr("%1@%2\n"
                                "The monomer indices do not correspond "
                                "to a valid polymer sequence range.\n"
                                "Avoid modifying the sequence while "
                                "working with mass searches.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                             QMessageBox::Ok);

        return;
      }

    SequenceEditorWnd *editorWnd = mp_parentDlg->editorWnd();

    // Remove the previous selection, so that we can start fresh.
    editorWnd->mpa_editorGraphicsView->resetSelection();

    editorWnd->mpa_editorGraphicsView->setSelection(oligomer->startIndex(),
                                                    oligomer->endIndex(),
                                                    false, false);
  }


  ///////// Contextual menu for copying to clipboard of mono/avg
  ///////// masses.
  void
  MassSearchOligomerTableView::copyMono()
  {
    return copyMassList(MXT_MASS_MONO);
  }


  void
  MassSearchOligomerTableView::copyAvg()
  {
    return copyMassList(MXT_MASS_AVG);
  }


  void
  MassSearchOligomerTableView::copyMassList(int monoOrAvg)
  {
    Application *application = static_cast<Application *>(qApp);
    QLocale locale = application->locale();

    QString massList;


    // We want to prepare a textual list of masses (either MONO or
    // AVG) of all the oligomers in the tableview, exactly as they are
    // currently displayed (that is, according to the proxy's model).

    QSortFilterProxyModel *sortModel =
      static_cast<QSortFilterProxyModel *>(model());

    // Get number of rows under the model.
    int rowCount = sortModel->rowCount();

    for(int iter = 0; iter < rowCount; ++iter)
      {
        // qDebug() << __FILE__ << __LINE__
        //          << "proxyIter:" << iter;

        QModelIndex proxyIndex = sortModel->index(iter, 0);
        QModelIndex sourceIndex = sortModel->mapToSource(proxyIndex);

        int sourceRow = sourceIndex.row();

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

        Oligomer *oligomer = mp_oligomerList->at(sourceRow);

        if(monoOrAvg == MXT_MASS_MONO)
          massList += oligomer->mono(locale, MXP_OLIGOMER_DEC_PLACES);
        else if (monoOrAvg == MXT_MASS_AVG)
          massList += oligomer->avg(locale, MXP_OLIGOMER_DEC_PLACES);
        else
          qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

        // End the mass item with a new line.
        massList += "\n";
      }

    if (massList.isEmpty())
      return;

    QClipboard *clipboard = QApplication::clipboard();

    clipboard->setText(massList, QClipboard::Clipboard);
  }

} // namespace massXpert
