/* 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 "RegionSelection.hpp"
#include "SequenceEditorGraphicsView.hpp"
#include <libXpertMass/Monomer.hpp>


namespace MsXpS
{

namespace massxpert
{


  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 libXpertMass::Monomer *
  RegionSelection::startMonomer() const
  {
    return mp_startMonomer;
  }


  const libXpertMass::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;
  }


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


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


} // namespace massxpert

} // namespace MsXpS
