/* util.c --  various utility functions

   Copyright 1993-2024 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/>. */

#include "info.h"
#include "session.h"
#include "util.h"
#include "tag.h"

/* wrapper for asprintf */
static int
xvasprintf (char **ptr, const char *template, va_list ap)
{
  int ret;
  ret = vasprintf (ptr, template, ap);
  if (ret < 0)
    abort (); /* out of memory */
  return ret;
}

int
xasprintf (char **ptr, const char *template, ...)
{
  int ret;
  va_list v;
  va_start (v, template);
  ret = xvasprintf (ptr, template, v);
  va_end (v);
  return ret;
}

/* Return the file buffer which belongs to WINDOW's node. */
FILE_BUFFER *
file_buffer_of_window (WINDOW *window)
{
  /* If this window has no node, then it has no file buffer. */
  if (!window->node)
    return NULL;

  if (window->node->fullpath)
    return info_find_file (window->node->fullpath);

  return NULL;
}

/* Return "(FILENAME)NODENAME" for NODE, or just "NODENAME" if NODE's
   filename is not set.  Return value should not be freed. */
char *
node_printed_rep (NODE *node)
{
  static char *rep;

  if (node->fullpath)
    {
      char *filename = filename_non_directory (node->fullpath);
      rep = xrealloc (rep, 1 + strlen (filename) + 1 + strlen (node->nodename) + 1);
      sprintf (rep, "(%s)%s", filename, node->nodename);
      return rep;
    }
  else
    return node->nodename;
}


/* Return a pointer to the part of PATHNAME that simply defines the file. */
char *
filename_non_directory (char *pathname)
{
  register char *filename = pathname + strlen (pathname);

  if (HAVE_DRIVE (pathname))
    pathname += 2;

  while (filename > pathname && !IS_SLASH (filename[-1]))
    filename--;

  return filename;
}

/* Return non-zero if NODE is one especially created by Info. */
int
internal_info_node_p (NODE *node)
{
  return (node != NULL) && (node->flags & N_IsInternal);
}

/* Make NODE appear to be one especially created by Info. */
void
name_internal_node (NODE *node, char *name)
{
  if (!node)
    return;

  node->fullpath = "";
  node->subfile = 0;
  node->nodename = name;
  node->flags |= N_IsInternal;
}

/* Return the window displaying NAME, the name of an internally created
   Info window. */
WINDOW *
get_internal_info_window (char *name)
{
  WINDOW *win;

  for (win = windows; win; win = win->next)
    if (internal_info_node_p (win->node) &&
        (strcmp (win->node->nodename, name) == 0))
      break;

  return win;
}


/* If ITER points to an ANSI escape sequence, process it, set PLEN to its
   length in bytes, and return 1.
   Otherwise, return 0.
 */
int
ansi_escape (mbi_iterator_t iter, int *plen)
{
  if (raw_escapes_p && *mbi_cur_ptr (iter) == '\033' && mbi_avail (iter))
    {
      mbi_advance (iter);
      if (*mbi_cur_ptr (iter) == '[' &&  mbi_avail (iter))
        {
          ITER_SETBYTES (iter, 1);
          mbi_advance (iter);
          if (isdigit ((unsigned char) *mbi_cur_ptr (iter))
              && mbi_avail (iter))
            {
              ITER_SETBYTES (iter, 1);
              mbi_advance (iter);
              if (*mbi_cur_ptr (iter) == 'm')
                {
                  *plen = 4;
                  return 1;
                }
              else if (isdigit ((unsigned char) *mbi_cur_ptr (iter))
                       && mbi_avail (iter))
                {
                  ITER_SETBYTES (iter, 1);
                  mbi_advance (iter);
                  if (*mbi_cur_ptr (iter) == 'm')
                    {
                      *plen = 5;
                      return 1;
                    }
                }
            }
        }
    }

  return 0;
}

static struct text_buffer printed_rep = { 0 };

