/* star-merge.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/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/merge-points.h"
#include "tla/libarch/local-cache.h"
#include "tla/libarch/make-changeset.h"
#include "tla/libarch/apply-changeset.h"
#include "tla/libarch/chatter.h"
#include "tla/libarch/inode-sig.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/star-merge.h"


/* __STDC__ prototypes for static functions */
static void star_merge_callback (void * vfd, const char * fmt, va_list ap);
static assoc_table tag_index_to_full_paths (t_uchar * tree_root);



/*
 *
 * star-merge FROM MY-TREE [MY-VERSION]
 *
 * from-patch-j is the latest rev from FROM's version in both my tree
 *              and FROM
 *
 * from-patch-k is the latest rev in FROM merging
 *              my-version-m into FROM where my-version-m
 *              is present in MY-TREE.
 *
 *
 * if j > k
 *   delta(from-patch-j,from-patch-k)[my-tree]
 * else
 *   delta(my-version-m,from-patch-k)[my-tree]
 * fi
 */

t_uchar *
arch_nearest_star_ancester (t_uchar * from_tree_root, t_uchar * from_archive, t_uchar * from_revision,
                            t_uchar * to_tree_root, t_uchar * to_archive, t_uchar * to_version)
{
  t_uchar * from_version = 0;
  rel_table to_logs_for_from_version = rel_table_nil;
  rel_table from_logs_for_from_version = rel_table_nil;
  rel_table common_from_logs = rel_table_nil;
  t_uchar * latest_common_from_revision = 0;
  rel_table from_merges_from_to = rel_table_nil;
  t_uchar * highest_common_to_revision = 0;
  t_uchar * from_rev_of_highest_common_to_revision = 0;
  t_uchar * answer = 0;
  int x;


  from_version = arch_parse_package_name (arch_ret_package_version, 0, from_revision);

  to_logs_for_from_version = arch_logs (to_tree_root, from_archive, from_version, 0);
  from_logs_for_from_version = arch_logs (from_tree_root, from_archive, from_version, 0);

  rel_sort_table_by_field (0, to_logs_for_from_version, 0);
  rel_sort_table_by_field (0, from_logs_for_from_version, 0);
  common_from_logs = rel_join (-1, rel_join_output (1,0, -1), 0, 0, to_logs_for_from_version, from_logs_for_from_version);
  arch_sort_table_by_patch_level_field (1, common_from_logs, 0);

  if (rel_n_records (common_from_logs))
    latest_common_from_revision = str_alloc_cat_many (0, from_version, "--", rel_peek_str (common_from_logs, 0, 0), str_end);

  from_merges_from_to = arch_tree_merge_points (from_tree_root, from_archive, from_version, to_archive, to_version);
  arch_sort_table_by_name_field (1, from_merges_from_to, 1);

  for (x = 0; !highest_common_to_revision && (x < rel_n_records (from_merges_from_to)); ++x)
    {
      if (arch_tree_has_log (to_tree_root, to_archive, rel_peek_str (from_merges_from_to, x, 1)))
        {
          highest_common_to_revision = str_save (0, rel_peek_str (from_merges_from_to, x, 1));
          from_rev_of_highest_common_to_revision = str_alloc_cat_many (0, from_version, "--", rel_peek_str (from_merges_from_to, x, 0), str_end);
        }
    }

  if (!from_rev_of_highest_common_to_revision && !latest_common_from_revision)
    {
      answer = 0;
    }
  else if (!from_rev_of_highest_common_to_revision)
    {
      answer = arch_fully_qualify (from_archive, latest_common_from_revision);
    }
  else if (!latest_common_from_revision)
    {
      answer = arch_fully_qualify (to_archive, highest_common_to_revision);
    }
  else if (0 < arch_names_cmp (from_rev_of_highest_common_to_revision, latest_common_from_revision))
    {
      answer = arch_fully_qualify (to_archive, highest_common_to_revision);
    }
  else
    {
      answer = arch_fully_qualify (from_archive, latest_common_from_revision);
    }


  lim_free (0, from_version);
  rel_free_table (to_logs_for_from_version);
  rel_free_table (from_logs_for_from_version);
  rel_free_table (common_from_logs);
  lim_free (0, latest_common_from_revision);
  rel_free_table (from_merges_from_to);
  lim_free (0, highest_common_to_revision);
  lim_free (0, from_rev_of_highest_common_to_revision);

  return answer;
}



