/*
 * File: dlib.c
 *
 * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org>
 * Copyright (C) 2024 Rodrigo Arias Mallo <rodarima@gmail.com>
 *
 * 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.
 */

/* Memory allocation, Simple dynamic strings, Lists (simple and sorted),
 * and a few utility functions
 */

/*
 * TODO: vsnprintf() is in C99, maybe a simple replacement if necessary.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>

#include "dlib.h"

static bool_t dLib_show_msg = TRUE;

/* dlib msgs go to stderr to avoid problems with filter dpis */
#define DLIB_MSG(...)                              \
   D_STMT_START {                                  \
      if (dLib_show_msg)                           \
         fprintf(stderr, __VA_ARGS__);             \
   } D_STMT_END

/*
 *- Memory --------------------------------------------------------------------
 */

void *dMalloc (size_t size)
{
  void *value = malloc (size);
  if (value == 0)
     exit(1);
  return value;
}

void *dRealloc (void *mem, size_t size)
{
  void *value = realloc (mem, size);
  if (value == 0)
     exit(1);
  return value;
}

void *dMalloc0 (size_t size)
{
  void *value = dMalloc (size);
  memset (value, 0, size);
  return value;
}

void dFree (void *mem)
{
   free(mem);
}

/*
 *- strings (char *) ----------------------------------------------------------
 */

char *dStrdup(const char *s)
{
   if (s) {
      int len = strlen(s)+1;
      char *ns = dNew(char, len);
      memcpy(ns, s, len);
      return ns;
   }
   return NULL;
}

char *dStrndup(const char *s, size_t sz)
{
   if (s) {
      char *ns = dNew(char, sz+1);
      memcpy(ns, s, sz);
      ns[sz] = 0;
      return ns;
   }
   return NULL;
}

/**
 * Concatenate a NULL-terminated list of strings
 */
char *dStrconcat(const char *s1, ...)
{
   va_list args;
   char *s, *ns = NULL;

   if (s1) {
      Dstr *dstr = dStr_sized_new(64);
      va_start(args, s1);
      for (s = (char*)s1; s; s = va_arg(args, char*))
         dStr_append(dstr, s);
      va_end(args);
      ns = dstr->str;
      dStr_free(dstr, 0);
   }
   return ns;
}

/**
 * Remove leading and trailing whitespace
 */
char *dStrstrip(char *s)
{
   char *p;
   int len;

   if (s && *s) {
      for (p = s; dIsspace(*p); ++p);
      for (len = strlen(p); len && dIsspace(p[len-1]); --len);
      if (p > s)
         memmove(s, p, len);
      s[len] = 0;
   }
   return s;
}

/**
 * Clear the contents of the string
 */
void dStrshred(char *s)
{
   if (s)
      memset(s, 0, strlen(s));
}

/**
 * Return a new string of length 'len' filled with 'c' characters
 */
char *dStrnfill(size_t len, char c)
{
   char *ret = dNew(char, len+1);
   for (ret[len] = 0; len > 0; ret[--len] = c);
   return ret;
}

/**
 * strsep() implementation
 */
char *dStrsep(char **orig, const char *delim)
{
   char *str, *p;

   if (!(str = *orig))
      return NULL;

   p = strpbrk(str, delim);
   if (p) {
      *p++ = 0;
      *orig = p;
   } else {
      *orig = NULL;
   }
   return str;
}

/*
 * ASCII functions to avoid the case difficulties introduced by I/i in
 * Turkic locales.
 */

/**
 * Case insensitive strstr
 */
char *dStriAsciiStr(const char *haystack, const char *needle)
{
   int i, j;
   char *ret = NULL;

   if (haystack && needle) {
      for (i = 0, j = 0; haystack[i] && needle[j]; ++i)
         if (D_ASCII_TOLOWER(haystack[i]) == D_ASCII_TOLOWER(needle[j])) {
            ++j;
         } else if (j) {
            i -= j;
            j = 0;
         }
      if (!needle[j])                 /* Got all */
         ret = (char *)(haystack + i - j);
   }
   return ret;
}

int dStrAsciiCasecmp(const char *s1, const char *s2)
{
   int ret = 0;

   while ((*s1 || *s2) &&
          !(ret = D_ASCII_TOLOWER(*s1) - D_ASCII_TOLOWER(*s2))) {
      s1++;
      s2++;
   }
   return ret;
}

