/* ====================================================================
 * 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 "Client.h"
#include "ErrorImpl.h"
#include "Revision.h"
#include "Notify.h"
#include "WcStatus.h"
#include "WcNotify.h"
#include "DirEntry.h"
#include "Info.h"
#include "CommitInfo.h"
#include "CommitItem.h"
#include "CommitBaton.h"
#include "StatusBaton.h"
#include "CancelBaton.h"
#include "BlameBaton.h"
#include "LogBaton.h"
#include "LogEntry.h"
#include "BlameLine.h"
#include "InfoBaton.h"
#include "PropListItem.h"
#include "PropGetItem.h"
#include "ClientContext.h"
#include "AuthPromptProvider.h"
#include "DiffSummarizeBaton.h"
#include "DiffSummarize.h"
#include "VisualDiff.h"
#include "VisualMerge.h"
#include "Error.h"
#include "RevisionFactory.h"
#include "util/apr.h"
#include "util/String.h"
#include "util/iconvstream.h"
#include "subversion/repos_diff2.h"

// svn
#include <svn_client.h>
#include <svn_path.h>
#include <svn_config.h>
#include <svn_time.h>

// apr
#include <apr_hash.h>

// sys
#include <cassert>


using sc::Success;

namespace svn
{

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

/* convenience shortcut to RevisionFactory */
static void convertRevision( const Revision* rev, svn_opt_revision_t* svnrev )
{
  RevisionFactory::toSvnRevision(rev,svnrev);
}

///////////////////////////////////////////////////////////////////////////////
// svn_client_get_commit_log_t
class CommitFns
{
public:
  static svn_error_t* get_commit_log( const char** logMsg, const char** tmpFile,
    apr_array_header_t* commitItems, void* baton, apr_pool_t* pool )
  {
    CommitBaton* cb = static_cast<CommitBaton*>(baton);

    CommitItems items;
    for( int i = 0; i < commitItems->nelts; i++ )
    {
      svn_client_commit_item_t* item =
        (APR_ARRAY_IDX(commitItems,i,svn_client_commit_item_t*));

     items.push_back( CommitItemPtr(new CommitItem(item)) );
    }

    *logMsg  = 0;
    *tmpFile = 0;

    sc::String log;
    sc::String file;

    bool commit = cb->getLogMsg( log, file, items );
    if( !commit )
      return svn_error_create( SVN_ERR_CANCELLED, 0, "canceled.." ); 
    
    *logMsg = apr_pstrdup( pool, log.getStr() );
    // \todo handle commit msg from tmpFile

    return SVN_NO_ERROR;
  }
};

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

// svn_cancel_func_t
class CancelFns
{
public:
  static svn_error_t* check_cancel( void* baton )
  {
    CancelBaton* cb = static_cast<CancelBaton*>(baton);

    if( cb && cb->isCanceled() )
    {
      return svn_error_create( SVN_ERR_CANCELLED, 0, "canceled.." ); 
    }
    return SVN_NO_ERROR;
  }
};

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

// svn_log_message_receiver_t
class LogFns
{
public:
  static svn_error_t* log( void *baton, apr_hash_t* changedPaths, svn_revnum_t rev,
    const char* author, const char* date, const char* msg, apr_pool_t* pool )
  {
    LogBaton* lb = static_cast<LogBaton*>(baton);

    Date tdate = 0;
    svn_time_from_cstring( &tdate, date, pool );

    LogEntryPtr entry = LogEntryPtr( new LogEntry(rev,tdate,sc::String(author),sc::String(msg)) );

    if( changedPaths )
    {
      apr_hash_index_t *hi;

      for( hi = apr_hash_first(pool,changedPaths); hi; hi = apr_hash_next(hi) )
      {
        const char*             key;
        svn_log_changed_path_t* item;

        apr_hash_this( hi, (const void**)&key, NULL, (void**)&item );
        LogEntry::Path path( key, item );
        entry->addPath( path );
      }
    }

    lb->receiveMessage( entry );

    return SVN_NO_ERROR;
  }
};

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

// svn_wc_notify_func_t
class NotifyFns
{
public:
  static void notify( void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool )
  {
    NotifyBaton* nb = static_cast<NotifyBaton*>(baton);
    WcNotifyPtr ny( new WcNotify(notify) );
    if(nb)
      nb->notify(ny);
  }
};

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

// svn_client_ctx_t, svn_auth_baton_t, prompt providers..
class AuthFns
{
public:
  static AuthPromptProvider* ab_cast( void* baton )
  {
    return static_cast<AuthPromptProvider*>(baton);
  }

  // svn_auth_simple_prompt_func_t
  static svn_error_t* simple_prompt( svn_auth_cred_simple_t **cred_p, void *baton,
    const char *realm, const char *username, svn_boolean_t may_save, apr_pool_t *pool )
  {
    AuthPromptProvider* auth = ab_cast(baton);

    AuthPromptProvider::simpleCredentials cred;
    bool result = auth->simplePrompt( realm, username, may_save != 0, cred );

    if( ! result )
    {
      return svn_error_create( SVN_ERR_AUTHN_CREDS_UNAVAILABLE, 0, "simple prompt" );
    }

    svn_auth_cred_simple_t *ret = (svn_auth_cred_simple_t*)apr_pcalloc( pool, sizeof(*ret) );

    // TODO remove when fixed in subversion
    ret->username = apr_pstrdup( pool, (const char*)cred._username ? (const char*)cred._username : "" );
    ret->password = apr_pstrdup( pool, (const char*)cred._password ? (const char*)cred._password : "" );
    ret->may_save = cred._save;

    *cred_p = ret;
    return SVN_NO_ERROR;
  }

