/*
 * svnconflict.c:  Non-interactive conflict resolution tool for Subversion.
 *
 * ====================================================================
 *    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.
 * ====================================================================
 */

/* ==================================================================== */



/*** Includes. ***/

#include <string.h>
#include <assert.h>

#include <apr_strings.h>
#include <apr_tables.h>
#include <apr_general.h>

#include "svn_cmdline.h"
#include "svn_pools.h"
#include "svn_client.h"
#include "svn_config.h"
#include "svn_string.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_opt.h"
#include "svn_utf.h"
#include "svn_auth.h"
#include "svn_hash.h"
#include "svn_version.h"

#include "private/svn_opt_private.h"
#include "private/svn_cmdline_private.h"
#include "private/svn_subr_private.h"

#include "svn_private_config.h"

typedef struct svnconflict_opt_state_t {
  svn_boolean_t version;         /* print version information */
  svn_boolean_t help;            /* print usage message */
  const char *auth_username;     /* auth username */
  const char *auth_password;     /* auth password */
  const char *config_dir;        /* over-riding configuration directory */
  apr_array_header_t *config_options; /* over-riding configuration options */
} svnconflict_opt_state_t;

typedef struct svnconflict_cmd_baton_t
{
  svnconflict_opt_state_t *opt_state;
  svn_client_ctx_t *ctx;
} svnconflict_cmd_baton_t;


/*** Option Processing ***/

/* Add an identifier here for long options that don't have a short
   option. Options that have both long and short options should just
   use the short option letter as identifier.  */
typedef enum svnconflict_longopt_t {
  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
  opt_auth_password_from_stdin,
  opt_auth_username,
  opt_config_dir,
  opt_config_options,
  opt_version,
} svnconflict_longopt_t;

/* Option codes and descriptions.
 * The entire list must be terminated with an entry of nulls. */
static const apr_getopt_option_t svnconflict_options[] =
{
  {"help",          'h', 0, N_("show help on a subcommand")},
  {NULL,            '?', 0, N_("show help on a subcommand")},
  {"version",       opt_version, 0, N_("show program version information")},
  {"username",      opt_auth_username, 1, N_("specify a username ARG")},
  {"password",      opt_auth_password, 1,
                    N_("specify a password ARG (caution: on many operating\n"
                       "                             "
                       "systems, other users will be able to see this)")},
  {"password-from-stdin",
                    opt_auth_password_from_stdin, 0,
                    N_("read password from stdin")},
  {"config-dir",    opt_config_dir, 1,
                    N_("read user configuration files from directory ARG")},
  {"config-option", opt_config_options, 1,
                    N_("set user configuration option in the format:\n"
                       "                             "
                       "    FILE:SECTION:OPTION=[VALUE]\n"
                       "                             "
                       "For example:\n"
                       "                             "
                       "    servers:global:http-library=serf")},
  {0,               0, 0, 0},
};



/*** Command dispatch. ***/

/* Forward declarations. */
static svn_error_t * svnconflict_help(apr_getopt_t *, void *, apr_pool_t *);
static svn_error_t * svnconflict_list(apr_getopt_t *, void *, apr_pool_t *);
static svn_error_t * svnconflict_options_text(apr_getopt_t *, void *,
                                              apr_pool_t *);
static svn_error_t * svnconflict_options_prop(apr_getopt_t *, void *,
                                              apr_pool_t *);
static svn_error_t * svnconflict_options_tree(apr_getopt_t *, void *,
                                              apr_pool_t *);
static svn_error_t * svnconflict_resolve_text(apr_getopt_t *, void *,
                                              apr_pool_t *);
static svn_error_t * svnconflict_resolve_prop(apr_getopt_t *, void *,
                                              apr_pool_t *);
static svn_error_t * svnconflict_resolve_tree(apr_getopt_t *, void *,
                                              apr_pool_t *);

/* Our array of available subcommands.
 *
 * The entire list must be terminated with an entry of nulls.
 *
 * In most of the help text "PATH" is used where a working copy path is
 * required, "URL" where a repository URL is required and "TARGET" when
 * either a path or a url can be used.  Hmm, should this be part of the
 * help text?
 */

/* Options that apply to all commands. */
static const int svnconflict_global_options[] =
{ opt_auth_username, opt_auth_password, opt_auth_password_from_stdin,
  opt_config_dir, opt_config_options, 0 };