int dStrnAsciiCasecmp(const char *s1, const char *s2, size_t n)
{
   int ret = 0;

   while (n-- && (*s1 || *s2) &&
          !(ret = D_ASCII_TOLOWER(*s1) - D_ASCII_TOLOWER(*s2))) {
      s1++;
      s2++;
   }
   return ret;
}

/*
 *- dStr ----------------------------------------------------------------------
 */

/**
 * Private allocator
 */
static void dStr_resize(Dstr *ds, int n_sz, int keep)
{
   if (n_sz >= 0) {
      if (keep && n_sz > ds->len) {
         ds->str = (Dstr_char_t*) dRealloc (ds->str, n_sz*sizeof(Dstr_char_t));
         ds->sz = n_sz;
      } else {
         dFree(ds->str);
         ds->str = dNew(Dstr_char_t, n_sz);
         ds->sz = n_sz;
         ds->len = 0;
         ds->str[0] = 0;
      }
   }
}

/**
 * Create a new string with a given size.
 * Initialized to ""
 */
Dstr *dStr_sized_new (int sz)
{
   Dstr *ds;
   if (sz < 2)
      sz = 2;

   ds = dNew(Dstr, 1);
   ds->str = NULL;
   dStr_resize(ds, sz + 1, 0); /* (sz + 1) for the extra '\0' */
   return ds;
}

/**
 * Return memory if there's too much allocated.
 */
void dStr_fit (Dstr *ds)
{
   dStr_resize(ds, ds->len + 1, 1);
}

/**
 * Insert a C string, at a given position, into a Dstr (providing length).
 * Note: It also works with embedded nil characters.
 */
void dStr_insert_l (Dstr *ds, int pos_0, const char *s, int l)
{
   int n_sz;

   if (ds && s && l && pos_0 >= 0 && pos_0 <= ds->len) {
      for (n_sz = ds->sz; ds->len + l >= n_sz; n_sz *= 2);
      if (n_sz > ds->sz) {
         dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0);
      }
      if (pos_0 < ds->len)
         memmove(ds->str+pos_0+l, ds->str+pos_0, ds->len-pos_0);
      memcpy(ds->str+pos_0, s, l);
      ds->len += l;
      ds->str[ds->len] = 0;
   }
}

/**
 * Insert a C string, at a given position, into a Dstr.
 */
void dStr_insert (Dstr *ds, int pos_0, const char *s)
{
   if (s)
      dStr_insert_l(ds, pos_0, s, strlen(s));
}

/**
 * Append a C string to a Dstr (providing length).
 * Note: It also works with embedded nil characters.
 */
void dStr_append_l (Dstr *ds, const char *s, int l)
{
   dStr_insert_l(ds, ds->len, s, l);
}

/**
 * Append a C string to a Dstr.
 */
void dStr_append (Dstr *ds, const char *s)
{
   dStr_append_l(ds, s, strlen(s));
}

/**
 * Create a new string.
 * Initialized to 's' or empty if 's == NULL'
 */
Dstr *dStr_new (const char *s)
{
   Dstr *ds = dStr_sized_new(0);
   if (s && *s)
      dStr_append(ds, s);
   return ds;
}

/**
 * Free a dillo string.
 * if 'all' free everything, else free the structure only.
 */
void dStr_free (Dstr *ds, int all)
{
   if (ds) {
      if (all)
         dFree(ds->str);
      dFree(ds);
   }
}

/**
 * Append one character.
 */
void dStr_append_c (Dstr *ds, int c)
{
   char cs[2];

   if (ds) {
      if (ds->sz > ds->len + 1) {
         ds->str[ds->len++] = (Dstr_char_t)c;
         ds->str[ds->len] = 0;
      } else {
         cs[0] = (Dstr_char_t)c;
         cs[1] = 0;
         dStr_append_l (ds, cs, 1);
      }
   }
}

/**
 * Truncate a Dstr to be 'len' bytes long.
 */
void dStr_truncate (Dstr *ds, int len)
{
   if (ds && len < ds->len) {
      ds->str[len] = 0;
      ds->len = len;
   }
}

/**
 * Clear a Dstr.
 */
void dStr_shred (Dstr *ds)
{
   if (ds && ds->sz > 0)
      memset(ds->str, '\0', ds->sz);
}

/**
 * Erase a substring.
 */
void dStr_erase (Dstr *ds, int pos_0, int len)
{
   if (ds && pos_0 >= 0 && len > 0 && pos_0 + len <= ds->len) {
      memmove(ds->str + pos_0, ds->str + pos_0 + len, ds->len - pos_0 - len);
      ds->len -= len;
      ds->str[ds->len] = 0;
   }
}

