/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - MassXpert, model polymer chemistries and simulate mass spectrometric data;
 * - MineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Qt includes


/////////////////////// Local includes
#include "RegionSelection.hpp"
#include "SequenceEditorGraphicsView.hpp"
#include <MsXpS/libXpertMassCore/Monomer.hpp>


namespace MsXpS
{

namespace MassXpert
{


RegionSelection::RegionSelection(SequenceEditorGraphicsView *view)
  : mp_view(view)
{
  Q_ASSERT(mp_view);
}


RegionSelection::~RegionSelection()
{
  eraseMark();
}


SequenceEditorGraphicsView *
RegionSelection::view()
{
  return mp_view;
}


void
RegionSelection::setIndexRange(int start_index, int stop_index)
{
  m_startIndex = start_index;
  m_stopIndex  = stop_index;

  if(m_startIndex > m_stopIndex)
    std::swap(m_startIndex, m_stopIndex);

  // We have to increment m_stopPoint by one unit, because the index
  // passed as argument is the "real" index, while we need a
  // position to compute the m_stopPoint.

  m_startPoint = mp_view->vignetteLocation(m_startIndex, NORTH_WEST);
  m_stopPoint  = mp_view->vignetteLocation(m_stopIndex + 1, SOUTH_WEST);

  mcsp_startMonomer =
    mp_view->getPolymer()->getSequenceCstRef().getMonomerCstSPtrAt(
      m_startIndex);
  mcsp_stopMonomer =
    mp_view->getPolymer()->getSequenceCstRef().getMonomerCstSPtrAt(m_stopIndex);
}


void
RegionSelection::setIndexRange(const QPointF &start_point,
                               const QPointF &stop_point)
{
  // Convert the point data into monomer indices and then back to
  // points that will be used to compute precise point coordinates
  // to actually draw the mark when asked.

  m_startIndex = mp_view->vignetteIndex(start_point);
  m_stopIndex  = mp_view->vignetteIndex(stop_point);

  // We want the indices to be in the increasing order and the
  // points also.

  if(m_startIndex > m_stopIndex)
    std::swap(m_startIndex, m_stopIndex);

  // qDebug() << __FILE__ << __LINE__
  //          << "Before checking for (m_startIndex < 0) : "
  //          << "m_startIndex:" << m_startIndex
  //          << "m_stopIndex:" << m_stopIndex;

  // If m_startIndex is less than 0, then set it to 0.
  if(m_startIndex < 0)
    m_startIndex = 0;

  // qDebug() << __FILE__ << __LINE__
  //          << "m_startIndex:" << m_startIndex
  //          << "m_stopIndex:" << m_stopIndex;

  m_startPoint = mp_view->vignetteLocation(m_startIndex, NORTH_WEST);
  m_stopPoint  = mp_view->vignetteLocation(m_stopIndex, SOUTH_WEST);

  // Because this region selection might be used for other scopes
  // than simple drawing of the selection on the sequence editor we
  // have to provide for the real end selection index, that is
  // m_stopIndex - 1. However, we only do this decrement if endIndex
  // is at least 1, otherwise we'd have a negative m_stopIndex, and
  // this makes the call msp_stopMonomer = mp_view->getPolymer()->at
  //(m_stopIndex); crash the program.

  if(m_stopIndex)
    --m_stopIndex;


  // Now, if the sequence selection is performed from right of the
  // last monomer in the sequence, m_startIndex would be >=
  // mp_view->getPolymer()->size(), and this would make the call to
  // mp_view->getPolymer()->at(m_startIndex); crash the program. So
  // before willing to identificate which MxpMonomer * is the first
  // monomer in the selection, that is the last monomer of the
  // sequence, we should make the following check. Drawing of the
  // selection will be ok, because all the related work has been
  // done already.

  if(m_startIndex >= static_cast<int>(mp_view->getPolymer()->size()))
    {
      if(m_startIndex)
        --m_startIndex;
    }

  // For the same reason above, the call to at(m_stopIndex) has to
  // be performed after the --m_stopIndex; instruction above.

  qDebug() << "m_startIndex:" << m_startIndex << "m_stopIndex:" << m_stopIndex;

  mcsp_startMonomer =
    mp_view->getPolymer()->getSequenceCstRef().getMonomerCstSPtrAt(
      m_startIndex);

  mcsp_stopMonomer =
    mp_view->getPolymer()->getSequenceCstRef().getMonomerCstSPtrAt(m_stopIndex);
}


const libXpertMassCore::MonomerCstSPtr
RegionSelection::getStartMonomer() const
{
  return mcsp_startMonomer;
}


const libXpertMassCore::MonomerCstSPtr
RegionSelection::getStopMonomer() const
{
  return mcsp_stopMonomer;
}


int
RegionSelection::getStartIndex() const
{
  return m_startIndex;
}


int
RegionSelection::getStopIndex() const
{
  return m_stopIndex;
}


int
RegionSelection::redrawMark()
{
  eraseMark();

  // Force the recalculation of the [start--end] QPointF's with the
  // unchanged indices. This function is typically called when the
  // sequence editor window is resized or when the size of the
  // vignettes is changed... Thus, the indices are not changed, what
  // change are the positions of the vignettes as described by the
  // QPointF member data.

  setIndexRange(m_startIndex, m_stopIndex);

  return drawMark();
}


int
RegionSelection::drawMark(const QPointF &start_point, const QPointF &stop_point)
{
  setIndexRange(start_point, stop_point);

  return drawMark();
}


int
RegionSelection::drawMark(int start_index, int stop_index)
{
  setIndexRange(start_index, stop_index);

  return drawMark();
}


// Returns the number of rectangle graphics items created.
int
RegionSelection::drawMark()
{
  QColor color(100, 100, 100, 127);
  QBrush brush(color, Qt::SolidPattern);
  QPen pen(Qt::NoPen);

  int requestedVignetteSize = mp_view->requestedVignetteSize();
  int leftMargin            = mp_view->leftMargin();
  int columns               = mp_view->columns();
  int width                 = 0;
  int height                = 0;

  int selectedRowCount = static_cast<int>((m_stopPoint.y() - m_startPoint.y()) /
                                          requestedVignetteSize);

  if(selectedRowCount == 1)
    {
      // The two vignettes are located on the same line, this is the
      // easiest situation, we just draw a single rectangle to cover
      // the region.

      QRectF *rect = new QRectF();
      m_rectangleList.append(rect);

      QGraphicsRectItem *mark = new QGraphicsRectItem(*(rect));

      mark->setRect(m_startPoint.x(),
                    m_startPoint.y(),
                    m_stopPoint.x() - m_startPoint.x(),
                    requestedVignetteSize);

      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);

      return m_markList.size();
    }
  else if(selectedRowCount == 2)
    {
      // Can we deal with the selection with a single two-row
      // rectangle?

      if(m_startPoint.x() == leftMargin &&
         m_stopPoint.x() == leftMargin + columns * requestedVignetteSize)
        {
          // This is like having two full rows selected : a single
          // scene()-wide rectangle over two rows.

          width = leftMargin + columns * requestedVignetteSize;

          QRectF *rect = new QRectF();
          m_rectangleList.append(rect);

          QGraphicsRectItem *mark = new QGraphicsRectItem(*(rect));

          mark->setRect(m_startPoint.x(),
                        m_startPoint.y(),
                        width,
                        selectedRowCount * requestedVignetteSize);
          mark->setPos(0, 0);
          mark->setZValue(10);

          mark->setBrush(brush);
          mark->setPen(pen);

          mp_view->scene()->addItem(mark);

          m_markList.append(mark);

          return m_markList.size();
        }

      // We have to draw two rectangles.

      // First row.

      width = static_cast<int>(leftMargin + columns * requestedVignetteSize -
                               m_startPoint.x());

      QRectF *rect = new QRectF();
      m_rectangleList.append(rect);

      QGraphicsRectItem *mark = new QGraphicsRectItem(*(rect));

      mark->setRect(
        m_startPoint.x(), m_startPoint.y(), width, requestedVignetteSize);
      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);

