/**************************************************************************
**                              komparesplitter.cpp
**                              -------------------
**      begin                   : Wed Jan 14 2004
**      Copyright 2004-2005 Jeff Snyder  <jeff-webcvsspam@caffeinated.me.uk>
**      Copyright 2007-2011 Kevin Kofler <kevin.kofler@chello.at>
***************************************************************************/

/**************************************************************************
**
**   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 2 of the License, or
**   (at your option) any later version.
**
***************************************************************************/

// associated header
#include "komparesplitter.h"

// qt
#include <QtGui/QStyle>
#include <QtCore/QString>
#include <QtCore/QTimer>
#include <QtGui/QScrollBar>
#include <QtCore/QMap>
#include <QtGui/QSplitter>
#include <QtGui/QApplication>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtGui/QKeyEvent>
#include <QtGui/QGridLayout>
#include <QtGui/QResizeEvent>
#include <QtCore/QChildEvent>
#include <QtCore/QEvent>
#include <QtGui/QWheelEvent>

// kde
#include <kdebug.h>
#include <kapplication.h>
#include <kglobalsettings.h>

// kompare
#include "komparelistview.h"
#include "viewsettings.h"
#include "kompareconnectwidget.h"
#include "diffmodel.h"
#include "difference.h"

using namespace Diff2;

KompareSplitter::KompareSplitter( ViewSettings *settings, QWidget *parent ) :
	QSplitter( Qt::Horizontal, parent ),
	m_settings( settings )
{
	QFrame *scrollFrame = static_cast<QFrame *>(parent);

	// Set up the scrollFrame
	scrollFrame->setFrameStyle( QFrame::NoFrame | QFrame::Plain );
	scrollFrame->setLineWidth(scrollFrame->style()->pixelMetric(QStyle::PM_DefaultFrameWidth));
	QGridLayout *pairlayout = new QGridLayout(scrollFrame);
	pairlayout->setSpacing(0);
	pairlayout->setContentsMargins( 0, 0, 0, 0 );
	m_vScroll = new QScrollBar( Qt::Vertical, scrollFrame );
	pairlayout->addWidget( m_vScroll, 0, 1 );
	m_hScroll = new QScrollBar( Qt::Horizontal, scrollFrame );
	pairlayout->addWidget( m_hScroll, 1, 0 );

	new KompareListViewFrame(true, m_settings, this, "source");
	new KompareListViewFrame(false, m_settings, this, "destination");
	pairlayout->addWidget( this, 0, 0 );

	// set up our looks
	setLineWidth( style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) );

	setHandleWidth(50);
	setChildrenCollapsible( false );
	setFrameStyle( QFrame::NoFrame );
	setSizePolicy( QSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored ));
	setOpaqueResize( true );
	setFocusPolicy( Qt::WheelFocus );

	connect( this, SIGNAL(configChanged()), SLOT(slotConfigChanged()) );
	connect( this, SIGNAL(configChanged()), SLOT(slotDelayedRepaintHandles()) );
	connect( this, SIGNAL(configChanged()), SLOT(slotDelayedUpdateScrollBars()) );
	
	// scrolling
	connect( m_vScroll, SIGNAL(valueChanged(int)), SLOT(slotScrollToId(int)) );
	connect( m_vScroll, SIGNAL(sliderMoved(int)),  SLOT(slotScrollToId(int)) );
	connect( m_hScroll, SIGNAL(valueChanged(int)), SIGNAL(setXOffset(int)) );
	connect( m_hScroll, SIGNAL(sliderMoved(int)),  SIGNAL(setXOffset(int)) );

	m_scrollTimer=new QTimer(this);
	m_restartTimer = false;
	connect (m_scrollTimer, SIGNAL(timeout()), SLOT(timerTimeout()) );

	// we need to receive childEvents now so that d->list is ready for when
	// slotSetSelection(...) arrives
	kapp->sendPostedEvents(this, QEvent::ChildAdded);

	// init stuff
	slotUpdateScrollBars();
}

KompareSplitter::~KompareSplitter()
{
}

QSplitterHandle* KompareSplitter::createHandle()
{
	return new KompareConnectWidgetFrame(m_settings, this);
}

void KompareSplitter::slotDelayedRepaintHandles()
{
	QTimer::singleShot(0, this, SLOT(slotRepaintHandles()));
}

void KompareSplitter::slotRepaintHandles()
{
	const int end = count();
	for ( int i = 1; i < end; ++i )
		handle(i)->update();
}

void KompareSplitter::timerTimeout()
{
	if ( m_restartTimer )
		m_restartTimer = false;
	else
		m_scrollTimer->stop();

	slotDelayedRepaintHandles();

	emit scrollViewsToId( m_scrollTo );
	slotRepaintHandles();
	m_vScroll->setValue( m_scrollTo );
}