/**
 * vsprintf-like function that appends.
 * Used by: dStr_vsprintf(), dStr_sprintf() and dStr_sprintfa().
 */
void dStr_vsprintfa (Dstr *ds, const char *format, va_list argp)
{
   int n, n_sz;

   if (ds && format) {
      va_list argp2;         /* Needed in case of looping on non-32bit arch */
      while (1) {
         va_copy(argp2, argp);
         n = vsnprintf(ds->str + ds->len, ds->sz - ds->len, format, argp2);
         va_end(argp2);
#if defined(__sgi)
         /* IRIX does not conform to C99; if the entire argument did not fit
          * into the buffer, n = buffer space used (minus 1 for terminator)
          */
         if (n > -1 && n + 1 < ds->sz - ds->len) {
            ds->len += n;      /* Success! */
            break;
         } else {
            n_sz = ds->sz * 2;
         }
#else
         if (n > -1 && n < ds->sz - ds->len) {
            ds->len += n;      /* Success! */
            break;
         } else if (n > -1) {  /* glibc >= 2.1 */
            n_sz = ds->len + n + 1;
         } else {              /* old glibc */
            n_sz = ds->sz * 2;
         }
#endif
         dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0);
      }
   }
}

/**
 * vsprintf-like function.
 */
void dStr_vsprintf (Dstr *ds, const char *format, va_list argp)
{
   if (ds) {
      dStr_truncate(ds, 0);
      dStr_vsprintfa(ds, format, argp);
   }
}

/**
 * Printf-like function
 */
void dStr_sprintf (Dstr *ds, const char *format, ...)
{
   va_list argp;

   if (ds && format) {
      va_start(argp, format);
      dStr_vsprintf(ds, format, argp);
      va_end(argp);
   }
}

/**
 * Printf-like function that appends.
 */
void dStr_sprintfa (Dstr *ds, const char *format, ...)
{
   va_list argp;

   if (ds && format) {
      va_start(argp, format);
      dStr_vsprintfa(ds, format, argp);
      va_end(argp);
   }
}

/**
 * Compare two dStrs.
 */
int dStr_cmp(Dstr *ds1, Dstr *ds2)
{
   int ret = 0;

   if (ds1 && ds2)
      ret = memcmp(ds1->str, ds2->str, MIN(ds1->len+1, ds2->len+1));
   return ret;
}

/**
 * Return a pointer to the first occurrence of needle in haystack.
 */
char *dStr_memmem(Dstr *haystack, Dstr *needle)
{
   int i;

   if (needle && haystack) {
      if (needle->len == 0)
         return haystack->str;

      for (i = 0; i <= (haystack->len - needle->len); i++) {
         if (haystack->str[i] == needle->str[0] &&
             !memcmp(haystack->str + i, needle->str, needle->len))
            return haystack->str + i;
      }
   }
   return NULL;
}

/**
 * Return a printable representation of the provided Dstr, limited to a length
 * of roughly maxlen.
 *
 * This is NOT threadsafe.
 */
const char *dStr_printable(Dstr *in, int maxlen)
{
   int i;
   static const char *const HEX = "0123456789ABCDEF";
   static Dstr *out = NULL;

   if (in == NULL)
      return NULL;

   if (out)
      dStr_truncate(out, 0);
   else
      out = dStr_sized_new(in->len);

   for (i = 0; (i < in->len) && (out->len < maxlen); ++i) {
      if (isprint(in->str[i]) || (in->str[i] == '\n')) {
         dStr_append_c(out, in->str[i]);
      } else {
         dStr_append_l(out, "\\x", 2);
         dStr_append_c(out, HEX[(in->str[i] >> 4) & 15]);
         dStr_append_c(out, HEX[in->str[i] & 15]);
      }
   }
   if (out->len >= maxlen)
      dStr_append(out, "...");
   return out->str;
}

/*
 *- dList ---------------------------------------------------------------------
 */

/**
 * Create a new empty list
 */
Dlist *dList_new(int size)
{
   Dlist *l;
   if (size <= 0)
      return NULL;

   l = dNew(Dlist, 1);
   l->len = 0;
   l->sz = size;
   l->list = dNew(void*, l->sz);
   return l;
}

/**
 * Free a list (not its elements)
 */
void dList_free (Dlist *lp)
{
   if (!lp)
      return;

   dFree(lp->list);
   dFree(lp);
}

/**
 * Insert an element at a given position [0 based]
 */