int
arch_star_merge (int chatter_fd,
                 struct arch_archive * from_arch, t_uchar * from_archive, t_uchar * from_revision,
                 t_uchar * to_tree_root, struct arch_archive * to_arch, t_uchar * to_archive, t_uchar * to_version,
                 t_uchar * cache_dir, t_uchar * changeset_output, int use_diff3, int forward,
                 int escape_classes)
{
  int answer = 2;
  t_uchar * tmp_dir = 0;
  t_uchar * changeset = 0;
  t_uchar * from_tree_root = 0;
  t_uchar * fq_orig_revision = 0;
  t_uchar * orig_archive = 0;
  t_uchar * orig_revision = 0;

  tmp_dir = tmp_file_name (to_tree_root, ",,star-merge");
  if (!changeset_output)
    changeset = file_name_in_vicinity (0, tmp_dir, ",,changeset");
  else
    changeset = str_save (0, changeset_output);

  rmrf_file (tmp_dir);
  safe_mkdir (tmp_dir, 0777);

  from_tree_root = arch_find_or_make_tmp_local_copy (chatter_fd, tmp_dir, to_tree_root, cache_dir, from_arch, from_archive, from_revision);

  fq_orig_revision = arch_nearest_star_ancester (from_tree_root, from_archive, from_revision, to_tree_root, to_archive, to_version);

  if (!fq_orig_revision)
    {
      safe_printfmt (2, "star-merge: unable to merge unrelated trees.\n");
      answer = 2;
    }
  else
    {
      t_uchar * orig_tree_root = 0;
      struct arch_make_changeset_report make_report = {0, };
      struct arch_apply_changeset_report apply_report = {0, };
      assoc_table inode_shortcut = 0;

      arch_chatter (chatter_fd, "* star-merge by delta(%s,%s/%s)[%s]\n", fq_orig_revision, from_archive, from_revision, to_tree_root);

      orig_archive = arch_parse_package_name (arch_ret_archive, 0, fq_orig_revision);
      orig_revision = arch_parse_package_name (arch_ret_non_archive, 0, fq_orig_revision);

      orig_tree_root = arch_find_or_make_tmp_local_copy (chatter_fd, tmp_dir, to_tree_root, cache_dir, (str_cmp (orig_archive, from_archive) ? 0 : from_arch), orig_archive, orig_revision);

      if (changeset_output)
        {
          make_report.callback = star_merge_callback;
          make_report.thunk = (void *)(long)chatter_fd;
        }

      arch_read_inode_sig (0, &inode_shortcut, from_tree_root, orig_archive, orig_revision);
      arch_make_changeset (&make_report, orig_tree_root, from_tree_root, changeset, arch_unspecified_id_tagging, arch_inventory_unrecognized, rel_table_nil, inode_shortcut, 0, escape_classes);

      if (changeset_output)
        answer = 0;
      else
        {
          assoc_table older_files_table = 0;
          assoc_table yours_files_table = 0;

          if (use_diff3)
            {
              older_files_table = tag_index_to_full_paths (orig_tree_root);
              yours_files_table = tag_index_to_full_paths (from_tree_root);
            }

          apply_report.callback = star_merge_callback;
          apply_report.thunk = (void *)(long)chatter_fd;

          arch_chatter (chatter_fd, "* applying changeset\n");
          safe_flush (chatter_fd);

          arch_apply_changeset (&apply_report, changeset, to_tree_root, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, forward, use_diff3, older_files_table, yours_files_table, escape_classes);

          if (arch_conflicts_occured (&apply_report))
            answer = 1;
          else
            answer = 0;

          free_assoc_table (older_files_table);
          free_assoc_table (yours_files_table);
        }

      arch_free_make_changeset_report_data (&make_report);
      arch_free_apply_changeset_report_data (&apply_report);

      lim_free (0, orig_tree_root);
      free_assoc_table (inode_shortcut);
    }

  rmrf_file (tmp_dir);

  lim_free (0, tmp_dir);
  lim_free (0, from_tree_root);
  lim_free (0, fq_orig_revision);
  lim_free (0, orig_archive);
  lim_free (0, orig_revision);

  return answer;
}



