/*
 * repos_diff2.c -- diff preview entry point
 * should go into libsvn_client/repos_diff.c
 *
 * ====================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 */

// skip with subversion 1.4 and up
#include <svn_version.h>
#if SVN_VER_MAJOR == 1 && SVN_VER_MINOR <= 3

#include "svn_wc.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_io.h"
#include "svn_props.h"

// hack to support the summarize hack with the 1.2 and 1.3 version of
// the non public client.h
#if SVN_VER_MAJOR >= 1 && SVN_VER_MINOR >= 3
#define svn_client__open_ra_session svn_client__open_ra_session_internal
#endif

#include "client.h"

/* ------------------------------------------------------------------ */
/* hacks to make it compile in subcommander */

#define _(A) A

// this function is copied from libsvn_client/diff

/* Helper function: given a working-copy PATH, return its associated
   url in *URL, allocated in POOL.  If PATH is *already* a URL, that's
   fine, just set *URL = PATH. */
static svn_error_t *
convert_to_url (const char **url,
                const char *path,
                apr_pool_t *pool)
{
  svn_wc_adm_access_t *adm_access;  /* ### FIXME local */
  const svn_wc_entry_t *entry;      

  if (svn_path_is_url (path))
    {
      *url = path;
      return SVN_NO_ERROR;
    }

  /* ### This may not be a good idea, see issue 880 */
  SVN_ERR (svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE,
                                  0, NULL, NULL, pool));
  SVN_ERR (svn_wc_entry (&entry, path, adm_access, FALSE, pool));
  SVN_ERR (svn_wc_adm_close (adm_access));
  if (! entry)
    return svn_error_createf (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
                              _("'%s' is not under version control"),
                              svn_path_local_style (path, pool));

  if (entry->url)  
    *url = apr_pstrdup (pool, entry->url);
  else
    *url = apr_pstrdup (pool, entry->copyfrom_url);
  return SVN_NO_ERROR;
}
/* ------------------------------------------------------------------ */

/** structure for passing around the diff parameters
 */
struct diff_parameters
{
  /* first input path */
  const char *path1;

  /* revision of first input path */
  const svn_opt_revision_t *revision1;

  /* second input path */
  const char *path2;

  /* revision of second input path */
  const svn_opt_revision_t *revision2;

  /* peg revision */
  const svn_opt_revision_t *peg_revision;

  /* recurse */
  svn_boolean_t recurse;

  /* ignore acestry */
  svn_boolean_t ignore_ancestry;

  /* ignore deleted */
  svn_boolean_t no_diff_deleted;

  /* ignore content type */
  svn_boolean_t ignore_content_type;
};

/** a structure filled by svn_client_check_paths or
 * svn_client_check_paths_peg
 */
struct diff_paths
{
  /* revision1 is a local revision */
  svn_boolean_t is_local_rev1;

  /* revision2 is a local revision */
  svn_boolean_t is_local_rev2;

  /* path1 is a repos url */
  svn_boolean_t is_repos_path1;

  /* path2 is a repos url, not used by svn_client_check_paths_peg */
  svn_boolean_t is_repos_path2;
};

/** check if paths are urls and if the revisions are local for
 * a diff with two source paths or urls.
 */
static svn_error_t *
svn_client_check_paths ( const struct diff_parameters* params,
                         struct diff_paths* paths )
{
  /* Either path could be a URL or a working copy path.  Let's figure
     out what's what. */
  paths->is_repos_path1 = svn_path_is_url (params->path1);
  paths->is_repos_path2 = svn_path_is_url (params->path2);

  /* Verify our revision arguments in light of the paths. */
  if ((params->revision1->kind == svn_opt_revision_unspecified)
      || (params->revision2->kind == svn_opt_revision_unspecified))
    return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Not all required revisions are specified"));

