/* patch-logs.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/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "file-utils/libfsutils/dir-listing.h"
#include "file-utils/libfsutils/ensure-dir.h"
#include "file-utils/libfsutils/rmrf.h"
#include "file-utils/libfsutils/copy-file.h"
#include "file-utils/libarch/namespace.h"
#include "file-utils/libarch/project-tree.h"
#include "file-utils/libarch/patch-logs.h"



t_uchar *
arch_make_log_file (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  t_uchar * log_name = 0;
  t_uchar * log_path = 0;

  invariant (arch_valid_archive_name (archive));
  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  /* The VI-user flame generator:
   */
  log_name = str_alloc_cat_many (0, "++log.", version, "--", archive, str_end);
  log_path = file_name_in_vicinity (0, tree_root, log_name);

  lim_free (0, log_name);
  return log_path;
}

t_uchar *
arch_make_log (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  int errn;
  t_uchar * log_path = 0;
  int out_fd;

  log_path = arch_make_log_file (tree_root, archive, version);

  out_fd = vu_open (&errn, log_path, O_WRONLY | O_CREAT | O_EXCL, 0666);

  if (out_fd >= 0)
    {
      safe_printfmt (out_fd, "Summary: \nKeywords: \n\n");
      safe_close (out_fd);
    }
  else if (errn != EEXIST)
    {
      safe_printfmt (2, "arch_make_log: I/O error (%s) creating %s\n", errno_to_string (errn), log_path);
      exit (2);
    }

  return log_path;
}


t_uchar *
arch_log_dir (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  t_uchar * category;
  t_uchar * package;
  t_uchar * package_version;

  t_uchar * t;
  t_uchar * log_dir;

  invariant (arch_valid_archive_name (archive));
  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  category = arch_parse_package_name (arch_ret_category, 0, version);
  package = arch_parse_package_name (arch_ret_package, 0, version);
  package_version = arch_parse_package_name (arch_ret_package_version, 0, version);

  log_dir = arch_tree_ctl_dir (tree_root);
  t = file_name_in_vicinity (0, log_dir, category);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, package);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, package_version);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, archive);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, "patch-log");
  lim_free (0, log_dir);
  log_dir = t;

  lim_free (0, category);
  lim_free (0, package);
  lim_free (0, package_version);

  return log_dir;
}


t_uchar *
arch_log_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision)
{
  t_uchar * version;
  t_uchar * dir;
  t_uchar * lvl;
  t_uchar * file;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);

  dir = arch_log_dir (tree_root, archive, version);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);
  file = file_name_in_vicinity (0, dir, lvl);

  lim_free (0, version);
  lim_free (0, dir);
  lim_free (0, lvl);
  return file;
}

void
arch_add_log (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  t_uchar * log_dir;

  log_dir = arch_log_dir (tree_root, archive, version);
  ensure_directory_exists (log_dir);

  lim_free (0, log_dir);
}


void
arch_remove_log (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  t_uchar * log_dir;

  log_dir = arch_log_dir (tree_root, archive, version);
  rmrf_file (log_dir);

  lim_free (0, log_dir);
}

int
arch_has_patch_log (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  t_uchar * dir = 0;
  int answer = 0;

  dir = arch_log_dir (tree_root, archive, version);

  if (!safe_access (dir, F_OK))
    answer = 1;

  lim_free (0, dir);
  return answer;
}