  // svn_auth_username_prompt_func_t
  static svn_error_t* username_prompt( svn_auth_cred_username_t **cred_p, void *baton,
    const char *realm, svn_boolean_t may_save, apr_pool_t *pool )
  {
    // sample implementation in subversion/libsvn_subr/prompt.c
    // "username"
    return SVN_NO_ERROR;
  }

  // svn_auth_ssl_server_trust_prompt_func_t
  static svn_error_t* ssl_server_trust_prompt( svn_auth_cred_ssl_server_trust_t **cred_p,
    void *baton, const char *realm, apr_uint32_t failures,
    const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool )
  {
    AuthPromptProvider* auth = ab_cast(baton);

    AuthPromptProvider::sslServerCertInfo certinfo;
    certinfo.hostname    = cert_info->hostname;
    certinfo.fingerprint = cert_info->fingerprint;
    certinfo.validFrom   = cert_info->valid_from;
    certinfo.validUntil  = cert_info->valid_until;
    certinfo.issuerDName = cert_info->issuer_dname;
    certinfo.asciiCert   = cert_info->ascii_cert;

    AuthPromptProvider::sslServerTrustCredentials cred;
    bool result = auth->sslServerTrustPrompt( realm, failures, certinfo, may_save != 0, cred );

    if( ! result )
    {
      return svn_error_create( SVN_ERR_AUTHN_CREDS_UNAVAILABLE, 0, "ssl server trust prompt" );
    }

    svn_auth_cred_ssl_server_trust_t *ret =
      (svn_auth_cred_ssl_server_trust_t*)apr_pcalloc( pool, sizeof(*ret) );

    ret->accepted_failures = cred._acceptedFailures;
    ret->may_save          = cred._save;

    *cred_p = ret;
    return SVN_NO_ERROR;
  }

  // svn_auth_ssl_client_cert_prompt_func_t
  static svn_error_t* ssl_client_cert_prompt( svn_auth_cred_ssl_client_cert_t **cred_p,
    void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool )
  {
    // sample implementation in subversion/libsvn_subr/prompt.c
    // "client certificate filename"
    return SVN_NO_ERROR;
  }

  // svn_auth_ssl_client_cert_pw_prompt_func_t
  static svn_error_t* ssl_client_cert_pw_prompt( svn_auth_cred_ssl_client_cert_pw_t **cred_p,
    void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool )
  {
    // sample implementation in subversion/libsvn_subr/prompt.c
    // "passphrase for realm"
    return SVN_NO_ERROR;
  }
};

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

// *svn_wc_status_func2_t, status_baton
class StatusFns
{
public:
  StatusFns( StatusBaton* baton ) : _baton(baton)
  {
  }

  static StatusFns* sf_cast( void* v )
  {
    return static_cast<StatusFns*>(v);
  }

  static void status( void* baton, const char* path, svn_wc_status2_t* state )
  {
    StatusFns* fns = sf_cast(baton);

    if( state )
    {
      WcStatusPtr wcs( new WcStatus( sc::String(path), state ) );
      fns->getBaton()->status( path, wcs );
    }
    else
    {
      fns->getBaton()->status( path, WcStatusPtr() );
    }
  }

  StatusBaton* getBaton() const
  {
    return _baton;
  }

private:
  StatusBaton* _baton;
};

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

class BlameFns
{
public:
  BlameFns( BlameBaton* baton ) : _baton(baton)
  {
  }

  static BlameFns* bf_cast( void* v )
  {
    return static_cast<BlameFns*>(v);
  }

  static svn_error_t* receive( void* baton, apr_int64_t lineNo, svn_revnum_t revision,
    const char* author, const char* date, const char* line, apr_pool_t* pool )
  {
    BlameFns* fns = bf_cast(baton);

    Date tdate = 0;
    if( date )
    {
      svn_time_from_cstring( &tdate, date, pool );
    }

    BlameLine* bline = new BlameLine( lineNo, revision, sc::String(author), tdate,
      sc::String(line) );

    fns->getBaton()->receive(BlameLinePtr(bline));

    return SVN_NO_ERROR;
  }

  BlameBaton* getBaton() const
  {
    return _baton;
  }

private:
  BlameBaton* _baton;
};

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

// *svn_diff_summarize_func_t, diff_summarize_baton
class DiffSummarizeFns
{
public:
  DiffSummarizeFns( DiffSummarizeBaton* baton ) : _baton(baton)
  {
  }

  static DiffSummarizeFns* dsf_cast( void* v )
  {
    return static_cast<DiffSummarizeFns*>(v);
  }

  static svn_error_t* summarize( const svn_client_diff_summarize_t *diff,
    void *baton, apr_pool_t *pool )
  {
    DiffSummarizeFns* fns = dsf_cast(baton);

    if( diff )
    {
      DiffSummarizePtr dp( new DiffSummarize(diff) );
      fns->getBaton()->summarize(dp);
    }

    return SVN_NO_ERROR;
  }

  DiffSummarizeBaton* getBaton() const
  {
    return _baton;
  }

private:
  DiffSummarizeBaton* _baton;
};

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

class InfoFns
{
public:
  InfoFns( InfoBaton* baton ) : _baton(baton)
  {
  }

  static InfoFns* if_cast( void* v )
  {
    return static_cast<InfoFns*>(v);
  }


typedef svn_error_t *(*svn_info_receiver_t)
     (void *baton,
      const char *path,
      const svn_info_t *info,
      apr_pool_t *pool);

