/*
 * ************************************************************************************************************
 * Author: Marcin Załęczny
 * Copyright (C)2010 Marcin Załęczny <mzaleczny@hotmail.com>
 * License: The GNU Lesser General Public License, version 3.0 (LGPLv3)
 * Library Name: mzstd
 * Version: 1.07
 * First release date:   2010-11-08
 * Current release date: 2011-03-04
 *
 *
 * This file is part of mzstd library
 * 
 * mzstd library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * mzstd library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * ************************************************************************************************************
 */

#include "mzString.h"
#include <wchar.h>
#include <stdio.h>
#include "mzLocale.h"
#include "mzMath.h"
#include "mzStringList.h"
#include "mzArrayUtils.h"

#define DEFAULT_STRING_BUFFER_LENGTH	128

#define FORMAT_PARSING_STATE_NONE           0
#define FORMAT_PARSING_STATE_READFLAGS      1
#define FORMAT_PARSING_STATE_READWIDTH      2
#define FORMAT_PARSING_STATE_READPRECISION  3
#define FORMAT_PARSING_STATE_READTYPE       4

#define FORMAT_DEFAULT_PRECISION            4

#define WORD_BOUNDARY_CHARS     L" \t.,;?!"

// --------------------          ą       ć       ę       ł       ń       ó       ś       ź       ż       Ą       Ć       Ę       Ł       Ń       Ó       Ś       Ź       Ż
BYTE allowed_codes_ansi[]	 = {  0xb9,   0xe6,   0xea,   0xb3,   0xf1,   0xf3,   0x9c,   0x9f,   0xbf,   0xa5,   0xc6,   0xca,   0xa3,   0xd1,   0xd3,   0x8c,   0x8f,   0xaf };
BYTE allowed_codes_iso2[]	 = {  0xb1,   0xe6,   0xea,   0xb3,   0xf1,   0xf3,   0xb6,   0xbc,   0xbf,   0xa1,   0xc6,   0xca,   0xa3,   0xd1,   0xd3,   0xa6,   0xac,   0xaf };
BYTE allowed_codes_oem_852[] = {  0xa5,   0x86,   0xa9,   0x88,   0xe4,   0xa2,   0x98,   0xab,   0xbe,   0xa4,   0x8f,   0xa8,   0x9d,   0xe3,   0xe0,   0x97,   0x8d,   0xbd };
WORD allowed_codes_utf8[]	 = {0x85c4, 0x87c4, 0x99c4, 0x82c5, 0x84c5, 0xb3c3, 0x9bc5, 0xbac5, 0xbcc5, 0x84c4, 0x86c4, 0x98c4, 0x81c5, 0x83c5, 0x93c3, 0x9ac5, 0xb9c5, 0xbbc5 };
WORD allowed_codes_unicode[] = {0x0105, 0x0107, 0x0119, 0x0142, 0x0144, 0x00f3, 0x015b, 0x017a, 0x017c, 0x0104, 0x0106, 0x0118, 0x0141, 0x0143, 0x00d3, 0x015a, 0x0179, 0x017b };


LONG WINAPI DetectDataEncoding(BYTE *buf, ULONG buflen) {
	if (buflen >= 3 && buf[0] == (BYTE)0xef && buf[1] == (BYTE)0xbb && buf[2] == (BYTE)0xbf) {
		return ENC_UTF8_WITH_BOM;
	}
	ULONG i;
	BOOL is_utf8 = TRUE;
	BOOL is_unicode = TRUE;
	BOOL is_ansi = TRUE;
	BOOL is_iso2 = TRUE;
    BOOL is_oem_852 = TRUE;
	BYTE b;
	WORD w;


	// najpierw sprawdzamy czy UTF8
	for (i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII lub znak końca linii, to pasuje do każdego kodowania
		if (b == (BYTE)0x0a || b == (BYTE)0x0d || (b >= (BYTE)0x20 && b <= (BYTE)0x7f)) {
			continue;
		}

		// sprawdzaymy czy dopuszczalny znak utf8
		// pobieramy słowo
		w = 0;
		if (i + 1 < buflen) {
			w = buf[i+1];
			w <<= 8;
			w |= b;
		}

		if (!in_array(allowed_codes_utf8, sizeof(allowed_codes_utf8), w)) {
			is_utf8 = FALSE;
			break;
		}

		i++;
	}

	if (is_utf8) {
		return ENC_UTF8;
	}


	// sprawdzamy czy UNICODE
	for (i = 0; i < buflen; i+=2) {
		b = buf[i];
		// pobieramy słowo
		w = 0;
		if (i + 1 < buflen) {
			w = buf[i+1];
			w <<= 8;
			w |= b;
		}

		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (w >= (WORD)0x20 && w <= (WORD)0x7f) {
			continue;
		}


		if (!in_array(allowed_codes_unicode, sizeof(allowed_codes_unicode), w)) {
			is_unicode = FALSE;
			break;
		}
	}

	if (is_unicode) {
		return ENC_UNICODE;
	}

	// now we check if have one-byte encoding such as ANSI/ISO2/OEM852
	for (i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (b >= (BYTE)0x20 && b <= (BYTE)0x7f) {
			continue;
		}

		// sprawdzaymy czy dopuszczalny znak ansi
		if (is_ansi) {
			if (!in_array(allowed_codes_ansi, sizeof(allowed_codes_ansi), b)) {
				is_ansi = FALSE;
			}
		}

		// sprawdzaymy czy dopuszczalny znak ansi
		if (is_iso2) {
			if (!in_array(allowed_codes_iso2, sizeof(allowed_codes_iso2), b)) {
				is_iso2 = FALSE;
			}
		}

		// sprawdzaymy czy dopuszczalny znak oem_852
		if (is_oem_852) {
			if (!in_array(allowed_codes_oem_852, sizeof(allowed_codes_oem_852), b)) {
				is_oem_852 = FALSE;
			}
		}

		// jeśli wszystkie kodowania jednobajtowe już odpadły, to nie sprwdzamy dalej
		if (!is_ansi && !is_iso2 && !is_oem_852) {
			break;
		}
	}

    // We accept one-byte encoding in order: ANSI, ISO2, OEM_852
	if (is_ansi) {
		return ENC_ANSI;
	}

	if (is_iso2) {
		return ENC_ISO2;
	}

	if (is_oem_852) {
		return ENC_OEM_852;
	}

	return 0;
}


VOID WINAPI Iso2ToAnsi(BYTE *buf, ULONG buflen) {
	BYTE b;
	for (ULONG i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (b >= (BYTE)0x20 && b <= (BYTE)0x7f) {
			continue;
		}

		for (ULONG k = 0; k < sizeof(allowed_codes_iso2); k++) {
			if (b == allowed_codes_iso2[k]) {
				buf[i] = allowed_codes_ansi[k];
				break;
			}
		}
	}
}


VOID WINAPI AnsiToIso2(BYTE *buf, ULONG buflen) {
	BYTE b;
	for (ULONG i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (b >= (BYTE)0x20 && b <= (BYTE)0x7f) {
			continue;
		}

		for (ULONG k = 0; k < sizeof(allowed_codes_ansi); k++) {
			if (b == allowed_codes_ansi[k]) {
				buf[i] = allowed_codes_iso2[k];
				break;
			}
		}
	}
}

DECLSPEC_MZSTD VOID WINAPI Oem852ToAnsi(BYTE *buf, ULONG buflen) {
	BYTE b;
	for (ULONG i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (b >= (BYTE)0x20 && b <= (BYTE)0x7f) {
			continue;
		}

		for (ULONG k = 0; k < sizeof(allowed_codes_oem_852); k++) {
			if (b == allowed_codes_oem_852[k]) {
				buf[i] = allowed_codes_ansi[k];
				break;
			}
		}
	}
}

DECLSPEC_MZSTD VOID WINAPI AnsiToOem852(BYTE *buf, ULONG buflen) {
	BYTE b;
	for (ULONG i = 0; i < buflen; i++) {
		b = buf[i];
		// jeśli mamy znak ASCII, to pasuje do każdego kodowania
		if (b >= (BYTE)0x20 && b <= (BYTE)0x7f) {
			continue;
		}

		for (ULONG k = 0; k < sizeof(allowed_codes_ansi); k++) {
			if (b == allowed_codes_ansi[k]) {
				buf[i] = allowed_codes_oem_852[k];
				break;
			}
		}
	}
}

WORD WINAPI getCharType(WCHAR ch) {
    WORD type;
    BOOL result = GetStringTypeEx(LOCALE_NAME_USER_DEFAULT, CT_CTYPE1, &ch, 1, &type);
    if (result) {
        return type;
    }

    return 0;
}

BOOL WINAPI isCharUpper(WCHAR ch) {
    return getCharType(ch) & C1_UPPER;
}

BOOL WINAPI isCharLower(WCHAR ch) {
    return getCharType(ch) & C1_LOWER;
}

BOOL WINAPI isCharDigit(WCHAR ch) {
    return getCharType(ch) & C1_DIGIT;
}

BOOL WINAPI isCharSpace(WCHAR ch) {
    return getCharType(ch) & C1_SPACE;
}

BOOL WINAPI isCharPunct(WCHAR ch) {
    return getCharType(ch) & C1_PUNCT;
}

BOOL WINAPI isCharCntrl(WCHAR ch) {
    return getCharType(ch) & C1_CNTRL;
}

BOOL WINAPI isCharBlank(WCHAR ch) {
    return getCharType(ch) & C1_BLANK;
}

BOOL WINAPI isCharXdigit(WCHAR ch) {
    return getCharType(ch) & C1_XDIGIT;
}

BOOL WINAPI isCharAlpha(WCHAR ch) {
    return getCharType(ch) & C1_ALPHA;
}

BOOL WINAPI isCharDefined(WCHAR ch) {
    return getCharType(ch) & C1_DEFINED;
}

BOOL WINAPI isCharAlphanumeric(WCHAR ch) {
    WORD type = getCharType(ch);
    return (type & C1_ALPHA) || (type & C1_DIGIT);
}

BOOL WINAPI isCharPrintable(WCHAR ch) {
    WORD type = getCharType(ch);
    return !(type & C1_CNTRL);
}

BOOL WINAPI isCharSentenceBoundary(WCHAR ch) {
    if (wcschr(WORD_BOUNDARY_CHARS, ch) != NULL) {
        return TRUE;
    }

    return FALSE;
}

