/* svn-wc-db-tester.c
 *
 * This is a crude command line tool that makes it possible to
 * run the wc-db validation checks directly.
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#include "svn_cmdline.h"
#include "svn_pools.h"
#include "svn_wc.h"
#include "svn_utf.h"
#include "svn_path.h"
#include "svn_opt.h"
#include "svn_version.h"

#include "private/svn_wc_private.h"
#include "private/svn_cmdline_private.h"

#include "../../../subversion/libsvn_wc/wc.h"
#include "../../../subversion/libsvn_wc/wc_db.h"

#include "svn_private_config.h"

#define OPT_VERSION SVN_OPT_FIRST_LONGOPT_ID

static svn_error_t *
version(apr_pool_t *pool)
{
  return svn_opt_print_help5(NULL, "svn-wc-db-tester", TRUE, FALSE, FALSE,
                             NULL, NULL, NULL, NULL, NULL, NULL, pool);
}

static void
usage(apr_pool_t *pool)
{
  svn_error_clear(svn_cmdline_fprintf
                  (stderr, pool,
                   _("Type 'svn-wc-db-tester --help' for usage.\n")));
}

struct verify_baton
{
  svn_boolean_t found_err;
};

static svn_error_t *
verify_cb(void *baton,
          const char *wc_abspath,
          const char *local_relpath,
          int op_depth,
          int id,
          const char *msg,
          apr_pool_t *scratch_pool)
{
  struct verify_baton *vb = baton;

  if (op_depth >= 0)
    {
      SVN_ERR(svn_cmdline_printf(scratch_pool, "%s (depth=%d) DBV%04d: %s\n",
                                 local_relpath, op_depth, id, msg));
    }
  else
    {
      SVN_ERR(svn_cmdline_printf(scratch_pool, "%s DBV%04d: %s\n",
                                 local_relpath, id, msg));
    }

  vb->found_err = TRUE;
  return SVN_NO_ERROR;
}

static svn_error_t *
verify_db(int argc, const char *path, apr_pool_t *pool)
{
  const char *local_abspath;
  svn_wc_context_t *wc_ctx;
  struct verify_baton vb = { FALSE };

  /* Read the parameters */
  path = svn_dirent_internal_style(path, pool);

  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));

  SVN_ERR(svn_wc__db_verify_db_full(wc_ctx->db, local_abspath,
                                    verify_cb, &vb, pool));

  if (vb.found_err)
    return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
              _("Found one or more potential wc.db inconsistencies"));

  return SVN_NO_ERROR;
}


static void
help(const apr_getopt_option_t *options, apr_pool_t *pool)
{
  svn_error_clear
    (svn_cmdline_fprintf
     (stdout, pool,
      _("usage: svn-wc-db-tester [OPTIONS] WC_PATH\n\n"
        "  Run verifications on the working copy\n"
        "\n"
        "  WC_PATH's parent directory must be a working copy, otherwise a\n"
        "  tree conflict cannot be raised.\n"
        "\n"
        "Valid options:\n")));
  while (options->description)
    {
      const char *optstr;
      svn_opt_format_option(&optstr, options, TRUE, pool);
      svn_error_clear(svn_cmdline_fprintf(stdout, pool, "  %s\n", optstr));
      ++options;
    }
}


/* Version compatibility check */
static svn_error_t *
check_lib_versions(void)
{
  static const svn_version_checklist_t checklist[] =
    {
      { "svn_subr",   svn_subr_version },
      { "svn_wc",     svn_wc_version },
      { NULL, NULL }
    };
  SVN_VERSION_DEFINE(my_version);

  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
}

/*
 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
 * return SVN_NO_ERROR.
 */
static svn_error_t *
sub_main(int *exit_code,
         int argc,
         const svn_cmdline__argv_char_t *cmdline_argv[],
         apr_pool_t *pool)
{
  apr_getopt_t *os;
  const apr_getopt_option_t options[] =
    {
      {"help", 'h', 0, N_("display this help")},
      {"version", OPT_VERSION, 0,
       N_("show program version information")},
      {0,             0,  0,  0}
    };
  apr_array_header_t *remaining_argv;
  const char **argv;

  /* Check library versions */
  SVN_ERR(check_lib_versions());

  SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool));

#if defined(WIN32) || defined(__CYGWIN__)
  /* Set the working copy administrative directory name. */
  if (getenv("SVN_ASP_DOT_NET_HACK"))
    {
      SVN_ERR(svn_wc_set_adm_dir("_svn", pool));
    }
#endif

  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));

  os->interleave = 1;
  while (1)
    {
      int opt;
      const char *arg;
      apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
      if (APR_STATUS_IS_EOF(status))
        break;
      if (status != APR_SUCCESS)
        {
          usage(pool);
          *exit_code = EXIT_FAILURE;
          return SVN_NO_ERROR;
        }

      switch (opt)
        {
        case 'h':
          help(options, pool);
          return SVN_NO_ERROR;
        case OPT_VERSION:
          SVN_ERR(version(pool));
          return SVN_NO_ERROR;
        default:
          usage(pool);
          *exit_code = EXIT_FAILURE;
          return SVN_NO_ERROR;
        }
    }

  /* Convert the remaining arguments to UTF-8. */
  remaining_argv = apr_array_make(pool, 0, sizeof(const char *));
  while (os->ind < argc)
    {
      const char *s;

      SVN_ERR(svn_utf_cstring_to_utf8(&s, os->argv[os->ind++], pool));
      APR_ARRAY_PUSH(remaining_argv, const char *) = s;
    }

  if (remaining_argv->nelts != 1)
    {
      usage(pool);
      *exit_code = EXIT_FAILURE;
      return SVN_NO_ERROR;
    }

  /* Do the main task */
  SVN_ERR(verify_db(remaining_argv->nelts,
                    APR_ARRAY_IDX(remaining_argv, 0, const char *),
                    pool));

  return SVN_NO_ERROR;
}

int
SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[])
{
  apr_pool_t *pool;
  int exit_code = EXIT_SUCCESS;
  svn_error_t *err;

  /* Initialize the app. */
  if (svn_cmdline_init("svn-wc-db-tester", stderr) != EXIT_SUCCESS)
    return EXIT_FAILURE;

  /* Create our top-level pool.  Use a separate mutexless allocator,
   * given this application is single threaded.
   */
  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));

  err = sub_main(&exit_code, argc, argv, pool);

  /* Flush stdout and report if it fails. It would be flushed on exit anyway
     but this makes sure that output is not silently lost if it fails. */
  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));

  if (err)
    {
      exit_code = EXIT_FAILURE;
      svn_cmdline_handle_exit_error(err, NULL, "svn-wc-db-tester: ");
    }

  svn_pool_destroy(pool);
  return exit_code;
}
