/* ====================================================================
 * Copyright (c) 2003-2007, 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 "config.h"
#include "VisualDiff.h"
#include "RevisionFactory.h"
#include "util/CommandArgs.h"

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

// apr
#include <apr_errno.h>


namespace svn
{

VisualDiff::VisualDiff( svn_client_ctx_t* context, const sc::String& diffCmd,
  apr_pool_t* pool ) : _pool(pool), _context(context), _diffCmd(diffCmd)
{
}

VisualDiff::~VisualDiff()
{
  if( ! _removePath1.isEmpty() )
  {
    svn_error_t* svnerr = svn_io_remove_file( _removePath1, _pool );

    if( svnerr != SVN_NO_ERROR )
    {
      // @todo error handling
    }
  }


  if( ! _removePath2.isEmpty() )
  {
    svn_error_t* svnerr = svn_io_remove_file( _removePath2, _pool );

    if( svnerr != SVN_NO_ERROR )
    {
      // @todo error handling
    }
  }
}

svn_error_t* VisualDiff::run( const char* path1, const svn_opt_revision_t*
  revision1, const char* path2, const svn_opt_revision_t* revision2 )
{
  svn_opt_revision_t peg;
  peg.kind = svn_opt_revision_unspecified;

  bool isRepos1 = svn_path_is_url(path1) == TRUE;
  bool isRepos2 = svn_path_is_url(path2) == TRUE;

  if(  (revision1->kind == svn_opt_revision_unspecified)
    || (revision2->kind == svn_opt_revision_unspecified)
    )
    return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL,
      "Not all required revisions are specified");

  bool isLocal1 = ((revision1->kind == svn_opt_revision_base)
                 || (revision1->kind == svn_opt_revision_working));
  bool isLocal2 = ((revision2->kind == svn_opt_revision_base)
                 || (revision2->kind == svn_opt_revision_working));

  if ((! isRepos1) && (! isLocal1))
    isRepos1 = true;
  if ((! isRepos2) && (! isLocal2))
    isRepos2 = true;

  if( isRepos1 )
  {
    if( isRepos2 )
    {
      // repository <-> repository
      return diffRpRp( path1, revision1, path2, revision2, &peg );
    }
    else
    {
      // repository <-> working
      return diffRpWc( path1, revision1, path2, revision2, &peg, true );
    }
  }
  else
  {
    if( isRepos2 )
    {
      // working <-> repository
      return diffRpWc( path2, revision2, path1, revision1, &peg, false );
    }
    else
    {
      // working <-> working
      return diffWcWc( path1, revision1, path2, revision2 );
    }
  }
  //return SVN_NO_ERROR;
}



svn_error_t* VisualDiff::run( const char* path, const svn_opt_revision_t*
  revision1, const svn_opt_revision_t* revision2, const svn_opt_revision_t*
  peg )
{
  if(  (revision1->kind == svn_opt_revision_unspecified)
    || (revision2->kind == svn_opt_revision_unspecified)
    )
    return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL,
      "Not all required revisions are specified" );

  bool isLocal1 = ((revision1->kind == svn_opt_revision_base)
                 || (revision1->kind == svn_opt_revision_working));
  bool isLocal2 = ((revision2->kind == svn_opt_revision_base)
                 || (revision2->kind == svn_opt_revision_working));

  if( isLocal1 && isLocal2 )
    return svn_error_create( SVN_ERR_CLIENT_BAD_REVISION, NULL,
      "At least one revision must be non-local for a pegged diff" );

  if( ! isLocal1 )
  {
    if( ! isLocal2 )
    {
      // repository <-> repository
      return diffRpRp( path, revision1, path, revision2, peg );
    }
    else
    {
      // repository <-> working
      return diffRpWc( path, revision1, path, revision2, peg, true );
    }
  }
  else
  {
    if( ! isLocal2 )
    {
      // working <-> repository
      return diffRpWc( path, revision2, path, revision1, peg, false );
    }
    else
    {
      // working <-> working
      return diffWcWc( path, revision1, path, revision2 );
    }
  }

  return SVN_NO_ERROR;
}


svn_error_t* VisualDiff::diffRpWc( const char* path1, const svn_opt_revision_t*
  revision1, const char* path2, const svn_opt_revision_t* revision2, const
  svn_opt_revision_t* peg, bool rpwc )
{
  // get the repository file
  const char* url1;
  SVN_ERR( svn_client_url_from_path(&url1, path1, _pool) );

  const char* name1;
  svn_error_t* err1 = cat(&name1, url1, revision1, peg);
  _removePath1 = name1;

  if( err1 && err1->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES )
    return err1;

  // get the local file
  svn_opt_revision_t lpeg;
  lpeg.kind = svn_opt_revision_unspecified;

  const char* name2;
  svn_error_t* err2 = cat(&name2, path2, revision2, &lpeg);
  _removePath2 = name2;

  if( err2 && err2->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES )
    return err2;

  sc::String label1 = createLabel( url1, revision1 );
  sc::String label2 = createLabel( path2, revision2 );

  if( err1 )
    label1 = err1->message;

  if( err2 )
    label2 = err2->message;

  if( rpwc )
  {
    SVN_ERR( run( name1, label1, name2, label2 ) );
  }
  else
  {
    SVN_ERR( run( name2, label2, name1, label1 ) );
  }

  return SVN_NO_ERROR;
}

svn_error_t* VisualDiff::diffRpRp( const char* path1, const svn_opt_revision_t*
  revision1, const char* path2, const svn_opt_revision_t* revision2, const
  svn_opt_revision_t* peg )
{
  const char* url1;
  const char* url2;

  SVN_ERR( svn_client_url_from_path(&url1, path1, _pool) );
  SVN_ERR( svn_client_url_from_path(&url2, path2, _pool) );

  const char* name1;
  svn_error_t* err1 = cat(&name1, url1, revision1, peg);
  _removePath1 = name1;

  if( err1 && err1->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES )
    return err1;

  const char* name2;
  svn_error_t* err2 = cat(&name2, url2, revision2, peg);
  _removePath2 = name2;

  if( err2 && err2->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES )
    return err2;

  sc::String label1 = createLabel( url1, revision1 );
  sc::String label2 = createLabel( url2, revision2 );

  if( err1 )
    label1 = err1->message;

  if( err2 )
    label2 = err2->message;

  SVN_ERR( run( name1, label1, name2, label2 ) );

  return SVN_NO_ERROR;
}

svn_error_t* VisualDiff::diffWcWc( const char* path1, const svn_opt_revision_t*
  revision1, const char* path2, const svn_opt_revision_t* revision2 )
{
  svn_opt_revision_t peg;
  peg.kind = svn_opt_revision_unspecified;

  const char* name1;
  svn_error_t* err1 = NULL;
  if( revision1->kind == svn_opt_revision_working )
  {
    name1 = path1;
  }
  else
  {  
    err1 = cat(&name1, path1, revision1, &peg);
    _removePath1 = name1;

    if( err1 && APR_TO_OS_ERROR(err1->apr_err) != ENOENT /*ERROR_FILE_NOT_FOUND*/ )
      return err1;
  }

  const char* name2;
  svn_error_t* err2 = NULL;
  if( revision2->kind == svn_opt_revision_working )
  {
    name2 = path2;
  }
  else
  {  
    err2 = cat(&name2, path2, revision2, &peg);
    _removePath2 = name2;

    if (err2 && APR_TO_OS_ERROR(err2->apr_err) != ENOENT /*ERROR_FILE_NOT_FOUND*/ )
      return err2;
  }
  
  sc::String label1 = createLabel( path1, revision1 );
  sc::String label2 = createLabel( path2, revision2 );

  if( err1 )
    label1 = err1->message;

  if( err2 )
    label1 = err2->message;

  SVN_ERR( run( name1, label1, name2, label2 ) );

  return SVN_NO_ERROR;
}