void dList_insert_pos (Dlist *lp, void *data, int pos0)
{
   int i;

   if (!lp || pos0 < 0 || pos0 > lp->len)
      return;

   if (lp->sz == lp->len) {
      lp->sz *= 2;
      lp->list = (void**) dRealloc (lp->list, lp->sz*sizeof(void*));
   }
   ++lp->len;

   for (i = lp->len - 1; i > pos0; --i)
      lp->list[i] = lp->list[i - 1];
   lp->list[pos0] = data;
}

/**
 * Append a data item to the list
 */
void dList_append (Dlist *lp, void *data)
{
   dList_insert_pos(lp, data, lp->len);
}

/**
 * Prepend a data item to the list
 */
void dList_prepend (Dlist *lp, void *data)
{
   dList_insert_pos(lp, data, 0);
}

/**
 * For completing the ADT.
 */
int dList_length (Dlist *lp)
{
   if (!lp)
      return 0;
   return lp->len;
}

/**
 * Remove a data item without preserving order.
 */
void dList_remove_fast (Dlist *lp, const void *data)
{
   int i;

   if (!lp)
      return;

   for (i = 0; i < lp->len; ++i) {
      if (lp->list[i] == data) {
         lp->list[i] = lp->list[--lp->len];
         break;
      }
   }
}

/*
 * Remove a data item preserving order.
 */
void dList_remove (Dlist *lp, const void *data)
{
   int i, j;

   if (!lp)
      return;

   for (i = 0; i < lp->len; ++i) {
      if (lp->list[i] == data) {
         --lp->len;
         for (j = i; j < lp->len; ++j)
            lp->list[j] = lp->list[j + 1];
         break;
      }
   }
}

/**
 * Return the nth data item,
 * NULL when not found or 'n0' is out of range
 */
void *dList_nth_data (Dlist *lp, int n0)
{
   if (!lp || n0 < 0 || n0 >= lp->len)
      return NULL;
   return lp->list[n0];
}

/**
 * Return the found data item, or NULL if not present.
 */
void *dList_find (Dlist *lp, const void *data)
{
   int i = dList_find_idx(lp, data);
   return (i >= 0) ? lp->list[i] : NULL;
}

/**
 * Search a data item.
 * Return value: its index if found, -1 if not present.
 * (this is useful for a list of integers, for finding number zero).
 */
int dList_find_idx (Dlist *lp, const void *data)
{
   int i, ret = -1;

   if (!lp)
      return ret;

   for (i = 0; i < lp->len; ++i) {
      if (lp->list[i] == data) {
         ret = i;
         break;
      }
   }
   return ret;
}

/**
 * Search a data item using a custom function.
 * func() is given the list item and the user data as parameters.
 * Return: data item when found, NULL otherwise.
 */
void *dList_find_custom (Dlist *lp, const void *data, dCompareFunc func)
{
   int i;
   void *ret = NULL;

   if (!lp)
      return ret;

   for (i = 0; i < lp->len; ++i) {
      if (func(lp->list[i], data) == 0) {
         ret = lp->list[i];
         break;
      }
   }
   return ret;
}

/**
 * QuickSort implementation.
 * This allows for a simple compare function for all the ADT.
 */
static void QuickSort(void **left, void **right, dCompareFunc compare)
{
  void **p = left, **q = right, **t = left;

  while (1) {
     while (p != t && compare(*p, *t) < 0)
        ++p;
     while (q != t && compare(*q, *t) > 0)
        --q;
     if (p > q)
        break;
     if (p < q) {
        void *tmp = *p;
        *p = *q;
        *q = tmp;
        if (t == p)
           t = q;
        else if (t == q)
           t = p;
     }
     if (++p > --q)
        break;
  }

  if (left < q)
     QuickSort(left, q, compare);
  if (p < right)
     QuickSort(p, right, compare);
}

/**
 * Sort the list using a custom function
 */
void dList_sort (Dlist *lp, dCompareFunc func)
{
   if (lp && lp->len > 1) {
      QuickSort(lp->list, lp->list + lp->len - 1, func);
   }
}

/**
 * Insert an element into a sorted list.
 * The comparison function receives two list elements.
 */
void dList_insert_sorted (Dlist *lp, void *data, dCompareFunc func)
{
   int i, st, min, max;

   if (lp) {
      min = st = i = 0;
      max = lp->len - 1;
      while (min <= max) {
         i = (min + max) / 2;
         st = func(lp->list[i], data);
         if (st < 0) {
            min = i + 1;
         } else if (st > 0) {
            max = i - 1;
         } else {
            break;
         }
      }
      dList_insert_pos(lp, data, (st >= 0) ? i : i+1);
   }
}

