/* cmd-log.c
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2005 Canonocal Ltd.
 * Original Authors Robert Collins and James Blackwell
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


/* FIXME: SEE THIS USING STDLIB FOR ATOI. THIS NEEDS TO BE IN HACKERLAB */
#include <stdlib.h>
/* FIXME: SEE THIS USING STDLIB FOR ATOI. THIS NEEDS TO BE IN HACKERLAB */

#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "libarch/ancestry.h"
#include "libarch/merge.h"
#include "libarch/patch-logs.h"
#include "libarch/project-tree.h"
#include "libarch/namespace.h"
#include "commands/log.h"
#include "commands/version.h"


enum log_types {
  LOG_BEGIN,
  LOG_END
};


static int get_logs(rel_table * output, arch_project_tree_t * tree);
static rel_record record_for_patch (arch_patch_id * patch_id, arch_project_tree_t *tree);
static int qualify_revision (arch_project_tree_t * tree,
                                     t_uchar * source,
                                     t_uchar ** qualed,
                                     int log_type);
static int limit_logs_by_range (rel_table * output, rel_table input,
                                   t_uchar * start, t_uchar * end);

static int split_range (t_uchar * range_input,
                        t_uchar ** start,
                        t_uchar **end);
static t_uchar * log_format_short (t_uchar * revision,
                                   t_uchar * date,
                                   t_uchar * creator,
                                   t_uchar * log);


static t_uchar * usage = N_("[-f|--full] [-r start[:end]]");


#define OPTS(OP) \
    OP (opt_help_msg, "h", "help", 0, \
              N_("Display a help message and exit.")) \
    OP (opt_long_help, "H", 0, 0, \
              N_("Display a verbose help message and exit.")) \
    OP (opt_version, "V", "version", 0, \
              N_("Display a release identifier string and exit."))  \
    OP (opt_range, "r", "range", 1, \
              N_("-r start[:end], where start+end are PATCH or PACKAGE")) \
    OP (opt_full_logs, "f", "full", 0, \
              N_("show the entire log message rather than the summary")) 

t_uchar arch_cmd_log_help[] = N_(
                  "Show the log messages for the working directory or given\n"
                  "files/directories, since the beginning of time (crossing\n"
                  "copies/renames) or since/between the revisions/dates \n"
                  "given. Can also be run out of a working directory to\n"
                  " interrogate an archive instead of a working tree->\n"
                  "\n"
                  "Examples:\n"
                  "Show me the changes made to scripts/dpkg-source.pl in the \n"
                  "archive:\n"
                  "   %% baz log -r dpkg--devel--1.13 scripts/dpkg-source.pl\n"
                  "\n"
                  "Show me the changes between a different branch's \n"
                  "patch-4, my branches patch-2:\n"
                  "    %% baz log -r dpkg--devel--1.13--patch-4:patch-2\n"
                  "\n"
                  "Show me the changes in all revisions of the tree-version\n"
                  "after and including patch-123.\n"
                  "    %% baz log -r patch-123:\n"
                  "\n"
                  " Show me the last ten changes:\n"
                  "    %% baz log -r 10\n");

enum options

{
  OPTS (OPT_ENUM)
};

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