void KompareSplitter::slotScrollToId( int id )
{
	m_scrollTo = id;

	if( m_restartTimer )
		return;

	if( m_scrollTimer->isActive() )
	{
		m_restartTimer = true;
	}
	else
	{
		emit scrollViewsToId( id );
		slotRepaintHandles();
		m_vScroll->setValue( id );
		m_scrollTimer->start( 30 );
	}
}

void KompareSplitter::slotDelayedUpdateScrollBars()
{
	QTimer::singleShot( 0, this, SLOT( slotUpdateScrollBars() ) );
}

void KompareSplitter::slotUpdateScrollBars()
{
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		KompareListView* lv = listView(i);
		int minHScroll = minHScrollId();
		if (lv->contentsX() < minHScroll) {
			lv->setXOffset(minHScroll);
		}
	}

	int m_scrollDistance = m_settings->m_scrollNoOfLines * lineHeight();
	int m_pageSize = pageSize();

	if( needVScrollBar() )
	{
		m_vScroll->show();

		m_vScroll->blockSignals( true );
		m_vScroll->setRange( minVScrollId(), 
		                     maxVScrollId() );
		m_vScroll->setValue( scrollId() );
		m_vScroll->setSingleStep( m_scrollDistance );
		m_vScroll->setPageStep( m_pageSize );
		m_vScroll->blockSignals( false );
	}
	else
	{
		m_vScroll->hide();
	}

	if( needHScrollBar() )
	{
		m_hScroll->show();
		m_hScroll->blockSignals( true );
		m_hScroll->setRange( minHScrollId(), maxHScrollId() );
		m_hScroll->setValue( maxContentsX() );
		m_hScroll->setSingleStep( 10 );
		m_hScroll->setPageStep( minVisibleWidth() - 10 );
		m_hScroll->blockSignals( false );
	}
	else
	{
		m_hScroll->hide();
	}
}

void KompareSplitter::slotDelayedUpdateVScrollValue()
{
	QTimer::singleShot( 0, this, SLOT( slotUpdateVScrollValue() ) );
}

void KompareSplitter::slotUpdateVScrollValue()
{
	m_vScroll->setValue( scrollId() );
}

void KompareSplitter::keyPressEvent( QKeyEvent* e )
{
	//keyboard scrolling
	switch ( e->key() ) {
	case Qt::Key_Right:
	case Qt::Key_L:
		m_hScroll->triggerAction( QAbstractSlider::SliderSingleStepAdd );
		break;
	case Qt::Key_Left:
	case Qt::Key_H:
		m_hScroll->triggerAction( QAbstractSlider::SliderSingleStepSub );
		break;
	case Qt::Key_Up:
	case Qt::Key_K:
		m_vScroll->triggerAction( QAbstractSlider::SliderSingleStepSub );
		break;
	case Qt::Key_Down:
	case Qt::Key_J:
		m_vScroll->triggerAction( QAbstractSlider::SliderSingleStepAdd );
		break;
	case Qt::Key_PageDown:
		m_vScroll->triggerAction( QAbstractSlider::SliderPageStepAdd );
		break;
	case Qt::Key_PageUp:
		m_vScroll->triggerAction( QAbstractSlider::SliderPageStepSub );
		break;
	}
	e->accept();
	slotRepaintHandles();
}

void KompareSplitter::wheelEvent( QWheelEvent* e )
{
	if ( e->orientation() == Qt::Vertical )
	{
		if ( e->modifiers() & Qt::ControlModifier ) {
			if ( e->delta() < 0 ) // scroll down one page
				m_vScroll->triggerAction( QAbstractSlider::SliderPageStepAdd );
			else // scroll up one page
				m_vScroll->triggerAction( QAbstractSlider::SliderPageStepSub );
		} else {
			if ( e->delta() < 0 ) // scroll down
				m_vScroll->triggerAction( QAbstractSlider::SliderSingleStepAdd );
			else // scroll up
				m_vScroll->triggerAction( QAbstractSlider::SliderSingleStepSub );
		}
	}
	else
	{
		if ( e->modifiers() & Qt::ControlModifier ) {
			if ( e->delta() < 0 ) // scroll right one page
				m_hScroll->triggerAction( QAbstractSlider::SliderPageStepAdd );
			else // scroll left one page
				m_hScroll->triggerAction( QAbstractSlider::SliderPageStepSub );
		} else {
			if ( e->delta() < 0 ) // scroll to the right
				m_hScroll->triggerAction( QAbstractSlider::SliderSingleStepAdd );
			else // scroll to the left
				m_hScroll->triggerAction( QAbstractSlider::SliderSingleStepSub );
		}
	}
	e->accept();
	slotDelayedRepaintHandles();
}

/* FIXME: this should return/the scrollId() from the listview containing the
 * /base/ of the diff. but there's bigger issues with that atm.
 */
 
