//
// String.cpp
//

#include <stdlib.h>
#include <string.h>
#include <regex>
#ifdef _WIN32
#include <time.h>
#else
#include <sys/time.h>
#include <iconv.h>
#endif
#include <errno.h>

#define DBG_LEVEL 0
#include <Raym/Log.h>
#include <Raym/String.h>
#include <Raym/Array.h>
#include <Raym/Data.h>

#define HEX2DEC(P1) (('a' <= P1) && (P1 <= 'f') ? (P1 - 'a' + 10) : ('A' <= P1) && (P1 <= 'F') ? (P1 - 'A' + 10) : ('0' <= P1) && (P1 <= '9') ? (P1 - '0') : 0)

namespace Raym
{

String *String::alloc()
{
    DebugLog2("String::alloc()");

    return new String();
}

String *String::string()
{
    DebugLog2("String::string()");

    String *result = String::alloc()->init();
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

String *String::stringWithCString(const char *nullTerminatedCString, StringEncoding encoding)
{
    DebugLog2("String::stringWithCString()");

    String *result = String::alloc()->initWithCString(nullTerminatedCString, encoding);
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

String *String::stringWithUTF8String(const char *nullTerminatedCString)
{
    DebugLog2("String::stringWithUTF8String()");

    String *result = String::alloc()->initWithUTF8String(nullTerminatedCString);
    if (result != NULL)
    {
        result->autorelease();
    }
    return result;
}

String *String::stringWithFormat(String *format, ...)
{
    DebugLog2("String::stringWithFormat(String *, ...)");

    String *result = NULL;
    if (format != NULL)
    {
        va_list ap;
        va_start(ap, format);
        result = String::alloc()->initWithFormat(format->cString(), ap);
        va_end(ap);
        if (result != NULL)
        {
            result->autorelease();
        }
    }
    return result;
}

String *String::stringWithFormat(const char *format, ...)
{
    DebugLog2("String::stringWithFormat(const char *, ...)");

    String *result = NULL;
    if (format != NULL)
    {
        va_list ap;
        va_start(ap, format);
        result = String::alloc()->initWithFormat(format, ap);
        va_end(ap);
        if (result != NULL)
        {
            result->autorelease();
        }
    }
    return result;
}

String *String::stringWithContentsOfFile(const char *path, StringEncoding encoding)
{
    DebugLog2("String::stringWithContentsOfFile()");

    String *result = NULL;
    if (path != NULL)
    {
        Data *data = Data::dataWithContentsOfFile(path);
        if (data != NULL)
        {
#if 0
            char *tmp = (char *)malloc(data->length() + 1);
            if (tmp != NULL)
            {
                memcpy(tmp, data->bytes(), data->length());
                tmp[data->length()] = '\0';
                result = String::stringWithUTF8String(tmp);
                free(tmp);
            }
#else
            result = String::alloc()->initWithData(data, encoding);
            if (result != NULL)
            {
                result->autorelease();
            }
            else
            {
                DebugLog0("String::stringWithContentsOfFile(): result == NULL");
            }
#endif
        }
        else
        {
            DebugLog0("String::stringWithContentsOfFile(): data == NULL");
        }
    }
    else
    {
        DebugLog0("String::stringWithContentsOfFile(): path == NULL");
    }

    return result;
}

String *String::stringWithString(String *string)
{
    DebugLog2("String::stringWithString()");

    String *result = NULL;
    if (string != NULL)
    {
        result = String::alloc()->initWithUTF8String(string->cString());
        if (result != NULL)
        {
            result->autorelease();
        }
    }
    return result;
}

String *String::base64StringWithBytes(const char *bytes, UInteger length)
{
    DebugLog2("String::base64StringWithBytes()");

    static char base64EncodingTable[64] =
    {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };

    if (bytes == NULL)
    {
        return NULL;
    }
    if (length == 0)
    {
        return String::string();
    }

    unsigned long long buflen = ((length + 3) / 3) * 4 + 1;
    char *encoded = (char *)malloc(buflen);
    if (encoded == NULL)
    {
        return NULL;
    }
    encoded[buflen - 1] = '\0';

    unsigned long long in_offset = 0;
    unsigned long long out_offset = 0;
    while (in_offset < length)
    {
        switch (length - in_offset)
        {
        case 1:
            encoded[out_offset]     = base64EncodingTable[(bytes[in_offset] & 0xFC) >> 2];
            encoded[out_offset + 1] = base64EncodingTable[((bytes[in_offset] & 0x03) << 4)];
            encoded[out_offset + 2] = '=';
            encoded[out_offset + 3] = '=';
            break;
        case 2:
            encoded[out_offset]     = base64EncodingTable[(bytes[in_offset] & 0xFC) >> 2];
            encoded[out_offset + 1] = base64EncodingTable[((bytes[in_offset] & 0x03) << 4) | ((bytes[in_offset + 1] & 0xF0) >> 4)];
            encoded[out_offset + 2] = base64EncodingTable[((bytes[in_offset + 1] & 0x0F) << 2)];
            encoded[out_offset + 3] = '=';
            break;
        default:
            encoded[out_offset]     = base64EncodingTable[(bytes[in_offset] & 0xFC) >> 2];
            encoded[out_offset + 1] = base64EncodingTable[((bytes[in_offset] & 0x03) << 4) | ((bytes[in_offset + 1] & 0xF0) >> 4)];
            encoded[out_offset + 2] = base64EncodingTable[((bytes[in_offset + 1] & 0x0F) << 2) | ((bytes[in_offset + 2] & 0xC0) >> 6)];
            encoded[out_offset + 3] = base64EncodingTable[bytes[in_offset + 2] & 0x3F];
            break;
        }
        in_offset += 3;
        out_offset += 4;
    }

    String *result = String::stringWithUTF8String(encoded);
    free(encoded);

    return result;
}

String::String()
{
    DebugLog2("String::String()");

    _str = NULL;
    _length = -1;
}

String::~String()
{
    DebugLog2("String::~String(\"%s\")", _str);

    if (_str != NULL)
    {
        free(_str);
        _str = NULL;
    }
    _length = -1;
}

String *String::init()
{
    DebugLog2("String::init()");

    _str = (char *)malloc(1);
    if (_str == NULL)
    {
        release();
        return NULL;
    }
    _str[0] = '\0';
    _length = 0;
    return this;
}

String *String::initWithCString(const char *nullTerminatedCString, StringEncoding encoding)
{
    DebugLog2("String::initWithCString(\"%s\", %d)", nullTerminatedCString, encoding);

    if (nullTerminatedCString != NULL)
    {
        UInteger length = (UInteger)strlen(nullTerminatedCString);
        if (length != 0)
        {
            switch (encoding)
            {
            case UTF8StringEncoding:
                _str = (char *)malloc(length + 1);
                if (_str != NULL)
                {
                    _length = length;
                    memcpy(_str, nullTerminatedCString, _length);
                    _str[_length] = '\0';
                }
                else
                {
                    DebugLog3("error: %s(): malloc", __FUNCTION__);
                    release();
                    return NULL;
                }
                break;

            case ShiftJISStringEncoding:
#ifdef _WIN32
                {
                    int wlen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, nullTerminatedCString, (int)(strlen(nullTerminatedCString) + 1), NULL, 0);
                    if (wlen != 0)
                    {
                        wchar_t *wbuf = (wchar_t *)malloc(sizeof(wchar_t) * (wlen + 1));
                        if (wbuf != NULL)
                        {
                            memset(wbuf, 0x00, sizeof(wchar_t) * (wlen + 1));
                            MultiByteToWideChar(CP_ACP, 0, nullTerminatedCString, (int)(strlen(nullTerminatedCString) + 1), wbuf, sizeof(wchar_t) * (wlen + 1));
                            int ulen = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL, NULL);
                            if (ulen != 0)
                            {
                                _str = (char *)malloc(ulen + 1);
                                if (_str != NULL)
                                {
                                    memset(_str, 0x00, ulen + 1);
                                    WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, _str, ulen + 1, NULL, NULL);
                                    free(wbuf);
                                    _length = ulen;
                                }
                                else
                                {
                                    DebugLog0("error: %s(): malloc", __FUNCTION__);
                                    free(wbuf);
                                    release();
                                    return NULL;
                                }
                            }
                            else
                            {
                                DebugLog0("error: %s(): WideCharToMultiByte", __FUNCTION__);
                                release();
                                return NULL;
                            }
                        }
                        else
                        {
                            DebugLog0("error: %s(): malloc", __FUNCTION__);
                            release();
                            return NULL;
                        }
                    }
                    else
                    {
                        DebugLog0("error: %s(): MultiByteToWideChar, 0x%08x", __FUNCTION__, GetLastError());
                        release();
                        return NULL;
                    }
                }
                break;
#endif


#ifndef _WIN32
                {
                    iconv_t cd;
                    const char *fromcode = NULL;
                    switch (encoding)
                    {
                    case JapaneseEUCStringEncoding:
                        fromcode = "EUC-JP";
                        break;
                    case ShiftJISStringEncoding:
                        fromcode = "SHIFT-JIS";
                        break;
                    default:
                        fromcode = "UTF-8";
                        break;
                    }
                    cd = iconv_open("UTF-8", fromcode);
                    if (cd != (iconv_t)-1)
                    {
                        size_t srclen = strlen(nullTerminatedCString);
                        _str = (char *)malloc(srclen * 3 + 1);
                        if (_str != NULL)
                        {
                            memset(_str, 0x00, srclen * 3 + 1);
                            size_t dstlen = srclen * 3 + 1;
                            char *dst = _str;
                            char *src = strdup(nullTerminatedCString);
                            char *src2 = src;
                            size_t ret = iconv(cd, &src, &srclen, &dst, &dstlen);
                            if (src2 != NULL)
                            {
                                free(src2);
                            }
                            if (ret == -1)
                            {
                                DebugLog0("nsstr: check 0: %d, %d\n", srclen, errno);
                                iconv_close(cd);
                                release();
                                return NULL;
                            }

                            iconv_close(cd);

                            char *p = (char *)realloc(_str, strlen(_str) + 1);
                            if (p == NULL)
                            {
                                DebugLog0("nsstr: check 1\n");
                                release();
                                return NULL;
                            }
                            _str = p;
                            _length = strlen(_str);
//                            DebugLog0("_str = %s\n", _str);
                        }
                        else
                        {
                            DebugLog0("nsstr: check 2\n");
                            iconv_close(cd);
                            release();
                            return NULL;
                        }
                    }
                    else
                    {
                        DebugLog0("nsstr: check 3\n");
                    }
                }
                break;
#endif

            default:
                DebugLog0("warning: Unknown String Encoding: 0x%08x", encoding);
                release();
                return NULL;
            }
        }
        else
        {
            // length is zero
            return init();
        }
    }
    else
    {
        DebugLog0("warning: string is null.");
        release();
        return NULL;
    }

