/* 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
#include <cmath>


/////////////////////// Qt includes
#include <QMouseEvent>
#include <QScrollBar>
#include <QGraphicsItem>
#include <QTransform>
#include <QStatusBar>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/PolChemDef.hpp>
#include <MsXpS/libXpertMassCore/IndexRangeCollection.hpp>
#include <MsXpS/libXpertMassCore/CrossLinker.hpp>
#include <MsXpS/libXpertMassCore/CrossLink.hpp>


/////////////////////// Local includes
#include "SequenceEditorGraphicsView.hpp"
#include "MonomerCodeEvaluator.hpp"
#include "ChemEntVignette.hpp"
#include "ChemEntVignetteRenderer.hpp"


namespace MsXpS
{

namespace MassXpert
{


SequenceEditorGraphicsView::SequenceEditorGraphicsView(
  SequenceEditorWnd *editorWnd)
  : mp_editorWnd(editorWnd)
{
  m_kbdShiftDown = false;
  m_kbdCtrlDown  = false;
  m_kbdAltDown   = false;

  m_ongoingMouseMultiSelection    = false;
  m_ongoingKeyboardMultiSelection = false;

  m_sequenceDrawn = false;

  m_requestedVignetteSize = 32;

  m_xScaleFactor = 1;
  m_yScaleFactor = 1;

  // These are pointers, and have to be 0ed.
  mpa_cursorRenderer = 0;
  mpa_cursorVignette = 0;

  mpa_selection = new SequenceSelection(this);

  mpa_monomerCodeEvaluator = 0;

  m_lastClickedVignette = 0;

  m_leftMargin = 64;

  // m_columns is often used a fractional denominator, cannot be 0.
  m_columns = 1;

  setMouseTracking(true);
  setFocusPolicy(Qt::StrongFocus);

  QScrollBar *vScrollBar = verticalScrollBar();
  connect(vScrollBar,
          SIGNAL(actionTriggered(int)),
          this,
          SLOT(vScrollBarActionTriggered(int)));

  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  // setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}


SequenceEditorGraphicsView::~SequenceEditorGraphicsView()
{
  // Make sure we first remove all selection items from the view.
  delete mpa_selection;
  delete mpa_cursorRenderer;
  delete mpa_monomerCodeEvaluator;

  return;
}


int
SequenceEditorGraphicsView::requestedVignetteSize()
{
  return m_requestedVignetteSize;
}


int
SequenceEditorGraphicsView::columns()
{
  return m_columns;
}


int
SequenceEditorGraphicsView::rows()
{
  return m_rows;
}


int
SequenceEditorGraphicsView::leftMargin()
{
  return m_leftMargin;
}


void
SequenceEditorGraphicsView::setPolymer(libXpertMassCore::PolymerQSPtr polymer_sp)
{
  msp_polymer = polymer_sp;
}


libXpertMassCore::PolymerQSPtr
SequenceEditorGraphicsView::getPolymer()
{
  return msp_polymer;
}


void
SequenceEditorGraphicsView::setEditorWnd(SequenceEditorWnd *editorWnd)
{
  Q_ASSERT(editorWnd);

  mp_editorWnd = editorWnd;
}


void
SequenceEditorGraphicsView::setMonomerCodeEvaluator(
  MonomerCodeEvaluator *evaluator)
{
  Q_ASSERT(evaluator);

  mpa_monomerCodeEvaluator = evaluator;
}


void
SequenceEditorGraphicsView::updateColumns()
{
  QRect viewRect = rect();

  m_columns = static_cast<int>(
    (viewRect.width() - m_leftMargin - m_requestedVignetteSize) /
    m_requestedVignetteSize);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "viewRect.width:" << viewRect.width()
  // 	      << "columns:" << m_columns;
}


void
SequenceEditorGraphicsView::updateVScrollBar()
{
  QScrollBar *scrollBar = verticalScrollBar();
  scrollBar->setSingleStep(m_requestedVignetteSize);

  QRect viewRect = QWidget::rect();

  scrollBar->setPageStep(viewRect.height());
}


void
SequenceEditorGraphicsView::updateSceneRect()
{
  QRect viewRect = rect();

  scene()->setSceneRect(
    0, 0, viewRect.width(), ++m_rows * m_requestedVignetteSize);
}


int
SequenceEditorGraphicsView::vignetteIndex(const QPointF &point)
{
  QPointF local = point;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Enter in vignetteIndex"
  // 	      << point;

  if(static_cast<int>(local.x()) < m_leftMargin)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "static_cast<int>(local.x()) < m_leftMargin";

      return -1;
    }

  if(local.x() > m_leftMargin + m_columns * m_requestedVignetteSize)
    {
      // 	  qDebug() << __FILE__ << __LINE__
      // 		    << "local.x() > m_leftMargin + "
      // 	    "m_columns * m_requestedVignetteSize";

      return -1;
    }

  if(local.y() < 0)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "local.y() < 0";

      return -1;
    }

  if(local.y() > m_rows * m_requestedVignetteSize)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 	      << "local.y() > m_rows * m_requestedVignetteSize"
      // 		  << "local.y():" << local.y()
      // 		  << "m_rows * m_requestedVignetteSize"
      // 		  << m_rows * m_requestedVignetteSize;

      return -1;
    }

  std::size_t row =
    static_cast<std::size_t>(local.y() / m_requestedVignetteSize);
  int yCount = row * m_columns;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "x--y:"
  // 	      << local.x() << "--" << local.y();

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "m_requestedVignetteSize: m_columns:"
  // 	      << m_requestedVignetteSize << m_columns;

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

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


  double xRatio = (local.x() - m_leftMargin) / m_requestedVignetteSize;

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

  double intPart = 0;

  double fracPart = modf(xRatio, &intPart);

  //       qDebug() << __FILE__ << __LINE__
  // 	      << "intPart -- fracPart" << intPart << fracPart;


  if(fracPart > 0.9)
    ++intPart;

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

  std::size_t idx = static_cast<std::size_t>(intPart + yCount);

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

  if(idx > msp_polymer->size())
    return -1;

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Exit from vignetteIndex";

  return idx;
}


QPointF
SequenceEditorGraphicsView::vignetteLocation(std::size_t index,
                                             MxtCardinalDirection cardDir)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Enter in vignetteLocation with index:" << index;

  std::size_t localIndex = 0;

  if(index > msp_polymer->size())
    localIndex = msp_polymer->size() - 1;
  else
    localIndex = index;

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

  Q_ASSERT(m_columns);

  // row is an index
  double rowIndex = localIndex / m_columns;

  // column is a position
  int column = localIndex - (static_cast<int>(rowIndex) * m_columns);

  //   qDebug() << "localIndex -- column -- rowIndex"
  //  	    << localIndex << "--" << column << "--" << rowIndex;

  double x = 0;
  double y = 0;


  if(cardDir == CENTER)
    {
      x = m_leftMargin + ((column + 0.5) * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }
  else if(cardDir == NORTH_WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == NORTH)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == NORTH_EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize;
    }
  else if(cardDir == EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }
  else if(cardDir == SOUTH_EAST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          m_requestedVignetteSize;
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == SOUTH)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == SOUTH_WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize);
      y = rowIndex * m_requestedVignetteSize + m_requestedVignetteSize;
    }
  else if(cardDir == WEST)
    {
      x = m_leftMargin + (column * m_requestedVignetteSize) +
          (m_requestedVignetteSize / 2);
      y = rowIndex * m_requestedVignetteSize + (m_requestedVignetteSize / 2);
    }


  if(localIndex >= msp_polymer->size() - 1)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "vignetteLocation:"
      // 		  << "m_columns:" << m_columns
      // 		  << "m_rows:" << m_rows;

      // In this case, IF the vignette is located at the right
      // margin of the last row(that is the sequence vignettes
      // cover a perfect rectangle and the vignette is the bottom
      // right one), then y cannot be greater than
      //
      //(m_rows * m_requestedVignetteSize).
      //
      // The point is that when selecting a sequence with the
      // keyboard, the selection goes right to the beginning of the
      // next line, and thus, if asking CENTER, y becomes too
      // big(see && column == 0 below to indicate that the cursor
      // passes to next line).
      //
      // We thus have to track the case of the vignette at 'index'
      // being at the right bottom corner.
      //
      // Note that this is only meaningful if the vignette is for
      // the last monomer in the polymer sequence, otherwise it
      // could not be the last vignette in the sequence and located
      // at the right bottom corner of the sequence rendering area.

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "vignetteLocation"
      // 		  << "rowIndex:" << rowIndex
      // 		  << "column:" << column;

      if(rowIndex == m_rows && column == 0)
        {
          // The vignette is actually at the last row of the display
          // and at the last column. That is, it is located at the
          // bottom right corner of a rectangular sequence display
          // area.

          if(cardDir == CENTER)
            {
              y = m_rows * m_requestedVignetteSize;

              // 		qDebug() << "vignetteLocation - QPointF's y set to:" << y;
            }
        }
    }


  QPointF pointF(x, y);

  //  qDebug() << "vignettePosition:" << pointF;

  return pointF;
}


void
SequenceEditorGraphicsView::setSelection(std::size_t start,
                                         std::size_t stop,
                                         bool is_multi_region_selection,
                                         bool is_multi_selection_region)
{
  std::size_t local_start = std::min(start, stop);
  std::size_t local_stop  = std::max(start, stop);

  if(local_stop >= msp_polymer->size())
    local_stop = msp_polymer->size();

  if(!is_multi_region_selection)
    mpa_selection->deselectRegions();

  mpa_selection->selectRegion(local_start,
                              local_stop,
                              is_multi_region_selection,
                              is_multi_selection_region);

  m_lastClickedVignette = local_stop + 1;

  positionCursor();

  QScrollBar *vScrollBar = verticalScrollBar();

  int visibleRows = rect().height() / m_requestedVignetteSize;
  int lines       = m_lastClickedVignette / m_columns;

  vScrollBar->setValue((lines + 1) * m_requestedVignetteSize -
                       visibleRows * m_requestedVignetteSize);

  mp_editorWnd->updateSelectedSequenceMasses();

  // Finally update the selection clipboard
  mp_editorWnd->clipboardCopy(QClipboard::Selection);
}


void
SequenceEditorGraphicsView::setSelection(
  const libXpertMassCore::IndexRange &index_range,
  bool is_multi_region_selection,
  bool is_multi_selection_region)
{
  setSelection(index_range.m_start,
               index_range.m_stop,
               is_multi_region_selection,
               is_multi_selection_region);
}


void
SequenceEditorGraphicsView::setSelection(
  const libXpertMassCore::IndexRangeCollection &index_range_collection,
  bool is_multi_region_selection,
  bool is_multi_selection_region)
{
  foreach(const libXpertMassCore::IndexRange *item,
          index_range_collection.getRangesCstRef())
    setSelection(item->m_start,
                 item->m_stop,
                 is_multi_region_selection,
                 is_multi_selection_region);
}

void
SequenceEditorGraphicsView::resetSelection()
{
  mpa_selection->deselectRegions();

  m_selectionFirstIndex  = -1;
  m_selectionSecondIndex = -1;

  m_selectionFirstPoint  = QPointF(0, 0);
  m_selectionSecondPoint = QPointF(0, 0);

  mp_editorWnd->updateSelectedSequenceMasses();
}


void
SequenceEditorGraphicsView::resetSelectionButLastRegion()
{
  // Deselect all the selected regions, but the lastly selected
  // region.
  mpa_selection->deselectRegionsButLast();

  if(mpa_selection->regionSelectionCount())
    {
      // If there was actually one selection left, then set the cursor
      // to be at the end of that selection.

      positionCursor(*(mpa_selection->getRegionSelectionsCstRef().back()));
    }
}


void
SequenceEditorGraphicsView::resetMultiSelectionRegionsButFirstSelection()
{
  // Deselect all the selected regions, but the lastly selected
  // region.
  mpa_selection->deselectMultiSelectionRegionsButFirstSelection();
}


bool
SequenceEditorGraphicsView::selectionIndices(
  libXpertMassCore::IndexRangeCollection &index_range_collection)
{
  index_range_collection.clear();

  // At this point we should ask the selection to provide the info.

  bool res = mpa_selection->selectionIndices(index_range_collection);

  // qDebug() << "Returned value:" << res
  //          << "with selection indices:" <<
  //          index_range_collection.indicesAsText();

  // qDebug() << "The member selection datum provided the following selection
  // indices:" << index_range_collection.indicesAsText(); qDebug() << "The
  // member selection datum provided the following selection postiions:" <<
  // index_range_collection.positionsAsText();

  if(!res)
    {
      // qDebug() << "Apparently there was no real selection, with indices:"
      // << index_range_collection.indicesAsText();

      // There is no real selection, thus, all we have to do is
      // construct a fake libXpertMassCore::IndexRange with the pseudo-selection,
      // that is a pseudo-region from start to last clicked
      // vignette. Remember, however, that the lastClickedVignette
      // index is one monomer vignette right of the actual vignette
      //(because the selection mark drawing functions require
      // this).

      libXpertMassCore::IndexRange index_range(this);
      index_range.m_start = 0;

      // But upon initialization of the sequence editor lastClickedVignette()
      // may be 0. And we cannot afford negative values with std::size_t.

      if(!lastClickedVignette())
        index_range.m_stop = 0;
      else
        index_range.m_stop = lastClickedVignette() - 1;

      index_range_collection.appendIndexRange(index_range);
    }
  else
    {
      qDebug() << "Apparently there was a real selection, with indices:"
               << index_range_collection.indicesAsText();
    }

  // However, return the real result so that caller knows if there
  // was a real selection or if the returned libXpertMassCore::IndexRange instance
  // is for the pseudo-selection from start of sequence to the last clicked
  // vignette.

  return res;
}


void
SequenceEditorGraphicsView::setOngoingMouseMultiSelection(bool value)
{
  m_ongoingMouseMultiSelection = value;
}


// SCENE-DRAWING FUNCTIONS /////////////////////////////////////////////
bool
SequenceEditorGraphicsView::renderCursor()
{
  //   qDebug() << __FILE__ << __LINE__ << "Entering renderCursor()";

  QString filePath = mp_editorWnd->getPolChemDefRenderingCstRPtr()
                       ->getPolChemDefCstSPtr()
                       ->getXmlDataDirPath() +
                     "/cursor.svg";

  //   qDebug() << __FILE__ << __LINE__ << "filePath" << filePath;

  mpa_cursorRenderer = new QSvgRenderer(filePath);

  if(!mpa_cursorRenderer->isValid())
    return false;

  mpa_cursorVignette = new QGraphicsSvgItem();

  mpa_cursorVignette->setSharedRenderer(mpa_cursorRenderer);

  scene()->addItem(mpa_cursorVignette);

  QRectF itemRectF = mpa_cursorVignette->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  mpa_cursorVignette->setTransform(matrix);

  scene()->update();

  //   qDebug() << __FILE__ << __LINE__ << "Exiting renderCursor()";

  return true;
}


bool
SequenceEditorGraphicsView::scaleCursor()
{
  if(!mpa_cursorVignette)
    return renderCursor();

  QRectF itemRectF = mpa_cursorVignette->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  mpa_cursorVignette->setTransform(matrix);

  scene()->update();

  return true;
}


bool
SequenceEditorGraphicsView::positionCursor(const QPointF &pos,
                                           MxtCursorLinePosition linePos)
{
  if(!mpa_cursorVignette)
    if(!renderCursor())
      return false;

  if(linePos == END_OF_LINE)
    {
      if(pos.x() == m_leftMargin && pos.y() >= m_requestedVignetteSize)
        {
          // Do not go to the left of first vignette of next row simply
          // because we are right of the last vignette in the row. That's
          // not intuitive.
          mpa_cursorVignette->setPos(m_leftMargin +
                                       m_columns * m_requestedVignetteSize -
                                       (m_requestedVignetteSize / 2),
                                     pos.y() - m_requestedVignetteSize);
          return true;
        }
    }

  mpa_cursorVignette->setPos(pos.x() - (m_requestedVignetteSize / 2), pos.y());

  return true;
}


bool
SequenceEditorGraphicsView::positionCursor(double x,
                                           double y,
                                           MxtCursorLinePosition linePos)
{
  return positionCursor(QPointF(x, y), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(std::size_t index,
                                           MxtCursorLinePosition linePos)
{
  return positionCursor(vignetteLocation(index), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(MxtCursorLinePosition linePos)
{
  return positionCursor(vignetteLocation(m_lastClickedVignette), linePos);
}


bool
SequenceEditorGraphicsView::positionCursor(
  const RegionSelection &regionSelection,
  [[maybe_unused]] MxtCursorLinePosition linePos)
{
  // We should place the cursor at the m_endIndex of the
  // regionSelection. Note that because we want the cursor to be
  // located RIGHT of the last vignette of the selection, we
  // increment the endIndex by one.
  m_lastClickedVignette = regionSelection.getStopIndex() + 1;
  return positionCursor();
}


ChemEntVignetteSPtr
SequenceEditorGraphicsView::renderMonomerVignette(
  const libXpertMassCore::MonomerCstSPtr monomer_csp, const QString &uuid)
{
  if(msp_polymer == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(monomer_csp == nullptr || monomer_csp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(uuid.isEmpty())
    qFatal() << "Programming error. The Uuid string cannot be empty.";

  QString confirm_uuid = msp_polymer->getSequenceCstRef().getUuidForMonomer(
    std::const_pointer_cast<libXpertMassCore::Monomer>(monomer_csp));

  if(uuid != confirm_uuid)
    qFatal() << "Programming error.";

  PolChemDefRenderingRPtr pol_chem_def_rendering_rp =
    mp_editorWnd->getPolChemDefRenderingRPtr();

  if(pol_chem_def_rendering_rp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  QString monomer_code = monomer_csp->getCode();
  if(monomer_code.isEmpty())
    qFatal() << "Programming error. Monomer code cannot be empty.";

  // qDebug() << "Asking renderer for monomer code:" << monomerCode;

  QSvgRendererSPtr monomer_renderer_sp =
    pol_chem_def_rendering_rp->chemEntVignetteRenderer(monomer_code);

  if(monomer_renderer_sp == nullptr)
    {
      //       qDebug() << __FILE__ << __LINE__
      // 		<< "Newing renderer for monomer code:" << monomerCode;

      monomer_renderer_sp =
        pol_chem_def_rendering_rp->newMonomerVignetteRendererFromMonomerCode(
          monomer_code);

      if(monomer_renderer_sp == nullptr)
        qFatal() << "Failed to make svg renderer for monomer."
                       << monomer_code;
    }

  // Ok, we have done the main work.

  ChemEntVignetteSPtr monomer_vignette_sp = std::make_shared<ChemEntVignette>();

  monomer_vignette_sp->setSharedRenderer(monomer_renderer_sp);
  monomer_vignette_sp->setUuid(uuid);
  monomer_vignette_sp->setName("MONOMER");
  monomer_vignette_sp->setChemicalEntity(
    libXpertMassCore::Enums::ChemicalEntity::MONOMER);
  monomer_vignette_sp->setZValue(1);

  scene()->addItem(monomer_vignette_sp.get());

  // Now, make sure that we test if the monomer is modified, if so
  // render the modif(s).

  // Iterate in the list of modification names...

  for(const libXpertMassCore::ModifSPtr &modif_sp : monomer_csp->getModifsCstRef())
    {
      qDebug() << "Asking renderer for modif name:" << modif_sp->getName();

      QString modif_uuid = monomer_csp->getUuidForModif(modif_sp);

      if(modif_uuid.isEmpty())
        qFatal() << "Programming error. Uuid cannot be empty.";

      QSvgRendererSPtr modif_renderer_sp =
        pol_chem_def_rendering_rp->chemEntVignetteRenderer(modif_sp->getName());

      if(modif_renderer_sp == nullptr)
        {
          // qDebug() << "Require to make new renderer for modif name:"
          //          << modif_sp->getName();

          modif_renderer_sp =
            pol_chem_def_rendering_rp->newModifVignetteRendererFromModifName(
              modif_sp->getName());

          if(modif_renderer_sp == nullptr)
            qFatal()
              << "Programming error. Failed to make svg renderer for Modif."
              << modif_sp->getName();
        }

      // At this point we actually have a modifRenderer for the
      // currently iterated modif. Use the Monomer vignette as the parent
      // of this new vignette.

      ChemEntVignette *modif_vignette_rp =
        new ChemEntVignette(monomer_vignette_sp.get());

      modif_vignette_rp->setSharedRenderer(modif_renderer_sp);
      modif_vignette_rp->setUuid(modif_uuid);
      modif_vignette_rp->setName("MODIF");
      modif_vignette_rp->setChemicalEntity(
        libXpertMassCore::Enums::ChemicalEntity::MODIF);
      modif_vignette_rp->setZValue(2);
    }

  // Finally we can update the scene.
  scene()->update(monomer_vignette_sp->boundingRect());

  return monomer_vignette_sp;
}


bool
SequenceEditorGraphicsView::modifyVignetteAt(std::size_t index,
                                             const libXpertMassCore::Modif &modif,
                                             const QString uuid)
{
  if(msp_polymer == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(uuid.isEmpty())
    qFatal() << "Programming error. The Uuid string cannot be empty.";

  PolChemDefRenderingRPtr pol_chem_def_rendering_rp =
    mp_editorWnd->getPolChemDefRenderingRPtr();

  if(pol_chem_def_rendering_rp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(index >= m_monomerVignettes.size())
    qFatal() << "Programming error. Out-of-bounds error.";

  // Get a pointer to the vignette of the Monomer to be modified at index
  // 'index'.
  ChemEntVignetteSPtr monomer_vignette_sp = m_monomerVignettes.at(index);
  if(monomer_vignette_sp == nullptr)
    qFatal()
      << "Programming error. Failed to find vignette for Monomer at "
         "index: << index.";

  QString name = modif.getName();
  if(name.isEmpty())
    qFatal() << "Programming error. Modif name cannot be empty.";

  // Use the entity name to create a renderer for that entity.
  QSvgRendererSPtr svg_renderer_sp =
    pol_chem_def_rendering_rp->chemEntVignetteRenderer(name);

  if(svg_renderer_sp == nullptr)
    {
      // A renderer for that modification did not exist already. We
      // have to create one proper.
      svg_renderer_sp =
        pol_chem_def_rendering_rp->newModifVignetteRendererFromModifName(name);

      if(!svg_renderer_sp)
        qFatal() << "Failed to make svg renderer for modif:" << name;
    }

  ChemEntVignette *modif_vignette_rp =
    new ChemEntVignette(monomer_vignette_sp.get());

  modif_vignette_rp->setSharedRenderer(svg_renderer_sp);
  modif_vignette_rp->setUuid(uuid);
  modif_vignette_rp->setName("MODIF");
  modif_vignette_rp->setChemicalEntity(
    libXpertMassCore::Enums::ChemicalEntity::MODIF);
  modif_vignette_rp->setZValue(2);

  scene()->update(monomer_vignette_sp->boundingRect());

  return true;
}


bool
SequenceEditorGraphicsView::unmodifyVignetteAt(std::size_t index,
                                               const QString uuid)
{
  if(uuid.isEmpty())
    qFatal() << "Programming error. The Uuid string cannot be empty.";

  if(index >= m_monomerVignettes.size())
    qFatal() << "Programming error. Out-of-bounds error.";

  // The modif vignette has parent a monomer vignette which we need
  // to locate first.

  ChemEntVignetteSPtr monomer_vignette_sp = m_monomerVignettes.at(index);

  // There MUST be a vignette for the monomer at index.
  if(monomer_vignette_sp == nullptr)
    qFatal()
      << "Programming error. Failed to get the Monomer vignette at index"
      << index;

  QList<QGraphicsItem *> childrenList =
    static_cast<QGraphicsItem *>(monomer_vignette_sp.get())->childItems();

  // Get all the children of the Monomer vignette. Amongst them will be
  // the cross-linker vignette we are destroying.

  // qDebug() << "The Monomer vignette has" << childrenList.size()
  //          << "children vignettes.";

  for(int iter = 0; iter < childrenList.size(); ++iter)
    {
      // qDebug() << "Iterating in another child vignette.";

      ChemEntVignette *item =
        static_cast<ChemEntVignette *>(childrenList.at(iter));

      if(item->getUuid() != uuid)
        continue;

      // Sanity check
      if(item->getChemicalEntity() !=
         libXpertMassCore::Enums::ChemicalEntity::MODIF)
        qFatal() << "Inconsistency between Uuid of ChemEntVignette and "
                          "ChemicalEntity.";

      // qDebug() << "Found the right item: removing the item and deleting it.";
      scene()->removeItem(item);
      delete(item);
    }

  return true;
}


bool
SequenceEditorGraphicsView::crossLinkVignetteAt(
  std::size_t index,
  libXpertMassCore::CrossLinkCstSPtr cross_link_csp,
  const QString uuid)
{
  if(cross_link_csp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(uuid.isEmpty())
    qFatal() << "Programming error. The Uuid string cannot be empty.";

  if(msp_polymer == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  PolChemDefRendering *pol_chem_def_rendering_rp =
    mp_editorWnd->getPolChemDefRenderingRPtr();

  if(pol_chem_def_rendering_rp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(index >= m_monomerVignettes.size())
    qFatal() << "Programming error. Out-of-bounds error.";

  // Get a pointer to the vignette to crossLink at index 'index'.
  ChemEntVignetteSPtr monomer_vignette_sp = m_monomerVignettes.at(index);
  if(monomer_vignette_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  libXpertMassCore::CrossLinkerCstSPtr cross_linker_csp =
    cross_link_csp->getCrossLinkerCstSPtr();

  QString name = cross_linker_csp->getName();
  if(name.isEmpty())
    qFatal()
      << "Programming error. The name of the CrossLinker cannot be empty.";

  // Use the crossLinker name to retrieve a renderer for that entity if already
  // existing.
  QSvgRendererSPtr svg_renderer_sp =
    pol_chem_def_rendering_rp->chemEntVignetteRenderer(name);

  if(svg_renderer_sp == nullptr)
    {
      // A renderer for that crossLink did not exist already. We
      // have to create one proper.
      svg_renderer_sp =
        pol_chem_def_rendering_rp->newCrossLinkerVignetteRenderer(name);

      if(svg_renderer_sp == nullptr)
        {
          qFatal() << "Failed to make svg renderer for cross-linker:"
                         << name;
        }
    }

  // qDebug() << "Creating CrossLink vignette with CrossLink Uuid:" << uuid;

  ChemEntVignette *cross_link_vignette_rp =
    new ChemEntVignette(monomer_vignette_sp.get());

  cross_link_vignette_rp->setSharedRenderer(svg_renderer_sp);
  cross_link_vignette_rp->setUuid(uuid);
  cross_link_vignette_rp->setName("CROSS_LINKER");
  cross_link_vignette_rp->setChemicalEntity(
    libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER);
  cross_link_vignette_rp->setZValue(2);

  scene()->update(monomer_vignette_sp->boundingRect());

  return true;
}


bool
SequenceEditorGraphicsView::uncrossLinkVignetteAt(std::size_t index,
                                                  const QString uuid)
{
  if(uuid.isEmpty())
    qFatal() << "Programming error. The Uuid string cannot be empty.";

  // qDebug() << "Now uncrosslinking the vignette at index" << index
  //          << "and with uuid" << uuid;

  if(index >= m_monomerVignettes.size())
    qFatal() << "Programming error. Out-of-bounds error.";

  // The cross-linker vignette has parent a monomer vignette which we need
  // to locate first.

  ChemEntVignetteCstSPtr monomer_vignette_csp = m_monomerVignettes.at(index);

  // There MUST be a vignette for the monomer at index.
  if(monomer_vignette_csp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  // Get all the children of the Monomer vignette. Amongst them will be
  // the cross-linker vignette we are destroying.
  QList<QGraphicsItem *> childrenList = monomer_vignette_csp->childItems();

  // qDebug() << "The Monomer vignette has" << childrenList.size()
  //          << "children vignettes.";

  for(int iter = 0; iter < childrenList.size(); ++iter)
    {
      qDebug() << "Iterating in another child vignette.";

      ChemEntVignette *item =
        static_cast<ChemEntVignette *>(childrenList.at(iter));

      if(item->getUuid() != uuid)
        continue;

      // Sanity check
      if(item->getChemicalEntity() !=
         libXpertMassCore::Enums::ChemicalEntity::CROSS_LINKER)
        qFatal() << "Inconsistency between Uuid of ChemEntVignette and "
                          "ChemicalEntity.";

      // qDebug() << "Found the right CROSS_LINKER vignette item with uuid:"
      //          << uuid << ". Now removing the item and deleting it.";

      scene()->removeItem(item);
      delete(item);
    }

  return true;
}


bool
SequenceEditorGraphicsView::removeMonomerVignetteAt(std::size_t index)
{
  if(index >= m_monomerVignettes.size())
    qFatal() << "Programming error. Out-of-bounds error.";

  scene()->removeItem(m_monomerVignettes.at(index).get());

  m_monomerVignettes.erase(m_monomerVignettes.begin() + index);

  return true;
}


bool
SequenceEditorGraphicsView::renderAllMonomerVignettes()
{
  // Each Monomer in the Sequence will be attributed a SVG vignette.
  // This vignette of type ChemEntVignette, will be stored in the
  // member container m_monomerVignettes.
  //
  // If the Monomer being rendered is found to be modified, a new
  // vignette corresponding to that Modif instance will be created
  // with the Monomer's vignette as parent. This way, when we
  // destroy the Monomer vignette all the Modif vignette that have
  // that Monomer vignette as parent will be destroyed along.
  //
  // Same reasoning with CrossLink instances.
  // When the Monomer vignette is created, the Uuid string of the corresponding
  // Monomer in the Sequence object is stored in the member
  // ChemEntVignette::m_uuid datum.

  std::size_t iter = 0;

  mp_editorWnd->statusBar()->showMessage(tr("Rendering vignettes"));

  if(msp_polymer->size())
    mp_editorWnd->progressBar()->setRange(1, msp_polymer->size());
  else
    mp_editorWnd->progressBar()->setRange(1, 1);

  for(iter = 0; iter < msp_polymer->size(); ++iter)
    {
      mp_editorWnd->progressBar()->setValue(iter + 1);

      libXpertMassCore::MonomerCstSPtr monomer_csp =
        msp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

      if(monomer_csp == nullptr)
        qFatal()
          << "Programming error. Monomer pointer cannot be nullptr.";

      // Sanity check.
      QString uuid = msp_polymer->getSequenceCstRef().getUuidForMonomer(
        std::const_pointer_cast<libXpertMassCore::Monomer>(monomer_csp));
      if(uuid.isEmpty())
        qFatal()
          << "Programming error. The Uuid string for Monomer cannot be empty.";

      mp_editorWnd->statusBar()->showMessage(tr("Rendering vignettes: "
                                                "%1 - %2")
                                               .arg(monomer_csp->getName())
                                               .arg(iter + 1));

      // The rendered Monomer vignette will be the parent of Modif vignettes
      // if the Monomer is modified.
      ChemEntVignetteSPtr monomer_vignette_sp =
        renderMonomerVignette(monomer_csp, uuid);

      if(monomer_vignette_sp == nullptr)
        qFatal()
          << "Programming error. Failed to create Monomer vignette.";

      m_monomerVignettes.push_back(monomer_vignette_sp);

      //       qDebug() << "Rendered vignette for monomer:"
      // 		<< monomer->name().toAscii();
    }

  // At this point we have to render all the crossLinks of the
  // sequence... We know the polymer sequence has the container of
  // cross-links.

  bool ok;

  for(libXpertMassCore::CrossLinkCstSPtr cross_link_csp :
      msp_polymer->getCrossLinksCstRef())
    {
      // For each monomer in the cross-link, make a vignette.
      for(const libXpertMassCore::MonomerCstSPtr &monomer_csp :
          cross_link_csp->getMonomersCstRef())
        {
          std::size_t index =
            msp_polymer->getSequenceCstRef().monomerIndex(monomer_csp, ok);

          if(!ok)
            qFatal()
              << "Programming error. Failed to determine the index of the "
                 "Monomer instance in the Sequence.";

          QString uuid = msp_polymer->getUuidForCrossLink(
            std::const_pointer_cast<libXpertMassCore::CrossLink>(cross_link_csp));
          if(uuid.isEmpty())
            qFatal()
              << "Programming error. Failed to get Uuid for cross-link.";

          if(!crossLinkVignetteAt(index, cross_link_csp, uuid))
            qFatal()
              << "Failed to render vignette for cross-linker:"
              << cross_link_csp->getCrossLinkerCstSPtr()->getName();
        }
    }

  mp_editorWnd->statusBar()->showMessage(tr("Done rendering vignettes"));

  mp_editorWnd->progressBar()->setValue(1);
  mp_editorWnd->statusBar()->clearMessage();

  return true;
}


bool
SequenceEditorGraphicsView::scaleVignette(
  ChemEntVignetteSPtr chem_ent_vignette_sp)
{
  if(chem_ent_vignette_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  QRectF itemRectF = chem_ent_vignette_sp->boundingRect();

  // QMatrix(double m11, double m12, double m21, double m22, double
  // dx, double dy). A QMatrix object contains a 3 x 3 matrix. The
  // dx and dy elements specify horizontal and vertical
  // translation. The m11 and m22 elements specify horizontal and
  // vertical scaling. And finally, the m21 and m12 elements
  // specify horizontal and vertical shearing.

  double xScale = m_requestedVignetteSize / itemRectF.width();
  double yScale = m_requestedVignetteSize / itemRectF.height();

  //       qDebug() << "xScale - yScale:"
  // 		<< xScale << "-" << yScale;

  // Deprecated since 5.14.2 or earlier.
  // QMatrix matrix(xScale, 0, 0, yScale, 0, 0);

  QTransform matrix(xScale, 0, 0, yScale, 0, 0);

  chem_ent_vignette_sp->setTransform(matrix);

  return true;
}


bool
SequenceEditorGraphicsView::scaleVignettes()
{

  if(m_monomerVignettes.size() < 1)
    return true;

  mp_editorWnd->statusBar()->showMessage(tr("Scaling vignettes"));

  int vignetteListSize = m_monomerVignettes.size();

  if(vignetteListSize)
    mp_editorWnd->progressBar()->setRange(1, vignetteListSize);
  else
    mp_editorWnd->progressBar()->setRange(1, 1);

  std::size_t vignette_index = 0;

  for(ChemEntVignetteSPtr chem_ent_vignette_sp : m_monomerVignettes)
    {
      mp_editorWnd->progressBar()->setValue(vignette_index + 1);

      mp_editorWnd->statusBar()->showMessage(tr("Scaling vignettes: "
                                                "%1")
                                               .arg(vignette_index + 1));
      scaleVignette(chem_ent_vignette_sp);

      ++vignette_index;
    }

  scene()->update();

  mp_editorWnd->statusBar()->showMessage(tr("Done scaling vignettes"));
  mp_editorWnd->progressBar()->setValue(1);

  return true;
}


bool
SequenceEditorGraphicsView::positionVignette(
  ChemEntVignetteSPtr chem_ent_vignette_sp, std::size_t index)
{
  if(chem_ent_vignette_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(index >= msp_polymer->size())
    qFatal() << "Programming error.";

  // row is an index
  double rowIndex = index / m_columns;

  // column is a position
  int column = index - (static_cast<int>(rowIndex) * m_columns);

  double x = 0;
  double y = 0;

  x = m_leftMargin + (column * m_requestedVignetteSize);
  y = rowIndex * m_requestedVignetteSize;

  chem_ent_vignette_sp->setPos(x, y);

  return true;
}


bool
SequenceEditorGraphicsView::positionVignettes()
{
  double x = m_leftMargin;
  double y = 0;

  while(!m_labelList.isEmpty())
    {
      QGraphicsTextItem *label = m_labelList.takeFirst();

      scene()->removeItem(label);
      delete(label);
    }

  if(m_monomerVignettes.size() < 1)
    return true;

  m_rows = 0;

  updateColumns();

  // Vignette positioning proper.

  int column = 0;

  std::size_t vignette_index = 0;

  for(ChemEntVignetteSPtr chem_ent_vignette_sp : m_monomerVignettes)
    {
      if(column < m_columns)
        {
          if(!vignette_index)
            {
              // We are at the beginning of first line.
              QString text = QString("%1").arg(vignette_index + 1);

              QGraphicsTextItem *label = new QGraphicsTextItem(text, 0);

              // Now add the new item to the scene.
              scene()->addItem(label);

              label->setPos(0, y);

              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Positioned label at:" << QPointF(0,y);

              m_labelList.append(label);
            }

          chem_ent_vignette_sp->setPos(x, y);
        }
      else
        {
          column = 0;
          m_rows++;

          x = m_leftMargin;
          y += m_requestedVignetteSize;

          chem_ent_vignette_sp->setPos(x, y);

          // We are at the beginning of a new line.
          QString text = QString("%1").arg(vignette_index + 1);

          QGraphicsTextItem *label = new QGraphicsTextItem(text, 0);

          scene()->addItem(label);

          label->setPos(0, y);

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Positioned label at:" << QPointF(0,y);

          m_labelList.append(label);
        }

      x += m_requestedVignetteSize;
      ++column;

      ++vignette_index;
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Exit positionVignettes.";

  return true;
}


int
SequenceEditorGraphicsView::drawSequence(bool reset)
{
  qDebug();

  // We have a pointer to the polymer for which we have to draw the
  // sequence in the scene. This is the first time the sequence is
  // drawn. So we must go through the creation of all the monomer
  // graphics items. However, we might have already a polymer
  // chemistry definition that holds a number of usable vignette renderer
  // instances.

  setCursor(Qt::WaitCursor);

  Q_ASSERT(msp_polymer);

  if(reset)
    {
      // We are asked to reset all the GraphicsSvgItem in the scene.

      QList<QGraphicsItem *> itemList = scene()->items();

      while(!itemList.isEmpty())
        delete(itemList.takeFirst());

      m_monomerVignettes.clear();
      m_labelList.clear();
    }

  mp_editorWnd->statusBar()->showMessage(tr("Rendering vignettes"));

  if(!renderAllMonomerVignettes())
    qFatal()
      << "Programming error. Failed to render all the Monomer vignettes.";

  mp_editorWnd->statusBar()->showMessage(tr("Scaling vignettes"));

  if(!scaleVignettes())
    qFatal()
      << "Programming error. Failed to scale all the Monomer vignettes.";

  mp_editorWnd->statusBar()->showMessage(tr("Positioning vignettes"));

  if(!positionVignettes())
    qFatal()
      << "Programming error. Failed to position all the Monomer vignettes.";

  mp_editorWnd->statusBar()->showMessage(tr("Done positioning vignettes"));

  renderCursor();
  m_selectionFirstIndex  = -1;
  m_selectionFirstPoint  = QPointF(0, 0);
  m_selectionSecondIndex = -1;
  m_selectionSecondPoint = QPointF(0, 0);
  positionCursor(0);

  updateSceneRect();

  updateVScrollBar();

  m_sequenceDrawn = true;

  setFocus();

  mp_editorWnd->statusBar()->clearMessage();
  mp_editorWnd->statusBar()->showMessage(tr("Done drawing sequence"), 3000);
  mp_editorWnd->progressBar()->setValue(1);

  setCursor(Qt::ArrowCursor);

  return 0;
}


bool
SequenceEditorGraphicsView::updateSequence()
{
  setCursor(Qt::WaitCursor);

  positionVignettes();

  positionCursor();

  updateSceneRect();

  updateVScrollBar();

  setCursor(Qt::ArrowCursor);

  return true;
}


// POLYMER SEQUENCE-MODIFYING FUNCTIONS ///////////////////////////////
bool
SequenceEditorGraphicsView::insertMonomerAtPoint(
  const libXpertMassCore::Monomer &monomer, bool freeze)
{
  // We need to craft a Uuid string that unambiguously will identify
  // th
  if(!insertMonomerAt(monomer, m_lastClickedVignette, freeze))
    qFatal() << "Programming error. Failed to insert Monomer at point.";

  mp_editorWnd->setWindowModified(true);
  mp_editorWnd->updateWindowTitle();

  return true;
}


bool
SequenceEditorGraphicsView::insertMonomerAt(
  const libXpertMassCore::Monomer &monomer, std::size_t index, bool freeze)
{
  if(index > msp_polymer->size())
    qFatal() << "Programming error.";

  // First of all, make sure we can insert the monomer in the Sequence. This
  // will provide us with a Uuid string that we'll use to identify the Monomer
  // that corresponds to the ChemEntVignette that we'll create for it.

  // Will allocate a std::shared_ptr starting from monomer.
  QString uuid = msp_polymer->getSequenceRef().insertMonomerAt(monomer, index);

  if(uuid.isEmpty())
    qFatal()
      << "Programming error. Failed to insert Monomer into Sequence at index:"
      << index;

  // Now get back the MonomerSPtr to render the vignette.
  libXpertMassCore::MonomerCstSPtr monomer_csp =
    msp_polymer->getSequenceRef().getMonomerForUuid(uuid);

  if(monomer_csp == nullptr || monomer_csp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  ChemEntVignetteSPtr monomer_vignette_sp =
    renderMonomerVignette(monomer_csp, uuid);

  if(monomer_vignette_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  m_monomerVignettes.emplace(m_monomerVignettes.begin() + index,
                             monomer_vignette_sp);

  scaleVignette(monomer_vignette_sp);

  ++m_lastClickedVignette;

  if(!freeze)
    {
      mp_editorWnd->setWindowModified(true);
      mp_editorWnd->updateWindowTitle();

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();

      updateSequence();

      QPointF pointF = vignetteLocation(m_lastClickedVignette);
      centerOn(pointF);

      scene()->update();
    }

  QScrollBar *hScrollBar = horizontalScrollBar();
  hScrollBar->setMinimum(0);
  hScrollBar->setValue(0);

  return true;
}


std::size_t
SequenceEditorGraphicsView::insertSequenceAtPoint(
  libXpertMassCore::Sequence &sequence)
{
  bool freeze       = sequence.size() > 10 ? true : false;
  std::size_t count = 0;

  for(libXpertMassCore::MonomerSPtr monomer_csp : sequence.getMonomersCstRef())
    {
      insertMonomerAtPoint(*monomer_csp, freeze);
      ++count;
    }

  if(freeze)
    {
      if(!mp_editorWnd->isWindowModified())
        {
          mp_editorWnd->setWindowModified(true);
          mp_editorWnd->updateWindowTitle();
        }

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();

      updateSequence();

      QPointF pointF = vignetteLocation(m_lastClickedVignette);
      centerOn(pointF);

      scene()->update();
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "hScrollBar.";
  QScrollBar *hScrollBar = horizontalScrollBar();
  hScrollBar->setMinimum(0);
  hScrollBar->setValue(0);

  return count;
}


bool
SequenceEditorGraphicsView::prepareMonomerRemovalAt(std::size_t index)
{
  if(index >= msp_polymer->size())
    qFatal() << "Programming error.";

  // We have to check if the monomer is cross-linked, if so first
  // uncross-link all its partners.

  qDebug() << "Preparing removal of monomer at index" << index;

  const libXpertMassCore::MonomerSPtr monomer_sp =
    msp_polymer->getSequenceRef().getMonomerSPtrAt(index);

  if(monomer_sp == nullptr)
    qFatal()
      << "Programming error. Failed to retrieve Monomer from its index.";

  qDebug() << "The Monomer at that index is:" << monomer_sp->getCode();

  // One monomer might be involved in more than one cross-link. Get
  // the list of the cross-links in which the monomer is involved.
  std::vector<std::size_t> cross_link_indices =
    msp_polymer->crossLinkIndicesInvolvingMonomer(monomer_sp);

  if(!cross_link_indices.size())
    {
      qDebug() << "There are no cross-links, just return true.";
      return true;
    }

  // At this point we know that the monomer is involved at least in
  // one crossLink. For each of the cross-links involving the
  // monomer, make sure we uncross-link all its partners.

  bool ok = false;

  qDebug() << "The Polymer says there is at least one cross-link involving the "
              "current monomer. Iterating in this/these cross-link/s.";

  for(std::size_t cross_link_index : cross_link_indices)
    {
      // Get the CrossLink that is at index in the Polymer
      // container of CrossLink. This CrossLink must involve
      // the monomer_sp Monomer.

      libXpertMassCore::CrossLinkSPtr cross_link_csp =
        msp_polymer->getCrossLinksCstRef().at(cross_link_index);

      qDebug() << "Iterating in cross-link index" << cross_link_index
               << "with cross-linker name:"
               << cross_link_csp->getCrossLinkerName();

      qDebug() << "Now going to iterate in each Monomer of this cross-link.";

      for(libXpertMassCore::MonomerCstSPtr monomer_csp :
          cross_link_csp->getMonomersCstRef())
        {
          std::size_t monomer_index =
            msp_polymer->getSequenceRef().monomerIndex(monomer_csp, ok);

          if(!ok)
            qFatal() << "Programming error.";

          QString uuid = msp_polymer->getUuidForCrossLink(
            std::const_pointer_cast<libXpertMassCore::CrossLink>(cross_link_csp));

          qDebug() << "Iterating in Monomer" << monomer_csp->getCode()
                   << "at index:" << monomer_index
                   << "with cross-link uuid in the Polymer:" << uuid;

          qDebug() << "Now going to un-cross-link the vignette at this index "
                      "and for this uuid.";

          ok = uncrossLinkVignetteAt(monomer_index, uuid);

          if(!ok)
            return false;
        }
    }

  return true;
}


// Returns true if one Monomer was removed, false otherwise.
bool
SequenceEditorGraphicsView::removeMonomerAt(std::size_t index, bool freeze)
{
  qDebug() << "Asking to remove Monomer at index" << index;

  if(index >= msp_polymer->size())
    qFatal() << "Programming error. Out of bounds error.";

  if(!msp_polymer->size())
    return false;

  if(!prepareMonomerRemovalAt(index))
    qFatal() << "Programming error.";

  if(!msp_polymer->removeMonomerAt(index))
    qFatal() << "Programming error.";

  // Get the pointer to the corresponding monomer vignette, so that we
  // can remove it from the scene.

  removeMonomerVignetteAt(index);

  if(!freeze)
    {
      if(!mp_editorWnd->isWindowModified())
        {
          mp_editorWnd->setWindowModified(true);
          mp_editorWnd->updateWindowTitle();
        }

      mp_editorWnd->updateWholeSequenceMasses();
      mp_editorWnd->updateSelectedSequenceMasses();
    }

  return true;
}


std::size_t
SequenceEditorGraphicsView::removeSelectedOligomer()
{
  // We only can remove a selected oligomer in non-multi region
  // selection mode. However, just to make sure, we use the lastly
  // selected region.

  libXpertMassCore::IndexRangeCollection index_range_collection;

  bool selectionPresent = selectionIndices(index_range_collection);

  if(!selectionPresent)
    return 0;

  libXpertMassCore::IndexRange *index_range =
    index_range_collection.getRangesCstRef().back();

  qDebug() << "Going to remove selected oligomer sequence region:"
           << index_range->m_start << "--" << index_range->m_stop;

  return removeSequenceRegion(index_range->m_start, index_range->m_stop);
}

std::size_t
SequenceEditorGraphicsView::removeSequenceRegion(std::size_t start,
                                                 std::size_t stop)
{
  std::size_t count       = 0;
  std::size_t local_start = 0;
  std::size_t local_stop  = 0;

  local_start = std::min(start, stop);
  local_stop  = std::max(start, stop);

  if(local_stop >= msp_polymer->size())
    qFatal() << "Programming error.";

  // If the number of monomers to remove is greater than 10, then
  // freeze the sequence display.
  bool freeze = local_stop - local_start > 10 ? true : false;

  qDebug() << "Now starting iteration between indices:" << local_stop << "to"
           << local_start << "all included and in reverse";

  std::size_t index = local_stop;

  while(index >= local_start)
    {
      qDebug() << "Iterating at index" << index << "to ask for its remvoval.";

      if(!removeMonomerAt(index, freeze))
        qFatal() << "Programming error.";

      ++count;

      // Now decrement index. There is a caveat, however: index is std::size_t,
      // that is, if it is 0 and we remove 1, then, it becomes
      // 18446744073709551615 and we continue iterating. So test for 0 here.

      if(!index)
        break;

      --index;
    }

  resetSelection();

  m_lastClickedVignette = local_start;

  mp_editorWnd->updateWholeSequenceMasses();
  mp_editorWnd->updateSelectedSequenceMasses();

  mp_editorWnd->setWindowModified(true);
  mp_editorWnd->updateWindowTitle();

  updateSequence();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);

  // Return the number of monomers removed.
  return count;
}


////////////////////////////// SLOTS ///////////////////////////////
void
SequenceEditorGraphicsView::vScrollBarActionTriggered(int action)
{
  // Actions are QAbstractSlider::SliderSingleStepAdd,
  // SliderSingleStepSub, SliderPageStepAdd, SliderPageStepSub,
  // SliderToMinimum, SliderToMaximum, and SliderMove.

  QScrollBar *vScrollBar = verticalScrollBar();
  int curPos             = vScrollBar->sliderPosition();

  if(action == QAbstractSlider::SliderSingleStepAdd)
    vScrollBar->setSliderPosition(curPos + m_requestedVignetteSize);
  else if(action == QAbstractSlider::SliderPageStepAdd)
    vScrollBar->setSliderPosition(curPos + rect().height());

  if(action == QAbstractSlider::SliderSingleStepSub)
    vScrollBar->setSliderPosition(curPos - m_requestedVignetteSize);
  else if(action == QAbstractSlider::SliderPageStepSub)
    vScrollBar->setSliderPosition(curPos - rect().height());
}


bool
SequenceEditorGraphicsView::requestVignetteSize(int size)
{
  // Be careful that this function can be called by the parent
  // sequence editor window, while the sequence editor is being
  // initialized(readSettings()). If the sequence was never drawn,
  // we just return avec having set the m_requestedVignetteSize value
  // that will be used during the sequence drawing.

  m_requestedVignetteSize = size;

  if(!m_sequenceDrawn)
    return true;

  if(!scaleVignettes())
    return false;

  if(!positionVignettes())
    return false;

  scaleCursor();

  positionCursor();

  mpa_selection->reselectRegions();

  updateSceneRect();

  updateVScrollBar();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);

  return true;
}


int
SequenceEditorGraphicsView::lastClickedVignette()
{
  return m_lastClickedVignette;
}


//////////////////// EVENT HANDLING FUNCTIONS ///////////////////

void
SequenceEditorGraphicsView::focusOutEvent([[maybe_unused]] QFocusEvent *event)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "focusOutEvent";

  m_kbdShiftDown                  = false;
  m_kbdCtrlDown                   = false;
  m_kbdAltDown                    = false;
  m_ongoingKeyboardMultiSelection = false;
}


void
SequenceEditorGraphicsView::resizeEvent(QResizeEvent *event)
{
  // Be careful that this function can be called during setting up of
  // the widget, even if no sequence was drawn, and thus a number of
  // variables are not properly initialized. Thus, if no sequence was
  // drawn, we have to return.

  if(!m_sequenceDrawn)
    return;

  QGraphicsView::resizeEvent(event);

  positionVignettes();

  mpa_selection->reselectRegions();

  positionCursor();

  updateSceneRect();

  updateVScrollBar();

  QPointF pointF = vignetteLocation(m_lastClickedVignette);
  centerOn(pointF);
}


void
SequenceEditorGraphicsView::paintEvent(QPaintEvent *event)
{
  QGraphicsView::paintEvent(event);
}


void
SequenceEditorGraphicsView::mousePressEvent(QMouseEvent *event)
{
  mp_editorWnd->getsFocus();

  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());

  int index = vignetteIndex(pointF);

  if(index < 0)
    {
      event->accept();
      return;
    }

  if(static_cast<std::size_t>(index) > msp_polymer->size())
    {
      event->accept();
      return;
    }

  m_lastClickedVignette = index;

  bool multiRegionSelection = mp_editorWnd->isMultiRegionSelection();
  bool multiSelectionRegion = mp_editorWnd->isMultiSelectionRegion();

  if(event->buttons() & Qt::LeftButton)
    {
      if(!m_kbdCtrlDown || !multiRegionSelection)
        mpa_selection->deselectRegions();

      if(m_kbdShiftDown)
        {
          // Usually, in text editors, when the cursor is somewhere
          // and that the user presses the shift key while clicking
          // the mouse in some other place, it is considered that the
          // user wanted to select the text between the first cursor
          // position and the newly pointed position.  Thus we set the
          // second selection index to the present value.

          m_selectionSecondPoint = pointF;

          // Immediately draw the selection rectangle and set the
          // pressed position to be the next round's first selection
          // point.

          // qDebug() << __FILE__ << __LINE__
          //          << "m_selectionFirstPoint: "
          //          << m_selectionFirstPoint
          //          << "m_selectionSecondPoint: "
          //          << m_selectionSecondPoint;


          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);
        }

      m_selectionFirstPoint  = pointF;
      m_selectionSecondPoint = pointF;

      positionCursor(index);

      // Not required, really, seems.
      // m_mouseDragging = true;
    }

  if(event->buttons() & Qt::MiddleButton)
    {
      mpa_selection->deselectRegions();

      mp_editorWnd->clipboardPaste(QClipboard::Selection);
    }

  //   qDebug() <<__FILE__ << __LINE__
  //      << QTime::currentTime()
  //      << "mousePressEvent"
  // 	    << "selectionFirstIndex:" << m_selectionFirstIndex
  // 	    << "selectionSecondIndex:" << m_selectionSecondIndex
  // 	    << "m_selectionFirstPoint" << m_selectionFirstPoint
  // 	    << "m_selectionSecondPoint" << m_selectionSecondPoint;

  mp_editorWnd->updateSelectedSequenceMasses();

  event->accept();
}


void
SequenceEditorGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
  mp_editorWnd->getsFocus();

  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());

  int index = vignetteIndex(pointF);

  if(index > -1)
    m_lastClickedVignette = index;
  else
    {
      event->accept();
      return;
    }

  m_ongoingMouseMultiSelection = false;

  //   qDebug() <<__FILE__ << __LINE__
  // << "m_ongoingMouseMultiSelection set to:" <<
  //       m_ongoingMouseMultiSelection;


  //   qDebug() << "mouseReleaseEvent"
  // 	    << QTime::currentTime()
  //      << "selectionFirstIndex:" << m_selectionFirstIndex
  // 	    << "selectionSecondIndex:" << m_selectionSecondIndex
  // 	    << "m_selectionFirstPoint" << m_selectionFirstPoint
  // 	    << "m_selectionSecondPoint" << m_selectionSecondPoint;

  //    mpa_selection->debugSelectionPutStdErr();

  // Finally update the selection clipboard
  mp_editorWnd->clipboardCopy(QClipboard::Selection);

  event->accept();
}

void
SequenceEditorGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
  // qDebug();

  mp_editorWnd->getsFocus();

  // If there is no sequence, then nothing to do.
  if(!msp_polymer->size())
    {
      event->accept();
      return;
    }


  // We have to map to the whole scene coordinates
  QPointF pointF = mapToScene(event->pos());


  int index = vignetteIndex(pointF);

  // qDebug() << QTime::currentTime() << "mouseMoveEvent" << "index:" << index;

  if(index < 0)
    {
      event->accept();
      return;
    }

  if(static_cast<std::size_t>(index) > msp_polymer->size())
    {
      event->accept();
      return;
    }

  if(static_cast<std::size_t>(index) + 1 < msp_polymer->size())
    mp_editorWnd->updateMonomerPosition(index + 1);
  else
    mp_editorWnd->updateMonomerPosition(msp_polymer->size());

  bool multiRegionSelection = mp_editorWnd->isMultiRegionSelection();
  bool multiSelectionRegion = mp_editorWnd->isMultiSelectionRegion();

  if(event->buttons() & Qt::LeftButton)
    {
      m_selectionSecondPoint = pointF;


#if Q_DEBUG
      int firstPointIndex  = vignetteIndex(m_selectionFirstPoint);
      int secondPointIndex = vignetteIndex(m_selectionSecondPoint);

      qDebug() << "SelectRegion with indices:" << firstPointIndex
               << m_selectionFirstPoint << secondPointIndex
               << m_selectionSecondPoint;
#endif

      if(!m_kbdCtrlDown)
        {
          mpa_selection->deselectRegions();

          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);
        }
      else
        {
          // We should be incrementing the last selected region, and
          // not adding larger regions on top of the ones previously
          // selected. Thus remove the lastly selected region and
          // update the selected region.

          if(m_ongoingMouseMultiSelection)
            {
              mpa_selection->deselectLastRegion();
            }

          mpa_selection->selectRegion(m_selectionFirstPoint,
                                      m_selectionSecondPoint,
                                      multiRegionSelection,
                                      multiSelectionRegion);

          m_ongoingMouseMultiSelection = true;
        }

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "positioning cursor at index:" << index;

      positionCursor(index);

      if(event->pos().y() >= rect().height() - (m_requestedVignetteSize / 4))
        vScrollBarActionTriggered(QAbstractSlider::SliderSingleStepAdd);
      else if(event->pos().y() <= (m_requestedVignetteSize / 4))
        vScrollBarActionTriggered(QAbstractSlider::SliderSingleStepSub);

      mp_editorWnd->updateSelectedSequenceMasses();
    }

  event->accept();
}

} // namespace MassXpert

} // namespace MsXpS
