/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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>
#include <QMenu>


/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "MassSearchOligomerTableView.hpp"
#include "MassSearchOligomerTableViewSortProxyModel.hpp"

#include <MsXpS/libXpertMassCore/Oligomer.hpp>

#include "MassSearchDlg.hpp"

#include "MassSearchOligomerTableViewMimeData.hpp"


namespace MsXpS
{

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->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::setOligomerCollection(
  libXpertMassCore::OligomerCollection *oligomerList)
{
  mp_oligomers = oligomerList;
}


const libXpertMassCore::OligomerCollection *
MassSearchOligomerTableView::oligomerList()
{
  return mp_oligomers;
}


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


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

// The newly added oligomers are added at index in the OligomerCollection
// passed as parameter.
int
MassSearchOligomerTableView::selectedOligomers(
  libXpertMassCore::OligomerCollection *oligomers_p, std::size_t index) const
{
  if(oligomers_p == nullptr)
    qFatal() << "Programming error.";

  int count = 0;

  std::size_t local_index = 0;

  // How many oligomers are there in the list passed as argument?
  std::size_t oligomer_count = oligomers_p->size();

  if(index > oligomer_count)
    qFatal() << "Programming error.";

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

      libXpertMassCore::OligomerSPtr oligomer_sp =
        mp_oligomers->getOligomersCstRef().at(row);

      libXpertMassCore::OligomerSPtr new_oligomer_sp =
        std::make_shared<libXpertMassCore::Oligomer>(*oligomer_sp);

#ifdef QT_DEBUG
      // Sanity check:
      libXpertMassCore::Prop *the_prop = oligomer_sp->prop("SEARCHED_MZ");
      Q_ASSERT(the_prop);
      double mzSearchedDouble = *static_cast<const double *>(the_prop->data());
      qDebug() << "The searched m/z:" << mzSearchedDouble;

      // Sanity check:
      libXpertMassCore::Prop *the_other_prop = new_oligomer_sp->prop("SEARCHED_MZ");
      Q_ASSERT(the_other_prop);
      mzSearchedDouble = *static_cast<const double *>(the_other_prop->data());
      qDebug() << "The other searched m/z:" << mzSearchedDouble;
#endif

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

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

      new_oligomer_sp->appendProp(prop);

      oligomers_p->getOligomersRef().insert(
        oligomers_p->getOligomersRef().begin() + local_index, new_oligomer_sp);

      ++local_index;
      ++count;
    }

  return count;
}


