/* ====================================================================
 * Copyright (c) 2003-2006, 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 "Diff.h"
#include "sublib/Line.h"
#include "sublib/LineTarget.h"
#include "LineTokenizer.h"
#include "sublib/ConflictType.h"
#include "DiffInfoTarget.h"
#include "DiffBaton.h"
#include "sublib/TextModelImpl.h"
#include "sublib/NullTextModel.h"
#include "DiffInfoModelImpl.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>




class OutBaton : public svn::OutputBaton
{
public:
  enum Source
  {
    srcOriginal = svn::Diff::srcOriginal,
    srcModified = svn::Diff::srcModified,
    srcMax      = srcModified + 1
  };

  OutBaton( Tokenizer** tokenizer, LineTarget** targets,
    DiffInfoTarget* infos, apr_pool_t *pool )
    : _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 );
  }

  ~OutBaton()
  {
    apr_pool_destroy(_pool);
  }

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

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

    _blockCnt++;
    return sc::Success;
  }

  sc::Error* diffModified( const svn::DiffOffsets& offs )
  {
    bool originalConflictEmpty = offs._originalLength == 0;
    bool modifiedConflictEmpty = offs._modifiedLength == 0;

    svn::Offset max = 0;
    max < offs._originalLength ? max = offs._originalLength : max += 0;
    max < offs._modifiedLength ? max = offs._modifiedLength : max += 0;

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

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

    _infos->addDiffInfo( DiffInfo(ctConflictAll,_blockCnt, createOffsets(max,
      offs._originalLength, offs._modifiedLength)) );

    _blockCnt++;
    return sc::Success;
  }

  sc::Error* diffLatest( const svn::DiffOffsets& offs )
  {
    return sc::Success;
  }

  sc::Error* diffCommon( const svn::DiffOffsets& offs )
  {
    return sc::Success;
  }

  sc::Error* conflict( const svn::DiffOffsets& offs, svn::DiffData* resolvedDiff )
  {
    return sc::Success;
  }

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, bool conflictMark )
  {
    ConflictType type = ctNop;

    if( conflictMark )
    {
      type   = ctConflictAllEmpty;
    }

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

  // todo inline, two things...
  BlockInfo createOffsets( svn::Offset length, svn::Offset originalLength,
    svn::Offset modifiedLength )
  {
    BlockInfo info(_blockStart,length,originalLength,modifiedLength);

    _blockStart += length;

    return info;
  }

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

  int             _blockCnt;
  svn::Offset     _blockStart;

  apr_pool_t*     _pool;
};


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

Diff::Diff( const FileDataPtr original, const FileDataPtr modified )
: _original(original), _modified(modified), _diffInfo(0)
{
}

Diff::~Diff()
{
}

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

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

    Tokenizer* tokenizer[OutBaton::srcMax];
    tokenizer[OutBaton::srcOriginal] = &orgTokenizer;
    tokenizer[OutBaton::srcModified] = &modTokenizer;

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

  {
    TextModelImpl* orgModel = new TextModelImpl( _original->getName() );
    TextModelImpl* modModel = new TextModelImpl( _modified->getName() );
    TextModelImpl* merModel = new TextModelImpl( _modified->getName() );

    DiffInfoModelImpl* diffInfoImpl = new DiffInfoModelImpl();

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

    Tokenizer* tokenizer[OutBaton::srcMax];
    tokenizer[OutBaton::srcOriginal] = &orgTokenizer;
    tokenizer[OutBaton::srcModified] = &modTokenizer;

    LineTarget* targets[OutBaton::srcMax];
    targets[OutBaton::srcOriginal] = orgModel;
    targets[OutBaton::srcModified] = modModel;

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

    diffInfoImpl->setModel( DiffInfoModel::dmOriginal, orgModel );
    diffInfoImpl->setModel( DiffInfoModel::dmModified, modModel );
    diffInfoImpl->setModel( DiffInfoModel::dmLatest, new NullTextModel() );
    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;
}

