/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */


#include "SkParse.h"

static inline bool is_between(int c, int min, int max)
{
    return (unsigned)(c - min) <= (unsigned)(max - min);
}

static inline bool is_ws(int c)
{
    return is_between(c, 1, 32);
}

static inline bool is_digit(int c)
{
    return is_between(c, '0', '9');
}

static inline bool is_sep(int c)
{
    return is_ws(c) || c == ',' || c == ';';
}

static int to_hex(int c)
{
    if (is_digit(c))
        return c - '0';

    c |= 0x20;  // make us lower-case
    if (is_between(c, 'a', 'f'))
        return c + 10 - 'a';
    else
        return -1;
}

static inline bool is_hex(int c)
{
    return to_hex(c) >= 0;
}

static const char* skip_ws(const char str[])
{
    SkASSERT(str);
    while (is_ws(*str))
        str++;
    return str;
}

static const char* skip_sep(const char str[])
{
    SkASSERT(str);
    while (is_sep(*str))
        str++;
    return str;
}

int SkParse::Count(const char str[]) 
{
    char c;
    int count = 0;
    goto skipLeading;
    do {
        count++;
        do {
            if ((c = *str++) == '\0')
                goto goHome;
        } while (is_sep(c) == false);
skipLeading:
        do {
            if ((c = *str++) == '\0')
                goto goHome;
        } while (is_sep(c));
    } while (true);
goHome:
    return count;
}

int SkParse::Count(const char str[], char separator) 
{
    char c;
    int count = 0;
    goto skipLeading;
    do {
        count++;
        do {
            if ((c = *str++) == '\0')
                goto goHome;
        } while (c != separator);
skipLeading:
        do {
            if ((c = *str++) == '\0')
                goto goHome;
        } while (c == separator);
    } while (true);
goHome:
    return count;
}

const char* SkParse::FindHex(const char str[], uint32_t* value)
{
    SkASSERT(str);
    str = skip_ws(str);

    if (!is_hex(*str))
        return NULL;

    uint32_t n = 0;
    int max_digits = 8;
    int digit;

    while ((digit = to_hex(*str)) >= 0)
    {
        if (--max_digits < 0)
            return NULL;
        n = (n << 4) | digit;
        str += 1;
    }

    if (*str == 0 || is_ws(*str))
    {
        if (value)
            *value = n;
        return str;
    }
    return NULL;
}

const char* SkParse::FindS32(const char str[], int32_t* value)
{
    SkASSERT(str);
    str = skip_ws(str);

    int sign = 0;
    if (*str == '-')
    {
        sign = -1;
        str += 1;
    }

    if (!is_digit(*str))
        return NULL;

    int n = 0;
    while (is_digit(*str))
    {
        n = 10*n + *str - '0';
        str += 1;
    }
    if (value)
        *value = (n ^ sign) - sign;
    return str;
}

const char* SkParse::FindMSec(const char str[], SkMSec* value)
{
    SkASSERT(str);
    str = skip_ws(str);

    int sign = 0;
    if (*str == '-')
    {
        sign = -1;
        str += 1;
    }

    if (!is_digit(*str))
        return NULL;

    int n = 0;
    while (is_digit(*str))
    {
        n = 10*n + *str - '0';
        str += 1;
    }
    int remaining10s = 3;
    if (*str == '.') {
        str++;
        while (is_digit(*str))
        {
            n = 10*n + *str - '0';
            str += 1;
            if (--remaining10s == 0)
                break;
        }
    }
    while (--remaining10s >= 0)
        n *= 10;
    if (value)
        *value = (n ^ sign) - sign;
    return str;
}

const char* SkParse::FindScalar(const char str[], SkScalar* value) {
    SkASSERT(str);
    str = skip_ws(str);
#ifdef SK_SCALAR_IS_FLOAT
    char* stop;
    float v = (float)strtod(str, &stop);
    if (str == stop) {
        return NULL;
    }
    if (value) {
        *value = v;
    }
    return stop;
#else
    int sign = 0;
    if (*str == '-')
    {
        sign = -1;
        str += 1;
    }

    if (!is_digit(*str) && *str != '.')
        return NULL;

    int n = 0;
    while (is_digit(*str))
    {
        n = 10*n + *str - '0';
        if (n > 0x7FFF)
            return NULL;
        str += 1;
    }
    n <<= 16;

    if (*str == '.')
    {
        static const int gFractions[] = { (1 << 24)  / 10, (1 << 24)  / 100, (1 << 24)  / 1000, 
            (1 << 24)  / 10000, (1 << 24)  / 100000 };
        str += 1;
        int d = 0;
        const int* fraction = gFractions;
        const int* end = &fraction[SK_ARRAY_COUNT(gFractions)];
        while (is_digit(*str) && fraction < end)
            d += (*str++ - '0') * *fraction++;
        d += 0x80; // round
        n += d >> 8;
    }
    while (is_digit(*str))
        str += 1;
    if (value)
    {
        n = (n ^ sign) - sign;  // apply the sign
        *value = SkFixedToScalar(n);
    }
#endif
    return str;
}

const char* SkParse::FindScalars(const char str[], SkScalar value[], int count)
{
    SkASSERT(count >= 0);

    if (count > 0)
    {
        for (;;)
        {
            str = SkParse::FindScalar(str, value);
            if (--count == 0 || str == NULL)
                break;

            // keep going
            str = skip_sep(str);
            if (value)
                value += 1;
        }
    }
    return str;
}

static bool lookup_str(const char str[], const char** table, int count)
{
    while (--count >= 0)
        if (!strcmp(str, table[count]))
            return true;
    return false;
}

bool SkParse::FindBool(const char str[], bool* value)
{
    static const char* gYes[] = { "yes", "1", "true" };
    static const char* gNo[] = { "no", "0", "false" };

    if (lookup_str(str, gYes, SK_ARRAY_COUNT(gYes)))
    {
        if (value) *value = true;
        return true;
    }
    else if (lookup_str(str, gNo, SK_ARRAY_COUNT(gNo)))
    {
        if (value) *value = false;
        return true;
    }
    return false;
}

int SkParse::FindList(const char target[], const char list[])
{
    size_t  len = strlen(target);
    int     index = 0;

    for (;;)
    {
        const char* end = strchr(list, ',');
        size_t      entryLen;

        if (end == NULL) // last entry
            entryLen = strlen(list);
        else
            entryLen = end - list;

        if (entryLen == len && memcmp(target, list, len) == 0)
            return index;
        if (end == NULL)
            break;

        list = end + 1; // skip the ','
        index += 1;
    }
    return -1;
}

#ifdef SK_SUPPORT_UNITTEST
void SkParse::UnitTest() 
{
    // !!! additional parse tests go here
    SkParse::TestColor();
}
#endif