/* stringtools.c - convenient string utility functions
   Copyright (C) 1996-2000 Paul Sheer

   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 2 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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */


#include <config.h>
#include "global.h"
#include <stdio.h>
#include <stdlib.h>
#include "my_string.h"
#include <stdarg.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include "stringtools.h"
#include "regex.h"
#include "time.h"

#include "mad.h"

/*
   This cats a whole lot of strings together.
   It has the advantage that the return result will
   be free'd automatically, and MUST NOT be free'd
   by the caller.
   It will hold the most recent NUM_STORED strings.
 */
#define NUM_STORED 256

static char *stacked[NUM_STORED] =
{0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0};

char *catstrs (const char *first,...)
{
    static int i = 0;
    va_list ap;
    int len;
    char *data;

    if (!first)
	return 0;

    len = strlen (first);
    va_start (ap, first);

    while ((data = va_arg (ap, char *)) != 0)
	 len += strlen (data);

    len++;

    i = (i + 1) % NUM_STORED;
    if (stacked[i])
	free (stacked[i]);

    stacked[i] = malloc (len);
    va_end (ap);
    va_start (ap, first);
    strcpy (stacked[i], first);
    while ((data = va_arg (ap, char *)) != 0)
	 strcat (stacked[i], data);
    va_end (ap);

    return stacked[i];
}

void catstrs_clean (void)
{
    int i;
    for (i = 0; i < NUM_STORED; i++)
	if (stacked[i]) {
	    free (stacked[i]);
	    stacked[i] = 0;
	}
}

char *space_string (const char *s)
{
    char *r, *p;
    int i;
    if (!s)
	return 0;
    p = r = malloc (strlen (s) + 3);
    while (*s == ' ')
	s++;
    *r++ = ' ';
    while (*s) {
	if (*s != '&')
	    *r++ = *s;
	s++;
    }
    *r = '\0';
    for (i = strlen (p) - 1; i > 0; i--) {
	if (p[i] == ' ')
	    p[i] = '\0';
	else
	    break;
    }
    r = p + strlen (p);
    *r++ = ' ';
    *r = '\0';
    return p;
}

/* alternative to free() */
void destroy (void **p)
{
    if (*p) {
	free (*p);
	*p = 0;
    }
}

char *strcasechr (const char *p, int c)
{
    unsigned char *s = (unsigned char *) p;
    for (; my_lower_case ((int) *s) != my_lower_case ((int) c); ++s)
	if (*s == '\0')
	    return 0;
    return (char *) s;
}

char *itoa (int i)
{
    static char t[20];
    char *s = t + 19;
    int j = i;
    i = abs (i);
    *s-- = 0;
    do {
	*s-- = i % 10 + '0';
    } while ((i = i / 10));
    if (j < 0)
	*s-- = '-';
    return ++s;
}

/* this comes from the Midnight Commander src/tools.c */
char *get_current_wd (char *buffer, int size)
{
    char *p;

#ifdef HAVE_GETCWD
    p = getcwd (buffer, size - 1);
#else
    p = (char *) getwd (buffer);
#endif
    p[size - 1] = '\0';
    return p;
}

/*
   cd's to path and sets current_dir variable if getcwd works, else set
   current_dir to "".
 */
extern char current_dir[];
int change_directory (const char *path)
{
    int e;
    e = chdir (path);
    if (e < 0)
	return e;
    if (!get_current_wd (current_dir, MAX_PATH_LEN))
	strcpy (current_dir, "/");
    return 0;
}


short *shortset (short *s, int c, size_t n)
{
    short *r = s;
    while (n--)
	*s++ = c;
    return r;
}

char *name_trunc (const char *txt, int trunc_len)
{
    static char x[1024];
    int txt_len, y;

    txt_len = strlen (txt);
    if (txt_len <= trunc_len) {
	strcpy (x, txt);
	return x;
    }
    y = trunc_len % 2;
    strncpy (x, txt, (trunc_len / 2) + y);
    strncpy (x + (trunc_len / 2) + y, txt + txt_len - (trunc_len / 2), trunc_len / 2);
    x[(trunc_len / 2) + y] = '~';
    x[trunc_len] = 0;
    return x;
}

int prop_font_strcolmove (unsigned char *str, int i, int column);

int strcolmove (unsigned char *str, int i, int column)
{
    return prop_font_strcolmove (str, i, column);
}

/*move to col character from beginning of line with i in the line somewhere. */
/*If col is past the end of the line, it returns position of end of line */
long strfrombeginline (const char *s, int i, int col)
{
    unsigned char *str = (unsigned char *) s;
    if (i < 0) {
/* NLS ? */
	fprintf (stderr, "strfrombeginline called with negative index.\n");
	exit (1);
    }
    while (i--)
	if (str[i] == '\n') {
	    i++;
	    break;
	}
    if (i < 0)
	i = 0;
    if (!col)
	return i;
    return strcolmove (str, i, col);
}

