/* @(#)str.c 1.8 95/09/16 */

/*
 * Copyright (c) 1994, 1995 by Wayne C. Gramlich.  All rights reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * for any purpose is hereby granted without fee provided that the above
 * copyright notice and this permission are retained.  The author makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

/* LINTLIBRARY */

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

#include "assert.h"
#include "chr_extern.h"
#include "memory_extern.h"
#include "str_extern.h"

/*
 * str_allocate(size)
 *	This routine will allocate and return a string that contain
 *	up to {size} characters.
 */
Str
str_allocate(
    unsigned size)
{
    Str str;

    str = (Str)memory_allocate(size + 1);
    str[0] = '\0';
    str[size] = '\0';
    return str;
}

/*
 * str_break(str, chrs)
 *	This routine will find the first occurrance of any character in
 *	{chrs} in {str} and return a pointer to it; otherwise, (Str)0
 *	is returned.
 */
Str
str_break(
    Str str,
    Str chrs)
{
    return (Str)strpbrk((const char *)str, (const char *)chrs);
}

/*
 * str_case_equal(str1, str2)
 *	This routine will return 1 if {str1} is equal to {str2} insensitive
 *	to case and 0 otherwise.
 */
int
str_case_equal(
    Str str1,
    Str str2)
{
    return (strcasecmp((char *)str1, (char *)str2) == 0) ? 1 : 0;
}

/*
 * str_case_equal_max(str1, str2, limit)
 *	This routine will return 1 if the first {limit} characters of
 *	{str1} is equal to {str2} insensitive to case and 0 otherwise.
 */
int
str_case_equal_max(
    Str str1,
    Str str2,
    unsigned limit)
{
    return (strncasecmp((char *)str1, (char *)str2, limit) == 0) ? 1 : 0;
}

/*
 * str_chr_reverse_search(str, chr)
 *	This routine will search backwards for the first occurrance of
 *	{chr} in {str} and return a pointer to it.  If {chr} is not found,
 *	(Chr *)0 is returned.
 */
Chr *
str_chr_reverse_search(
    Str str,
    Chr chr)
{
    Str result = (Str)strrchr((const char *)str, chr);
    return result;
}

/*
 * str_chr_search(str, chr)
 *	This routine will search for the first occurrance of {chr} in
 *	{str} and return a pointer to it.  If {chr} is not found,
 *	(Chr *)0 is returned.
 */
Chr *
str_chr_search(
    Str str,
    Chr chr)
{
    Str result = (Str)strchr((const char *)str, chr);
    return result;
}

/*
 * str_copy(str)
 *	This routine will create and return a copy of {str}.
 */
Str
str_copy(
    Str str)
{
    Str result = str_allocate(str_size(str));
    (void)strcpy((char *)result, (const char *)str);
    return result;
}

/*
 * str_copy_max(str, count)
 *	This routine will allocate and return a string that contains
 *	the first {count} characters from {str}.
 */
Str
str_copy_max(
    Str str,
    unsigned count)
{
    Str result = str_allocate(count + 1);
    (void)strncpy((char *)result, (const char *)str, count);
    result[count] = '\0';
    return result;
}

/*
 * str_create_empty()
 *	This routine will allocate and return an empty string.
 */
Str
str_create_empty(void)
{
    return str_copy((Str)"");
}

/*
 * str_encode(str)
 *	This routine will return a newly allocated string whose contents
 *	consists of {str} where all non-alpha numeric characters have
 *	been converted into `:xx', where `xx' are two hexadecimal digits.
 *
 *	The choice of ':' as a prefix was based on the desire to use
 *	a character that can show up in file names and not be confused
 *	with a shell character.
 */
Str
str_encode(
    Str str)
{
    unsigned index;
    Chr chr;
    Chr *ptr;
    Str result;
    unsigned result_size;
    unsigned size;

    /* Figure out the size of the resulting string: */
    result_size = 0;
    size = str_size(str);
    for (index = 0; index < size; index++) {
	if (chr_is_name(str[index])) {
	    result_size += 1;
	} else {
	    result_size += 3;
	}
    }

    /* Copy the string: */
    result = str_allocate(result_size);
    ptr = result;
    for (index = 0; index < size; index++) {
	chr = str[index];
	if (chr_is_name(chr)) {
	    *ptr++ = chr;
	} else {
	    *ptr++ = ':';
	    *ptr++ = "0123456789abcdef"[chr >> 4];
	    *ptr++ = "0123456789abcdef"[chr & 0xf];
	}
    }
    *ptr = '\0';
    return result;
}

/*
 * str_equal(str1, str2)
 *	This routine will return 1 if {str1} is equal to {str2} and
 *	0 otherwise.
 */
int
str_equal(
    Str str1,
    Str str2)
{
    return (strcmp((char *)str1, (char *)str2) == 0) ? 1 : 0;
}

/*
 * str_equal_max(str1, str2, limit)
 *	This routine will return 1 if the first {limit} characters of
 *	{str1} is equal to {str2} and 0 otherwise.
 */
int
str_equal_max(
    Str str1,
    Str str2,
    unsigned limit)
{
    return (strncmp((char *)str1, (char *)str2, limit) == 0) ? 1 : 0;
}

