/* ====================================================================
 * Copyright (c) 2003-2009  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 "NullDiffInfoModel.h"
#include "settings/FontSettings.h"
#include "sublib/Line.h"
#include "sublib/TextModel.h"
#include "sublib/SplitLayout.h"
#include "sublib/Splitter.h"
#include "util/String.h"

// qt
#include <QtGui/QApplication>
#include <QtGui/QScrollBar>
#include <QtGui/QPushButton>
#include <QtGui/QLabel>
#include <QtGui/QWheelEvent>

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


static NullDiffInfoModel NullInfo;

Diff3Widget::Diff3Widget( FontSettings* fs, QWidget *parent, const char *name )
: super( parent, name ), _diffInfo(&NullInfo)
{
  setCaption( _q("submerge") );

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

  setSizePolicy( QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding) );

  QGridLayout* bl = new QGridLayout(this);
  bl->setMargin(0);
  {
    _splitMerge = new Splitter( this, Splitter::Last );
    _splitMerge->setOrientation( Qt::Vertical );
    bl->addWidget(_splitMerge, 1, 0, 1, 1);
    {
      _splitOrg = new Splitter(_splitMerge);
      {
        _single = new SingleTextWidget(_splitOrg);
        _single->setHScrollBarOff( SingleTextWidget::sboDisable );
        _single->setAcceptDrops(true);

        _double = new DoubleTextWidget(_splitOrg);
        _double->setHScrollBarOff( SingleTextWidget::sboDisable );
        _double->setAcceptDrops(true);
      }

      _merged = new SingleTextWidget(_splitMerge);
      _merged->enableSelection( true );
      _merged->setEditable( true );
      _splitMerge->addWidget(_merged);
    }
  }

  // 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();

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

  int line = me->replaceBlock(b,om);
  _merged->setModel(me);

  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();

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

  int line = me->replaceBlock(b,mm);
  _merged->setModel(me);

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

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

// 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();

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

  int line = me->replaceBlock(b,lm);
  _merged->setModel(me);

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

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

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

  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);
  _merged->update();
}

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);

  emit diffChanged(num);
}

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

void Diff3Widget::wheelEvent( QWheelEvent* e )
{
  if( shouldScrollNormal(e->modifiers()) )
    scrollNormal(e->delta());
  else
    scrollToDiff(e->delta());

  e->accept();
}

void Diff3Widget::scrollNormal( int delta )
{
  QScrollBar* sbSingle = _single->getVScrollBar();
  QScrollBar* sbDouble = _double->getVScrollBar();
  sbSingle->setValue(sbSingle->value()-delta);
  sbDouble->setValue(sbDouble->value()-delta);
}

bool Diff3Widget::shouldScrollNormal( Qt::KeyboardModifiers modifiers )
{
  if( modifiers == Qt::ShiftModifier )
    return true;

  if( _diffInfo->getDiffCnt() == 0 )
    return true;

  return false;
}

void Diff3Widget::scrollToDiff( int delta )
{
  if( delta > 0 )
    scrollToPrevDiff();
  else
    scrollToNextDiff();
}

void Diff3Widget::scrollToPrevDiff()
{
  if( !_diffInfo->hasPrevDiff() )
    return;

  jumpToBlock( _diffInfo->prevDiff() );
  setActiveDiff( _diffInfo->getActiveDiff() );
}

void Diff3Widget::scrollToNextDiff()
{
  if( !_diffInfo->hasNextDiff() )
    return;

  jumpToBlock( _diffInfo->nextDiff() );
  setActiveDiff( _diffInfo->getActiveDiff() );
}

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 );
}