    return this;
}

String *String::initWithUTF8String(const char *bytes)
{
    DebugLog2("String::initWithUTF8String()");

    return initWithCString(bytes, UTF8StringEncoding);
}

String *String::initWithData(Data *data, StringEncoding encoding)
{
    DebugLog2("String::initWithData()");

    String *result = NULL;
    if (data != NULL)
    {
        char *tmp = (char *)malloc(data->length() + 1);
        if (tmp != NULL)
        {
            memcpy(tmp, data->bytes(), data->length());
            tmp[data->length()] = '\0';
            result = String::initWithCString(tmp, encoding);
            free(tmp);
        }
    }
    else
    {
        DebugLog0("warning: data is null.");
        release();
    }

    return result;
}

String *String::initWithFormat(const char *format, ...)
{
    DebugLog2("String::initWithFormat(const char *, ...)");

    if (format == NULL)
    {
        release();
        return NULL;
    }

    va_list ap;
    va_start(ap, format);
    String *result = initWithFormat(format, ap);
    va_end(ap);

    return result;
}

//
// format(書式は１つのみ)とvalueで、新たにmallocした領域にsnprintfして返す。
// 呼び元はfreeすること。malloc失敗等の場合はNULLを返す。
//
static char *print_1(const char *format, void *value)
{
    static const int DEFAULT_LENGTH = 32;
    char *buf = (char *)malloc(DEFAULT_LENGTH);
    if (buf != NULL)
    {
#ifdef _WIN32
        int require = _snprintf_s(buf, DEFAULT_LENGTH, _TRUNCATE, format, value);
#else
        int require = snprintf(buf, DEFAULT_LENGTH, format, value);
#endif
        if (require >= DEFAULT_LENGTH)
        {
            ++require;
            char *buf2 = (char *)realloc(buf, require);
            if (buf2 != NULL)
            {
                buf = buf2;
#ifdef _WIN32
                _snprintf_s(buf, require, _TRUNCATE, format, value);
#else
                snprintf(buf, require, format, value);
#endif
            }
            else
            {
                free(buf);
                buf = NULL;
            }
        }
    }
    return buf;
}

