/*
        Copyright (C) 1994 Sean Luke

        COWSStringNode.m
        Version 1.5a_mj
        Code mangled beyond recognition by Michal Jaegermann

*/




#import "COWSStringNode.h"
#import "COWSInterpreter.h"
#import <string.h>
#import <stdlib.h>



@implementation COWSStringNode

#define CPREC  12   /* fixed precision for conversion of doubles to strings */
#define WBUFSZ 128  /* size of buffer - should be big enough to hold
                       results of any conversion */
#define CVFORMAT "%.*e"

static char wbuf[WBUFSZ];  /* work buffer for number output formatting */
/*
 * This function is doing what really "%g" sprintf() is supposed
 * to do, but it is not doing on NeXT machines, because is plainly
 * broken and NeXT does not want to be bothered to fix it.
 *
 * For simplicity we will use here a fixed precision
 *
 * Returns a number of characters written to 'wbuf' without
 * counting a trailing 0.
 *
 * This code is based on a code for sgfmt() from gawk sources.
 */
static int
dtostring(double val)
{
    int          precision, count;
    char        *pos, *dot;
    char         cvfmt[] = CVFORMAT;    /* first we will try this */
    extern int   atoi(const char *);
    extern char *strrchr(const char*, int);

    if (val == 0.0) {
        wbuf[0] = '0';
        wbuf[1] = '\0';
        return 1;
    }
    precision = CPREC - 1;
    count = sprintf(wbuf, cvfmt, precision, val);
    if ((pos = strrchr(wbuf, 'e')) != NULL) {  /*exponent starts here */
        int exp = atoi(pos + 1);
        if (exp >= -4 && exp <= precision) {
            /* try again in 'f' format */
            *(cvfmt + (sizeof(cvfmt) - 2)) = 'f';
            precision -= exp;
            count = sprintf(wbuf, cvfmt, precision, val);
            pos = wbuf + count;
            /* a bit of paranoia, but play it safe */
            while (*--pos == ' ')
                count -= 1;
            pos += 1;
        }

        /* remove trailing zeros */
        if (NULL != (dot = strrchr(wbuf, '.'))) {
            while ( pos > dot && *--pos == '0')
                precision -= 1;
            if (dot == pos)
                precision -= 1;
            if (precision < 0)
                precision = 0;
            count = sprintf(wbuf, cvfmt, precision, val);
        }
        else {
            *pos = '\0';
        }
    }
    return count;
}


- init
{
    id returnval = [super init];

    string = NULL;
    len = 0;
    value_flags = COWSSTRINGNODE_STORED_STRING;
    return returnval;
}


- free
{
    if (string != NULL) {
        [self setString :NULL size:0];  // on purpose - internals may change
    }
    return[super free];
}


- (const char *)string
{
    if (!STORED_STRING(value_flags)) {
        if (!STORED_NUMBER(value_flags)) {
            [self setString :NULL size:0];  // should not happen
        }
        else {
            /* number but not a string - we need to convert */
            size_t dvlen = dtostring(double_value);
            [self setString :wbuf size:dvlen];
            SN_SET(value_flags, COWSSTRINGNODE_STORED_STRING);
        }
    }
    // used to be:  return (const char*)string;
	// changed to by Sean so NULLs are never returned:
	return (string==NULL) ? "" : (const char*) string;
}

- setString:(const char *)this
{
    size_t this_size;

    this_size = (this == NULL ? 0 : strlen(this));

    return [self setString :this size:this_size];
}

// passing a NULL string here will cause freeing of 'string' storage
- setString:(const char *)this size:(size_t)this_size
{
    size_t  newlen = 0;

    SN_SET(value_flags, COWSSTRINGNODE_STORED_STRING);
    SN_CLEAR(value_flags, COWSSTRINGNODE_STORED_NUMBER);

    if (this == NULL) {
        if (string != NULL) {    /* freeing NULL value should be OK - sigh!! */
            free(string);
            string = NULL;
        }
    }
    else {
        newlen = this_size + 1;
        if (newlen != len) {  /* grow or shrink */
            /* realloc should really do that itself, but is often buggy */
            string = ((string == NULL) ? malloc (newlen) :
                                         realloc(string, newlen));
        }
    }
    if (string != NULL) {    /* don't try to copy if allocation failed */
        strncpy(string, this, this_size);
        string[this_size] = '\0';
        len = newlen;
    }
    else {
        len = 0;
    }
    return self;
}


- setDoubleVal:(double)this
{
    SN_SET(value_flags, COWSSTRINGNODE_STORED_NUMBER);
    SN_CLEAR(value_flags, COWSSTRINGNODE_STORED_STRING);

    double_value = this;
    return self;
}