/*
 * str_free(str)
 *	This routine will release the storage associated with {str}.
 */
void
str_free(
    Str str)
{
    memory_free((void *)str);
}

/*
 * str_is_empty(str)
 *	This routine will return 1 if {str} is empty and 0 otherwise.
 */
int
str_is_empty(
    Str str)
{
    return (str[0] == '\0');
}

/*
 * str_parse_decimal(str)
 *	This routine will treat {str} as a string containing
 *	a decimal number and return the resulting number.
 */
int
str_parse_decimal(
    Str str)
{
    int number;

    number = atoi((char *)str);
    return number;
}

/*PRINTFLIKE1*/
/*
 * str_printf(format, arg1, ..., argN)
 *	This routine will print {arg1} through {argN} into a newly
 *	allocated {Str} object using {format} and return it.
 */
Str
str_printf(
    char *format,
    ...)
{
    va_list args;
    static FILE *null_file = (FILE *)0;
    unsigned size;
    Str str;
    int zilch;

    /*
     * The usual hack is to have some gigantic fixed size buffer
     * and vsprintf() the string into it and hope that it all fits.
     * This strategy fails every once in a while as somebody
     * throws something really big at the fixed size buffer.
     *
     * Instead, we will carefully compute the required buffer
     * size, then vsprintf() into it.  This is a little slower
     * since we have to do the formatting operations twice --
     * once to compute the size and once to fill the buffer.
     * However, it is totally safe, since we will never blow
     * out a fixed sized buffer.
     *
     * We first need to compute the size of the final output string.
     * This is done by first formatting the string out to the null
     * file using vfprintf() and getting the size.  Usually the string
     * will be sufficiently small that it will not overflow the
     * buffer, so no actual I/O to /dev/null actually occurs.
     * We always fseek() back to the beginning to try to
     * prevent any I/O from occuring.  For really big strings
     * I/O system calls to /dev/null will occur, but we can
     * live with that.
     */

#if defined(lint)
    /* Lint notices that {zilch} is unused.  The following shuts lint up: */
    zilch = 1;
    zilch = zilch;
#endif /* defined(lint) */

    if (null_file == (FILE *)0) {
	/*
	 * We open /dev/null once at the beginning and leave it open.
	 * This is technically a file descriptor leak.
	 */
	null_file = fopen("/dev/null", "w");
	assert(null_file != (FILE *)0);
    }
    (void)fseek(null_file, 0L, SEEK_SET);
    va_start(args, zilch);
    size = vfprintf(null_file, (char *)format, args);
    va_end(args);

    /*
     * Now that we have the size, we can format the output
     * into the string buffer.
     */
    str = str_allocate(size);
    va_start(args, zilch);
    assert(vsprintf((char *)str, (char *)format, args) == size);
    va_end(args);
    return str;
}

/*
 * str_restore(in_file)
 *	This routine will read a {Str} object from {in_file} that was
 *	written by {str_save()}.
 */
Str
str_restore(
    FILE *in_file)
{
    Chr chr;
    unsigned position;
    unsigned size;
    Str str;

    chr = fgetc(in_file);
    if (chr == '0') {
	return (Str)0;
    }

    /* FIXME: This is pretty sloppy for now! */
    assert(chr == '"');
    position = ftell(in_file);
    size = 0;
    while (fgetc(in_file) != '"') {
	size++;
    }
    str = str_allocate(size);
    assert(fseek(in_file, (long)position, SEEK_SET) == 0);
    assert(fread((void *)str, 1, size, in_file) == size);
    assert(fgetc(in_file) == '"');
    return str;
}

/*
 * str_save(str, out_file)
 *	This routine will write {str} to {out_file} in a way that
 *	can be read back in by {str_restore()}.
 */
void
str_save(
    Str str,
    FILE *out_file)
{
    /* FIXME: This is pretty sloppy for now. */
    if (str == (Str)0) {
	(void)fprintf(out_file, "0");
    } else {
	(void)fprintf(out_file, "\"%s\"", str);
    }
}

/*
 * str_size(str)
 *	This routine will return the size of {str}.
 */
unsigned
str_size(
    Str str)
{
    unsigned size = strlen((const char *)str);
    return size;
}

/*
 * str_span(str, chrs)
 *	This routine will return the pointer to the first character
 *	in {str} that is not in {chrs}.
 */
Str
str_span(
    Str str,
    Str chrs)
{
    return str + strspn((const char *)str, (const char *)chrs);
}

/*
 * str_weak_equal(str1, str2)
 *	This routine will return 1 if either {str1} and {str2} are both
 *	(Str)0 or if they are both non-null and the string contents are
 *	equal; otherwise, 0 is returned.
 */
int
str_weak_equal(
    Str str1,
    Str str2)
{
    if ((str1 == (Str)0) && (str2 == (Str)0)) {
	return 1;
    } else if ((str1 == (Str)0) || (str2 == (Str)0)) {
	return 0;
    }
    return str_equal(str1, str2);
}

/*
 * str_write(str, out_file)
 *	This routine will write {str} to {out_file}.
 */
void
str_write(
    Str str,
    FILE *out_file)
{
    (void)fprintf(out_file, "%s", str);
}