//
// allocしたs1をs2分加算してreallocしてstrcatし、新しい（または同じ）アドレスを返す。
// 失敗した場合はNULLを返す。その場合 s1 は解放はしない。
static char *strcat_with_realloc(char *s1, const char *s2)
{
    char *buf = (char *)realloc(s1, strlen(s1) + strlen(s2) + 1);
    if (buf != NULL)
    {
#ifdef _WIN32
        strcat_s(buf, (strlen(s1) + strlen(s2) + 1), s2);
#else
        strcat(buf, s2);
#endif
    }
    return buf;
}

String *String::initWithFormat(const char *format, va_list ap)
{
    DebugLog2("String::initWithFormat(const char *, va_list)");

    char *buf = NULL;

#if 0
    va_list ap2;
    va_copy(ap2, ap);
    printf("initWithFormat: format: %s\n", format);
    void *p = va_arg(ap2, void *);
    printf("initWithFormat: arg1: 0x%016lx\n", p);

    va_end(ap2);
#endif

    if (strstr(format, "%@") == NULL)
    {
        // とりあえず 256バイトくらいで実行してみて
#define INITIAL_LENGTH 256
        buf = (char *)malloc(INITIAL_LENGTH);
        if (buf == NULL)
        {
            DebugLog3("malloc NG. (1)\n");
            release();
            return NULL;
        }

        va_list ap2;
        va_copy(ap2, ap);

#ifdef _WIN32
        int result = vsnprintf_s(buf, INITIAL_LENGTH, INITIAL_LENGTH - 1, format, ap);
        // バッファに収まっていなければ
        if (result < 0)
        {
            int count = 2;
            while (true)
            {
                char *buf2 = (char *)realloc(buf, INITIAL_LENGTH * count);
                if (buf2 == NULL)
                {
                    DebugLog3("realloc NG. (1)\n");
                    free(buf);
                    va_end(ap2);
                    release();
                    return NULL;
                }
                buf = buf2;
                result = vsnprintf_s(buf, INITIAL_LENGTH * count, INITIAL_LENGTH * count - 1, format, ap2);
                if (result >= INITIAL_LENGTH)
                {
                    break;
                }
                ++count;
            }

        }
#else
        int require = vsnprintf(buf, INITIAL_LENGTH, format, ap);

        // バッファに収まっていなければ
        if (require >= INITIAL_LENGTH)
        {
            // 必要なサイズのバッファを再捕捉
            ++require;
            char *buf2 = (char *)realloc(buf, require);
            if (buf2 == NULL)
            {
                DebugLog3("realloc NG. (1)\n");
                free(buf);
                va_end(ap2);
                release();
                return NULL;
            }
            buf = buf2;
            vsnprintf(buf, require, format, ap2);
        }
#endif
        va_end(ap2);
    }
    else
    {
        // windows で動かしたら %@ がうまくいかないので
        // しばらく封印する。。。
        printf("%%@ tu ka e nai\n");
        abort();

#ifdef _WIN32
        char *fmt = _strdup(format);
#else
        char *fmt = strdup(format);
#endif
        if (fmt == NULL)
        {
            DebugLog3("strdup NG.\n");
            release();
            return NULL;
        }

        // とりあえず、null文字分確保
        buf = (char *)malloc(1);
        if (buf == NULL)
        {
            DebugLog3("malloc NG. (2)\n");
            free(fmt);
            release();
            return NULL;
        }

        char *p1 = fmt;
        char *p2;
        while (true)
        {
            if (strlen(p1) == 0)
            {
                break;
            }
            p2 = strchr(p1 + 1, '%');
            if (p2 != NULL)
            {
                if (p2 == p1 + 1)
                {
                    p1 += 2;
                    buf = (char *)realloc(buf, strlen(buf) + 1 + 2);
                    if (buf == NULL)
                    {
                        DebugLog3("realloc NG. (2)\n");
                        free(fmt);
                        release();
                        return NULL;
                    }
#ifdef _WIN32
                    strcat_s(buf, (strlen(buf) + 3), "%%");
#else
                    strcat(buf, "%%");
#endif
                }
                else
                {
                    *p2 = '\0';

                    if (strstr(p1, "%") != NULL)
                    {
                        void *ptr;
                        char *at;
                        if ((at = strstr(p1, "%@")) != NULL)
                        {
                            ++at;
                            *at = 's';
                            Object *obj = va_arg(ap, Object *);
                            ptr = (void *)obj->description()->cString();
                        }
                        else
                        {
                            ptr = va_arg(ap, void *);
                        }

                        char *tmpbuf = print_1(p1, ptr);
                        if (tmpbuf == NULL)
                        {
                            free(fmt);
                            release();
                            return NULL;
                        }

                        char *buf2 = strcat_with_realloc(buf, tmpbuf);
                        free(tmpbuf);
                        if (buf2 == NULL)
                        {
                            free(fmt);
                            free(buf);
                            release();
                            return NULL;
                        }
                        buf = buf2;
                    }
                    else
                    {
                        char *buf2 = strcat_with_realloc(buf, p1);
                        if (buf2 == NULL)
                        {
                            free(fmt);
                            free(buf);
                            release();
                            return NULL;
                        }
                        buf = buf2;
                    }

                    *p2 = '%';
                    p1 = p2;
                }
            }
            else
            {
                if (*p1 == '%')
                {
                    void *ptr = (void *)"(nil)";
                    char *at;
                    if ((at = strstr(p1, "%@")) != NULL)
                    {
                        ++at;
                        *at = 's';
                        Object *obj = va_arg(ap, Object *);
                        if (obj != NULL)
                        {
                            ptr = (void *)obj->description()->cString();
                        }
                    }
                    else
                    {
                        ptr = va_arg(ap, void *);
                    }
                    char *tmpbuf = print_1(p1, ptr);
                    if (tmpbuf == NULL)
                    {
                        free(fmt);
                        release();
                        return NULL;
                    }

                    char *buf2 = strcat_with_realloc(buf, tmpbuf);
                    free(tmpbuf);
                    if (buf2 == NULL)
                    {
                        free(fmt);
                        free(buf);
                        release();
                        return NULL;
                    }
                    buf = buf2;
                }
                else
                {
                    char *buf2 = strcat_with_realloc(buf, p1);
                    if (buf2 == NULL)
                    {
                        free(fmt);
                        free(buf);
                        release();
                        return NULL;
                    }
                    buf = buf2;
                }
                break;
            }
        }
        free(fmt);
    }

    String *result= initWithUTF8String(buf);
    free(buf);

    return result;
}

