/* ====================================================================
 * Copyright (c) 2003-2006, Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */


// sc
#include "config.h"
#include "Diff3Widget.h"
#include "DiffInfoModel.h"
#include "SingleTextWidget.h"
#include "DoubleTextWidget.h"
#include "TextWidget.h"
#include "settings/FontSettings.h"
#include "sublib/Line.h"
#include "sublib/TextModel.h"
#include "sublib/SplitLayout.h"
#include "util/String.h"

// qt
#include <qapplication.h>
#include <qscrollbar.h>
#include <qpushbutton.h>
#include <qsplitter.h>
#include <qvbox.h>
#include <qlabel.h>

// sys
#include <assert.h>
#include <vector>
#include <iostream>
#include <stdio.h>
#include <algorithm>



Diff3Widget::Diff3Widget( FontSettings* fs, QWidget *parent, const char *name )
: super( parent, name ), _diffInfo(0)
{
  setCaption( _q("Subcommander <diff3 - submerge>") );
  setIconText( _q("Subcommander <icontext>") );

  QFont font = fs->getEditorFont();
  QApplication::setFont( font, false, "TextWidget" );
  QApplication::setFont( font, false, "TextLineNrWidget" );
  QApplication::setFont( font, false, "TextGlueWidget" );

  setSizePolicy( QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding) );
  //setFont( QFont("Courier New",10,QFont::Normal,false/*QFont::Unicode*/) );

  QGridLayout* bl = new QGridLayout(this);
  {
    QWidget*      vs  = new QWidget(this);
    VSplitLayout* vsl = new VSplitLayout(vs);
    _splitMerge = vsl;
    bl->addWidget(vs,0,0);
    {
      QWidget*      hs  = new QWidget(vs);
      HSplitLayout* hsl = new HSplitLayout(hs);
      _splitOrg = hsl;
      vsl->addWidgetOne( hs, false );
      {
        _single = new SingleTextWidget(hs);
        _single->setHScrollBarOff( SingleTextWidget::sboDisable );
        _single->setAcceptDrops(true);
        hsl->addWidgetOne( _single, true, 1 );

        _double = new DoubleTextWidget(hs);
        _double->setHScrollBarOff( SingleTextWidget::sboDisable );
        _double->setAcceptDrops(true);
        hsl->addWidgetTwo( _double, false, 2 );
      }

      _merged = new SingleTextWidget(vs);
      _merged->enableSelection( true );
      _merged->setEditable( true );
      vsl->addWidgetTwo( _merged, true );
    }

#if 0
    // experimental vertical line display
    QFontMetrics m( font() );

    QVBox* f1 = new QVBox(this);
    f1->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    f1->setMargin(1);
    f1->setSpacing(1);
    f1->setSizePolicy( QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed) );
    bl->addWidget(f1,1,0);
    {
      TextWidget* tw1 = new TextWidget(f1);
      tw1->setFixedHeight( m.height() );

      TextWidget* tw2 = new TextWidget(f1);
      tw2->setFixedHeight( m.height() );
    }
#endif
  }

  // sync v scrollbars
  connect(_single->getVScrollBar(), SIGNAL(valueChanged(int)), SLOT(vsbChange(int)) );
  connect(_double->getVScrollBar(), SIGNAL(valueChanged(int)), SLOT(vsbChange(int)) );

  // h scrollbars
  connect( _single, SIGNAL(updatedScrollBars()), SLOT(hsbChange()) );
  connect( _double, SIGNAL(updatedScrollBars()), SLOT(hsbChange()) );

  // block selection
  connect( _single->getText(), SIGNAL(blockChanged(int)), this, SLOT(blockChangeO(int)) );
  connect( _double->getLeftText(), SIGNAL(blockChanged(int)), this, SLOT(blockChangeL(int)) );
  connect( _double->getRightText(), SIGNAL(blockChanged(int)), this, SLOT(blockChangeR(int)) );
  connect( _merged->getText(), SIGNAL(blockChanged(int)), this, SLOT(blockChangeM(int)) );
}

Diff3Widget::~Diff3Widget()
{
}

void Diff3Widget::setModel( TextModel* original, TextModel* modified, TextModel* latest )
{
  _single->setModel( original );
  _double->setModel( modified, latest );
}

void Diff3Widget::setModel( DiffInfoModel* info )
{
  _double->setModel(info);
  _diffInfo = info;
}

void Diff3Widget::setMergeModel( TextModel* merged )
{
  _merged->setModel(merged);
}

void Diff3Widget::setLeftLabel( const sc::String& l )
{
  _single->setLabel(l);
}

