/****************************************************************************
**
** Implementation of crossvc' diff tool.
**
** Based on code created by Bernd Gehrmann
** Copyright (C) 1999 Bernd Gehrmann
** bernd@physik.hu-berlin.de
**
**
**
** Copyright (C) 2000-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>,
**                         Wim Delvaux <wim.delvaux@chello.be>,
**                         Jose Hernandez <joseh@tesco.net>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"

#include <iostream>
#include <stdlib.h>
#include <ctype.h> 
#include <qpainter.h>
#include <qscrollbar.h>
#include <qstyle.h>
#include <qregexp.h>
#include <qapplication.h>

#include "diffview.h"

class DiffViewItem
{
public:
   DiffView::DiffInfoList diffInfoList;
   DiffView::DiffType type;
   bool inverted;
   int no;
   int pixelwidth;
};
    
class DiffViewItemList : public QPtrList<DiffViewItem>
{
protected:
   virtual int compareItems(QCollection::Item item1, QCollection::Item item2);
};


int DiffViewItemList::compareItems(QCollection::Item item1, QCollection::Item item2)
{
   return (static_cast<DiffViewItem*>(item1)->no
	 == static_cast<DiffViewItem*>(item2)->no)? 0 : 1;
}


static const int BORDER = 7;


DiffView::DiffView(QWidget *parent /*=0*/, const char *name /*=0*/, WFlags f /*=0*/)
   : QtTableView(parent, name, f), linenos(false), marker(false), m_blockScrollPos(false)
{
   adjustLeftMargin();
    
   setFocusPolicy(QWidget::WheelFocus);
   setNumCols(1 + (linenos ? 1 : 0) + (marker ? 1 : 0));
   setNumRows(0);
   setTableFlags(Tbl_autoVScrollBar | Tbl_autoHScrollBar |
	 Tbl_smoothVScrolling);
   setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
   setBackgroundMode(PaletteBase);
   setWFlags(WResizeNoErase);

   setCellWidth(0);
   adjustCellHeight();

   items = new DiffViewItemList;
   TableSize = 0;

   items->setAutoDelete(true);
   m_ScrollBarMinPos = -1;
}


void DiffView::setFont(const QFont &f)
{
   QtTableView::setFont(f);
   adjustLeftMargin();
   adjustCellHeight();
}


void DiffView::adjustLeftMargin()
{
   QFontMetrics fm(font());
   
   m_markerTypeWidth = QMAX(QMAX(fm.width("-"), fm.width("+")),
	 fm.width("|"))+2*BORDER;
   m_lineNbTypeWidth = fm.width("99999");
}


void DiffView::adjustCellHeight()
{
   QFontMetrics fm(font());
   setCellHeight(fm.lineSpacing());
}

   
void DiffView::setLineNumbers(bool On)
{
   linenos = On;
   setNumCols(1 + (linenos ? 1 : 0) + (marker ? 1 : 0));
}


void DiffView::setMarkers(bool On)
{
   marker = On;
   setNumCols(1 + (linenos ? 1 : 0) + (marker ? 1 : 0));
}


DiffView::~DiffView()
{
   delete items;
}


void DiffView::setPartner(DiffView *other)
{
   partner = other;
   if (partner)
      {
	 connect( verticalScrollBar(), SIGNAL(valueChanged(int)),
	       SLOT(yPositionChanged(int)) );
	 connect( verticalScrollBar(), SIGNAL(sliderMoved(int)),
	       SLOT(yPositionChanged(int)) );
	 connect( horizontalScrollBar(), SIGNAL(valueChanged(int)),
	       SLOT(xPositionChanged(int)) );
	 connect( horizontalScrollBar(), SIGNAL(sliderMoved(int)),
	       SLOT(xPositionChanged(int)) );
      }
}


void DiffView::wheelEvent( QWheelEvent *e) {
   if (viewRect().contains(e->pos())) {
   int d = e->delta();
   int v = verticalScrollBar()->value();
   int l = verticalScrollBar()->lineStep();
   l *= QApplication::wheelScrollLines();
   if (d > 0) {
      d = v - l;
   } else {
      d = v + l;
   }
   verticalScrollBar()->setValue(d);
   e->accept();
   } else {
      e->ignore();
   }
}

