/* ====================================================================
 * Copyright (c) 2003-2008  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 "TextGlueWidget.h"
#include "sublib/Line.h"
#include "sublib/TextModel.h"
#include "sublib/NullTextModel.h"
#include "sublib/ColorStorage.h"
#include "ColorId.h"
#include "PainterSetup.h"
#include "DiffInfoModel.h"
#include "TextPositionCalculator.h"
#include "ScrollPositionCalculator.h"
#include "Conflict.h"
#include "ConflictMarkerWidget.h"

// sys
#include <stdio.h>
#include <algorithm>
#include "util/max.h"

// qt
#include <QtGui/QPaintEvent>
#include <QtGui/QLinearGradient>

static const int dashLinePad = 1;

QColor TextGlueWidget::_bgDash(255,255,255);
QColor TextGlueWidget::_fgDash(150,150,150);

QColor TextGlueWidget::_bg(255,255,255);
QColor TextGlueWidget::_fg(230,230,230);

QColor TextGlueWidget::_fgMerge(160,170,195);



static NullTextModel nullModel;

TextGlueWidget::TextGlueWidget( QWidget *parent, const char *name )
: QWidget( parent, name ), _model(0), _left(&nullModel), _right(&nullModel),
  _lines(0), _flags(0), _ypos(0)
{
  _lnColumns = 3;
  _lnLeftPad  = 3;
  _lnRightPad = 3;

  setSizePolicy( QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Expanding) );
  setBackgroundMode( Qt::NoBackground );
}

TextGlueWidget::~TextGlueWidget()
{
}

void TextGlueWidget::setModel( DiffInfoModel* model )
{
  _model = model;
}

void TextGlueWidget::setModel( TextModel* left, TextModel* right )
{
  _left  = left;
  _right = right;

  updateGeometry();
  update();
}


QColor TextGlueWidget::getColor( DiffInfo& di, bool left )
{
  if( di.getDiffNumber() == _currDiff )
  {
    switch( di.getMergeType() )
    {
    case msModified:
      {
        if( left )
        {
          return _fgMerge;
        }
        return QColor(0,0,0);
      }
    case msLatest:
      {
        if( ! left )
        {
          return _fgMerge;
        }
        return QColor(0,0,0);
      }
    //case msNotMerged:
    //case msOriginal:
    default:
      {
        return QColor(0,0,0);
      }
    }
  }
  return _fg;
}

const QColor& getConflictBgColor( ConflictType t )
{
  switch( t )
  {
    case ctConflictAll:
    case ctConflictAllEmpty:
    {
      return ColorStorage::getColor(ColorConflictBgAll);
    }
    case ctConflictLatestModified:
    case ctConflictLatestModifiedEmpty:
    {
      return ColorStorage::getColor(ColorConflictBgLatestModified);
    }
    case ctConflictModifiedLatest:
    case ctConflictModifiedLatestEmpty:
    {
      return ColorStorage::getColor(ColorConflictBgModifiedLatest);
    }
    case ctConflictOriginal:
    case ctConflictOriginalEmpty:
    {
      return ColorStorage::getColor(ColorConflictBgOriginal);
    }
    case ctNop:
    {
      return ColorStorage::getColor(ColorNopBg);
    }
    case ctConflictAllMerged:
    case ctConflictLatestModifiedMerged:
    case ctConflictModifiedLatestMerged:
    case ctConflictOriginalMerged:
    case ctConflictAllEmptyMerged:
    case ctConflictLatestModifiedEmptyMerged:
    case ctConflictModifiedLatestEmptyMerged:
    case ctConflictOriginalEmptyMerged:
    {
      return ColorStorage::getColor(ColorMergedBg);
    }
    default:
    {
      return ColorStorage::getColor(ColorNormalBg);
    }
  }
}

const QColor& getMergeBgColor( MergeType t )
{
  return ColorStorage::getColor(ColorMergedBg);
}

const QColor& getBgColor( ConflictType ct, MergeType mt, bool left )
{
  if( mt == msModified && left )
    return getMergeBgColor(mt);
  else if( mt == msLatest && ! left ) 
    return getMergeBgColor(mt);
  else
    return getConflictBgColor(ct);
}


void TextGlueWidget::paintEvent( QPaintEvent *e )
{
  QRect pr = e->rect(); 

  QPainter pp(this);
  //pp.setPen(_fg); // grey...
  pp.setBackgroundColor(_bg);
  pp.save();

  QFontMetrics m(font());
  TextPositionCalculator tpc( m, pr, 0, _ypos );
  int topLine = tpc.getTopLine();
  int botLine = tpc.getBottomLine();
  int lnDefWidth = sizeHint().width();
  sc::Size maxLine = std::max(_left->getLineCnt(),_right->getLineCnt());
  
  // clear line backgrounds
  for( int curLine = topLine; curLine <= botLine; curLine++ )
  {
    pp.eraseRect( 0, tpc.getLineY(curLine), lnDefWidth, m.height() );
  }

  // draw left dash line background
  pp.setPen( _bgDash );
  pp.drawLine( 0, pr.y(), 0, pr.y()+pr.height() );

  // draw right dash line background
  // right dash line
  pp.setPen( _bgDash );
  pp.drawLine( lnDefWidth-dashLinePad, pr.y(), lnDefWidth-dashLinePad, pr.y()+pr.height() );
  
  
  
  // draw connection background
  int block = -1;
  for( int curLine = topLine; curLine <= botLine; curLine++ )
  {
    if( !_model )
      break;

    if( (sc::Size)curLine >= maxLine )
      break;    
    
    Line lline = _left->getLine(curLine);
    Line rline = _right->getLine(curLine);

    const BlockInfo& lb = _model->getInfo( lline.getBlockNr() ).getBlockInfo();
    
    // already drawn or common block?
    if( block == lline.getBlockNr() || lline.isCommon() )
      continue;
    
    block = lline.getBlockNr();

    svn::Offset sl = lb.getStart() + lb.getOriginalLength();
    svn::Offset sr = lb.getStart() + lb.getModifiedLength();
    
    if( sl == lb.getStart() )
      sl++;
    
    if( sr == lb.getStart() )
      sr++;

    int top = tpc.getLineY((int)lb.getStart());
    int botLeft  = tpc.getLineY((int)sl);
    int botRight = tpc.getLineY((int)sr);
    
    QPainterPath path;
    path.moveTo(0,top);
    path.lineTo(0,botLeft);
    path.cubicTo(lnDefWidth-5,botLeft, 5,botRight, lnDefWidth,botRight);
    path.lineTo(lnDefWidth,top);
    path.lineTo(0,top);
          
    DiffInfo& info = _model->getInfo(block);
    QColor lcolor = getBgColor( lline.getType(), info.getMergeType(), true );
    QColor rcolor = getBgColor( rline.getType(), info.getMergeType(), false );
    
    QLinearGradient gradient(0,0,lnDefWidth,0);
    gradient.setColorAt(0, lcolor );
    gradient.setColorAt(1, rcolor );
    QBrush brush(gradient);
    
    pp.setRenderHint(QPainter::Antialiasing);
    pp.setPen(Qt::NoPen);
    pp.setBrush(brush);
    pp.drawPath(path);  
  }

  for( int curLine = topLine; curLine <= botLine; curLine++ )
  {
    if( ! _model )
    {
      continue;
    }

    // the block stuff below doesn't like an invalid line but we need the clean
    // the line at least.
    sc::Size maxLine = std::max(_left->getLineCnt(),_right->getLineCnt());
    if( (sc::Size)curLine >= maxLine )
    {
      continue;
    }

    Line       lline    = _left->getLine(curLine);
    DiffInfo&  linfo    = _model->getInfo( lline.getBlockNr() );
    const BlockInfo& lb = linfo.getBlockInfo();
    QColor     lcolor   = getColor( linfo, true );

    Line       rline    = _right->getLine(curLine);
    DiffInfo&  rinfo    = _model->getInfo( rline.getBlockNr() );
    //const BlockInfo& rb = rinfo.getBlockInfo();
    QColor     rcolor   = getColor( rinfo, false );

    if( lb.getStart() == curLine && (lline.isDifference() || rline.isDifference()) )
    {
      if( linfo.getDiffNumber() == _currDiff || rinfo.getDiffNumber() == _currDiff )
      {
        QFont font = pp.font();
        font.setBold(true);
        pp.setFont(font);
        pp.setPen(QColor(0,0,0));
      }
      else
      {
        pp.setPen(QColor(100,100,100)/*_fg*/);
      }

      // draw text centered
      char lnBuf[32];
      int  lnLength  = sprintf( lnBuf, "%d", linfo.getDiffNumber() );
      int  lnWidth   = m.width( lnBuf, lnLength );
      pp.drawText( (lnDefWidth/2)-(lnWidth/2), tpc.getTextY(curLine), lnBuf, lnLength );
    }
  }
  
  pp.restore();
  

  pp.setPen( _fgDash );
  for( int d = pr.y()+(_ypos + pr.y())%2; d < pr.y()+pr.height(); d+=2 )
  {
    pp.drawPoint( 0, d );
  }

  pp.setPen( _fgDash );
  for( int d = pr.y()+(_ypos + pr.y())%2; d < pr.y()+pr.height(); d+=2 )
  {
    pp.drawPoint( lnDefWidth-dashLinePad, d );
  }
}

