/* cmd-rbrowse.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 "hackerlab/char/str.h"
#include "tla/libarch/my.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/cmd-rbrowse.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/archives.h"



/* __STDC__ prototypes for static functions */
static void rbrowse_print_logs (rel_table patch_output,
                                int summarized_headers);
static rel_table rbrowse_patchlog (struct arch_archive * arch,
                                   regex_t patch_regex_needle,
                                   const t_uchar * version,
                                   rel_table saved_since);
static int rbrowse_since_has_newest (t_uchar * revision,
                                     rel_table saved_since);
static t_uchar * rbrowse_revision_range (const t_uchar * latest_revision);
static void rbrowse_build_since_table (struct arch_archive *arch, 
                                       const t_uchar *version,
                                       rel_table * since_output);
static rel_table rbrowse_load_since (const t_uchar *filename);
static void rbrowse_save_since (const t_uchar *filename,
                                int snap_force,
                                rel_table since_table);



static t_uchar * usage = "[options] [[ARCHIVE_NAME/]LIMIT_REGEX]";
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") \
OP (opt_all_archives, 0, "all", 0, \
    "Search all archives") \
OP (opt_since, 0, "since SNAP-FILE", 1, \
    "Only show differences since SNAP-FILE") \
OP (opt_snap, 0, "snap SNAP-FILE", 1, \
    "Snap an archive") \
OP (opt_snap_force, "f", "snap-force", 0, \
    "Force snap writing") \
OP (opt_log_reverse, "r", 0 , 0, \
    "Reverse patch logs") \
OP (opt_log_summary, "s", 0 , 0, \
    "Print the summary of matching patches") \
OP (opt_log_creator, "c", 0 , 0, \
    "Print the creator of matching patches") \
OP (opt_log_date, "D", 0 , 0, \
    "Print the date of matching patches") \
OP (opt_patch_regex, 0, "patch-regex REGEX", 1, \
    "Only show revisions that contain [REGEX]\n" \
    "(implies -s)") \
OP (opt_archive, "A", "archive", 1, \
    "Use [archive] instead of default") \
OP (opt_unhide_sealed, 0, "show-sealed", 0, \
    "Do not hide sealed branches") 

t_uchar arch_cmd_rbrowse_help[] = ("print an outline describing an archive's contents\n"
                                   "\n"
                                   "an outline of an archive will be printed, showing the\n"
                                   "categories, branches and revisions of the specified archive\n"
                                   "if no archive is given then `my-default-archive` is used.\n"
                                   "\n"
                                   "If [LIMIT REGEX] is specified, revisions will only be shown\n"
                                   "if the category, branch or version matches [LIMIT REGEX]. If\n"
                                   "--patch-regex [REGEX] is given, then only patchlogs matching\n"
                                   "[REGEX] will be given" );

enum options

{
  OPTS (OPT_ENUM)
};

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



