/* ====================================================================
 * 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 "Diff.h"
#include "Error.h"
#include "util/AprException.h"

// apr
#include <apr_errno.h>
#include <apr_pools.h>

// svn
#include <svn_diff.h>



namespace svn
{


class DiffFns
{
public:
  DiffFns( DiffBaton* baton )
  : _baton(baton)
  {
  }

  DiffBaton* getBaton() const
  {
    return _baton;
  }

  svn_diff_fns_t* getFns() const
  {
    return &_fns;
  }

  static Diff::DataSource getDataSource( svn_diff_datasource_e ds )
  {
    switch( ds )
    {
    case svn_diff_datasource_original:
      return Diff::srcOriginal;
    case svn_diff_datasource_modified:
      return Diff::srcModified;
    case svn_diff_datasource_latest:
      return Diff::srcLatest;
    case svn_diff_datasource_ancestor:
      return Diff::srcAncestor;
    }
    // TODO, not apr
    throw apr::Exception( 0, sc::String("unknown svn_diff_datasource_x") );
  }

  static DiffFns* df_cast( void* v )
  {
    return static_cast<DiffFns*>(v);
  }

  static DiffToken* dt_cast( void* v )
  {
    return static_cast<DiffToken*>(v);
  }


  static svn_error_t* datasource_open( void *diff_baton, svn_diff_datasource_e datasource )
  {
    DiffFns*   fns = df_cast(diff_baton);
    sc::Error* err = fns->getBaton()->open( getDataSource(datasource) );
    return unwrapError(err);
  }

  static svn_error_t* datasource_close( void *diff_baton, svn_diff_datasource_e datasource )
  {
    DiffFns*   fns = df_cast(diff_baton);
    sc::Error* err = fns->getBaton()->close( getDataSource(datasource) );
    return unwrapError(err);
  }

  static svn_error_t* datasource_get_next_token( apr_uint32_t *hash, void **token, void *diff_baton, svn_diff_datasource_e datasource )
  {
    DiffFns*   fns       = df_cast(diff_baton);
    DiffToken* diffToken = NULL;

    sc::Error* err = fns->getBaton()->getNextToken( &diffToken, getDataSource(datasource) );

    *token = diffToken; 
    return unwrapError(err);
  }

  static svn_error_t* token_compare( void *diff_baton, void *ltoken, void *rtoken, int* compare )
  {
    DiffFns* fns = df_cast(diff_baton);
    *compare = fns->getBaton()->compareToken( dt_cast(ltoken), dt_cast(rtoken) );
    return SVN_NO_ERROR;
  }
#if 0
  static int token_compare( void *diff_baton, void *ltoken, void *rtoken )
  {
    DiffFns* fns = df_cast(diff_baton);
    return fns->getBaton()->compareToken( dt_cast(ltoken), dt_cast(rtoken) );
  }
#endif

  static void token_discard( void *diff_baton, void *token )
  {
    DiffFns* fns = df_cast(diff_baton);
    fns->getBaton()->discardToken( dt_cast(token) );
  }

  static void token_discard_all(void *diff_baton)
  {
    DiffFns* fns = df_cast(diff_baton);
    fns->getBaton()->discardAllToken();
  }

private:
  static svn_diff_fns_t _fns;

  DiffBaton* _baton;
};

svn_diff_fns_t DiffFns::_fns = 
{
  datasource_open,
  datasource_close,
  datasource_get_next_token,
  token_compare,
  token_discard,
  token_discard_all
};

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

Diff::Diff( apr_pool_t* pool )
{
  apr_status_t status = apr_pool_create( &_pool, pool );
  if( status != APR_SUCCESS )
  {
    throw apr::Exception(status, sc::String("failed to create pool") );
  }
}

Diff::~Diff()
{
  apr_pool_destroy(_pool);
}


sc::Error* Diff::diff( DiffData** diff, DiffBaton* baton )
{
  svn_error_t*   svn_error  = NULL;
  svn_diff_t*    svn_diff   = NULL;  

  DiffFns fns(baton);

  svn_error = svn_diff_diff( &svn_diff, &fns, fns.getFns(), _pool );

  *diff = new DiffData(svn_diff);

  return wrapError(svn_error);
}

sc::Error* Diff::diff3( DiffData** diff, DiffBaton* baton )
{
  svn_error_t*   svn_error  = NULL;
  svn_diff_t*    svn_diff   = NULL;  

  DiffFns fns(baton);

  svn_error = svn_diff_diff3( &svn_diff, &fns, fns.getFns(), _pool );

  *diff = new DiffData(svn_diff);

  return wrapError(svn_error);
}

sc::Error* Diff::diff4( DiffData** diff, DiffBaton* baton )
{
  svn_error_t*   svn_error  = NULL;
  svn_diff_t*    svn_diff   = NULL;  

  DiffFns fns(baton);

  svn_error = svn_diff_diff4( &svn_diff, &fns, fns.getFns(), _pool );

  *diff = new DiffData(svn_diff);

  return wrapError(svn_error);
}


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

DiffOffsets::DiffOffsets()
: _originalStart(0), _originalLength(0),
  _modifiedStart(0), _modifiedLength(0),
  _latestStart(0),   _latestLength(0)
{
}

DiffOffsets::DiffOffsets( 
  Offset oS, Offset oL,
  Offset mS, Offset mL,
  Offset lS, Offset lL )
: _originalStart(oS), _originalLength(oL),
  _modifiedStart(mS), _modifiedLength(mL),
  _latestStart(lS),   _latestLength(lL)
{
}


class OutputFns
{
public:
  OutputFns( OutputBaton* baton )
  : _baton(baton)
  {
  }

  OutputBaton* getBaton() const
  {
    return _baton;
  }

  svn_diff_output_fns_t* getFns() const
  {
    return &_fns;
  }

  static OutputFns* of_cast( void* v )
  {
    return static_cast<OutputFns*>(v);
  }

  static svn_error_t* output_common( void *output_baton,
    apr_off_t original_start, apr_off_t original_length,
    apr_off_t modified_start, apr_off_t modified_length,
    apr_off_t latest_start,   apr_off_t latest_length )
  {
    OutputFns* fns = of_cast(output_baton);

    DiffOffsets offs(
      original_start, original_length,
      modified_start, modified_length,
      latest_start,   latest_length );

    sc::Error* err = fns->getBaton()->common( offs );

    return unwrapError(err);
  }

  static svn_error_t* output_diff_modified( void *output_baton,
    apr_off_t original_start, apr_off_t original_length,
    apr_off_t modified_start, apr_off_t modified_length,
    apr_off_t latest_start,   apr_off_t latest_length )
  {
    OutputFns* fns = of_cast(output_baton);

    DiffOffsets offs(
      original_start, original_length,
      modified_start, modified_length,
      latest_start,   latest_length );

    sc::Error* err = fns->getBaton()->diffModified( offs );

    return unwrapError(err);
  }

  static svn_error_t* output_diff_latest( void *output_baton,
    apr_off_t original_start, apr_off_t original_length,
    apr_off_t modified_start, apr_off_t modified_length,
    apr_off_t latest_start,   apr_off_t latest_length )
  {
    OutputFns* fns = of_cast(output_baton);

    DiffOffsets offs(
      original_start, original_length,
      modified_start, modified_length,
      latest_start,   latest_length );

    sc::Error* err = fns->getBaton()->diffLatest( offs );

    return unwrapError(err);
  }

  static svn_error_t* output_diff_common( void *output_baton,
    apr_off_t original_start, apr_off_t original_length,
    apr_off_t modified_start, apr_off_t modified_length,
    apr_off_t latest_start,   apr_off_t latest_length )
  {
    OutputFns* fns = of_cast(output_baton);

    DiffOffsets offs(
      original_start, original_length,
      modified_start, modified_length,
      latest_start,   latest_length );

    sc::Error* err = fns->getBaton()->diffCommon( offs );

    return unwrapError(err);
  }

  static svn_error_t* output_conflict( void *output_baton,
    apr_off_t original_start, apr_off_t original_length,
    apr_off_t modified_start, apr_off_t modified_length,
    apr_off_t latest_start,   apr_off_t latest_length,
    svn_diff_t *resolved_diff )
  {
    OutputFns* fns = of_cast(output_baton);

    // TODO!?!?
    DiffData    data(resolved_diff);
    DiffOffsets offs(
      original_start, original_length,
      modified_start, modified_length,
      latest_start,   latest_length );

    sc::Error* err = fns->getBaton()->conflict( offs, &data );

    return unwrapError(err);
  }

private:
  static svn_diff_output_fns_t _fns;

  OutputBaton* _baton;
};

svn_diff_output_fns_t OutputFns::_fns = 
{
  output_common,
  output_diff_modified,
  output_diff_latest,
  output_diff_common,
  output_conflict
};

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

DiffData::DiffData(svn_diff_t* diff) : _diff(diff)
{
}

DiffData::~DiffData()
{
}

sc::Error* DiffData::output( OutputBaton* baton )
{
  svn_error_t* svn_error  = NULL;
  OutputFns    fns(baton);

  svn_error = svn_diff_output( _diff, &fns, fns.getFns() );

  return wrapError(svn_error);
}

bool DiffData::hasConflicts() const
{
  return svn_diff_contains_conflicts(_diff) == TRUE;
}

bool DiffData::hasDifferences() const
{
  return svn_diff_contains_diffs(_diff) == TRUE;
}


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




} // namespace