/*
   strip backspaces from the nroff file to produce normal text.
   returns strlen(result) if l is non null
 */
char *str_strip_nroff (char *t, int *l)
{
    unsigned char *s = (unsigned char *) t;
    unsigned char *r, *q;
    int p;

    q = r = malloc (strlen (t) + 2);
    if (!r)
	return 0;

    for (p = 0; s[p]; p++) {
	while (s[p + 1] == '\b' && isprint (s[p + 2]) && isprint (s[p]))
	    p += 2;
	*q++ = s[p];
    }
    *q = 0;
    if (l)
	*l = ((unsigned long) q - (unsigned long) r);
    return (char *) r;
}

long countlinesforward (const char *text, long from, long amount, long lines, int width)
{
    if (amount) {
	int i = 0;
	amount += from;
	for (;;) {
	    from = strcolmove ((unsigned char *) text, from, width);
	    if (from >= amount || !text[from])
		return i;
	    i++;
	    from++;
	}
    } else if (lines) {
	int i;
	for (i = 0; i < lines; i++) {
	    int q;
	    q = strcolmove ((unsigned char *) text, from, width);
	    if (!text[q])
		break;
	    from = q + 1;
	}
	return from;
    }
    return 0;
}

/* returns pos of begin of line moved to */
/* move forward from i, `lines' can be negative --- moveing backward */
long strmovelines (const char *str, long from, long lines, int width)
{
    int p, q;
    if (lines > 0)
	return countlinesforward (str, from, 0, lines, width);
    if (lines == 0)
	return from;
    else {
	int line = 0;
	p = from;
	for (; p > 0;) {
	    q = p;
	    p = strfrombeginline (str, q - 1, 0);
	    line += countlinesforward (str, p, q - p, 0, width);
	    if (line > -lines)
		return countlinesforward (str, p, 0, line + lines, width);
	    if (line == -lines)
		return p;
	}
	return 0;
    }
}



/*returns a positive or negative count of lines */
long strcountlines (const char *str, long i, long amount, int width)
{
    int lines, p;
    if (amount > 0) {
	return countlinesforward (str, i, amount, 0, width);
    }
    if (amount == 0)
	return 0;
    if (i + amount < 0)
	amount = -i;
    p = strfrombeginline (str, i + amount, 0);
    lines = countlinesforward (str, p, i + amount - p, 0, width);
    return -countlinesforward (str, p, i - p, 0, width) + lines;
}

/*
   returns a null terminated string. The string
   is a copy of the line beginning at p and ending at '\n' 
   in the string src.
   The result must not be free'd. This routine caches the last
   four results.
 */
char *strline (const char *src, int p)
{
    static char line[4][1024];
    static int last = 0;
    int i = 0;
    char *r;
    while (src[p] != '\n' && src[p] && i < 1000) {
	i++;
	p++;
    }
    r = line[last & 3];
    memcpy (r, src + p - i, i);
    r[i] = 0;
    last++;
    return r;
}

size_t strnlen (const char *s, size_t count)
{
    const char *sc;

    for (sc = s; count-- && *sc != '\0'; ++sc)
	/* nothing */ ;
    return sc - s;
}

#ifdef CRASHES_ON_STARTUP

size_t vfmtlen (const char *fmt, va_list ap)
{
    char *p, *s;
    size_t i;
    p = malloc (8192);
    vsprintf (p, fmt, ap);
    i = strlen (p);
    free (p);
    return i;
}

#else

#define is_digit(x) ((x) >= '0' && (x) <= '9')

#define scount(v) { \
		*p1++ = *p++; \
		*p1++ = '%'; \
		*p1++ = 'n'; \
		*p1 = 0; \
		sprintf(s,q1,v,&n); \
		count += n; \
	    }


/* Returns the length of a string that would be printed if this
   command was vprintf, but prints nothing */