int
arch_cmd_rbrowse (t_uchar * program_name,
                  int argc,
                  char * argv[])
{
  int o;
  int w;
  int all_archives = 0;
  int snap_force = 0;
  int summarized_headers = 0;
  int want_logs = 0;
  int unhide_sealed = 0;
  int log_reverse=0;
  struct opt_parsed * option = 0;
  regex_t patch_regex_needle;
  regex_t limit_regex_needle;
  t_uchar * errname;
  t_uchar * snap_file = 0;
  t_uchar * since_filename = 0;
  t_uchar * default_archive = 0;
  t_uchar * patch_regex = 0;
  t_uchar * limit_regex = 0;
  rel_table since_output = rel_table_nil;
  rel_table saved_since = rel_table_nil;
  rel_table archive_list = rel_table_nil;


  errname = argv[0];
/*  safe_buffer_fd (1, 0, O_WRONLY, 0); */


  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, version_string, arch_cmd_rbrowse_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_all_archives:
            {
              all_archives = 1;
              break;
            }
        case opt_snap:
            {
              snap_file = str_save(0, option->arg_string);
              break;
            }
        case opt_snap_force:
            {
              snap_force = 1;
              break;
            }
        case opt_since:
            {
              if (since_filename)
                panic("snap-file specified more than once!");
              since_filename = str_save(0, option->arg_string);
              break;
            }
        case opt_archive:
            {
              default_archive = str_save(0, option->arg_string);
              break;
            }
        case opt_log_summary:
            {
              summarized_headers |= arch_include_summary;
              want_logs = 1;
              break;
            }
        case opt_log_creator:
            {
              summarized_headers |= arch_include_creator;
              want_logs = 1;
              break;
            }
        case opt_log_date:
            {
              summarized_headers |= arch_include_date;
              want_logs = 1;
              break;
            }
        case opt_log_reverse:
            {
              log_reverse = (! log_reverse);
              want_logs = 1;
              break;
            }
        case opt_unhide_sealed:
            {
              unhide_sealed = 1;
              break;
            }
        case opt_patch_regex:
            {
              if (patch_regex)
                panic("patch regex given more than once!");
              patch_regex = str_save(0, option->arg_string);
              summarized_headers |= arch_include_summary;
              want_logs = 1;
              break;
            }
        }
    }

  if (argc > 2)
    goto usage_error;

  /* This is slightly long. Basically, we're checking to see if we were
   * given an limit regex that looks like archive_name/regex. If so, then
   * we need to split them up. */
  if (argc == 1)
    limit_regex = str_save (0, ".*");
  else
    {
      t_uchar *slash;

      /* Did the user give us something resembling archive/regex ? */
      if ( 0 == (slash = str_chr_index (argv[1], '/')))
        limit_regex = str_save (0, argv[1]);
      else
        {

          if (default_archive)
            panic ("Archive specified twice! ");

          limit_regex = str_save(0, argv[1]);
          default_archive = str_separate (&limit_regex, "/");

          if (! limit_regex)
            limit_regex = str_save (0, ".*");
            /* limit_regex unset, so make something sensible */
        }
    }

  if (!default_archive)
    default_archive = arch_my_default_archive (default_archive);

  if (regcomp(&limit_regex_needle, limit_regex, REG_EXTENDED))
    panic ("Invalid regular expression given for limit.");

  if (! patch_regex)
    patch_regex = str_save(0, ".*");

  if (regcomp(&patch_regex_needle, patch_regex, REG_EXTENDED))
    panic ("Invalid regular expression given for patch.");

  if (since_filename)
    saved_since = rbrowse_load_since (since_filename);

  if (!arch_valid_archive_name (default_archive))
    panic ("Invalid archive given");

  if (! all_archives)
    {
      rel_add_records (&archive_list, rel_singleton_record_taking (rel_make_field_str (default_archive)), rel_record_null);
    }
  else
    {
      archive_list = arch_registered_archives ();
    }

  for (w = 0; w < rel_n_records (archive_list); w++)
    {
      int x;
      int y;
      int z;
      int shown_archive = 0;
      struct arch_archive * arch = 0;
      rel_table categories = rel_table_nil;


      if (str_cmp("-MIRROR", str_chr_rindex(rel_peek_str (archive_list, w, 0), '-')) == 0 ||
          str_cmp("-SOURCE", str_chr_rindex(rel_peek_str (archive_list, w, 0), '-')) == 0 )
        continue;

      arch = arch_archive_connect(rel_peek_str (archive_list, w, 0), 0);

      categories = arch_archive_categories(arch);

      for (x = 0; x < rel_n_records (categories); ++x)
        {
          int shown_category = 0;
          rel_table branches = rel_table_nil;

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

          for (y = 0; y < rel_n_records (branches); ++y)
            {
              int shown_branch = 0;
              rel_table versions = rel_table_nil;

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

              for (z = 0; z < rel_n_records (versions); ++z)
                {
                  int needshow = 0;
                  int version_show = 0;
                  int since_changed = 0;
                  int limit_since;
                  int have_patches=0;
                  int sealed_branch;
                  t_uchar * patch;
                  t_uchar * only_patch;
                  t_uchar * patch_line = 0;
                  rel_table patch_output;

                  patch = arch_archive_latest_revision(arch, rel_peek_str (versions, z, 0),1);

                  if (!patch)
                    {
                      only_patch = str_save (0, "");
                    }
                  else
                    {
                      only_patch = arch_parse_package_name (arch_ret_patch_level, 0, patch);
                    }

                  if (! since_filename)
                    {
                      since_changed = 1;
                      limit_since = 1;
                    }
                  else
                    {
                      limit_since = 0;
                      since_changed = ! rbrowse_since_has_newest (patch, saved_since);
                    }
                  
                  if (snap_file)
                    rbrowse_build_since_table (arch, rel_peek_str (versions, z, 0), &since_output);
                  

                  if (regexec (&limit_regex_needle, rel_peek_str (versions, z, 0), 0, 0, 0) == 0)
                    version_show = 1;
                        
                  needshow = needshow || since_changed;
                  needshow = needshow && version_show;

                  if (needshow && want_logs)
                    {
                      patch_output = rbrowse_patchlog(arch, patch_regex_needle, rel_peek_str (versions, z, 0), saved_since);
                      have_patches = !! rel_n_records(patch_output);
                      needshow = needshow && have_patches;
                    }

                  if (needshow)
                    {
                      sealed_branch = ! str_cmp_prefix ("version", only_patch);

                      needshow = needshow && !(sealed_branch && !(unhide_sealed) );
                    }
                  
                  if (needshow)
                    {

                      if (!shown_archive)
                        {
                          shown_archive = 1;
                          safe_printfmt (1,"%s\n", rel_peek_str (archive_list, w, 0));
                        }
                      if (!shown_category)
                        {
                          shown_category = 1;
                          safe_printfmt (1, "   %s\n", rel_peek_str (categories, x, 0));
                        }
                      if (!shown_branch)
                        {
                          shown_branch = 1;
                          safe_printfmt(1, "      %s\n", rel_peek_str (branches, y, 0));
                        }

                      safe_printfmt(1, "         %s\n", rel_peek_str (versions, z, 0));
                      
                      patch_line =  rbrowse_revision_range (only_patch);
                      safe_printfmt(1, "             %s\n\n", patch_line);
                      lim_free(0, patch_line);
                      
                      if (want_logs)
                        {
                          if (log_reverse && rel_n_records(patch_output) > 1)
                            rel_reverse_table (patch_output);
                          
                          rbrowse_print_logs (patch_output, summarized_headers);
                        } 
                    }

                  if (have_patches)
                    rel_free_table(patch_output);

                  lim_free(0, patch);
                  lim_free(0, only_patch);
                }

              rel_free_table(versions);
            }

          rel_free_table(branches);
        }

      rel_free_table(categories);
    }

  if (snap_file)
    {
      if (!rel_n_records (since_output))
        panic("Nothing to Snap!");
      else
        {
          rbrowse_save_since (snap_file, snap_force, since_output);
          rel_free_table (since_output);
        }
    }
  return 0;
}