int
arch_cmd_log (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  t_uchar * log_range_start = 0;
  t_uchar * log_range_end = 0;
  int show_full_logs = 0;
  struct opt_parsed * option;

  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, libarch_version_string, arch_cmd_log_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_range :
            {
              if ( log_range_start || log_range_end )
                panic(N_("More than exactly one range provided."));

              if (! split_range (option->arg_string,
                               &log_range_start,
                               &log_range_end))
                {
                  panic(N_("Invalid range given"));
                }


              break;
            }
        case opt_full_logs:
            {
              show_full_logs = 1;
              break;
            }
        }
    }

  {
    arch_project_tree_t * tree;
    rel_table unlimited_logs = 0;
    rel_table range_limited_logs = 0;
    t_uchar * latest_revision;
    t_uchar * fqrvn;
    t_uchar * revision_start = 0;
    t_uchar * revision_end = 0;
    int status;
    int loop;

    /* Before we can do much, we have to figure out where we are */
    tree = arch_project_tree_new (talloc_context, ".");
    latest_revision = arch_tree_latest_revision (tree->root);
    fqrvn = str_alloc_cat_many (0, tree->archive, "/", latest_revision, str_end);

    if (get_logs ( & unlimited_logs , tree))
      panic (N_("Unable to get information about previous revisions\n"));
    
    /* Now, lets pound our user input into something useful  */
    if (argc > 1)
      panic (N_("Unhandled arguments. Did you forget to use -r or -d?"));

    /* First, get the start revision right */
    if (log_range_start)
      if (0 != qualify_revision (tree, log_range_start,
                                 &revision_start, LOG_BEGIN))
        panic(N_("Unable to parse range start."));

    /* Now, get the end revision right */
    if (log_range_end)
      if (0 !=  qualify_revision (tree, log_range_end, &revision_end, LOG_END))
        panic (N_("Unable to parse range end."));



    /* Now lint out the stuff the user told us he cared about */
    status = limit_logs_by_range (&range_limited_logs, unlimited_logs,
                                     revision_start, revision_end);

    /* All that work, just to print... sigh */
    for (loop = 0; loop < rel_n_records (range_limited_logs); loop++)
      {
        if (show_full_logs)
          {
            t_uchar * log = 0;
            log = arch_log_contents (tree->root,
                                      tree->archive,
                                      range_limited_logs[loop][0]);
            safe_printfmt(1, "%s\n", log);
            safe_printfmt(1, "       ================================"
                             "==================================\n\n");
            lim_free (0, log);
          }
        else
          {
            t_uchar * output = log_format_short (range_limited_logs[loop][0],
                                       range_limited_logs[loop][1],
                                       range_limited_logs[loop][2],
                                       range_limited_logs[loop][3]);
            safe_printfmt(1, "%s", output);
            lim_free (0, output);
          }
      }

    arch_project_tree_delete (tree);
    rel_free_table (unlimited_logs);
    lim_free (0, fqrvn);
    lim_free (0, latest_revision);

  } 

  return 0;
}

t_uchar *
log_format_short (t_uchar * revision,
                  t_uchar * date,
                  t_uchar * creator,
                  t_uchar * log)
{
  t_uchar * output = 0;

  output = str_alloc_cat_many (0, revision, "\n",
                                  "    ", date, "\n",
                                  "    ", creator, "\n",
                                  "    ", log, "\n\n",
                                  str_end);

  return output;
}

int
split_range (t_uchar * range_input, t_uchar ** start, t_uchar **end)
{
  t_uchar * colon;

  if (! range_input)
    return 1;

  colon = str_chr_index(range_input, ':');

  if ( !colon)
    *start = str_save (0, range_input);
  else
    {
      *start = str_save_n (0, range_input, colon - range_input);
      *end = str_save (0, colon+1);
    }

  return 1;
}

int 
qualify_revision (arch_project_tree_t * tree, t_uchar * source,
                  t_uchar ** qualed, int log_type)
{
  t_uchar * with_archive = 0;


  /* Check to see if we're already done */
  if ( arch_valid_package_name ( source, arch_maybe_archive,
                                   arch_req_patch_level, 0))
    {
      return 0;
    }
  if ( 0 != atoi(source)) 
    {
      *qualed = str_save (0, source);
      return 0;
    }

  /* If given patchlevel, we need to qualify it and return */
  if (arch_valid_patch_level_name (source))
    {
      *qualed = str_alloc_cat_many (0, tree->fqversion, "--",
                                       source, str_end);
      return 0;
    }

  /* Now it get harder. First, make sure its fully qualified */
  if (str_chr_index (source, '/'))
    with_archive = str_save (0, source);
  else
    with_archive = str_alloc_cat_many (0, tree->archive, "/",
                                         source, str_end );


  /* Now, check to ee if we were given a version. The patchlevel
   * we want is determined by whether we're doing the begin, or
   * the end
   */
  if ( arch_valid_package_name (source, arch_req_archive,
                                arch_req_version,0))
    {
      if (log_type == LOG_END)
        *qualed = str_save (0, with_archive);
      else /* LOG_BEGIN */
        *qualed = str_alloc_cat_many (0, with_archive, "--base-0", str_end);
    }


  return 0;
}

