/* footnotes.c -- Some functions for manipulating footnotes.

   Copyright 1993-2022 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 "session.h"
#include "scan.h"
#include "util.h"
#include "footnotes.h"

/* Nonzero means attempt to show footnotes when displaying a new window. */
int auto_footnotes_p = 0;

static char *footnote_nodename = "*Footnotes*";

/* Find the window currently showing footnotes. */
static WINDOW *
find_footnotes_window (void)
{
  WINDOW *win;

  /* Try to find an existing window first. */
  for (win = windows; win; win = win->next)
    if (internal_info_node_p (win->node) &&
        (strcmp (win->node->nodename, footnote_nodename) == 0))
      break;

  return win;
}

/* Manufacture a node containing the footnotes of this node, and
   return the manufactured node.  If NODE has no footnotes, return a 
   NULL pointer. */
NODE *
make_footnotes_node (NODE *node)
{
  NODE *fn_node, *footnotes_node = NULL, *result = NULL;
  long fn_start = -1;
  char *fnptr;

  /* Make the initial assumption that the footnotes appear as simple
     text within this windows node. */
  fn_node = node;

  /* See if this node contains the magic footnote label. */
    {
      char saved = node->contents[node->nodelen];
      node->contents[node->nodelen] = '\0';
      fnptr = strstr (node->contents, FOOTNOTE_LABEL);
      node->contents[node->nodelen] = saved;
    }
  if (fnptr)
    fn_start = fnptr - node->contents;

  /* If it doesn't, check to see if it has an associated footnotes node. */
  if (!fnptr)
    {
      REFERENCE **refs;

      refs = node->references;

      if (refs)
        {
          register int i;
          char *refname;
          int reflen = strlen ("-Footnotes") + strlen (node->nodename);

          refname = xmalloc (reflen + 1);

          strcpy (refname, node->nodename);
          strcat (refname, "-Footnotes");

          for (i = 0; refs[i]; i++)
            if (refs[i]->type == REFERENCE_XREF
                && (refs[i]->nodename != NULL)
                /* Support both the older "foo-Footnotes" and the new
                   style "foo-Footnote-NN" references.  */
                && (strcmp (refs[i]->nodename, refname) == 0 ||
                 (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 &&
                  refs[i]->nodename[reflen - 1] == '-' &&
                  isdigit (refs[i]->nodename[reflen]))))
              {
                footnotes_node = info_get_node (node->fullpath, refname);
                if (footnotes_node)
                  {
                    fn_node = footnotes_node;
                    fn_start = 0;
                  }
                break;
              }

          free (refname);
        }
    }

  /* If we never found the start of a footnotes area, quit now. */
  if (fn_start == -1)
    return NULL;

  /* Make the new node. */
  result = info_create_node ();

  /* Get the size of the footnotes appearing within this node. */
  {
    char *header;
    long text_start = fn_start;

    xasprintf (&header,
              "*** Footnotes appearing in the node '%s' ***\n",
              node->nodename);

    /* Move the start of the displayed text to right after the first line.
       This effectively skips either "---- footno...", or "File: foo...". */
    while (text_start < fn_node->nodelen)
      if (fn_node->contents[text_start++] == '\n')
        break;
  
    result->nodelen = strlen (header) + fn_node->nodelen - text_start;

    /* Set the contents of this node. */
    result->contents = xmalloc (1 + result->nodelen);
    sprintf (result->contents, "%s", header);
    memcpy (result->contents + strlen (header),
            fn_node->contents + text_start, fn_node->nodelen - text_start);
    result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0';

   /* Copy and adjust references that appear in footnotes section. */
    {
      REFERENCE **ref = fn_node->references;

      for (; *ref; ref++)
        {
          if ((*ref)->start > text_start)
            break;
        }

      result->references = info_copy_references (ref);

      for (ref = result->references; *ref; ref++)
        {
          (*ref)->start -= text_start - strlen (header);
          (*ref)->end -= text_start - strlen (header);
        }
    }

    result->nodename = xstrdup (footnote_nodename);
    result->flags |= N_IsInternal | N_WasRewritten;

    /* Needed in case the user follows a reference in the footnotes window. */
    result->fullpath = fn_node->fullpath;
    result->subfile = fn_node->subfile;

    free (header);
  }

  free_history_node (footnotes_node);
  return result;
}

/* Create or delete the footnotes window depending on whether footnotes
   exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
   and displayed.  Returns FN_UNFOUND if there were no footnotes found
   in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
   window to show them couldn't be made. */
int
info_get_or_remove_footnotes (WINDOW *window)
{
  WINDOW *fn_win;
  NODE *new_footnotes = 0;

  fn_win = find_footnotes_window ();

  /* If we are in the footnotes window, change nothing. */
  if (fn_win == window)
    return FN_FOUND;

  /* Don't display footnotes for the "*" node (entire contents of file) or
     for nodes without a name like completion windows. */
  if (window->node->nodename && strcmp ("*", window->node->nodename))
    /* Try to find footnotes for this window's node. */
    new_footnotes = make_footnotes_node (window->node);

  if (!new_footnotes)
    {
      /* If there was a window showing footnotes, and there are no footnotes
         for the current window, delete the old footnote window. */
      if (fn_win && windows->next)
        info_delete_window_internal (fn_win);
      return FN_UNFOUND;
    }

  /* If there is no window around showing footnotes, try
     to make a new window. */
  if (!fn_win)
    {
      WINDOW *old_active;
      WINDOW *last, *win;

      /* Always make this window be the last one appearing in the list.  Find
         the last window in the chain. */
      for (win = windows, last = windows; win; last = win, win = win->next);

      /* Try to split this window, and make the split window the one to
         contain the footnotes. */
      old_active = active_window;
      active_window = last;
      fn_win = window_make_window ();
      active_window = old_active;

      /* If we are hacking automatic footnotes, and there are footnotes
         but we couldn't display them, print a message to that effect. */
      if (!fn_win)
        {
          if (auto_footnotes_p)
            info_error (_("Footnotes could not be displayed"));
          return FN_UNABLE;
        }
    }

  /* Note that info_set_node_of_window calls this function
     (info_get_or_remove_footnotes), but we do not recurse indefinitely
     because we check if we are in the footnote window above. */
  info_set_node_of_window (fn_win, new_footnotes);
  fn_win->flags |= W_TempWindow;

  /* Make the height be the number of lines appearing in the footnotes. */
  if (new_footnotes)
    window_change_window_height (fn_win, fn_win->line_count - fn_win->height);

  return FN_FOUND;
}

/* Show the footnotes associated with this node in another window. */
DECLARE_INFO_COMMAND (info_show_footnotes,
   _("Show the footnotes associated with this node in another window"))
{
  /* Make the window go away if it is already showing. */
    WINDOW *fn_win = find_footnotes_window ();

    /* If there is an old footnotes window, and it isn't the only window
       on the screen, delete it. */
    if (fn_win && windows->next)
      {
        info_delete_window_internal (fn_win);
        return;
      }


    switch (info_get_or_remove_footnotes (window))
      {
      case FN_UNFOUND:
        info_error ("%s", msg_no_foot_node);
        break;

      case FN_UNABLE:
        info_error ("%s", msg_win_too_small);
        break;
      }
}