void TextGlueWidget::setScrollPosY( int ypos )
{
  ScrollPositionCalculator spc;
  int oy = _ypos;
  int ny = spc.calcPos( oy, ypos, height(), sizeHint().height() );
  _ypos = ny;

  if( oy != _ypos )
  {
    super::scroll( 0, oy - _ypos );
  }
  update();
}

void TextGlueWidget::setLineCnt( sc::Size l )
{
  _lines = l;
}

void TextGlueWidget::setActiveDiff( int num )
{
  _currDiff = num;
  update();
}

QSize TextGlueWidget::sizeHint() const
{
  QFontMetrics m(font());
  sc::Size width  = m.width('x')*_lnColumns + _lnLeftPad + _lnRightPad + 2*dashLinePad;
  sc::Size height = m.height()  *_lines;
  return QSize( (int)width, (int)height );
}

#if 0
int TextGlueWidget::width() const
{
  return super::width();
}

int TextGlueWidget::height() const
{
  return super::height();
}
#endif

#if 0
  DiffInfos infos = _model->getInfos();
  DiffInfo  info;

  for( DiffInfos::iterator it = infos.begin(); it != infos.end(); it++ )
  {
    const DiffInfo& info = (*it);

    if( info.getBlockInfo().getStart() < topLine )
    {
      info =
    }
  }
    for( DiffInfos::iterator it = infos.begin(); it != infos.end(); it++ )
    {
      const DiffInfo& info = (*it);

      if( info.getType() != ctCommon )
      {
      }
    }
#endif
