/* cmd-annotate.c:
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2005 Canonical Ltd.
 * 	Authors: Robert Collins <robert.collins@canonical.com>
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/tmp-files.h"
#include "libarch/annotation-builder.h"
#include "libarch/changeset-report.h"
#include "libarch/namespace.h"
#include "libarch/project-tree.h"
#include "libarch/my.h"
#include "libarch/ancestry.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/patch-logs.h"
#include "libarch/invent.h"
#include "libarch/local-cache.h"
#include "libarch/project-tree.h"
#include "libfsutils/file-contents.h"
#include "commands/cmdutils.h"
#include "commands/annotate.h"
#include "commands/version.h"



static t_uchar * usage = N_("[options] [path ...]");

#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\n" \
      "and exit.")) \
  OP (opt_dir, "d", "dir DIR", 1, \
      N_("cd to DIR first")) 
/*  OP (opt_merges, "m", "merges", 0, \
      N_("follow merges to determine the original author of each change line")) */ 

t_uchar arch_cmd_annotate_help[] = N_("display the changesets that last modified lines in the tree\n");

enum options
{
  OPTS (OPT_ENUM)
};

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

/**
 * \brief return the content of a file, broken at newlines
 */
static rel_table
tree_file_lines (arch_project_tree_t *tree, t_uchar const *rel_path)
{
    t_uchar *path=file_name_in_vicinity (0, tree->root, rel_path);
    rel_table result = file_lines (path);
    lim_free (0, path);
    return result;
}

/**
 * \brief return the number of lines in the file
 */
static int
tree_file_line_count (arch_project_tree_t *tree, t_uchar const *rel_path)
{
    rel_table lines = tree_file_lines (tree, rel_path);
    int result = rel_n_records (lines);
    rel_free_table (lines);
    return result;
}

/**
 * \brief get the contents of a patch file from disk
 * 
 * fixes up the broken paths returned by evaluate_changeset
 * \param patch_path the relpath to the file in the changeset
 * 
 */
static t_uchar *
patch_contents (t_uchar const * patch_path)
{
    t_uchar *path=str_alloc_cat (0, patch_path, ".patch");
    t_uchar *result=file_contents (path);
    lim_free (0, path);
    return result;
}

/**
 * \brief return non zero if a file is a symlink
 */
static int
is_symlink (t_uchar const *path)
{
  struct stat stat_info;
  safe_lstat ((t_uchar *)path, &stat_info);
  return  S_ISLNK (stat_info.st_mode);
}
  
/**
 * \brief return non zero if a file is a symlink
 */
static int
tree_is_symlink (arch_project_tree_t *tree, t_uchar const *rel_path)
{
    t_uchar *path=file_name_in_vicinity (0, tree->root, rel_path);
    int result = is_symlink (path);
    lim_free (0, path);
    return result;
}