static void
rbrowse_print_logs (rel_table patch_output,
                    int summarized_headers)
{
  int x;
  

  for (x = 0 ; x < rel_n_records(patch_output); ++x)
    {
      
      assoc_table headers = 0;
      t_uchar * this_revision;
      t_uchar * patch_name;
      

      arch_parse_log (0, &headers, 0, rel_peek_str (patch_output, x, 0));
      this_revision = str_save (0, assoc_get_str_taking (headers, rel_make_field_str ("revision")));
      patch_name = arch_parse_package_name (arch_ret_patch_level, 0, this_revision);

      if (summarized_headers)
        {
          safe_printfmt(1, "               %s\n", patch_name);
          arch_print_headers_summary (1, 18, headers, summarized_headers);
          safe_printfmt(1, "\n");
          free_assoc_table (headers);
        }
      
      lim_free(0, this_revision);
      lim_free(0, patch_name);
    }
}


static rel_table
rbrowse_patchlog (struct arch_archive * arch,
                  regex_t patch_regex_needle,
                  const t_uchar * version,
                  rel_table saved_since)
{
  rel_table revisions;
  rel_table patch_output = rel_table_nil;
  int saw_since;
  int x=0;

  saw_since = (! rel_n_records(saved_since));

  revisions = arch_archive_revisions(arch, version, 1);
  
  for (x = 0; x < rel_n_records(revisions); ++x)
    {
      int y;
      int missing_in_since = 1;
      t_uchar *revisions_version;
      revisions_version = arch_parse_package_name (arch_ret_package_version, 0, rel_peek_str (revisions, x, 0));

      /* First, see if our revision exists in the since file */
      for (y=0; y < rel_n_records(saved_since); ++y)
        {
          t_uchar *since_version;

          since_version = arch_parse_package_name (arch_ret_package_version, 0, rel_peek_str (saved_since, y, 0));

          if (str_cmp(revisions_version, since_version) == 0)
            missing_in_since = 0;

          lim_free (0, since_version);
        }

      if (saw_since || missing_in_since)
        {
          t_uchar * log;
          t_uchar * revision;
          t_uchar * summary;
          assoc_table headers = 0;

          revision = str_chr_index (rel_peek_str (revisions, x, 0), '/');
          revision++;

          log = arch_archive_log(arch, revision);
          arch_parse_log (0, &headers, 0, log);
          summary = str_save (0, assoc_get_str_taking (headers, rel_make_field_str ("summary")));
          
          if (regexec (&patch_regex_needle, summary, 0, 0, 0) == 0)
            {
              rel_add_records (&patch_output, rel_singleton_record_taking (rel_make_field_str (log)), rel_record_null);
            }

          lim_free(0, summary);
          lim_free(0, log);
          free_assoc_table (headers);
        }
      if (! saw_since)
        {
          int z;
          for (z = 0 ; z < rel_n_records(saved_since); z++)
            {
              if ( str_cmp(rel_peek_str (revisions, x, 0), rel_peek_str (saved_since, z, 0)) == 0)
                saw_since = 1;
            }
        }
      lim_free (0, revisions_version);
    }

  return patch_output;
}