QString *
MassSearchOligomerTableView::describeSelectedOligomersAsPlainText(
  QString delimiter,
  bool withSequence,
  bool forXpertMiner,
  libXpertMassCore::Enums::MassType massType) const
{
  // Let's get all the currently selected oligomers in one list.

  libXpertMassCore::OligomerCollection selected_oligomers;

  // The selected oligomers are *allocated* into the selected_oligomers
  // OligomerCollection. These oligomers are inserted at index 0 below (which
  // is like appending, since the collection is empty).

  // As we use them below,  we'll free them by simply erasing them from the
  // container.
  std::size_t selected_oligomer_count =
    selectedOligomers(&selected_oligomers, /* insertion index*/ 0);

  // Sanity check
  if(selected_oligomer_count != selected_oligomers.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:
  // libXpertMassCore::Enums::MassType::MONO or MASS_AVG. Also, we want the format
  // to be :

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

  // When exporting for clipboard or file:

  // clang-format off
  // Searched:  searched mass $ found mono $ found avg $ charge $ name $ index_range $ isModified $
  // Searched: 827.34700 $ 827.34654 $ 828.05672 $ 1 $ 827.347-z1#1 $ [1-8] $ 0 $
  // MAMISGMS
  // clang-format on

  if(!forXpertMiner)
    {
      // The header of the column set output only if the data are
      // not for XpertMiner.
      *text += QString(
                 "\nSearched mass%1Found Mono%1Found Avg%1Charge%1Name%1Index "
                 "Range%1isModified?\n\n")
                 .arg(delimiter);
    }

  std::vector<libXpertMassCore::OligomerSPtr>::iterator the_iterator =
    selected_oligomers.getOligomersRef().begin();
  std::vector<libXpertMassCore::OligomerSPtr>::iterator the_end_iterator =
    selected_oligomers.getOligomersRef().end();

  while(the_iterator != the_end_iterator)
    {
      // Get the searched mz out of the libXpertMassCore::Prop object.

      libXpertMassCore::Prop *prop = (*the_iterator)->prop("SEARCHED_MZ");
      Q_ASSERT(prop);

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

      QString mzSearchedString;
      mzSearchedString.setNum(
        mzSearchedDouble, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

      prop = nullptr;
      prop = (*the_iterator)->prop("ERROR_MZ");
      Q_ASSERT(prop);

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

      QString mzErrorString;
      mzErrorString.setNum(
        mzErrorDouble, 'f', libXpertMassCore::OLIGOMER_DEC_PLACES);

      // Searched: 827.34700 $ 827.34654 $ 828.05672 $ 1 $ 827.347-z1#1 $ [1-8]
      // $ 0 $ MAMISGMS

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

      if(forXpertMiner && massType == libXpertMassCore::Enums::MassType::AVG)
        {
        }
      else
        {
          *text +=
            QString("%1%2")
              .arg(
                (*the_iterator)->getMass(libXpertMassCore::Enums::MassType::MONO),
                0,
                'f',
                libXpertMassCore::OLIGOMER_DEC_PLACES)
              .arg(delimiter);
        }

      if(forXpertMiner && massType == libXpertMassCore::Enums::MassType::MONO)
        {
        }
      else
        {
          *text +=
            QString("%1%2")
              .arg((*the_iterator)->getMass(libXpertMassCore::Enums::MassType::AVG),
                   0,
                   'f',
                   libXpertMassCore::OLIGOMER_DEC_PLACES)
              .arg(delimiter);
        }

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

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

      *text +=
        QString("%1%2")
          .arg(
            (*the_iterator)->getIndexRangeCollectionCstRef().positionsAsText())
          .arg(delimiter);

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

      // We cannot export the sequence if data are for XpertMiner
      if(!forXpertMiner && withSequence)
        {
          QString sequence =
            (*the_iterator)
              ->getPolymerCstSPtr()
              ->getSequenceCstRef()
              .getSequence((*the_iterator)->getIndexRangeCollectionCstRef());

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

      // Terminate the stanza
      *text += QString("\n");

      ++the_iterator;
    }

  // We can now delete all the allocated oligomers, since we do not
  // need them anymore.
  selected_oligomers.getOligomersRef().clear();

  // 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->globalPosition().toPoint());
      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->exec(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.

  libXpertMassCore::OligomerSPtr oligomer_sp =
    mp_oligomers->getOligomersCstRef().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.

  libXpertMassCore::IndexRange *index_range_p =
    oligomer_sp->getCalcOptionsCstRef()
      .getIndexRangeCollectionCstRef()
      .mostInclusiveLeftRightIndexRange();

  qsizetype start_index = index_range_p->m_start;
  qsizetype stop_index  = index_range_p->m_stop;

  delete index_range_p;

  if(start_index >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size() ||
     stop_index >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - 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_sp->getPolymerCstSPtr()->getSequenceCstRef().getSequence();

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

  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.

  libXpertMassCore::OligomerSPtr oligomer_sp =
    mp_oligomers->getOligomersCstRef().at(row);

  libXpertMassCore::IndexRange *index_range_p =
    oligomer_sp->getCalcOptionsCstRef()
      .getIndexRangeCollectionCstRef()
      .mostInclusiveLeftRightIndexRange();

  qsizetype start_index = index_range_p->m_start;
  qsizetype stop_index  = index_range_p->m_stop;

  if(start_index >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size() ||
     stop_index >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - 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(*index_range_p, false, false);

  delete index_range_p;
}


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


void
MassSearchOligomerTableView::copyAvg()
{
  return copyMassList(libXpertMassCore::Enums::MassType::AVG);
}


void
MassSearchOligomerTableView::copyMassList(
  libXpertMassCore::Enums::MassType mass_type)
{
  if(mass_type == libXpertMassCore::Enums::MassType::BOTH)
    qFatal() << "Programming error.";

  QString text;


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

      libXpertMassCore::OligomerSPtr oligomer_sp =
        mp_oligomers->getOligomersCstRef().at(sourceRow);

      if(mass_type == libXpertMassCore::Enums::MassType::MONO)
        text += QString::number(
          oligomer_sp->getMass(libXpertMassCore::Enums::MassType::MONO),
          'f',
          libXpertMassCore::OLIGOMER_DEC_PLACES);
      else if(mass_type == libXpertMassCore::Enums::MassType::AVG)
        text += QString::number(
          oligomer_sp->getMass(libXpertMassCore::Enums::MassType::AVG),
          'f',
          libXpertMassCore::OLIGOMER_DEC_PLACES);
      else
        qFatal() << "Programming error.";

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

  if(text.isEmpty())
    return;

  QClipboard *clipboard = QApplication::clipboard();

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

} // namespace MassXpert

} // namespace MsXpS