void Diff3Widget::setCenterLabel( const sc::String& l )
{
  _double->setLeftLabel(l);
}

void Diff3Widget::setRightLabel( const sc::String& l )
{
  _double->setRightLabel(l);
}

// todo move to "model"

// selected original block
void Diff3Widget::blockChangeO(int b)
{
  setActiveDiffBlock(b);

  TextModel* om = _single->getText()->getModel();
  TextModel* mm = _double->getLeftText()->getModel();
  TextModel* lm = _double->getRightText()->getModel();
  TextModel* me = _merged->getText()->getModel();

  //TextModel* om = _diffInfo->getModel( DiffInfoModel::dmOriginal );
  //TextModel* mm = _diffInfo->getModel( DiffInfoModel::dmModified );
  //TextModel* lm = _diffInfo->getModel( DiffInfoModel::dmLatest );
  //TextModel* me = _diffInfo->getModel( DiffInfoModel::dmMerged );

  _double->getLeftText()->clearBlockSelection();
  _double->getRightText()->clearBlockSelection();

  int line = me->replaceBlock(b,om);
  _merged->setModel(me);
  // TODO that is getting strange here...
  // i think jumpToLine needs a better implementation, without
  // the processEvents() it often jumps to the wrong position
  // when a block get clicked. If it clicked a second time it
  // jumps again. See also nextDiff() in MainWindow, which has
  // the same problem.
  //_merged->updateGeometry();
  //qApp->processEvents();

  DiffInfo& di = _diffInfo->getInfo(b);
  di.setMergeType( msOriginal );

  _merged->getText()->setBlockSelection(b);
  _merged->update();
  _merged->jumpToLine(line-2);
}

// selected left block
void Diff3Widget::blockChangeL(int b)
{
  setActiveDiffBlock(b);

  TextModel* om = _single->getText()->getModel();
  TextModel* mm = _double->getLeftText()->getModel();
  TextModel* lm = _double->getRightText()->getModel();
  TextModel* me = _merged->getText()->getModel();

  //TextModel* om = _diffInfo->getModel( DiffInfoModel::dmOriginal );
  //TextModel* mm = _diffInfo->getModel( DiffInfoModel::dmModified );
  //TextModel* lm = _diffInfo->getModel( DiffInfoModel::dmLatest );
  //TextModel* me = _diffInfo->getModel( DiffInfoModel::dmMerged );

  _single->getText()->clearBlockSelection();
  _double->getRightText()->clearBlockSelection();

  int line = me->replaceBlock(b,mm);
  _merged->setModel(me);
  //_merged->updateGeometry();
  //qApp->processEvents();

  DiffInfo& di = _diffInfo->getInfo(b);
  di.setMergeType( msModified );

  _merged->getText()->setBlockSelection(b);
  _merged->update();
  _merged->jumpToLine(line-2);
}

// select right block
void Diff3Widget::blockChangeR(int b)
{
  setActiveDiffBlock(b);

  TextModel* om = _single->getText()->getModel();
  TextModel* mm = _double->getLeftText()->getModel();
  TextModel* lm = _double->getRightText()->getModel();
  TextModel* me = _merged->getText()->getModel();

  //TextModel* om = _diffInfo->getModel( DiffInfoModel::dmOriginal );
  //TextModel* mm = _diffInfo->getModel( DiffInfoModel::dmModified );
  //TextModel* lm = _diffInfo->getModel( DiffInfoModel::dmLatest );
  //TextModel* me = _diffInfo->getModel( DiffInfoModel::dmMerged );

  _single->getText()->clearBlockSelection();
  _double->getLeftText()->clearBlockSelection();

  int line = me->replaceBlock(b,lm);
  _merged->setModel(me);
  //_merged->updateGeometry();
  //qApp->processEvents();

  DiffInfo& di = _diffInfo->getInfo(b);
  di.setMergeType( msLatest );

  _merged->getText()->setBlockSelection(b);
  _merged->update();
  _merged->jumpToLine(line-2);
}