  static svn_error_t* receive( void* baton, const char* path, const svn_info_t* info,
    apr_pool_t* pool )
  {
    InfoFns* fns = if_cast(baton);

    InfoPtr infp( new Info( sc::String(path), info ) );

    fns->getBaton()->info(infp);

    return SVN_NO_ERROR;
  }

  InfoBaton* getBaton() const
  {
    return _baton;
  }

private:
  InfoBaton* _baton;
};

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

ClientContext::ClientContext( AuthPromptProvider* pp, CancelBaton* cb,
  NotifyBaton* nb, CommitBaton* cob, const sc::String& diff, const sc::String&
  merge ) : _authProv(pp), _diff(diff), _merge(merge)
{
  // see subversion/clients/commandline/main.c for what is going on here..

  _pool = apr::createPool(0);

  svn_error_t* err;

  // make sure the system-wide subversion configuration exits.
  err = svn_config_ensure( 0, _pool );
  // \todo handle error

  err = svn_client_create_context( &_context, _pool );
  // \todo handle error

  // load system wide configuration
  err = svn_config_get_config( &(_context->config), 0, _pool );
  // \todo handle error

  // log message callback
  _context->log_msg_func  = CommitFns::get_commit_log;
  _context->log_msg_baton = cob;

  // set up our cancellation support
  _context->cancel_func   = CancelFns::check_cancel;
  _context->cancel_baton  = cb;

  // set up notify callback
  _context->notify_func2  = NotifyFns::notify;
  _context->notify_baton2 = nb;

  // authentication
  //svn_boolean_t             store_password_val = TRUE;
  svn_auth_provider_object_t* provider;
  svn_auth_baton_t*           ab;

  // the whole list of registered providers
  apr_array_header_t *providers = apr_array_make( _pool, 10,
    sizeof(svn_auth_provider_object_t*) );

  // The main disk-caching auth providers, for both
  // 'username/password' creds and 'username' creds.
#ifdef WIN32
  svn_auth_get_windows_simple_provider( &provider, _pool );
  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
#endif
#ifdef SVN_HAVE_KEYCHAIN_SERVICES
  svn_auth_get_keychain_simple_provider( &provider, _pool );
  APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *)  = provider;