static const svn_opt_subcommand_desc3_t svnconflict_cmd_table[] =
{
  /* This command is also invoked if we see option "--help", "-h" or "-?". */
  { "help", svnconflict_help, {"?", "h"}, {N_(
     "Describe the usage of this program or its subcommands.\n"
     "usage: help [SUBCOMMAND...]\n"
    )},
    {0} },

  { "list", svnconflict_list, {"ls"}, {N_(
     "List conflicts at a conflicted path.\n"
     "usage: list PATH\n"
     "\n"), N_(
     "  List conflicts at PATH, one per line. Possible conflicts are:\n"
     "  \n"
     "  text-conflict\n"
     "    One or more text merge conflicts are present in a file.\n"
     "    This conflict can be resolved with the resolve-text subcommand.\n"
     "  \n"
     "  prop-conflict: PROPNAME\n"
     "    The property PROPNAME contains a text merge conflic conflict.\n"
     "    This conflict can be resolved with the resolve-prop subcommand.\n"
     "  \n"
     "  tree-conflict: DESCRIPTION\n"
     "    The PATH is a victim of a tree conflict described by DESCRIPTION.\n"
     "    This conflict can be resolved with the resolve-tree subcommand.\n"
     "    If a tree conflict exists, no text or property conflicts exist.\n"
     "  \n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "options-text", svnconflict_options_text, {0}, {N_(
     "List options for resolving a text conflict at path.\n"
     "usage: options-text PATH\n"
     "\n"), N_(
     "  List text conflict resolution options at PATH, one per line.\n"
     "  Each line contains a numeric option ID, a colon, and a description.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "options-prop", svnconflict_options_prop, {0}, {N_(
     "List options for resolving a property conflict at path.\n"
     "usage: options-prop PATH\n"
     "\n"), N_(
     "  List property conflict resolution options at PATH, one per line.\n"
     "  Each line contains a numeric option ID, a colon, and a description.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "options-tree", svnconflict_options_tree, {0}, {N_(
     "List options for resolving a tree conflict at path.\n"
     "usage: options-tree PATH\n"
     "\n"), N_(
     "  List tree conflict resolution options at PATH, one per line.\n"
     "  Each line contains a numeric option ID, a colon, and a description.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "resolve-text", svnconflict_resolve_text, {0}, {N_(
     "Resolve the text conflict at path.\n"
     "usage: resolve-text OPTION_ID PATH\n"
     "\n"), N_(
     "  Resolve the text conflict at PATH with a given resolution option.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "resolve-prop", svnconflict_resolve_prop, {0}, {N_(
     "Resolve the property conflict at path.\n"
     "usage: resolve-prop PROPNAME OPTION_ID PATH\n"
     "\n"), N_(
     "  Resolve conflicted property PROPNAME at PATH with a given resolution option.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { "resolve-tree", svnconflict_resolve_tree, {0}, {N_(
     "Resolve the tree conflict at path.\n"
     "usage: resolve-tree OPTION_ID PATH\n"
     "\n"), N_(
     "  Resolve the tree conflict at PATH with a given resolution option.\n"
     "  If PATH is not in conflict, the exit code will be 1, and 0 otherwise.\n"
    )},
    {0}, },

  { NULL, NULL, {0}, {NULL}, {0} }
};

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

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