BOOL WINAPI isDecimal(WCHAR* str) {
    if (!str) return FALSE;

    LONG len = wcslen(str);
    if (len < 1) return FALSE;

    for (LONG i = 0; i < len; i++) {
        if (i == 0 && str[i] == L'-') {
            continue;
        }

        if (!iswdigit(str[i])) {
            return FALSE;
        }
    }

    return TRUE;
}

BOOL WINAPI isDouble(WCHAR* str) {
    if (!str) return FALSE;

    LONG len = wcslen(str);
    BOOL hasDot = FALSE;
    WCHAR ch;

    if (len < 1) return FALSE;

    for (LONG i = 0; i < len; i++) {
        ch = str[i];

        if (i == 0 && ch == L'-') {
            continue;
        }

        if (!hasDot && (ch == L'.' || ch == L',')) {
            hasDot = TRUE;
            continue;
        }

        if (!iswdigit(ch)) {
            return FALSE;
        }
    }

    return TRUE;
}

CString WINAPI FormatString(const CString& s, LONG align, WCHAR fillChar, ULONG width) {
    CString result(s);
    ULONG len = result.length();

    if (width > 0 && width > len) {
        ULONG paddingLen = width - len;
        CString padding = CString::makeString(fillChar, paddingLen);
        if (align == FORMAT_ALIGN_RIGHT) {
            result.prepend(padding);
        } else {
            result.append(padding);
        }
    }

    return result;
}

