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



#include "config-options.h"

#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/char/str.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/my.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/pristines.h"
#include "tla/libarch/build-revision.h"

#include "tla/libarch/inode-sig.h"
#include "tla/libarch/libraries.h"
#include "tla/libarch/local-cache.h"
#include "tla/libarch/library-txn.h"
#include "tla/libarch/cmd.h"
#include "tla/libarch/cmdutils.h"
#include "tla/libarch/cmd-export.h"



/* gettext support not yet incorporated into tla, reserve the gettext notation for later use */
#define _(x) (x)
#define N_(x) (x)

static t_uchar * usage = N_("[options] [revision] dir ");

#define OPTS(OP) \
  OP (opt_dir, "d", "dir DIR", 1, \
      N_("Change to DIR first.")) \
  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_silent, "s", "silent", 0, \
      N_("no output"))


t_uchar arch_cmd_export_help[] = N_("export all or some of a tree revision\n"
				    "Extract REVISION from an archive, creating a new source tree with no control information.\n"
                    "Note if export the local tree revision, local modifications will not be included; the exported\n"
                    "file will only be copies in the repository\n"
				    "\n"
  );
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
                                   "\n"
                                   "Copyright 2003 Tom Lord\n"
                                   "\n"
                                   "This is free software; see the source for copying conditions.\n"
                                   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
                                   "PARTICULAR PURPOSE.\n"
                                   "\n"
                                   "Report bugs to " cfg__tla_bug_mail ".\n"
                                   "\n"
                                   cfg__std__release_id_string
                                   "\n");

enum options
{
  OPTS (OPT_ENUM)
};

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



static int
filename_matches (regex_t * pattern, char * filename)
{
  int answer;

  answer = regexec (pattern, filename, 0, 0, 0);

  if (answer == REG_NOMATCH)
    return 0;

  if (answer == REG_NOERROR)
    return 1;

  panic ("unexpected regexec error in arch_inventory_traversal");
  return -1;
}

/* 
 * return true if the path is in .arch-ids or {arch}, ie if it matches
 *  s/(\{arch\}|\.arch-ids)/.
 */
int
arch_is_in_control_dir(t_uchar * path)
{
  static int compiled = 0;
  static regex_t control_pattern = {0,};

  if (!compiled)
    {
      int re_error;

      re_error = regcomp (&control_pattern,
                          "\\.arch-ids|\\{arch\\}",
                          REG_EXTENDED);
      invariant (!re_error);
      compiled = 1;
    }

  return filename_matches (&control_pattern, path);
}

/*
 * return a new rel_table which does not contain any control files
 */
rel_table
arch_filter_control_files (rel_table index)
{
  rel_table new_index = rel_table_nil;
  int l = 0;

  for (l = 0; l < rel_n_records (index); ++l)
    {
      t_uchar *s;
      s = str_save(0, rel_peek_str(index, l, 0));

      if (!(
	     (arch_is_control_file (s, NULL) && str_cmp_suffix_n(s, ".arch-inventory", 15)) ||
	      arch_is_in_control_dir (s)))
 	    { 
	      rel_add_records (&new_index, rel_copy_record (rel_peek_record(index, l)), 0);
	    }
      lim_free(0, s);
    }
  safe_flush (1);
  return new_index;
}

/*
 * return a new rel_table which begins with prefix
 */
rel_table
arch_filter_to_subdir (rel_table index, t_uchar * prefix)
{
  rel_table new_index = rel_table_nil;
  int l = 0;
  for (l = 0; l < rel_n_records (index); ++l)
    {
      t_uchar *s;
      s = str_save(0, rel_peek_str(index, l, 0));
      if (!str_cmp_prefix(prefix, s))
	      rel_add_records (&new_index, rel_copy_record (rel_peek_record(index, l)), 0);
      lim_free(0, s);
    }
  return new_index;
}