size_t vfmtlen (const char *fmt, va_list ap)
{
    char *q, *p, s[66];
    int n;
    char q1[32];
    char *p1;
    size_t count = 0;
    int min, max;

    p = q = (char *) fmt;

    while ((p = strchr (p, '%'))) {
	count += (size_t) ((unsigned long) p - (unsigned long) q);
	q = p;
	p1 = q1;
	*p1++ = *p++;
	if (*p == '%') {
	    p++;
	    count++;
	    q = p;
	    continue;
	}
	if (*p == 'n') {
	    p++;
	    q = p;
	    *va_arg (ap, int *) = count;
	    continue;
	}
	if (*p == '#')
	    *p1++ = *p++;
	if (*p == '0')
	    *p1++ = *p++;
	if (*p == '-')
	    *p1++ = *p++;
	if (*p == '+')
	    *p1++ = *p++;
	min = 0;
	max = (1 << 30);
	if (*p == '*') {
	    p++;
	    strcpy (p1, itoa (min = va_arg (ap, int)));
	    p1 += strlen (p1);
	} else {
	    char *g = p1;
	    while (is_digit (*p))
		*p1++ = *p++;
	    *p1 = 0;
	    if (*g)
		min = atoi (g);
	}
	if (*p == '.')
	    *p1++ = *p++;
	if (*p == '*') {
	    p++;
	    strcpy (p1, itoa (max = va_arg (ap, int)));
	    p1 += strlen (p1);
	} else {
	    char *g = p1;
	    while (is_digit (*p))
		*p1++ = *p++;
	    *p1 = 0;
	    if (*g)
		max = atoi (g);
	}
	if (*p == 's') {
	    if ((n = strnlen (va_arg (ap, char *), max)) < min)
		n = min;
	    count += n;
	    p++;
	} else if (*p == 'h') {
	    if (strchr ("diouxX", *p))
#if 0        /* this is not allowed by ANSI */
		scount (va_arg (ap, short));
#else
		scount (va_arg (ap, int));
#endif
	} else if (*p == 'l') {
	    *p1++ = *p++;
	    if (strchr ("diouxX", *p))
		scount (va_arg (ap, long));
	} else if (strchr ("cdiouxX", *p)) {
	    scount (va_arg (ap, int));
	} else if (*p == 'L') {
	    *p1++ = *p++;
	    if (strchr ("EefgG", *p))
		scount (va_arg (ap, double));	/* should be long double, but gives warnings on some machines */
	} else if (strchr ("EefgG", *p)) {
	    scount (va_arg (ap, double));
	} else if (strchr ("DOU", *p)) {
	    scount (va_arg (ap, long));
	} else if (*p == 'p') {
	    scount (va_arg (ap, void *));
	}
	q = p;
    }
    return count + strlen (q);
}

#endif				/* !CRASHES_ON_STARTUP */

/* #define DEBUG_VFMTLEN */
/* vsprintf with memory allocation. result must be free'd */
#ifdef HAVE_MAD
char *mad_vsprintf_alloc (const char *fmt, va_list ap, char *file, int line)
#else
char *vsprintf_alloc (const char *fmt, va_list ap)
#endif
{
    char *s;
    size_t l;

#ifdef __va_copy
    va_list va_save;
    __va_copy (va_save, ap);
#else
#define va_save ap
#endif /* defined __va_copy */

#ifdef HAVE_MAD
    s = mad_alloc ((l = vfmtlen (fmt, ap)) + 1, file, line);
#else
    s = malloc ((l = vfmtlen (fmt, ap)) + 1);
#endif
    if (!s)
/* NLS ? */
	fprintf (stderr, "cooledit:%s:%d: malloc return zero\n", __FILE__, __LINE__);
    s[l] = 0;
    vsprintf (s, fmt, va_save);
    if (s[l]
#ifdef DEBUG_VFMTLEN
     || strlen (s) != l
#endif
    )
/* this is just in case there is a bug in vfmtlen above (it also
    happens if you pass a incorrect format string) */
/* NLS ? */
	fprintf (stderr, "cooledit:%s:%d: vsprintf wrote out of bounds\n", __FILE__, __LINE__);
    return s;
}

char *sprintf_alloc (const char *fmt,...)
{
    char *s;
    va_list ap;
    va_start (ap, fmt);
    s = vsprintf_alloc (fmt, ap);
    va_end (ap);
    return s;
}

int readall (int fd, char *buf, int len)
{
    int count;
    int total = 0;
    if (len <= 0)
	return 0;
    for (;;) {
	count = read (fd, buf, len);
	if (count == -1) {
	    if (errno == EINTR || errno == EAGAIN)
		continue;
	    return -1;
	}
	if (!count)
	    return -1;
	buf += count;
	len -= count;
	total += count;
	if (len <= 0)
	    break;
    }
    return total;
}

int writeall (int fd, char *buf, int len)
{
    int count;
    int total = 0;
    if (len <= 0)
	return 0;
    for (;;) {
	count = write (fd, buf, len);
	if (count == -1) {
	    if (errno == EINTR || errno == EAGAIN)
		continue;
	    return -1;
	}
	if (!count)
	    return -1;
	buf += count;
	len -= count;
	total += count;
	if (len <= 0)
	    break;
    }
    return total;
}