rel_table
arch_logs (t_uchar * tree_root,
           t_uchar * only_archive,
           t_uchar * only_category,
           t_uchar * only_branch,
           t_uchar * only_version)
{
  t_uchar * logs_root;
  int lim;
  int x;
  rel_table dir_listing;
  rel_table categories;
  int c_lim;
  int c;
  rel_table branches;           /* [0] == cat--branch,  [1] ==  path of branch's log dir */
  int b_lim;
  int b;
  rel_table versions;           /* [0] == cat--branch--vsn,  [1] ==  path of vsn's log dir */
  int v_lim;
  int v;
  rel_table logs;               /* [0] == fqversion, [1] == patch-log dir for fqversion  */

  logs_root = arch_tree_ctl_dir (tree_root);


  if (only_version)
    only_version = str_save (0, only_version);

  if (only_version && !only_branch)
    {
      only_branch = arch_parse_package_name (arch_ret_package, 0, only_version);
    }
  else if (only_branch)
    only_branch = str_save (0, only_branch);

  if (only_branch && !only_category)
    {
      only_category = arch_parse_package_name (arch_ret_category, 0, only_branch);
    }
  else if (only_category)
    only_category = str_save (0, only_category);



  /****************************************************************
   * Get a list of all categories in the log
   */

  dir_listing = directory_files (logs_root);

  categories = 0;
  lim = rel_n_records (dir_listing);
  for (x = 0; x < lim; ++x)
    {
      if (only_category && str_cmp (dir_listing[x][0], only_category))
        continue;

      if (arch_valid_package_name (dir_listing[x][0], arch_no_archive, arch_req_category, 0))
        rel_add_records (&categories, rel_copy_record (dir_listing[x]), 0);
    }
  rel_free_table (dir_listing);

  /****************************************************************
   * For each category, get a list of each branch for it in the log
   */

  branches = 0;
  c_lim = rel_n_records (categories);
  for (c = 0; c < c_lim; ++c)
    {
      t_uchar * category;
      t_uchar * cat_dir;

      category = categories[c][0];
      cat_dir = file_name_in_vicinity (0, logs_root, category);

      dir_listing = directory_files (cat_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          t_uchar * maybe_branch;
          t_uchar * branch_cat;

          maybe_branch = dir_listing[x][0];

          if (only_branch && str_cmp (only_branch, maybe_branch))
            continue;

          if (!arch_valid_package_name (maybe_branch, arch_no_archive, arch_req_package, 0))
            continue;

          branch_cat = arch_parse_package_name (arch_ret_category, 0, maybe_branch);
          if (!str_cmp (category, branch_cat))
            {
              t_uchar * branch_path;

              branch_path = file_name_in_vicinity (0, cat_dir, maybe_branch);
              rel_add_records (&branches, rel_make_record (maybe_branch, branch_path, 0), 0);
              lim_free (0, branch_path);
            }
          lim_free (0, branch_cat);
        }

      rel_free_table (dir_listing);
      lim_free (0, cat_dir);
    }

  rel_free_table (categories);

  /****************************************************************
   * For each branch, get a list of each version for it in the log
   */


  versions = 0;
  b_lim = rel_n_records (branches);
  for (b = 0; b < b_lim; ++b)
    {
      t_uchar * branch;
      t_uchar * branch_dir;

      branch = branches[b][0];
      branch_dir = branches[b][1];

      dir_listing = directory_files (branch_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          t_uchar * maybe_version;
          t_uchar * version_pkg;

          maybe_version = dir_listing[x][0];

          if (only_version && str_cmp (only_version, maybe_version))
            continue;
          
          if (!arch_valid_package_name (maybe_version, arch_no_archive, arch_req_version, 0))
            continue;

          version_pkg = arch_parse_package_name (arch_ret_package, 0, maybe_version);
          if (!str_cmp (branch, version_pkg))
            {
              t_uchar * version_path;

              version_path = file_name_in_vicinity (0, branch_dir, maybe_version);
              rel_add_records (&versions, rel_make_record (maybe_version, version_path, 0), 0);
              lim_free (0, version_path);
            }
          lim_free (0, version_pkg);
        }

      rel_free_table (dir_listing);
    }

  rel_free_table (branches);


  /****************************************************************
   * For each version, get a list of each archive for it in the log
   */
  
  logs = 0;
  v_lim = rel_n_records (versions);
  for (v = 0; v < v_lim; ++v)
    {
      t_uchar * version;
      t_uchar * vsn_dir;

      version = versions[v][0];
      vsn_dir = versions[v][1];

      dir_listing = directory_files (vsn_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          t_uchar * maybe_archive;
          t_uchar * archive_path;
          t_uchar * patch_log_path;

          maybe_archive = dir_listing[x][0];

          if (only_archive && str_cmp (only_archive, maybe_archive))
            continue;
          
          if (!arch_valid_archive_name (maybe_archive))
            continue;

          archive_path = file_name_in_vicinity (0, vsn_dir, maybe_archive);
          patch_log_path = file_name_in_vicinity (0, archive_path, "patch-log");

          if (!safe_access (patch_log_path, F_OK))
            {
              t_uchar * fqversion;

              fqversion = arch_fully_qualify (maybe_archive, version);
              rel_add_records (&logs, rel_make_record (fqversion, patch_log_path, 0), 0);
              lim_free (0, fqversion);
            }

          lim_free (0, archive_path);
          lim_free (0, patch_log_path);
        }

      rel_free_table (dir_listing);
    }

  rel_free_table (versions);

  if (only_category)
    lim_free (0, only_category);
  if (only_branch)
    lim_free (0, only_branch);
  if (only_version)
    lim_free (0, only_version);

  return logs;
}


