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

#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/link-tree.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/same.h"
#include "tla/libarch/arch.h"
#include "tla/libarch/chatter.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/build-revision.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/libraries.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/apply-changeset.h"
#include "tla/libarch/inode-sig.h"
#include "tla/libarch/replay.h"
#include "tla/libarch/my.h"
#include "tla/libarch/library-txn.h"


/* __STDC__ prototypes for static functions */
static void library_txn_callback (void * vfp,
                                  const char * fmt,
                                  va_list ap);



/*(c arch_library_add)
 * t_uchar * arch_library_add (int chatter_fd,
 *                             struct arch_archive * arch,
 *                             t_uchar * revision,
 *                             t_uchar * opt_library_spec,
 *                             t_uchar * opt_same_dev_path)
 * 
 * Return the path to the _library_ where REVISION from ARCH
 * should be added by library-add.
 *
 * OPT_LIBRARY_SPEC is the (optional) path to a library to which 
 * the user explicitly requested addition.   If not 0, we essentially
 * validate that parameter.
 * 
 * OPT_SAME_DEV_PATH is the (optional) path to a file or directory
 * on the device to which we want to library-add.   This is for
 * things like --link: we need to make sure that the revision
 * is added to a library on the same device.
 * 
 * Subject to the constraints of the OPT_ parameters, the users 
 * library path is searched (in add order):
 * 
 * If the revision exists in some library, the first such library
 * is returned.
 * 
 * Otherwise, if the revision's version exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's branch exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's category exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, if the revision's archive exists in some library, the 
 * first such library is returned.
 * 
 * Otherwise, the first library in the (qualifying part of) the path
 * is returned.
 * 
 * This function will not return 0.   If no place can be found to add
 * the revision subject to the constraints of the OPT_* parameters,
 * an error message is printed and the process exitted with status 2.
 */
t_uchar *
arch_library_add_choice (const t_uchar * archive,
                         const t_uchar * revision,
                         const t_uchar * opt_library,
                         const t_uchar * opt_same_dev_path)
{
  rel_table full_user_path = rel_table_nil;
  rel_table search_path = rel_table_nil;
  t_uchar * target_library = 0;


  if (opt_library && opt_same_dev_path)
    {
      if (!on_same_device (opt_library, opt_same_dev_path))
        {
          safe_printfmt (2, "tla: indicated library is not on the right device for dir\n  lib %s\n  dir %s\n", opt_library, opt_same_dev_path);
          exit (2);
        }
    }


  /* For the library path on which to search for
   * a place to add this revision.
   * 
   * Note that the path is in general a subsequence of
   * the user's full path:
   */

  full_user_path = arch_my_library_path (arch_library_path_add_order);

  if (opt_library)
    {
      int x;

      /* The user wants a specific library.
       * 
       * Make sure it is really on the user's library path,
       * then make that one library the entire search path.
       */

      for (x = 0; x < rel_n_records (full_user_path); ++x)
        {
          if (names_same_inode (opt_library, rel_peek_str (full_user_path, x, 0)))
            break;
        }

      if (x == rel_n_records (full_user_path))
        {
          safe_printfmt (2, "tla: indicated library is not on library path\n  dir %s\n", opt_library);
          exit (2);
        }

      rel_add_records (&search_path, rel_singleton_record_taking (rel_get_field (full_user_path, x, 0)), rel_record_null);
    }

  else
    search_path=make_search_path(full_user_path, opt_same_dev_path, 0);

  if (!rel_n_records (search_path))
    {
      if (opt_same_dev_path)
	safe_printfmt (2, "tla: no library on the same device as dir\n  dir %s\n", opt_same_dev_path);
      else
	safe_printfmt (2, "tla: no library to add to\n");
      exit (2);
    }


  /* Now the search path is set.
   * 
   * If the requested revision is already somewhere on that path,
   * we're done.
   */
  {
    t_uchar * already_there = 0;

    already_there = arch_library_find (search_path, archive, revision, 1);
    if (already_there)
      {
        t_uchar * there_vsn = 0;
        t_uchar * there_branch = 0;
        t_uchar * there_category = 0;
        t_uchar * there_archive = 0;

        there_vsn = file_name_directory_file (0, already_there);
        there_branch = file_name_directory_file (0, there_vsn);
        there_category = file_name_directory_file (0, there_branch);
        there_archive = file_name_directory_file (0, there_category);

        target_library = file_name_directory_file (0, there_archive);

        lim_free (0, there_vsn);
        lim_free (0, there_branch);
        lim_free (0, there_category);
        lim_free (0, there_archive);
        lim_free (0, already_there);
      }
    /* Not there -- we must decide where to add it */
    else
      {
	target_library = arch_find_best_library (archive, search_path, revision);
      }
  }

  rel_free_table (full_user_path);
  rel_free_table (search_path);

  return target_library;
}