CString WINAPI FormatLongValue(LONG value, LONG align, WCHAR fillChar, ULONG width) {
    CString result(value);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatULongValue(ULONG value, LONG align, WCHAR fillChar, ULONG width) {
    CString result(value);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatBoolValue(BOOL value, BOOL uppercase, LONG align, WCHAR fillChar, ULONG width) {
    WCHAR uc = L'T';
    if (!uppercase) {
        uc = L't';
    }
    CString result(value, uc);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatCharValue(CHAR value, LONG align, WCHAR fillChar, ULONG width) {
    CString result(value);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatWcharValue(WCHAR value, LONG align, WCHAR fillChar, ULONG width) {
    CString result(value);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatFloatValue(float value, LONG align, WCHAR fillChar, ULONG width, LONG precision) {
    if (precision == 0) {
        precision = FORMAT_DEFAULT_PRECISION;
    }

    CString result(value, precision);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatDoubleValue(double value, LONG align, WCHAR fillChar, ULONG width, LONG precision) {
    if (precision == 0) {
        precision = FORMAT_DEFAULT_PRECISION;
    }

    CString result(value, precision);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatHexValue(ULONG value, LONG align, WCHAR fillChar, ULONG width, BOOL prepend0x, BOOL appendH, BOOL useSmallAlpha, LONG minLen) {
    CString result = intToHex(value, minLen, useSmallAlpha, prepend0x);
    if (appendH) {
        result += L'h';
    }
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatStringValue(WCHAR* value, LONG align, WCHAR fillChar, ULONG width) {
    CString result(value);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatTimeValue(SYSTEMTIME* value, LONG align, WCHAR fillChar, ULONG width) {
    CString result = CString((ULONG)value->wHour, FALSE, 2) + L":" + CString((ULONG)value->wMinute, FALSE, 2) + L":" +
                     CString((ULONG)value->wSecond, FALSE, 2) + L"." + CString((ULONG)value->wMilliseconds, FALSE, 5);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatDateValue(SYSTEMTIME* value, LONG align, WCHAR fillChar, ULONG width) {
    CString result = CString((ULONG)value->wYear, FALSE, 4) + L"-" + CString((ULONG)value->wMonth, FALSE, 2) + L"-" +
                     CString((ULONG)value->wDay, FALSE, 2);
    return FormatString(result, align, fillChar, width);
}

CString WINAPI FormatDateTimeValue(SYSTEMTIME* value, LONG align, WCHAR fillChar, ULONG width) {
    CString result = CString((ULONG)value->wYear, FALSE, 4) + L"-" + CString((ULONG)value->wMonth, FALSE, 2) + L"-" +
                     CString((ULONG)value->wDay, FALSE, 2) + L" " +
                     CString((ULONG)value->wHour, FALSE, 2) + L":" + CString((ULONG)value->wMinute, FALSE, 2) + L":" +
                     CString((ULONG)value->wSecond, FALSE, 2) + L"." + CString((ULONG)value->wMilliseconds, FALSE, 5);
    return FormatString(result, align, fillChar, width);
}

CString __cdecl FormatMessage(WCHAR* format, va_list paramList) {
    // format parameter is a string that can contain fields of following formats:
    // %[flags][width][.precision]type
    //     [flags] can be one CHAR of following:
    //          - - align value to the left of space defined by [width] parameter (default align to the right)
    //          _(WCHAR) - defines filling CHAR (default is space: ' ') for padding value if [width] value
    //                     is greater than text length of the value
    //     [width] is number defining CHAR width of the string in which value is placed. If [width] is greater
    //             than text length of the value then the rest of the place is filled by filling CHAR.
    //     [.precision] - in case of floating point values defines number of digits after period to print (default = 4)
    //                    in other cases it doesn't matter
    //     type:
    //         d   - LONG value
    //         dbx - LONG value with hex (BYTE size)
    //         dwx - LONG value with hex (WORD size)
    //         ddx - LONG value with hex (DWORD size)
    //         ud  - UNSIGNED LONG value
    //         f   - floating point value
    //         fd  - double value
    //         b   - boolean value (true/false)
    //         B   - boolean value (TRUE/FALSE)
    //         tm  - insert current time (no value is get from parameters stack) in format: 'hh:mm:ss.ms'
    //         dt  - insert current date (no value is get from parameters stack) in format: 'yyyy-mm-dd'
    //         dtm - insert current date time (no value is get from parameters stack) in format: 'yyyy-mm-dd hh:mm:ss.ms'
    //         x   - UNSIGNED LONG value presented as hex, use small hex digits
    //         #x  - UNSIGNED LONG value presented as hex preceded by L"0x", use small hex digits
    //         xh  - UNSIGNED LONG value presented as hex followed by L'h', use small hex digits
    //         #9x - UNSIGNED LONG value presented as hex preceded by L"0x" and left-padded by L'0', use small hex digits
    //               to the specified count
    //         x9h - UNSIGNED LONG value presented as hex followed by L'h' and left-padded by L'0'
    //               to the specified count, use small hex digits
    //         X   - UNSIGNED LONG value presented as hex, use large hex digits
    //         #X  - UNSIGNED LONG value presented as hex preceded by L"0x", use large hex digits
    //         Xh  - UNSIGNED LONG value presented as hex followed by L'h', use large hex digits
    //         #9X - UNSIGNED LONG value presented as hex preceded by L"0x" and left-padded by L'0', use large hex digits
    //               to the specified count
    //         X9h - UNSIGNED LONG value presented as hex followed by L'h' and left-padded by L'0'
    //               to the specified count, use large hex digits
    //         p   - alias for #8x
    //         P   - alias for #8X
    //         s   - wide string value (parameter must be WCHAR*)
    //         sc  - string value (parameter must be CHAR*)
    //         cs  - string value (parameter must be CString*)
    //         o   - object value (parameter must be CObject*) - prints result of o->toString()
    //         c   - char value (parameter must be CHAR)
    //         wc  - wide char value (parameter must be WCHAR)
    CString result;
    CString token;
    DWORD len = wcslen(format);
    ULONG i;
    WCHAR ch;
    BOOL token_started = FALSE;
    LONG fieldAlign = FORMAT_ALIGN_RIGHT;
    LONG fieldWidth = 0;
    LONG fieldPrecision = 0;
    WCHAR fieldFillChar = L' ';
    LONG fieldReadingState = FORMAT_PARSING_STATE_NONE;
    LONG longValue;
    ULONG ulongValue;
    BOOL boolValue;
    WCHAR* wstrValue;
    WCHAR wcharValue;
    CHAR* strValue;
    CHAR charValue;
    //float floatValue;
    double doubleValue;
    LONG doExtraIncrement = 0;
    BOOL valueFormatted = FALSE;

    i = 0;
    while (i < len) {
        ch = format[i];
        i++;
        if (ch != L'%' && !token_started) {
            result += ch;
        } else {
            if (ch == L'%') {
                if (!token_started) {
                    token_started = TRUE;
                    fieldReadingState = FORMAT_PARSING_STATE_READFLAGS;
                } else {
                    result += ch;
                }
            } else {
                switch (fieldReadingState) {
                    case FORMAT_PARSING_STATE_READFLAGS: // read flags
                        if (ch == L'-') {
                            fieldAlign = FORMAT_ALIGN_LEFT;
                            token = COMMON_EMPTY_STRING;
                            continue;
                        }
                        if (ch == L'_') {
                            if (i < len) {
                                fieldFillChar = format[i];
                                fieldReadingState = FORMAT_PARSING_STATE_READWIDTH;
                                token = COMMON_EMPTY_STRING;
                                i++;
                            }
                            continue;
                        }
                        fieldReadingState = FORMAT_PARSING_STATE_READWIDTH;
                        i--;
                        break;
                    case FORMAT_PARSING_STATE_READWIDTH: // read width
                        if (isCharDigit(ch)) {
                            token += ch;
                            continue;
                        }
                        if (token.length() > 0) {
                            fieldWidth = token.toInt();
                        }
                        if (ch == L'.') {
                            fieldReadingState = FORMAT_PARSING_STATE_READPRECISION;
                            token = COMMON_EMPTY_STRING;
                            continue;
                        }
                        fieldReadingState = FORMAT_PARSING_STATE_READTYPE;
                        token = COMMON_EMPTY_STRING;
                        i--;
                        break;
                    case FORMAT_PARSING_STATE_READPRECISION: // read precision
                        if (isCharDigit(ch)) {
                            token += ch;
                            continue;
                        }
                        if (token.length() > 0) {
                            fieldPrecision = token.toInt();
                        }
                        fieldReadingState = FORMAT_PARSING_STATE_READTYPE;
                        token = COMMON_EMPTY_STRING;
                        i--;
                        break;
                    case FORMAT_PARSING_STATE_READTYPE: // read type
                        doExtraIncrement = 0;
                        valueFormatted = FALSE;
                        // Format time value
                        if (!valueFormatted && ch == L't' && i < len && format[i] == L'm') {
                            SYSTEMTIME tm;
                            GetLocalTime(&tm);
                            result += FormatTimeValue(&tm, fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format date-time value
                        if (!valueFormatted && ch == L'd' && i < len && format[i] == L't' && i + 1 < len && format[i+1] == L'm') {
                            SYSTEMTIME tm;
                            GetLocalTime(&tm);
                            result += FormatDateTimeValue(&tm, fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format date value
                        if (!valueFormatted && ch == L'd' && i < len && format[i] == L't') {
                            SYSTEMTIME tm;
                            GetLocalTime(&tm);
                            result += FormatDateValue(&tm, fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format decimal BYTE value with hex
                        if (!valueFormatted && ch == L'd' && i < len && format[i] == L'b' && i+1 < len && format[i+1] == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatLongValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth) + L" [";
                            result += FormatHexValue(ulongValue, FORMAT_ALIGN_LEFT, L'0', 0, TRUE, FALSE, TRUE, 2) + L"]";
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format decimal WORD value with hex
                        if (!valueFormatted && ch == L'd' && i < len && format[i] == L'w' && i+1 < len && format[i+1] == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatLongValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth) + L" [";
                            result += FormatHexValue(ulongValue, FORMAT_ALIGN_LEFT, L'0', 0, TRUE, FALSE, TRUE, 4) + L"]";
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format decimal DWORD value with hex
                        if (!valueFormatted && ch == L'd' && i < len && format[i] == L'd' && i+1 < len && format[i+1] == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatLongValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth) + L" [";
                            result += FormatHexValue(ulongValue, FORMAT_ALIGN_LEFT, L'0', 0, TRUE, FALSE, TRUE, 8) + L"]";
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format decimal value
                        if (!valueFormatted && ch == L'd') {
                            longValue = va_arg(paramList, LONG);
                            result += FormatLongValue(longValue, fieldAlign, fieldFillChar, fieldWidth);
                            valueFormatted = TRUE;
                        }
                        // Format boolean value
                        if (!valueFormatted && (ch == L'b' || ch == L'B')) {
                            boolValue = va_arg(paramList, BOOL);
                            BOOL uppercase = TRUE;
                            if (ch == L'b') {
                                uppercase = FALSE;
                            }
                            result += FormatBoolValue(boolValue, uppercase, fieldAlign, fieldFillChar, fieldWidth);
                            valueFormatted = TRUE;
                        }
                        // Format unsigned decimal value
                        if (!valueFormatted && ch == L'u' && i < len && format[i] == L'd') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatULongValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format double value
                        if (!valueFormatted && ch == L'f' && i < len && format[i] == L'd') {
                            doubleValue = va_arg(paramList, double);
                            result += FormatDoubleValue(doubleValue, fieldAlign, fieldFillChar, fieldWidth, fieldPrecision);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format float value
                        if (!valueFormatted && ch == L'f') {
                            // float - 4bytes, double - 8bytes
                            // 2 lines of code below is OK, but it shouldn't
                            doubleValue = va_arg(paramList, double);
                            result += FormatDoubleValue(doubleValue, fieldAlign, fieldFillChar, fieldWidth, fieldPrecision);
                            // --- Below code doesn't work! Don't know why ---
                            /*
                            floatValue = va_arg(paramList, float); // Here is wrong getting parameter
                            wprintf(L"---%f\n", floatValue); // floatValue is ERROR
                            result += FormatFloatValue(floatValue, fieldAlign, fieldFillChar, fieldWidth, fieldPrecision);
                            */
                            valueFormatted = TRUE;
                        }
                        // Format pointer value
                        if (!valueFormatted && (ch == L'p' || ch == L'P')) {
                            ulongValue = va_arg(paramList, ULONG);
                            if (ch == L'p') {
                                result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, TRUE, 8);
                            } else {
                                result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, FALSE, 8);
                            }
                            valueFormatted = TRUE;
                        }
                        // Format string value
                        if (!valueFormatted && ch == L's' && i < len && format[i] == L'c') {
                            strValue = va_arg(paramList, CHAR*);
                            CString s(strValue);
                            result += FormatStringValue(s.getBuffer(), fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format wide string value
                        if (!valueFormatted && ch == L's') {
                            wstrValue = va_arg(paramList, WCHAR*);
                            CString s(wstrValue);
                            result += FormatStringValue(s.getBuffer(), fieldAlign, fieldFillChar, fieldWidth);
                            valueFormatted = TRUE;
                        }
                        // Format cstring value
                        if (!valueFormatted && ch == L'c' && i < len && format[i] == L's') {
                            CString* s = va_arg(paramList, CString*);
                            result += FormatStringValue(s->getBuffer(), fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format cstring value
                        if (!valueFormatted && ch == L'c') {
                            charValue = va_arg(paramList, CHAR);
                            result += FormatCharValue(charValue, fieldAlign, fieldFillChar, fieldWidth);
                            valueFormatted = TRUE;
                        }
                        // Format cstring value
                        if (!valueFormatted && ch == L'w' && i < len && format[i] == L'c') {
                            wcharValue = va_arg(paramList, WCHAR);
                            result += FormatWcharValue(wcharValue, fieldAlign, fieldFillChar, fieldWidth);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format object value
                        if (!valueFormatted && ch == L'o') {
                            CObject* obj = va_arg(paramList, CObject*);
                            result += FormatStringValue(obj->toString().getBuffer(), fieldAlign, fieldFillChar, fieldWidth);
                            valueFormatted = TRUE;
                        }
                        // Format #9x value
                        if (!valueFormatted && ch == L'#' && i < len && isCharDigit(format[i]) && i+1 < len && format[i+1] == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, TRUE, format[i] - L'0');
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format #9X value
                        if (!valueFormatted && ch == L'#' && i < len && isCharDigit(format[i]) && i+1 < len && format[i+1] == L'X') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, FALSE, format[i] - L'0');
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format #x9h value
                        if (!valueFormatted && ch == L'x' && i < len && isCharDigit(format[i]) && i+1 < len && format[i+1] == L'h') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, TRUE, TRUE, format[i] - L'0');
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format X9h value
                        if (!valueFormatted && ch == L'X' && i < len && isCharDigit(format[i]) && i+1 < len && format[i+1] == L'h') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, TRUE, FALSE, format[i] - L'0');
                            doExtraIncrement = 2;
                            valueFormatted = TRUE;
                        }
                        // Format xh value
                        if (!valueFormatted && ch == L'x' && i < len && format[i] == L'h') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, TRUE, TRUE, 1);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format Xh value
                        if (!valueFormatted && ch == L'X' && i < len && format[i] == L'h') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, TRUE, FALSE, 1);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format x (lowercase hex) value
                        if (!valueFormatted && ch == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, FALSE, TRUE, 1);
                            valueFormatted = TRUE;
                        }
                        // Format X (uppercase hex) value
                        if (!valueFormatted && ch == L'X') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, FALSE, FALSE, FALSE, 1);
                            valueFormatted = TRUE;
                        }
                        // Format #x value
                        if (!valueFormatted && ch == L'#' && i < len && format[i] == L'x') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, TRUE, 1);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        // Format #X value
                        if (!valueFormatted && ch == L'#' && i < len && format[i] == L'X') {
                            ulongValue = va_arg(paramList, ULONG);
                            result += FormatHexValue(ulongValue, fieldAlign, fieldFillChar, fieldWidth, TRUE, FALSE, FALSE, 1);
                            doExtraIncrement = 1;
                            valueFormatted = TRUE;
                        }
                        token_started = FALSE;
                        token = COMMON_EMPTY_STRING;
                        fieldAlign = FORMAT_ALIGN_RIGHT;
                        fieldWidth = 0;
                        fieldPrecision = 0;
                        fieldFillChar = L' ';
                        fieldReadingState = FORMAT_PARSING_STATE_NONE;
                        i += doExtraIncrement;
                        break;
                    default:
                        break;
                }
            }
        }
    }

    return result;
}

CString __cdecl FormatMessage(WCHAR* format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString result = FormatMessage(format, paramList);
    va_end(paramList);

    return result;
}

VOID __cdecl Stdout(CString format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString s = FormatMessage(format.toWCHAR(), paramList);
    fwprintf(stdout, L"%s", s.getBuffer());
    fflush(stdout);
    va_end(paramList);
}

VOID __cdecl Stdout(WCHAR* format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString s = FormatMessage(format, paramList);
    fwprintf(stdout, L"%s", s.getBuffer());
    fflush(stdout);
    va_end(paramList);
}

VOID __cdecl Stderr(WCHAR* format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString s = FormatMessage(format, paramList);
    fwprintf(stderr, L"%s", s.getBuffer());
    fflush(stderr);
    va_end(paramList);
}

VOID __cdecl StdoutInEnc(LONG outputEncoding, WCHAR* format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString s = FormatMessage(format, paramList);
    va_end(paramList);

    ULONG str_len = s.length();
    ULONG buf_size = str_len + 1;
    CHAR* outputstr = new CHAR[buf_size];
    if (outputstr) {
        if (outputEncoding == ENC_ANSI) {
            s.toAnsi(outputstr, buf_size);
        } else if (outputEncoding == ENC_ISO2) {
            s.toIso2(outputstr, buf_size);
        } else if (outputEncoding == ENC_OEM_852) {
            s.toOem852(outputstr, buf_size);
        } else {
            s.toAnsi(outputstr, buf_size);
        }
        fprintf(stdout, "%s", outputstr);
        fflush(stdout);
        delete[] outputstr;
    }
}

VOID __cdecl StderrInEnc(LONG outputEncoding, WCHAR* format, ...) {
    va_list paramList;
    va_start(paramList, format);
    CString s = FormatMessage(format, paramList);
    ULONG str_len = s.length();
    CHAR* outputstr = new CHAR[str_len+1];
    if (outputstr) {
        if (outputEncoding == ENC_ANSI) {
            s.toAnsi(outputstr, str_len);
        } else if (outputEncoding == ENC_ISO2) {
            s.toIso2(outputstr, str_len);
        } else if (outputEncoding == ENC_OEM_852) {
            s.toOem852(outputstr, str_len);
        } else {
            s.toAnsi(outputstr, str_len);
        }
        fprintf(stderr, "%s", outputstr);
        fflush(stderr);
        delete[] outputstr;
    }
    va_end(paramList);
}





VOID CString::_commonInit() {
	this->_growBy = DEFAULT_STRING_BUFFER_LENGTH;
    this->buf_size = 0;
    this->buf = NULL;

    DWORD bufsize = wcslen(::cstringClassName) + 1;
    this->_originClassName = new WCHAR[bufsize];
    if (this->_originClassName) {
        wcscpy_s(this->_originClassName, bufsize, ::cstringClassName);
    }
    this->_realClassName = new WCHAR[64];
    if (this->_realClassName) {
        wcscpy_s(this->_realClassName, 64, ::cstringClassName);
    }
}

CString::CString() {
	this->_commonInit();
	this->initBuffer();
}

CString::CString(const CString &s) {
	this->_commonInit();
	this->initBuffer(s.getBufferSize());
    // copy all memory
	CopyMemory((VOID*)this->buf, (VOID*)s.getBuffer(), s.getBufferSize() * 2);
}

CString::CString(const WCHAR *string) {
	this->_commonInit();
	if (string == NULL || string[0] == NULL) {
		this->initBuffer();
	} else {
		this->initBuffer(wcslen(string) + 1);
		wcscat_s(this->buf, this->buf_size, string);
	}
}

CString::CString(WCHAR *string, ULONG buf_size) {
	this->_commonInit();
	this->buf = NULL;
	this->setBuffer(string, buf_size);
}

CString::CString(const CHAR* string) {
	this->_commonInit();
	if (string == NULL || string[0] == NULL) {
		this->initBuffer();
	} else {
        ULONG buf_size = strlen(string) + 1;
		this->initBuffer(buf_size);
        LONG encoding = DetectDataEncoding((BYTE*)string, buf_size);
        if (encoding == ENC_UTF8) {
            MultiByteToWideChar(CP_UTF8, 0, string, -1, this->buf, this->buf_size);
        } else {
            MultiByteToWideChar(CP_ACP, 0, string, -1, this->buf, this->buf_size);
        }
	}
}

CString::CString(WCHAR ch) {
	this->_commonInit();
	this->initBuffer();
	this->buf[0] = ch;
	this->buf[1] = NULL;
}

CString::CString(WCHAR ch, LONG repeatCount) {
	this->_commonInit();
	this->initBuffer(repeatCount + 1);
	for (LONG i = 0; i < repeatCount; i++) {
		this->buf[i] = ch;
	}
	this->buf[repeatCount] = NULL;
}

CString::CString(CHAR ch) {
	this->_commonInit();
	this->initBuffer();
	CHAR tmp[2];
	tmp[0] = ch;
	tmp[1] = NULL;
	MultiByteToWideChar(CP_ACP, 0, tmp, -1, this->buf, this->buf_size);
}

CString::CString(UCHAR ch) {
	this->_commonInit();
	this->initBuffer();
    /*
	WCHAR w;
    if (ch < 127) {
        w = ch;
    } else {
        UCHAR* ptr = (UCHAR*)&w;
        ptr[0] = 0xc0 | ((ch & 0xc0) >> 24);
        ptr[1] = ((ch & 0x3f) | 0x80);
    }
    this->buf[0] = w;
    this->buf[1] = NULL;
    */
    UCHAR tmp[2];
	tmp[0] = ch;
	tmp[1] = NULL;
	MultiByteToWideChar(CP_ACP, 0, (LPCSTR)tmp, -1, this->buf, this->buf_size);
}

CString::CString(INT number, BOOL toHex, LONG minDigits) {
	this->_commonInit();
	this->initBuffer();
	_itow_s(number, this->buf, this->buf_size, toHex ? 16 : 10);
    this->addPrefixZeros(minDigits);
}

CString::CString(UINT number, BOOL toHex, LONG minDigits) {
	this->_commonInit();
	this->initBuffer();
	_ui64tow_s(number, this->buf, this->buf_size, toHex ? 16 : 10);
    this->addPrefixZeros(minDigits);
}

CString::CString(LONG number, BOOL toHex, LONG minDigits) {
	this->_commonInit();
	this->initBuffer();
    if (sizeof(LONG) == sizeof(INT)) {
        _itow_s(number, this->buf, this->buf_size, toHex ? 16 : 10);
    } else {
        _i64tow_s(number, this->buf, this->buf_size, toHex ? 16 : 10);
    }
    this->addPrefixZeros(minDigits);
}

CString::CString(ULONG number, BOOL toHex, LONG minDigits) {
	this->_commonInit();
	this->initBuffer();
    _ui64tow_s(number, this->buf, this->buf_size, toHex ? 16 : 10);
    this->addPrefixZeros(minDigits);
}

CString::CString(BOOL value, WCHAR uppercase) {
	this->_commonInit();
    this->initBuffer(6);
    if (value) {
        if (uppercase == 'T') {
            wcscat_s(this->buf, 6, L"TRUE");
        } else {
            wcscat_s(this->buf, 6, L"true");
        }
    } else {
        if (uppercase == 'T') {
            wcscat_s(this->buf, 6, L"FALSE");
        } else {
            wcscat_s(this->buf, 6, L"false");
        }
    }
}

CString::CString(double number, LONG precision) {
	this->_commonInit();
	this->initBuffer();

	WCHAR* formats[21] = {
		L"%.0f", L"%.1f", L"%.2f", L"%.3f", L"%.4f", L"%.5f", L"%.6f", L"%.7f",
		L"%.8f", L"%.9f", L"%.10f", L"%.11f", L"%.12f", L"%.13f", L"%.14f",
		L"%.15f", L"%.16f", L"%.17f", L"%.18f", L"%.19f", L"%.20f"
	};
	if (precision < 0 || precision > 20) {
		precision = 2;
	}

	swprintf_s(this->buf, this->buf_size, formats[precision], number);
    CLocale* locale = new CLocale();
    WCHAR sdecimal = L',';
    if (locale) {
        sdecimal = locale->locale_sdecimal[0];
        delete locale;
    }

	this->replace(L'.', sdecimal);
}

CString::~CString() {
	this->cleanup();
}

VOID CString::initBuffer() {
	this->initBuffer(DEFAULT_STRING_BUFFER_LENGTH);
}

VOID CString::initBuffer(ULONG size) {
    this->buf = new WCHAR[size];
    if (!this->buf) {
        this->buf_size = 0;
        return;
    }
	this->buf_size = size;
	this->buf[0] = NULL;
}

VOID CString::zeroBuffer() {
    if (this->buf) {
        ZeroMemory(this->buf, this->buf_size*2);
    }
}

VOID CString::extendBuffer(ULONG added_size) {
	ULONG new_size = this->buf_size + added_size;
	WCHAR *b = new WCHAR[new_size];
    if (!b) {
        return;
    }
	if (this->buf) {
        CopyMemory(b, this->buf, this->buf_size * 2);
        delete[] this->buf;
	}
	this->buf_size = new_size;
	this->buf = b;
}

VOID CString::cleanup() {
	if (this->buf) {
        delete[] this->buf;
		this->buf = NULL;
		this->buf_size = 0;
	}

    if (this->_originClassName) {
        delete[] this->_originClassName;
        this->_originClassName = NULL;
    }

    if (this->_realClassName) {
        delete[] this->_realClassName;
        this->_realClassName = NULL;
    }
}

VOID CString::setBuffer(WCHAR *buf, ULONG buf_size) {
	this->cleanup();
	if (buf != NULL) {
		this->buf = buf;
		this->buf_size = buf_size;
	}
}

VOID CString::addPrefixZeros(LONG minLen) {
    if (minLen > 0) {
        LONG len = wcslen(this->buf);
        if (minLen > len) {
            ULONG headsize = minLen - len;
            WCHAR* prep = new WCHAR[headsize+1];
            if (!prep) {
                return;
            }
            for (ULONG i = 0; i < headsize; i++) {
                prep[i] = L'0';
            }
            prep[headsize] = NULL;
            this->prepend(prep);
            delete[] prep;
        }
    }
}

BOOL CString::equals(CString* str) {
    if (str) {
        return wcscmp(this->buf, str->buf) == 0;
    }
    return FALSE;
}


BOOL CString::equals(CObject* obj) {
    CString* s = dynamic_cast<CString*>(obj);
    if (s) {
        return wcscmp(this->buf, s->buf) == 0;
    }
    return wcscmp(this->buf, obj->toString().getBuffer()) == 0;
}

CString CString::toString() {
    return *this;
}

CString &CString::append(const CString &s) {
	if (this->buf_size <= this->length() + s.length()) {
		this->extendBuffer(s.length() + DEFAULT_STRING_BUFFER_LENGTH);
	}
	wcscat_s(this->buf, this->buf_size, s.getBuffer());
	return *this;
}

CString &CString::append(CHAR *s) {
	CString cs(s);
	*this += cs;
	return *this;
}

CString &CString::append(WCHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::append(CHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::append(UCHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::append(WCHAR *s) {
	*this += s;
	return *this;
}

CString &CString::append(INT num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::append(UINT num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::append(LONG num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::append(ULONG num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::append(double num) {
	CString cs(num);
	*this += cs;
	return *this;
}

// STATIC method!
LONG CString::findDoubleNullPos(WCHAR* buf, ULONG bufsize) {
    LONG pos = 0;
    BOOL found = FALSE;
    while (pos + 1 < bufsize) {
        if (buf[pos] == NULL && buf[pos+1] == NULL) {
            found = TRUE;
            break;
        }
        pos++;
    }

    if (found) {
        return pos;
    }

    return -1;
}

LONG CString::findDoubleNullPos() {
    return CString::findDoubleNullPos(this->buf, this->buf_size);
}

ULONG CString::lengthDoubleNull() {
    LONG pos = this->findDoubleNullPos();
    if (pos >= 0) {
        return pos;
    }
    return 0;
}

VOID CString::setStringArray(WCHAR *buf, ULONG bufsize) {
    LONG pos = CString::findDoubleNullPos(this->buf, this->buf_size);
    if (pos >= 0) {
        if (this->buf_size < pos+2) {
            this->extendBuffer(pos+2 - this->buf_size);
        }
        this->buf[0] = NULL;
        CopyMemory((VOID*)this->buf, (VOID*)buf, (pos+2) * 2);
    }
}

CString& CString::appendAfterEos(const CString &s, WCHAR glueChar) {
    return this->appendAfterEos(s.buf, glueChar);
}

CString& CString::appendAfterEos(CHAR *s, WCHAR glueChar) {
	CString cs(s);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(WCHAR ch, WCHAR glueChar) {
	CString cs(ch);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(CHAR ch, WCHAR glueChar) {
	CString cs(ch);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(UCHAR ch, WCHAR glueChar) {
	CString cs(ch);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(WCHAR *s, WCHAR glueChar) {
    if (s) {
        LONG strlen = this->length();
        LONG s_len = wcslen(s);
        if (s_len > 0) {
            LONG requiredBufferSize = strlen + 1 + s_len + 1 + 1;
            if (this->buf_size < requiredBufferSize) {
                this->extendBuffer(requiredBufferSize + DEFAULT_STRING_BUFFER_LENGTH);
            }

            LONG appendAtPos = -1;
            if (strlen > 0) {
                appendAtPos = this->findDoubleNullPos();
            }
            if (appendAtPos == -1) {
                appendAtPos = strlen > 0 ? strlen + 1 : 0;
            } else {
                appendAtPos++;
            }

            if (appendAtPos > 0) {
                this->buf[appendAtPos+1] = NULL;
                wcscat_s(&this->buf[appendAtPos], this->buf_size - appendAtPos - 1, s);
                this->buf[appendAtPos-1] = glueChar;
                this->buf[appendAtPos + s_len] = glueChar;
                this->buf[appendAtPos + s_len + 1] = glueChar;
            } else {
                wcscat_s(this->buf, this->buf_size, s);
                this->buf[s_len] = glueChar;
                this->buf[s_len + 1] = glueChar;
            }
        }
    }
    
	return *this;
}

CString& CString::appendAfterEos(INT num, WCHAR glueChar) {
	CString cs(num);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(UINT num, WCHAR glueChar) {
	CString cs(num);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(LONG num, WCHAR glueChar) {
	CString cs(num);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(ULONG num, WCHAR glueChar) {
	CString cs(num);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString& CString::appendAfterEos(double num, WCHAR glueChar) {
	CString cs(num);
	return this->appendAfterEos(cs.buf, glueChar);
}

CString &CString::prepend(CString &s) {
    return this->prepend(s.buf);
}

CString &CString::prepend(CHAR *s) {
	CString cs(s);
    return this->prepend(cs.buf);
}

CString &CString::prepend(WCHAR ch) {
	CString cs(ch);
    return this->prepend(cs.buf);
}

CString &CString::prepend(CHAR ch) {
	CString cs(ch);
    return this->prepend(cs.buf);
}

CString &CString::prepend(UCHAR ch) {
	CString cs(ch);
    return this->prepend(cs.buf);
}

CString &CString::prepend(WCHAR *s) {
    if (s) {
        ULONG s_len = wcslen(s);
        if (s_len > 0) {
            ULONG size = this->buf_size;
            ULONG this_len = this->length();

            if (size < this_len + s_len + 1) {
                size = this_len + s_len + 1;
            }

            WCHAR *tmp = new WCHAR[size];
            if (tmp) {
                tmp[0] = NULL;
                wcscat_s(tmp, size, s);
                wcscat_s(tmp, size, this->buf);
                if (size > this->buf_size) {
                    this->setBuffer(tmp, size);
                } else {
                    wcscpy_s(this->buf, this->buf_size, tmp);
                    delete[] tmp;
                }
            }
        }
    }

    return *this;
}

CString &CString::prepend(INT num) {
	CString cs(num);
    return this->prepend(cs.buf);
}

CString &CString::prepend(UINT num) {
	CString cs(num);
    return this->prepend(cs.buf);
}

CString &CString::prepend(LONG num) {
	CString cs(num);
    return this->prepend(cs.buf);
}

CString &CString::prepend(ULONG num) {
	CString cs(num);
    return this->prepend(cs.buf);
}

CString &CString::prepend(double num) {
	CString cs(num);
    return this->prepend(cs.buf);
}

VOID CString::padLeft(WCHAR ch, ULONG requiredSize) {
	ULONG len = this->length();

	if (len >= requiredSize) {
		return;
	}

	LONG extraLength = requiredSize - len;
	CString prependStr(ch, extraLength);
	this->prepend(prependStr);
}

VOID CString::padRight(WCHAR ch, ULONG requiredSize) {
	ULONG len = this->length();
	ULONG pos = len;

	if (len >= requiredSize) {
		return;
	}

	if (this->buf_size <= requiredSize) {
		this->extendBuffer(this->buf_size + DEFAULT_STRING_BUFFER_LENGTH);
	}
	while (len < requiredSize) {
		this->buf[pos] = ch;
		pos++;
		len++;
	}
	this->buf[pos] = NULL;
}

CString &CString::operator=(CString &s) {
	if (this->buf_size <= s.getBufferSize()) {
		this->extendBuffer((DEFAULT_STRING_BUFFER_LENGTH > s.getBufferSize() - this->buf_size) ? DEFAULT_STRING_BUFFER_LENGTH : s.getBufferSize());
	}
	this->buf[0] = NULL;
    // copy all memory
	CopyMemory((VOID*)this->buf, (VOID*)s.getBuffer(), s.getBufferSize() * 2);

	return *this;
}

CString &CString::operator=(CHAR *s) {
	CString cs(s);
	*this = cs;
	return *this;
}

CString &CString::operator=(WCHAR ch) {
	CString cs(ch);
	*this = cs;
	return *this;
}

CString &CString::operator=(CHAR ch) {
	CString cs(ch);
	*this = cs;
	return *this;
}

CString &CString::operator=(UCHAR ch) {
	CString cs(ch);
	*this = cs;
	return *this;
}

CString &CString::operator=(WCHAR *s) {
    if (!s) {
        this->buf[0] = NULL;
    } else {
        ULONG strlen = wcslen(s);
        if (strlen == 0) {
            this->buf[0] = NULL;
        } else {
            if (strlen+1 > this->buf_size) {
                this->extendBuffer(this->buf_size + strlen);
            }
            wcscpy_s(this->buf, this->buf_size, s);
        }
    }

	return *this;
}

CString &CString::operator=(INT num) {
	CString cs(num);
	*this = cs;
	return *this;
}

CString &CString::operator=(UINT num) {
	CString cs(num);
	*this = cs;
	return *this;
}

CString &CString::operator=(LONG num) {
	CString cs(num);
	*this = cs;
	return *this;
}

CString &CString::operator=(ULONG num) {
	CString cs(num);
	*this = cs;
	return *this;
}

CString &CString::operator=(double num) {
	CString cs(num);
	*this = cs;
	return *this;
}

CString &CString::operator+=(CString &s) {
	if (this->buf_size <= this->length() + s.length()) {
		this->extendBuffer(s.length() + this->_growBy);
		this->_growBy *= 2;
	}
	wcscat_s(this->buf, this->buf_size, s.getBuffer());
	return *this;
}

CString &CString::operator+=(CHAR *s) {
	CString cs(s);
	*this += cs;
	return *this;
}

CString &CString::operator+=(WCHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::operator+=(CHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::operator+=(UCHAR ch) {
	CString cs(ch);
	*this += cs;
	return *this;
}

CString &CString::operator+=(WCHAR *s) {
    if (s) {
        ULONG s_len = wcslen(s);
        if (this->buf_size < this->length() + s_len + 1) {
            this->extendBuffer(s_len + this->_growBy);
            this->_growBy *= 2;
        }
        wcscat_s(this->buf, this->buf_size, s);
    }
	return *this;
}

CString &CString::operator+=(INT num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::operator+=(UINT num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::operator+=(LONG num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::operator+=(ULONG num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString &CString::operator+=(double num) {
	CString cs(num);
	*this += cs;
	return *this;
}

CString CString::operator+(CString &s) {
	ULONG size;
    if (this->buf_size >= this->length() + s.length() + 1) {
		size = this->buf_size;
	} else {
		size = this->buf_size + s.length();
	}

	WCHAR *buf = new WCHAR[size];
	buf[0] = NULL;
    wcscat_s(buf, size, this->buf);
    wcscat_s(buf, size, s.getBuffer());

	CString newstr(buf, size);
	return newstr;
}

CString CString::operator+(CHAR *s) {
	CString cs(s);
	return ((*this) + cs);
}

CString CString::operator+(WCHAR ch) {
	CString cs(ch);
	return ((*this) + cs);
}

CString CString::operator+(CHAR ch) {
	CString cs(ch);
	return ((*this) + cs);
}

CString CString::operator+(UCHAR ch) {
	CString cs(ch);
	return ((*this) + cs);
}

CString CString::operator+(WCHAR *s) {
    if (s) {
        ULONG strlen = wcslen(s);
        if (strlen > 0) {
            ULONG size = this->length() + strlen + 1;
            WCHAR *buf = new WCHAR[size];
            buf[0] = NULL;
            wcscat_s(buf, size, this->buf);
            wcscat_s(buf, size, s);
            CString result(buf, size);
            return result;
        }
    }

	return this->getBuffer();
}

CString CString::operator+(INT num) {
	CString cs(num);
	return ((*this) + cs);
}

CString CString::operator+(UINT num) {
	CString cs(num);
	return ((*this) + cs);
}

CString CString::operator+(LONG num) {
	CString cs(num);
	return ((*this) + cs);
}

CString CString::operator+(ULONG num) {
	CString cs(num);
	return ((*this) + cs);
}

CString CString::operator+(double num) {
	CString cs(num);
	return ((*this) + cs);
}

LONG CString::charCount(CHAR ch) {
    LONG count = 0;

    CHAR source[2];
    WCHAR dest[2];

    source[0] = ch;
    source[1] = NULL;
	MultiByteToWideChar(CP_ACP, 0, source, -1, dest, 2);

    WCHAR cmp = dest[0];
    DWORD len = this->length();
    for (ULONG i = 0; i < len; i++) {
        if (this->buf[i] == cmp) {
            count++;
        }
    }

    return count;
}

LONG CString::charCount(WCHAR ch) {
    LONG count = 0;
    DWORD len = this->length();

    for (ULONG i = 0; i < len; i++) {
        if (this->buf[i] == ch) {
            count++;
        }
    }

    return count;
}

ULONG CString::wordCount() {
    ULONG count = 0;
    DWORD len = this->length();
    ULONG i;
    WCHAR ch = 0;

    for (i = 0; i < len; i++) {
        ch = this->buf[i];
        if (isCharSentenceBoundary(ch)) {
            count++;
        }
    }

    if (i > 0) {
        if (!isCharSentenceBoundary(ch)) {
            count++;
        }
    }

    return count;
}

CString CString::getFirstToken(WCHAR* tokenBoundaryChars) {
    DWORD len = this->length();
    ULONG i;
    WCHAR ch;
    CString result;

    for (i = 0; i < len; i++) {
        ch = this->buf[i];
        if (wcschr(tokenBoundaryChars, ch) == NULL) {
            result += ch;
        } else {
            break;
        }
    }

    return result;
}

CString CString::popFirstToken(WCHAR* tokenBoundaryChars) {
    DWORD len = this->length();
    ULONG i;
    WCHAR ch;
    CString result;

    for (i = 0; i < len; i++) {
        ch = this->buf[i];
        if (wcschr(tokenBoundaryChars, ch) == NULL) {
            result += ch;
        } else {
            break;
        }
    }

    this->deleteSubstr(0, i);
    this->ltrimAllChars(tokenBoundaryChars);

    return result;
}

CString CString::getLastToken(WCHAR* tokenBoundaryChars) {
    return this->strAfterLastChar(tokenBoundaryChars);
}

CString CString::popLastToken(WCHAR* tokenBoundaryChars) {
    CString result(this->strAfterLastChar(tokenBoundaryChars));
    this->cutAtLastChar(tokenBoundaryChars);
    this->rtrimAllChars(tokenBoundaryChars);
    return result;
}

CString CString::getFirstWord() {
    return this->getFirstToken(WORD_BOUNDARY_CHARS);
}

CString CString::popFirstWord() {
    return this->popFirstToken(WORD_BOUNDARY_CHARS);
}

CString CString::getLastWord() {
    return this->getLastToken(WORD_BOUNDARY_CHARS);
}

CString CString::popLastWord() {
    return this->popLastToken(WORD_BOUNDARY_CHARS);
}

CStringList* CString::explode(WCHAR ch, ULONG maxItems) {
    ULONG num = 0;
    if (maxItems == 0) {
        memset(&maxItems, 0xff, sizeof(maxItems));
    }

    CStringList* retval = new CStringList(10);
    CString tmp = COMMON_EMPTY_STRING;

    WCHAR cmp = ch;
    ULONG len = this->length();
    ULONG i;
    for (i = 0; i < len; i++) {
        if (this->buf[i] == cmp) {
            retval->push(tmp);
            tmp = COMMON_EMPTY_STRING;
            num++;
            if (num == maxItems - 1) {
                if (i+1 < len) {
                    CString s(&this->buf[i+1]);
                    retval->push(s);
                }
                return retval;
            }
        } else {
            tmp += this->buf[i];
        }
    }

    retval->setAt(num, tmp);
    return retval;
}

CStringList* CString::explodeAfterMinLength(WCHAR ch, ULONG minItemLength) {
    LONG num = 0;

    CStringList* retval = new CStringList(20);
    CString tmp = COMMON_EMPTY_STRING;

    WCHAR cmp;
    LONG len = this->length();
    LONG curpos = 0;
    while (curpos < len) {
        cmp = this->buf[curpos];
        if (cmp == ch && (tmp.length() >= minItemLength)) {
            retval->push(tmp);
            tmp = COMMON_EMPTY_STRING;
            num++;
        } else {
            tmp += cmp;
        }

        curpos++;
    }

    retval->setAt(num, tmp);
    return retval;
}

CStringList* CString::explodeToWords(ULONG wordCount) {
    LONG num = 0;

    CStringList* retval = new CStringList(20);
    CString tmp = COMMON_EMPTY_STRING;

    WCHAR cmp;
    LONG len = this->length();
    LONG curpos = 0;
    while (curpos < len) {
        cmp = this->buf[curpos];
        if (isCharSentenceBoundary(cmp)) {
            if (curpos > 0 && isCharSentenceBoundary(this->buf[curpos-1])) {
                curpos++;
                continue;
            }
            if (tmp.wordCount() < wordCount) {
                tmp += cmp;
            } else {
                retval->push(tmp);
                tmp = COMMON_EMPTY_STRING;
                num++;
            }
        } else {
            tmp += cmp;
        }

        curpos++;
    }

    retval->setAt(num, tmp);
    return retval;
}

CStringList* CString::explodeAtChar(WCHAR* stringOfChars) {
    LONG num = 0;
    LONG length = 20; // default size of string list

    CStringList* retval = new CStringList(length);
    CString tmp = COMMON_EMPTY_STRING;

    ULONG len = this->length();
    for (ULONG i = 0; i < len; i++) {
        if (wcschr(stringOfChars, this->buf[i]) != NULL) {
            retval->push(tmp);
            tmp = COMMON_EMPTY_STRING;
            num++;
        } else {
            tmp += this->buf[i];
        }
    }

    retval->setAt(num, tmp);
    return retval;
}

CString CString::substr(LONG start, LONG length) {
    LONG start_pos = start;
    if (start < 0) {
        start_pos = this->length() + start;
    }

    if (start_pos < 0 || start_pos >= (LONG)this->length()) {
        return "";
    }

    if (length < 0) {
        length = this->length() - start_pos;
    }

    if (length < 1) {
        return "";
    }

    ULONG new_buf_size = length + 1;
    WCHAR *new_buf = new WCHAR[new_buf_size];
    memcpy(new_buf, &this->buf[start_pos], length * 2);
    new_buf[length] = NULL;
    CString s(new_buf, new_buf_size);
    return s;
}

VOID CString::rtrim(WCHAR ch) {
    LONG i = this->length() - 1;
    while (i >= 0 && this->buf[i] == ch) {
        this->buf[i] = NULL;
        i --;
    }
}

VOID CString::rtrimAllChars(WCHAR* stringOfChars) {
    LONG i = this->length() - 1;
    while (i >= 0 && wcschr(stringOfChars, this->buf[i]) != NULL) {
        this->buf[i] = NULL;
        i --;
    }
}

VOID CString::ltrim(WCHAR ch) {
    ULONG start_pos = 0;
    ULONG len = this->length();
    while (start_pos < len && this->buf[start_pos] == ch) {
        start_pos++;
    }

    if (start_pos == this->length()) {
        this->buf[0] = NULL;
        return;
    }

    ULONG pos = 0;
    while (start_pos < len) {
        this->buf[pos] = this->buf[start_pos];
        pos++;
        start_pos++;
    }
    this->buf[pos] = NULL;
}

VOID CString::ltrimAllChars(WCHAR* stringOfChars) {
    ULONG start_pos = 0;
    ULONG len = this->length();
    while (start_pos < len && wcschr(stringOfChars, this->buf[start_pos]) != NULL) {
        start_pos++;
    }

    if (start_pos == this->length()) {
        this->buf[0] = NULL;
        return;
    }

    ULONG pos = 0;
    while (start_pos < len) {
        this->buf[pos] = this->buf[start_pos];
        pos++;
        start_pos++;
    }
    this->buf[pos] = NULL;
}

VOID CString::trim(WCHAR ch) {
    this->ltrim(ch);
    this->rtrim(ch);
}

BOOL CString::startsWith(CString s) {
    return wcsstr(this->buf, s.buf) == this->buf;
}

BOOL CString::startsWithChar(WCHAR ch) {
    return this->buf[0] == ch;
}

BOOL CString::startsWithOneOfChars(WCHAR* stringOfChars) {
    return wcschr(stringOfChars, this->buf[0]) != NULL;
}

BOOL CString::endsWith(CString s) {
    LONG slen = s.length();
    LONG len = this->length();
    WCHAR *buf = this->buf + len - slen;
    return wcsstr(buf, s.buf) == buf;
}

BOOL CString::endsWithChar(WCHAR ch) {
    LONG len = this->length();
    if (len > 0) {
        return this->buf[len-1] == ch;
    }
    return FALSE;
}

BOOL CString::endsWithOneOfChars(WCHAR* stringOfChars) {
    WCHAR ch = this->lastChar();
    return wcschr(stringOfChars, ch) != NULL;
}

BOOL CString::contains(WCHAR* substring) {
    return wcsstr(this->buf, substring) != NULL;
}

BOOL CString::contains(WCHAR ch) {
    LONG len = this->length();
    for (LONG i = 0; i < len; i++) {
        if (this->buf[i] == ch) {
            return TRUE;
        }
    }
    return FALSE;
}

BOOL CString::containsOneOfChars(WCHAR* stringOfChars) {
    LONG len = this->length();
    for (LONG i = 0; i < len; i++) {
        if (wcschr(stringOfChars, this->buf[i]) != NULL) {
            return TRUE;
        }
    }
    return FALSE;
}

BOOL CString::containsWhiteSpace() {
    LONG len = this->length();
    for (LONG i = 0; i < len; i++) {
        if (isCharSpace(this->buf[i])) {
            return TRUE;
        }
    }
    return FALSE;
}

VOID CString::trimAllChars(WCHAR* stringOfChars) {
    this->ltrimAllChars(stringOfChars);
    this->rtrimAllChars(stringOfChars);
}

VOID CString::cutAtLast(WCHAR ch) {
    LONG pos = this->length() - 1;
    while (pos >= 0 && this->buf[pos] != ch) {
        pos--;
    }

    if (pos >= 0) {
        this->buf[pos] = NULL;
    }
}

VOID CString::cutAtLastChar(WCHAR* stringOfChars) {
    if (!stringOfChars || stringOfChars[0] == NULL) {
        return;
    }

    LONG pos = this->length() - 1;
    WCHAR ch;
    LONG len = wcslen(stringOfChars);
    BOOL breakLoop = FALSE;

    while (pos >= 0) {
        for (LONG i = 0; i < len; i++) {
            ch = stringOfChars[i];
            if (this->buf[pos] == ch) {
                breakLoop = TRUE;
                break;
            }
        }

        if (breakLoop) {
            break;
        }

        pos--;
    }

    if (pos >= 0) {
        this->buf[pos] = NULL;
    }
}

CString CString::strAfterLast(WCHAR ch) {
    ULONG pos = this->length() - 1;
    while (pos >= 0 && this->buf[pos] != ch) {
        pos--;
    }

    if (pos == this->length() - 1) {
        return "";
    }

    if (pos >= 0) {
        return this->substr(pos + 1);
    }

    return "";
}

CString CString::strAfterLastChar(WCHAR* stringOfChars) {
    if (!stringOfChars || stringOfChars[0] == NULL) {
        return COMMON_EMPTY_STRING;
    }

    ULONG pos = this->length() - 1;
    ULONG len = wcslen(stringOfChars);
    WCHAR ch;
    BOOL breakLoop = FALSE;

    while (pos >= 0) {
        for (ULONG i = 0; i < len; i++) {
            ch = stringOfChars[i];
            if (this->buf[pos] == ch) {
                breakLoop = TRUE;
                break;
            }
        }
        if (breakLoop) {
            break;
        }
        pos--;
    }

    if (pos == this->length() - 1) {
        return COMMON_EMPTY_STRING;
    }

    if (pos >= 0) {
        return this->substr(pos + 1);
    }

    return COMMON_EMPTY_STRING;
}

VOID CString::truncateAtEnd(ULONG numChars) {
	if (numChars > 0) {
		if (numChars > this->length()) {
			numChars = this->length();
		}
		ULONG pos = this->length();
		pos -= numChars;
		this->buf[pos] = NULL;
	}
}

ULONG CString::linesCount() {
    ULONG count = 0;
    if (this->buf && this->buf[0] != NULL) {
        count = 1;
    }

    if (!this->buf) {
        return count;
    }

    ULONG len = this->length();
    for (ULONG i = 0; i < len; i++) {
        if (this->buf[i] == L'\n') {
            count++;
        }
    }

    return count;
}

CString& CString::prependEachLine(CString &s) {
    return this->prependEachLine(s.buf);
}

CString& CString::prependEachLine(CHAR *s) {
	CString cs(s);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(WCHAR ch) {
	CString cs(ch);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(CHAR ch) {
	CString cs(ch);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(UCHAR ch) {
	CString cs(ch);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(WCHAR *s) {
    if (s) {
        ULONG s_len = wcslen(s);
        ULONG this_len = this->length();
        if (s_len > 0) {
            ULONG linesCount = this->linesCount();
            if (linesCount == 0) {
                linesCount = 1;
            }

            ULONG extraChars = linesCount * s_len;
            ULONG new_buf_size = this_len + extraChars + 1;
            WCHAR *new_buf = new WCHAR[new_buf_size];
            if (new_buf) {
                ULONG src_i;
                ULONG dst_i;

                new_buf[0] = NULL;
                memcpy(new_buf, s, s_len * 2);
                new_buf[s_len] = NULL;
                dst_i = s_len;
                for (src_i = 0; src_i < this_len; src_i++) {
                    new_buf[dst_i] = this->buf[src_i];
                    dst_i++;
                    if (this->buf[src_i] == L'\n' && src_i+1 < this_len && this->buf[src_i] != NULL) {
                        memcpy(&new_buf[dst_i], s, s_len * 2);
                        dst_i += s_len;
                    }
                }
                new_buf[dst_i] = NULL;

                if (this->buf) {
                    delete[] this->buf;
                }
                this->buf = new_buf;
                this->buf_size = new_buf_size;
            }
        }
    }
    
    return *this;
}

CString& CString::prependEachLine(INT num) {
	CString cs(num);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(UINT num) {
	CString cs(num);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(LONG num) {
	CString cs(num);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(ULONG num) {
	CString cs(num);
    return this->prependEachLine(cs.buf);
}

CString& CString::prependEachLine(double num) {
	CString cs(num);
    return this->prependEachLine(cs.buf);
}


BOOL CString::isInt() {
	if (!this->buf) {
		return FALSE;
	}

	BOOL retval = TRUE;
	ULONG i = 0;
	ULONG len = this->length();
	if (i < len && (this->buf[i] == L'-' || this->buf[i] == L'+')) {
		i++;
	}
    // jeśli łańcuch składa się z samego minusa lub plusa, to nie jest to liczba
    if (i == len) {
        return FALSE;
    }

	for (; i < len; i++) {
		if (!iswdigit(this->buf[i])) {
			retval = FALSE;
			break;
		}
	}

	return retval;
}

BOOL CString::isDouble() {
	BOOL retval = TRUE;
	BOOL decsep_found = FALSE;
	ULONG i = 0;
	ULONG len = this->length();

	if (i < len && (this->buf[i] == L'-' || this->buf[i] == L'+')) {
		i++;
	}
    // jeśli łańcuch składa się z samego minusa lub plusa, to nie jest to liczba
    if (i == len) {
        return FALSE;
    }
	if (i < len && (this->buf[i] == L',' || this->buf[i] == L'.')) {
		i++;
		decsep_found = TRUE;
	}

	for (; i < len; i++) {
		if (iswdigit(this->buf[i])) {
			continue;
		}
		if ((this->buf[i] == L',' || this->buf[i] == L'.') && !decsep_found) {
			decsep_found = TRUE;
			continue;
		}
		retval = FALSE;
		break;
	}

	return retval;
}


BOOL CString::isNumeric(BOOL *isInt, BOOL *isDouble) {
	BOOL _isInt = TRUE;
	BOOL _isDouble = TRUE;
	//BOOL retval = TRUE;
	BOOL decsep_found = FALSE;
	ULONG i = 0;
	ULONG len = this->length();
    //CLocale* locale = new CLocale();

	if (i < len && (this->buf[i] == L'-' || this->buf[i] == L'+')) {
		i++;
	}
    // jeśli łańcuch składa się z samego minusa lub plusa, to nie jest to liczba
    if (i == len) {
        *isInt = FALSE;
        *isDouble = FALSE;
        return FALSE;
    }
	if (i < len && (this->buf[i] == L',' || this->buf[i] == L'.')) {
		i++;
		decsep_found = TRUE;
		_isInt = FALSE;
	}

	for (; i < len; i++) {
		if (iswdigit(this->buf[i])) {
			continue;
		}
		if ((this->buf[i] == L',' || this->buf[i] == L'.') && !decsep_found) {
			decsep_found = TRUE;
			_isInt = FALSE;
			continue;
		}
		_isDouble = FALSE;
		_isInt = FALSE;
		break;
	}

	if (isInt) {
		*isInt = _isInt;
	}
	if (isDouble) {
		*isDouble = _isDouble;
	}

	return _isInt || _isDouble;
}

LONG CString::toIntRound() {
	BOOL isInt;
	BOOL isDouble;
	BOOL isNumeric = this->isNumeric(&isInt, &isDouble);

	if (!isNumeric) {
		return 0L;
	}

	if (isDouble && !isInt) {
		double retval = this->toDouble();
		return round(retval);
	}

	return (_wtol(this->buf));
}


LONG CString::toIntFloor() {
	BOOL isInt;
	BOOL isDouble;
	BOOL isNumeric = this->isNumeric(&isInt, &isDouble);

	if (!isNumeric) {
		return 0L;
	}

	if (isDouble && !isInt) {
		double retval = this->toDouble();
		return (LONG)floor(retval);
	}

	return (_wtol(this->buf));
}

LONG CString::toIntCeil() {
	BOOL isInt;
	BOOL isDouble;
	BOOL isNumeric = this->isNumeric(&isInt, &isDouble);

	if (!isNumeric) {
		return 0L;
	}

	if (isDouble && !isInt) {
		double retval = this->toDouble();
		return (LONG)ceil(retval);
	}

	return (_wtol(this->buf));
}

LONG CString::toInt() {
	BOOL isInt;
	BOOL isDouble;
	BOOL isNumeric = this->isNumeric(&isInt, &isDouble);

	if (!isNumeric) {
		return 0L;
	}

	if (isDouble && !isInt) {
		double retval = this->toDouble();
		return (LONG)retval;
	}

	return (_wtol(this->buf));
}

double CString::toDouble() {
	if (!this->isDouble()) {
		return 0.0;
	}

	CString num = *this;
    CLocale* locale = new CLocale();
    WCHAR sdecimal = L',';
    if (locale) {
        sdecimal = locale->locale_sdecimal[0];
        delete locale;
    }

	num.replace(sdecimal, L'.');
	return _wtof(num.buf);
}

ULONG CString::replace(WCHAR src, WCHAR dst) {
	ULONG count = 0;
	ULONG len = this->length();
	for (ULONG i = 0; i < len; i++) {
		if (this->buf[i] == src) {
			this->buf[i] = dst;
			count++;
		}
	}

	return count;
}

ULONG CString::replaceCharsWithCodeLessThan(WCHAR vkCode, WCHAR dst) {
	ULONG count = 0;
	ULONG len = this->length();
	for (ULONG i = 0; i < len; i++) {
		if (this->buf[i] < vkCode) {
			this->buf[i] = dst;
			count++;
		}
	}

	return count;
}

VOID CString::replace(ULONG start, ULONG length, const CString& replaceWith) {
    ULONG str_len = this->length();
    if (str_len < 1 || start >= str_len) {
        this->append(replaceWith);
        return;
    }

    // end_index - index ostatniego znaku, który usuwamy
    ULONG end_index = start + length - 1;
    ULONG lengthOfStringToRemove;
    ULONG new_buf_size;

    // jeśli zastępujemy fragment od pozycji start do końca łańcucha, to
    if (end_index >= str_len) {
        end_index = str_len - 1;
        lengthOfStringToRemove = end_index - start + 1;
    }
    // poniżej zastępujemy podłańcuch wewnątrz łańcucha
    else {
        lengthOfStringToRemove = length;
    }

    new_buf_size = str_len + replaceWith.length() - lengthOfStringToRemove + 1;
    WCHAR* new_buf = new WCHAR[new_buf_size];
    // Jeśli usuwamy nie od pierwszego znaku, to kopiujemy do bufora wyjściowego tekst przed
    // usuwanym łańcuchem
    if (start > 0) {
        CopyMemory(new_buf, this->buf, start * sizeof(WCHAR));
    }
    // wklejamy łańcuch replaceWith
    ULONG replaceWithLen = replaceWith.length();
    if (replaceWithLen > 0) {
        CopyMemory(&new_buf[start], replaceWith.getBuffer(), replaceWithLen * sizeof(WCHAR));
    }
    // jeśli końcowy indeks jest przed końcem źródłowego łańcucha, to dokopiowujemy pozostały
    // tekst bufora źródłowego
    ULONG endPartLen = 0;
    if (end_index < str_len - 1) {
        endPartLen = str_len - end_index - 1;
        CopyMemory(&new_buf[start + replaceWithLen], &this->buf[end_index + 1], endPartLen * sizeof(WCHAR));
    }

    // NULL-ujemy na końcu nowego łańcucha
    new_buf[start + replaceWithLen + endPartLen] = NULL;
    this->setBuffer(new_buf, new_buf_size);
}


LONG CString::replace(CString toReplace, CString replaceWith, ULONG startFrom, ULONG maxReplacementCount) {
    if (maxReplacementCount == 0) {
        maxReplacementCount = MAXINT;
    }

    if (startFrom >= this->length()) {
        return 0;
    }

    LONG toReplaceLength = toReplace.length();
    LONG replaceWithLength = replaceWith.length();
    WCHAR* pos = wcsstr(&this->buf[startFrom], toReplace.buf);
    LONG count = 0;
    while (pos) {
        startFrom = pos - this->buf;
        this->replace(startFrom, toReplaceLength, replaceWith);
        count++;
        startFrom += replaceWithLength;
        if (startFrom >= this->length()) {
            break;
        }
        pos = wcsstr(&this->buf[startFrom], toReplace.buf);
    }

    return count;
}

ULONG CString::remove(WCHAR ch) {
	ULONG count = 0;
	LONG len = (LONG)this->length();
	for (LONG i = 0; i < len; i++) {
		if (this->buf[i] == ch) {
			for (LONG j = i + 1; j < len; j++) {
				this->buf[j - 1] = this->buf[j];
			}
			len--;
			count++;
            i--;
		}
	}
	this->buf[len] = NULL;

	return count;
}

ULONG CString::removeCharsWithCodeLessThan(WCHAR vkCode) {
	ULONG count = 0;
	LONG len = (LONG)this->length();
	for (LONG i = 0; i < len; i++) {
		if (this->buf[i] < vkCode) {
			for (LONG j = i + 1; j < len; j++) {
				this->buf[j - 1] = this->buf[j];
			}
			len--;
			count++;
            i--;
		}
	}
	this->buf[len] = NULL;

	return count;
}

VOID CString::insertCharAt(WCHAR ch, ULONG pos) {
    ULONG str_len = this->length();
    // poniżej jest plus 2 bo liczymy dodatkowy znak oraz znak NULL kończący łańcuch
	if (this->buf_size <= str_len + 2) {
		this->extendBuffer(2 + DEFAULT_STRING_BUFFER_LENGTH);
	}

    if (pos >= str_len) {
        this->buf[str_len] = ch;
        this->buf[str_len + 1] = NULL;
    } else {
        // przenosimy podłańcuch od podanej pozycji o jeden znak w prawo
        ULONG j;
        for (j = str_len; j > pos && j > 0; j--) {
            this->buf[j] = this->buf[j - 1];
        }
        this->buf[j] = ch;
        this->buf[str_len + 1] = NULL;
    }
}

VOID CString::insertAt(ULONG pos, const CString& s) {
    ULONG this_len = this->length();
    ULONG s_len = s.length();

    ULONG new_buf_size = this_len + s_len + 1;
    WCHAR* new_buf = new WCHAR[new_buf_size];

    if (this_len == 0) {
        CopyMemory(new_buf, s.getBuffer(), s_len * sizeof(WCHAR));
    } else {
        if (pos >= this_len) {
            CopyMemory(new_buf, this->buf, this_len * sizeof(WCHAR));
            CopyMemory(&new_buf[this_len], s.getBuffer(), s_len * sizeof(WCHAR));
        } else {
            if (pos > 0) {
                CopyMemory(new_buf, this->buf, pos * sizeof(WCHAR));
            }
            CopyMemory(&new_buf[pos], s.getBuffer(), s_len * sizeof(WCHAR));
            CopyMemory(&new_buf[pos + s_len], &this->buf[pos], (this_len - pos) * sizeof(WCHAR));
        }
    }

    new_buf[new_buf_size-1] = NULL;
    this->setBuffer(new_buf, new_buf_size);
}

VOID CString::deleteCharAt(ULONG pos) {
    ULONG str_len = this->length();
    if (pos < str_len) {
        if (str_len == 1) {
            this->buf[0] = NULL;
            return;
        }
        ULONG j;
        for (j = pos; j < str_len - 1; j++) {
            this->buf[j] = this->buf[j + 1];
        }
        this->buf[str_len - 1] = NULL;
    }
}

VOID CString::deleteSubstr(ULONG startpos, ULONG length) {
    if (length == 0) {
        return;
    }

    ULONG copyFrom = startpos + length;
    ULONG len = this->length();

    if (startpos < len) {
        this->buf[startpos] = NULL;
        if (copyFrom < len) {
            wcscat_s(&this->buf[startpos], this->buf_size, &this->buf[copyFrom]);
        }
    }
}

ULONG CString::toCharString(CHAR* dest, DWORD destSize, LONG outputEncoding) const {
    if (!dest) {
        return 0;
    }

    ULONG str_len = this->length();
    if (str_len < 1) {
        dest[0] = NULL;
        return 0;
    }

    LONG retval = WideCharToMultiByte(CP_ACP, 0, this->buf, str_len, (LPSTR)dest, destSize, NULL, NULL);
    if (retval >= 0) {
        dest[retval] = NULL;
    }

    if (outputEncoding == ENC_ANSI) {
        return retval;
    }

    if (str_len >= destSize) {
        str_len = destSize - 1;
    }

    if (outputEncoding == ENC_ISO2) {
        AnsiToIso2((BYTE*)dest, str_len);
        return retval;
    }

    if (outputEncoding == ENC_OEM_852) {
        AnsiToOem852((BYTE*)dest, str_len);
        return retval;
    }

    return retval;
}

LONG CString::strpos(WCHAR* substring, ULONG startFrom) {
    if (startFrom < this->length()) {
        WCHAR* pos = wcsstr(&this->buf[startFrom], substring);
        if (pos != NULL) {
            return pos - this->buf;
        }
    }

    return -1;
}

LONG CString::strposLast(WCHAR* substring, ULONG startFrom) {
    if (startFrom < this->length()) {
        WCHAR* lastPos = NULL;
        WCHAR* pos = wcsstr(&this->buf[startFrom], substring);

        while (pos != NULL) {
            lastPos = pos;
            startFrom = pos - this->buf + 1;
            pos = wcsstr(&this->buf[startFrom], substring);
        }

        if (lastPos == NULL) {
            return -1;
        }

        return lastPos - this->buf;
    }

    return -1;
}

LONG CString::foreachSubcontentDo(WCHAR* beginTag, WCHAR* endTag, BOOL (WINAPI *doFunc)(LONG startPos, LONG endPos, CString* subcontentItem, LPVOID customData), LPVOID customData) {
    LONG pos, pos2;
    CString subcontent;
    LONG beginTagLen;
    LONG endTagLen;
    LONG count = 0;

    beginTagLen = wcslen(beginTag);
    endTagLen = wcslen(endTag);

    if (!doFunc) {
        return 0;
    }

    if (beginTagLen < 1 || endTagLen < 1) {
        return 0;
    }

    pos = this->strpos(beginTag);
    pos2 = this->strpos(endTag, pos + beginTagLen);

    while (pos >= 0 && pos2 > pos) {
        pos += beginTagLen;
        subcontent = this->substr(pos, pos2 - pos);
        count++;
        if (doFunc(pos, pos2 - 1, &subcontent, customData)) {
            break;
        }
        pos = this->strpos(beginTag, pos2 + 1);
        pos2 = this->strpos(endTag, pos + beginTagLen);
    }

    return count;
}

CString WINAPI intToHex(ULONG num, LONG minHexNumberLength, BOOL useSmallAlpha, BOOL prependx0) {
    CString retval = COMMON_EMPTY_STRING;
    WCHAR* smallAlpha = L"abcdef";
    WCHAR* largeAlpha = L"ABCDEF";
    WCHAR* usedAlpha;
    if (useSmallAlpha) {
        usedAlpha = smallAlpha;
    } else {
        usedAlpha = largeAlpha;
    }

    // jeśliminHexNumberLength jest liczbą nieparzystą, to zwiększamy go o 1, żeby ilość
    // wyświetlanych cyfr była parzysta
    if (minHexNumberLength % 2) {
        minHexNumberLength += 1;
    }

    DWORD len = sizeof(LONG) * 8; // rozmiar LONG-a w bitach
    DWORD numCompare = len / 4;   // porównujemy łańcuchy 4-bitowe, numCompare zawiera ilość tych
                                // porównań
    DWORD mask = 0x0f; // maska wskazuje najpierw najmłodsze 4 bity
    DWORD out;
    for (LONG i = 0; i < numCompare; i++) {
        out = num & mask;
        out >>= i * 4;
        if (out < 10) {
            retval.prepend((DWORD)out);
        } else {
            out -= 10;
            retval.prepend(usedAlpha[out]);
        }
        // przesuwamy maskę o 4 bity w lewo
        mask <<= 4;
    }

    // usuwamy początkowe zera
    retval.ltrim(L'0');

    // jeśli minimalna długość łańcucha jest większa od aktualnej, to paddujemy łańcuch 0-ami
    // z lewej strony
    LONG paddCount = minHexNumberLength - retval.length();
    while (paddCount > 0) {
        retval.prepend(L"0");
        paddCount--;
    }

    if (prependx0) {
        retval.prepend(L"0x");
    }

    return retval;
}

LONG WINAPI hexToInt(CString hex) {
    LONG len = hex.length();
    if (len < 1) {
        return 0;
    }

    WCHAR *buf = hex.getBuffer();
    if (buf[len-1] == L'h') {
        buf[len-1] = NULL;
        len -= 1;
    }

    if (len == 0) {
        return 0;
    }

    if (buf[0] == L'#') {
        hex.ltrim(L'#');
        len -= 1;
    }

    if (len == 0) {
        return 0;
    }

    if (buf[0] == L'0' && buf[1] == L'x') {
        hex = hex.substr(2);
        len -= 2;
    }

    if (len == 0) {
        return 0;
    }

    LONG retval = 0;
    buf = hex.getBuffer();
    LONG count = 0;
    LONG maxcount = sizeof(LONG)<<1;
    WCHAR c;
    LONG b;
    for (LONG i = len - 1; i >= 0; i--) {
        c = buf[i];
        if (c >= 0x30 && c <= 0x39) {
            b = (LONG)(c - 0x30);
        } else {
            if (c == L'a' || c == L'A') {
                b = 10;
            } else if (c == L'b' || c == L'B') {
                b = 11;
            } else if (c == L'c' || c == L'C') {
                b = 12;
            } else if (c == L'd' || c == L'D') {
                b = 13;
            } else if (c == L'e' || c == L'E') {
                b = 14;
            } else if (c == L'f' || c == L'F') {
                b = 15;
            } else {
                return 0;
            }
        }
        retval = retval | (b << (count * 4));
        count++;
        if (count == maxcount) {
            break;
        }
    }

    return retval;
}



// *****************************************
// Static CString member functions
// *****************************************
WCHAR CString::charToWchar(CHAR ch) {
    CHAR source[2];
    WCHAR dest[2];

    source[0] = ch;
    source[1] = NULL;
	MultiByteToWideChar(CP_ACP, 0, source, -1, dest, 2);

    return dest[0];
}

CString CString::makeString(WCHAR ch, ULONG count) {
    ULONG buf_size = count + 1;
    WCHAR* buf = new WCHAR[buf_size];
    if (!buf) {
        return COMMON_EMPTY_STRING;
    }

    for (ULONG i = 0; i < count; i++) {
        buf[i] = ch;
    }
    buf[count] = NULL;

    return CString(buf, buf_size);
}

CString& CString::toUppercase() {
    _wcsupr(this->buf);
    return *this;
}

CString& CString::toLowercase() {
    _wcslwr(this->buf);
    return *this;
}
