#ifndef SLANG_CORE_CHAR_UTIL_H
#define SLANG_CORE_CHAR_UTIL_H

#include "slang-string.h"

namespace Slang
{

struct CharUtil
{
    typedef uint8_t Flags;
    struct Flag
    {
        enum Enum : Flags
        {
            Upper = 0x01, ///< A-Z
            Lower = 0x02, ///< a-z
            Digit = 0x04, ///< 0-9
            HorizontalWhitespace =
                0x08,        ///< Whitespace that can appear horizontally (ie excluding CR/LF)
            HexDigit = 0x10, ///< 0-9, a-f, A-F
            VerticalWhitespace = 0x20, ///< \n \r
        };
    };

    SLANG_FORCE_INLINE static bool isDigit(char c) { return c >= '0' && c <= '9'; }
    SLANG_FORCE_INLINE static bool isLower(char c) { return c >= 'a' && c <= 'z'; }
    SLANG_FORCE_INLINE static bool isUpper(char c) { return c >= 'A' && c <= 'Z'; }
    SLANG_FORCE_INLINE static bool isHorizontalWhitespace(char c) { return c == ' ' || c == '\t'; }
    SLANG_FORCE_INLINE static bool isVerticalWhitespace(char c) { return c == '\n' || c == '\r'; }
    SLANG_FORCE_INLINE static bool isWhitespace(char c)
    {
        return (getFlags(c) & (Flag::HorizontalWhitespace | Flag::VerticalWhitespace)) != 0;
    }

    /// True if it's alpha
    SLANG_FORCE_INLINE static bool isAlpha(char c)
    {
        return (getFlags(c) & (Flag::Upper | Flag::Lower)) != 0;
    }
    /// True if it's alpha or a digit
    SLANG_FORCE_INLINE static bool isAlphaOrDigit(char c)
    {
        return (getFlags(c) & (Flag::Upper | Flag::Lower | Flag::Digit)) != 0;
    }

    /// True if the character is a valid hex character
    SLANG_FORCE_INLINE static bool isHexDigit(char c)
    {
        return (getFlags(c) & Flag::HexDigit) != 0;
    }

    /// True if the character is an octal digit
    SLANG_FORCE_INLINE static bool isOctalDigit(char c) { return c >= '0' && c <= '7'; }

    /// For a given character get the associated flags
    SLANG_FORCE_INLINE static Flags getFlags(char c)
    {
        return g_charFlagMap.flags[static_cast<unsigned char>(c)];
    }

    /// Given a character return the lower case equivalent
    SLANG_FORCE_INLINE static char toLower(char c)
    {
        return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c;
    }
    /// Given a character return the upper case equivalent
    SLANG_FORCE_INLINE static char toUpper(char c)
    {
        return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
    }

    /// Given a value between 0-15 inclusive returns the hex digit. Uses lower case hex.
    SLANG_FORCE_INLINE static char getHexChar(Index i)
    {
        SLANG_ASSERT((i & ~Index(0xf)) == 0);
        return char(i >= 10 ? (i - 10 + 'a') : (i + '0'));
    }

    /// Returns the value if c interpretted as a decimal digit
    /// If c is not a valid digit returns -1
    inline static int getDecimalDigitValue(char c) { return isDigit(c) ? (c - '0') : -1; }

    /// Returns the value if c interpretted as a hex digit
    /// If c is not a valid hex returns -1
    inline static int getHexDigitValue(char c);

    /// Returns the value if c interpretted as a octal digit
    /// If c is not a valid octal returns -1
    inline static int getOctalDigitValue(char c) { return isOctalDigit(c) ? (c - '0') : -1; }

    struct CharFlagMap
    {
        Flags flags[0x100];
    };

    static CharFlagMap makeCharFlagMap();

    // HACK!
    // JS: Many of the inlined functions of CharUtil just access a global map. That referencing this
    // global is *NOT* enough to link correctly with CharUtil on linux for a shared library. Caling
    // this function can force linkage.
    static int _ensureLink();

    static const CharFlagMap g_charFlagMap;
};

// ------------------------------------------------------------------------------------
inline /* static */ int CharUtil::getHexDigitValue(char c)
{
    if (c >= '0' && c <= '9')
    {
        return c - '0';
    }
    else if (c >= 'a' && c <= 'f')
    {
        return c - 'a' + 10;
    }
    else if (c >= 'A' && c <= 'F')
    {
        return c - 'A' + 10;
    }
    return -1;
}

} // namespace Slang

#endif // SLANG_CHAR_UTIL_H