/**
 * Search a sorted list.
 * Return the found data item, or NULL if not present.
 * func() is given the list item and the user data as parameters.
 */
void *dList_find_sorted (Dlist *lp, const void *data, dCompareFunc func)
{
   int i, st, min, max;
   void *ret = NULL;

   if (lp && lp->len) {
      min = 0;
      max = lp->len - 1;
      while (min <= max) {
         i = (min + max) / 2;
         st = func(lp->list[i], data);
         if (st < 0) {
            min = i + 1;
         } else if (st > 0) {
            max = i - 1;
         } else {
            ret = lp->list[i];
            break;
         }
      }
   }

   return ret;
}

/*
 *- Parse function ------------------------------------------------------------
 */

/**
 * Take a dillo rc line and return 'name' and 'value' pointers to it.
 * Notes:
 *    - line is modified!
 *    - it skips blank lines and lines starting with '#'
 *
 * Return value: 1 on blank line or comment, 0 on successful value/pair,
 *              -1 otherwise.
 */
int dParser_parse_rc_line(char **line, char **name, char **value)
{
   char *eq, *p;
   int len, ret = -1;

   dReturn_val_if_fail(*line, ret);

   *name = NULL;
   *value = NULL;
   dStrstrip(*line);
   if (!*line[0] || *line[0] == '#') {
      /* blank line or comment */
      ret = 1;
   } else if ((eq = strchr(*line, '='))) {
      /* get name */
      for (p = *line; *p && *p != '=' && !dIsspace(*p); ++p);
      *p = 0;
      *name = *line;

      /* skip whitespace */
      if (p < eq)
         for (++p; dIsspace(*p); ++p);

      /* get value */
      if (p == eq) {
         for (++p; dIsspace(*p); ++p);
         len = strlen(p);
         if (len >= 2 && *p == '"' && p[len-1] == '"') {
            p[len-1] = 0;
            ++p;
         }
         *value = p;
         ret = 0;
      }
   }

   return ret;
}

/*
 *- Dlib messages -------------------------------------------------------------
 */
void dLib_show_messages(bool_t show)
{
   dLib_show_msg = show;
}

/*
 *- Misc utility functions ----------------------------------------------------
 */

/**
 * Return the current working directory in a new string
 */
char *dGetcwd (void)
{
  size_t size = 128;

  while (1) {
      char *buffer = dNew(char, size);
      if (getcwd (buffer, size) == buffer)
        return buffer;
      dFree (buffer);
      if (errno != ERANGE)
        return 0;
      size *= 2;
  }
}

/**
 * Return the home directory in a static string (don't free)
 */
char *dGethomedir (void)
{
   static char *homedir = NULL;

   if (!homedir) {
      if (getenv("HOME")) {
         homedir = dStrdup(getenv("HOME"));

      } else if (getenv("HOMEDRIVE") && getenv("HOMEPATH")) {
         homedir = dStrconcat(getenv("HOMEDRIVE"), getenv("HOMEPATH"), NULL);
      } else {
         DLIB_MSG("dGethomedir: $HOME not set, using '/'.\n");
         homedir = dStrdup("/");
      }
   }
   return homedir;
}

/**
 * Get a line from a FILE stream.
 * Return value: read line on success, NULL on EOF.
 */
char *dGetline (FILE *stream)
{
   int ch;
   Dstr *dstr;
   char *line;

   dReturn_val_if_fail (stream, 0);

   dstr = dStr_sized_new(64);
   while ((ch = fgetc(stream)) != EOF) {
      dStr_append_c(dstr, ch);
      if (ch == '\n')
         break;
   }

   line = (dstr->len) ? dstr->str : NULL;
   dStr_free(dstr, (line) ? 0 : 1);
   return line;
}

/**
 * Close a FD handling EINTR.
 */
int dClose(int fd)
{
   int st;

   do
      st = close(fd);
   while (st == -1 && errno == EINTR);
   return st;
}

/**
 * Portable usleep() function.
 *
 * The usleep() function is deprecated in POSIX.1-2001 and removed in
 * POSIX.1-2008, see usleep(3).
 */
int dUsleep(unsigned long usec)
{
	struct timespec ts;
	int res;

	ts.tv_sec = usec / 1000000UL;
	ts.tv_nsec = (usec % 1000000UL) * 1000UL;

	do {
		res = nanosleep(&ts, &ts);
	} while (res && errno == EINTR);

	return res;
}