int
limit_logs_by_range (rel_table * output,
                        rel_table input,
                        t_uchar * input_start,
                        t_uchar * input_end)
{
  int loop;
  t_uchar * start = 0;
  t_uchar * end = 0;
  int start_offset = 0;
  int loop_end=-1;
  int end_offset;
  int start_found = 0;
  int end_found = 0;


  /* First, we setup our start offset and our start. We can't
   * have both, so either one is 0, or they're both 0 */
  if (! str_length(input_start))
    {
      start_offset = 0;
      start = 0;
    }
  else
    {
      if ( atoi(input_start))
        {
          start_offset = atoi(input_start);
          start = 0;
        }
      else
        {
          start_offset = 0;
          start = str_save(0, input_start);
        }
    }

  /* First, we setup our end offset and our end. We can't
   * have both, so either one is 0, or they're both 0 */
  if (! str_length(input_end))
    {
      end_offset = 0;
      end = 0;
    }
  else
    {
      if ( atoi(input_end))
        {
          end_offset = atoi(input_end);
          end = 0;
        }
      else
        {
          end_offset = 0;
          end = str_save(0, input_end);
        }
    }



  /* If there's no end, then we've already found it, and we need
   * to start counting the number of logs we added
   */
  if ( !end )
    {
      end_found = 1;
      if (start_offset)
        loop_end = start_offset;
    }

  for (loop = end_offset;!start_found && loop < rel_n_records(input); loop++)
    {
      /* Keep skipping until we find the end */
      if (! end_found && str_cmp_prefix (end, input[loop][0]))
        continue;

      /* As soon as we find the end, we need to calculate where the 
       * number of hops for where the start is
       */
      if (! end_found)
        {
          if (start_offset)
            loop_end = loop + start_offset;
          end_found = 1;
        }

      if ( loop == loop_end)
        start_found = 1;

      /* Is the the starting revision we're looking for */
      if ( start && 0 == str_cmp_prefix (start, input[loop][0]))
        start_found = 1;

      rel_add_records (output, rel_copy_record (input[loop]), 0);
      
    }

  return !(!start || start_found) && (!end || end_found);
}

/**
 * \brief retrieve the logs we will output according to the users requests
 * the output rel table has the results appended with the revision in 0, the
 * date in 1 and the summary in 2.
 * if a non fatal error occurs, the result is non zero.
 */
int
get_logs(rel_table * output, arch_project_tree_t *tree)
{
  int result = 0;

  /* The idea behind this is that we're keeping our interface to the other
   * (libarch) api as simple as possible. If necessary, between this api and 
   * the libarch api, we write a conversion function, an "adaptor" that works
   * as a shim between the two. Some day,if you ahve control over the other 
   * api, you could potentially refactor it out, converting other callers to 
   * the new interface - assuming its more sensible for them.
   * Otherwise, that's a sign that the api leaning towards 97 pound 
   * weakling, and needs to support both( or more) apis.
   */

  struct exception * e;
  Try 
    {
      arch_patch_id * current_revision = talloc_steal (talloc_context,
                                                       arch_patch_id_new (tree->fqrevision));
      while (current_revision)
        {
          rel_add_records ( output, record_for_patch( current_revision, tree), 0);
          arch_patch_id * next = arch_patch_ancestor (talloc_context, current_revision, 0);
          talloc_free (current_revision);
          current_revision = next;
        }
    }
  Catch (e)
    {
      /* dont mask critical errors */
      if (e->code < 0)
          Throw (e);
      talloc_free (e);
    }

  return result;
}

/**
 * \brief retrieve the logs we will output according to the users requests
 * the output rel table has the results appended with the revision in 0, the date in 1 and the 
 * summary in 2.
 * if a non fatal error occurs, the result is non zero.
 */
rel_record
record_for_patch (arch_patch_id * patch_id, arch_project_tree_t *tree)
{
  rel_record output = 0;
  assoc_table headers = 0;
  t_uchar * summary = "";


  t_uchar * log;

  log = arch_project_tree_patch_log (tree, arch_patch_id_patch_id (patch_id));
  if (log)
    {
      arch_parse_log (0, &headers, 0, log);
      
      if (!assoc_ref (headers, "summary"))
        summary = "";
      else
        summary = assoc_ref(headers, "summary");
      
      output = rel_make_record ( arch_patch_id_patch_id (patch_id),
                                 assoc_ref (headers, "date"),
                                 assoc_ref (headers, "creator"),
                                 summary, 0);
    }
  else
    {
      output = rel_make_record (arch_patch_id_patch_id (patch_id),
                                N_("UNKNOWN DATE"),
                                N_("UNKNOWN CREATOR"),
                                N_("LOG MISSING"), 0);
    }

  return output;
}




/* tag: James Blackwell Sun Feb  6 18:21:45 EST 2005 (log.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1