      // Second row.

      width = static_cast<int>(m_stopPoint.x() - leftMargin);

      rect = new QRectF();
      m_rectangleList.append(rect);

      mark = new QGraphicsRectItem(*(rect));

      mark->setRect(leftMargin,
                    m_stopPoint.y() - requestedVignetteSize,
                    width,
                    requestedVignetteSize);

      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);
    }
  else if(selectedRowCount > 2)
    {
      // At this point we have to draw three distinct rectangles as
      // the first point of first row does not begin at left of the
      // row and the last point of last line does not end at end of
      // row. There is room for optimization here. I know it.

      // First row

      width  = static_cast<int>(leftMargin + columns * requestedVignetteSize -
                               m_startPoint.x());
      height = requestedVignetteSize;

      QRectF *rect = new QRectF();
      m_rectangleList.append(rect);

      QGraphicsRectItem *mark = new QGraphicsRectItem(*(rect));

      mark->setRect(m_startPoint.x(), m_startPoint.y(), width, height);
      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);

      // Middle full-width region

      width  = columns * requestedVignetteSize;
      height = static_cast<int>(m_stopPoint.y() - m_startPoint.y() -
                                2 * requestedVignetteSize);

      rect = new QRectF();
      m_rectangleList.append(rect);

      mark = new QGraphicsRectItem(*(rect));

      mark->setRect(
        leftMargin, m_startPoint.y() + requestedVignetteSize, width, height);

      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);

      // Last row

      width  = static_cast<int>(m_stopPoint.x() - leftMargin);
      height = requestedVignetteSize;

      rect = new QRectF();
      m_rectangleList.append(rect);

      mark = new QGraphicsRectItem(*(rect));

      mark->setRect(leftMargin,
                    m_stopPoint.y() - requestedVignetteSize,
                    width,
                    requestedVignetteSize);

      mark->setPos(0, 0);
      mark->setZValue(10);

      mark->setBrush(brush);
      mark->setPen(pen);

      mp_view->scene()->addItem(mark);

      m_markList.append(mark);
    }

  return m_markList.size();
}


