/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Qt includes
#include <QDebug>
#include <QMouseEvent>
#include <QMessageBox>
#include <QApplication>
#include <QDrag>


/////////////////////// Local includes
#include <massxpert/nongui/globals.hpp>
#include <massxpert/gui/MassSearchOligomerTableView.hpp>
#include <massxpert/gui/MassSearchOligomerTableViewSortProxyModel.hpp>

#include <massxpert/nongui/Oligomer.hpp>

#include <massxpert/gui/MassSearchDlg.hpp>

#include <massxpert/gui/MassSearchOligomerTableViewMimeData.hpp>


namespace msXpSmassXpert
{

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


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,
  msXpS::MassType massType) const
{
  // 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:
  // msXpS::MassType::MASS_MONO or 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;
      mzSearched.setNum(mzSearchedDouble, 'f', msXpS::OLIGOMER_DEC_PLACES);

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

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

      QString mzError;
      mzError.setNum(mzErrorDouble, 'f', msXpS::OLIGOMER_DEC_PLACES);

      if(!forXpertMiner)
        *text += QString("\nSearched: %1%2").arg(mzSearched).arg(delimiter);

      if(forXpertMiner && massType == msXpS::MassType::MASS_AVG)
        {
        }
      else
        {
          *text += QString("%1%2")
                     .arg(oligomer->monoString(msXpS::OLIGOMER_DEC_PLACES))
                     .arg(delimiter);
        }

      if(forXpertMiner && massType == msXpS::MassType::MASS_MONO)
        {
        }
      else
        {
          *text += QString("%1%2")
                     .arg(oligomer->avgString(msXpS::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(msXpS::MassType::MASS_MONO);
}


void
MassSearchOligomerTableView::copyAvg()
{
  return copyMassList(msXpS::MassType::MASS_AVG);
}


void
MassSearchOligomerTableView::copyMassList(int monoOrAvg)
{
  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 == msXpS::MassType::MASS_MONO)
        massList += oligomer->monoString(msXpS::OLIGOMER_DEC_PLACES);
      else if(monoOrAvg == msXpS::MassType::MASS_AVG)
        massList += oligomer->avgString(msXpS::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 msXpSmassXpert
