/*
******************************************************************************
*
*   Copyright (C) 2000-2001, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*
* File sprntf_p.c
*
* Modification History:
*
*   Date        Name        Description
*   02/08/00    george      Creation. Copied from uprntf_p.c
******************************************************************************
*/

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "sprntf_p.h"
#include "ufmt_cmn.h"

/* flag characters for uprintf */
#define FLAG_MINUS 0x002D
#define FLAG_PLUS 0x002B
#define FLAG_SPACE 0x0020
#define FLAG_POUND 0x0023
#define FLAG_ZERO  0x0030
#define FLAG_PAREN 0x0028

#define ISFLAG(s)    (s) == FLAG_MINUS || \
            (s) == FLAG_PLUS || \
            (s) == FLAG_SPACE || \
            (s) == FLAG_POUND || \
            (s) == FLAG_ZERO || \
            (s) == FLAG_PAREN

/* special characters for uprintf */
#define SPEC_ASTERISK 0x002A
#define SPEC_DOLLARSIGN 0x0024
#define SPEC_PERIOD 0x002E
#define SPEC_PERCENT 0x0025

/* unicode digits */
#define DIGIT_ZERO 0x0030
#define DIGIT_ONE 0x0031
#define DIGIT_TWO 0x0032
#define DIGIT_THREE 0x0033
#define DIGIT_FOUR 0x0034
#define DIGIT_FIVE 0x0035
#define DIGIT_SIX 0x0036
#define DIGIT_SEVEN 0x0037
#define DIGIT_EIGHT 0x0038
#define DIGIT_NINE 0x0039

#define ISDIGIT(s)    (s) == DIGIT_ZERO || \
            (s) == DIGIT_ONE || \
            (s) == DIGIT_TWO || \
            (s) == DIGIT_THREE || \
            (s) == DIGIT_FOUR || \
            (s) == DIGIT_FIVE || \
            (s) == DIGIT_SIX || \
            (s) == DIGIT_SEVEN || \
            (s) == DIGIT_EIGHT || \
            (s) == DIGIT_NINE

/* u_printf modifiers */
#define MOD_H 0x0068
#define MOD_LOWERL 0x006C
#define MOD_L 0x004C

#define ISMOD(s)    (s) == MOD_H || \
            (s) == MOD_LOWERL || \
            (s) == MOD_L