int
RegionSelection::eraseMark()
{
  while(!m_rectangleList.isEmpty())
    delete m_rectangleList.takeFirst();

  // Be careful, the scene might have been deleted! Remember, the
  // scene get deleted before the view!  No problem, because the
  // items that were set to the scene do belong to the scene and
  // thus are freed along with the scene.

  QGraphicsScene *scene = mp_view->scene();

  if(!scene)
    return 1;

  while(!m_markList.isEmpty())
    {
      QGraphicsRectItem *mark = m_markList.takeFirst();

      scene->removeItem(mark);

      delete mark;
    }

  return 1;
}


bool
RegionSelection::encompassing(int index1, int index2)
{
  // We should be able to tell if *this region encompasses the
  // region contained in [index1--index2]

  if(index1 == -1 && index2 == -1)
    return false;

  if(index2 == -1)
    {
      if(index1 >= m_startIndex && index1 <= m_stopIndex)
        return true;
      else
        return false;
    }

  int temp;

  if(index1 > index2)
    {
      temp   = index2;
      index2 = index1;
      index1 = temp;
    }

  // The tested region([index1--index2] must start before the end
  // of *this region and must end before the start of *this region.

  if(index1 <= m_stopIndex && index2 >= m_startIndex)
    return true;

  return false;
}


libXpertMassCore::IndexRange
RegionSelection::getIndexRange()
{
  return libXpertMassCore::IndexRange(m_startIndex, m_stopIndex);
}

QString
RegionSelection::getIndicesAsText() const
{
  return QString("[%1-%2]").arg(m_startIndex).arg(m_stopIndex);
}

} // namespace MassXpert
} // namespace MsXpS
