/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.filomace.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


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


/////////////////////// Local includes
#include "regionSelection.hpp"
#include "sequenceEditorGraphicsView.hpp"
#include "monomer.hpp"


namespace massXpert
{


  RegionSelection::RegionSelection(SequenceEditorGraphicsView *view)
    : mp_view(view), m_rectangleCount(3)
  {
    Q_ASSERT(mp_view);
    
    //    m_rectangleCount = 3;
    
    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, MXT_NORTH_WEST);
    m_endPoint = mp_view->vignetteLocation(m_endIndex + 1, MXT_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, MXT_NORTH_WEST);
    m_endPoint = mp_view->vignetteLocation(m_endIndex, MXT_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 massXpert