/* Return pointer to string that is the printed representation of character
   (or other logical unit) at ITER if it were printed at screen column
   PL_CHARS.  Use ITER_SETBYTES (util.h) on ITER if we need to advance
   past a unit that the multibyte iteractor doesn't know about (like an ANSI
   escape sequence).  If ITER points at an end-of-line character, set *DELIM to
   this character.  *PCHARS gets the number of screen columns taken up by
   outputting the return value, and *PBYTES the number of bytes in returned
   string.  Return value is not null-terminated.  Return value must not be
   freed by caller. */
char *
printed_representation (mbi_iterator_t *iter, int *delim, size_t pl_chars,
                        int *pchars, int *pbytes)
{
  struct text_buffer *rep = &printed_rep;

  char *cur_ptr = (char *) mbi_cur_ptr (*iter);
  int cur_len = mb_len (mbi_cur (*iter));

  text_buffer_reset (&printed_rep);

  if (mb_isprint (mbi_cur (*iter)))
    {
      /* cur.wc gives a wchar_t object.  See mbiter.h in the
         gnulib/lib directory. */
      *pchars = wcwidth ((*iter).cur.wc);
      *pbytes = cur_len;
      return cur_ptr;
    }
  else if (cur_len == 1)
    {
      if (*cur_ptr == '\n' || *cur_ptr == '\r')
        {
          /* If this is a CRLF line ending, ignore this character. */
          if (*cur_ptr == '\r' && cur_ptr[1] == '\n')
            {
              *pchars = 0;
              *pbytes = 0;
              return cur_ptr;
            }

          *pchars = 1;
          *pbytes = cur_len;
          *delim = *cur_ptr;
          text_buffer_add_char (rep, ' ');
          return cur_ptr;
        }
      else if (ansi_escape (*iter, &cur_len))
        {
          *pchars = 0;
          *pbytes = cur_len;
          ITER_SETBYTES (*iter, cur_len);

          return cur_ptr;
        }
      else if (*cur_ptr == '\t')
        {
          int i = 0;

          /* compute the number of columns to the next tab stop, assuming
             8 columns for a tab.

             To determine the next tab stop, add 8 to the current column,
             and then subtract the remainder of the division by 8.

             (n & 0xf8) does (n - n mod 8) in one step as the binary
             representation of 0xf8 is 1111 1000 so the result has
             to be a multiple of 8.
             TODO this doesn't work if there are more than 255 columns.
           */
          *pchars = ((pl_chars + 8) & 0xf8) - pl_chars;
          *pbytes = *pchars;

          /* We must output spaces instead of the tab because a tab may
             not clear characters already on the screen. */
          for (i = 0; i < *pbytes; i++)
            text_buffer_add_char (rep, ' ');
          return text_buffer_base (rep);
        }
    }

  /* Show CTRL-x as "^X".  */
  if (iscntrl ((unsigned char) *cur_ptr) && *(unsigned char *)cur_ptr < 127)
    {
      *pchars = 2;
      *pbytes = 2;
      text_buffer_add_char (rep, '^');
      text_buffer_add_char (rep, *cur_ptr | 0x40);
      return text_buffer_base (rep);
    }
  else if (*cur_ptr == DEL)
    {
      *pchars = 0;
      *pbytes = 0;
      return text_buffer_base (rep);
    }
  else
    {
      /* Original byte was not recognized as anything.  Display its octal
         value.  This could happen in the C locale for bytes above 128,
         or for bytes 128-159 in an ISO-8859-1 locale.  Don't output the bytes
         as they are, because they could have special meaning to the
         terminal. */
      *pchars = 4;
      *pbytes = 4;
      text_buffer_printf (rep, "\\%o", *(unsigned char *)cur_ptr);
      return text_buffer_base (rep);
    }
}

/* Flexible Text Buffer */

void
text_buffer_init (struct text_buffer *buf)
{
  memset (buf, 0, sizeof *buf);
}

void
text_buffer_free (struct text_buffer *buf)
{
  free (buf->base);
}