String *String::retain()
{
    DebugLog2("String::retain()");

    Object::retain();
    return this;
}

String *String::autorelease()
{
    DebugLog2("String::autorelease()");

    Object::autorelease();
    return this;
}

UInteger String::length()
{
    DebugLog2("String::length()");

    return _length;
}

bool String::hasPrefix(String *prefix)
{
    bool result = false;
    if (prefix != NULL)
    {
        if (length() >= prefix->length())
        {
            result = (strncmp(_str, prefix->_str, prefix->length()) == 0);
        }
    }
    return result;
}

bool String::hasPrefix(const char *prefix)
{
    DebugLog2("String::hasPrefix()");

    String *k = String::alloc()->initWithUTF8String(prefix);
    bool ret = hasPrefix(k);
    k->release();
    return ret;
}

bool String::hasSuffix(String *suffix)
{
    bool result = false;
    if (suffix != NULL)
    {
        if (length() >= suffix->length())
        {
            result = (strncmp(&_str[length() - suffix->length()], suffix->_str, suffix->length()) == 0);
        }
    }
    return result;
}

bool String::hasSuffix(const char *suffix)
{
    DebugLog3("String::hasSuffix()");

    String *k = String::alloc()->initWithUTF8String(suffix);
    bool ret = hasSuffix(k);
    k->release();
    return ret;
}