int
arch_cmd_export (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * cache_dir_spec = 0;
  t_uchar * selected_library = 0;
  t_uchar *dir = 0;
  int silent = 0;
  int revision_from_working_tree = 0;
  dir = str_save (0, ".");
  
  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, version_string, arch_cmd_export_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      switch (o)
        {
        case opt_dir:
          {
            lim_free (0, dir);
            dir = str_save (0, option->arg_string);
            break;
          }
        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_silent:
          {
            silent = 1;
            break;
          }
        }
    }
  
  if ((argc < 2) || (argc > 3))
    goto usage_error;
  
  {
    int here_fd;
    int tmp_dir_is_local_cache = 0;
    t_uchar * revision_spec = 0;
    t_uchar * revision = 0;
    t_uchar * output_dir = 0;
    t_uchar * tmp_dir = 0;
    t_uchar * last_arg = 0;
    t_uchar * subdir = 0;
    struct arch_archive * arch = 0;
    
    t_uchar * tree_root = 0;
    t_uchar *tree_arch = 0;
    
    if (argc == 2)
      {
        output_dir = str_save(0, argv[1]);
        tree_root = arch_tree_root(0, dir, 0);
	    if (tree_root)
	      {
	        revision_spec = arch_get_tree_fqrevision(tree_root);
	        revision_from_working_tree = 1;
          }
        else
          {
	        safe_printfmt (2, _("Cannot determine revision of directory %s\n"), dir);
	        exit (2);
          }   
      } 
    else if (argc == 3)
      {
        output_dir = str_save(0, argv[2]);
        revision_spec = arch_get_fqrevision(argv[1]);
      }
    
    
    here_fd = safe_open (".", O_RDONLY, 0);
    
    last_arg = str_save (0, argv[1]);
    tree_arch = arch_parse_package_name (arch_ret_archive, 0, revision_spec);
    if (tree_arch)
      arch = arch_archive_connect(tree_arch, 0);
    
    if (!tree_arch || !arch)
      {
	    safe_printfmt (2, _("Cannot connect to archive for revision %s\n"), revision_spec);
	    exit (2);
      }
    
    invariant (!!arch);
    
    revision = arch_parse_package_name (arch_ret_non_archive, NULL, revision_spec);
    
    if (!output_dir)
      {
	    /* name of revision part */
	    t_uchar * cwd = safe_current_working_directory ();
	    output_dir = file_name_in_vicinity (0, cwd, revision);
	    lim_free (0, cwd);
      }
    
    invariant (!!output_dir);
    
    {
      t_uchar * canonical_dir = 0;
      t_uchar * output_dirname = 0;
      t_uchar * cwd = safe_current_working_directory ();
      t_uchar * absolute_output_dir = 0;
      int output_dirname_exists;
      if (file_name_is_absolute(output_dir))
	    absolute_output_dir = str_save(0, output_dir);
      else
	    absolute_output_dir = str_alloc_cat_many(0, cwd, "/", output_dir, str_end);
      canonical_dir = file_name_from_directory(0, absolute_output_dir);
      output_dirname = file_name_directory (0, canonical_dir);
      
      output_dirname_exists = safe_access (output_dirname, F_OK) == 0;
      
      if (!output_dirname_exists)
	    {
	      safe_printfmt (2, _("Parent dir of output directory,  %s,  does not exist\n"), output_dirname);
	      exit (2);
	    }
      lim_free(0, canonical_dir);
      lim_free(0, output_dirname);
      lim_free(0, cwd);
      lim_free(0, absolute_output_dir);
      
    }
    
    if (!safe_access (output_dir, F_OK))
      {
        safe_printfmt (2, "%s: output directory already exists (%s)\n",
                       argv[0], output_dir);
        exit (1);
      }
    
    {
      t_uchar * revision_dir = 0;
      t_uchar * tmp_tree_root = 0;
      rel_table index = rel_table_nil;
      rel_table index_stripped = rel_table_nil;
      rel_table index_subdird = rel_table_nil;
      
      /* FIXME: inefficient, since arch_build_revision is already
       * doing an inventory and throwing it away.
       */
      if (revision_from_working_tree)
	    {
	      arch_chatter (1, "* trying to build pristine export from local cache\n");
	      tmp_dir = arch_find_or_make_local_copy ((silent ? -1 : 1), tree_root, dir, arch, tree_arch, revision);
	      if (tmp_dir)
                tmp_dir_is_local_cache = 1;
	    }
      if (!tmp_dir)
	    {
	      tmp_dir = tmp_file_name_in_tmp ("export");
	      rmrf_file (tmp_dir);
	      safe_mkdir (tmp_dir, 0777);
	      arch_build_revision ((silent ? -1 : 1), tmp_dir, arch, tree_arch, revision, NULL);
	    }
      tmp_tree_root = arch_tree_root(0, tmp_dir, 0);
      index = arch_source_inventory (tmp_tree_root, 1, 0, 0);
      safe_flush (1);
      index_stripped = arch_filter_control_files (index);
      if (subdir)
	    {
	      safe_printfmt(2," limiting to %s", subdir);
	      index_subdird = arch_filter_to_subdir (index_stripped, str_alloc_cat(0, "./", subdir));
	    }
          else
	    {
	      index_subdird = index_stripped;
	    }
      rel_sort_table_by_field (0, index_subdird, 0);
      arch_chatter (1, "* building clean tree in %s\n", output_dir);
      copy_file_list (output_dir, tmp_dir, index_subdird, 1);

      lim_free (0, revision_dir);
      lim_free(0, tmp_tree_root);
    }

    arch_archive_close (arch);
    
    safe_fchdir (here_fd);
    safe_close (here_fd);
    if (tmp_dir_is_local_cache == 0)
      rmrf_file (tmp_dir);
    
    lim_free (0, revision_spec);
    lim_free (0, revision);
    lim_free (0, output_dir);
    lim_free (0, tmp_dir);
    lim_free (0, tree_root);
  }
  
  lim_free (0, selected_library);
  lim_free (0, cache_dir_spec);
  
  return 0;
}

