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

#include <pappsomspp/utils.h>

#include <libXpertMass/Oligomer.hpp>

/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "../nongui/FragmentOligomer.hpp"
#include "FragmentOligomerTableView.hpp"
#include "FragmentOligomerTableViewSortProxyModel.hpp"
#include "FragmentationDlg.hpp"
#include "FragmentOligomerTableViewMimeData.hpp"


namespace MsXpS
{

namespace massxpert
{


FragmentOligomerTableView::FragmentOligomerTableView(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);
}


FragmentOligomerTableView::~FragmentOligomerTableView()
{
}


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


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


FragmentationDlg *
FragmentOligomerTableView::parentDlg()
{
  return mp_parentDlg;
}

void
FragmentOligomerTableView::setParentDlg(FragmentationDlg *dlg)
{
  Q_ASSERT(dlg);
  mp_parentDlg = dlg;
}


int
FragmentOligomerTableView::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.

  // For debugging.
  QString row_text;

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

      // For debugging
      QModelIndex local_index;

      local_index =
        oligomerIndex.siblingAtColumn(FRAGMENT_OLIGO_PATTERN_COLUMN);
      row_text += local_index.data().toString();
      row_text += " ";

      local_index = oligomerIndex.siblingAtColumn(FRAGMENT_OLIGO_NAME_COLUMN);
      row_text += local_index.data().toString();
      row_text += " ";

      local_index = oligomerIndex.siblingAtColumn(FRAGMENT_OLIGO_MONO_COLUMN);

      row_text += local_index.data().toString();
      row_text += " ";

      local_index = oligomerIndex.siblingAtColumn(FRAGMENT_OLIGO_CHARGE_COLUMN);
      row_text += local_index.data().toString();
      row_text += " ";

      // We absolutely need to allocate a FragmentOligomer because only that
      // class has the ability to provide the correct elemental composition.

      FragmentOligomerSPtr new_oligomer_sp = std::make_shared<FragmentOligomer>(
        *(std::dynamic_pointer_cast<FragmentOligomer>(
          mp_oligomerList->at(row))));

      row_text += new_oligomer_sp->elementalComposition();
      row_text += "\n";

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

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

      new_oligomer_sp->appendProp(prop);

      oligomerList->insert(localIndex, new_oligomer_sp);

      ++localIndex;
      ++count;
    }

  // For debugging.
  // qDebug().noquote() << row_text;
  //pappso::Utils::writeToFile(row_text,
                             //QString("/tmp/mass/rows-%1.txt")
                               //.arg(QDateTime::currentDateTime().toString()));

  return count;
}


QString *
FragmentOligomerTableView::selectedOligomersAsPlainText(
  QString delimiter,
  bool withSequence,
  bool forXpertMiner,
  libXpertMass::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__);

  // 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:
  // MassType::MASS_MONO or MASS_AVG. Also, we want the format to be :

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

  QString *text = new QString();

  while(!oligomerList.isEmpty())
    {
      libXpertMass::OligomerSPtr oligomer_sp = oligomerList.takeFirst();

      if(!forXpertMiner)
        *text +=
          QString("\n%1%2").arg(oligomer_sp->description()).arg(delimiter);

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

      if(forXpertMiner && massType == libXpertMass::MassType::MASS_MONO)
        {
        }
      else
        {
          *text += QString("%1%2")
                     .arg(oligomer_sp->avgString(libXpertMass::OLIGOMER_DEC_PLACES))
                     .arg(delimiter);
        }

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

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

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

      if(!forXpertMiner)
        *text += QString("%1%2").arg(oligomer_sp->isModified()).arg(delimiter);

      // We cannot export the sequence if data are for XpertMiner
      if(!forXpertMiner && withSequence)
        {
          QString *sequence = oligomer_sp->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.
      oligomer_sp.reset();
    }

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

  return text;
}


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

  QTableView::mousePressEvent(mouseEvent);
}


void
FragmentOligomerTableView::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
FragmentOligomerTableView::startDrag()
{
  FragmentOligomerTableViewMimeData *mimeData =
    new FragmentOligomerTableViewMimeData(
      this, mp_parentDlg->editorWnd(), mp_parentDlg);

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


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

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

  libXpertMass::OligomerSPtr oligomer_sp = mp_oligomerList->at(row);

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

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

      return;
    }

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

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

  Q_ASSERT(!text->isEmpty());

  mp_parentDlg->updateOligomerSequence(text);

  delete text;

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

  libXpertMass::CalcOptions calcOptions = oligomer_sp->calcOptions();

  mp_parentDlg->updateFragmentationDetails(calcOptions);

  QTableView::currentChanged(sourceIndex, previous);
}


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

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

  libXpertMass::OligomerSPtr oligomer_sp = mp_oligomerList->at(row);

  SequenceEditorWnd *editorWnd = mp_parentDlg->editorWnd();

  // The formula is not correct by now. The fragmentation formula
  // does not seem to be taken into account. However, it works fine
  // for oligomers computed from cleavage.
  //
  // QString formula = oligomer->elementalComposition();
  // qDebug() << __FILE__ << __LINE__
  //          << "Elemental composition: " << formula;

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

      return;
    }

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


///////// Contextual menu for copying to clipboard of mono/avg
///////// masses.
void
FragmentOligomerTableView::copyMono()
{
  return copyMassList(libXpertMass::MassType::MASS_MONO);
}


void
FragmentOligomerTableView::copyAvg()
{
  return copyMassList(libXpertMass::MassType::MASS_AVG);
}


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

      libXpertMass::OligomerSPtr oligomer_sp = mp_oligomerList->at(sourceRow);

      if(monoOrAvg == libXpertMass::MassType::MASS_MONO)
        massList += oligomer_sp->monoString(libXpertMass::OLIGOMER_DEC_PLACES);
      else if(monoOrAvg == libXpertMass::MassType::MASS_AVG)
        massList += oligomer_sp->avgString(libXpertMass::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

} // namespace MsXpS