t_uchar *
arch_highest_patch_level (t_uchar * tree_root, t_uchar * archive, t_uchar * version)
{
  rel_table logs = 0;
  t_uchar * answer = 0;

  logs = arch_log_ls (tree_root, archive, version, 0);
  arch_sort_table_by_patch_level_field (1, logs, 0);

  if (logs)
    answer = str_save (0, logs[0][0]);

  rel_free_table (logs);
  return answer;
}


t_uchar *
arch_latest_logged_vesion (t_uchar * tree_root, t_uchar * archive, t_uchar * package)
{
  rel_table log_versions = 0;
  t_uchar * answer = 0;

  log_versions = arch_logs (tree_root, archive, 0, package, 0);
  arch_sort_table_by_name_field (1, log_versions, 0);

  if (log_versions)
    {
      answer = arch_parse_package_name (arch_ret_non_archive, 0, log_versions[0][0]);
    }

  rel_free_table (log_versions);
  return answer;
}


rel_table
arch_log_ls (t_uchar * tree_root, t_uchar * archive, t_uchar * version, int full)
{
  rel_table answer;             /* [0] == lvl, [2] == path */
  t_uchar * log_dir;
  rel_table dir_listing;
  int lim;
  int x;

  answer = 0;

  log_dir = arch_log_dir (tree_root, archive, version);

  if (safe_access (log_dir, F_OK))
    goto leave;

  dir_listing = directory_files (log_dir);

  lim = rel_n_records (dir_listing);
  for (x = 0; x < lim; ++x)
    {
      if (arch_valid_patch_level_name (dir_listing[x][0]))
        {
          t_uchar * path = 0;

          path = file_name_in_vicinity (0, log_dir, dir_listing[x][0]);

          if (!full)
            {
              rel_add_records (&answer, rel_make_record (dir_listing[x][0], path, 0), 0);
            }
          else
            {
              t_uchar * full_name = 0;

              full_name = str_alloc_cat_many (0, archive, "/", version, "--", dir_listing[x][0], str_end);
              rel_add_records (&answer, rel_make_record (full_name, path, 0), 0);

              lim_free (0, full_name);
            }

          lim_free (0, path);
        }
    }

 leave:
  lim_free (0, log_dir);

  if (full)
    arch_sort_table_by_name_field (0, answer, 0);
  else
    arch_sort_table_by_patch_level_field (0, answer, 0);
  return answer;
}

int
arch_valid_log_file (t_uchar * log)
{
  size_t len;
  t_uchar * pos;

  len = str_length (log);
  pos = log;

  /* Parse the headers.
   */

  while (1)
    {
      t_uchar * eol;
      t_uchar * colon;

      eol = str_chr_index (pos, '\n');
      if (!eol)
        eol = pos + len;

      if (eol == pos)
        break;

      colon = str_chr_index_n (pos, eol - pos, ':');
      if (!colon)
        return 0;

      len -= (1 + (colon - pos));
      pos = colon + 1;

      while (char_is_blank (*pos))
        {
          ++pos;
          --len;
        }
      
      while (*eol && char_is_blank (eol[1]))
        {
          eol = str_chr_index (eol + 1, '\n');
          if (!eol)
            eol = pos + len;
        }

      if (*eol)
        ++eol;

      len -= (eol - pos);
      pos = eol;
    }

  /* skip the blank line */

  if (!*pos)
    return 0;

  return 1;
}


void
arch_parse_log (rel_table * headers_list, assoc_table * headers, t_uchar ** body, t_uchar * log)
{
  size_t len;
  t_uchar * pos;

  len = str_length (log);
  pos = log;

  /* Parse and assemble the headers.
   */

  while (1)
    {
      t_uchar * eol;
      t_uchar * colon;
      t_uchar * field_name;
      t_uchar * field_value;

      eol = str_chr_index (pos, '\n');
      if (!eol)
        eol = pos + len;

      if (eol == pos)
        break;

      colon = str_chr_index_n (pos, eol - pos, ':');
      if (!colon)
        panic ("bogusly formatted log message (missing field name in headers)");

      field_name = str_save_n (0, pos, colon - pos);
      {
        size_t x;
        size_t l;

        l = str_length (field_name);
        for (x = 0; x < l; ++x)
          field_name[x] = char_to_lower (field_name[x]);
      }


      len -= (1 + (colon - pos));
      pos = colon + 1;

      while (char_is_blank (*pos))
        {
          ++pos;
          --len;
        }
      
      while (*eol && char_is_blank (eol[1]))
        {
          eol = str_chr_index (eol + 1, '\n');
          if (!eol)
            eol = pos + len;
        }

      field_value = str_save_n (0, pos, eol - pos);

      if (headers)
        assoc_set (headers, field_name, field_value);
      if (headers_list)
        rel_add_records (headers_list, rel_make_record (field_name, field_value, 0), 0);

      lim_free (0, field_name);
      lim_free (0, field_value);

      if (*eol)
        ++eol;

      len -= (eol - pos);
      pos = eol;
    }

  /* skip the blank line */

  if (*pos)
    ++pos;

  /* save the body */
  if (body)
    *body = str_save (0, pos);
}