/*** Subcommands ***/

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svnconflict_opt_state_t *opt_state = b ? b->opt_state : NULL;
  char help_header[] =
  N_("usage: svnconflict <subcommand> [args]\n"
     "Type 'svnconflict --version' to see the program version and RA modules,\n"
     "\n"
     "svnconflict provides a non-interactive conflict resolution interface.\n"
     "It is intended for use by non-interactive scripts which cannot make\n"
     "use of interactive conflict resolution provided by 'svn resolve'.\n"
     "\n"
     "svnconflict operates on a single working copy path only. It is assumed that\n"
     "scripts are able to discover conflicted paths in the working copy via other\n"
     "means, such as 'svn status'.\n"
     "Some advanced operations offered by 'svn resolve' are not supported.\n"
     "\n"
     "svnconflict may contact the repository to obtain information about a conflict.\n"
     "It will never modify the repository, but only read information from it.\n"
     "svnconflict will not prompt for credentials. If read-access to the repository\n"
     "requires credentials but no suitable credentials are stored in Subversion's\n"
     "authentication cache or provided on the command line, the operation may fail.\n"
     "\nAvailable subcommands:\n");
  char help_footer[] =
  N_("Subversion is a tool for version control.\n"
     "For additional information, see http://subversion.apache.org/\n");
  const char *ra_desc_start
    = _("The following repository access (RA) modules are available:\n\n");
  svn_stringbuf_t *version_footer = svn_stringbuf_create_empty(pool);

  if (opt_state && opt_state->version)
    {
      svn_stringbuf_appendcstr(version_footer, ra_desc_start);
      SVN_ERR(svn_ra_print_modules(version_footer, pool));
    }

  SVN_ERR(svn_opt_print_help5(os,
                              "svnconflict",   /* ### erm, derive somehow? */
                              opt_state ? opt_state->version : FALSE,
                              FALSE, /* quiet */
                              FALSE, /* verbose */
                              version_footer->data,
                              _(help_header),
                              svnconflict_cmd_table,
                              svnconflict_options,
                              svnconflict_global_options,
                              _(help_footer),
                              pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
get_conflicts(svn_boolean_t *text_conflicted,
              apr_array_header_t **props_conflicted,
              svn_boolean_t *tree_conflicted,
              svn_client_conflict_t **conflict,
              const char *local_abspath,
              svn_client_ctx_t *ctx,
              apr_pool_t *pool)
{
  svn_boolean_t text;
  apr_array_header_t *props;
  svn_boolean_t tree;

  SVN_ERR(svn_client_conflict_get(conflict, local_abspath, ctx, pool, pool));
  SVN_ERR(svn_client_conflict_get_conflicted(&text, &props, &tree,
                                             *conflict, pool, pool));

  if (!text && props->nelts == 0 && !tree)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' is not in conflict"),
                             local_abspath);

  if (text_conflicted)
    *text_conflicted = text;
  if (props_conflicted)
    *props_conflicted = props;
  if (tree_conflicted)
    *tree_conflicted = tree;

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_list(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  svn_boolean_t text_conflicted;
  apr_array_header_t *props_conflicted;
  svn_boolean_t tree_conflicted;
  int i;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
  path = APR_ARRAY_IDX(args, 0, const char *);
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(&text_conflicted, &props_conflicted, &tree_conflicted,
                        &conflict, local_abspath, ctx, pool));

  if (text_conflicted)
    SVN_ERR(svn_cmdline_printf(pool, "text-conflict\n"));

  for (i = 0; i < props_conflicted->nelts; i++)
    {
      const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *); 
      SVN_ERR(svn_cmdline_printf(pool, "prop-conflict: %s\n", propname));
    }

  if (tree_conflicted)
    {
      const char *incoming_change;
      const char *local_change;

      SVN_ERR(svn_client_conflict_tree_get_description(&incoming_change,
                                                       &local_change,
                                                       conflict, ctx,
                                                       pool, pool));
      SVN_ERR(svn_cmdline_printf(pool, "tree-conflict: %s %s\n",
                                 incoming_change, local_change));
    }

  return SVN_NO_ERROR;
}

