/* 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 "file-utils/libarch/invent.h"
#include "file-utils/libarch/project-tree.h"
#include "file-utils/libarch/proj-tree-lint.h"


/* __STDC__ prototypes for static functions */
static void tree_lint_callback (t_uchar * path,
                                struct stat * stat_buf,
                                enum arch_inventory_category category,
                                t_uchar * tag,
                                void * vthunk);
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 (t_uchar * key);



struct lint_traversal_thunk
{
  struct arch_tree_lint_result * answer;
  struct hashtree * duplicated_tags_index;
  struct arch_inventory_options * options;
};

struct lint_tag_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 status;

  status = 0;

  if (lint->unrecognized_files)
    {
      safe_printfmt (fd, "These files violate naming conventions:\n\n");
      rel_print_table (fd, lint->unrecognized_files);
      safe_printfmt (fd, "\n\n");
      status = -1;
    }
  if (lint->symlinks_sans_targets)
    {
      safe_printfmt (fd, "These symlinks point to nonexistent files:\n\n");
      rel_print_table (fd, lint->symlinks_sans_targets);
      safe_printfmt (fd, "\n\n");
      if (!status)
        status = 1;
    }
  if (lint->untagged_files)
    {
      safe_printfmt (fd, "These apparent source files lack inventory tags:\n\n");
      rel_print_table (fd, lint->untagged_files);
      safe_printfmt (fd, "\n\n");
      status = -1;
    }
  if (lint->tags_sans_files)
    {
      safe_printfmt (fd, "These explicit tags have no corresponding file:\n\n");
      rel_print_table (fd, lint->tags_sans_files);
      safe_printfmt (fd, "\n\n");
      status = -1;
    }
  if (lint->duplicate_tag_groups)
    {
      int lim;
      int x;

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

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

      for (x = 0; x < lim; ++x)
        {
          rel_print_table (fd, lint->duplicate_tag_groups[x]);
          safe_printfmt (fd, "\n");
        }
      safe_printfmt (fd, "\n\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_tags = 1;
  options.include_untagged = 1;
  options.method = (!tree_root ? arch_names_tagging : arch_tree_tagging_method (tree_root, 0));
  options.nested = 0;
  options.include_excluded = 1;
  arch_get_inventory_naming_conventions (&options, tree_root);

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

  arch_inventory_traversal (&options, ".", tree_lint_callback, &thunk);

  safe_fchdir (here_fd);
  safe_close (here_fd);

  hashtree_free (thunk.duplicated_tags_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;
}

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->tags_sans_files);
  for (x = 0; x < ar_size ((void *)result->duplicate_tag_groups, 0, sizeof (rel_table)); ++x)
    rel_free_table (result->duplicate_tag_groups[x]);
  ar_free ((void **)&result->duplicate_tag_groups, 0);
  lim_free (0, result);
}


static void
tree_lint_callback (t_uchar * path,
                    struct stat * stat_buf,
                    enum arch_inventory_category category,
                    t_uchar * tag,
                    void * vthunk)
{
  struct lint_traversal_thunk * thunk;

  thunk = (struct lint_traversal_thunk *)vthunk;

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

  /* 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_make_record (path, 0), 0);
        }
    }


  /* untagged file that passes naming conventions
   */
  if ((thunk->options->method == arch_implicit_tagging) && (category == arch_inventory_source) && (!tag || (tag[0] == '?')))
    {
      rel_add_records (&thunk->answer->untagged_files, rel_make_record (path, 0), 0);
    }
  else if ((thunk->options->method == arch_explicit_tagging) && (category == arch_inventory_source) && !tag)
    {
      t_uchar * basename;

      basename = file_name_tail (0, path);
      rel_add_records (&thunk->answer->untagged_files, rel_make_record (path, 0), 0);
      lim_free (0, basename);
    }

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

      len = str_length (path);
      if ((len > 3) && !str_cmp (path + len - 3, ".id"))
        {
          int errn;
          struct stat statb;
          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);
          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") && vu_lstat (&errn, file, &statb))
            {
              if (errn != ENOENT)
                {
                  safe_printfmt (2, "i/o error during inventory traversal (%s) for %s\n", errno_to_string (errn), file);
                  exit (2);
                }
              rel_add_records (&thunk->answer->tags_sans_files, rel_make_record (path, 0), 0);
            }

          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 tags
   */
  if (tag)
    {
      struct hashtree_item * item; 
      
      item = hashtree_store (thunk->duplicated_tags_index, hash_key (tag), tag, &tree_lint_hashtree_rules);

      if (item->key == tag)
        {
          struct lint_tag_mapping * binding;

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

          binding = (struct lint_tag_mapping *)item->binding;
          if (binding->index < 0)
            {
              *(rel_table *)ar_push ((void **)&thunk->answer->duplicate_tag_groups, 0, sizeof (rel_table)) = 0;
              binding->index = ar_size ((void *)thunk->answer->duplicate_tag_groups, 0, sizeof (rel_table)) - 1;
              rel_add_records (&thunk->answer->duplicate_tag_groups[binding->index], rel_make_record (binding->first_occurence, tag, 0), 0);
            }

          rel_add_records (&thunk->answer->duplicate_tag_groups[binding->index], rel_make_record (path, 0), 0);
        }

      
    }
}

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_tag_mapping * binding;
  lim_free (0, it->key);
  binding = (struct lint_tag_mapping *)it->binding;
  lim_free (0, binding->first_occurence);
}

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




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