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


/////////////////////// stdlib includes


/////////////////////// Qt includes
#include <QDebug>
#include <QHeaderView>
#include <QMouseEvent>
#include <QMessageBox>
#include <QDrag>
#include <QMenu>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/utils.h>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/Oligomer.hpp>


/////////////////////// libXpertMassGui includes


/////////////////////// Local includes
#include "../nongui/globals.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::setOligomerCollection(
  libXpertMassCore::OligomerCollection *oligomers)
{
  mp_oligomers = oligomers;
}


const libXpertMassCore::OligomerCollection *
FragmentOligomerTableView::getOligomers()
{
  return mp_oligomers;
}


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

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

// The oligomers OligomerCollection might not be empty and
// index indicates at what index to insert the currently
// selected oligomers. If index = -1, then the oligomers
// need to be simply appended.
int
FragmentOligomerTableView::selectedOligomers(
  libXpertMassCore::OligomerCollection &oligomers, int index) const
{
  int count = 0;

  int oligomers_insertion_index = 0;

  // How many oligomers are there in the list passed as argument?
  int oligomerCount = oligomers.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 (that is, to insert past the last item).
  if(index == -1)
    oligomers_insertion_index = oligomers.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 += " ";

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

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

      row_text += new_oligomer_sp->getFormulaCstRef().getActionFormula();
      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.

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

      new_oligomer_sp->appendProp(prop);

      oligomers.getOligomersRef().insert(oligomers.getOligomersRef().begin() +
                                           oligomers_insertion_index,
                                         new_oligomer_sp);

      ++oligomers_insertion_index;
      ++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::describeOligomerAsPlainText(
  QString delimiter,
  bool withSequence,
  bool forXpertMiner,
  libXpertMassCore::Enums::MassType massType) const
{
  // Let's get all the currently selected oligomers in one list.
  libXpertMassCore::OligomerCollection oligomers;

  // Append the selected oligomers to the empty list.
  std::size_t appended_oligomer_count = selectedOligomers(oligomers, -1);

  // Sanity check
  if(appended_oligomer_count != oligomers.size())
    qFatal() << "Programming error.";

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

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

  QString *text = new QString();

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

  while(the_iterator != the_end_iterator)
    {
      if(!forXpertMiner)
        *text += QString("\n%1%2")
                   .arg((*the_iterator)->getDescription())
                   .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)
                        ->getCalcOptionsCstRef()
                        .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)
                                              ->getCalcOptionsCstRef()
                                              .getIndexRangeCollectionCstRef());

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

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

      ++the_iterator;
    }

  // At this point, just clear the oligomers.
  oligomers.clear();

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

  libXpertMassCore::OligomerSPtr oligomer_sp =
    mp_oligomers->getOligomersCstRef().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->getCalcOptionsCstRef()
         .getIndexRangeCollectionCstRef()
         .leftMostIndexRangeStart() >=
       (qsizetype)oligomer_sp->getPolymerCstSPtr()->size() ||
     oligomer_sp->getCalcOptionsCstRef()
         .getIndexRangeCollectionCstRef()
         .rightMostIndexRangeStop() >=
       (qsizetype)oligomer_sp->getPolymerCstSPtr()->size())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - 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->getPolymerCstSPtr()->getSequenceCstRef().getSequence(
      oligomer_sp->getCalcOptionsCstRef().getIndexRangeCollectionCstRef());

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

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

  libXpertMassCore::CalcOptions calcOptions(oligomer_sp->getCalcOptionsCstRef());

  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.

  libXpertMassCore::OligomerSPtr oligomer_sp =
    mp_oligomers->getOligomersRef().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->getCalcOptionsCstRef()
         .getIndexRangeCollectionCstRef()
         .leftMostIndexRangeStart() >=
       (qsizetype)oligomer_sp->getPolymerCstSPtr()->size() ||
     oligomer_sp->getCalcOptionsCstRef()
         .getIndexRangeCollectionCstRef()
         .rightMostIndexRangeStop() >=
       (qsizetype)oligomer_sp->getPolymerCstSPtr()->size())
    {
      QMessageBox::warning(this,
                           tr("MassXpert3 - 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->getCalcOptionsCstRef()
      .getIndexRangeCollectionCstRef()
      .leftMostIndexRangeStart(),
    oligomer_sp->getCalcOptionsCstRef()
      .getIndexRangeCollectionCstRef()
      .rightMostIndexRangeStop(),
    false,
    false);
}


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


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


void
FragmentOligomerTableView::copyMassList(libXpertMassCore::Enums::MassType mass_type)
{
  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->getOligomersRef().at(sourceRow);

      if(mass_type == libXpertMassCore::Enums::MassType::MONO)
        text += QString("%1").arg(
          oligomer_sp->getMass(libXpertMassCore::Enums::MassType::MONO),
          0,
          'f',
          libXpertMassCore::OLIGOMER_DEC_PLACES);
      else if(mass_type == libXpertMassCore::Enums::MassType::AVG)
        text += QString("%1").arg(
          oligomer_sp->getMass(libXpertMassCore::Enums::MassType::AVG),
          0,
          '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