bool String::isEqualToString(String *string)
{
    DebugLog3("String::isEqualToString(\"%s\")", string->cString());

    bool result = false;
    if (string != NULL)
    {
        if (_str == NULL)
        {
            DebugLog0("String::isEqualToString() : _str == NULL");
        }
        if (string->_str == NULL)
        {
            DebugLog0("String::isEqualToString() : string->_str == NULL");
        }
        result = (strcmp(_str, string->_str) == 0);
    }
    return result;
}

bool String::isEqualToString(const char *string)
{
    DebugLog3("String::isEqualToString(\"%s\")", string);

    String *k = String::alloc()->initWithCString(string, UTF8StringEncoding);
    bool ret = isEqualToString(k);
    k->release();
    return ret;
}

bool String::isMatch(String *regex)
{
    DebugLog3("String::isMatch()");

    bool result = false;
#if 0
    if (regex != NULL)
    {
        std::regex re(regex->cString());
        std::match_results<const char *>results;

        result = std::regex_search(_str, results, re, std::regex_constants::match_default);
    }
#else
    if (regex != NULL)
    {
        result = isMatch(regex->cString());
    }
#endif
    return result;
}

bool String::isMatch(const char *regex)
{
    DebugLog3("String::isMatch()");

    bool result = false;
    if (regex != NULL)
    {
        std::regex re(regex);
        std::match_results<const char *>results;

        result = std::regex_search(_str, results, re, std::regex_constants::match_default);
    }
    return result;
}

