/* util.c: General utilities. */

#define _POSIX_SOURCE

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <fcntl.h>
#include "x.tab.h"
#include "util.h"
#include "cmstring.h"
#include "data.h"
#include "config.h"
#include "ident.h"
#include "token.h"
#include "memory.h"
#include "log.h"

#define FORMAT_BUF_INITIAL_LENGTH 48
#define MAX_SCRATCH 2

/* crypt() is not POSIX. */
extern char *crypt();

char lowercase[128];
char uppercase[128];
static int reserve_fds[MAX_SCRATCH];
static int fds_used;

static void claim_fd(int i);

void init_util(void)
{
    int i;

    for (i = 0; i < 128; i++) {
	lowercase[i] = (isupper(i) ? tolower(i) : i);
	uppercase[i] = (islower(i) ? toupper(i) : i);
    }
    srand(time(NULL) + getpid());
}

unsigned long hash(char *s)
{
    unsigned long hashval = 0, g;

    /* Algorithm by Peter J. Weinberger. */
    for (; *s; s++) {
	hashval = (hashval << 4) + *s;
	g = hashval & 0xf0000000;
	if (g) {
	    hashval ^= g >> 24;
	    hashval ^= g;
	}
    }
    return hashval;
}

unsigned long hash_case(char *s, int n)
{
    unsigned long hashval = 0, g;
    int i;

    /* Algorithm by Peter J. Weinberger. */
    for (i = 0; i < n; i++) {
	hashval = (hashval << 4) + (s[i] & 0x5f);
	g = hashval & 0xf0000000;
	if (g) {
	    hashval ^= g >> 24;
	    hashval ^= g;
	}
    }
    return hashval;
}

long atoln(char *s, int n)
{
    long val = 0;

    while (n-- && isdigit(*s))
	val = val * 10 + *s++ - '0';
    return val;
}

char *long_to_ascii(long num, Number_buf nbuf)
{
    char *p = &nbuf[NUMBER_BUF_SIZE - 1];
    int sign = 0;

    *p-- = 0;
    if (num < 0) {
	sign = 1;
	num = -num;
    } else if (!num) {
	*p-- = '0';
    }
    while (num) {
	*p-- = num % 10 + '0';
	num /= 10;
    }
    if (sign)
	*p-- = '-';
    return p + 1;
}

/* Compare two strings, ignoring case. */
int strccmp(char *s1, char *s2)
{
    while (*s1 && LCASE(*s1) == LCASE(*s2))
	s1++, s2++;
    return LCASE(*s1) - LCASE(*s2);
}

/* Compare two strings up to n characters, ignoring case. */
int strnccmp(char *s1, char *s2, int n)
{
    while (n-- && *s1 && LCASE(*s1) == LCASE(*s2))
	s1++, s2++;
    return (n >= 0) ? LCASE(*s1) - LCASE(*s2) : 0;
}

/* Look for c in s, ignoring case. */
char *strcchr(char *s, int c)
{
    for (; *s; s++) {
	if (LCASE(*s) == c)
	    return s;
    }
    return (c) ? NULL : s;
}

char *strcstr(char *s, char *search)
{
    char *p;
    int search_len = strlen(search);

    for (p = strcchr(s, *search); p; p = strcchr(p + 1, *search)) {
	if (strnccmp(p, search, search_len) == 0)
	    return p;
    }

    return NULL;
}

/* A random number generator.  A lot of Unix rand() implementations don't
 * produce very random low bits, so we shift by eight bits if we can do that
 * without truncating the range. */
long random_number(long n)
{
    long num = rand();
    if (!n)
      return 0;

#ifdef SUNOS
    if (256           >= n)
#else
    if (RAND_MAX >> 8 >= n)
#endif
	num >>= 8;
    return num % n;
}

/* Encrypt a string.  The salt can be NULL. */
char *crypt_string(char *key, char *salt)
{
    char rsalt[2];

    if (!salt) {
	rsalt[0] = random_number(95) + 32;
	rsalt[1] = random_number(95) + 32;
	salt = rsalt;
    }

    return crypt(key, salt);
}