void DiffView::keyPressEvent ( QKeyEvent * e) {
   switch (e->key()) {
      case Qt::Key_Up: {
	 int v = verticalScrollBar()->value();
	 v -= verticalScrollBar()->lineStep();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Down: {
	 int v = verticalScrollBar()->value();
	 v += verticalScrollBar()->lineStep();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Left: {
	 int v = horizontalScrollBar()->value();
	 v -= horizontalScrollBar()->lineStep();
	 horizontalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Right: {
	 int v = horizontalScrollBar()->value();
	 v += horizontalScrollBar()->lineStep();
	 horizontalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Prior: {
	 int v = verticalScrollBar()->value();
	 v -= verticalScrollBar()->pageStep();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Next: {
	 int v = verticalScrollBar()->value();
	 v += verticalScrollBar()->pageStep();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_Home: {
	 int v = verticalScrollBar()->minValue();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      case Qt::Key_End: {
	 int v = verticalScrollBar()->maxValue();
	 verticalScrollBar()->setValue(v);
	 e->accept();
	 break;
      }
      default: {
	 break;
      }
   }
}


void DiffView::yPositionChanged(int val)
{
   if (partner)
      partner->setYOffset(val);
}


void DiffView::xPositionChanged(int val)
{
   if ( !m_blockScrollPos && partner && partner->horizontalScrollBar() && partner->horizontalScrollBar()->isVisible() ) {
      int w = horizontalScrollBar()->maxValue() - horizontalScrollBar()->minValue();
      int wp = partner->horizontalScrollBar()->maxValue() - partner->horizontalScrollBar()->minValue();
      int value = (int)((double)wp*(double)val/(double)w);
      partner->m_blockScrollPos = true;
      partner->horizontalScrollBar()->setValue(value);
      partner->m_blockScrollPos = false;
   }
}


void DiffView::recalcTableSize( DiffViewItem * NewItem ) {
   int maxline = 0;

   if( NewItem ) {
      // just update
      maxline = NewItem->pixelwidth;
   } else {
      DiffViewItem * it;
      // find largest line
      for( unsigned int i = 0 ; i < items->count(); i ++ ) {
	 it = items->at(i);
	 if( it->pixelwidth > maxline )
	    maxline = it->pixelwidth;
      }
   }

   if( maxline >= TableSize ) {
      TableSize = maxline;
      updateTableSize();
   }
}


void DiffView::setCenterOffset(int offset)
{
   if (!rowIsVisible(offset))
      {
	 int visiblerows = viewHeight()/cellHeight(0);
	 setTopCell( QMAX(0, offset - visiblerows/2) );
      }
}


void DiffView::addLine(QString line, DiffList diffList, DiffType type, int no)
{
   DiffViewItem *item = new DiffViewItem;
   QFontMetrics fm(font());

   int index = line.length()-1;
   while( line[index].isSpace() ) {
      index --;
   } 
   //contains trailing spaces
   line.truncate( index+1 );

   item->type = type;
   item->no = no;
   item->inverted = false;

   int lastPos = 0;
   int fmFullSize = 0;
   int fullWidth = 0;
   DiffList::iterator it;
   for (it = diffList.begin(); it != diffList.end(); ++it) {
      DiffInfo diffInfo;
      diffInfo.txt = line.mid( (*it).pos,(*it).len);
      diffInfo.type = (*it).type;
      diffInfo.fmSize = fm.width( diffInfo.txt);
      fmFullSize += diffInfo.fmSize;
      fullWidth += (*it).len;
      lastPos = (*it).pos + (*it).len;
      item->diffInfoList.append(diffInfo);
   }
   item->pixelwidth = fmFullSize;

   items->append(item);
   recalcTableSize( item );
   setNumRows(numRows()+1);
}


int DiffView::count()
{
   return items->count();
}


int DiffView::findLine(int lineno)
{
   int offset;
   DiffViewItem tmp;
   tmp.no = lineno;
   if ( (offset = items->find(&tmp)) == -1)
      {
	 exit( 234);
	 return -1;
      }
   return offset;
}


void DiffView::findNextDiff (void)
{
   if (numRows() < 1) return;
    
   QPtrListIterator<DiffViewItem> it(*items);
   DiffType type;

   /* Look for the next change and move the view to it */
   for (it+=topCell(), type=it.current()->type; it.current(); ++it)
      {
	 if (!it.current()) return;

	 if (type != it.current()->type)
	    {
	       type = it.current()->type;

	       if ((type != Neutral) && (type != Unchanged))
		  {
		     setTopCell (items->findRef (it.current()));
		     return;
		  }
	    }
      }

   /* Scroll to the bottom of the view if no further changes were found */
   setTopCell (numRows ());
}

void DiffView::findPrevDiff (void)
{
   if (numRows() < 1) return;
    
   QPtrListIterator<DiffViewItem> it(*items);
   DiffType type;
   /* Look for the previous change and move the view to it */
   for (it+=topCell(), type=it.current()->type; it.current(); --it)
      {
	 if (type != it.current()->type)
	    {
	       type = it.current()->type;

	       if ((type != Neutral) && (type != Unchanged))
		  {
		     /* Adjust so we show the begining of the
		      * change rather than the end.
		      */
		     while (type == it.current()->type) {
			--it;
			if (it == 0) {
			   setTopCell (0);
			   return;
			}
		     }
		     setTopCell (items->findRef (++it));
		     return;
		  }
	    }
      }

   /* Scroll to the top of the view if no further changes were found */
   setTopCell (0);
}


void DiffView::setInverted(int lineno, bool inverted)
{
   int offset;
   if ( (offset = findLine(lineno)) != -1)
      items->at(offset)->inverted = inverted;
}


void DiffView::setCenterLine(int lineno)
{
   int offset;
   if ( (offset = findLine(lineno)) != -1)
      setCenterOffset(offset);
}


int DiffView::cellWidth(int col) {

   if (linenos && (col == 0) ) {
      // col 0 contains linenos in this case
      return m_lineNbTypeWidth;
   } else {
      if (marker && (col <= 1) ) return m_markerTypeWidth;
      else return TableSize;
   }
}

QSize DiffView::sizeHint() const
{
   QFontMetrics fm(font());
   return QSize( 4*fm.width("0123456789"), fm.lineSpacing()*8 );
}


void DiffView::paintCell(QPainter *p, int row, int col)
{
   DiffViewItem *item = items->at(row);

   int width = cellWidth(col);
   int height = cellHeight();

   QColor backgroundColor;
   bool inverted;
   int align;
   int innerborder;
   QString str;
    
   bool isDiffLine = FALSE;
   if (col == 0 && linenos) {
      backgroundColor = QColor(222, 222, 222);
      inverted = false;
      align = AlignLeft;
      innerborder = 0;
      if (item->no == -1)
	 str = "+++++";
      else
	 str.setNum(item->no);
   } else if (marker && (col == 0 || col == 1)) {

      //backgroundColor = lightGray;   --> lightgray is wrongly mapped under IRIX
      backgroundColor = QColor(222, 222, 222);
       
      inverted = false;
      align = AlignRight;
      innerborder = BORDER;
      str = (item->type==Change)? "|"
	 : (item->type==Insert)? "+" 
	 : (item->type==Delete)? "-" : " ";
   } else {
      backgroundColor =
	 (item->type==Change)? QColor(237, 190, 190)
	 : (item->type==Insert)? QColor(190, 190, 237)
	 : (item->type==Delete)? QColor(190, 237, 190)
	 //: (item->type==Neutral)? gray : white;   --> gray is wrongly mapped under IRIX
	 : (item->type==Neutral)? QColor(170, 170, 170) : QColor(255,255,255);
      inverted = item->inverted;
      align = AlignLeft;
      innerborder = 0;
      isDiffLine = TRUE;
   }

   if (inverted) {
      p->setPen(backgroundColor);
      //backgroundColor = black; --> black also
      backgroundColor = QColor(90, 90, 90);
   } else
      p->setPen(black);

   p->fillRect(0, 0, width, height, backgroundColor);

   if (isDiffLine) {
      DiffInfoList::iterator it;
      QPen oriPen = p->pen();
      QPen changePen(QColor(255,0,0));//changed
      QPen insertPen(QColor(0,0,255));//added
      QPen deletePen(QColor(80,255,80));//removed
      int offset = 0;
      for (it = item->diffInfoList.begin(); it != item->diffInfoList.end(); ++it) {
	 switch ( (*it).type) {
	    case Insert: {
	       p->setPen(insertPen);
	       break;
	    }
	    case Delete: {
	       p->setPen(deletePen);
	       break;
	    }
	    case Change: {
	       p->setPen(changePen);
	       break;
	    }
	    default: {
	       p->setPen(oriPen);
	       break;
	    }
	 }
	 p->drawText(innerborder+offset, 0, width-2*innerborder-offset, height, align, (*it).txt);
	 offset += (*it).fmSize;
      }
   } else {
      p->drawText(innerborder, 0, width-2*innerborder, height, align, str);
   }

}    

void DiffView::resizeEvent( QResizeEvent *e) {
   QtTableView::resizeEvent(e);
   updateScrollBars();
   if (verticalScrollBar()->isVisible()) {
      QRect rect = verticalScrollBar()->style().querySubControlMetrics(QStyle::CC_ScrollBar,
	    verticalScrollBar(),
	    QStyle::SC_ScrollBarGroove);
      m_ScrollBarMinPos = rect.top();
      m_ScrollBarMaxPos = rect.height();
   }
}

void DiffView::adjustScrollBarOffsets(int &from, int &height) {
   updateScrollBars();

   if (verticalScrollBar()->isVisible()) {
      if (m_ScrollBarMinPos == -1) {

	 QRect rect = verticalScrollBar()->style().querySubControlMetrics(QStyle::CC_ScrollBar,
	       verticalScrollBar(),
	       QStyle::SC_ScrollBarGroove);
	 m_ScrollBarMinPos = rect.top();
	 m_ScrollBarMaxPos = rect.height();
      }
      from += m_ScrollBarMinPos;
      height = m_ScrollBarMaxPos;
   }
}