static int
rbrowse_since_has_newest (t_uchar * revision,
                          rel_table saved_since)
{
  int exists=0;
  int x;

  for (x=0; x < rel_n_records (saved_since); ++x)
    {
      if (str_cmp (revision, rel_peek_str (saved_since, x, 0)) == 0)
        exists = 1;
    }
  return exists;
}


static t_uchar *
rbrowse_revision_range (const t_uchar * latest_revision)
{
  t_uchar *retval;

  if (str_length (latest_revision) < 1)
    {
      retval = str_save (0, "");
    }
  else if (str_cmp(latest_revision, "base-0") == 0)
    {
      retval = str_save (0, "base-0");
    }
  else
      retval = str_alloc_cat_many (0, "base-0 .. ", latest_revision, str_end);

  return retval;
}


static void 
rbrowse_build_since_table (struct arch_archive *arch, 
                           const t_uchar *version,
                           rel_table * since_output)
{
  t_uchar *latest_revision;
  
  latest_revision = arch_archive_latest_revision (arch, version, 1);
  rel_add_records (since_output, rel_singleton_record_taking (rel_make_field_str (latest_revision)),rel_record_null);
  lim_free(0, latest_revision);

}


static rel_table
rbrowse_load_since (const t_uchar *filename)
{
  int in_fd;
  rel_table since_table;
  int x;

  in_fd = safe_open (filename, O_RDONLY, 0);

  since_table = rel_read_table (in_fd, 1, "rbrowse_load_since" , filename);
  
  for (x=0; x < rel_n_records( since_table ); ++x)
    {
      if (! arch_valid_package_name( rel_peek_str (since_table, x, 0), arch_maybe_archive, arch_req_version, 1))
        {
          safe_printfmt (2, "illegal line ('%s') in since file '%s'\n", rel_peek_str (since_table, x, 0), filename);
          exit(1);
        }
    }

  safe_close (in_fd);

  return since_table;
}


static void
rbrowse_save_since (const t_uchar *filename,
                    int snap_force,
                    rel_table since_table)
{
  int out_fd;
  int x;
  
  
  out_fd = safe_open (filename, O_WRONLY | O_CREAT | (snap_force ? 0 : O_EXCL), 0666);

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



/* tag: James Blackwell Thu Dec 11 16:50:14 EST 2003 (cmd-rbrowse.c)
*/