// selected merge block
void Diff3Widget::blockChangeM(int b)
{
  setActiveDiffBlock(b);

  TextModel* om = _single->getText()->getModel();
  TextModel* mm = _double->getLeftText()->getModel();
  TextModel* lm = _double->getRightText()->getModel();
  TextModel* me = _merged->getText()->getModel();

  //TextModel* om = _diffInfo->getModel( DiffInfoModel::dmOriginal );
  //TextModel* mm = _diffInfo->getModel( DiffInfoModel::dmModified );
  //TextModel* lm = _diffInfo->getModel( DiffInfoModel::dmLatest );
  //TextModel* me = _diffInfo->getModel( DiffInfoModel::dmMerged );

  const DiffInfo& di = _diffInfo->getInfo(b);

  switch( di.getMergeType() )
  {
  case msOriginal:
    {
      _single->getText()->setBlockSelection(b);
      _double->getLeftText()->clearBlockSelection();
      _double->getRightText()->clearBlockSelection();
      break;
    }
  case msModified:
    {
      _single->getText()->clearBlockSelection();
      _double->getLeftText()->setBlockSelection(b);
      _double->getRightText()->clearBlockSelection();
      break;
    }
  case msLatest:
    {
      _single->getText()->clearBlockSelection();
      _double->getLeftText()->clearBlockSelection();
      _double->getRightText()->setBlockSelection(b);
      break;
    }
  case msNotMerged:
    {
      _single->getText()->setBlockSelection(b);
      _double->getLeftText()->setBlockSelection(b);
      _double->getRightText()->setBlockSelection(b);
      break;
    }
  }

  // TODO we need a way to get the line correction value
  // from one place so we don't repeat it all the time....
  jumpToLine( di.getBlockInfo().getStart()-2 );
}

void Diff3Widget::vsbChange(int y)
{
  _single->getVScrollBar()->setValue(y);
  _double->getVScrollBar()->setValue(y);
}

void Diff3Widget::hsbChange()
{
  QScrollBar* sh = _single->getHScrollBar();
  QScrollBar* dh = _double->getHScrollBar();

  bool sd = ! _single->isVisible();
  bool dd = ! _double->isVisible();

  if( _single->getHScrollBarOff() == TextViewWidget::sboDisable )
  {
    sd |= ! sh->isEnabled();
    dd |= ! dh->isEnabled();
  }
  else
  {
    sd |= ! sh->isVisible();
    dd |= ! dh->isVisible();
  }

  if( sd && dd )
  {
    sh->hide();
    dh->hide();
  }
  else
  {
    sh->show();
    dh->show();
  }
}

void Diff3Widget::jumpToLine( int line )
{
  if( line < 0 )
  {
    line = 0;
  }

  _single->jumpToLine(line);
  _double->jumpToLine(line);
  //_merged->jumpToLine(line);
}

void Diff3Widget::jumpToBlock( int block )
{
  _single->jumpToBlock(block);
  _double->jumpToBlock(block);
  _merged->jumpToBlock(block);
  // set selection
  _merged->getText()->setBlockSelection(block);
}

void Diff3Widget::setActiveDiffBlock( int block )
{
  DiffInfo& bi = _diffInfo->getInfo(block);

  _double->setActiveDiff( bi.getDiffNumber() );
  _diffInfo->setActiveDiff( bi.getDiffNumber() );

  emit diffChanged( bi.getDiffNumber() );
}

void Diff3Widget::setActiveDiff( int num )
{
  _double->setActiveDiff(num);
}

void Diff3Widget::enableOriginal( bool enable, bool open )
{
  _splitOrg->enableHandle(enable);
  _splitOrg->jumpPos(open);
}
  
void Diff3Widget::enableMerged( bool enable, bool open )
{
  _splitMerge->enableHandle(enable);
  _splitMerge->jumpPos(open);
}

void Diff3Widget::wheelEvent( QWheelEvent* e )
{
  if( ! _diffInfo )
  {
    e->ignore();
    return;
  }

  //printf( "wheel delta: %d (%p)\n", e->delta(), e );

  int b = 0;

  if( e->delta() > 0 )
  {
    // forward  -> to screen
    if( _diffInfo->hasPrevDiff() )
    {
      b = _diffInfo->prevDiff();
    }
  }
  else
  {
    // backward -> to user
    if( _diffInfo->hasNextDiff() )
    {
      b = _diffInfo->nextDiff();
    }
  }

  if( b == 0 )
  {
    e->ignore();
    return;
  }

  jumpToBlock( b );
  setActiveDiff( _diffInfo->getActiveDiff() );

  emit diffChanged( _diffInfo->getActiveDiff() );

  e->accept();
}

void Diff3Widget::connectOriginalDrop( const QObject* receiver, const char* member )
{
  connect( _single->getText(), SIGNAL(fileDropped(const QString&)), receiver, member );
}

void Diff3Widget::connectModifiedDrop( const QObject* receiver, const char* member )
{
  connect( _double->getLeftText(), SIGNAL(fileDropped(const QString&)), receiver, member );
}

void Diff3Widget::connectLatestDrop( const QObject* receiver, const char* member )
{
  connect( _double->getRightText(), SIGNAL(fileDropped(const QString&)), receiver, member );
}


syntax highlighted by Code2HTML, v. 0.9.1