String *String::stringByAppendingPathComponent(String *pathComponent)
{
    DebugLog3("String::stringByAppendingPathComponent()");

    String *result = NULL;
    if (pathComponent != NULL)
    {
        Array *paths = pathComponent->pathComponents();
        if (paths != NULL)
        {
            if (paths->count() > 0)
            {
                if (((String *)paths->objectAtIndex(0))->isEqualToString("/") ||
                    ((String *)paths->objectAtIndex(0))->isEqualToString("\\"))
                {
                    paths->removeObjectAtIndex(0);
                }
                if (paths->count() > 0)
                {
                    if (hasSuffix("/") || hasSuffix("\\"))
                    {
                        result = stringByAppendingString((String *)paths->objectAtIndex(0));
                    }
                    else
                    {
                        result = stringByAppendingString("\\");
                        result = result->stringByAppendingString((String *)paths->objectAtIndex(0));
                    }
                    paths->removeObjectAtIndex(0);
                    if (paths->count() > 0)
                    {
                        for (UInteger i = 0; i < paths->count(); ++i)
                        {
                            result = result->stringByAppendingString("\\");
                            result = result->stringByAppendingString((String *)paths->objectAtIndex(i));
                        }
                    }
                }
            }
        }
    }
    if (result == NULL)
    {
        result = String::stringWithString(this);
    }
    return result;
}