int KompareSplitter::scrollId()
{
	if(widget(0))
		return listView(0)->scrollId();
	return minVScrollId();
}

int KompareSplitter::lineHeight()
{
	if(widget(0))
		return listView(0)->fontMetrics().height();
	return 1;
}

int KompareSplitter::pageSize()
{
	if(widget(0)) {
		KompareListView *view = listView(0);
		return view->visibleHeight() - view->style()->pixelMetric( QStyle::PM_ScrollBarExtent );
	}
	return 1;
}

bool KompareSplitter::needVScrollBar()
{
	int pagesize = pageSize();
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		KompareListView *view = listView(i);
		if( view ->contentsHeight() > pagesize)
			return true;
	}
	return false;
}

int KompareSplitter::minVScrollId()
{

	int min = -1;
	int mSId;
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		mSId = listView(i)->minScrollId();
		if (mSId < min || min == -1)
			min = mSId;
	}
	return ( min == -1 ) ? 0 : min;
}

int KompareSplitter::maxVScrollId()
{
	int max = 0;
	int mSId;
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		mSId = listView(i)->maxScrollId();
		if ( mSId > max )
			max = mSId;
	}
	return max;
}

bool KompareSplitter::needHScrollBar()
{
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		KompareListView *view = listView(i);
		if ( view->contentsWidth() > view->visibleWidth() )
			return true;
	}
	return false;
}

int KompareSplitter::minHScrollId()
{
	// hardcode an offset to hide the tree controls
	return 6;
}

int KompareSplitter::maxHScrollId()
{
	int max = 0;
	int mHSId;
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		KompareListView *view = listView(i);
		mHSId = view->contentsWidth() - view->visibleWidth();
		if ( mHSId > max )
			max = mHSId;
	}
	return max;
}

int KompareSplitter::maxContentsX()
{
	int max = 0;
	int mCX;
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		mCX = listView(i)->contentsX();
		if ( mCX > max )
			max = mCX;
	}
	return max;
}

int KompareSplitter::minVisibleWidth()
{
	// Why the hell do we want to know this?
	// ah yes, it is because we use it to set the "page size" for horiz. scrolling.
	// despite the fact that *none* has a pgright and pgleft key :P
	// But we do have mousewheels with horizontal scrolling functionality,
	// pressing shift and scrolling then goes left and right one page at the time
	int min = -1;
	int vW;
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		vW = listView(i)->visibleWidth();
		if ( vW < min || min == -1 )
			min = vW;
	}
	return ( min == -1 ) ? 0 : min;
}

KompareListView* KompareSplitter::listView( int index )
{
	return static_cast<KompareListViewFrame*>(widget(index))->view();
}

KompareConnectWidget* KompareSplitter::connectWidget( int index )
{
	return static_cast<KompareConnectWidgetFrame*>(handle(index))->wid();
}

void KompareSplitter::slotSetSelection( const DiffModel* model, const Difference* diff )
{
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		connectWidget(i)->slotSetSelection( model, diff );
		listView(i)->slotSetSelection( model, diff );
		static_cast<KompareListViewFrame*>(widget(i))->slotSetModel( model );
	}

	slotDelayedRepaintHandles();
	slotDelayedUpdateScrollBars();
}

void KompareSplitter::slotSetSelection( const Difference* diff )
{
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		connectWidget(i)->slotSetSelection( diff );
		listView(i)->slotSetSelection( diff );
	}

	slotDelayedRepaintHandles();
	slotDelayedUpdateScrollBars();
}

void KompareSplitter::slotApplyDifference( bool apply )
{
	const int end = count();
	for ( int i = 0; i < end; ++i )
		listView(i)->slotApplyDifference( apply );
	slotDelayedRepaintHandles();
}

void KompareSplitter::slotApplyAllDifferences( bool apply )
{
	const int end = count();
	for ( int i = 0; i < end; ++i )
		listView(i)->slotApplyAllDifferences( apply );
	slotDelayedRepaintHandles();
	slotScrollToId( m_scrollTo ); // FIXME!
}

void KompareSplitter::slotApplyDifference( const Difference* diff, bool apply )
{
	const int end = count();
	for ( int i = 0; i < end; ++i )
		listView(i)->slotApplyDifference( diff, apply );
	slotDelayedRepaintHandles();
}

void KompareSplitter::slotDifferenceClicked( const Difference* diff )
{
	const int end = count();
	for ( int i = 0; i < end; ++i )
		listView(i)->setSelectedDifference( diff, false );
	emit selectionChanged( diff );
}

void KompareSplitter::slotConfigChanged()
{
	const int end = count();
	for ( int i = 0; i < end; ++i ) {
		KompareListView *view = listView(i);
		view->setFont( m_settings->m_font );
		view->update();
	}
}
#include "komparesplitter.moc"