/* Result must be copied before it can be re-used.  Non-reentrant. */
String *vformat(char *fmt, va_list arg)
{
    String *buf, *str;
    char *p, *s;
    Number_buf nbuf;

    buf = string_new(0);

    while (1) {

	/* Find % or end of string. */
	p = strchr(fmt, '%');
	if (!p || !p[1]) {
	    /* No more percents; copy rest and stop. */
	    buf = string_add_chars(buf, fmt, strlen(fmt));
	    break;
	}
	buf = string_add_chars(buf, fmt, p - fmt);

	switch (p[1]) {

	  case '%':
	    buf = string_addc(buf, '%');
	    break;

	  case 's':
	    s = va_arg(arg, char *);
	    buf = string_add_chars(buf, s, strlen(s));
	    break;

	  case 'S':
	    str = va_arg(arg, String *);
	    buf = string_add(buf, str);
	    break;

	  case 'd':
	    s = long_to_ascii(va_arg(arg, int), nbuf);
	    buf = string_add_chars(buf, s, strlen(s));
	    break;

	  case 'l':
	    s = long_to_ascii(va_arg(arg, long), nbuf);
	    buf = string_add_chars(buf, s, strlen(s));
	    break;

	  case 'D':
	    str = data_to_literal(va_arg(arg, Data *));
	    if (string_length(str) > MAX_DATA_DISPLAY) {
		str = string_truncate(str, MAX_DATA_DISPLAY - 3);
		str = string_add_chars(str, "...", 3);
	    }
	    buf = string_add_chars(buf, string_chars(str), string_length(str));
	    string_discard(str);
	    break;

	  case 'I':
	    s = ident_name(va_arg(arg, long));
	    if (is_valid_ident(s))
		buf = string_add_chars(buf, s, strlen(s));
	    else
		buf = string_add_unparsed(buf, s, strlen(s));
	    break;
	}

	fmt = p + 2;
    }

    va_end(arg);
    return buf;
}

String *format(char *fmt, ...)
{
    va_list arg;
    String *str;

    va_start(arg, fmt);
    str = vformat(fmt, arg);
    va_end(arg);
    return str;
}

void fformat(FILE *fp, char *fmt, ...)
{
    va_list arg;
    String *str;

    va_start(arg, fmt);

    str = vformat(fmt, arg);
    fputs(string_chars(str), fp);
    string_discard(str);

    va_end(arg);
}

String *fgetstring(FILE *fp)
{
    String *line;
    int len;
    char buf[1000];

    line = string_new(0);
    while (fgets(buf, 1000, fp)) {
	len = strlen(buf);
	if (buf[len - 1] == '\n') {
	    line = string_add_chars(line, buf, len-1);
	    return line;
	} else
	    line = string_add_chars(line, buf, len);
    }

    if (line->len) {
	return line;
    } else {
	string_discard(line);
	return NULL;
    }
}

char *english_type(int type)
{
    switch (type) {
      case INTEGER:	return "an integer";
      case STRING:	return "a string";
      case DBREF:	return "a dbref";
      case LIST:	return "a list";
      case SYMBOL:	return "a symbol";
      case ERROR:	return "an error";
      case FROB:	return "a frob";
      case DICT:	return "a dictionary";
      case BUFFER:	return "a buffer";
      default:		return "a mistake";
    }
}

char *english_integer(int n, Number_buf nbuf)
{
    static char *first_eleven[] = {
	"no", "one", "two", "three", "four", "five", "six", "seven",
	"eight", "nine", "ten" };

    if (n <= 10)
	return first_eleven[n];
    else
	return long_to_ascii(n, nbuf);
}

long parse_ident(char **sptr)
{
    String *str;
    char *s = *sptr;
    long id;

    if (*s == '"') {
	str = string_parse(&s);
    } else {
	while (isalnum(*s) || *s == '_')
	    s++;
	str = string_from_chars(*sptr, s - *sptr);
    }

    id = ident_get(string_chars(str));
    string_discard(str);
    *sptr = s;
    return id;
}

FILE *open_scratch_file(char *name, char *type)
{
    FILE *fp;

    if (fds_used == MAX_SCRATCH)
	return NULL;

    close(reserve_fds[fds_used++]);
    fp = fopen(name, type);
    if (!fp) {
	claim_fd(--fds_used);
	return NULL;
    }
    return fp;
}

void close_scratch_file(FILE *fp)
{
    fclose(fp);
    claim_fd(--fds_used);
}

void init_scratch_file(void)
{
    int i;

    for (i = 0; i < MAX_SCRATCH; i++)
	claim_fd(i);
}

static void claim_fd(int i)
{
    reserve_fds[i] = open("/dev/null", O_WRONLY);
    if (reserve_fds[i] == -1)
	panic("Couldn't reset reserved fd.");
}