/* We parse the argument list in Unicode */
int32_t
u_sprintf_parse_spec (const UChar     *fmt,
             u_sprintf_spec    *spec)
{
    const UChar *s = fmt;
    const UChar *backup;
    u_sprintf_spec_info *info = &(spec->fInfo);

    /* initialize spec to default values */
    spec->fWidthPos     = -1;
    spec->fPrecisionPos = -1;
    spec->fArgPos       = -1;

    info->fPrecision    = -1;
    info->fWidth        = -1;
    info->fSpec         = 0x0000;
    info->fPadChar      = 0x0020;
    info->fAlt          = FALSE;
    info->fSpace        = FALSE;
    info->fLeft         = FALSE;
    info->fShowSign     = FALSE;
    info->fZero         = FALSE;
    info->fIsLongDouble = FALSE;
    info->fIsShort      = FALSE;
    info->fIsLong       = FALSE;
    info->fIsLongLong   = FALSE;

    /* skip over the initial '%' */
    s++;

    /* Check for positional argument */
    if(ISDIGIT(*s)) {

        /* Save the current position */
        backup = s;

        /* handle positional parameters */
        if(ISDIGIT(*s)) {
            spec->fArgPos = (int) (*s++ - DIGIT_ZERO);

            while(ISDIGIT(*s)) {
                spec->fArgPos *= 10;
                spec->fArgPos += (int) (*s++ - DIGIT_ZERO);
            }
        }

        /* if there is no '$', don't read anything */
        if(*s != SPEC_DOLLARSIGN) {
            spec->fArgPos = -1;
            s = backup;
        }
        /* munge the '$' */
        else
            s++;
    }

    /* Get any format flags */
    while(ISFLAG(*s)) {
        switch(*s++) {

            /* left justify */
        case FLAG_MINUS:
            info->fLeft = TRUE;
            break;

            /* always show sign */
        case FLAG_PLUS:
            info->fShowSign = TRUE;
            break;

            /* use space if no sign present */
        case FLAG_SPACE:
            info->fShowSign = TRUE;
            info->fSpace = TRUE;
            break;

            /* use alternate form */
        case FLAG_POUND:
            info->fAlt = TRUE;
            break;

            /* pad with leading zeroes */
        case FLAG_ZERO:
            info->fZero = TRUE;
            info->fPadChar = 0x0030;
            break;

            /* pad character specified */
        case FLAG_PAREN:

            /* first four characters are hex values for pad char */
            info->fPadChar = (UChar)ufmt_digitvalue(*s++);
            info->fPadChar = (UChar)((info->fPadChar * 16) + ufmt_digitvalue(*s++));
            info->fPadChar = (UChar)((info->fPadChar * 16) + ufmt_digitvalue(*s++));
            info->fPadChar = (UChar)((info->fPadChar * 16) + ufmt_digitvalue(*s++));

            /* final character is ignored */
            s++;

            break;
        }
    }

    /* Get the width */

    /* width is specified out of line */
    if(*s == SPEC_ASTERISK) {

        info->fWidth = -2;

        /* Skip the '*' */
        s++;

        /* Save the current position */
        backup = s;

        /* handle positional parameters */
        if(ISDIGIT(*s)) {
            spec->fWidthPos = (int) (*s++ - DIGIT_ZERO);

            while(ISDIGIT(*s)) {
                spec->fWidthPos *= 10;
                spec->fWidthPos += (int) (*s++ - DIGIT_ZERO);
            }
        }

        /* if there is no '$', don't read anything */
        if(*s != SPEC_DOLLARSIGN) {
            spec->fWidthPos = -1;
            s = backup;
        }
        /* munge the '$' */
        else
            s++;
    }
    /* read the width, if present */
    else if(ISDIGIT(*s)){
        info->fWidth = (int) (*s++ - DIGIT_ZERO);

        while(ISDIGIT(*s)) {
            info->fWidth *= 10;
            info->fWidth += (int) (*s++ - DIGIT_ZERO);
        }
    }

    /* Get the precision */

    if(*s == SPEC_PERIOD) {

        /* eat up the '.' */
        s++;

        /* precision is specified out of line */
        if(*s == SPEC_ASTERISK) {

            info->fPrecision = -2;

            /* Skip the '*' */
            s++;

            /* save the current position */
            backup = s;

            /* handle positional parameters */
            if(ISDIGIT(*s)) {
                spec->fPrecisionPos = (int) (*s++ - DIGIT_ZERO);

                while(ISDIGIT(*s)) {
                    spec->fPrecisionPos *= 10;
                    spec->fPrecisionPos += (int) (*s++ - DIGIT_ZERO);
                }

                /* if there is no '$', don't read anything */
                if(*s != SPEC_DOLLARSIGN) {
                    spec->fPrecisionPos = -1;
                    s = backup;
                }
                else {
                    /* munge the '$' */
                    s++;
                }
            }
        }
        /* read the precision */
        else if(ISDIGIT(*s)){
            info->fPrecision = (int) (*s++ - DIGIT_ZERO);

            while(ISDIGIT(*s)) {
                info->fPrecision *= 10;
                info->fPrecision += (int) (*s++ - DIGIT_ZERO);
            }
        }
    }

    /* Get any modifiers */
    if(ISMOD(*s)) {
        switch(*s++) {

            /* short */
        case MOD_H:
            info->fIsShort = TRUE;
            break;

            /* long or long long */
        case MOD_LOWERL:
            if(*s == MOD_LOWERL) {
                info->fIsLongLong = TRUE;
                /* skip over the next 'l' */
                s++;
            }
            else
                info->fIsLong = TRUE;
            break;

            /* long double */
        case MOD_L:
            info->fIsLongDouble = TRUE;
            break;
        }
    }

    /* finally, get the specifier letter */
    info->fSpec = *s++;

    /* return # of characters in this specifier */
    return (int32_t)(s - fmt);
}

#endif /* #if !UCONFIG_NO_FORMATTING */
