/* dir.c -- how to build a special "dir" node from "localdir" files.

   Copyright 1993-2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#include "info.h"
#include "scan.h"
#include "filesys.h"
#include "tilde.h"

static void add_menu_to_node (char *contents, size_t size, NODE *node);
static void insert_text_into_node (NODE *node, long start,
    char *text, int textlen);

static NODE *dir_node = 0;

static NODE *build_dir_node (void);

/* Return composite directory node.  Return value should be freed by caller,
   but none of its fields should be. */
NODE *
get_dir_node (void)
{
  NODE *node;

  if (!dir_node)
    dir_node = build_dir_node ();

  node = xmalloc (sizeof (NODE));
  *node = *dir_node;

  return node;
}

static char *dir_contents;

static NODE *
build_dir_node (void)
{
  char *this_dir;
  int path_index = 0;

  NODE *node;

  node = info_create_node ();
  node->nodename = xstrdup ("Top");
  node->fullpath = xstrdup ("dir");
  node->contents = xstrdup (

"File: dir,	Node: Top,	This is the top of the INFO tree.\n"
"\n"
"This is the Info main menu (aka directory node).\n"
"A few useful Info commands:\n"
"\n"
"  'q' quits;\n"
"  'H' lists all Info commands;\n"
"  'h' starts the Info tutorial;\n"
"  'mTexinfo RET' visits the Texinfo manual, etc.\n"

  );

  node->nodelen = strlen (node->contents);

 for (this_dir = infopath_first (&path_index); this_dir; 
        this_dir = infopath_next (&path_index))
   {
     char *result;
     char *fullpath;
     int len;
     size_t filesize;
     struct stat finfo;
     int compressed;
     char *contents;

/* Space for an appended compressed file extension, like ".gz". */
#define PADDING "XXXXXXXXX"

     len = xasprintf (&fullpath, "%s/dir%s", this_dir, PADDING);
     fullpath[len - strlen(PADDING)] = '\0';

     result = info_check_compressed (fullpath, &finfo);
     if (!result)
       {
         free (fullpath);
         continue;
       }

      contents = filesys_read_info_file (fullpath, &filesize,
                                         &finfo, &compressed);
      if (contents)
        {
          add_menu_to_node (contents, filesize, node);
          free (contents);
        }

      free (fullpath);
    }

  node->flags |= N_IsDir;
  dir_contents = node->contents;
  scan_node_contents (node, 0, 0);
  return node;
}

/* Given CONTENTS and NODE, add the menu found in CONTENTS to the menu
   found in NODE->contents.  SIZE is the total size of CONTENTS. */
static void
add_menu_to_node (char *contents, size_t size, NODE *node)
{
  SEARCH_BINDING contents_binding, fb_binding;
  long contents_offset, fb_offset;

  contents_binding.buffer = contents;
  contents_binding.start = 0;
  contents_binding.end = size;
  contents_binding.flags = S_FoldCase | S_SkipDest;

  fb_binding.buffer = node->contents;
  fb_binding.start = 0;
  fb_binding.end = node->nodelen;
  fb_binding.flags = S_FoldCase | S_SkipDest;

  /* Move to the start of the menus in CONTENTS and NODE. */
  if (search_forward (INFO_MENU_LABEL, &contents_binding, &contents_offset)
      != search_success)
    /* If there is no menu in CONTENTS, quit now. */
    return;

  /* There is a menu in CONTENTS, and contents_offset points to the first
     character following the menu starter string.  Skip all whitespace
     and newline characters. */
  contents_offset += skip_whitespace_and_newlines (contents + contents_offset);

  /* If there is no menu in NODE, make one. */
  if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
      != search_success)
    {
      fb_binding.start = node->nodelen;

      insert_text_into_node
        (node, fb_binding.start, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));

      fb_binding.buffer = node->contents;
      fb_binding.start = 0;
      fb_binding.end = node->nodelen;
      if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
	  != search_success)
        abort ();
    }

  /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
     appear in their respective buffers.  Add the remainder of CONTENTS
     to the end of NODE's menu. */
  fb_binding.start = fb_offset;
  fb_offset = find_node_separator (&fb_binding);
  if (fb_offset != -1)
    fb_binding.start = fb_offset;
  else
    fb_binding.start = fb_binding.end;

  /* Leave exactly one blank line between directory entries. */
  {
    int num_found = 0;

    while ((fb_binding.start > 0) &&
           (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
      {
        num_found++;
        fb_binding.start--;
      }

    /* Optimize if possible. */
    if (num_found >= 2)
      {
        fb_binding.buffer[fb_binding.start++] = '\n';
        fb_binding.buffer[fb_binding.start++] = '\n';
      }
    else
      {
        /* Do it the hard way. */
        insert_text_into_node (node, fb_binding.start, "\n\n", 2);
        fb_binding.start += 2;
      }
  }

  /* Insert the new menu. */
  insert_text_into_node
    (node, fb_binding.start, contents + contents_offset, size - contents_offset);
}

static void
insert_text_into_node (NODE *node, long start, char *text, int textlen)
{
  char *contents;
  long end;

  end = node->nodelen;

  contents = xmalloc (node->nodelen + textlen + 1);
  memcpy (contents, node->contents, start);
  memcpy (contents + start, text, textlen);
  memcpy (contents + start + textlen, node->contents + start, end - start + 1);
  free (node->contents);
  node->contents = contents;
  node->nodelen += textlen;
}

/* Return directory entry.  Return value should not be freed or modified. */
REFERENCE *
lookup_dir_entry (char *label, int sloppy)
{
  REFERENCE *entry;

  if (!dir_node)
    dir_node = build_dir_node ();

  entry = info_get_menu_entry_by_label (dir_node, label, sloppy);

  return entry;
}

/* Look up entry in "dir" in search directory.  Return
   value is a pointer to a newly allocated REFERENCE. */
REFERENCE *
dir_entry_of_infodir (char *label, char *searchdir)
{
  char *dir_fullpath;
  int len;
  char *result;

  struct stat dummy;
  char *entry_fullpath;

  NODE *dir_node;
  REFERENCE *entry;

  if (IS_ABSOLUTE(searchdir))
    len = xasprintf (&dir_fullpath, "%s/dir%s", searchdir, PADDING);
  else
    len = xasprintf (&dir_fullpath, "./%s/dir%s", searchdir, PADDING);
  dir_fullpath[len - strlen(PADDING)] = '\0';

  result = info_check_compressed (dir_fullpath, &dummy);
  if (!result)
    {
      free (dir_fullpath);
      return 0;
    }

  dir_node = info_get_node (dir_fullpath, "Top");
  free (dir_fullpath);
  entry = info_get_menu_entry_by_label (dir_node, label, 1);
  if (!entry || !entry->filename)
    {
      free_history_node (dir_node);
      return 0;
      /* A dir entry with no filename is unlikely, but not impossible. */
    }

  entry = info_copy_reference (entry);
  entry_fullpath = info_add_extension (searchdir, entry->filename, &dummy);
  if (entry_fullpath)
    {
      free (entry->filename);
      entry->filename = entry_fullpath;
    }

  free_history_node (dir_node);
  return entry;
}