String *String::stringByAppendingPathComponent(const char *pathComponent)
{
    return stringByAppendingPathComponent(String::stringWithUTF8String(pathComponent));
}

String *String::stringByAppendingString(String *aString)
{
    String *result = NULL;
    if (aString != NULL)
    {
        result = String::stringWithFormat("%s%s", _str, aString->cString());
    }
    return result;
}

String *String::stringByAppendingString(const char *aString)
{
    return stringByAppendingString(String::stringWithUTF8String(aString));
}

String *String::stringByAbbreviatingWithTildeInPath()
{
    String *result = NULL;
    result = String::stringWithUTF8String(cString());
    return result;
}

String *String::stringByReplacingOccurrencesOfString(String *target, String *replacement)
{
    String *result = NULL;
    if ((target != NULL) && (replacement != NULL))
    {
        std::string tmp = _str;
        while (strstr(tmp.c_str(), target->cString()) != NULL)
        {
            tmp.replace(tmp.find(target->cString(), 0), target->length(), replacement->cString());
        }
        result = String::stringWithUTF8String(tmp.c_str());
    }
    if (result == NULL)
    {
        result = String::stringWithUTF8String(_str);
    }
    return result;
}

String *String::stringByReplacingOccurrencesOfString(const char *target, const char *replacement)
{
    return stringByReplacingOccurrencesOfString(String::stringWithUTF8String(target),
                                                String::stringWithUTF8String(replacement));
}

String *String::stringByReplacingOccurrencesOfString(const char *target, String *replacement)
{
    return stringByReplacingOccurrencesOfString(String::stringWithUTF8String(target), replacement);
}

String *String::stringByStandardizingPath()
{
    String *result = NULL;
    result = String::stringWithUTF8String(cString());
    return result;
}

String *String::stringByRemovingPercentEncoding()
{
    String *result = NULL;
    char *buf = (char *)malloc(_length + 1);
    if (buf != NULL)
    {
        char *dst = buf;
        char *p = _str;
        while (*p != '\0')
        {
            if (*p == '%')
            {
                *dst = (HEX2DEC(p[1]) << 4) | HEX2DEC(p[2]);
                DebugLog3("%02x", *dst);
                ++dst;
                p += 3;
            }
            else
            {
                *(dst++) = *(p++);
            }
        }
        *dst = '\0';
        DebugLog3("buf: %s", buf);
        result = String::stringWithUTF8String(buf);
        free(buf);
    }
    else
    {
        result = String::string();
    }
    return result;
}

String *String::stringByTrimming()
{
    String *result = NULL;
#ifdef _WIN32
    char *buf = _strdup(_str);
#else
    char *buf = strdup(_str);
#endif
    if (buf != NULL)
    {
        char *st = buf;
        while (true)
        {
            if (*st == ' ')
            {
                ++st;
            }
            else
            {
                break;
            }
        }
        while (true)
        {
            if (st[strlen(st) - 1] == ' ')
            {
                st[strlen(st) - 1] = '\0';
            }
            else
            {
                break;
            }
        }
        result = String::stringWithUTF8String(st);
        free(buf);
    }
    else
    {
        result = String::string();
    }
    return result;
}