/*
 * This function is similar to the above, but it may return NULL
 *
 * It finds the best greedy library to add a given revision to
 * If opt_same_dev_path has a value, matches are restricted to libraries on the
 * device containing this path
 */
t_uchar *
arch_library_greedy_add_choice (const t_uchar * archive, 
                                const t_uchar * revision, 
                                const t_uchar * opt_same_dev_path,
                                int require_greedy)
{
  rel_table full_user_path = rel_table_nil;
  rel_table search_path = rel_table_nil;

  t_uchar * target_library = 0;  

  full_user_path = arch_my_library_path (arch_library_path_add_order);
  search_path = make_search_path (full_user_path, opt_same_dev_path, require_greedy);

  target_library = arch_find_best_library (archive, search_path, revision);
  
  rel_free_table (full_user_path);
  rel_free_table (search_path);
  return target_library;
}

/* Produces a search path containing only libraries that matches the given
 * criteria:
 * if opt_same_dev_path has a value, canditates must be on the same device as
 * the provided path
 * if require_greedy is true, candidates must be greedy
 * returns 0 if there are no matching candidates
 */
rel_table
make_search_path (rel_table full_user_path,
                  const t_uchar * opt_same_dev_path,
                  int require_greedy)
{
  rel_table search_path = rel_table_nil;
  int x;

  for (x = 0; x < rel_n_records (full_user_path); ++x)
    {
      if (opt_same_dev_path && !on_same_device (opt_same_dev_path, rel_peek_str (full_user_path, x, 0)))
	continue;

      if (require_greedy && !arch_library_is_greedy(rel_peek_str (full_user_path, x, 0)))
	continue;
      rel_add_records (&search_path, rel_singleton_record_taking (rel_get_field (full_user_path, x, 0)), rel_record_null);
    }

  return search_path;
}


/* We search along the path and pick the first revision
 * with the most specific already-present stuff (same version,
 * branch, category, or archive) -- defaulting to the first in the 
 * path.
 */
t_uchar *
arch_find_best_library(const t_uchar * archive, 
                       rel_table search_path,
                       const t_uchar * revision)
{
  const t_uchar * best_guess;

  if (rel_n_records (search_path) == 0)
    return 0;

  if (rel_n_records (search_path) == 1)
    {
      best_guess = rel_peek_str (search_path, 0, 0);
    }
  else
    {
      t_uchar * category = 0;
      t_uchar * branch = 0;
      t_uchar * version = 0;
      int guess_priority;
      int x;

      category = arch_parse_package_name (arch_ret_category, 0, revision);
      branch = arch_parse_package_name (arch_ret_package, 0, revision);
      version = arch_parse_package_name (arch_ret_package_version, 0, revision);
      
      best_guess = rel_peek_str (search_path, 0, 0);
      guess_priority = 0;
      
      for (x = 0; x < rel_n_records (search_path); ++x)
        {
          if (arch_library_has_version (rel_peek_str (search_path, x, 0), archive, version))
            {
              best_guess = rel_peek_str (search_path, x, 0);
              break;
            }
          else if (arch_library_has_branch (rel_peek_str (search_path, x, 0), archive, branch))
            {
              best_guess = rel_peek_str (search_path, x, 0);
              guess_priority = 3;
            }
          else if ((guess_priority < 2) && arch_library_has_category (rel_peek_str (search_path, x, 0), archive, category))
            {
              best_guess = rel_peek_str (search_path, x, 0);
              guess_priority = 2;
            }
          else if ((guess_priority < 1) && arch_library_has_archive (rel_peek_str (search_path, x, 0), archive))
            {
              best_guess = rel_peek_str (search_path, x, 0);
              guess_priority = 1;
            }
        }
      lim_free (0, category);
      lim_free (0, branch);
      lim_free (0, version);
    }
      
  if (!best_guess)
    return 0;
  else
    return str_save (0, best_guess);
}


