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

/////////////////////// pappsomspp includes


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


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


/////////////////////// Local includes
#include "../nongui/globals.hpp"
#include "CleaveOligomerTableView.hpp"
#include "CleaveOligomerTableViewSortProxyModel.hpp"
#include "CleavageDlg.hpp"
#include "CleaveOligomerTableViewMimeData.hpp"


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


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


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

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

// The oligomers are deep-copied from this object's mp_oligomers container
// into the oligomers argument to this function.
int
CleaveOligomerTableView::selectedOligomers(
  libXpertMassCore::OligomerCollection &oligomers, int index) const
{
  int count = 0;

  int localIndex = 0;

  // How many oligomers are there in the list passed as argument?
  int oligomerCount = oligomers.size();

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

  // If index is -1 , then we are asked to append the oligomers to
  // the list.
  if(index == -1)
    localIndex = 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 seven 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);

      // Older version
      // Oligomer *oligomer =
      // static_cast<Oligomer *>(mp_oligomers.at(row).get());
      // OligomerSPtr new_oligomer_sp =
      // std::make_shared<Oligomer>(*oligomer);

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

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

      // 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() + localIndex, new_oligomer_sp);

      ++localIndex;
      ++count;
    }

  return count;
}


QString *
CleaveOligomerTableView::describeSelectedOligomersAsPlainText(
  const QString &pattern,
  QString delimiter,
  bool with_sequence,
  bool for_xpert_miner,
  libXpertMassCore::Enums::MassType mass_type) const
{
  // Let's get all the currently selected oligomers in one container. Note that
  //  the Oligomer instances in oligomers are allocated fully for us. We will
  //  thefore free them upon use below.
  libXpertMassCore::OligomerCollection oligomers;
  std::size_t oligomers_count = selectedOligomers(oligomers, -1);

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

  // There are two possibilities:
  // 1. The pattern string is not empty, in which case it should be used to
  // craft the output.
  // 2. The pattern string is empty, in which case the delimiter is used to
  // craft a default generally-usable output.

  bool withPattern;

  if(pattern.isEmpty() || for_xpert_miner)
    {
      withPattern = false;

      // If delimiter is empty, then set to "$".
      if(delimiter.isEmpty())
        delimiter = "$";
    }
  else
    {
      withPattern = true;
    }

  // The text that we will return, that must be allocated on the heap.
  QString *output = new QString;

  // Slot to get the text from the called functions.
  QString *text = nullptr;

  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(withPattern)
        {
          text = describeOligomerAsPlainTextWithPattern((*the_iterator).get(),
                                                        pattern);

          if(text == nullptr)
            qFatal() << "Programming error.";

          *output += *text;

          delete text;
        }
      else
        {
          text = describeOligomerAsPlainTextWithDelimiter((*the_iterator).get(),
                                                          delimiter,
                                                          with_sequence,
                                                          for_xpert_miner,
                                                          mass_type);
          if(text == nullptr)
            qFatal() << "Programming error.";

          *output += *text;

          delete text;
        }

      ++the_iterator;

      // The version below is buggy. See oligomers.clear() below.
      // the_iterator = oligomers.getOligomersRef().erase(the_iterator);
    }

  oligomers.clear();

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

  return output;
}