Array *String::pathComponents()
{
    Array *result = Array::arrayWithCapacity(0);
#ifdef _WIN32
    char *tmp = _strdup(_str);
#else
    char *tmp = strdup(_str);
#endif
    if (tmp != NULL)
    {
        char *p = tmp;
        if ((*p == '\\') || (*p == '/'))
        {
            result->addObject(String::stringWithUTF8String("\\"));
            ++p;
        }
        while (p != NULL)
        {
            char *p2 = strchr(p, '\\');
            if (p2 == NULL)
            {
                p2 = strchr(p, '/');
            }
            if (p2 != NULL)
            {
                *p2 = '\0';
            }
            if (strlen(p) > 0)
            {
                result->addObject(String::stringWithUTF8String(p));
            }
            if (p2 != NULL)
            {
                p = p2 + 1;
            }
            else
            {
                p = NULL;
            }
        }

        free(tmp);
    }
    return result;
}

String *String::pathExtension()
{
    String *result = NULL;
    Array *comps = pathComponents();
    if (comps != NULL)
    {
        if (comps->count() > 0)
        {
            String *last = (String *)comps->objectAtIndex(comps->count() - 1);
            char *p = strrchr(last->_str, '.');
            if (p != NULL)
            {
                result = String::stringWithUTF8String(++p);
            }
        }
    }
    if (result == NULL)
    {
        result = String::stringWithUTF8String("");
    }
    return result;
}

Array *String::componentsSeparatedByString(String *separator)
{
    return componentsSeparatedByString(separator->cString());
}

Array *String::componentsSeparatedByString(const char *separator)
{
    Array *result = Array::arrayWithCapacity(0);
#ifdef _WIN32
    char *tmp = _strdup(_str);
#else
    char *tmp = strdup(_str);
#endif
    char *p1 = tmp;
    while (true)
    {
        char *p2 = strstr(p1, separator);
        if (p2 != NULL)
        {
            *p2 = '\0';
            result->addObject(String::stringWithUTF8String(p1));
            p1 = p2 + strlen(separator);
        }
        else
        {
            result->addObject(String::stringWithUTF8String(p1));
            break;
        }
    }
    free(tmp);
    return result;
}

String *String::substringFromIndex(UInteger anIndex)
{
    String *result = NULL;
    if (anIndex < _length)
    {
        result = String::stringWithUTF8String(&_str[anIndex]);
    }
    return result;
}

String *String::substringToIndex(UInteger anIndex)
{
    String *result = NULL;
    if (anIndex < _length)
    {
#ifdef _WIN32
        char *tmp = _strdup(_str);
#else
        char *tmp = strdup(_str);
#endif
        if (tmp != NULL)
        {
            tmp[anIndex] = '\0';
            result = String::stringWithUTF8String(tmp);
            free(tmp);
        }
    }
    else if (anIndex == _length)
    {
        result = String::stringWithUTF8String(_str);
    }
    return result;
}

String *String::lowercaseString()
{
    String *result = NULL;
#ifdef _WIN32
    char *tmp = _strdup(_str);
#else
    char *tmp = strdup(_str);
#endif
    if (tmp != NULL)
    {
        for (size_t idx = 0; idx < strlen(tmp); ++idx)
        {
            tmp[idx] = tolower(tmp[idx]);
        }
        result = String::stringWithUTF8String(tmp);
        free(tmp);
    }
    if (result == NULL)
    {
        result = String::stringWithUTF8String(_str);
    }
    return result;
}

Range String::rangeOfString(String *aString)
{
    Range result = {NotFound, 0};
    if (aString != NULL)
    {
        result = rangeOfString(aString->cString());
    }
    return result;
}

Range String::rangeOfString(const char *aString)
{
    Range result = {NotFound, 0};
    if (aString != NULL)
    {
        char *p = strstr(_str, aString);
        if (p != NULL)
        {
            result.location = (UInteger)(p - _str);
            result.length = (UInteger)strlen(aString);
        }
    }
    return result;
}

int String::intValue()
{
    return atoi(_str);
}

const char *String::cString()
{
    DebugLog2("String::cString()\n");

    if (_str == NULL)
    {
        DebugLog3("why?\n");
    }

    return _str;
}

const char *String::className()
{
    return "String";
}

String *String::description()
{
    return this;
}

} // Raym
