/* proj-tree-lint.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/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/hash/hashtree.h"
#include "hackerlab/hash/hash-utils.h"
#include "hackerlab/vu/safe.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/proj-tree-lint.h"


/* __STDC__ prototypes for static functions */
static void find_unused_ids (rel_table * ids_sans_files, rel_table explicit_ids, struct hashtree * id_map, struct hashtree_rules * tree_lint_hashtree_rules);
static void tree_lint_callback (const t_uchar * path,
                                struct stat * stat_buf,
                                enum arch_inventory_category category,
                                const t_uchar * id,
                                int has_source_name,
                                void * vthunk,
                                int escape_classes);
static int str_key_eq (void * va, void * vb, struct hashtree_rules * r);
static void tree_lint_hashtree_freefn (struct hashtree_item * it, struct hashtree_rules * rules);
static t_ulong hash_key (const t_uchar * key);



struct lint_traversal_thunk
{
  struct arch_tree_lint_result * answer;
  struct hashtree * duplicated_ids_index;
  struct arch_inventory_options * options;
  rel_table explicit_ids;
};

struct lint_id_mapping
{
  t_uchar * first_occurence;
  int index;
};

static struct hashtree_rules tree_lint_hashtree_rules = { str_key_eq, 0, 0, 0, 0, 0 };



int
arch_print_tree_lint_report (int fd, struct arch_tree_lint_result * lint, int escape_classes)
{
  int status;

  status = 0;

  if (rel_n_records (lint->warning_files))
    {
      safe_printfmt (fd, "These files would be source but lack inventory ids (`tla add' or a tagline perhaps?):\n\n");
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->warning_files);
      safe_printfmt (fd, "\n\n");
      if (!status)
        status = 1;
    }

  if (rel_n_records (lint->unrecognized_files))
    {
      safe_printfmt (fd, "These files violate naming conventions:\n\n");
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->unrecognized_files);
      safe_printfmt (fd, "\n\n");
      status = -1;
    }

  if (rel_n_records (lint->symlinks_sans_targets))
    {
      safe_printfmt (fd, "These symlinks point to nonexistent files:\n\n");
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->symlinks_sans_targets);
      safe_printfmt (fd, "\n\n");
      if (!status)
        status = 1;
    }

  if (rel_n_records (lint->untagged_files))
    {
      safe_printfmt (fd, "These apparent source files lack inventory ids:\n\n");
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->untagged_files);
      safe_printfmt (fd, "\n\n");

      if ((lint->id_tagging_method == arch_implicit_id_tagging) || (lint->id_tagging_method == arch_tagline_id_tagging))
        {
          if (!status)
            status = 1;
        }
      else
        status = -1;
    }

  if (rel_n_records (lint->ids_sans_files))
    {
      safe_printfmt (fd, "These explicit ids have no corresponding file:\n\n");
      rel_print_table (fd, lint->ids_sans_files);
      safe_printfmt (fd, "\n\n");
      status = -1;
    }

  if (lint->duplicate_id_groups)
    {
      int lim;
      int x;

      safe_printfmt (fd, "Duplicated ids among each group of files listed here:\n\n");

      lim = ar_size ((void *)lint->duplicate_id_groups, 0, sizeof (rel_table));

      for (x = 0; x < lim; ++x)
        {
          rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->duplicate_id_groups[x]);
          safe_printfmt (fd, "\n");
        }
      safe_printfmt (fd, "\n\n");
      status = -1;
    }
  return status;
}

int
arch_print_filtered_tree_lint_report (int fd,
                                      struct arch_tree_lint_result * lint,
                                      t_uint categories,
                                      int escape_classes)
{
  int status;

  status = 0;

  if (rel_n_records (lint->unrecognized_files) && (categories & unrecognized_files))
    {
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->unrecognized_files);
      status = -1;
    }
  if (rel_n_records (lint->symlinks_sans_targets) && (categories & symlinks_sans_targets))
    {
      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->symlinks_sans_targets);
      if (!status)
        status = 1;
    }
  if ((rel_n_records (lint->warning_files) || rel_n_records (lint->untagged_files)) && (categories & untagged_files))
    {
      rel_table combined;

      combined = rel_copy_table (lint->warning_files);
      rel_append_x (&combined, lint->untagged_files);
      rel_sort_table_by_field (0, combined, 0);

      rel_print_pika_escape_iso8859_1_table (fd, escape_classes, combined);

      if ((lint->id_tagging_method == arch_implicit_id_tagging) || (lint->id_tagging_method == arch_tagline_id_tagging))
        {
          if (!status)
            status = 1;
        }
      else
        status = -1;

      rel_free_table (combined);
    }

  if (rel_n_records (lint->ids_sans_files) && (categories & ids_sans_files))
    {
      rel_print_table (fd, lint->ids_sans_files);
      status = -1;
    }

  if (lint->duplicate_id_groups && (categories & duplicate_id_groups))
    {
      int lim;
      int x;

      lim = ar_size ((void *)lint->duplicate_id_groups, 0, sizeof (rel_table));

      for (x = 0; x < lim; ++x)
        {
          rel_print_pika_escape_iso8859_1_table (fd, escape_classes, lint->duplicate_id_groups[x]);
          safe_printfmt (fd, "\n");
        }
      status = -1;
    }

  return status;
}