- (double)doubleVal
{
    if (!STORED_NUMBER(value_flags)) {
        double result;
        if (STORED_STRING(value_flags) && (NULL != string)) {
            char *eptr;
            result = strtod((const char *)string, &eptr);
            if (0.0 != result) {
                /*
                 * check if what we converted is a "pure" number
                 * taking into account that strtod on NeXT is broken;
                 * this test accepts ".1", but rejects "1."
                 */
                while(NXIsSpace(*eptr) || ('\0' == *eptr)) {
                    eptr -= 1;
                    // eptr will not drop below string; we converted something,
		    // hence some digits are in (string..eptr) interval
                }
                if (!NXIsDigit(*eptr))
                    result = 0.0;
            }
        }
        else {
            result = 0.0;
        }
        SN_SET(value_flags, COWSSTRINGNODE_STORED_NUMBER);
        double_value = result;
    }
    return double_value;
}

// the following four routines just a syntactic sugar
// although some rounding may be forced as a result

- setFloatVal:(float)this
{
    return [self setDoubleVal :(double)this];
}

- (float)floatVal
{
    return (float)[self doubleVal];
}

- setIntVal:(int)this
{
    return [self setDoubleVal :(double)this];
}

- (int)intVal
{
    return (int)[self doubleVal];
}

- setBooleanVal:(BOOL)this
{
    if (this)
        [self setString:"t" size:(sizeof("t") - 1)];
    else
        [self setString:"" size:(sizeof("") - 1)];
    return self;
}

- (BOOL)booleanVal
{
    [self string];    // force conversion if needed
    return (len > 1); // i.e. "" string and NULL give false, other
                      // strings need more storage than one char.
}

- (BOOL)isCanonicallyTrue
{
    const char *this = [self string];
    return ((len == 2) && ('t' == *this));
}

- (BOOL)isCanonicallyFalse
{
    [self string];
    return (len <= 1);
}

// who really use this?  should not this be private?
- (int)value_state
{
    return value_flags;
}

- (int) state
{
	if (STORED_STRING(value_flags)) return COWSSTRINGNODE_STORED_STRING;
	if (STORED_NUMBER(value_flags)) return COWSSTRINGNODE_STORED_NUMBER;
    return 0;
}

- (size_t) allocated   // this IS private, do not advertise
{
    return len;
}

- copyValue:(COWSStringNode *) from_this
{
    int         vflags;
    size_t      vlen;

    vflags = [from_this value_state];

    if (STORED_STRING(vflags)) {
        vlen = [from_this allocated];
        [self setString:[from_this string] size:(vlen - 1)];
    }
    if (STORED_NUMBER(vflags)) {
        double_value = [from_this doubleVal];
    }
    value_flags = vflags;
    return self;
}


#if 0

// attach string 'this' to a value stored in some COWSStringNode;
// forces string representation as a side effect

- (const char *)catString :(const char *)this
{
    size_t        newlen, start;
    char         *old_string = (char *)[self string];

    if ((NULL ==  this) || ('\0' == *this)) {
        return old_string;
    }
    newlen = strlen(this);
    if (NULL == old_string) {
        newlen += 1;
        old_string = malloc(newlen + 1);
        start = 0;
    }
    else {
        start = len;
        newlen += start;  /* increase by a length of a storage for
                             the previous string plus a space for '\0' */
        start -= 1;
        old_string = realloc(old_string, newlen);
    }
    if (NULL != old_string)
        string = strcpy(old_string + start, this);
    else
        newlen = 0;
    len = newlen;
    // whatever number was stored here is is now invalid
    SN_CLEAR(value_flags, COWSSTRINGNODE_STORED_NUMBER);
    return (const char*)string;
}

// Add 'this' to a value n some COWSStringNode;
// forces floating point representation as a side effect

- (double)addToVal :(double)this
{
    double old_val = [self doubleVal];
    if (0.0 ==  this) {
        return old_val;
    }
    double_value = old_val + this;
    SN_CLEAR(value_flags, COWSSTRINGNODE_STORED_STRING);
    return double_value;
}

#endif /* 0 */

- setError:(BOOL)true_or_false
{
    if (true_or_false) {
        SN_SET(value_flags, COWSSTRINGNODE_STORED_ERROR);
    }
    else {
        SN_CLEAR(value_flags, COWSSTRINGNODE_STORED_ERROR);
    }
    return self;
}


- (BOOL)error
{
    return (SN_CHECK(value_flags, COWSSTRINGNODE_STORED_ERROR) ? 1 : 0);
}

// debugging
- printContents
{
    printf("\t#%s#\n", [self string]);  // force conversion if needed
    return self;
}

@end