  /* Revisions can be said to be local or remote.  BASE and WORKING,
     for example, are local.  */
  paths->is_local_rev1 = ((params->revision1->kind == svn_opt_revision_base)
                       || (params->revision1->kind == svn_opt_revision_working));
  paths->is_local_rev2 = ((params->revision2->kind == svn_opt_revision_base)
                       || (params->revision2->kind == svn_opt_revision_working));

  /* Working copy paths with non-local revisions get turned into
     URLs.  We don't do that here, though.  We simply record that it
     needs to be done, which is information that helps us choose our
     diff helper function.  */
  if ((! paths->is_repos_path1) && (! paths->is_local_rev1))
    paths->is_repos_path1 = TRUE;
  if ((! paths->is_repos_path2) && (! paths->is_local_rev2))
    paths->is_repos_path2 = TRUE;

  return SVN_NO_ERROR;
}

/** check if path is a url and if the revisions are local for
 * a peg diff with a single source path or url.
 */
static svn_error_t *
svn_client_check_paths_peg (const struct diff_parameters* params,
                            struct diff_paths* paths)
{
  /* Either path could be a URL or a working copy path.  Let's figure
     out what's what. */
  paths->is_repos_path1 = svn_path_is_url (params->path1);
  paths->is_repos_path2 = svn_path_is_url (params->path2);

  /* Verify our revision arguments in light of the paths. */
  if ((params->revision1->kind == svn_opt_revision_unspecified)
      || (params->revision2->kind == svn_opt_revision_unspecified))
    return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Not all required revisions are specified"));

  /* Revisions can be said to be local or remote.  BASE and WORKING,
     for example, are local.  */
  paths->is_local_rev1 = ((params->revision1->kind == svn_opt_revision_base)
                       || (params->revision1->kind == svn_opt_revision_working));
  paths->is_local_rev2 = ((params->revision2->kind == svn_opt_revision_base)
                       || (params->revision2->kind == svn_opt_revision_working));

  if (paths->is_local_rev1 && paths->is_local_rev2)
    return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("At least one revision must be non-local for "
                               "a pegged diff"));
  return SVN_NO_ERROR;
}

/** structure filled by diff_prepare_repos_repos
 */
struct diff_repos_repos
{
  /* url created from path1 */
  const char *url1;

  /* url created from path2 */
  const char *url2;

  /* the BASE_PATH for the diff */
  const char *base_path;

  /* url1 and url2 are the same */
  svn_boolean_t same_urls;

  /* revision of url1 */
  svn_revnum_t rev1;
  
  /* revision of url2 */
  svn_revnum_t rev2;

  /* anchor & target based on url1 */
  const char *anchor1;
  const char *target1;

  /* anchor & target based on url2 */
  const char *anchor2;
  const char *target2;
};

/** prepare some info for a repos repos diff
 */