struct arch_tree_lint_result *
arch_tree_lint (t_uchar * dirspec)
{
  int here_fd;
  t_uchar * dir = 0;
  t_uchar * tree_root = 0;
  struct arch_tree_lint_result * answer;
  struct arch_inventory_options options;
  struct lint_traversal_thunk thunk;

  here_fd = safe_open (".", O_RDONLY, 0);

  safe_chdir (dirspec);
  dir = safe_current_working_directory ();

  tree_root = arch_tree_root (0, dir, 0);
  invariant (!tree_root || !str_cmp (tree_root, dir));

  if (tree_root)
    safe_chdir (tree_root);

  answer = lim_malloc (0, sizeof (*answer));
  mem_set0 ((t_uchar *)answer, sizeof (*answer));

  mem_set0 ((t_uchar *)&options, sizeof (options));
  options.categories = (arch_inventory_source
                        | arch_inventory_precious
                        | arch_inventory_backup
                        | arch_inventory_junk
                        | arch_inventory_unrecognized); /* not arch_inventory_{tree,excludes} */
  options.want_ids = 1;
  options.treat_unrecognized_source_as_source = 1;
  /* options.method = (!tree_root ? arch_names_id_tagging : arch_tree_id_tagging_method (tree_root, 0)); */
  options.nested = 0;
  options.include_excluded = 1;
  arch_get_inventory_naming_conventions (&options, tree_root);

  answer->id_tagging_method = options.method;

  mem_set0 ((t_uchar *)&thunk, sizeof (thunk));
  thunk.answer = answer;
  thunk.duplicated_ids_index = hashtree_alloc (&tree_lint_hashtree_rules);
  thunk.options = &options;
  thunk.explicit_ids = rel_table_nil;

  arch_inventory_traversal (&options, ".", tree_lint_callback, &thunk, 0);
  find_unused_ids (&(answer->ids_sans_files), thunk.explicit_ids, thunk.duplicated_ids_index, &tree_lint_hashtree_rules);

  safe_fchdir (here_fd);
  safe_close (here_fd);

  rel_free_table (thunk.explicit_ids);
  hashtree_free (thunk.duplicated_ids_index, tree_lint_hashtree_freefn, &tree_lint_hashtree_rules);
  arch_free_inventory_naming_conventions (&options);
  lim_free (0, dir);
  lim_free (0, tree_root);

  return answer;
}

static void
find_unused_ids (rel_table * ids_sans_files, rel_table explicit_ids, struct hashtree * id_map, struct hashtree_rules * tree_lint_hashtree_rules)
{
  int n_records=rel_n_records (explicit_ids);
  int i;
  for (i = 0; i < n_records; ++i)
    {
      if (hashtree_find (id_map, hash_key (rel_peek_str (explicit_ids, i, 0)), (void *)rel_peek_str (explicit_ids, i, 0), tree_lint_hashtree_rules) == 0)
	rel_add_records (ids_sans_files, rel_singleton_record_taking (rel_get_field (explicit_ids, i, 1)), rel_record_null);
    }
}


void
arch_free_lint_result (struct arch_tree_lint_result * result)
{
  int x;
  rel_free_table (result->unrecognized_files);
  rel_free_table (result->symlinks_sans_targets);
  rel_free_table (result->untagged_files);
  rel_free_table (result->ids_sans_files);
  for (x = 0; x < ar_size ((void *)result->duplicate_id_groups, 0, sizeof (rel_table)); ++x)
    rel_free_table (result->duplicate_id_groups[x]);
  ar_free ((void **)&result->duplicate_id_groups, 0);
  lim_free (0, result);
}