#endif
  svn_client_get_simple_provider( &provider, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
  svn_client_get_username_provider( &provider, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;

  // ... server-cert, client-cert, and client-cert-password providers.
  svn_client_get_ssl_server_trust_file_provider( &provider, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
  svn_client_get_ssl_client_cert_file_provider( &provider, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
  svn_client_get_ssl_client_cert_pw_file_provider( &provider, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;

  // Two basic prompt providers: username/password, and just username.
  svn_client_get_simple_prompt_provider(
    &provider, AuthFns::simple_prompt, pp, 2, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
#if 0
  svn_client_get_username_prompt_provider(
    &provider, AuthFns::username_prompt, pp, 2, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
#endif

  // Three ssl prompt providers: server-certs, client-certs, and
  // client-cert-passphrases.
  svn_client_get_ssl_server_trust_prompt_provider(
    &provider, AuthFns::ssl_server_trust_prompt, pp, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
#if 0
  svn_client_get_ssl_client_cert_prompt_provider(
    &provider, AuthFns::ssl_client_cert_prompt, pp, 2, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
#endif
#if 0
  svn_client_get_ssl_client_cert_pw_prompt_provider(
    &provider, AuthFns::ssl_client_cert_pw_prompt, pp, 2, _pool );
  APR_ARRAY_PUSH( providers, svn_auth_provider_object_t* ) = provider;
#endif

  // build the authentication baton
  svn_auth_open( &ab, providers, _pool );
  _context->auth_baton = ab;
}

ClientContext::~ClientContext()
{
  delete (CancelBaton*)_context->cancel_baton;
  delete (NotifyBaton*)_context->notify_baton;
  delete (NotifyBaton*)_context->notify_baton2;
  delete (CommitBaton*)_context->log_msg_baton;
  delete _authProv;

  apr::destroyPool(_pool);
}

CancelBaton* ClientContext::getCancelBaton() const
{
  return (CancelBaton*)_context->cancel_baton;
}

const sc::String& ClientContext::getDiffCmd() const
{
  return _diff;
}

const sc::String& ClientContext::getMergeCmd() const
{
  return _merge;
}

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

/**
 * create a subversion Client object. The Client object takes ownership of
 * the ClientContext pointer.
 *
 * \param context the ClientContext. 
 * \param pool    an optional root pool.    
 */
Client::Client( ClientContext* context, apr_pool_t *pool )
{
  _pool    = apr::createPool(pool);
  _context = context;
}

Client::~Client()
{
  delete _context;
  apr::destroyPool(_pool);
}

sc::Error* Client::checkout( Revnumber* resultRev, const sc::String& url,
  const sc::String& path, const Revision& peg, const Revision& rev, bool recurse )
{
  apr::Pool pool( _pool );

  const char* curl  = preparePathOrUrl( url, pool );
  const char* cpath = preparePathOrUrl( path, pool );

  svn_revnum_t       resrev = 0;
  svn_opt_revision_t svnrev;
  svn_opt_revision_t svnpeg;
  convertRevision( &rev, &svnrev );
  convertRevision( &peg, &svnpeg );

  svn_error_t* err = svn_client_checkout2( &resrev, curl, cpath, &svnpeg, &svnrev,
    recurse, false /* todo external*/, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resrev;
  return Success;
}

sc::Error* Client::update( Revnumber* resultRev, const sc::String& path,
  const Revision& rev, bool recurse )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( path, pool );

  svn_revnum_t       resrev = 0;
  svn_opt_revision_t svnrev;
  convertRevision( &rev, &svnrev );

  svn_error_t* err = svn_client_update( &resrev, cpath, &svnrev, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resrev;
  return Success;
}

sc::Error* Client::switchx( Revnumber* resultRev, const sc::String& path,
  const sc::String& url, const Revision& rev, bool recurse )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( path, pool );
  const char* curl  = preparePathOrUrl( url, pool );

  svn_revnum_t       resrev = 0;
  svn_opt_revision_t svnrev;
  convertRevision( &rev, &svnrev );

  svn_error_t* err = svn_client_switch( &resrev, cpath, curl, &svnrev, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resrev;
  return Success;
}

sc::Error* Client::status( Revnumber* resultRev, const sc::String& path,
  const Revision& rev, StatusBaton *baton, bool recurse, bool all, bool update,
  bool ignore )
{
  apr::Pool pool( _pool );

  const char *cpath = preparePathOrUrl( path, pool );

  svn_revnum_t       resrev = 0;
  svn_opt_revision_t svnrev;
  convertRevision( &rev, &svnrev );

  StatusFns fns(baton);
  svn_error_t* err = svn_client_status2( &resrev, cpath, &svnrev,
    StatusFns::status, &fns, recurse, all, update, ignore, false /*todo external*/,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resrev;
  return Success;
}

sc::Error* Client::commit( CommitInfo& resultInfo, const Paths& paths,
  bool recurse, bool keepLocks )
{
  apr::Pool pool( _pool );

  apr_array_header_t* patharr = apr_array_make( pool, (int)paths.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_client_commit_info_t* commitInfo = 0;
  svn_error_t* err = svn_client_commit2( &commitInfo, patharr, recurse,
    keepLocks, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  resultInfo = commitInfo;
  return Success;
}

sc::Error* Client::ls( const sc::String& pathOrUrl, const Revision& rev,
  bool recurse, DirEntries& entries )
{
  apr::Pool   pool(_pool);

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );

  apr_hash_t*        hash;
  svn_opt_revision_t svnrev;
  convertRevision( &rev, &svnrev );

  svn_error_t* err = svn_client_ls( &hash, cpath, &svnrev, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  apr_hash_index_t* hi;

  for( hi = apr_hash_first(_pool,hash); hi; hi = apr_hash_next(hi) )
  {
    const char*   key; 
    apr_ssize_t   klen;
    svn_dirent_t* val;

    apr_hash_this( hi, (const void**)&key, &klen, (void**)&val );

    sc::String name = pathOrUrl;
    name += "/";
    name += sc::String(key,klen);
    entries.push_back( DirEntryPtr(new DirEntry(name,val)) );
  }

  return Success;
}

sc::Error* Client::mkdir( CommitInfo& resultInfo, const Paths& pathsOrUrls )
{
  apr::Pool pool( _pool );
  apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_client_commit_info_t* commitInfo = 0;
  svn_error_t* err = svn_client_mkdir( &commitInfo, patharr,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  resultInfo = commitInfo;
  return Success;
}

#if 0
Error*Client::add( const sc::String& path, bool recurse, bool force )
{
  apr::Pool   pool( _pool );

  const char* cpath = svn_path_canonicalize( path, pool );

  svn_error_t* err = svn_client_add2( cpath, recurse, force, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}
#endif

sc::Error* Client::add( const Paths& paths, bool recurse, bool force )
{
  apr::Pool pool( _pool );

  for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );

    svn_error_t* err = svn_client_add2( cpath, recurse, force,
      _context->_context, pool );

    if( err )
    {
      return wrapError(err);
    }
  }

  return Success;
}

sc::Error* Client::deletex( CommitInfo& resultInfo, const Paths& pathsOrUrls,
  bool force )
{
  apr::Pool           pool( _pool );
  apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_client_commit_info_t* commitInfo = 0;
  svn_error_t* err = svn_client_delete( &commitInfo, patharr, force,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  resultInfo = commitInfo;
  return Success;
}

sc::Error* Client::revert( const Paths& paths, bool recurse )
{
  apr::Pool           pool( _pool );
  apr_array_header_t* patharr = apr_array_make( pool, (int)paths.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = paths.begin(); it != paths.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_error_t* err = svn_client_revert( patharr, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::import( CommitInfo& resultInfo, const sc::String& path,
  const sc::String& url, bool recurse )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( path, pool );
  const char* curl  = preparePathOrUrl( url, pool );

  svn_client_commit_info_t* commitInfo = 0;
  svn_error_t* err = svn_client_import( &commitInfo, cpath, curl, ! recurse, 
    _context->_context, _pool );

  if( err )
  {
    return wrapError(err);
  }

  resultInfo = commitInfo;
  return Success;
}

sc::Error* Client::blame( const sc::String& pathOrUrl, const Revision& begin,
  const Revision& end, BlameBaton* baton )
{
  apr::Pool   pool(_pool);

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );

  svn_opt_revision_t revBegin;
  convertRevision( &begin, &revBegin );
  svn_opt_revision_t revEnd;
  convertRevision( &end, &revEnd );

  BlameFns fns(baton);
  svn_error_t* err = svn_client_blame( cpath, &revBegin, &revEnd,
    BlameFns::receive, &fns, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::cleanup( const sc::String& path )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( path, pool );

  svn_error_t* err = svn_client_cleanup( cpath, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::relocate( const sc::String& path, const sc::String& fromUrl,
  const sc::String& toUrl, bool recurse )
{
  apr::Pool   pool( _pool );

  const char* cpath    = preparePathOrUrl( path, pool );
  const char* cfromUrl = preparePathOrUrl( fromUrl, pool );
  const char* ctoUrl   = preparePathOrUrl( toUrl, pool );

  svn_error_t* err = svn_client_relocate( cpath, cfromUrl, ctoUrl, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::resolved( const sc::String& path, bool recurse )
{
  apr::Pool   pool( _pool );

  const char* cpath = preparePathOrUrl( path, pool );

  svn_error_t* err = svn_client_resolved( cpath, recurse, _context->_context,
    pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::log( const Paths& pathsOrUrls, const Revision& start,
  const Revision& stop, int limit, bool discoverChangedPaths, bool strictNodeHistory,
  LogBaton* baton )
{
  apr::Pool pool( _pool );

  apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  convertRevision( &start, &crev1 );
  convertRevision( &stop, &crev2 );

  svn_error_t* err = svn_client_log2( patharr, &crev1, &crev2, limit, discoverChangedPaths,
    strictNodeHistory, &LogFns::log, baton, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::diff( const sc::String& pathOrUrl1, const Revision& rev1,
  const sc::String& pathOrUrl2, const Revision& rev2, bool recurse,
  bool ancestry, bool deleted, bool patch, sc::String& patchFile )
{
  apr::Pool pool( _pool );

  apr_array_header_t* dopts = apr_array_make( pool, 0, sizeof(char*) );
  const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool );
  const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );

  apr_file_t *outfile;
  apr_file_t *errfile;
  apr_status_t status;
  const char* outfileName;

  apr_time_t now    = apr_time_now();
  char*      nowstr = apr_psprintf( pool, "%" APR_TIME_T_FMT, now );

  apr_int32_t flags = APR_CREATE|APR_WRITE;
  if( ! patch )
  {
    flags |= APR_DELONCLOSE;
  }

  {
    // todo error handling..
    //char* err = apr_strerror( status, errbuf, 255 );
    //assert( status == APR_SUCCESS );

    const char* tempdir = 0;
    status = apr_temp_dir_get( &tempdir, pool );

    char* tempout = apr_pstrcat( pool, tempdir, "/sc_stdout.log.", nowstr, NULL );
    status = apr_file_open( &outfile, tempout, flags, APR_OS_DEFAULT, pool );
      
    outfileName = tempout;
  }

  {
    const char* tempdir = 0;
    status = apr_temp_dir_get( &tempdir, pool );

    char* temperr = apr_pstrcat( _pool, tempdir, "/sc_stderr.log.", nowstr, NULL );
    status = apr_file_open( &errfile, temperr, flags, APR_OS_DEFAULT, pool );
  }

  svn_error_t* err = svn_client_diff( dopts, cpath1, &crev1, cpath2, &crev2,
    recurse, !ancestry, !deleted, outfile, errfile, _context->_context, pool );

  apr_file_close(outfile);
  apr_file_close(errfile);

  if( err )
  {
    return wrapError(err);
  }

  patchFile = outfileName;
  return Success;
}

sc::Error* Client::diff( const sc::String& srcPathOrUrl, const Revision& rev1,
  const Revision& rev2, const Revision& peg, bool recurse, bool ancestry,
  bool deleted, bool patch, sc::String& patchFile )
{
  apr::Pool pool( _pool );

  apr_array_header_t* dopts = apr_array_make( pool, 0, sizeof(char*) );
  const char* cpath = preparePathOrUrl( srcPathOrUrl, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  svn_opt_revision_t cpeg;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );
  convertRevision( &peg, &cpeg );

  apr_file_t *outfile;
  apr_file_t *errfile;
  apr_status_t status;
  const char* outfileName;

  apr_time_t now    = apr_time_now();
  char*      nowstr = apr_psprintf( pool, "%" APR_TIME_T_FMT, now );

  apr_int32_t flags = APR_CREATE|APR_WRITE;
  if( ! patch )
  {
    flags |= APR_DELONCLOSE;
  }

  {
    const char* tempdir = 0;
    status = apr_temp_dir_get( &tempdir, pool );

    char* tempout = apr_pstrcat( pool, tempdir, "/sc_stdout.log.", nowstr, NULL );
    status = apr_file_open( &outfile, tempout, flags, APR_OS_DEFAULT, pool );
      
    outfileName = tempout;
  }

  {
    const char* tempdir = 0;
    status = apr_temp_dir_get( &tempdir, pool );

    char* temperr = apr_pstrcat( _pool, tempdir, "/sc_stderr.log.", nowstr, NULL );
    status = apr_file_open( &errfile, temperr, flags, APR_OS_DEFAULT, pool );
  }

  svn_error_t* err = svn_client_diff_peg( dopts, cpath, &cpeg, &crev1, &crev2,
    recurse, !ancestry, !deleted, outfile, errfile, _context->_context, pool );

  apr_file_close(outfile);
  apr_file_close(errfile);

  if( err )
  {
    return wrapError(err);
  }

  patchFile = outfileName;
  return Success;
}

sc::Error* Client::diffSummarize( const sc::String& pathOrUrl1,
  const Revision& rev1, const sc::String& pathOrUrl2, const Revision& rev2,
  DiffSummarizeBaton* baton, bool recurse, bool ancestry )
{
  apr::Pool pool( _pool );

  const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool );
  const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );

  DiffSummarizeFns fns(baton);
  svn_error_t* err = svn_client_diff_summarize( cpath1, &crev1,
    cpath2, &crev2, recurse, !ancestry, DiffSummarizeFns::summarize,
    &fns, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::diffSummarize( const sc::String& srcPathOrUrl,
  const Revision& rev1, const Revision& rev2, const Revision& peg,
  DiffSummarizeBaton* baton, bool recurse, bool ancestry )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( srcPathOrUrl, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  svn_opt_revision_t cpeg;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );
  convertRevision( &peg, &cpeg );

  DiffSummarizeFns fns(baton);
  svn_error_t* err = svn_client_diff_summarize_peg( cpath, &cpeg,
    &crev1, &crev2, recurse, !ancestry, DiffSummarizeFns::summarize,
    &fns, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::visualDiff( const sc::String& pathOrUrl1, const Revision& rev1,
  const sc::String& pathOrUrl2, const Revision& rev2 )
{
  apr::Pool pool( _pool );

  const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool );
  const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );

  VisualDiff vdiff( _context->_context, _context->getDiffCmd(), pool );
  svn_error_t* err = vdiff.run( cpath1, &crev1, cpath2, &crev2 );
  
  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::visualDiff( const sc::String& srcPathOrUrl, const Revision& rev1,
 const Revision& rev2, const Revision& peg )
{
  apr::Pool pool( _pool );

  const char* cpath = preparePathOrUrl( srcPathOrUrl, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  svn_opt_revision_t cpeg;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );
  convertRevision( &peg, &cpeg );

  VisualDiff vdiff( _context->_context, _context->getDiffCmd(), pool );
  svn_error_t* err = vdiff.run( cpath, &crev1, &crev2, &cpeg );
  
  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::merge( const sc::String& pathOrUrl1, const Revision& rev1,
  const sc::String& pathOrUrl2, const Revision& rev2,
  const sc::String& targetPath, bool recurse, bool ancestry, bool force,
  bool dryRun )
{
  apr::Pool pool( _pool );

  const char* cpath1 = preparePathOrUrl( pathOrUrl1, pool );
  const char* cpath2 = preparePathOrUrl( pathOrUrl2, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );

  const char* ctarget = svn_path_canonicalize( targetPath, pool );

  svn_error_t* err = svn_client_merge( cpath1, &crev1, cpath2, &crev2,
    ctarget, recurse, !ancestry, force, dryRun, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::merge( const sc::String& srcPathOrUrl, const Revision& rev1,
  const Revision& rev2, const Revision& peg, const sc::String& targetPath,
  bool recurse, bool ancestry, bool force, bool dryRun )
{
  apr::Pool pool( _pool );

  const char* cpath1 = preparePathOrUrl( srcPathOrUrl, pool );
  svn_opt_revision_t crev1;
  svn_opt_revision_t crev2;
  svn_opt_revision_t cpeg;
  convertRevision( &rev1, &crev1 );
  convertRevision( &rev2, &crev2 );
  convertRevision( &peg,  &cpeg );

  const char* ctarget = preparePathOrUrl( targetPath, pool );

  svn_error_t* err = svn_client_merge_peg( cpath1, &crev1, &crev2, &cpeg,
    ctarget, recurse, !ancestry, force, dryRun, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::visualMerge( const WcStatusPtr file )
{
  apr::Pool pool( _pool );

  VisualMerge vmerge( _context->_context, _context->getMergeCmd(), pool );
  svn_error_t* err = vmerge.run( file );
  
  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::copy( CommitInfo& resultInfo,  const Paths& srcPathsOrUrls,
  const Revision& srcRev, const sc::String& dstPathOrUrl )
{
  apr::Pool pool( _pool );

  for( Paths::const_iterator it = srcPathsOrUrls.begin(); it != srcPathsOrUrls.end(); it++ )
  {
    const char* csrc = preparePathOrUrl( *it, pool );
    svn_opt_revision_t crev;
    convertRevision( &srcRev, &crev );

    const char* cdst = preparePathOrUrl( dstPathOrUrl, pool );

    svn_client_commit_info_t* commitInfo = 0;
    svn_error_t* err = svn_client_copy( &commitInfo, csrc, &crev, cdst,
      _context->_context, pool );

    if( err )
    {
      return wrapError(err);
    }

    resultInfo = commitInfo;
  }
  return Success;
}

sc::Error* Client::move( CommitInfo& resultInfo, const Paths& srcPathsOrUrls,
  const Paths& dstPathsOrUrls, bool force )
{
  apr::Pool pool( _pool );

  for( int idx = 0; idx < (int)srcPathsOrUrls.size(); idx++ )
  {
    const char* csrc = preparePathOrUrl( srcPathsOrUrls[idx], pool );
    const char* cdst = preparePathOrUrl( dstPathsOrUrls[idx], pool );

    svn_client_commit_info_t* commitInfo = 0;
    svn_error_t* err = svn_client_move2( &commitInfo, csrc, cdst, force,
      _context->_context, pool );

    if( err )
      return wrapError(err);

    resultInfo = commitInfo;
  }
  return Success;
}

sc::Error* Client::exportx( Revnumber* resultRev, const sc::String& srcPathOrUrl,
  const Revision& srcRev, const sc::String& dstPath, bool force,
  const sc::String& eol )
{
  apr::Pool pool( _pool );

  const char *csrc = preparePathOrUrl( srcPathOrUrl, pool );
  const char *cdst = preparePathOrUrl( dstPath, pool );

  svn_revnum_t       resrev = 0;
  svn_opt_revision_t svnrev;
  convertRevision( &srcRev, &svnrev );

  svn_error_t* err = svn_client_export2( &resrev, csrc, cdst, &svnrev, force,
    eol, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resrev;
  return Success;
}


class MemoryStream
{
public:
  MemoryStream( apr::Pool& pool )
  {
    _stream = svn_stream_create( this, pool );

    svn_stream_set_read( _stream, read_fn );
    svn_stream_set_write( _stream, write_fn );
    svn_stream_set_close( _stream, close_fn );
  }

  static svn_error_t* read_fn( void* baton, char* buffer, apr_size_t* len )
  {
    return SVN_NO_ERROR;
  }

  static svn_error_t* write_fn( void* baton, const char* buffer, apr_size_t* len )
  {
    MemoryStream* stream = static_cast<MemoryStream*>(baton);
    stream->_str += sc::String( buffer, *len );
    return SVN_NO_ERROR;
  }

  static svn_error_t* close_fn( void* baton )
  {
    return SVN_NO_ERROR;
  }

  operator svn_stream_t*() const
  {
    return _stream;
  }

  const sc::String& get() const
  {
    return _str;
  }

private:
  svn_stream_t* _stream;
  sc::String    _str;
};


sc::Error* Client::cat( sc::String& out, const sc::String& pathOrUrl,
  const Revision& rev, const Revision& peg )
{
  apr::Pool pool( _pool );

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );
  svn_opt_revision_t svnrev;
  svn_opt_revision_t pegrev;
  convertRevision( &rev, &svnrev );
  convertRevision( &peg, &pegrev );

  MemoryStream stream( pool );

  svn_error_t* err = svn_client_cat2( stream, cpath, &pegrev, &svnrev,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  // in locale encoding!
  out = stream.get();

  return Success;
}

sc::Error* Client::proplist( PropListItems& items, const sc::String& pathOrUrl,
  const Revision& rev, bool recurse )
{
  apr::Pool pool( _pool );

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );

  apr_array_header_t* propItems;
  svn_opt_revision_t  svnrev;
  convertRevision( &rev, &svnrev );

  svn_error_t* err = svn_client_proplist( &propItems, cpath, &svnrev, recurse,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  for( int i = 0; i < propItems->nelts; i++ )
  {
    svn_client_proplist_item_t* item =
      (APR_ARRAY_IDX(propItems,i,svn_client_proplist_item_t*));

    items.push_back( PropListItemPtr(new PropListItem(item)) );
  }

  return Success;
}

sc::Error* Client::propget( PropGetItems& items, const sc::String& propName,
  const sc::String& pathOrUrl, const Revision& rev, bool recurse )
{
  apr::Pool pool( _pool );

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );

  svn_opt_revision_t  svnrev;
  convertRevision( &rev, &svnrev );
  apr_hash_t* prophash = 0;

  svn_error_t* err = svn_client_propget( &prophash, propName, cpath, &svnrev,
    recurse, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  apr_hash_index_t* hi;

  for( hi = apr_hash_first(_pool,prophash); hi; hi = apr_hash_next(hi) )
  {
    const char*   key; 
    apr_ssize_t   klen;
    svn_string_t* val;

    apr_hash_this( hi, (const void**)&key, &klen, (void**)&val );

    items.push_back( PropGetItemPtr(
      new PropGetItem(sc::String(key,klen),sc::String(val->data))) );
  }

  return Success;
}

sc::Error* Client::propset( const sc::String& propName, const sc::String& propVal,
  const sc::String& path, bool recurse )
{
  apr::Pool pool( _pool );

  const char*   cpath = preparePathOrUrl( path, pool );
  svn_string_t* cval  = propVal.getStr() ? svn_string_create( propVal, pool ) : 0;

  svn_error_t* err = svn_client_propset( propName, cval, cpath, recurse, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::propsetrev( const sc::String& propName,
  const sc::String& propVal, const sc::String& pathOrUrl,
  const Revision& srcRev, Revnumber* resultRev, bool force )
{
  apr::Pool pool( _pool );

  const char*   url = 0;
  svn_error_t*  err = svn_client_url_from_path( &url, pathOrUrl, pool );

  if( err )
  {
    return wrapError(err);
  }

  svn_revnum_t  resultrev = 0;
  const char*   curl = preparePathOrUrl( url, pool );
  svn_string_t* cval = svn_string_create( propVal, pool );
  svn_opt_revision_t svnrev;
  convertRevision( &srcRev, &svnrev );

  err = svn_client_revprop_set( propName, cval, curl, &svnrev, &resultrev,
    force, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  *resultRev = resultrev;
  return Success;
}

sc::Error* Client::lock( const Paths& pathsOrUrls, const sc::String& comment,
  bool stealLocks )
{
  apr::Pool pool( _pool );

  apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(),
    sizeof(char*) );

  for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_error_t* err = svn_client_lock( patharr, comment.getStr(), stealLocks,
    _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::unlock( const Paths& pathsOrUrls, bool breakLocks )
{
  apr::Pool pool( _pool );

  apr_array_header_t* patharr = apr_array_make( pool, (int)pathsOrUrls.size(), sizeof(char*) );

  for( Paths::const_iterator it = pathsOrUrls.begin(); it != pathsOrUrls.end(); it++ )
  {
    const char* cpath = preparePathOrUrl( *it, pool );
    APR_ARRAY_PUSH( patharr, const char* ) = cpath;
  }

  svn_error_t* err = svn_client_unlock( patharr, breakLocks, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}

sc::Error* Client::info( const sc::String& pathOrUrl, const Revision& rev, const Revision& peg,
 InfoBaton* baton, bool recurse )
{
  apr::Pool pool( _pool );

  const char *cpath = preparePathOrUrl( pathOrUrl, pool );

  svn_opt_revision_t svnrev;
  svn_opt_revision_t pegrev;
  convertRevision( &rev, &svnrev );
  convertRevision( &peg, &pegrev );

  InfoFns ib(baton);
  svn_error_t* err = svn_client_info( cpath, &pegrev, &svnrev, InfoFns::receive,
    &ib, recurse, _context->_context, pool );

  if( err )
  {
    return wrapError(err);
  }

  return Success;
}


sc::Error* Client::details( Revnumber* resultRev, sc::String& root,
  const sc::String& pathOrUrl )
{
  class Ra
  {
  public:
    static svn_error_t* open_tmp_file( apr_file_t **f, void *callback_baton,
      apr_pool_t *pool )
    {
      const char* path;
      const char* ignore;

      SVN_ERR( svn_io_temp_dir( &path, pool ) );
      path = svn_path_join( path, "tempfile", pool );
      SVN_ERR( svn_io_open_unique_file( f, &ignore, path, ".tmp", true, pool ) );
      return SVN_NO_ERROR;
    }
  };

  apr::Pool pool( _pool );

  const char* url;
  void*       raSession;
  void*       raBaton;

  svn_ra_plugin_t* raLib;

  svn_ra_callbacks_t* raCb = (svn_ra_callbacks_t*)apr_pcalloc( pool, sizeof(*raCb));
  raCb->open_tmp_file      = Ra::open_tmp_file;
  raCb->auth_baton         = _context->_context->auth_baton;

  const char *resultUrl;
  *resultRev = SVN_INVALID_REVNUM; 

  SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) );

  SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) );
  SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) );

  SC_SVN_ERR( raLib->open( &raSession, url, raCb, 0, _context->_context->config, pool) );
  SC_SVN_ERR( raLib->get_latest_revnum( raSession, resultRev, pool ) );
  SC_SVN_ERR( raLib->get_repos_root( raSession, &resultUrl, pool ) );

  root = sc::String(resultUrl);
  return Success;
}

bool Client::isWorkingCopy( const sc::String& path )
{
  apr::Pool pool;
  svn_wc_adm_access_t* adm;

  // handle an empty path, svn_path_canonicalize crashes when it gets an
  // empty string.
  if( path.isEmpty() )
  {
    return false;
  }

  const char*  cpath = preparePathOrUrl( path, pool );
  svn_error_t* err   = svn_wc_adm_open2( &adm, NULL, cpath, false, 0, pool );

  if( err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY )
  {
    svn_error_clear(err);
    return false;
  }
  return true;
}

bool Client::getUrlFromPath( const sc::String& pathOrUrl, sc::String& url )
{
  apr::Pool pool;

  const char*   curl = 0;
  svn_error_t*  err  = svn_client_url_from_path( &curl, pathOrUrl, pool );

  // we eat the error to make it easier for the caller...
  if( err )
  {
    svn_error_clear(err);
    return false;
  }

  url = curl;
  return true;
}

#if 0
/**
 * \brief get the revision number of 'head' on the given path or url.
 *
 * \param  resultRev variable to store the 'head' revision number of pathOrUrl in.
 * \param  pathOrUrl the utf-8 encoded path or url we want the 'head' revision from.
 * \return the error object or SVN_SUCCESS.
 */

// based on libsvn_client/cat.c:svn_client_cat
Error* Client::getHeadRevision( Revnumber* resultRev, const sc::String& pathOrUrl )
{
  apr::Pool pool( _pool );

  const char*      url;
  void*            raSession;
  void*            raBaton;
  svn_ra_plugin_t* raLib;

  *resultRev = SVN_INVALID_REVNUM; 

  SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) );

  SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) );
  SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) );

  SC_SVN_ERR( svn_client__open_ra_session( &raSession, raLib, url,
    0, 0, 0, false, false, _context->_context, pool ) ); // raLib->open?

  SC_SVN_ERR( raLib->get_latest_revnum( raSession, resultRev, pool ) );

  return SC_SVN_SUCCESS;
}

Error* Client::getReposRoot( const sc::String& pathOrUrl, sc::String& root )
{
  apr::Pool pool( _pool );

  const char*      url;
  void*            raSession;
  void*            raBaton;
  svn_ra_plugin_t* raLib;

  const char *resultUrl;

  SC_SVN_ERR( svn_client_url_from_path( &url, pathOrUrl, pool ) );

  SC_SVN_ERR( svn_ra_init_ra_libs( &raBaton, pool ) );
  SC_SVN_ERR( svn_ra_get_ra_library( &raLib, raBaton, url, pool ) );

  SC_SVN_ERR( svn_client__open_ra_session( &raSession, raLib, url,
    0, 0, 0, false, false, _context->_context, pool ) ); //raLib->open?

  SC_SVN_ERR( raLib->get_repos_root( raSession, &resultUrl, pool ) );

  root = sc::String(resultUrl);

  return SC_SVN_SUCCESS;
}
#endif

const ClientContext* Client::getContext() const
{
  return _context;
}

void Client::setCommitBaton( CommitBaton* baton )
{
  _context->_context->log_msg_baton = baton;
}

const char* Client::preparePathOrUrl( const char* pathOrUrl, apr_pool_t* pool )
{
  // based on svn_opt_args_to_target_array2

  const char* prepared = pathOrUrl;

  if( svn_path_is_url(pathOrUrl) )
  {
    // do anything necessary to create proper url
    prepared = svn_path_uri_from_iri( prepared, pool );
    prepared = svn_path_uri_autoescape( prepared, pool );
    prepared = svn_path_canonicalize( prepared, pool );
  }
  else
  {
    prepared = svn_path_canonicalize( prepared, pool );
  }

  return prepared;
}



} // namespace