QString *
CleaveOligomerTableView::describeOligomerAsPlainTextWithDelimiter(
  libXpertMassCore::Oligomer *oligomer_p,
  const QString &delimiter,
  bool with_sequence,
  bool for_xpert_miner,
  libXpertMassCore::Enums::MassType massType) const
{
  if(oligomer_p == nullptr)
    qFatal() << "Programming error.";

  // For export to XpertMiner, we only want the masses asked for:
  // libXpertMassCore::Enums::MassType::MONO or AVG. Also, we want the format to
  // be: mass <delim> charge <delim> name <delim> coords

  QString *text = new QString;

  if(!for_xpert_miner)
    *text += QString("\n%1%2").arg(oligomer_p->getDescription()).arg(delimiter);

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

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

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

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

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

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

  // We cannot export the sequence if data are for XpertMiner
  if(!for_xpert_miner && with_sequence)
    {
      const QString sequence =
        oligomer_p->getPolymerCstSPtr()->getSequenceCstRef().getSequence(
          oligomer_p->getCalcOptionsCstRef().getIndexRangeCollectionCstRef());

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

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

  return text;
}


QString *
CleaveOligomerTableView::describeOligomerAsPlainTextWithPattern(
  libXpertMassCore::Oligomer *oligomer_p, const QString &pattern) const
{
  // We receive a pattern that we use to craft the output. The following
  // shortcuts are used:
  // %f - filename of mxp file
  // %m - mono mass
  // %a - avg mass
  // %z - charge
  // %s - sequence
  // %c - coordinates
  // %e - enzyme
  // %o - modif (boolean)
  // %n - oligomer name
  // %p - partial
  // %i - polymer sequence name (identity)

  if(oligomer_p == nullptr)
    qFatal() << "Programming error.";

  // qDebug() << oligomer_p->getCalcOptionsCstRef()
  //               .getIndexRangeCollectionCstRef()
  //               .positionsAsText()
  //          << "with name:" << oligomer_p->getName();


  QString *text = new QString;

  QChar prevChar = ' ';

  for(int iter = 0; iter < pattern.size(); ++iter)
    {
      QChar curChar = pattern.at(iter);

      // qDebug() << __FILE__ << __LINE__
      // << "Current char:" << curChar;

      if(curChar == '\\')
        {
          if(prevChar == '\\')
            {
              *text += '\\';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '\\';
              continue;
            }
        }

      if(curChar == '%')
        {
          if(prevChar == '\\')
            {
              *text += '%';
              prevChar = ' ';
              continue;
            }
          else
            {
              prevChar = '%';
              continue;
            }
        }

      if(curChar == 'n')
        {
          if(prevChar == '\\')
            {
              *text += QString("\n");

              // Because a newline only works if it is followed by something,
              // and if the user wants a termination newline, then we need to
              // duplicate that new line char if we are at the end of the
              // string.

              if(iter == pattern.size() - 1)
                {
                  // This is the last character of the line, then, duplicate
                  // the newline so that it actually creates a new line in the
                  // text.

                  *text += QString("\n");
                }

              prevChar = ' ';
              continue;
            }
        }

      if(prevChar == '%')
        {
          // The current character might have a specific signification.
          if(curChar == 'f')
            {
              QFileInfo fileInfo(
                mp_parentDlg->editorWnd()->getPolymer()->getFilePath());
              if(fileInfo.exists())
                *text += fileInfo.fileName();
              else
                *text += "Untitled";

              prevChar = ' ';
              continue;
            }
          if(curChar == 'm')
            {
              *text += QString("%1").arg(
                oligomer_p->getMass(libXpertMassCore::Enums::MassType::MONO),
                0,
                'f',
                libXpertMassCore::OLIGOMER_DEC_PLACES);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'a')
            {
              *text += QString("%1").arg(
                oligomer_p->getMass(libXpertMassCore::Enums::MassType::AVG),
                0,
                'f',
                libXpertMassCore::OLIGOMER_DEC_PLACES);

              prevChar = ' ';
              continue;
            }
          if(curChar == 'z')
            {
              *text +=
                QString("%1").arg(oligomer_p->getIonizerCstRef().charge());

              prevChar = ' ';
              continue;
            }
          if(curChar == 's')
            {
              *text += oligomer_p->getPolymerCstSPtr()
                         ->getSequenceCstRef()
                         .getSequence(oligomer_p->getCalcOptionsCstRef()
                                        .getIndexRangeCollectionCstRef());

              prevChar = ' ';
              continue;
            }
          if(curChar == 'l')
            {
              QString elidedSequence = libXpertMassCore::Utils::elideText(
                oligomer_p->getPolymerCstSPtr()
                  ->getSequenceCstRef()
                  .getSequence(oligomer_p->getCalcOptionsCstRef()
                                 .getIndexRangeCollectionCstRef()));

              *text += elidedSequence;

              prevChar = ' ';
              continue;
            }
          if(curChar == 'c')
            {
              *text += oligomer_p->getCalcOptionsCstRef()
                         .getIndexRangeCollectionCstRef()
                         .positionsAsText();

              prevChar = ' ';
              continue;
            }
          if(curChar == 'e')
            {
              *text += oligomer_p->getDescription();

              prevChar = ' ';
              continue;
            }
          if(curChar == 'o')
            {
              *text += QString("%1").arg(oligomer_p->isModified());

              prevChar = ' ';
              continue;
            }
          if(curChar == 'n')
            {
              *text += oligomer_p->getName();

              prevChar = ' ';
              continue;
            }
          if(curChar == 'p')
            {
              *text += QString("%1").arg(oligomer_p->getPartialCleavage());

              prevChar = ' ';
              continue;
            }
          if(curChar == 'i')
            {
              *text += mp_parentDlg->editorWnd()->getPolymer()->getName();

              prevChar = ' ';
              continue;
            }

          // At this point the '%' is not followed by any special character
          // above, so we skip them both from the text. If the '%' is to be
          // printed, then it needs to be escaped.

          continue;
        }
      // End of
      // if(prevChar == '%')

      // The character prior this current one was not '%' so we just append
      // the current character.
      *text += curChar;
    }
  // End of
  // for (int iter = 0; iter < pattern.size(); ++iter)

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

  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.

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

  Q_ASSERT(!text.isEmpty());

  text += QString(" -- %1").arg(oligomer_sp->getDescription());

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

  QString formula = oligomer_sp->elementalComposition();

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

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

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

  SequenceEditorWnd *seq_editor_wnd = mp_parentDlg->editorWnd();

  const libXpertMassCore::IndexRangeCollection &index_range_collection =
    oligomer_sp->getCalcOptionsCstRef().getIndexRangeCollectionCstRef();

  // Remove the previous selection, so that we can start fresh.
  seq_editor_wnd->mpa_editorGraphicsView->resetSelection();

  foreach(const libXpertMassCore::IndexRange *item,
          index_range_collection.getRangesCstRef())
    {
      if(item->m_start >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size() ||
         item->m_stop >= (qsizetype)oligomer_sp->getPolymerCstSPtr()->size())
        {
          QMessageBox::warning(this,
                               tr("MassXpert3 - 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;
        }

      seq_editor_wnd->mpa_editorGraphicsView->setSelection(*item, true, false);
    }

  seq_editor_wnd->updateSelectedSequenceMasses();
}


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


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


void
CleaveOligomerTableView::copyMassList(libXpertMassCore::Enums::MassType mass_type)
{
  QString masses_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 row = sourceIndex.row();

      // qDebug() << __FILE__ << __LINE__
      //          << "row:" << row;

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

      masses_text += QString("%1\n").arg(oligomer_sp->getMass(mass_type),
                                         0,
                                         'f',
                                         libXpertMassCore::OLIGOMER_DEC_PLACES);
    }

  if(masses_text.isEmpty())
    return;

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

} // namespace MassXpert
} // namespace MsXpS
