/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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 <massxpert/gui/RegionSelection.hpp>
#include <massxpert/gui/SequenceEditorGraphicsView.hpp>
#include <massxpert/nongui/Monomer.hpp>


namespace msXpSmassXpert
{


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

  m_startIndex = -1;
  m_endIndex   = -1;
}


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


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


void
RegionSelection::setCoordinates(int index1, int index2)
{
  if(index1 <= index2)
    {
      m_startIndex = index1;
      m_endIndex   = index2;
    }
  else
    {
      m_startIndex = index2;
      m_endIndex   = index1;
    }

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

  m_startPoint = mp_view->vignetteLocation(m_startIndex, NORTH_WEST);
  m_endPoint   = mp_view->vignetteLocation(m_endIndex + 1, SOUTH_WEST);

  mp_startMonomer = mp_view->polymer()->at(m_startIndex);
  mp_endMonomer   = mp_view->polymer()->at(m_endIndex);
}


void
RegionSelection::setCoordinates(const QPointF &point1, const QPointF &point2)
{
  // 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.

  int firstIndex  = mp_view->vignetteIndex(point1);
  int secondIndex = mp_view->vignetteIndex(point2);

  // qDebug() << __FILE__ << __LINE__
  //          << "firstIndex:" << firstIndex
  //          << "secondIndex:" << secondIndex;

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

  if(firstIndex <= secondIndex)
    {
      m_startIndex = firstIndex;
      m_endIndex   = secondIndex;
    }
  else
    {
      m_startIndex = secondIndex;
      m_endIndex   = firstIndex;
    }

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

  // 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_endIndex:" << m_endIndex;

  m_startPoint = mp_view->vignetteLocation(m_startIndex, NORTH_WEST);
  m_endPoint   = mp_view->vignetteLocation(m_endIndex, 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_endIndex - 1. However, we only do this decrement if endIndex
  // is at least 1, otherwise we'd have a negative m_endIndex, and
  // this makes the call mp_endMonomer = mp_view->polymer()->at
  //(m_endIndex); crash the program.

  if(m_endIndex)
    --m_endIndex;


  // Now, if the sequence selection is performed from right of the
  // last monomer in the sequence, m_startIndex would be >=
  // mp_view->polymer()->size(), and this would make the call to
  // mp_view->polymer()->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 >= mp_view->polymer()->size())
    {
      if(m_startIndex)
        --m_startIndex;
    }

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

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

  mp_startMonomer = mp_view->polymer()->at(m_startIndex);

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

  mp_endMonomer = mp_view->polymer()->at(m_endIndex);
}


const Monomer *
RegionSelection::startMonomer() const
{
  return mp_startMonomer;
}


const Monomer *
RegionSelection::endMonomer() const
{
  return mp_endMonomer;
}


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


int
RegionSelection::endIndex() const
{
  return m_endIndex;
}


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.

  setCoordinates(m_startIndex, m_endIndex);

  return drawMark();
}


int
RegionSelection::drawMark(const QPointF &point1, const QPointF &point2)
{
  setCoordinates(point1, point2);

  return drawMark();
}


int
RegionSelection::drawMark(int index1, int index2)
{
  setCoordinates(index1, index2);

  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_endPoint.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_endPoint.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_endPoint.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_endPoint.x() - leftMargin);

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

      mark = new QGraphicsRectItem(*(rect));

      mark->setRect(leftMargin,
                    m_endPoint.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_endPoint.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_endPoint.x() - leftMargin);
      height = requestedVignetteSize;

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

      mark = new QGraphicsRectItem(*(rect));

      mark->setRect(leftMargin,
                    m_endPoint.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_endIndex)
        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_endIndex && index2 >= m_startIndex)
    return true;

  return false;
}


Coordinates *
RegionSelection::coordinates()
{
  Coordinates *coordinates = new Coordinates(m_startIndex, m_endIndex);
  return coordinates;
}


void
RegionSelection::debugSelectionIndicesPutStdErr()
{
  qDebug() << __FILE__ << __LINE__ << "RegionSelection: [" << m_startIndex
           << "--" << m_endIndex << "]";
}


} // namespace msXpSmassXpert