int
arch_cmd_annotate (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * dir = 0;

  safe_buffer_fd (1, 0, O_WRONLY, 0);

  dir = str_save (0, ".");

  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, libarch_version_string, arch_cmd_annotate_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_dir:
          {
            lim_free (0, dir);
            dir = str_save (0, option->arg_string);
            break;
          }
        }
    }

    {
      arch_project_tree_t * tree;
      t_uchar * current_revision;
      arch_annotation_builder_t * builder;
      rel_table source_files;
      arch_archive_connection_cache cache;
      arch_archive_connection_cache_init (&cache);

      tree = arch_project_tree_new (talloc_context, dir);

      source_files = arch_paths_from_user (NULL, "annotate", tree, argc - 1, &argv[1]);

      builder = arch_annotation_builder_new (talloc_context);
      /* seed the builder */
        {
          int position;
          
          for (position = 0; position < rel_n_records (source_files); ++position)
            {
              invariant (0 < str_length (source_files[position][1]));
              /* we don't keep the files in memory at this point, as that is a heavy memory burden for large trees,
               * and for small trees they'll likely stay in cache
               * -RBC 20050223
               */
              if (!tree_is_symlink (tree, source_files[position][0]))
                  arch_annotation_builder_add_file (builder, source_files[position][1], tree_file_line_count(tree, source_files[position][0]));
            }
        }

      current_revision = str_save (0, tree->fqrevision);
      while (current_revision)
        {
          t_uchar * next_rev = NULL;
          struct exception * e;
          Try 
            {
              arch_patch_id * patch_id = talloc_steal (talloc_context, 
                                                       arch_patch_id_new (current_revision));
              arch_patch_id * patch_result = arch_patch_ancestor (talloc_context,
                                                                  patch_id, 0);
              if (patch_result)
                  next_rev = str_save (0, arch_patch_id_patch_id (patch_result));
              else
                  next_rev = NULL;
            }
          Catch (e)
            {
              /* dont mask critical errors */
              if (e->code < 0)
                  Throw (e);
              talloc_free (e);
              /* cannot dig deeper */
              lim_free (0, current_revision);
              current_revision = NULL;
              next_rev = NULL;
            }
          
            {
              arch_patch_id aPatch;
 //             t_uchar *creator;
              struct arch_archive * this_arch;
              
              arch_patch_id_init (&aPatch, current_revision);
              this_arch = arch_archive_connection_cache_find_or_maybe_connect (&cache, arch_patch_id_archive(&aPatch), 1);
              
   //           creator = 
              arch_annotation_builder_add_patch (builder, &aPatch, "creator");

              if (!this_arch)
                {
                  /* cannot dig deeper */
                  lim_free (0, next_rev);
                  lim_free (0, current_revision);
                  current_revision = NULL;
                  continue;
                }
              
              if (arch_archive_get_revision_type(this_arch, arch_patch_id_revision (&aPatch)) == arch_import_revision)
              /* 
               * get the file lines, and synthetically process
               * as though its a patch.
               */
                {
                  t_uchar * temp_root = arch_find_or_make_local_copy (-1, tree, NULL, NULL, arch_patch_id_archive(&aPatch),
                                                                      arch_patch_id_revision (&aPatch));
                  arch_project_tree_t * temp_tree;
                  rel_table reference_source_files;
                  int file;
                  temp_tree = arch_project_tree_new (talloc_context, temp_root);
                  reference_source_files = arch_source_files_inventory (temp_tree, 0, 0);
                  for (file = 0; file < rel_n_records (reference_source_files); ++file)
                    {
                      int line;
                      int lines;
                      if (!arch_annotation_builder_has_file (builder, reference_source_files[file][1]))
                          continue;
                      if (tree_is_symlink (temp_tree, reference_source_files[file][0]))
                          continue;
                      lines = tree_file_line_count(temp_tree, reference_source_files[file][0]);
                      arch_annotation_builder_add_file (builder, reference_source_files[file][1], -1);
                      for (line=0; line < lines; ++ line)
                          arch_annotation_builder_add_line(builder, 0);
                    }
                  
                  arch_project_tree_delete (temp_tree);
                  lim_free (0, temp_root);
                }
              else
                {
                  /* get unpacked changeset */
                  t_uchar * tmppatch = talloc_tmp_file_name (talloc_context, tree->root, ",,annotate-patch");
                  struct arch_changeset_report changeset;
                  int file;
                  arch_get_patch (this_arch, &aPatch, tmppatch);
                  /* create memory object */
                  arch_changeset_report_init (&changeset);
                  arch_evaluate_changeset (&changeset, tmppatch);
                  //rel_print_table (2, changeset.patched_regular_files);

                  /* patched files */
                  for (file = 0; file < rel_n_records (changeset.patched_regular_files); ++file)
                    {
                      t_uchar *patch_content;
                      patch_line_changes_list changes;
                      if (!arch_annotation_builder_has_file (builder, changeset.patched_regular_files[file][1]))
                          continue;
                      patch_content = patch_contents (changeset.patched_regular_files[file][2]);
                      changes = patch_parser_parse(patch_content);
                      
                      arch_annotation_builder_add_file (builder, changeset.patched_regular_files[file][1], -1);
                      arch_annotation_builder_process_changes(builder, changes);

                      ar_free_patch_line_change(&changes);
                      lim_free (0, patch_content);
                    }
                  
                  rel_for_each (changeset.added_files, file)
                    {
                      int line;
                      int lines;
                      if (!arch_annotation_builder_has_file (builder, changeset.added_files[file][1]))
                          continue;
                      lines = file_line_count(changeset.added_files[file][2]);
                      arch_annotation_builder_add_file (builder, changeset.added_files[file][1], -1);
                      for (line=0; line < lines; ++ line)
                          arch_annotation_builder_add_line(builder, 0);
                    }
                  
                  arch_free_changeset_report_data (&changeset);
                  rmrf_file(tmppatch);
                  talloc_free (tmppatch);
                }
              arch_patch_id_finalise (&aPatch);
              lim_free (0, current_revision);
              current_revision = next_rev;
            }

        }
      
      /* print the result */
      /* TODO make this callable with an fd and a list of files */
        {
          int position;
          
          for (position = 0; position < rel_n_records (source_files); ++position)
            {
              int line;
              rel_table lines;
              arch_annotated_file_t *aFile;
              invariant (0 < str_length (source_files[position][1]));
              if (tree_is_symlink (tree, source_files[position][0]))
                  continue;
              safe_printfmt (1, "ANNOTATIONS FOR %s\n", source_files[position][0]);
              lines=tree_file_lines (tree, source_files[position][0]);
              aFile = arch_annotation_builder_get_file (builder, talloc_context, source_files[position][1]);
              for (line = 0; line < rel_n_records (lines); ++line)
                {
                  if (aFile->lines[line])
                      safe_printfmt (1, "%s: %s\n", arch_patch_id_patch_id(arch_patch_patch_id(aFile->lines[line])), lines[line][0]);
                  else 
                      safe_printfmt (1, "unknown: %s\n", lines[line][0]);
                }
              talloc_free (aFile);
              rel_free_table (lines);
            }
          
        }
      
      rel_free_table (source_files);
      arch_archive_connection_cache_finalise (&cache, NULL);
      arch_project_tree_delete (tree);
      talloc_free (builder);
    }

  lim_free (0, dir);

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1