/**********
Copyright 1992 Regents of the University of California.  All rights reserved.
Author: 1992 David A. Gates, U. C. Berkeley CAD Group
**********/

/*
 * Read and write dimension/index arrays via strings.
 */

#include "ngspice/ngspice.h"
#include "ngspice/dvec.h"               /* For MAXDIMS */
#include "dimens.h"
#include "ngspice/stringskip.h"


static int atodims_bracketed(const char *p, int *data, int *p_n_dim);
static int atodims_unbracketed(const char *p, int *data, int *p_n_dim);
static int get_bracketed_dim(const char *p, int *p_val);
static int atodims_csv(const char *p, int *data, int *p_n_dim);
static int get_dim(const char *p, int *p_val);



/*
 * Create a string of the form "12,1,10".
 *
 * Parameters
 * dim_data: Array of sizes of dimensions, [12, 1, 10] for the example
 * n_dim: Number of elements in the array, 3 in the example
 * retstring: Address of buffer where the string is returned.

 * Remarks
 * It is assumed that the buffer in retstring is large enough, which for
 * MAXDIMS, would require MAXDIMS * 11 bytes in the worst case assuming
 * 32-bit ints. A looser but more general bound only assuming 8-bit bytes
 * would be MAXDIMS * (3 * sizeof(int) + 1).
 */
void
dimstring(const int *dim_data, int n_dim, char *retstring)
{
    /* Handle case of no dimensions */
    if (dim_data == (int *) NULL || n_dim < 1) {
        *retstring = '\0';
        return;
    }

    /* Append each dimension */
    retstring += sprintf(retstring, "%d", dim_data[0]); /* first */
    int  i;
    for (i = 1; i < n_dim; i++) { /* rest are prefixed by a comma */
        retstring += sprintf(retstring, ",%d", dim_data[i]);
    }
} /* end of function dimstring */



/*
 * Create a string of the form "[12][1][10]" in retstring.
 *
 * Parameters
 * dim_data: Array of sizes of dimensions, [12, 1, 10] for the example
 * n_dim: Number of elements in the array, 3 in the example
 * retstring: Address of buffer where the string is returned.

 * Remarks
 * It is assumed that the buffer in retstring is large enough, which for
 * MAXDIMS, would require MAXDIMS * 12 + 1 bytes in the worst case assuming
 * 32-bit ints. A looser but more general bound only assuming 8-bit bytes
 * would be MAXDIMS * (3 * sizeof(int) + 2) + 1.
 */
void
indexstring(const int *dim_data, int n_dim, char *retstring)
{
    /* Handle case of no dimensions */
    if (dim_data == (int *) NULL || n_dim < 1) {
        *retstring = '\0';
        return;
    }

    /* Append each dimension */
    int  i;
    for (i = 0; i < n_dim; i++) {
        retstring += sprintf(retstring, "[%d]", dim_data[i]);
    }
} /* end of function indexstring */



/*
 * Add one to anstrchr into an array with sizes in dims.
 * Return 1 when all counters overflow at once.
 */
int
incindex(int *counts, int numcounts, const int *dims, int numdims)
{
    int i, start;

    if (!counts || numcounts < 1 || !dims || numdims < 1)
        return 0;

    start = numcounts - 1;

    for (i = start; i >= 0; i--)
        if (++counts[i] < dims[i])
            break;      /* This counter is not maxed out. */
        else
            counts[i] = 0;

    if (i == 0)
        return (1);
    else
        return (0);
}


/*
 * Read a string of one of the following forms into a dimensions array:
 *  [12][1][10]
 *  [12,1,10]
 *  12,1,10
 *  12, 1, 10
 *  12 , 1 , 10
 *  Basically, we require that all brackets be matched, that all numbers
 *  be separated by commas or by "][", that all whitespace is ignored, and
 *  the beginning [ and end ] are ignored if they exist.  The only valid
 *  characters in the string are brackets, commas, spaces, and digits.
 *  If any dimension is blank, its entry in the array is set to 0.
 *
 *  Return 0 on success, 1 on failure.
 */
int atodims(const char *p, int *data, int *p_n_dim)
{
    /* Validate arguments partially */
    if (!data || !p_n_dim) {
        return 1;
    }

    /* NULL string = no dimensions */
    if (!p) {
        *p_n_dim = 0;
        return 0;
    }

    /* Move to first "real" character */
    p = skip_ws(p);

    /* Allowed first char is [ to start bracked string or a number */
    return *p == '[' ? atodims_bracketed(p, data, p_n_dim) :
            atodims_unbracketed(p, data, p_n_dim);
} /* end of function atodims */



/* This function processes a dimension string of the form
 * [1,2,3,4] or [1][2][3][4]. Whitespace is allowed anywhere except
 * at the beginning of the string or between the digits of a dimension.
 *
 * Return codes
 * 0: OK
 * +1: Error
 */