static svn_error_t *
diff_prepare_repos_repos (const struct diff_parameters *params,
                          struct diff_repos_repos *drr,
                          svn_client_ctx_t *ctx,
                          apr_pool_t *pool)
{
  svn_ra_session_t *ra_session1, *ra_session2;
  svn_node_kind_t kind1, kind2;

  apr_pool_t *temppool = svn_pool_create (pool);

  /* Figure out URL1 and URL2. */
  SVN_ERR (convert_to_url (&drr->url1, params->path1, pool));
  SVN_ERR (convert_to_url (&drr->url2, params->path2, pool));
  drr->same_urls = (strcmp (drr->url1, drr->url2) == 0);

  /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
     calculated for PATH2 override the one for PATH1 (since the diff
     will be "applied" to URL2 anyway). */
  drr->base_path = NULL;
  if (drr->url1 != params->path1)
    drr->base_path = params->path1;
  if (drr->url2 != params->path2)
    drr->base_path = params->path2;

  /* If we are performing a pegged diff, we need to find out what our
     actual URLs will be. */
  if (params->peg_revision->kind != svn_opt_revision_unspecified)
    {
      svn_opt_revision_t *start_ignore, *end_ignore;
      
      SVN_ERR (svn_client__repos_locations (&drr->url1, &start_ignore,
                                            &drr->url2, &end_ignore,
                                            params->path2,
                                            params->peg_revision,
                                            params->revision1,
                                            params->revision2,
                                            ctx, pool));

      // TODO when called from diff_repos_repos
      //callback_baton->orig_path_1 = url1;
      //callback_baton->orig_path_2 = url2;
    }

  
  /* Open temporary RA sessions to each URL. */
  SVN_ERR (svn_client__open_ra_session (&ra_session1, drr->url1, NULL,
                                        NULL, NULL, FALSE, TRUE, 
                                        ctx, temppool));
  SVN_ERR (svn_client__open_ra_session (&ra_session2, drr->url2, NULL,
                                        NULL, NULL, FALSE, TRUE, 
                                        ctx, temppool));

  /* Resolve named revisions to real numbers. */
  SVN_ERR (svn_client__get_revision_number
           (&drr->rev1, ra_session1, params->revision1, 
            (params->path1 == drr->url1) ? NULL : params->path1, pool));
  //TODO when called from diff_repos_repos
  //callback_baton->revnum1 = rev1;
  SVN_ERR (svn_client__get_revision_number
           (&drr->rev2, ra_session2, params->revision2,
            (params->path2 == drr->url2) ? NULL : params->path2, pool));
  //TODO when called from diff_repos_repos
  //callback_baton->revnum2 = rev2;

  /* Choose useful anchors and targets for our two URLs, and verify
     that both sides of the diff exist.  */
  drr->anchor1 = drr->url1;
  drr->anchor2 = drr->url2;
  drr->target1 = "";
  drr->target2 = "";
  SVN_ERR (svn_ra_check_path (ra_session1, "", drr->rev1, &kind1, temppool));
  SVN_ERR (svn_ra_check_path (ra_session2, "", drr->rev2, &kind2, temppool));
  if (kind1 == svn_node_none)
    return svn_error_createf 
      (SVN_ERR_FS_NOT_FOUND, NULL,
       _("'%s' was not found in the repository at revision %ld"),
       drr->url1, drr->rev1);
  if (kind2 == svn_node_none)
    return svn_error_createf 
      (SVN_ERR_FS_NOT_FOUND, NULL,
       _("'%s' was not found in the repository at revision %ld"),
       drr->url2, drr->rev2);
  if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
    {
      svn_path_split (drr->url1, &drr->anchor1, &drr->target1, pool); 
      drr->target1 = svn_path_uri_decode (drr->target1, pool);
      svn_path_split (drr->url2, &drr->anchor2, &drr->target2, pool); 
      drr->target2 = svn_path_uri_decode (drr->target2, pool);
      if (drr->base_path)
        drr->base_path = svn_path_dirname (drr->base_path, pool);
    }

  /* Destroy the temporary pool, which closes our RA session. */
  svn_pool_destroy (temppool);

  return SVN_NO_ERROR;
}

static svn_error_t *
diff_summarize_repos_repos (const struct diff_parameters* params,
                            svn_client_diff_summarize_func_t summarize_func,
                            void *summarize_baton,
                            svn_client_ctx_t *ctx,
                            apr_pool_t *pool)
{
  svn_ra_session_t *ra_session1, *ra_session2;

  const svn_ra_reporter2_t *reporter;
  void *report_baton;

  const svn_delta_editor_t *diff_editor;
  void *diff_edit_baton;

  struct diff_repos_repos drr;

  /* prepare info for the repos repos diff */
  SVN_ERR (diff_prepare_repos_repos (params, &drr, ctx, pool ));

  /* Now, we reopen two RA session to the correct anchor/target
     locations for our URLs. */
  SVN_ERR (svn_client__open_ra_session (&ra_session1, drr.anchor1,
                                        NULL, NULL, NULL, FALSE, TRUE, 
                                        ctx, pool));
  SVN_ERR (svn_client__open_ra_session (&ra_session2, drr.anchor1,
                                        NULL, NULL, NULL, FALSE, TRUE,
                                        ctx, pool));      

  /* Set up the repos_diff editor on BASE_PATH, if available.
     Otherwise, we just use "". */
  SVN_ERR(svn_client__get_diff_summarize_editor
          (drr.base_path ? drr.base_path : "", summarize_func,
           summarize_baton, ra_session2, drr.rev1, ctx->cancel_func,
           ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));

  /* We want to switch our txn into URL2 */
  SVN_ERR (svn_ra_do_diff (ra_session1, &reporter, &report_baton,
                           drr.rev2, drr.target1, params->recurse,
                           params->ignore_ancestry, drr.url2,
                           diff_editor, diff_edit_baton, pool));

  /* Drive the reporter; do the diff preview. */
  SVN_ERR (reporter->set_path (report_baton, "", drr.rev1, FALSE, NULL, pool));
  SVN_ERR (reporter->finish_report (report_baton, pool));

  return SVN_NO_ERROR;
}