svn_error_t* VisualDiff::run( const char* path1, const char* label1,
  const char* path2, const char* label2 )
{
  int            result;
  apr_exit_why_e why;

  CommandArgs cmdArgs( _diffCmd, _pool );
  cmdArgs.setArg( sc::String("{left}"),   sc::String(path1) );
  cmdArgs.setArg( sc::String("{llabel}"), sc::String(label1) );
  cmdArgs.setArg( sc::String("{right}"),  sc::String(path2) );
  cmdArgs.setArg( sc::String("{rlabel}"), sc::String(label2) );
 
  return svn_io_run_cmd( cmdArgs.getPath(), cmdArgs.getArgs()[0],
    cmdArgs.getArgs(), &result, &why, true, 0, 0, 0, _pool );
}

svn_error_t* VisualDiff::cat( const char** file, const char* pathOrUrl,
  const svn_opt_revision_t* rev, const svn_opt_revision_t* peg )
{
  const char* tdir;
  SVN_ERR( svn_io_temp_dir( &tdir, _pool ) );
  tdir = apr_pstrcat( _pool, tdir, "/subcommander", NULL );

  apr_file_t* aprfile;
  SVN_ERR( svn_io_open_unique_file( &aprfile, file, tdir, ".tmp", false, _pool) );

  svn_stream_t* stream;
  stream = svn_stream_from_aprfile( aprfile, _pool );
  svn_error_t* err = svn_client_cat2( stream, pathOrUrl, peg, rev, _context, _pool );
  apr_file_close(aprfile);

  return err;
}

sc::String VisualDiff::createLabel( const char* pathOrUrl, const svn_opt_revision_t* rev )
{
  RevisionPtr r(RevisionFactory::toRevision(rev));

  sc::String label(pathOrUrl);
  label += " (";
  label += r->toString();
  label += ")";

  return label;
}


} // namespace