void
arch_print_headers_summary (int out_fd, assoc_table headers, int summarized_headers)
{
  if (arch_include_date & summarized_headers)
    {
      t_uchar * d;

      d = assoc_ref (headers, "date");
      if (!d)
        safe_printfmt (out_fd, "    <<<no date: header>>>\n");
      else
        safe_printfmt (out_fd, "    %s\n", d);
    }

  if (arch_include_creator & summarized_headers)
    {
      t_uchar * c;

      c = assoc_ref (headers, "creator");
      if (!c)
        safe_printfmt (out_fd, "    <<<no creator: header>>>\n");
      else
        safe_printfmt (out_fd, "    %s\n", c);
    }

  if (arch_include_summary & summarized_headers)
    {
      t_uchar * s;

      s = assoc_ref (headers, "summary");
      if (!s)
        safe_printfmt (out_fd, "    <<<no summary: header>>>\n");
      else
        safe_printfmt (out_fd, "    %s\n", s);
    }
}



void
arch_print_log_list_header (int out_fd, t_uchar * header, rel_table list, int field)
{
  int x;
  size_t col;
  int any_on_this_line;

  safe_printfmt (out_fd, "%s: ", header);
  col = str_length (header) + 2;
  any_on_this_line = 0;

  for (x = 0; x < rel_n_records (list); ++x)
    {
      size_t len_this;

      len_this = str_length (list[x][field]);

      if (!any_on_this_line)
        {
          safe_printfmt (out_fd, "%s", list[x][field]);
          col += len_this;
          any_on_this_line = 1;
        }
      else
        {
          if ((col + 1 + len_this) >= 60)
            {
              safe_printfmt (out_fd, "\n    %s", list[x][field]);
              col = 4 + len_this;
              any_on_this_line = 1;
            }
          else
            {
              safe_printfmt (out_fd, " %s", list[x][field]);
              col += len_this;
              any_on_this_line = 1;
            }
        }
    }
  safe_printfmt (out_fd, "\n");
}


void
arch_print_log_pairs_header (int out_fd, t_uchar * header, rel_table list, int field_a, int field_b)
{
  int x;
  int once;

  safe_printfmt (out_fd, "%s: ", header);

  once = 0;

  for (x = 0; x < rel_n_records (list); ++x)
    {
      safe_printfmt (out_fd, "%s%s %s\n", (once ? "    " : ""), list[x][field_a], list[x][field_b]);
      once = 1;
    }
  safe_printfmt (out_fd, "\n");
}


void
arch_copy_to_patch_log (t_uchar * tree_root, t_uchar * archive, t_uchar * revision, t_uchar * log_file)
{
  int ign;
  t_uchar * version = 0;
  t_uchar * lvl = 0;
  t_uchar * log_dir = 0;
  t_uchar * dest_path = 0;
  t_uchar * dest_dir = 0;
  t_uchar * tmp_path = 0;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);
  
  log_dir = arch_log_dir (tree_root, archive, version);
  dest_path = file_name_in_vicinity (0, log_dir, lvl);
  dest_dir = file_name_directory_file (0, dest_path);
  tmp_path = file_name_in_vicinity (0, dest_dir, ",,new-log");

  ensure_directory_exists (log_dir);

  vu_unlink (&ign, tmp_path);

  copy_file (log_file, tmp_path);

  safe_rename (tmp_path, dest_path);

  lim_free (0, version);
  lim_free (0, lvl);
  lim_free (0, log_dir);
  lim_free (0, dest_path);
  lim_free (0, dest_dir);
  lim_free (0, tmp_path);
}

void
arch_rename_to_patch_log (t_uchar * tree_root, t_uchar * archive, t_uchar * revision, t_uchar * log_file)
{
  t_uchar * version = 0;
  t_uchar * lvl = 0;
  t_uchar * log_dir = 0;
  t_uchar * dest_path = 0;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);
  
  log_dir = arch_log_dir (tree_root, archive, version);
  dest_path = file_name_in_vicinity (0, log_dir, lvl);

  ensure_directory_exists (log_dir);
  safe_rename (log_file, dest_path);

  lim_free (0, version);
  lim_free (0, lvl);
  lim_free (0, log_dir);
  lim_free (0, dest_path);
}




/* tag: Tom Lord Mon May 12 19:20:16 2003 (patch-logs.c)
 */