void
arch_library_add (int chatter_fd,
                  int no_patch_noise,
                  struct arch_archive * arch,
                  const t_uchar * revision,
                  const t_uchar * opt_library,
                  const t_uchar * opt_same_dev_path,
                  int sparse,
                  int escape_classes)
{
  t_uchar * dest_library = 0;
  t_uchar * dest_directory = 0;
  t_uchar * fq_ancestor_rev = 0;
  t_uchar * previous_revision = 0;
  t_uchar * ancestor_archive = 0;
  t_uchar * ancestor_revision = 0;
  t_uchar * library_ancestor = 0;
  t_uchar * dest_dir_dir = 0;
  t_uchar * tmp_dir = 0;
  t_uchar * patch_set_dir = 0;
  t_uchar * log_path = 0;
  t_uchar * copy_log_path = 0;
  t_uchar * ancestor_rec_path = 0;
  t_uchar * prev_rec_path = 0;

  dest_library = arch_library_add_choice (arch->official_name, revision, opt_library, opt_same_dev_path);
  dest_directory = arch_library_revision_dir_in_lib (dest_library, arch->official_name, revision);

  if (!safe_access (dest_directory, F_OK))
    {
      lim_free (0, dest_library);
      lim_free (0, dest_directory);
      return;
    }

  if (sparse < 0)
    {
      sparse = arch_library_is_sparse (dest_library);
    }

  fq_ancestor_rev = arch_ancestor_revision (arch, revision);
  previous_revision = arch_previous_revision (arch, revision);

  if (fq_ancestor_rev)
    {
      ancestor_archive = arch_parse_package_name (arch_ret_archive, 0, fq_ancestor_rev);
      ancestor_revision = arch_parse_package_name (arch_ret_non_archive, 0, fq_ancestor_rev);
      library_ancestor = arch_library_find (rel_table_nil, ancestor_archive, ancestor_revision, 1);
    }

  dest_dir_dir = file_name_directory_file (0, dest_directory);
  ensure_directory_exists (dest_dir_dir);
  tmp_dir = tmp_file_name (dest_dir_dir, ",,new-revision");

  if (library_ancestor)
    {
      const int full_meta = arch_tree_has_meta_flag (library_ancestor);

      /* If the immediate ancestor exists in the same library,
       * then fill our tmp dir with a link-tree to that ancestor.
       * It will be patched for our target revision later.
       */

      arch_chatter (chatter_fd, "* found immediate ancestor revision in library (%s/%s)\n", ancestor_archive, ancestor_revision);
      build_link_tree (library_ancestor, tmp_dir, full_meta);
    }
  else if (!fq_ancestor_rev || str_cmp (ancestor_archive, arch->official_name))
    {
      /* If there is no immediate ancestor (we're trying to build 
       * an import revision) or if the ancestor is in some other archive,
       * fill the tmp-dir with the target revision just by building
       * it from scratch.
       * 
       * There's no need to further patch the tmp-dir after that.
       */

      safe_mkdir (tmp_dir, 0777);
      arch_build_revision (chatter_fd, tmp_dir, arch, arch->official_name, revision, 0);

      patch_set_dir = file_name_in_vicinity (0, tmp_dir, ",,patch-set");
      rmrf_file (patch_set_dir);

      if (fq_ancestor_rev)
        {
          arch_get_patch (arch, revision, patch_set_dir);
        }
      else
        {
          safe_mkdir (patch_set_dir, 0777);
        }

      goto no_need_to_patch;
    }
  else if (!sparse)
    {
      const int full_meta = arch_tree_has_meta_flag (library_ancestor);

      /* There is an ancestor, it's in the same archive, it's not 
       * in the library, and we're not building a --sparse library.
       * So, recursively add the ancestor to the library and then
       * build a link-tree into tmp-dir.   We'll patch tmp-dir to the
       * target revision later.
       */
      arch_chatter (chatter_fd, "* recursively adding %s/%s to library\n", ancestor_archive, ancestor_revision);
      arch_library_add (chatter_fd, no_patch_noise, arch, ancestor_revision, opt_library, opt_same_dev_path, 0, escape_classes);
      library_ancestor = arch_library_find (rel_table_nil, ancestor_archive, ancestor_revision, 1);
      build_link_tree (library_ancestor, tmp_dir, full_meta);
    }
  else
    {
      /* There is an ancestor, it's in the same archive, it's
       * not in the library, but we're building a --sparse library.
       *
       * If some _earlier_ ancestor is at hand, link to that and then
       * patch.   Otherwise, build the target revision from scratch.
       * 
       * Either way, at the end, tmp-dir will have the target revision
       * and there's no need to further patch it.
       */
      t_uchar * anc_arch = str_save (0, ancestor_archive);
      t_uchar * anc_rev = str_save (0, ancestor_revision);
      t_uchar * anc_lib = 0;
      int replay_status = 1;
      /* list of patches to apply to revision in library to achieve new revision
       */
      rel_table revisions = rel_table_nil;

      /* this is the first patch missing
       */
      rel_add_records (&revisions,  rel_singleton_record_taking (rel_make_field_str (revision)), rel_record_null);

      arch_chatter (chatter_fd, "* searching ancestor revision in library in archive %s\n", ancestor_archive);

      /* search backwards in the library for a revision to link to
       */
      while (anc_rev && !anc_lib)
        {
          t_uchar * fq_anc_rev = 0;
          t_uchar * prev_rev = 0;

          /* add revision to list
           */

          rel_add_records (&revisions,  rel_singleton_record_taking (rel_make_field_str (anc_rev)), rel_record_null);

          /* look in the library for the immediate ancestor
           */
          fq_anc_rev = arch_ancestor_revision (arch, anc_rev);

          if (fq_anc_rev)
            {
              lim_free (0, anc_arch);
              lim_free (0, anc_rev);

              anc_arch = arch_parse_package_name (arch_ret_archive, 0, fq_anc_rev);
              anc_rev = arch_parse_package_name (arch_ret_non_archive, 0, fq_anc_rev);
              anc_lib = arch_library_find (rel_table_nil, anc_arch, anc_rev, 1);
            }

          if (!fq_anc_rev || str_cmp (anc_arch, arch->official_name))
            {
              /* no ancestor in the same archive
               */
              arch_chatter (chatter_fd, "* no ancestor in library for revision %s/%s in the same archive\n", anc_arch, revision);
              lim_free (0, fq_anc_rev);
              lim_free (0, prev_rev);
              break;
            }

          lim_free (0, fq_anc_rev);
          lim_free (0, prev_rev);
        }

      /* let's patch the found revision in the library up to the needed revision
       */
      if (anc_lib)
        {
          const int full_meta = arch_tree_has_meta_flag (anc_lib);
          arch_chatter (chatter_fd, "* found ancestor revision in library (%s/%s)\n", anc_arch, anc_rev);
          arch_chatter (chatter_fd, "* patching for this revision (%s/%s)\n", ancestor_archive, revision);
          build_link_tree (anc_lib, tmp_dir, full_meta);
          rel_reverse_table (revisions);
          replay_status = arch_replay_list (chatter_fd, 1, tmp_dir, arch, revisions, 0, 0, escape_classes);
          invariant (!replay_status);
          lim_free (0, anc_lib);
        }
      else
        {
          /* otherwise (1st revision in library) build the revision from scratch
           */
          safe_mkdir (tmp_dir, 0777);
          arch_build_revision (chatter_fd, tmp_dir, arch, arch->official_name, revision, 0);
        }

      /* keep last patchset inside revision
       */
      patch_set_dir = file_name_in_vicinity (0, tmp_dir, ",,patch-set");
      rmrf_file (patch_set_dir);
      arch_get_patch (arch, revision, patch_set_dir);

      lim_free (0, anc_arch);
      lim_free (0, anc_rev);
      rel_free_table (revisions);
      goto no_need_to_patch;
    }


  /* This is reached only if we have one-last patch to make to 
   * tmp-dir to achieve the target revision.
   * 
   * If tmp-dir already has the target, then we `goto no_need_to_patch',
   * below.
   */

  patch_set_dir = file_name_in_vicinity (0, tmp_dir, ",,patch-set");
  rmrf_file (patch_set_dir);
  arch_get_patch (arch, revision, patch_set_dir);

  {
    struct arch_apply_changeset_report report = {0,};

    arch_chatter (chatter_fd, "* patching for this revision (%s/%s)\n", arch->official_name, revision);

    if ((chatter_fd >= 0) && !no_patch_noise)
      {
        report.callback = library_txn_callback;
        report.thunk = (void *)(long)chatter_fd;
      }

    arch_apply_changeset (&report, patch_set_dir, tmp_dir, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, 0, 0, 0, 0, escape_classes);
    invariant (!arch_conflicts_occured (&report));

    arch_free_apply_changeset_report_data (&report);
  }

 no_need_to_patch:

  /* Finish up adding ancillary files to the tmp-dir and then
   * install it in the library.
   */

  log_path = arch_log_file (tmp_dir, arch->official_name, revision);
  copy_log_path = file_name_in_vicinity (0, patch_set_dir, "=log.txt");
  copy_file (log_path, copy_log_path);

  ancestor_rec_path = file_name_in_vicinity (0, patch_set_dir, "=ancestor");
  prev_rec_path = file_name_in_vicinity  (0, patch_set_dir, "=previous");

  {
    int out_fd;

    out_fd = safe_open (ancestor_rec_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
    safe_printfmt (out_fd, "%s\n", fq_ancestor_rev);
    safe_close (out_fd);

    out_fd = safe_open (prev_rec_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
    safe_printfmt (out_fd, "%s\n", previous_revision);
    safe_close (out_fd);
  }

  {
    t_uchar * version = 0;
    rel_table index = rel_table_nil;
    t_uchar * index_by_name_path = 0;
    t_uchar * index_path = 0;
    int index_by_name_fd = -1;
    int index_fd = -1;

    version = arch_parse_package_name (arch_ret_package_version, 0, revision);
    arch_set_tree_version (tmp_dir, arch->official_name, version);

    index = arch_source_inventory (tmp_dir, 1, 0, 0);
    index_by_name_path = file_name_in_vicinity (0, tmp_dir, ",,index-by-name");
    index_path = file_name_in_vicinity (0, tmp_dir, ",,index");

    rmrf_file (index_by_name_path);
    rmrf_file (index_path);

    index_by_name_fd = safe_open (index_by_name_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
    rel_print_pika_escape_iso8859_1_table (index_by_name_fd, arch_escape_classes, index);
    safe_close (index_by_name_fd);

    rel_sort_table_by_field (0, index, 1);
    index_fd = safe_open (index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
    rel_print_pika_escape_iso8859_1_table (index_fd, arch_escape_classes, index);

    safe_close (index_fd);

    lim_free (0, version);
    rel_free_table (index);
    lim_free (0, index_by_name_path);
    lim_free (0, index_path);
  }

  arch_snap_inode_sig (tmp_dir, arch->official_name, revision);
  safe_rename (tmp_dir, dest_directory);

  lim_free (0, dest_library);
  lim_free (0, dest_directory);
  lim_free (0, fq_ancestor_rev);
  lim_free (0, previous_revision);
  lim_free (0, ancestor_archive);
  lim_free (0, ancestor_revision);
  lim_free (0, library_ancestor);
  lim_free (0, dest_dir_dir);
  lim_free (0, tmp_dir);
  lim_free (0, patch_set_dir);
  lim_free (0, ancestor_rec_path);
  lim_free (0, prev_rec_path);
}


static void
library_txn_callback (void * vfp,
                      const char * fmt,
                      va_list ap)
{
  int fd;

  fd = (int)(t_ulong)vfp;

  if (fd >= 0)
    {
      safe_printfmt_va_list (fd, fmt, ap);
      safe_flush (1);
    }
}




/* tag: Tom Lord Fri May 30 13:50:27 2003 (library-txn.c)
 */