size_t
text_buffer_vprintf (struct text_buffer *buf, const char *format, va_list ap)
{
  ssize_t n;
  va_list ap_copy;

  if (!buf->base)
    {
      if (buf->size == 0)
        buf->size = MIN_TEXT_BUF_ALLOC; /* Initial allocation */

      buf->base = xmalloc (buf->size);
    }

  for (;;)
    {
      va_copy (ap_copy, ap);
      n = vsnprintf (buf->base + buf->off, buf->size - buf->off,
                     format, ap_copy);
      va_end (ap_copy);
      if (n < 0 || buf->off + n >= buf->size ||
          !memchr (buf->base + buf->off, '\0', buf->size - buf->off + 1))
        {
          size_t newlen = buf->size * 2;
          if (newlen < buf->size)
            xalloc_die ();
          buf->size = newlen;
          buf->base = xrealloc (buf->base, buf->size);
        }
      else
        {
          buf->off += n;
          break;
        }
    }
  return n;
}

/* Make sure there are LEN free bytes at end of BUF. */
void
text_buffer_alloc (struct text_buffer *buf, size_t len)
{
  if (buf->off + len > buf->size)
    {
      buf->size = buf->off + len;
      if (buf->size < MIN_TEXT_BUF_ALLOC)
        buf->size = MIN_TEXT_BUF_ALLOC;
      buf->base = xrealloc (buf->base, buf->size);
    }
}

/* Return number of bytes that can be written to text buffer without
   reallocating the text buffer. */
size_t
text_buffer_space_left (struct text_buffer *buf)
{
  /* buf->size is the offset of the first byte after the allocated space.
     buf->off is the offset of the first byte to be written to. */
  return buf->size - buf->off;
}

#if HAVE_ICONV

/* Run iconv using text buffer as output buffer. */
size_t
text_buffer_iconv (struct text_buffer *buf, iconv_t iconv_state,
                   ICONV_CONST char **inbuf, size_t *inbytesleft)
{
  size_t out_bytes_left;
  char *outptr;
  size_t iconv_ret;
  size_t extra_alloc = 1;

  while (1)
    {
      outptr = text_buffer_base (buf) + text_buffer_off (buf);
      out_bytes_left = text_buffer_space_left (buf);

      iconv_ret = iconv (iconv_state, inbuf, inbytesleft,
                         &outptr, &out_bytes_left);
      text_buffer_off (buf) = outptr - text_buffer_base (buf);

      if (iconv_ret != (size_t) -1)
        break; /* success */

      /* If we ran out of space, allocate more and try again. */
      if (errno == E2BIG)
        text_buffer_alloc (buf, (extra_alloc *= 4));
      else
        break; /* let calling code deal with it */
    }
  return iconv_ret;
}

#endif /* HAVE_ICONV */

size_t
text_buffer_add_string (struct text_buffer *buf, const char *str, size_t len)
{
  text_buffer_alloc (buf, len);
  memcpy (buf->base + buf->off, str, len);
  buf->off += len;
  return len;
}

size_t
text_buffer_fill (struct text_buffer *buf, int c, size_t len)
{
  char *p;
  size_t i;

  text_buffer_alloc (buf, len);

  for (i = 0, p = buf->base + buf->off; i < len; i++)
    *p++ = c;
  buf->off += len;

  return len;
}

void
text_buffer_add_char (struct text_buffer *buf, int c)
{
  char ch = c;
  text_buffer_add_string (buf, &ch, 1);
}

size_t
text_buffer_printf (struct text_buffer *buf, const char *format, ...)
{
  va_list ap;
  size_t n;

  va_start (ap, format);
  n = text_buffer_vprintf (buf, format, ap);
  va_end (ap);
  return n;
}

#if defined(__MSDOS__) || defined(__MINGW32__)
/* Cannot use FILENAME_CMP here, since that does not consider forward-
   and back-slash characters equal.  */
int
fncmp (const char *fn1, const char *fn2)
{
  const char *s1 = fn1, *s2 = fn2;

  while (tolower (*s1) == tolower (*s2)
         || (IS_SLASH (*s1) && IS_SLASH (*s2)))
    {
      if (*s1 == 0)
        return 0;
      s1++;
      s2++;
    }

  return tolower (*s1) - tolower (*s2);
}

#endif