static void
star_merge_callback (void * vfd, const char * fmt, va_list ap)
{
  int fd;

  fd = (int)(t_ulong)vfd;
  if (fd >= 0)
    {
      safe_printfmt_va_list (fd, fmt, ap);
      safe_flush (fd);
    }
}



static assoc_table
tag_index_to_full_paths (t_uchar * tree_root)
{
  rel_table index = rel_table_nil;
  assoc_table answer = 0;
  int x;

  index = arch_source_inventory (tree_root, 1, 0, 0);
  for (x = 0; x < rel_n_records (index); ++x)
    {
      rel_field relpath_field;
      rel_field id_field;
      t_uchar * full_path, *cwd = 0, *tpath;

      relpath_field = rel_get_field (index, x, 0);
      id_field = rel_get_field (index, x, 1);
      full_path = file_name_in_vicinity (0, tree_root, rel_field_str (relpath_field));
        
      if (!file_name_is_absolute(full_path))
        {
           cwd = safe_current_working_directory ();
           tpath = full_path;
           full_path = str_alloc_cat_many(0, cwd, "/", tpath, str_end);
           lim_free(0, cwd);
           lim_free(0, tpath);
        }

      assoc_set_taking (&answer, rel_field_ref (id_field), rel_make_field_str (full_path));

      rel_field_unref (relpath_field);
      rel_field_unref (id_field);
      lim_free (0, full_path);
    }

  rel_free_table (index);

  return answer;
}


extern void 
arch_merge3(int chatter_fd, t_uchar * mine_tree_root, t_uchar * base_tree_root, t_uchar * other_tree_root, int escape_classes)
{
  struct arch_make_changeset_report make_report = {0, };
  struct arch_apply_changeset_report apply_report = {0, };

  assoc_table older_files_table;
  assoc_table yours_files_table;

  t_uchar * tmp_dir = tmp_file_name (mine_tree_root, ",,star-merge");
  t_uchar * changeset = file_name_in_vicinity (0, tmp_dir, ",,changeset");
  assoc_table inode_shortcut = 0;

  t_uchar * base_revision = arch_tree_latest_revision (base_tree_root);
 
  rmrf_file (tmp_dir);
  safe_mkdir (tmp_dir, 0777);

  if (chatter_fd != -1)
    {
      make_report.callback = star_merge_callback;
      make_report.thunk = (void *)(long)chatter_fd;
      apply_report.callback = star_merge_callback;
      apply_report.thunk = (void *)(long)chatter_fd;
    }
  older_files_table = tag_index_to_full_paths (base_tree_root);
  yours_files_table = tag_index_to_full_paths (other_tree_root);
  
  arch_read_inode_sig (0, &inode_shortcut, base_tree_root, other_tree_root, base_revision);

  arch_make_changeset (&make_report, base_tree_root, other_tree_root, changeset,
  arch_unspecified_id_tagging, arch_inventory_unrecognized, rel_table_nil, inode_shortcut, 0, escape_classes);
 
  arch_apply_changeset (&apply_report, changeset, mine_tree_root, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, 0, 1, older_files_table, yours_files_table, escape_classes);
  rmrf_file (tmp_dir);
}




/* tag: Tom Lord Sat Jun 28 16:40:26 2003 (star-merge.c)
 */