static svn_error_t *
print_conflict_options(apr_array_header_t *options, apr_pool_t *pool)
{
  int i;

  for (i = 0; i < options->nelts; i++)
    {
      svn_client_conflict_option_t *option;
      svn_client_conflict_option_id_t id;
      const char *label;

      option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
      id = svn_client_conflict_option_get_id(option);
      label = svn_client_conflict_option_get_label(option, pool);
      SVN_ERR(svn_cmdline_printf(pool, "%d: %s\n", id, label));
    }
  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_options_text(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  svn_boolean_t text_conflicted;
  apr_array_header_t *options;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
  path = APR_ARRAY_IDX(args, 0, const char *);
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(&text_conflicted, NULL, NULL,
                        &conflict, local_abspath, ctx, pool));

  if (!text_conflicted)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' has no text conflict"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_text_get_resolution_options(&options,
                                                          conflict, ctx,
                                                          pool, pool));
  SVN_ERR(print_conflict_options(options, pool));

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_options_prop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  apr_array_header_t *props_conflicted;
  apr_array_header_t *options;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
  path = APR_ARRAY_IDX(args, 0, const char *);
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(NULL, &props_conflicted, NULL,
                        &conflict, local_abspath, ctx, pool));

  if (props_conflicted->nelts == 0)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' has no property conflict"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_prop_get_resolution_options(&options,
                                                          conflict, ctx,
                                                          pool, pool));
  SVN_ERR(print_conflict_options(options, pool));

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_options_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  svn_boolean_t tree_conflicted;
  apr_array_header_t *options;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
  path = APR_ARRAY_IDX(args, 0, const char *);
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(NULL, NULL, &tree_conflicted,
                        &conflict, local_abspath, ctx, pool));

  if (!tree_conflicted)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' is not a tree conflict victim"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, pool));
  SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options,
                                                          conflict, ctx,
                                                          pool, pool));
  SVN_ERR(print_conflict_options(options, pool));

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_resolve_text(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *option_id_str;
  int optid;
  svn_client_conflict_option_id_t option_id;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  svn_boolean_t text_conflicted;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
  option_id_str = APR_ARRAY_IDX(args, 0, const char *);
  path = APR_ARRAY_IDX(args, 1, const char *);
  SVN_ERR(svn_cstring_atoi(&optid, option_id_str));
  option_id = (svn_client_conflict_option_id_t)optid;
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(&text_conflicted, NULL, NULL,
                        &conflict, local_abspath, ctx, pool));

  if (!text_conflicted)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' has no text conflict"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id, ctx,
                                                 pool));

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_resolve_prop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *option_id_str;
  int optid;
  svn_client_conflict_option_id_t option_id;
  const char *path;
  const char *propname;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  apr_array_header_t *props_conflicted;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 3, pool));
  propname = APR_ARRAY_IDX(args, 0, const char *);
  option_id_str = APR_ARRAY_IDX(args, 1, const char *);
  path = APR_ARRAY_IDX(args, 2, const char *);
  SVN_ERR(svn_cstring_atoi(&optid, option_id_str));
  option_id = (svn_client_conflict_option_id_t)optid;
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(NULL, &props_conflicted, NULL,
                        &conflict, local_abspath, ctx, pool));

  if (props_conflicted->nelts == 0)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' has no property conflict"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname,
                                                 option_id, ctx, pool));

  return SVN_NO_ERROR;
}

/* This implements the `svn_opt_subcommand_t' interface. */
static svn_error_t *
svnconflict_resolve_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
  svnconflict_cmd_baton_t *b = baton;
  svn_client_ctx_t *ctx = b->ctx;
  apr_array_header_t *args;
  const char *option_id_str;
  int optid;
  svn_client_conflict_option_id_t option_id;
  const char *path;
  const char *local_abspath;
  svn_client_conflict_t *conflict;
  svn_boolean_t tree_conflicted;

  SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
  option_id_str = APR_ARRAY_IDX(args, 0, const char *);
  path = APR_ARRAY_IDX(args, 1, const char *);
  SVN_ERR(svn_cstring_atoi(&optid, option_id_str));
  option_id = (svn_client_conflict_option_id_t)optid;
  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));

  SVN_ERR(get_conflicts(NULL, NULL, &tree_conflicted,
                        &conflict, local_abspath, ctx, pool));

  if (!tree_conflicted)
    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                             _("The path '%s' is not a tree conflict victim"),
                             local_abspath);

  SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, pool));
  SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id, ctx,
                                                 pool));

  return SVN_NO_ERROR;
}


/*** Main. ***/