svn_error_t *
svn_client_diff_summarize (const char *path1,
                         const svn_opt_revision_t *revision1,
                         const char *path2,
                         const svn_opt_revision_t *revision2,
                         svn_boolean_t recurse,
                         svn_boolean_t ignore_ancestry,
                         svn_client_diff_summarize_func_t summarize_func,
                         void *summarize_baton,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *pool)
{
  struct diff_parameters diff_params;
  struct diff_paths      diff_paths;
  svn_opt_revision_t*    peg_revision = 
    apr_palloc (pool, sizeof (*peg_revision));

  /* We will never do a pegged diff from here. */
  peg_revision->kind = svn_opt_revision_unspecified;

  /* fill diff_param */
  diff_params.path1 = path1;
  diff_params.revision1 = revision1;
  diff_params.path2 = path2;
  diff_params.revision2 = revision2;
  diff_params.peg_revision = peg_revision;
  diff_params.recurse = recurse;
  diff_params.ignore_ancestry = ignore_ancestry;
  diff_params.no_diff_deleted = FALSE;

  /* Check if paths/revisions are urls/local. */ 
  SVN_ERR (svn_client_check_paths ( &diff_params, &diff_paths ));

  if (diff_paths.is_repos_path1 && diff_paths.is_repos_path2)
    {
      SVN_ERR(diff_summarize_repos_repos(&diff_params, summarize_func,
                                         summarize_baton, ctx, pool));
    }
  else
    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                            _("Summarizing diff can only compare repository "
                              "to repository"));

  return SVN_NO_ERROR;
}

svn_error_t *
svn_client_diff_summarize_peg (const char *path,
                             const svn_opt_revision_t *peg_revision,
                             const svn_opt_revision_t *start_revision,
                             const svn_opt_revision_t *end_revision,
                             svn_boolean_t recurse,
                             svn_boolean_t ignore_ancestry,
                             svn_client_diff_summarize_func_t summarize_func,
                             void *summarize_baton,
                             svn_client_ctx_t *ctx,
                             apr_pool_t *pool)
{
  struct diff_parameters diff_params;
  struct diff_paths      diff_paths;

  /* fill diff_param */
  diff_params.path1 = path;
  diff_params.revision1 = start_revision;
  diff_params.path2 = path;
  diff_params.revision2 = end_revision;
  diff_params.peg_revision = peg_revision;
  diff_params.recurse = recurse;
  diff_params.ignore_ancestry = ignore_ancestry;
  diff_params.no_diff_deleted = FALSE;

  /* Check if paths/revisions are urls/local. */ 
  SVN_ERR (svn_client_check_paths_peg ( &diff_params, &diff_paths ));

  if (diff_paths.is_repos_path1 && diff_paths.is_repos_path2)
    {
      SVN_ERR(diff_summarize_repos_repos(&diff_params, summarize_func,
                                         summarize_baton, ctx, pool));
    }
  else
    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                            _("Summarizing diff can only compare repository "
                              "to repository"));

  return SVN_NO_ERROR;
}

#endif // SVN_VER_MAJOR == 1 && SVN_VER_MINOR <= 3
