/****************************************************************************
 * util.[ch]: Miscellaneous generic utility functions, defs, and macros.
 *
 * If you want the error handlers to mention program name, call set_prog_name
 * at the beginning of your program.
 ****************************************************************************/

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

#include "util.h"

static const char*  prog = "";

/* fgets with dynamic allocation, returning the allocated string, or NULL on
 * eof or error. Any trailing newline or crlf will be stripped.
 */
char* dgets(FILE* f)
{
    char*    str  = NULL;
    int32    len  = 0;
    int32    old_len;
    boolean  done = FALSE;
    char     chunk[128];
    int32    chunk_len;

    while (!done) {
        if (!fgets(chunk, 128, f)) {
            if (ferror(f)) {
                free(str);
                return NULL;
            } else
                return str;
        }
        chunk_len = strlen(chunk);
        if (chunk[chunk_len-1] == '\n') {
            chunk[--chunk_len] = '\0';
            done = TRUE;
        }
        old_len = len;
        len += chunk_len;
        str = safe_realloc(str, (len+1) * sizeof(char));
        strcpy(str + old_len, chunk);
    }
    if (len > 0 && str[len-1] == '\r')
        str[len-1] = '\0';

    return str;
}

/* sprintf with dynamic allocation, returning the allocated string.
 */
char* dsprintf(const char* format, ...)
{
    va_list  args;
    char*    str    = NULL;

    va_start(args, format);
    str = vdsprintf(format, args);
    va_end(args);
    return str;
}

/* Append src to the dynamically allocated string dest, reallocing to make room. Return the
 * new dest.
 */
char* dstrcat(char** dest, const char* src)
{
    *dest = safe_realloc(*dest, (strlen(*dest) + strlen(src) + 1) * sizeof(char));
    strcat(*dest, src);
    return *dest;
}

/* Return true if the given string is composed entirely of whitespace, or is the empty string.
 */
boolean is_blank(const char* str)
{
    const char*  p;

    for (p=str; *p; p++) {
        if (!isspace(*p))
            return FALSE;
    }
    return TRUE;
}

/* Return true if the given string is composed entirely of hexadecimal digits (upper or lowercase).
 * An empty string is considered non-hexadecimal.
 */
boolean is_hexadecimal(const char* str)
{
    const char*  p;

    if (!(*str))
        return FALSE;
    for (p=str; *p; p++) {
        if (!isxdigit(*p))
            return FALSE;
    }
    return TRUE;
}

/* Return true if the given string is composed entirely of numeric digits. An empty string is
 * considered non-numeric.
 */
boolean is_numeric(const char* str)
{
    const char*  p;

    if (!(*str))
        return FALSE;
    for (p=str; *p; p++) {
        if (!isdigit(*p))
            return FALSE;
    }
    return TRUE;
}

/* Count and return the number of decimal digits in the given positive number.
 */
int32 num_digits(uint32 num)
{
    int32  count = 1;

    while (num >= 10) {
        num /= 10;
        count++;
    }

    return count;
}

/* Calloc, aborting if out of memory
 */
void* safe_calloc(size_t count, size_t element_size)
{
    void*  mem;

    mem = calloc(count, element_size);
    if (!mem && count > 0)
        error_abort("out of memory!");

    return mem;
}

/* Malloc, aborting if out of memory
 */
void* safe_malloc(size_t len)
{
    void*  mem;

    mem = malloc(len);
    if (!mem && len > 0)
        error_abort("out of memory!");

    return mem;
}

/* Realloc, aborting if out of memory
 */
void* safe_realloc(void* mem, size_t len)
{
    mem = realloc(mem, len);
    if (!mem && len > 0)
        error_abort("out of memory!");

    return mem;
}

/* Strdup, aborting if out of memory
 */
char* safe_strdup(const char* str)
{
  char*  new_str;

  new_str = safe_malloc((strlen(str)+1) * sizeof(char));
  strcpy(new_str, str);
  return new_str;
}

/* Remove whitespace from the beginning and end of str
 */
void trim_whitespace(char* str)
{
    char*  c;
    int32  i;

    for (c=str; *c && isspace(*c); c++) {}
    memmove(str, c, strlen(c)+1);
    for (i=strlen(str)-1; i >= 0 && isspace(str[i]); i--) {}
    str[i+1] = '\0';
}

/* vsprintf with dynamic allocation, returning the allocated string.
 */
char* vdsprintf(const char* format, va_list args)
{
    char*    str    = NULL;
    int32    buflen = 128;
    int32    nchars;

    while (1) {
        str = safe_realloc(str, buflen * sizeof(char));
        nchars = vsnprintf(str, buflen, format, args);
        if (nchars < 0)             /* pre-glibc 2.1 method */
            buflen *= 2;
        else if (nchars >= buflen)  /* post-glibc 2.1 method */
            buflen = nchars + 1;
        else
            break;
    }

    return str;
}

/* Error Handlers */

/* Print a formatted warning to stderr
 */
void warn(const char* format, ...)
{
    va_list  args;

    fprintf(stderr, "%s warning: ", prog);
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, "\n");
}

/* Abort with a formatted error message
 */
void error_abort(const char* format, ...)
{
    va_list  args;

    fprintf(stderr, "%s dying: ", prog);
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, "\n");

    exit(-1);
}

/* Print a formatted warning to stderr, including the system error string
 */
void sys_warn(const char *format, ...)
{
    va_list  args;

    fprintf(stderr, "%s warning: ", prog);
    va_start(args, format);
    vfprintf(stderr, format, args);
    fprintf(stderr, " (%s)\n", strerror(errno));
    va_end(args);
}

/* Abort with a formatted error message, including the system error string
 */
void sys_error_abort(const char* format, ...)
{
    va_list  args;

    fprintf(stderr, "%s dying: ", prog);
    va_start(args, format);
    vfprintf(stderr, format, args);
    fprintf(stderr, " (%s)\n", strerror(errno));
    va_end(args);

    exit(-1);
}

/* Set the program name. Call this at the beginning of your program if you
 * want the error handlers to mention program name.
 */
void set_prog_name(const char* name)
{
    prog = name;
}