/*
 * 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 char *argv[], apr_pool_t *pool)
{
  svn_error_t *err;
  int opt_id;
  apr_getopt_t *os;
  svnconflict_opt_state_t opt_state = { 0 };
  svn_client_ctx_t *ctx;
  apr_array_header_t *received_opts;
  svnconflict_cmd_baton_t command_baton;
  int i;
  const svn_opt_subcommand_desc3_t *subcommand = NULL;
  svn_auth_baton_t *ab;
  svn_config_t *cfg_config;
  apr_hash_t *cfg_hash;
  svn_boolean_t read_pass_from_stdin = FALSE;

  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));

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

#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

  /* Initialize the RA library. */
  SVN_ERR(svn_ra_initialize(pool));

  /* No args?  Show usage. */
  if (argc <= 1)
    {
      SVN_ERR(svnconflict_help(NULL, NULL, pool));
      *exit_code = EXIT_FAILURE;
      return SVN_NO_ERROR;
    }

  /* Else, parse options. */
  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));

  os->interleave = 1;
  while (1)
    {
      const char *opt_arg;
      const char *utf8_opt_arg;

      /* Parse the next option. */
      apr_status_t apr_err = apr_getopt_long(os, svnconflict_options, &opt_id,
                                             &opt_arg);
      if (APR_STATUS_IS_EOF(apr_err))
        break;
      else if (apr_err)
        {
          SVN_ERR(svnconflict_help(NULL, NULL, pool));
          *exit_code = EXIT_FAILURE;
          return SVN_NO_ERROR;
        }

      /* Stash the option code in an array before parsing it. */
      APR_ARRAY_PUSH(received_opts, int) = opt_id;

      switch (opt_id) {
      case 'h':
      case '?':
        opt_state.help = TRUE;
        break;
      case opt_version:
        opt_state.version = TRUE;
        break;
      case opt_auth_username:
        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
                                        opt_arg, pool));
        break;
      case opt_auth_password:
        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
                                        opt_arg, pool));
        break;
      case opt_auth_password_from_stdin:
        read_pass_from_stdin = TRUE;
        break;
      case opt_config_dir:
        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
        opt_state.config_dir = svn_dirent_internal_style(utf8_opt_arg, pool);
        break;
      case opt_config_options:
        if (!opt_state.config_options)
          opt_state.config_options =
                   apr_array_make(pool, 1,
                                  sizeof(svn_cmdline__config_argument_t*));

        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
        SVN_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
                                                 utf8_opt_arg, "svnconflict: ",
                                                 pool));
        break;
      default:
        break;
      }
    }

  /* ### This really belongs in libsvn_client. */
  SVN_ERR(svn_config_ensure(opt_state.config_dir, pool));

  /* If the user asked for help, then the rest of the arguments are
     the names of subcommands to get help on (if any), or else they're
     just typos/mistakes.  Whatever the case, the subcommand to
     actually run is svnconflict_help(). */
  if (opt_state.help)
    subcommand = svn_opt_get_canonical_subcommand3(svnconflict_cmd_table,
                                                   "help");

  /* If we're not running the `help' subcommand, then look for a
     subcommand in the first argument. */
  if (subcommand == NULL)
    {
      if (os->ind >= os->argc)
        {
          if (opt_state.version)
            {
              /* Use the "help" subcommand to handle the "--version" option. */
              static const svn_opt_subcommand_desc3_t pseudo_cmd =
                { "--version", svnconflict_help, {0}, {""},
                  {opt_version,    /* must accept its own option */
                   opt_config_dir  /* all commands accept this */
                  } };

              subcommand = &pseudo_cmd;
            }
          else
            {
              svn_error_clear
                (svn_cmdline_fprintf(stderr, pool,
                                     _("Subcommand argument required\n")));
              svn_error_clear(svnconflict_help(NULL, NULL, pool));
              *exit_code = EXIT_FAILURE;
              return SVN_NO_ERROR;
            }
        }
      else
        {
          const char *first_arg;

          SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
                                          pool));
          subcommand = svn_opt_get_canonical_subcommand3(svnconflict_cmd_table,
                                                         first_arg);
          if (subcommand == NULL)
            {
              svn_error_clear
                (svn_cmdline_fprintf(stderr, pool,
                                     _("Unknown subcommand: '%s'\n"),
                                     first_arg));
              svn_error_clear(svnconflict_help(NULL, NULL, pool));
              *exit_code = EXIT_FAILURE;
              return SVN_NO_ERROR;
            }
        }
    }

  /* Check that the subcommand wasn't passed any inappropriate options. */
  for (i = 0; i < received_opts->nelts; i++)
    {
      opt_id = APR_ARRAY_IDX(received_opts, i, int);

      /* All commands implicitly accept --help, so just skip over this
         when we see it. Note that we don't want to include this option
         in their "accepted options" list because it would be awfully
         redundant to display it in every commands' help text. */
      if (opt_id == 'h' || opt_id == '?')
        continue;

      if (! svn_opt_subcommand_takes_option4(subcommand, opt_id,
                                             svnconflict_global_options))
        {
          const char *optstr;
          const apr_getopt_option_t *badopt =
            svn_opt_get_option_from_code3(opt_id, svnconflict_options,
                                          subcommand, pool);
          svn_opt_format_option(&optstr, badopt, FALSE, pool);
          if (subcommand->name[0] == '-')
            svn_error_clear(svnconflict_help(NULL, NULL, pool));
          else
            svn_error_clear
              (svn_cmdline_fprintf
               (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
                                "Type 'svnconflict help %s' for usage.\n"),
                subcommand->name, optstr, subcommand->name));
          *exit_code = EXIT_FAILURE;
          return SVN_NO_ERROR;
        }
    }

  err = svn_config_get_config(&cfg_hash, opt_state.config_dir, pool);
  if (err)
    {
      /* Fallback to default config if the config directory isn't readable
         or is not a directory. */
      if (APR_STATUS_IS_EACCES(err->apr_err)
          || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
        {
          svn_handle_warning2(stderr, err, "svnconflict: ");
          svn_error_clear(err);

          SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
        }
      else
        return err;
    }

  /* Update the options in the config */
  if (opt_state.config_options)
    {
      svn_error_clear(
          svn_cmdline__apply_config_options(cfg_hash,
                                            opt_state.config_options,
                                            "svnconflict: ",
                                            "--config-option"));
    }

  cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);

  /* Get password from stdin if necessary */
  if (read_pass_from_stdin)
    {
      SVN_ERR(svn_cmdline__stdin_readline(&opt_state.auth_password, pool, pool));
    }


  /* Create a client context object. */
  command_baton.opt_state = &opt_state;
  SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
  command_baton.ctx = ctx;

  /* Set up Authentication stuff. */
  SVN_ERR(svn_cmdline_create_auth_baton2(
            &ab,
            TRUE, /* non-interactive */
            opt_state.auth_username,
            opt_state.auth_password,
            opt_state.config_dir,
            TRUE, /* no auth cache */
            FALSE, FALSE, FALSE, FALSE, FALSE, /* reject invalid SSL certs */
            cfg_config,
            NULL, NULL,
            pool));

  ctx->auth_baton = ab;

  /* We don't use legacy libsvn_wc conflict handlers. */
  {
    ctx->conflict_func = NULL;
    ctx->conflict_baton = NULL;
    ctx->conflict_func2 = NULL;
    ctx->conflict_baton2 = NULL;
  }

  /* And now we finally run the subcommand. */
  err = (*subcommand->cmd_func)(os, &command_baton, pool);
  if (err)
    {
      /* For argument-related problems, suggest using the 'help'
         subcommand. */
      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
        {
          err = svn_error_quick_wrapf(
                  err, _("Try 'svnconflict help %s' for more information"),
                  subcommand->name);
        }
      if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
        {
          err = svn_error_quick_wrap(err,
                                     _("Please see the 'svn upgrade' command"));
        }

      /* Tell the user about 'svn cleanup' if any error on the stack
         was about locked working copies. */
      if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
        {
          err = svn_error_quick_wrap(
                  err, _("Run 'svn cleanup' to remove locks "
                         "(type 'svn help cleanup' for details)"));
        }

      if (err->apr_err == SVN_ERR_SQLITE_BUSY)
        {
          err = svn_error_quick_wrap(err,
                                     _("Another process is blocking the "
                                       "working copy database, or the "
                                       "underlying filesystem does not "
                                       "support file locking; if the working "
                                       "copy is on a network filesystem, make "
                                       "sure file locking has been enabled "
                                       "on the file server"));
        }

      if (svn_error_find_cause(err, SVN_ERR_RA_CANNOT_CREATE_TUNNEL) &&
          (opt_state.auth_username || opt_state.auth_password))
        {
          err = svn_error_quick_wrap(
                  err, _("When using svn+ssh:// URLs, keep in mind that the "
                         "--username and --password options are ignored "
                         "because authentication is performed by SSH, not "
                         "Subversion"));
        }

      return err;
    }

  return SVN_NO_ERROR;
}

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

  /* Initialize the app. */
  if (svn_cmdline_init("svn", 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, "svnconflict: ");
    }

  svn_pool_destroy(pool);

  svn_cmdline__cancellation_exit();

  return exit_code;
}
