/* ====================================================================
 * 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 "Diff3.h"
#include "LineTokenizer.h"
#include "DiffBaton.h"
#include "DiffInfoTarget.h"
#include "DiffInfoModelImpl.h"
#include "sublib/Line.h"
#include "sublib/LineTarget.h"
#include "sublib/ConflictType.h"
#include "sublib/TextModelImpl.h"
#include "sublib/NullTextModel.h"
#include "Merge.h"
#include "svn/Diff.h"
#include "svn/Error.h"
#include "util/iconvstream.h"
#include "util/Error.h"

// apr
#include <apr_pools.h>

// sys
#include <assert.h>
#include <fstream>


// TODO fix code duplication with OutBaton
class OutBaton3 : public svn::OutputBaton
{
public:
  enum Source
  {
    srcOriginal = svn::Diff::srcOriginal,
    srcModified = svn::Diff::srcModified,
    srcLatest   = svn::Diff::srcLatest,
    srcAncestor = svn::Diff::srcAncestor,
    srcMax      = srcAncestor + 1
  };

  OutBaton3( Tokenizer** tokenizer, LineTarget** targets,
    DiffInfoTarget* infos, apr_pool_t *pool = 0 )
    : _tokenizer(tokenizer), _targets(targets), _infos(infos),
      _blockCnt(1), _blockStart(0)
  {
    assert(_tokenizer);
    assert(_targets);

    apr_status_t status = apr_pool_create(&_pool,pool);
    assert( status == APR_SUCCESS );
  }

  ~OutBaton3()
  {
    apr_pool_destroy(_pool);
  }


  // common lines in all datasources
  sc::Error* common( const svn::DiffOffsets& offs )
  {
    for( svn::Offset i = 1; i <= offs._originalLength; i++ )
    {
      addTextLine( srcOriginal, ctCommon );
      addTextLine( srcModified, ctCommon );
      addTextLine( srcLatest,   ctCommon );
    }

    _infos->addDiffInfo( DiffInfo(ctCommon,_blockCnt,createOffsets(offs._originalLength)) );

    _blockCnt++;
    return 0;
  }

  // original = latest != modified
  sc::Error* diffModified( const svn::DiffOffsets& offs )
  {
    bool originalConflictEmpty = offs._originalLength == 0;
    bool modifiedConflictEmpty = offs._modifiedLength == 0;
    bool latestConflictEmpty   = offs._latestLength   == 0;

    assert( offs._originalLength == offs._latestLength );
    svn::Offset max = 0;
    if( offs._originalLength > max ) max = offs._originalLength;
    if( offs._modifiedLength > max ) max = offs._modifiedLength;

    for( svn::Offset i = 1; i <= max; i++ )
    {
      if( i <= offs._originalLength )
      {
        addTextLine( srcOriginal, ctConflictLatestModified );
      }
      else
      {
        addControlLine( srcOriginal, ctConflictLatestModifiedEmpty, originalConflictEmpty );
        originalConflictEmpty = false;
      }

      if( i <= offs._modifiedLength )
      {
        addTextLine( srcModified, ctConflictAll );
      }
      else
      {
        addControlLine( srcModified, ctConflictAllEmpty, modifiedConflictEmpty );
        modifiedConflictEmpty = false;
      }

      if( i <= offs._latestLength )
      {
        addTextLine( srcLatest, ctConflictLatestModified );
      }
      else
      {
        addControlLine( srcLatest, ctConflictLatestModifiedEmpty, latestConflictEmpty );
        latestConflictEmpty = false;
      }
    }

    _infos->addDiffInfo( DiffInfo(ctConflictLatestModified,_blockCnt,createOffsets(max)) );

    _blockCnt++;
    return 0;
  }

  // original = modified != latest
  sc::Error* diffLatest( const svn::DiffOffsets& offs )
  {
    assert( offs._originalLength == offs._modifiedLength );

    bool originalConflictEmpty = offs._originalLength == 0;
    bool modifiedConflictEmpty = offs._modifiedLength == 0;
    bool latestConflictEmpty   = offs._latestLength   == 0;

    svn::Offset max = 0;
    if( offs._originalLength > max ) max = offs._originalLength;
    if( offs._latestLength   > max ) max = offs._latestLength;

    for( svn::Offset i = 1; i <= max; i++ )
    {
      if( i <= offs._originalLength )
      {
        addTextLine( srcOriginal, ctConflictModifiedLatest );
      }
      else
      {
        addControlLine( srcOriginal, ctConflictModifiedLatestEmpty, originalConflictEmpty );
        originalConflictEmpty = false;
      }

      if( i <= offs._modifiedLength )
      {
        addTextLine( srcModified, ctConflictModifiedLatest );
      }
      else
      {
        addControlLine( srcModified, ctConflictModifiedLatestEmpty, modifiedConflictEmpty );
        modifiedConflictEmpty = false;
      }

      if( i <= offs._latestLength )
      {
        addTextLine( srcLatest, ctConflictAll );
      }
      else
      {
        addControlLine( srcLatest, ctConflictAllEmpty, latestConflictEmpty );
        latestConflictEmpty = false;
      }
    }

    _infos->addDiffInfo( DiffInfo(ctConflictModifiedLatest,_blockCnt,createOffsets(max)) );

    _blockCnt++;
    return 0;
  }

  // original != lastest = modified
  sc::Error* diffCommon( const svn::DiffOffsets& offs )
  {
    assert( offs._modifiedLength == offs._latestLength );

    bool originalConflictEmpty = offs._originalLength == 0;
    bool modifiedConflictEmpty = offs._modifiedLength == 0;
    bool latestConflictEmpty   = offs._latestLength   == 0;

    svn::Offset max = 0;
    if( offs._originalLength > max ) max = offs._originalLength;
    if( offs._modifiedLength > max ) max = offs._modifiedLength;

    for( svn::Offset i = 1; i <= max; i++ )
    {
      if( i <= offs._originalLength )
      {
        addTextLine( srcOriginal, ctConflictAll );
      }
      else
      {
        addControlLine( srcOriginal, ctConflictAllEmpty, originalConflictEmpty );
        originalConflictEmpty = false;
      }

      if( i <= offs._modifiedLength )
      {
        addTextLine( srcModified, ctConflictOriginal );
      }
      else
      {
        addControlLine( srcModified, ctConflictOriginalEmpty, modifiedConflictEmpty );
        modifiedConflictEmpty = false;
      }

      if( i <= offs._latestLength )
      {
        addTextLine( srcLatest, ctConflictOriginal );
      }
      else
      {
        addControlLine( srcLatest, ctConflictOriginalEmpty, latestConflictEmpty );
        latestConflictEmpty = false;
      }
    }

    _infos->addDiffInfo( DiffInfo(ctConflictOriginal,_blockCnt,createOffsets(max)) );

    _blockCnt++;
    return 0;
  }

  // original != latest != modified
  sc::Error* conflict( const svn::DiffOffsets& offs, svn::DiffData* resolvedDiff )
  {
    bool originalConflictEmpty = offs._originalLength == 0;
    bool modifiedConflictEmpty = offs._modifiedLength == 0;
    bool latestConflictEmpty   = offs._latestLength   == 0;

    svn::Offset max = 0;
    if( offs._originalLength > max ) max = offs._originalLength;
    if( offs._modifiedLength > max ) max = offs._modifiedLength;
    if( offs._latestLength   > max ) max = offs._latestLength;

    for( svn::Offset i = 1; i <= max; i++ )
    {
      if( i <= offs._originalLength )
      {
        addTextLine( srcOriginal, ctConflictAll );
      }
      else
      {
        addControlLine( srcOriginal, ctConflictAllEmpty, originalConflictEmpty );
        originalConflictEmpty = false;
      }

      if( i <= offs._modifiedLength )
      {
        addTextLine( srcModified, ctConflictAll );
      }
      else
      {
        addControlLine( srcModified, ctConflictAllEmpty, modifiedConflictEmpty );
        modifiedConflictEmpty = false;
      }

      if( i <= offs._latestLength )
      {
        addTextLine( srcLatest, ctConflictAll );
      }
      else
      {
        addControlLine( srcLatest, ctConflictAllEmpty, latestConflictEmpty );
        latestConflictEmpty = false;
      }
    }

    _infos->addDiffInfo( DiffInfo(ctConflictAll,_blockCnt,createOffsets(max)) );

    _blockCnt++;
    return 0;
  }

private:
  void addTextLine( Source src, ConflictType type )
  {
    char   *token;
    size_t  size;
    bool b;

    b = _tokenizer[src]->nextToken( &token, &size );
    assert(b);
    Line line( sc::String(token,size), _blockCnt, type );
    b = _targets[src]->addLine( line );
    assert(b);
  }

  void addControlLine( Source src, ConflictType cType, bool hasConflictEmpty )
  {
    ConflictType type = ctNop;

    if( hasConflictEmpty )
    {
      type = cType;
    }

    Line line( sc::String(""), _blockCnt, type );
    bool b = _targets[src]->addLine( line );
    assert(b);
  }

  BlockInfo createOffsets( svn::Offset length )
  {
    BlockInfo info(_blockStart,length);

    _blockStart += length;

    return info;
  }

private:
  Tokenizer**     _tokenizer;
  LineTarget**    _targets;
  DiffInfoTarget* _infos;

  int             _blockCnt;
  svn::Offset     _blockStart;

  apr_pool_t*    _pool;
};

///////////////////////////////////////////////////////////////////////////////

Diff3::Diff3( const FileDataPtr original, const FileDataPtr modified,
  const FileDataPtr latest, const sc::String& merged )
: _original(original), _modified(modified), _latest(latest), _merged(merged),
  _diffInfo(0)
{
}

Diff3::~Diff3()
{
}

const sc::Error* Diff3::diff3( bool ignoreWhitespace )
{
  svn::Diff      diff;
  svn::DiffData* diffData = 0;
  sc::Error*     err;

  {
    LineTokenizer orgTokenizer( _original->getBuffer(), _original->getBufferSize() );
    LineTokenizer modTokenizer( _modified->getBuffer(), _modified->getBufferSize() );
    LineTokenizer latTokenizer( _latest->getBuffer(),   _latest->getBufferSize() );

    Tokenizer* tokenizer[OutBaton3::srcMax];
    tokenizer[OutBaton3::srcOriginal] = &orgTokenizer;
    tokenizer[OutBaton3::srcModified] = &modTokenizer;
    tokenizer[OutBaton3::srcLatest]   = &latTokenizer;

    DiffBaton diffBaton( tokenizer, ignoreWhitespace );
    err = diff.diff3( &diffData, &diffBaton );
    SC_ERR(err);
  }

  {
    TextModelImpl* orgModel = new TextModelImpl( _original->getName() );
    TextModelImpl* modModel = new TextModelImpl( _modified->getName() );
    TextModelImpl* latModel = new TextModelImpl( _latest->getName() );
    TextModelImpl* merModel = new TextModelImpl( 
      _merged.isEmpty() ? _modified->getName() : _merged );

    DiffInfoModelImpl* diffInfoImpl = new DiffInfoModelImpl();

    LineTokenizer orgTokenizer( _original->getBuffer(), _original->getBufferSize() );
    LineTokenizer modTokenizer( _modified->getBuffer(), _modified->getBufferSize() );
    LineTokenizer latTokenizer( _latest->getBuffer(),   _latest->getBufferSize() );

    Tokenizer* tokenizer[OutBaton3::srcMax];
    tokenizer[OutBaton3::srcOriginal] = &orgTokenizer;
    tokenizer[OutBaton3::srcModified] = &modTokenizer;
    tokenizer[OutBaton3::srcLatest]   = &latTokenizer;

    LineTarget* targets[OutBaton3::srcMax];
    targets[OutBaton3::srcOriginal] = orgModel;
    targets[OutBaton3::srcModified] = modModel;
    targets[OutBaton3::srcLatest]   = latModel;

    OutBaton3 outBaton( tokenizer, targets, diffInfoImpl );
    err = diffData->output( &outBaton );
    SC_ERR(err);

    diffInfoImpl->setModel( DiffInfoModel::dmOriginal, orgModel );
    diffInfoImpl->setModel( DiffInfoModel::dmModified, modModel );
    diffInfoImpl->setModel( DiffInfoModel::dmLatest,   latModel );
    diffInfoImpl->setModel( DiffInfoModel::dmMerged,   merModel );
    _diffInfo = diffInfoImpl;

    // automatically merge where possible...
    Merge m( merModel, _diffInfo );
    m.merge();

    diffInfoImpl->setConflictCnt( m.getNotMergedCnt() );
  }

  delete diffData;
  return sc::Success;
}


//
#if 0
  {
    apr_file_t*  file;
    apr_status_t status = apr_file_open( &file, "out.txt", APR_WRITE, APR_OS_DEFAULT, pool );

    svn_error_t* error = svn_diff_file_output_merge(
      file, diffData->_diff,
      "org.txt", "new.txt", "new2.txt", 0, 0, 0, 0, false, true, pool );
  }
#endif
//


syntax highlighted by Code2HTML, v. 0.9.1