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

/////////////////////// Local includes
#include "globals.hpp"
#include "cleaveOligomerTableView.hpp"
#include "cleaveOligomerTableViewSortProxyModel.hpp"
#include "oligomer.hpp"
#include "cleavageDlg.hpp"
#include "application.hpp"
#include "cleaveOligomerTableViewMimeData.hpp"


namespace massXpert
{

  CleaveOligomerTableView::CleaveOligomerTableView(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->setSectionsClickable(true);
    headerView->setSectionsMovable(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);
  }


  CleaveOligomerTableView::~CleaveOligomerTableView()
  {

  }


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


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


  CleavageDlg *
  CleaveOligomerTableView::parentDlg()
  {
    return mp_parentDlg;
  }

  void
  CleaveOligomerTableView::setParentDlg(CleavageDlg *dlg)
  {
    Q_ASSERT(dlg);
    mp_parentDlg = dlg;
  }


  int
  CleaveOligomerTableView::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();

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

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

        CleaveOligomer *oligomer =
          static_cast<CleaveOligomer *>(mp_oligomerList->at(row));

        CleaveOligomer *newOligomer = new CleaveOligomer(*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 *
  CleaveOligomerTableView::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__);

    // If delimiter is empty, then set to "$".
    if (delimiter.isEmpty())
      delimiter = "$";
    
    // Allocate a string in which we describe all the selected items.

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

    QString *text = new QString();

    while(!oligomerList.isEmpty())
      {
        CleaveOligomer *oligomer =
          static_cast<CleaveOligomer *>(oligomerList.takeFirst());

        if(!forXpertMiner)
          *text +=  QString("\n%1%2")
            .arg(oligomer->cleavageAgentName())
            .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
  CleaveOligomerTableView::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
  CleaveOligomerTableView::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
  CleaveOligomerTableView::startDrag()
  {
    CleaveOligomerTableViewMimeData *mimeData =
      new CleaveOligomerTableViewMimeData(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
  CleaveOligomerTableView::currentChanged(const QModelIndex &current,
                                          const QModelIndex &previous)
  {
    if (!current.isValid())
      return;

    CleaveOligomerTableViewSortProxyModel *sortModel =
      static_cast<CleaveOligomerTableViewSortProxyModel *>(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 CleavageDlg
    // instance.

    CleaveOligomer *oligomer =
      static_cast<CleaveOligomer *>(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 - Cleavage"),
                             tr("%1@%2\n"
                                "The monomer indices do not correspond "
                                "to a valid polymer sequence range.\n"
                                "Avoid modifying the sequence while "
                                "working with cleavages.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                             QMessageBox::Ok);

        return;
      }

    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 cleavage
    // product list.

    Q_ASSERT(!text->isEmpty());

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

    // Get the formula of the oligomer and display it all along.

    QString formula = oligomer->elementalComposition();

    *text += QString("(formula: %1)")
      .arg(formula);

    mp_parentDlg->updateOligomerSequence(text);

    delete text;

    // Get the mass calculation engine's options out of the oligomer,
    // so that we can display them correctly.

    CalcOptions calcOptions = oligomer->calcOptions();

    mp_parentDlg->updateCleavageDetails(calcOptions);

    QTableView::currentChanged(current, previous);
  }


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

    CleaveOligomerTableViewSortProxyModel *sortModel =
      static_cast<CleaveOligomerTableViewSortProxyModel *>(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.

    CleaveOligomer *oligomer =
      static_cast<CleaveOligomer *>(mp_oligomerList->at(row));

    SequenceEditorWnd *editorWnd = mp_parentDlg->editorWnd();

    CoordinateList *coordinateList =
      static_cast<CoordinateList *>(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();

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

            return;
          }

        editorWnd->mpa_editorGraphicsView->setSelection(*coordinates,
                                                        true, false);
      }

    editorWnd->updateSelectedSequenceMasses();
  }


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


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


  void
  CleaveOligomerTableView::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;

        CleaveOligomer *oligomer =
          static_cast<CleaveOligomer *>(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
