/* cmd-abrowse.c
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/cmd/main.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/my.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/cmd-abrowse.h"



static t_uchar * usage = "[options] [limit]";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
                                   "\n"
                                   "Copyright 2003 Tom Lord\n"
                                   "\n"
                                   "This is free software; see the source for copying conditions.\n"
                                   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
                                   "PARTICULAR PURPOSE.\n"
                                   "\n"
                                   "Report bugs to " cfg__tla_bug_mail ".\n"
                                   "\n"
                                   cfg__std__release_id_string
                                   "\n");

#define OPTS(OP) \
  OP (opt_help_msg, "h", "help", 0, \
      "Display a help message and exit.") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "Display a release identifier string\n" \
      "and exit.") \
  OP (opt_archive, "A", "archive", 1, \
      "Override `my-default-archive'") \
  OP (opt_reverse, "r", "reverse", 0, \
      "sort revisions from newest to oldest") \
  OP (opt_summary, "s", "summary", 0, \
      "print a summary of each patch") \
  OP (opt_creator, "c", "creator", 0, \
      "print the creator of each patch") \
  OP (opt_date, "D", "date", 0, \
      "print the date of each patch") \
  OP (opt_kind, "k", "kind", 0, \
      "show each revision kind (import, changeset or id)") \
  OP (opt_cacherevs, "C", "cacherevs", 0, \
      "show cached revisions") \
  OP (opt_full, "f", "full", 0, \
      "print full patch level names") \
  OP (opt_desc, 0, "desc", 0, \
      "implies -s -c -D -k -C") \
  OP (opt_local_merges, 0, "local-merges", 0, \
      "list merges from the same archive") \
  OP (opt_foreign_merges, 0, "foreign-merges", 0, \
      "list merges from other archives") \
  OP (opt_all_merges, 0, "merges", 0, \
      "list all merges") \
  OP (opt_categories, 0, "categories", 0, \
      "show category names only") \
  OP (opt_branches, 0, "branches", 0, \
      "show branch names only") \
  OP (opt_versions, 0, "versions", 0, \
      "show version names only") \
  OP (opt_omit_empty, 0, "omit-empty", 0, \
      "omit empty or unchanged-since items") \
  OP (opt_since, 0, "since SNAP-FILE", 1, \
      "show revisions after those listed in SNAP-FILE") \
  OP (opt_since_limits, 0, "since-limits", 0, \
      "limit output to items in the since file") \
  OP (opt_snap, 0, "snap SNAP-FILE", 1, \
      "record the highest revisions shown") \
  OP (opt_force, 0, "force", 0, \
      "overwrite an existing snap-file")

t_uchar arch_cmd_abrowse_help[] = ("print an outline describing archive contents\n"
                                   "Describe the contents of an archive in outline format.\n"
                                   "\n"
                                   "With LIMIT, look only that part of the archive.\n"
                                   "\n"
                                   "LIMIT may be a fully qualified name and may be an archive name.\n");

enum options
{
  OPTS (OPT_ENUM)
};

static struct opt_desc opts[] =
{
  OPTS (OPT_DESC)
    {-1, 0, 0, 0, 0}
};



enum abrowse_depth
{
  category_depth,
  branch_depth,
  version_depth,
  revision_depth,
};

int
arch_cmd_abrowse (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * default_archive = 0;
  int reverse = 0;
  int wanted_headers = 0;
  int show_kind = 0;
  int show_cacherevs = 0;
  enum abrowse_depth depth = revision_depth;
  t_uchar * since = 0;
  int since_limits_opt = 0;
  t_uchar * snap = 0;
  int snap_force = 0;
  int omit_empty = 0;
  int full = 0;

  safe_buffer_fd (1, 0, O_WRONLY, 0);

  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, version_string, arch_cmd_abrowse_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      switch (o)
        {
        default:
          safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
          panic ("internal error parsing arguments");

        usage_error:
          opt_usage (2, argv[0], program_name, usage, 1);
          exit (1);

          /* bogus_arg: */
          safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
          goto usage_error;

        case opt_archive:
          {
            default_archive = str_save (0, option->arg_string);
            break;
          }

        case opt_reverse:
          {
            reverse = 1;
            break;
          }

        case opt_summary:
          {
            wanted_headers |= arch_include_summary;
            break;
          }

        case opt_creator:
          {
            wanted_headers |= arch_include_creator;
            break;
          }

        case opt_date:
          {
            wanted_headers |= arch_include_date;
            break;
          }

        case opt_local_merges:
          {
            wanted_headers |= arch_include_local_merges;
            break;
          }

        case opt_foreign_merges:
          {
            wanted_headers |= arch_include_foreign_merges;
            break;
          }

        case opt_all_merges:
          {
            wanted_headers |= arch_include_local_merges;
            wanted_headers |= arch_include_foreign_merges;
            break;
          }

        case opt_kind:
          {
            show_kind = 1;
            break;
          }

        case opt_cacherevs:
          {
            show_cacherevs = 1;
            break;
          }

        case opt_desc:
          {
            wanted_headers |= (arch_include_summary | arch_include_creator | arch_include_date);
            show_kind = 1;
            show_cacherevs = 1;
            break;
          }

        case opt_categories:
          {
            depth = category_depth;
            break;
          }

        case opt_branches:
          {
            depth = branch_depth;
            break;
          }

        case opt_versions:
          {
            depth = version_depth;
            break;
          }

        case opt_since:
          {
            lim_free (0, since);
            since = str_save (0, option->arg_string);
            break;
          }

        case opt_since_limits:
          {
            since_limits_opt = 1;
            break;
          }

        case opt_snap:
          {
            lim_free (0, since);
            snap = str_save (0, option->arg_string);
            break;
          }

        case opt_force:
          {
            snap_force = 1;
            break;
          }

        case opt_omit_empty:
          {
            omit_empty = 1;
            break;
          }

	case opt_full:
	  {
	    full = 1;
	    break;
	  }
        }
    }

  if (argc > 2)
    goto usage_error;

  default_archive = arch_my_default_archive (default_archive);


  {
    t_uchar * limit_spec = 0;
    t_uchar * archive = 0;
    t_uchar * limit_category = 0;
    t_uchar * limit_branch = 0;
    t_uchar * limit_version = 0;
    t_uchar * limit_revision = 0;
    assoc_table limit_categories = 0;
    assoc_table limit_branches = 0;
    assoc_table limit_versions = 0;
    struct arch_archive * arch = 0;
    assoc_table since_limits = 0;
    rel_table old_since_limits = rel_table_nil; /* [0] version [1] level */
    rel_table new_since_limits = rel_table_nil; /* [0] version [1] level */


    /********************************
     * Set archive and limit spec.
     *
     */

    if (argc == 2)
      {
        limit_spec = str_save (0, argv[1]);
      }

    if (limit_spec && arch_valid_archive_name (limit_spec))
      {
        archive = limit_spec;
        limit_spec = 0;
      }
    else if (limit_spec && !arch_valid_package_name (limit_spec, arch_maybe_archive, arch_req_category, 1))
      {
        safe_printfmt (1, "abrowse: invalid limit spec (%s)\n", limit_spec);
        exit (1);
      }
    else if (limit_spec)
      {
        archive = arch_parse_package_name (arch_ret_archive, default_archive, limit_spec);
      }
    else
      {
        archive = arch_my_default_archive (default_archive);
      }

    /********************************
     * sanity check
     */
    if (snap && !snap_force && !safe_access (snap, F_OK))
      {
        safe_printfmt (2, "%s: --snap output exists and --force not provided (%s)\n",
                       argv[0], snap);
        exit (1);
      }

    if (snap && (depth != revision_depth))
      {
        safe_printfmt (2, "%s: --snap is incompatible with --categories, --branches, and --versions\n",
                       argv[0]);
        exit (1);
      }

    if (since_limits_opt && !since)
      {
        safe_printfmt (2, "%s: --since-limits requires --since\n", argv[0]);
        exit (1);
      }


    /********************************
     * parse the limit spec
     */

    if (arch_valid_package_name (limit_spec, arch_maybe_archive, arch_req_category, 1))
      {
        limit_category = arch_parse_package_name (arch_ret_category, 0, limit_spec);
        limit_branch = arch_parse_package_name (arch_ret_package, 0, limit_spec);

        if (!str_cmp (limit_category, limit_branch))
          {
            lim_free (0, limit_branch);
            limit_branch = 0;
          }
        else if (arch_valid_package_name (limit_spec, arch_maybe_archive, arch_req_version, 1))
          {
            limit_version = arch_parse_package_name (arch_ret_package_version, 0, limit_spec);

            if (arch_valid_package_name (limit_spec, arch_maybe_archive, arch_req_patch_level, 0))
              limit_revision = arch_parse_package_name (arch_ret_non_archive, 0, limit_spec);
          }
      }

    /********************************
     * parse the --since file, if any
     */
    if (since)
      {
        int in_fd;
        rel_table since_table = rel_table_nil;
        int s;


        in_fd = safe_open (since, O_RDONLY, 0);
        since_table = rel_read_table (in_fd, 1, argv[0], since);


        for (s = 0; s < rel_n_records (since_table); ++s)
          {
            t_uchar * rev = 0;
            t_uchar * version = 0;
            t_uchar * level = 0;

            rev = str_save (0, rel_peek_str (since_table, s, 0));

            if (!arch_valid_package_name (rev, arch_no_archive, arch_req_version, 1))
              {
                safe_printfmt (2, "illegal line in --since file (%s)\n  %s\n", since, rev);
                exit (1);
              }

            version = arch_parse_package_name (arch_ret_package_version, 0, rev);

            if (arch_valid_package_name (rev, arch_no_archive, arch_req_patch_level, 0))
              {
                level = arch_parse_package_name (arch_ret_patch_level, 0, rev);
                assoc_set_taking (&since_limits, rel_make_field_str (version), rel_make_field_str (level));
                rel_add_records (&old_since_limits, rel_make_record_2_taking (rel_make_field_str (version), rel_make_field_str (level)), rel_record_null);
              }

            if (since_limits_opt)
              {
                t_uchar * category = 0;
                t_uchar * branch = 0;

                category = arch_parse_package_name (arch_ret_category, 0, rev);
                branch = arch_parse_package_name (arch_ret_package, 0, rev);

                assoc_set_taking (&limit_categories, rel_make_field_str (category), rel_make_field_str ("yes"));
                assoc_set_taking (&limit_branches, rel_make_field_str (branch), rel_make_field_str ("yes"));
                assoc_set_taking (&limit_versions, rel_make_field_str (version), rel_make_field_str ("yes"));

                lim_free (0, category);
                lim_free (0, branch);
              }

            lim_free (0, rev);
            lim_free (0, version);
            lim_free (0, level);
          }


        safe_close (in_fd);
        rel_free_table (since_table);
      }


    /********************************
     * connect to the archive
     */

    arch = arch_archive_connect (archive, 0);


    /****************************************************************
     * produce output
     */

    safe_printfmt (1, "%s\n", archive);
    safe_flush (1);

    {
      rel_table categories = rel_table_nil;
      int c;
      t_uchar * last_category_printed = 0;
      t_uchar * last_branch_printed = 0;
      t_uchar * last_version_printed = 0;

      categories = arch_archive_categories (arch);

      for (c = 0;  c < rel_n_records (categories); ++c)
        {
          if (limit_category && str_cmp (limit_category, rel_peek_str (categories, c, 0)))
            continue;

          if (since_limits_opt && !assoc_get_str_taking (limit_categories, rel_get_field (categories, c, 0)))
            continue;

          if (!omit_empty)
            {
              safe_printfmt (1, "  %s\n", rel_peek_str (categories, c, 0));
              safe_flush (1);
              lim_free (0, last_category_printed);
              last_category_printed = str_save (0, rel_peek_str (categories, c, 0));
            }

          if (omit_empty || (depth != category_depth))
            {
              rel_table branches = rel_table_nil;
              int b;

              branches = arch_archive_branches (arch, rel_peek_str (categories, c, 0));

              for (b = 0;  b < rel_n_records (branches); ++b)
                {
                  if (limit_branch && str_cmp (limit_branch, rel_peek_str (branches, b, 0)))
                    continue;

                  if (since_limits_opt && !assoc_get_str_taking (limit_branches, rel_get_field (branches, b, 0)))
                    continue;

                  if (!omit_empty)
                    {
                      safe_printfmt (1, "    %s\n", rel_peek_str (branches, b, 0));
                      safe_flush (1);
                      lim_free (0, last_branch_printed);
                      last_branch_printed = str_save (0, rel_peek_str (branches, b, 0));
                    }

                  if (omit_empty || (depth != branch_depth))
                    {
                      rel_table versions = rel_table_nil;
                      int v;

                      versions = arch_archive_versions (arch, rel_peek_str (branches, b, 0));

                      for (v = 0;  v < rel_n_records (versions); ++v)
                        {
                          if (limit_version && str_cmp (limit_version, rel_peek_str (versions, v, 0)))
                            continue;

                          if (since_limits_opt && !assoc_get_str_taking (limit_versions, rel_get_field (versions, v, 0)))
                            continue;

                          if (!omit_empty)
                            {
			      safe_printfmt (1, "      %s\n", rel_peek_str (versions, v, 0));
                              safe_flush (1);
                              lim_free (0, last_version_printed);
                              last_version_printed = str_save (0, rel_peek_str (versions, v, 0));
                            }

                          if (omit_empty || (depth != version_depth))
                            {
                              t_uchar * start_at_level = 0;
                              rel_table revisions = rel_table_nil;
                              int r;
                              int any_output = 0;

                              {
                                const t_uchar * since_limit = 0;

                                since_limit = assoc_get_str_taking (since_limits, rel_get_field (versions, v, 0));

                                if (since_limit)
                                  start_at_level = str_save (0, since_limit);
                              }

                              revisions = arch_archive_revisions (arch, rel_peek_str (versions, v, 0), 0);

                              if (reverse)
                                {
                                  arch_sort_table_by_patch_level_field (1, revisions, 0);
                                  if (rel_n_records (revisions))
                                    rel_add_records (&new_since_limits,
                                                     rel_make_record_2_taking (rel_get_field (versions, v, 0),
                                                                               rel_get_field (revisions, 0, 0)),
                                                     rel_record_null);
                                }
                              else
                                {
                                  if (rel_n_records (revisions))
                                    rel_add_records (&new_since_limits,
                                                     rel_make_record_2_taking (rel_get_field (versions, v, 0),
                                                                               rel_get_field (revisions, rel_n_records (revisions) - 1, 0)),
                                                     rel_record_null);
                                }

                              for (r = 0; r < rel_n_records (revisions); ++r)
                                {
                                  if (start_at_level)
                                    {
                                      if (reverse && (0 <= arch_patch_lvl_cmp (start_at_level, rel_peek_str (revisions, r, 0))))
                                        break;

                                      if (!reverse && (0 <= arch_patch_lvl_cmp (start_at_level, rel_peek_str (revisions, r, 0))))
                                        continue;
                                    }

                                  if (!any_output)
                                    {
                                      any_output = 1;
                                      if (omit_empty)
                                        {
                                          if (str_cmp (last_category_printed, rel_peek_str (categories, c, 0)))
                                            {
                                              safe_printfmt (1, "  %s\n", rel_peek_str (categories, c, 0));
                                              lim_free (0, last_category_printed);
                                              last_category_printed = str_save (0, rel_peek_str (categories, c, 0));
                                            }

                                          if (str_cmp (last_branch_printed, rel_peek_str (branches, b, 0)))
                                            {
                                              safe_printfmt (1, "    %s\n", rel_peek_str (branches, b, 0));
                                              lim_free (0, last_branch_printed);
                                              last_branch_printed = str_save (0, rel_peek_str (branches, b, 0));
                                            }

                                          if (str_cmp (last_version_printed, rel_peek_str (versions, v, 0)))
                                            {
                                              safe_printfmt (1, "      %s\n", rel_peek_str (versions, v, 0));
                                              lim_free (0, last_version_printed);
                                              last_version_printed = str_save (0, rel_peek_str (versions, v, 0));
                                            }
                                        }
                                      if (wanted_headers)
                                        safe_printfmt (1, "\n");
                                    }

                                  if (!wanted_headers)
                                    {
                                      if (!show_kind && !show_cacherevs)
                                        {
                                          if (rel_n_records (revisions) == 1)
                                            {
                                              safe_printfmt (1, "        %s\n", rel_peek_str (revisions, r, 0));
                                            }
                                          else
                                            {
                                              int s;
                                              s = rel_n_records (revisions) - 1;
                                              safe_printfmt (1, "        %s .. %s\n", rel_peek_str (revisions, r, 0), rel_peek_str (revisions, s, 0));
                                              r = s;
                                            }
                                          }
                                      else
                                        {
                                          /* show revision kinds and/or cacherevs */
                                          t_uchar * version_stem = 0;
                                          t_uchar * package_revision = 0;
                                          enum arch_revision_type this_type;
					  int is_cached;
                                          int s;

                                          version_stem = str_alloc_cat (0, rel_peek_str (versions, v, 0), "--");

                                          package_revision = str_alloc_cat (0, version_stem, rel_peek_str (revisions, r, 0));
                                          arch_revision_type (&this_type, &is_cached, arch, package_revision);

                                          if (this_type == arch_continuation_revision)
                                            {
                                              t_uchar * tagged = 0;

                                              tagged = arch_get_continuation (arch, package_revision);

					      safe_printfmt (1, "        %s", rel_peek_str (revisions, r, 0));
					      if (show_kind)
						safe_printfmt (1, " (tag of %s)", tagged);
					      if (show_cacherevs)
						safe_printfmt (1, "%s", " [cached]");
					      safe_printfmt (1, "\n");

                                              lim_free (0, tagged);
                                            }
                                          else
                                            {
                                              for (s = r + 1; s < rel_n_records (revisions); ++s)
                                                {
                                                  enum arch_revision_type s_type;
						  int s_is_cached;

                                                  lim_free (0, package_revision);
                                                  package_revision = str_alloc_cat (0, version_stem, rel_peek_str (revisions, s, 0));
                                                  arch_revision_type (&s_type, &s_is_cached, arch, package_revision);

						  if ((show_cacherevs && (s_is_cached != is_cached))
						      || (show_kind && (s_type != this_type)))
                                                    break;
                                                }
                                              --s;

                                              if (s == r)
                                                {
                                                  safe_printfmt (1, "          %s", rel_peek_str (revisions, r, 0));
                                                }
                                              else
                                                {
                                                  safe_printfmt (1, "          %s .. %s", rel_peek_str (revisions, r, 0), rel_peek_str (revisions, s, 0));
                                                }

					      if (show_kind)
						{
						  safe_printfmt (1, " ");
						  if (this_type == arch_import_revision)
						    safe_printfmt (1, "(initial import)");
						  else
						    safe_printfmt (1, "(simple changeset)");
						}
						  
					      if (show_cacherevs && is_cached)
						safe_printfmt (1, "%s", " [cached]");
					      safe_printfmt (1, "\n");
					      
                                              r = s;
                                            }

                                          lim_free (0, version_stem);
                                          lim_free (0, package_revision);
                                        }

                                      safe_flush (1);
                                    }
                                  else
                                    {
                                      t_uchar * rev = 0;
				      t_uchar * fqrev = 0;
                                      t_uchar * log = 0;
                                      assoc_table headers = 0;

                                      rev = str_alloc_cat_many (0, rel_peek_str (versions, v, 0), "--", rel_peek_str (revisions, r, 0), str_end);
				      fqrev = arch_fully_qualify (archive, rev);
                                      if (!show_kind && !show_cacherevs)
                                        {
                                          safe_printfmt (1, "        %s\n", full ? fqrev : rel_peek_str (revisions, r, 0));
                                        }
                                      else
                                        {
                                          enum arch_revision_type type;
					  int is_cached;

                                          arch_revision_type (&type, &is_cached, arch, rev);

					  safe_printfmt (1, "        %s   ", full ? fqrev : rel_peek_str (revisions, r, 0));
					  if (show_kind)
					    {
					      switch (type)
						{
						case arch_import_revision:
						  {
						    safe_printfmt (1, "(initial import)");
						    break;
						  }
						case arch_simple_revision:
						  {
						    safe_printfmt (1, "(simple changeset)");
						    break;
						  }
						case arch_continuation_revision:
						  {
						    t_uchar * continuation_of = 0;

						    continuation_of = arch_get_continuation (arch, rev);
						    safe_printfmt (1, "(tag revision of %s)", continuation_of);

						    lim_free (0, continuation_of);
						    break;
						  }
						}
					    }
					  if (show_cacherevs && is_cached)
					    safe_printfmt (1, "%s", " [cached]");
					  safe_printfmt (1, "\n");
                                        }

                                      log = arch_archive_log (arch, rev);

                                      arch_parse_log (0, &headers, 0, log);
                                      arch_print_headers_summary (1, 10, headers, wanted_headers);
                                      safe_printfmt (1, "\n");

                                      lim_free (0, rev);
				      lim_free (0, fqrev);
                                      lim_free (0, log);
                                      free_assoc_table (headers);
                                    }
                                  safe_flush (1);
                                }

                              if (!omit_empty && (!any_output || !wanted_headers))
                                safe_printfmt (1, "\n");

                              lim_free (0, start_at_level);
                              rel_free_table (revisions);
                            }
                        }

                      rel_free_table (versions);
                    }
                }

              rel_free_table (branches);
            }
        }

      rel_free_table (categories);
      lim_free (0, last_category_printed);
      lim_free (0, last_branch_printed);
      lim_free (0, last_version_printed);
    }

    /****************************************************************
     * snap a new since file
     */

    if (snap)
      {
        rel_table unchanged = rel_table_nil;
        rel_table changed = rel_table_nil;
        rel_table all = rel_table_nil;
        int out_fd;
        int x;

        rel_sort_table_by_field (0, old_since_limits, 0);
        rel_sort_table_by_field (0, new_since_limits, 0);

        unchanged = rel_join (1, rel_join_output (1,0, 1,1, -1), 0, 0, old_since_limits, new_since_limits);
        all = rel_copy_table (new_since_limits);
        rel_append_x (&all, unchanged);

        arch_sort_table_by_name_field (0, all, 0);

        out_fd = safe_open (snap, O_WRONLY | O_CREAT | (snap_force ? 0 : O_EXCL), 0666);

        for (x = 0; x < rel_n_records (all); ++x)
          {
            safe_printfmt (out_fd, "%s--%s\n", rel_peek_str (all, x, 0), rel_peek_str (all, x, 1));
          }

        safe_close (out_fd);
        rel_free_table (unchanged);
        rel_free_table (changed);
        rel_free_table (all);
      }


    lim_free (0, limit_spec);
    lim_free (0, archive);
    lim_free (0, limit_category);
    lim_free (0, limit_branch);
    lim_free (0, limit_version);
    lim_free (0, limit_revision);
    arch_archive_close (arch);
    free_assoc_table (since_limits);
    rel_free_table (old_since_limits);
    rel_free_table (new_since_limits);
    free_assoc_table (limit_categories);
    free_assoc_table (limit_branches);
    free_assoc_table (limit_versions);
  }

  return 0;
}




/* tag: Tom Lord Thu Jul 17 10:54:10 2003 (cmd-abrowse.c)
 */