static int atodims_bracketed(const char *p, int *data, int *p_n_dim)
{
    /* Process the first element, which is special because it determines
     * if the string is of form [] or [1,2...] or [1][2]... */
    p = skip_ws(++p); /* Step to number */

    {
        int rc;

        /* Get the dimension value exiting with an error on failure */
        if ((rc = get_dim(p, data)) <= 0) { /* no number or overflow */
            if (rc < 0) { /* overflow */
                return +1;
            }
            /* Handle special case of [] */
            if (*p == ']') {
                *p_n_dim = 0;
                return 0;
            }
            return +1; /* else an error */
        }

        
        p = skip_ws(p + rc); /* at comma or ] (or error) */
        switch (*p) {
        case ',': /* form [1,2,... */
            *p_n_dim = 1;
            rc = atodims_csv(++p, data, p_n_dim);
            if (rc <= 1) { /* error or invalid termination */
                return +1; /* Return error */
            }

            /* Else scan ended with ']', but did it end the string,
             * whitespace excluded? */
            p = skip_ws(p + rc);

            return *p != '\0';
        case ']': /* form [1][2]... */
            ++p; /* step past ']' */
            break;
        default: /* invalid char */
            return +1;
        }
    }

    /* Continue parsing form [1][2]... */
    unsigned int n_dim = 1; /* already 1 dim from above */
    for ( ; ; ) {
        if (n_dim == MAXDIMS) { /* too many dimensions */
            return +1;
        }

        int rc = get_bracketed_dim(p, data + n_dim);
        if (rc <= 0) { /* error or normal exit */
            *p_n_dim = n_dim;
            return !!rc;
        }
        p += rc; /* step after the dimension that was processed */
        ++n_dim; /* one more dimension */
    } /* end of loop getting dimensions */
} /* end of function atodims_bracketed */



/* This function processes a dimension string of the form
 *  1,2,3,4. Whiltespace is allowed anywhere except
 * at the beginning of the string or between the digits of a dimension.
 *
 * Return codes
 * 0: OK
 * +1: Error
 */
static int atodims_unbracketed(const char *p, int *data, int *p_n_dim)
{
    *p_n_dim = 0; /* either "" so 0 or init for atodims_csv */

    if (*p == '\0') { /* special case of "" */
        return 0;
    }

    /* Scan comma-separated dimensions. Must end with '\0' (rc=0) */
    return !!atodims_csv(p, data, p_n_dim);
} /* end of function atodims_unbracked */



/* This function processes dimension strings of the form
 *  1,2,3,4 and 1,2,3,4]. Whiltespace is allowed anywhere except
 * at the beginning of the string or between the digits of a dimension.
 * On entry, *p_n_dim is the number of dimensions already added to data
 * and p points to the first number to be processed.

 *
 * Return codes
 * -1: Error
 * 0: OK, scan ended by '\0'
 * >0: OK, scan ended by ']', returned value = # chars processed
 */
static int atodims_csv(const char *p, int *data, int *p_n_dim)
{
    const char *p0 = p;
    unsigned int n_dim = (unsigned int) *p_n_dim;
    for ( ; ; ) {
        int val;
        p = skip_ws(p);
        int rc = get_dim(p, &val);
        if (rc <= 0) { /* No number or overflow */
            return -1;
        }

        /* Dimension was read */
        if (n_dim >= MAXDIMS) { /* too many dimensions */
            return -1;
        }
        data[n_dim++] = val; /* Add data for this dimension */
        p = skip_ws(p + rc); /* step after the dimension that was processed */

        /* Should normally be at comma, but there are special cases for
         * end of regular list or bracketed list */
        switch (*p) {
        case ',': /* inter-dimension comma */
            ++p;
            break;
        case ']': /* ] ended scan */
            *p_n_dim = n_dim;
            return (int) (p - p0) + 1;
        case '\0': /* end of string ended scan */
            *p_n_dim = n_dim;
            return 0;
        default: /* invalid char */
            return -1;
        } /* end of switch over character ending scan */
    } /* end of loop getting dimensions */
} /* end of function atodims_csv */



/* This function gets the dimension value in a string of the form
 * [1] where spaces may appear anywhere except between the digits of the
 * number.
 *
 * Return codes
 * -1: Error
 * 0: String ended before '['
 * >0: Number of characters processed
 */
static int get_bracketed_dim(const char *p, int *p_val)
{
    const char *p0 = p; /* save start */
    p = skip_ws(p); /* move to opening bracket */

    const char char_cur = *p;
    if (char_cur == '\0') { /* end of string */
        return 0;
    }
    if (char_cur != '[') { /* no bracket */
        return -1;
    }
    p = skip_ws(++p); /* move to dimension */

    int rc = get_dim(p, p_val); /* read the dimension */
    if (rc <= 0) { /* error */
        return -1;
    }

    p = skip_ws(p + rc); /* move to closing backet */
    if (*p != ']') { /* no bracket */
        return -1;
    }

    return (int) (p - p0) + 1;
} /* end of function get_bracketed_dim */



/* This function reads the unsigned number at p as a dimension
 *
 * Return codes
 * -1: overflow
 * 0: *p is not a digit
 * >0: Number of characters processed
 */
static int get_dim(const char *p, int *p_val)
{
    unsigned int val = 0;
    const char *p0 = p;
    for ( ; ; ++p) {
        const char c_cur = *p;
        unsigned int digit_cur = c_cur - '0';
        unsigned int val_new;
        if (digit_cur > 9) { /* not a digit */
            if ((*p_val = (int) val) < 0) { /* overflow */
                return -1;
            }
            return (int) (p - p0);
        } /* end of case of not a digit */
        if ((val_new = 10 * val + digit_cur) < val) { /* overflow */
            return -1;
        }
        val = val_new; /* update number */
    } /* end of loop over digits */
} /* end of function get_dim */



#ifdef COMPILE_UNUSED_FUNCTIONS
/* #ifdef COMPILE_UNUSED_FUNCTIONS added 2019-03-31 */
/*
 * Count number of empty dimensions in an array.
 */
int
emptydims(int *data, int length)
{
    int i, numempty = 0;

    for (i = 0; i < length; i++)
        if (data[i] == 0)
            numempty++;

    return (numempty);
}



/*
 * Skip to the first character that cannot be part of a dimension string.
 */
char *
skipdims(char *p)
{
    if (!p)
        return NULL;

    while (*p && (*p == '[' || *p == ']' || *p == ',' ||
            isspace_c(*p) || isdigit_c(*p)))
        p++;

    return (p);
}
#endif /* COMPILE_UNUSED_FUNCTIONS */