static void
tree_lint_callback (const t_uchar * path,
                    struct stat * stat_buf,
                    enum arch_inventory_category category,
                    const t_uchar * id,
                    int has_source_name,
                    void * vthunk,
                    int escape_classes)
{
  struct lint_traversal_thunk * thunk;

  thunk = (struct lint_traversal_thunk *)vthunk;

  /* Remove leading ./  */
  if (path[0] == '.' && path[1] == '/')
    path += 2;

  /* violations of naming conventions
   */
  if (category == arch_inventory_unrecognized)
    {
      rel_add_records (&thunk->answer->unrecognized_files, rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
    }

  /* files classified as non-source because they lack an inventory id
   */
  if ((category != arch_inventory_source) && has_source_name)
    {
      rel_add_records (&thunk->answer->warning_files, rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
    }


  /* symlink but no target
   */
  if (S_ISLNK (stat_buf->st_mode))
    {
      if (safe_access (path, F_OK))
        {
          rel_add_records (&thunk->answer->symlinks_sans_targets, rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
        }
    }


  /* untagged file that passes naming conventions
   */
  if (((thunk->options->method == arch_implicit_id_tagging) || (thunk->options->method == arch_tagline_id_tagging))
      && (category == arch_inventory_source)
      && (!id || (id[0] == '?')))
    {
      rel_add_records (&thunk->answer->untagged_files, rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
    }
  else if ((thunk->options->method == arch_explicit_id_tagging) && (category == arch_inventory_source) && !id)
    {
      t_uchar * dir = 0;

      dir = file_name_directory_file (0, path);
      if (!arch_is_dont_care_explicit_dflt_dir (dir))
        rel_add_records (&thunk->answer->untagged_files, rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
      lim_free (0, dir);
    }

  /* explicit id but no file
   */
  if (category == arch_inventory_source)
    {
      size_t len;

      len = str_length (path);
      if ((len > 3) && !str_cmp (path + len - 3, ".id"))
        {
          t_uchar * path_dir;
          t_uchar * path_dir_basename;
          t_uchar * path_dir_dir;
          t_uchar * basename;
          t_uchar * file;

          path_dir = file_name_directory_file (0, path);
	  if (path_dir != 0)
	    {
	      path_dir_basename = file_name_tail (0, path_dir);
	      path_dir_dir = file_name_directory_file (0, path_dir);
	      basename = file_name_tail (0, path);
	      basename[str_length(basename) - 3] = 0;
	      file = file_name_in_vicinity (0, path_dir_dir, basename);
	      if (!str_cmp (path_dir_basename, ".arch-ids")) 
		{
		  int errn = 0;
		  t_uchar * id = arch_id_from_explicit_file (&errn, path);
		  if (id == 0)
		    {
		      safe_printfmt (2, "i/o error during inventory traversal (%s) for %s\n", errno_to_string (errn), file);
		      exit (2);
		    }
		  rel_add_records (&thunk->explicit_ids, rel_make_record_2_taking (rel_make_field_str (id), rel_make_field_str (path)), rel_record_null);
		  lim_free (0, id);
		}
	      lim_free (0, path_dir);
	      lim_free (0, path_dir_basename);
	      lim_free (0, path_dir_dir);
	      lim_free (0, basename);
	      lim_free (0, file);
	   }
        }
    }

  /* duplicate ids
   */
  if (id)
    {
      struct hashtree_item * item;

      item = hashtree_store (thunk->duplicated_ids_index, hash_key (id), (void *)id, &tree_lint_hashtree_rules);

      if (item->key == id)
        {
          struct lint_id_mapping * binding;

          item->key = str_save (0, id);
          binding = lim_malloc (0, sizeof (*binding));
          binding->first_occurence = str_save (0, path);
          binding->index = -1;
          item->binding = (void *)binding;
        }
      else
        {
          struct lint_id_mapping * binding;

          binding = (struct lint_id_mapping *)item->binding;
          if (binding->index < 0)
            {
              *(rel_table *)ar_push ((void **)&thunk->answer->duplicate_id_groups, 0, sizeof (rel_table)) = rel_table_nil;
              binding->index = ar_size ((void *)thunk->answer->duplicate_id_groups, 0, sizeof (rel_table)) - 1;
              rel_add_records (&thunk->answer->duplicate_id_groups[binding->index], rel_make_record_2_taking (rel_make_field_str (binding->first_occurence), rel_make_field_str (id)), rel_record_null);
            }

          rel_add_records (&thunk->answer->duplicate_id_groups[binding->index], rel_singleton_record_taking (rel_make_field_str (path)), rel_record_null);
        }


    }
}

static int
str_key_eq (void * va, void * vb, struct hashtree_rules * r)
{
  t_uchar * a;
  t_uchar * b;

  a = (t_uchar *)va;
  b = (t_uchar *)vb;

  return !str_cmp (a, b);
}

static void
tree_lint_hashtree_freefn (struct hashtree_item * it, struct hashtree_rules * rules)
{
  struct lint_id_mapping * binding;
  lim_free (0, it->key);
  binding = (struct lint_id_mapping *)it->binding;
  lim_free (0, binding->first_occurence);
}

static t_ulong
hash_key (const t_uchar * key)
{
  return hash_mem (key, str_length (key));
}




/* tag: Tom Lord Wed May 14 14:30:47 2003 (proj-tree-lint.c)
 */
