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

#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/pika-escaping-utils.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libarch/diffs.h"
#include "tla/libawk/relassoc.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/make-changeset-files.h"


/* __STDC__ prototypes for static functions */
static void __attribute__((format (printf, 2, 3))) invoke_report_callback (struct arch_make_changeset_report * report,
                                                                           const char * fmt, ...);
     static int find_id_in_inventory (rel_table invent,
                                      const t_uchar * id);
     static void double_check_file_identity (const t_uchar * from_here_path,
                                             const t_uchar * tree_root,
                                             const t_uchar * loc);
     

#if !defined(__GNUC__)
#  undef __attribute__
#  define __attribute__(X)
#endif

static void __attribute__((format (printf, 2, 3)))
invoke_report_callback (struct arch_make_changeset_report * report,
                        const char * fmt, ...)
{
  va_list ap;

  if (report->callback)
    {
      va_start (ap, fmt);
      report->callback (report->thunk, fmt, ap);
      va_end (ap);
    }
}


void
arch_make_files_changeset (struct arch_make_changeset_report * report,
                           const t_uchar * dest,
                           rel_table file_list,
                           const t_uchar * orig,
                           const t_uchar * mod,
                           enum arch_id_tagging_method method,
                           enum arch_inventory_category untagged_source_category,
                           int escape_classes)
{
  int x;

  struct arch_changeset_report csr = {rel_table_nil,};

  if (method == arch_unspecified_id_tagging)
    {
      method = arch_tree_id_tagging_method (&untagged_source_category, mod, 0);
    }

  mem_set0 ((t_uchar *)&report->orig_index, sizeof (report->orig_index));
  mem_set0 ((t_uchar *)&report->mod_index, sizeof (report->mod_index));

  arch_changeset_inventory (&report->orig_index, orig, orig, method, untagged_source_category, escape_classes);      
  report->mod_index.dirs = rel_copy_table (report->orig_index.dirs);
  report->mod_index.files = rel_copy_table (report->orig_index.files);


  /****************************************************************
   * build associative tables too and from ids and locs
   */
  report->orig_dir_id_of = rel_to_assoc (report->orig_index.dirs, 0, 1);
  report->orig_dir_loc_of = rel_to_assoc (report->orig_index.dirs, 1, 0);

  report->orig_file_id_of = rel_to_assoc (report->orig_index.files, 0, 1);
  report->orig_file_loc_of = rel_to_assoc (report->orig_index.files, 1, 0);

  report->mod_dir_id_of = rel_to_assoc (report->mod_index.dirs, 0, 1);
  report->mod_dir_loc_of = rel_to_assoc (report->mod_index.dirs, 1, 0);

  report->mod_file_id_of = rel_to_assoc (report->mod_index.files, 0, 1);
  report->mod_file_loc_of = rel_to_assoc (report->mod_index.files, 1, 0);

  assoc_set_taking (&report->orig_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&report->mod_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&report->orig_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));
  assoc_set_taking (&report->mod_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));

  arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_dir_id, report->orig_dir_id_of, report->orig_index.dirs);
  arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_file_id, report->orig_dir_id_of, report->orig_index.files);
  arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_dir_id, report->mod_dir_id_of, report->mod_index.dirs);
  arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_file_id, report->mod_dir_id_of, report->mod_index.files);

  arch_make_empty_changeset (report, &csr, dest);

  for (x = 0; x < rel_n_records (file_list); ++x)
    {
      t_uchar * id = 0;
      int orig_index;
      int mod_index;
      t_uchar * orig_path = 0;
      t_uchar * mod_path = 0;
      int diff_fd;

      id = arch_inventory_id (method, untagged_source_category, rel_peek_str (file_list, x, 0), 0, 0, 0);

      orig_index = find_id_in_inventory (report->orig_index.files, id);
      mod_index = find_id_in_inventory (report->mod_index.files, id);

      if (orig_index < 0)
        {
          safe_printfmt (2, "make-changeset-files: file missing from ORIG tree (%s)\n", rel_peek_str (file_list, x, 0));
          exit (1);
        }

      if (mod_index < 0)
        {
          safe_printfmt (2, "make-changeset-files: file missing from MOD tree (%s)\n", rel_peek_str (file_list, x, 0));
          exit (1);
        }

      double_check_file_identity (rel_peek_str (file_list, x, 0), mod, rel_peek_str (report->mod_index.files, mod_index, 0));

      orig_path = file_name_in_vicinity (0, orig, rel_peek_str (report->orig_index.files, orig_index, 0));
      mod_path = file_name_in_vicinity (0, mod, rel_peek_str (report->mod_index.files, mod_index, 0));

      if (arch_binary_files_differ (orig_path, mod_path, 0, 0))
        {
          int diff_stat; 
          t_uchar * escaped_tmp;

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, rel_peek_str (file_list, x, 0));
          invoke_report_callback (report, "M  %s\n", escaped_tmp);
          diff_fd = arch_changeset_add_diffs (&csr, report, dest, rel_peek_str (report->orig_index.files, orig_index, 0), rel_peek_str (report->mod_index.files, mod_index, 0), id);
          diff_stat = arch_invoke_diff (diff_fd, orig_path, rel_peek_str (report->orig_index.files, orig_index, 0), mod_path, rel_peek_str (report->orig_index.files, orig_index, 0), 0, 0);
          safe_close (diff_fd);

          lim_free (0, escaped_tmp);

          if (diff_stat == 1)
            {
              t_uchar diff_pseudo_magic[sizeof ("binary files ")];
              t_uchar * patches_path = 0;
              t_uchar * patch_path = 0;
              int diff_in_fd;
              long amt;

              patches_path = file_name_in_vicinity (0, dest, "patches");
              patch_path = file_name_in_vicinity (0, patches_path, rel_peek_str (report->mod_index.files, mod_index, 0));
              patch_path = str_realloc_cat (0, patch_path, ".patch");

              diff_in_fd = safe_open (patch_path, O_RDONLY, 0);

              /* diff might have just said "binary files differ" or something
               * similar.
               */
              amt = safe_read (diff_in_fd, diff_pseudo_magic, sizeof (diff_pseudo_magic) - 1);

              if (amt > 0)
                {
                  diff_pseudo_magic[amt] = 0;
                  if (!str_casecmp (diff_pseudo_magic, "binary files "))
                    diff_stat = 2;
                }
              safe_close (diff_in_fd);
              lim_free (0, patch_path);
              lim_free (0, patches_path);
            }

          if (diff_stat == 2)
            {
              safe_printfmt (2, "binary files not yet supported with --files or --file-list\n");
              exit (2);
            }
          
        }

      lim_free (0, id);
      lim_free (0, orig_path);
      lim_free (0, mod_path);
    }

  arch_changeset_rewrite_indexes (dest, &csr);
  arch_free_changeset_report_data (&csr);
}




static int
find_id_in_inventory (rel_table invent,
                      const t_uchar * id)
{
  int x;

  for (x = 0; x < rel_n_records (invent); ++x)
    {
      if (!str_cmp (id, rel_peek_str (invent, x, 1)))
        return x;
    }

  return -1;
}


static void
double_check_file_identity (const t_uchar * from_here_path,
                            const t_uchar * tree_root,
                            const t_uchar * loc)
{
  struct stat users_stat;
  t_uchar * computed_path = 0;
  struct stat computed_stat;

  safe_stat (from_here_path, &users_stat);
  computed_path = file_name_in_vicinity (0, tree_root, loc);
  safe_stat (computed_path, &computed_stat);

  lim_free (0, computed_path);
  if ((users_stat.st_dev != computed_stat.st_dev) || (users_stat.st_ino != computed_stat.st_ino))
    {
      safe_printfmt (2, "make-changeset --files: the file specified as %s\n  is not the same as the one in the project tree (%s)\n", from_here_path, computed_path);
      exit (1);
    }
}




/* tag: Tom Lord Tue Jun 17 21:39:34 2003 (make-changeset-files.c)
 */
