/*
    PIP - Platform Independent Primitives
    String
    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program 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.

    This program 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 "pistring.h"

#include "piincludes_p.h"
#include "piliterals.h"
#include "pimathbase.h"
#include "pistringlist.h"
#include "pitranslator.h"
#ifdef PIP_ICU
#  define U_NOEXCEPT
#  include "unicode/ucnv.h"
#endif
#ifdef WINDOWS
#  include <stringapiset.h>
#endif
#include <codecvt>
#include <locale>
#include <string>

//! \class PIString pistring.h
//! \~\details
//! \~english \section PIString_sec0 Synopsis
//! \~russian \section PIString_sec0 Краткий обзор
//!
//! \~english
//! String is a sequence of \a PIChar. Real memory size of string is symbols count * 2.
//! String can be constucted from many types of data and can be converted
//! to many types. There are many operators and handly functions to use
//! string as you wish.
//!
//! \~russian
//! Строка состоит из последовательности \a PIChar. Реальный объем памяти,
//! занимаемый строкой, равен количеству символов * 2. Строка может быть
//! создана из множества типов и преобразована в несколько типов.
//! Имеет множество методов для манипуляций.
//!


const char PIString::toBaseN[]   = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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', '[', '\\', ']', '^'};
const char PIString::fromBaseN[] = {
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)0,  (char)1,  (char)2,  (char)3,
	(char)4,  (char)5,  (char)6,  (char)7,  (char)8,  (char)9,  (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22,
	(char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, (char)32, (char)33, (char)34, (char)35,
	(char)36, (char)37, (char)38, (char)39, (char)-1, (char)-1, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16,
	(char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29,
	(char)30, (char)31, (char)32, (char)33, (char)34, (char)35, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1,
	(char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1};

const float PIString::ElideLeft   = 0.f;
const float PIString::ElideCenter = .5f;
const float PIString::ElideRight  = 1.f;


template<typename T>
T toDecimal(const PIString & s) {
	int part = 0, exp = 0;
	bool negative = false, negative_exp = false, err = false, has_digit = false;
	T ret = 0., frac = 0., frac_delim = 1.;
	for (const PIChar pc: s) {
		char c = pc.toAscii();
		switch (part) {
		case 0: // sign
			if (pc.isSpace()) continue;
			if (c >= '0' && c <= '9') {
				has_digit = true;
				ret       = c - '0';
				part      = 1;
				continue;
			}
			if (c == '+') {
				part = 1;
				continue;
			}
			if (c == '-') {
				negative = true;
				part     = 1;
				continue;
			}
			if (c == '.' || c == ',') {
				part = 2;
				continue;
			}
			err = true;
			break;
		case 1: // integer
			if (c >= '0' && c <= '9') {
				has_digit = true;
				ret       = ret * 10 + (c - '0');
				continue;
			}
			if (c == '.' || c == ',') {
				part = 2;
				continue;
			}
			if ((c == 'e' || c == 'E') && has_digit) {
				part = 3;
				continue;
			}
			err = true;
			break;
		case 2: // fractional
			if (c >= '0' && c <= '9') {
				has_digit = true;
				frac      = frac * 10 + (c - '0');
				frac_delim *= 10.;
				continue;
			}
			if ((c == 'e' || c == 'E') && has_digit) {
				part = 3;
				continue;
			}
			err = true;
			break;
		case 3: // exponent with sign
			if (c == '+') {
				part = 4;
				continue;
			}
			if (c == '-') {
				negative_exp = true;
				part         = 4;
				continue;
			}
		case 4: // exponent
			if (c >= '0' && c <= '9') {
				exp = (exp * 10) + (c - '0');
				continue;
			}
			err = true;
			break;
		}
		if (err) break;
	}
	frac /= frac_delim;
	ret += frac;
	if (negative && has_digit) ret = -ret;
	if (exp > 0) {
		if (negative_exp)
			ret /= pow10((T)exp);
		else
			ret *= pow10((T)exp);
	}
	return ret;
}


#define pisprintf(f, v)      \
	char ch[256];            \
	piZeroMemory(ch, 256);   \
	snprintf(ch, 256, f, v); \
	return PIStringAscii(ch);

PIString PIString::itos(const int num) {
	pisprintf("%d", num);
}
PIString PIString::ltos(const long num) {
	pisprintf("%ld", num);
}
PIString PIString::lltos(const llong num) {
	pisprintf("%lld", num);
}
PIString PIString::uitos(const uint num) {
	pisprintf("%u", num);
}
PIString PIString::ultos(const ulong num) {
	pisprintf("%lu", num);
}
PIString PIString::ulltos(const ullong num) {
	pisprintf("%llu", num);
}
PIString PIString::dtos(const double num, char format, int precision) {
	char f[8] = "%.";
	int wr    = snprintf(&(f[2]), 4, "%d", precision);
	if (wr > 4) wr = 4;
	f[2 + wr] = format;
	f[3 + wr] = 0;
	char ch[256];
	piZeroMemory(ch, 256);
	snprintf(ch, 256, f, num);
	return PIStringAscii(ch).replaceAll(',', '.');
}
#undef pisprintf


PIString PIString::fromNumberBaseS(const llong value, int base, bool * ok) {
	if (value == 0LL) return PIString('0');
	if ((base < 2) || (base > 40)) {
		if (ok != 0) *ok = false;
		return PIString();
	}
	if (ok != 0) *ok = true;
	if (base == 10) return lltos(value);
	PIString ret;
	llong v = value < 0 ? -value : value, cn;
	int b   = base;
	while (v >= llong(base)) {
		cn = v % b;
		v /= b;
		// cout << int(cn) << ", " << int(v) << endl;
		ret.push_front(PIChar(toBaseN[cn]));
	}
	if (v > 0) ret.push_front(PIChar(toBaseN[v]));
	if (value < 0) ret.push_front('-');
	return ret;
}

PIString PIString::fromNumberBaseU(const ullong value, int base, bool * ok) {
	if (value == 0ULL) return PIString('0');
	if ((base < 2) || (base > 40)) {
		if (ok != 0) *ok = false;
		return PIString();
	}
	if (ok != 0) *ok = true;
	if (base == 10) return ulltos(value);
	PIString ret;
	ullong v = value, cn;
	int b    = base;
	while (v >= ullong(base)) {
		cn = v % b;
		v /= b;
		// cout << int(cn) << ", " << int(v) << endl;
		ret.push_front(PIChar(toBaseN[cn]));
	}
	if (v > 0) ret.push_front(PIChar(toBaseN[v]));
	return ret;
}


llong PIString::toNumberBase(const PIString & value, int base, bool * ok) {
	static const PIString s_0x = "0x"_a;
	PIString v                 = value.trimmed();
	if (base < 0) {
		int ind = v.find(s_0x);
		if (ind == 0 || ind == 1) {
			v.remove(ind, 2);
			base = 16;
		} else {
			base = 10;
		}
	} else if ((base < 2) || (base > 40)) {
		if (ok != 0) *ok = false;
		return 0;
	}
	if (ok) *ok = true;
	PIVector<int> digits;
	llong ret = 0, m = 1;
	bool neg = false;
	char cs;
	for (int i = 0; i < v.size_s(); ++i) {
		if (v[i] == PIChar('-')) {
			neg = !neg;
			continue;
		}
		cs = fromBaseN[int(v[i].toAscii())];
		if (cs < 0 || cs >= base) {
			if (ok) *ok = false;
			break;
		}
		digits << cs;
	}
	for (int i = digits.size_s() - 1; i >= 0; --i) {
		ret += digits[i] * m;
		m *= base;
	}
	if (neg) ret = -ret;
	return ret;
}


void PIString::appendFromChars(const char * c, int s, const char * codepage) {
	// piCout << "appendFromChars";
	if (s == 0) return;
	int old_sz = size_s();
	if (s == -1) s = strlen(c);
	if (s <= 0) return;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(codepage, &e);
	if (cc) {
		d.enlarge(s);
		e      = (UErrorCode)0;
		int sz = ucnv_toUChars(cc, (UChar *)(d.data(old_sz)), s, c, s, &e);
		d.resize(old_sz + sz);
		ucnv_close(cc);
		return;
	}
#else
#  ifdef WINDOWS
	int sz = MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, 0, 0);
	if (sz <= 0) return;
	d.enlarge(sz);
	MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, (LPWSTR)d.data(old_sz), sz);
#  else
	std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> ucs2conv;
	std::u16string ucs2 = ucs2conv.from_bytes(c, c + s);
	d.enlarge(ucs2.size());
	ucs2.copy((char16_t *)d.data(old_sz), ucs2.size());
#  endif
#endif
	changed_ = true;
}


PIString PIString::fromConsole(const PIByteArray & ba) {
	PIString ret;
	if (ba.isNotEmpty()) ret.appendFromChars((const char *)ba.data(), ba.size(), __sysoemname__);
	return ret;
}


PIString PIString::fromConsole(const char * s) {
	PIString ret;
	if (!s) return ret;
	if (s[0] != '\0') ret.appendFromChars(s, -1, __sysoemname__);
	return ret;
}


PIString PIString::fromSystem(const PIByteArray & ba) {
	PIString ret;
	if (ba.isNotEmpty()) ret.appendFromChars((const char *)ba.data(), ba.size(), __syslocname__);
	return ret;
}


PIString PIString::fromSystem(const char * s) {
	PIString ret;
	if (!s) return ret;
	if (s[0] != '\0') ret.appendFromChars(s, -1, __syslocname__);
	return ret;
}


PIString PIString::fromUTF8(const char * s, int l) {
	PIString ret;
	if (!s) return ret;
	if (s[0] != '\0') {
		if ((uchar)s[0] == 0xEF && (uchar)s[1] == 0xBB && (uchar)s[2] == 0xBF) {
			s += 3;
			if (l > 0) l -= 3;
			if (s[0] == '\0') return ret;
		}
		ret.appendFromChars(s, l, __utf8name__);
	}
	return ret;
}


PIString PIString::fromUTF8(const PIByteArray & ba) {
	PIString ret;
	if (ba.isNotEmpty()) {
		const char * data = (const char *)ba.data();
		int size          = ba.size();
		if (ba.size() >= 3) {
			if (ba[0] == 0xEF && ba[1] == 0xBB && ba[2] == 0xBF) {
				data += 3;
				size -= 3;
				if (size == 0) return ret;
			}
		}
		ret.appendFromChars(data, size, __utf8name__);
	}
	return ret;
}


PIString PIString::fromAscii(const char * s) {
	return fromAscii(s, strlen(s));
}


PIString PIString::fromAscii(const char * s, int len) {
	PIString ret;
	ret.resize(len);
	for (int l = 0; l < len; ++l) {
		ret[l] = s[l];
	}
	return ret;
}


PIString PIString::fromAscii(const PIByteArray & ascii) {
	PIString ret;
	ret.resize(ascii.size());
	for (int l = 0; l < ret.size_s(); ++l) {
		ret[l] = PIChar(ascii[l]);
	}
	return ret;
}


PIString PIString::fromCodepage(const char * s, const char * c) {
	PIString ret;
	if (s[0] > '\0')
		ret.appendFromChars(s,
		                    -1
#ifdef PIP_ICU
		                    ,
		                    c
#else
#  ifdef WINDOWS
		                    ,
		                    __utf8name__
#  endif
#endif
		);
	return ret;
}


//! \~\details
//! \~english
//! Example:
//! \~russian
//! Пример:
//! \~\code
//! piCout << PIString::readableSize(512); // 512 B
//! piCout << PIString::readableSize(5120); // 5.0 KiB
//! piCout << PIString::readableSize(512000); // 500.0 KiB
//! piCout << PIString::readableSize(5120000); // 4.8 MiB
//! piCout << PIString::readableSize(512000000); // 488.2 MiB
//! piCout << PIString::readableSize(51200000000); // 47.6 GiB
//! \endcode
PIString PIString::readableSize(llong bytes) {
	PIString s;
	s.setReadableSize(bytes);
	return s;
}


void PIString::buildData(const char * cp) const {
	if (!changed_ && (last_loc_ == cp) && data_) return;
	last_loc_ = cp;
	changed_  = false;
	deleteData();
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(cp, &e);
	if (cc) {
		const size_t len = UCNV_GET_MAX_BYTES_FOR_STRING(size(), ucnv_getMaxCharSize(cc)) + 1; // MB_CUR_MAX * size() + 1;
		data_            = (char *)malloc(len);
		int sz           = ucnv_fromUChars(cc, data_, len, (const UChar *)(d.data()), d.size_s(), &e);
		ucnv_close(cc);
		data_[sz]  = '\0';
		data_size_ = sz;
		return;
	}
#else
#  ifdef WINDOWS
	int sz = WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)d.data(), d.size_s(), 0, 0, NULL, NULL);
	if (sz <= 0) {
		data_    = (char *)malloc(1);
		data_[0] = '\0';
		return;
	}
	data_ = (char *)malloc(sz + 1);
	WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)d.data(), d.size_s(), (LPSTR)data_, sz, NULL, NULL);
	data_[sz]  = '\0';
	data_size_ = sz;
	return;
#  else
	std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> ucs2conv;
	std::string u8str = ucs2conv.to_bytes((char16_t *)d.data(), (char16_t *)d.data() + d.size());
	data_             = (char *)malloc(u8str.size() + 1);
	strcpy(data_, u8str.c_str());
	data_[u8str.size()] = '\0';
	data_size_          = u8str.size();
#  endif
#endif
}


void PIString::deleteData() const {
	if (!data_) return;
	free(data_);
	data_      = nullptr;
	data_size_ = 0;
}


void PIString::trimsubstr(int & st, int & fn) const {
	for (int i = 0; i < d.size_s(); ++i) {
		if (at(i) != ' ' && at(i) != '\t' && at(i) != '\n' && at(i) != '\r' && at(i) != char(12) && at(i) != uchar(0)) {
			st = i;
			break;
		}
	}
	if (st < 0) return;
	for (int i = d.size_s() - 1; i >= 0; --i) {
		if (at(i) != ' ' && at(i) != '\t' && at(i) != '\n' && at(i) != '\r' && at(i) != char(12) && at(i) != uchar(0)) {
			fn = i;
			break;
		}
	}
}


PIString PIString::minArgPlaceholder() {
	if (size() < 2) return {};
	int min = -1;
	PIString ret, tmp;
	for (int i = 0; i < size_s(); ++i) {
		if (at(i) == '%') {
			int j = 0;
			for (j = i + 1; j < size_s(); ++j) {
				if (!at(j).isDigit()) break;
			}
			bool ok = false;
			tmp     = mid(i + 1, j - i - 1);
			int cur = tmp.toInt(10, &ok);
			if (!ok || tmp.isEmpty()) continue;
			if (min < 0 || min > cur) {
				min = cur;
				ret = tmp;
			}
		}
	}
	if (ret.isEmpty()) return {};
	return "%" + ret;
}


uint PIString::hash() const {
	return piHashData((const uchar *)d.data(), d.size() * sizeof(PIChar));
}


PIByteArray PIString::toAscii() const {
	if (isEmpty()) return PIByteArray();
	PIByteArray ret;
	ret.resize(size());
	for (int i = 0; i < size_s(); ++i) {
		ret[i] = uchar(at(i).ch);
	}
	return ret;
}


PIByteArray PIString::toSystem() const {
	if (isEmpty()) return PIByteArray();
	buildData(__syslocname__);
	return PIByteArray(data_, strlen(data_));
}


PIByteArray PIString::toUTF8() const {
	if (isEmpty()) return PIByteArray();
	buildData(__utf8name__);
	return PIByteArray(data_, strlen(data_));
}


PIByteArray PIString::toCharset(const char * c) const {
	if (isEmpty()) return PIByteArray();
	buildData(
#ifdef PIP_ICU
		c
#else
#  ifdef WINDOWS
		__utf8name__
#  endif
#endif
	);
	return PIByteArray(data_, strlen(data_));
}


PIString PIString::simplified() const {
	PIString ret(*this);
	for (int i = 0; i < ret.size_s(); ++i)
		if (!ret[i].isAscii()) ret[i] = '?';
	return ret;
}


PIString & PIString::mask(const PIString & symbols, PIChar mc) {
	for (int i = 0; i < size_s(); ++i)
		if (symbols.contains(at(i))) {
			insert(i, mc);
			++i;
		}
	return *this;
}


PIString & PIString::unmask(const PIString & symbols, const PIChar mc) {
	for (int i = 0; i < size_s() - 1; ++i)
		if (at(i) == mc) {
			if (symbols.contains(at(i + 1))) {
				remove(i);
			}
		}
	return *this;
}


PIString & PIString::operator+=(const char * str) {
	if (!str) return *this;
	appendFromChars(str, -1, __syslocname__);
	return *this;
}


PIString::~PIString() {
	deleteData();
}


PIString & PIString::operator+=(const wchar_t * str) {
	if (!str) return *this;
	int i = -1;
	while (str[++i]) {
		d.push_back(PIChar(str[i]));
	}
	changed_ = true;
	return *this;
}


PIString & PIString::operator+=(const char16_t * str) {
	if (!str) return *this;
	int i = -1;
	while (str[++i]) {
		d.push_back(PIChar(str[i]));
	}
	changed_ = true;
	return *this;
}


PIString & PIString::operator+=(const PIString & str) {
	d.append(str.d);
	changed_ = true;
	return *this;
}


PIString & PIString::operator+=(const PIConstChars & str) {
	if (!str.isEmpty()) {
		size_t os = d.size();
		d.enlarge(str.size());
		for (size_t l = 0; l < str.size(); ++l) {
			d[os + l] = str[l];
		}
	}
	changed_ = true;
	return *this;
}


bool PIString::operator==(const PIString & str) const {
	return d == str.d;
}


bool PIString::operator!=(const PIString & str) const {
	return d != str.d;
}


bool PIString::operator<(const PIString & str) const {
	size_t l = str.size();
	if (size() < l) return true;
	if (size() > l) return false;
	for (size_t i = 0; i < l; ++i) {
		if (at(i) == str.at(i)) continue;
		if (at(i) < str.at(i))
			return true;
		else
			return false;
	}
	return false;
}


bool PIString::operator>(const PIString & str) const {
	size_t l = str.size();
	if (size() < l) return false;
	if (size() > l) return true;
	for (size_t i = 0; i < l; ++i) {
		if (at(i) == str.at(i)) continue;
		if (at(i) < str.at(i))
			return false;
		else
			return true;
	}
	return false;
}


//! \~\details
//! \~english
//! If "len" < 0 then returns substring from symbol "start" to end.
//! "start" should be >= 0.
//! \~russian
//! Если "len" < 0 тогда возвращается подстрока от символа "start" и до конца.
//! "start" должен быть >= 0.
//! \~\code
//! PIString s("0123456789");
//! piCout << s.mid(0, -1);  // s = "0123456789"
//! piCout << s.mid(0, 2);   // s = "01"
//! piCout << s.mid(3);      // s = "3456789"
//! piCout << s.mid(3, 4);   // s = "3456"
//! piCout << s.mid(7, 1);   // s = "7"
//! piCout << s.mid(7, 4);   // s = "789"
//! piCout << s.mid(-1);     // s = ""
//! \endcode
//! \~\sa \a left(), \a right()
PIString PIString::mid(int start, int len) const {
	if (len == 0 || start >= length()) return PIString();
	if (start < 0) {
		return PIString();
	}
	if (len < 0) {
		return PIString(d.data(start), size_s() - start);
	} else {
		if (len > length() - start) len = length() - start;
		return PIString(d.data(start), len);
	}
	return PIString();
}


//! \~\details
//! \~english
//! If "len" < 0 then remove substring from symbol "start" to end.
//! "start" should be >= 0.
//! \~russian
//! Если "len" < 0 тогда удаляется подстрока от символа "start" и до конца.
//! "start" должен быть >= 0.
//! \~\code
//! PIString s("0123456789");
//! s.cutMid(1, 3);
//! piCout << s; // s = "0456789"
//! s.cutMid(0, 2);
//! piCout << s; // s = "56789"
//! s.cutMid(3, -1);
//! piCout << s; // s = "567"
//! s.cutMid(-1, -1);
//! piCout << s; // s = "567"
//! \endcode
//! \~\sa \a cutLeft(), \a cutRight()
PIString & PIString::cutMid(int start, int len) {
	if (len == 0) return *this;
	if (start < 0) {
		return *this;
	}
	if (len < 0) {
		d.remove(start, size() - start);
	} else {
		if (len > length() - start) len = length() - start;
		d.remove(start, len);
	}
	changed_ = true;
	return *this;
}


//! \~\details
//! \~english Remove spaces, tabulations, line feeds and null symbols:
//! \~russian Удаляет пробелы, табуляцию, переводы строк и нулевые символы:
//! \~ ' ', '\\n', '\\r', '\\t', '\\0'
//! \~\code
//! PIString s(" \t string  \n");
//! s.trim();
//! piCout << s; // s = "string"
//! \endcode
//! \~\sa \a trimmed()
PIString & PIString::trim() {
	int st = -1, fn = 0;
	trimsubstr(st, fn);
	if (st < 0) {
		clear();
		return *this;
	}
	if (fn < size_s() - 1) cutRight(size_s() - fn - 1);
	if (st > 0) cutLeft(st);
	return *this;
}


PIString PIString::trimmed() const {
	int st = -1, fn = 0;
	trimsubstr(st, fn);
	if (st < 0) return PIString();
	return mid(st, fn - st + 1);
}


//! \~\details
//! \~\code
//! PIString s("0123456789");
//! s.replace(2, 3, "_cut_");
//! piCout << s; // s = "01_cut_56789"
//! s.replace(0, 1, "one_");
//! piCout << s; // s = "one_1_cut_56789"
//! \endcode
//! \~\sa \a replaced(), \a replaceAll()
PIString & PIString::replace(int from, int count, const PIString & with) {
	count = piMini(count, length() - from);
	if (count == with.size_s()) {
		memcpy(d.data(from), with.d.data(), count * sizeof(PIChar));
	} else {
		d.remove(from, count);
		d.insert(from, with.d);
	}
	changed_ = true;
	return *this;
}


//! \~\details
//! \~english If "ok" is not null, it set to "true" if something was replaced
//! \~russian Если "ok" не null, то устанавливает в "true" если замена произведена
//! \~\code
//! PIString s("pip string");
//! bool ok;
//! s.replace("string", "conf", &ok);
//! piCout << s << ok; // s = "pip conf", true
//! s.replace("PIP", "PlInPr", &ok);
//! piCout << s << ok; // s = "pip conf", false
//! \endcode
//! \~\sa \a replaced(), \a replaceAll()
PIString & PIString::replace(const PIString & what, const PIString & with, bool * ok) {
	if (what.isEmpty()) {
		if (ok != 0) *ok = false;
		return *this;
	}
	int s = find(what);
	if (s >= 0) replace(s, what.length(), with);
	if (ok != 0) *ok = (s >= 0);
	return *this;
}


//! \~\details
//! \~\code
//! PIString s("substrings");
//! s.replaceAll("s", "_");
//! piCout << s; // s = "_ub_tring_"
//! \endcode
//! \~\sa \a replace(), \a replaced(), \a replacedAll()
PIString & PIString::replaceAll(const PIString & what, const PIString & with) {
	if (what.isEmpty() || what == with) return *this;
	if (with.isEmpty()) {
		removeAll(what);
	} else {
		int l = what.length(), dl = with.length() - what.length();
		for (int i = 0; i < length() - l + 1; ++i) {
			bool match = true;
			for (int j = 0; j < l; ++j) {
				if (at(j + i) != what[j]) {
					match = false;
					break;
				}
			}
			if (!match) continue;
			if (dl > 0) d.insert(i, PIDeque<PIChar>((size_t)dl));
			if (dl < 0) d.remove(i, -dl);
			memcpy(d.data(i), with.d.data(), with.size() * sizeof(PIChar));
			i += dl;
		}
	}
	changed_ = true;
	return *this;
}


//! \~\details
//! \~\code
//! PIString s("substrings");
//! s.replaceAll("s", '_');
//! piCout << s; // s = "_ub_tring_"
//! \endcode
//! \~\sa \a replace(), \a replaced(), \a replacedAll()
PIString & PIString::replaceAll(const PIString & what, const char with) {
	if (what.isEmpty()) return *this;
	int l = what.length(), dl = what.length() - 1;
	for (int i = 0; i < length() - l + 1; ++i) {
		bool match = true;
		for (int j = 0; j < l; ++j) {
			if (at(j + i) != what[j]) {
				match = false;
				break;
			}
		}
		if (!match) continue;
		if (dl > 0) d.remove(i, dl);
		d[i] = PIChar(with);
	}
	changed_ = true;
	return *this;
}


//! \~\details
//! \~\code
//! PIString s("substrings");
//! s.replaceAll('s', '_');
//! piCout << s; // s = "_ub_tring_"
//! \endcode
//! \~\sa \a replace(), \a replaced(), \a replacedAll()
PIString & PIString::replaceAll(const char what, const char with) {
	int l = length();
	for (int i = 0; i < l; ++i) {
		if (at(i) == what) d[i] = with;
	}
	changed_ = true;
	return *this;
}


PIString & PIString::removeAll(const PIString & str) {
	if (str.isEmpty()) return *this;
	int l = str.length();
	for (int i = 0; i < length() - l + 1; ++i) {
		bool match = true;
		for (int j = 0; j < l; ++j) {
			if (d.at(j + i) != str.at(j)) {
				match = false;
				break;
			}
		}
		if (!match) continue;
		d.remove(i, l);
		i -= l;
	}
	changed_ = true;
	return *this;
}


PIString & PIString::insert(int index, const PIString & str) {
	d.insert(index, str.d);
	changed_ = true;
	return *this;
}


PIString & PIString::elide(int size, float pos) {
	static const PIString s_dotdot = ".."_a;
	if (length() <= size) return *this;
	if (length() <= 2) {
		fill('.');
		return *this;
	}
	pos    = piClampf(pos, 0.f, 1.f);
	int ns = size - 2;
	int ls = piRoundf(ns * pos);
	d.remove(ls, length() - ns);
	d.insert(ls, s_dotdot.d);
	changed_ = true;
	return *this;
}


PIStringList PIString::split(const PIString & delim) const {
	PIStringList sl;
	if (isEmpty() || delim.isEmpty()) return sl;
	PIString ts(*this);
	int ci = ts.find(delim);
	while (ci >= 0) {
		sl << ts.left(ci);
		ts.cutLeft(ci + delim.length());
		ci = ts.find(delim);
	}
	if (ts.length() > 0) sl << ts;
	return sl;
}


bool PIString::contains(const PIRegularExpression & regexp) const {
	return regexp.match(const_cast<PIString &>(*this)).hasMatch();
}


//! \~\details
//! \~\code
//! PIString s("012345012345");
//! piCout << s.find('-'); // -1
//! piCout << s.find('3'); // 3
//! piCout << s.find('3', 4); // 9
//! piCout << s.find('3', 10); // -1
//! \endcode
//! \~\sa \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::find(const char c, const int start) const {
	for (int i = start; i < length(); ++i) {
		if (at(i) == c) return i;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s("012345012345");
//! piCout << s.find("-"); // -1
//! piCout << s.find("34"); // 3
//! piCout << s.find("3", 4); // 9
//! piCout << s.find("3", 10); // -1
//! \endcode
//! \~\sa \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::find(const PIString & str, const int start) const {
	int l = str.length();
	for (int i = start; i < length() - l + 1; ++i) {
		if (mid(i, l) == str) return i;
	}
	return -1;
}


int PIString::find(const PIRegularExpression & regexp, const int start) const {
	return regexp.match(const_cast<PIString &>(*this), start).matchedStart();
}


//! \~\details
//! \~\code
//! piCout << PIString("1.str").findAny(".,:"); // 1
//! piCout << PIString("1,str").findAny(".,:"); // 1
//! piCout << PIString("1:str").findAny(".,:"); // 1
//! \endcode
//! \~\sa \a find(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::findAny(const PIString & str, const int start) const {
	for (int i = start; i < length(); ++i) {
		if (str.contains(at(i))) return i;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s("012345012345");
//! piCout << s.findLast('-'); // -1
//! piCout << s.findLast('3'); // 9
//! piCout << s.findLast('3', 4); // 9
//! piCout << s.findLast('3', 10); // -1
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::findLast(const char c, const int start) const {
	for (int i = length() - 1; i >= start; --i) {
		if (at(i) == c) return i;
	}
	return -1;
}

int PIString::findLast(PIChar c, const int start) const {
	for (int i = length() - 1; i >= start; --i) {
		if (at(i) == c) return i;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s("012345012345");
//! piCout << s.findLast("-"); // -1
//! piCout << s.findLast("34"); // 9
//! piCout << s.findLast("3", 4); // 9
//! piCout << s.findLast("3", 10); // -1
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::findLast(const PIString & str, const int start) const {
	int l = str.length();
	for (int i = length() - l; i >= start; --i) {
		if (mid(i, l) == str) return i;
	}
	return -1;
}


int PIString::findLast(const PIRegularExpression & regexp, const int start) const {
	auto m  = regexp.match(const_cast<PIString &>(*this), start);
	int ret = m.matchedStart();
	while (m.next()) {
		ret = m.matchedStart();
	}
	return ret;
}


//! \~\details
//! \~\code
//! piCout << PIString(".str.0").findAnyLast(".,:"); // 4
//! piCout << PIString(".str,0").findAnyLast(".,:"); // 4
//! piCout << PIString(".str:0").findAnyLast(".,:"); // 4
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findLast(), \a findWord(), \a findCWord(), \a findRange()
int PIString::findAnyLast(const PIString & str, const int start) const {
	for (int i = length() - 1; i >= start; --i) {
		if (str.contains(at(i))) return i;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s("this is <PIP>");
//! piCout << s.findWord("this"); // 0
//! piCout << s.findWord("is"); // 5
//! piCout << s.findWord("PIP", 4); // -1
//! piCout << s.findWord("<PIP>", 4); // 8
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findCWord(), \a findRange()
int PIString::findWord(const PIString & word, const int start) const {
	int f = start - 1, tl = length(), wl = word.length();
	while ((f = find(word, f + 1)) >= 0) {
		bool ok = true;
		PIChar c;
		if (f > 0) {
			c = at(f - 1);
			if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
				ok = false;
				continue;
			}
		}
		if (f + wl < tl) {
			c = at(f + wl);
			if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
				ok = false;
				continue;
			}
		}
		if (ok) return f;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s("this::is <PIP>");
//! piCout << s.findCWord("this"); // 0
//! piCout << s.findCWord("is"); // 6
//! piCout << s.findCWord("PIP", 4); // 10
//! piCout << s.findCWord("<PIP>", 4); // 9
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findRange()
int PIString::findCWord(const PIString & word, const int start) const {
	int f = start - 1, tl = length(), wl = word.length();
	while ((f = find(word, f + 1)) >= 0) {
		bool ok = true;
		PIChar c;
		if (f > 0) {
			c = at(f - 1);
			if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) {
				ok = false;
				continue;
			}
		}
		if (f + wl < tl) {
			c = at(f + wl);
			if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) {
				ok = false;
				continue;
			}
		}
		if (ok) return f;
	}
	return -1;
}


//! \~\details
//! \~\code
//! PIString s(" {figures{inside}}");
//! int len = -1;
//! piCout << s.findRange('{', '}', '\\', 0, &len) << len << s.mid(2, len); // 2 15 figures{inside}
//! s = "\"text\\\"shielded\" next";
//! piCout << s.findRange('"', '"', '\\', 0, &len) << len << s.mid(1, len); // 1 14 text\"shielded
//! \endcode
//! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord()
int PIString::findRange(const PIChar start, const PIChar end, const PIChar shield, const int start_index, int * len) const {
	if (len) *len = 0;
	bool trim_ = (start != ' ' && start != '\t' && start != '\n' && start != '\r'), eq = (start == end);
	int sz = size_s(), ls = -1, le = -1, cnt = 0;
	for (int i = start_index; i < sz; ++i) {
		PIChar c = at(i);
		if (c == shield) {
			++i;
			continue;
		}
		if (trim_) {
			if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
			trim_ = false;
		}
		if (eq) {
			if (c == start) {
				if (cnt == 0)
					ls = i;
				else {
					le  = i;
					cnt = 0;
					break;
				}
				cnt++;
			}
		} else {
			if (c == start) {
				if (cnt == 0) ls = i;
				cnt++;
			}
			if (c == end) {
				cnt--;
				if (cnt == 0) le = i;
			}
		}
		if (cnt <= 0) break;
	}
	// piCout << ls << le << cnt;
	if (le < ls || ls < 0 || le < 0 || cnt != 0) return -1;
	if (len) *len = le - ls - 1;
	return ls + 1;
}


int PIString::entries(const PIChar c) const {
	int sz = size_s(), ret = 0;
	for (int i = 0; i < sz; ++i) {
		if (at(i) == c) ++ret;
	}
	return ret;
}


int PIString::entries(const PIString & str) const {
	int sz = size_s(), ssz = str.size_s();
	if (ssz == 0 || sz < ssz) return 0;
	int btc = str.size_s() * sizeof(PIChar), ret = 0;
	for (int i = 0; i < sz - ssz + 1; ++i) {
		if (piCompareBinary(d.data(i), str.d.data(0), btc)) ++ret;
	}
	return ret;
}


int PIString::lineNumber(int pos) const {
	if (isEmpty()) return 0;
	if (pos < 0 || pos >= size_s()) pos = size_s() - 1;
	int line = 1;
	for (int i = 0; i < pos; ++i) {
		if (at(i) == '\n') ++line;
	}
	return line;
}


bool PIString::startsWith(const PIChar c) const {
	if (isEmpty()) return false;
	return front() == c;
}


bool PIString::startsWith(const PIString & str) const {
	if (size() < str.size()) return false;
	return str == left(str.size());
}


bool PIString::endsWith(const PIChar c) const {
	if (isEmpty()) return false;
	return back() == c;
}


bool PIString::endsWith(const PIString & str) const {
	if (size() < str.size()) return false;
	return str == right(str.size());
}


//! \~\details
//! \~\code
//! piCout << PIString("true").toBool(); // true
//! piCout << PIString("Yes").toBool(); // true
//! piCout << PIString("  TRUE ").toBool(); // true
//! piCout << PIString(" 1 ").toBool(); // true
//! piCout << PIString("0").toBool(); // false
//! piCout << PIString("0.1").toBool(); // true
//! piCout << PIString("-1").toBool(); // false
//! piCout << PIString("").toBool(); // false
//! \endcode
bool PIString::toBool() const {
	static const PIString s_true = "true"_a;
	static const PIString s_yes  = "yes"_a;
	static const PIString s_on   = "on"_a;
	static const PIString s_ok   = "ok"_a;
	PIString s(*this);
	s = s.trimmed().toLowerCase();
	if (s == s_true || s == s_yes || s == s_on || s == s_ok) return true;
	if (toDouble() > 0.) return true;
	return false;
}


//! \~\details
//! \~\code
//! PIString s("\t ! word");
//! piCout << s.takeSymbol(); // "!"
//! piCout << s.takeSymbol(); // "w"
//! piCout << s.takeSymbol(); // "o"
//! piCout << s; // "rd"
//! \endcode
//! \~\sa \a takeWord(), \a takeCWord(), \a takeLine(), \a takeNumber(), \a takeRange()
PIString PIString::takeSymbol() {
	PIString ret;
	int sz = size_s(), ss = -1;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
		ss = i;
		break;
	}
	if (ss < 0) return ret;
	ret = mid(ss, 1);
	cutLeft(ss + 1);
	return ret;
}


//! \~\details
//! \~\code
//! PIString s("some words\nnew line ");
//! piCout << s.takeWord(); // "some"
//! piCout << s.takeWord(); // "words"
//! piCout << s.takeWord(); // "new"
//! piCout << s; // " line "
//! \endcode
//! \~\sa \a takeSymbol(), \a takeCWord(), \a takeLine(), \a takeNumber(), \a takeRange()
PIString PIString::takeWord() {
	int sz = size_s(), ws = -1, we = -1;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
			if (we < 0 && ws >= 0) {
				we = i;
				break;
			}
		} else {
			if (ws < 0) ws = i;
			if (we >= 0) break;
		}
	}
	PIString ret = mid(ws, we - ws);
	cutLeft(we < 0 ? sz : we);
	return ret;
}


//! \~\details
//! \~\code
//! \endcode
//! \~\sa \a takeSymbol(), \a takeWord(), \a takeLine(), \a takeNumber(), \a takeRange()
PIString PIString::takeCWord() {
	PIString ret;
	int sz = size_s(), ws = -1, we = -1;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
			if (we < 0 && ws >= 0) {
				we = i;
				break;
			}
		} else {
			if (ws < 0) {
				if (c.isAlpha() || c == '_') {
					ws = i;
				} else {
					return ret;
				}
			} else {
				if (!c.isAlpha() && !c.isDigit() && c != '_') {
					we = i;
					break;
				}
			}
			if (we >= 0) break;
		}
	}
	ret = mid(ws, we - ws);
	cutLeft(we < 0 ? sz : we);
	return ret;
}


//! \~\details
//! \~\code
//! PIString s("some words\nnew line \n\nend");
//! piCout << s.takeLine(); // "some words"
//! piCout << s.takeLine(); // "new line "
//! piCout << s.takeLine(); // ""
//! piCout << s; // "end"
//! \endcode
//! \~\sa \a takeSymbol(), \a takeWord(), \a takeCWord(), \a takeNumber(), \a takeRange()
PIString PIString::takeLine() {
	int sz = size_s(), le = -1;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == '\n') {
			le = i;
			break;
		}
	}
	PIString ret = left(le);
	if (!ret.isEmpty()) {
		if (ret.back() == '\r') {
			ret.cutRight(1);
		}
	}
	cutLeft(le < 0 ? sz : le + 1);
	return ret;
}


//! \~\details
//! \~\code
//! PIString s(" 0xFF -99 1.2E+5f 1000L");
//! piCout << s.takeNumber(); // "0xFF"
//! piCout << s.takeNumber(); // "-99"
//! piCout << s.takeNumber(); // "1.2E+5f"
//! piCout << s.takeNumber(); // "1000L"
//! piCout << s; // ""
//! \endcode
//! \~\sa \a takeSymbol(), \a takeWord(), \a takeCWord(), \a takeLine(), \a takeRange()
PIString PIString::takeNumber() {
	PIString ret;
	int sz = size_s(), ls = -1, le = -1, phase = 0;
	for (int i = 0; i < sz; ++i) {
		if (phase > 7) break;
		PIChar c = at(i);
		// piCout << "char " << c << "phase" << phase;
		switch (phase) {
		case 0: // trim
			if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
				continue;
			}
			phase = 7;
		case 7: // sign
			if (c == '-' || c == '+') {
				ls    = i;
				phase = 1;
				break;
			}
		case 1: // search start
			if ((c >= '0' && c <= '9') || c == '.') {
				le = i;
				if (ls < 0) ls = i;
				if (c == '.')
					phase = 3;
				else
					phase = 2;
				break;
			}
			phase = 9;
			break;
		case 2: // integer
			if (c == '.') {
				le    = i;
				phase = 3;
				break;
			}
			if (c == 'e' || c == 'E') {
				le    = i;
				phase = 4;
				break;
			}
			if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == 'x') {
				le = i;
				break;
			}
			phase = 6;
			break;
		case 3: // point
			if (c == 'e' || c == 'E') {
				le    = i;
				phase = 4;
				break;
			}
			if (c >= '0' && c <= '9') {
				le = i;
				break;
			}
			phase = 6;
			break;
		case 4: // exp
			if ((c >= '0' && c <= '9') || c == '-' || c == '+') {
				le    = i;
				phase = 5;
				break;
			}
			phase = 6;
			break;
		case 5: // power
			if (c >= '0' && c <= '9') {
				le = i;
				break;
			}
			phase = 6;
			break;
		case 6: // suffix
			if (c == 'f' || c == 's' || c == 'u' || c == 'l' || c == 'L') {
				le = i;
				break;
			}
			phase = 9;
			break;
		default: break;
		}
		if (phase == 6) {
			if (c == 'f' || c == 's' || c == 'u' || c == 'l' || c == 'L')
				le = i;
			else
				phase = 9;
		}
	}
	// piCout << ls << le;
	if (le < ls) return ret;
	ret = mid(ls, le - ls + 1);
	cutLeft(le + 1);
	return ret;
}


PIString PIString::takeInteger() {
	PIString ret;
	int sz = size_s(), ws = -1, we = -1;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
			if (we < 0 && ws >= 0) {
				we = i;
				break;
			}
		} else {
			if (ws < 0) ws = i;
			if (!c.isDigit()) {
				we = i;
				break;
			}
			if (we >= 0) break;
		}
	}
	ret = mid(ws, we - ws);
	cutLeft(we < 0 ? sz : we);
	return ret;
}


//! \~\details
//! \~english "shield" symbol prevent analysis of the next symbol
//! \~russian Символ "shield" экранирует следующий символ
//! \~\code
//! PIString s(" {figures{inside}}");
//! piCout << s.takeRange('{', '}'); // "figures{inside}"
//! piCout << s; // ""
//! s = "\"text\\\"shielded\" next";
//! piCout << s.takeRange('"', '"'); // "text\"shielded"
//! piCout << s; // " next"
//! \endcode
//! \~\sa \a takeSymbol(), \a takeWord(), \a takeLine(), \a takeNumber()
PIString PIString::takeRange(const PIChar start, const PIChar end, const PIChar shield) {
	PIString ret;
	bool trim_ = (start != ' ' && start != '\t' && start != '\n' && start != '\r'), eq = (start == end);
	int sz = size_s(), ls = -1, le = -1, cnt = 0;
	for (int i = 0; i < sz; ++i) {
		PIChar c = at(i);
		if (c == shield) {
			++i;
			continue;
		}
		if (trim_) {
			if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
				continue;
			}
			trim_ = false;
		}
		if (eq) {
			if (c == start) {
				if (cnt == 0) {
					ls = i;
				} else {
					le  = i;
					cnt = 0;
					break;
				}
				cnt++;
			}
		} else {
			if (c == start) {
				if (cnt == 0) ls = i;
				cnt++;
			}
			if (c == end) {
				cnt--;
				if (cnt == 0) le = i;
			}
		}
		if (cnt <= 0) break;
	}
	// piCout << ls << le << cnt;
	if (le < ls || ls < 0 || le < 0 || cnt != 0) return ret;
	ret = mid(ls + 1, le - ls - 1);
	cutLeft(le + 1);
	return ret;
}


//! \~\details
//! \~\code
//! PIString s("a(b(c)d)e");
//! piCout << s.inBrackets('(', ')'); // "b(c)d"
//! piCout << s; // s = "a(b(c)d)e"
//! \endcode
PIString PIString::inBrackets(const PIChar start, const PIChar end) const {
	int slen = length();
	int st = -1, bcnt = 0;
	PIChar cc;
	for (int i = 0; i < slen; i++) {
		cc = at(i);
		if (cc == start) {
			if (bcnt == 0) st = i;
			bcnt++;
		}
		if (cc == end && st >= 0) {
			bcnt--;
			if (bcnt == 0) return mid(st + 1, i - st - 1);
		}
	}
	return PIString();
}


//! \~\details
//! \~english
//! This function fill internal buffer by sequence of chars.
//! Length of this buffer is count of symbols + end byte '\0'.
//! Returned pointer is valid until next execution of this function.
//! \~russian
//! Этот метод заполняет внутренный байтовый буфер. Размер
//! этого буфера равен количеству символов строки + завершающий байт '\0'.
//! Возвращаемый указатель действителен до следующего вызова этого метода.
//! \~\code
//! piCout << PIString("0123456789").data(); // 0123456789
//! piCout << PIString("№1").data(); // №1
//! \endcode
//! \~\sa \a dataConsole(), \a dataUTF8()
const char * PIString::data() const {
	if (isEmpty()) return "";
	buildData(__syslocname__);
	return data_;
}


//! \~\details
//! \~english
//! This function fill internal buffer by sequence of chars.
//! Length of this buffer is count of symbols + end byte '\0'.
//! Returned pointer is valid until next execution of this function.
//! \~russian
//! Этот метод заполняет внутренный байтовый буфер. Размер
//! этого буфера равен количеству символов строки + завершающий байт '\0'.
//! Возвращаемый указатель действителен до следующего вызова этого метода.
//! \~\sa \a data(), \a dataUTF8()
const char * PIString::dataConsole() const {
	if (isEmpty()) return "";
	buildData(__sysoemname__);
	return data_;
}


//! \~\details
//! \~english
//! This function fill internal buffer by sequence of chars.
//! Length of this buffer is count of symbols + end byte '\0'.
//! Returned pointer is valid until next execution of this function.
//! \~russian
//! Этот метод заполняет внутренный байтовый буфер. Размер
//! этого буфера равен количеству символов строки + завершающий байт '\0'.
//! Возвращаемый указатель действителен до следующего вызова этого метода.
//! \~\sa \a data(), \a dataConsole()
const char * PIString::dataUTF8() const {
	if (isEmpty()) return "";
	buildData(__utf8name__);
	return data_;
}


//! \~\details
//! \~english
//! This function fill internal buffer by sequence of chars.
//! Length of this buffer is count of symbols + end byte '\0'.
//! Returned pointer is valid until next execution of this function.
//! \~russian
//! Этот метод заполняет внутренный байтовый буфер. Размер
//! этого буфера равен количеству символов строки + завершающий байт '\0'.
//! Возвращаемый указатель действителен до следующего вызова этого метода.
//! \~\sa \a dataConsole(), \a dataUTF8()
const char * PIString::dataAscii() const {
	if (isEmpty()) return "";
	deleteData();
	data_ = (char *)malloc(size() + 1);
	for (int i = 0; i < size_s(); ++i) {
		data_[i] = uchar(at(i).ch);
	}
	data_[size()] = '\0';
	return data_;
}


PIString PIString::toUpperCase() const {
	PIString str(*this);
	int l = str.size();
	for (int i = 0; i < l; ++i) {
		str.d[i] = str.d[i].toUpper();
	}
	return str;
}


PIString PIString::toLowerCase() const {
	PIString str(*this);
	int l = str.size();
	for (int i = 0; i < l; ++i) {
		str.d[i] = str.d[i].toLower();
	}
	return str;
}


char PIString::toChar() const {
	char v = 0;
	sscanf(dataAscii(), "%c", &v);
	return v;
}


float PIString::toFloat() const {
	return toDecimal<float>(*this);
}


double PIString::toDouble() const {
	return toDecimal<double>(*this);
}


ldouble PIString::toLDouble() const {
	return toDecimal<ldouble>(*this);
}


//! \~\details
//! \~english
//! Example:
//! \~russian
//! Пример:
//! \~\code
//! PIString s;
//! s.setReadableSize(512);
//! piCout << s; // 512 B
//! s.setReadableSize(5120);
//! piCout << s; // 5.0 KiB
//! s.setReadableSize(512000);
//! piCout << s; // 500.0 KiB
//! s.setReadableSize(5120000);
//! piCout << s; // 4.8 MiB
//! s.setReadableSize(512000000);
//! piCout << s; // 488.2 MiB
//! s.setReadableSize(51200000000);
//! piCout << s; // 47.6 GiB
//! \endcode
PIString & PIString::setReadableSize(llong bytes) {
	clear();
	if (bytes < 1024) {
		*this += (PIString::fromNumber(bytes) + " "_a + "B"_tr("PIString"));
		return *this;
	}
	double fres     = bytes;
	llong res       = bytes;
	auto checkRange = [this, &fres, &res](const PIString & unit, bool last = false) {
		fres = res / 1024.;
		res /= 1024;
		fres -= res;
		if (res < 1024 || last) {
			*this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " "_a + unit);
			return true;
		}
		return false;
	};
	if (checkRange("KiB"_tr("PIString"))) return *this;
	if (checkRange("MiB"_tr("PIString"))) return *this;
	if (checkRange("GiB"_tr("PIString"))) return *this;
	if (checkRange("TiB"_tr("PIString"))) return *this;
	if (checkRange("PiB"_tr("PIString"))) return *this;
	if (checkRange("EiB"_tr("PIString"))) return *this;
	if (checkRange("ZiB"_tr("PIString"))) return *this;
	checkRange("YiB"_tr("PIString"), true);
	return *this;
}


PIString PIString::toPercentageEncoding() const {
	static const PIString valid = "-._~"_a;
	PIString ret;
	PIByteArray utf8;
	bool ok = false;
	for (const auto & c: *this) {
		ok = false;
		if (c.isAscii()) {
			if (c.isAlpha() || c.isDigit())
				ok = true;
			else if (valid.contains(c))
				ok = true;
		}
		if (ok) {
			ret.append(c);
		} else {
			utf8 = PIString(c).toUTF8();
			for (auto u: utf8)
				ret.append('%').append(PIString::fromNumber(u, 16).expandLeftTo(2, '0'));
		}
	}
	return ret;
}


PIString PIString::fromPercentageEncoding(const PIString & in) {
	PIByteArray utf8;
	PIChar c;
	for (int i = 0; i < in.size_s(); ++i) {
		c = in[i];
		if (c == '%') {
			utf8 << static_cast<uchar>(in.mid(i + 1, 2).toInt(16));
			i += 2;
		} else
			utf8 << static_cast<uchar>(c.toAscii());
	}
	return PIString::fromUTF8(utf8);
}


PIString & PIString::arg(const PIString & v) {
	auto ph = minArgPlaceholder();
	if (!ph.isEmpty()) replaceAll(ph, v);
	return *this;
}


const static PIString _versionDelims_ = "._-+"_a;


void parseVersion(PIString s, PIVector<int> & codes, PIStringList & strs) {
	s.trim();
	if (s.isEmpty()) {
		codes.resize(3, 0);
		return;
	}
	int mccnt = 2 - s.entries('.');
	if (mccnt > 0) {
		int ind = s.findLast('.') + 1;
		while (!_versionDelims_.contains(s[ind])) {
			++ind;
			if (ind > s.size_s() - 1) break;
		}
		for (int i = 0; i < mccnt; ++i) {
			s.insert(ind, ".0"_a);
		}
	}
	PIStringList comps;
	while (!s.isEmpty()) {
		int ind = s.findAny(_versionDelims_);
		if (ind >= 0) {
			comps << s.takeLeft(ind);
			s.cutLeft(1).trim();
		} else {
			comps << s;
			s.clear();
		}
	}
	for (int i = 0; i < comps.size_s(); ++i) {
		if (comps[i].isEmpty()) comps[i] = '0';
		bool ok = false;
		int val = comps[i].toInt(-1, &ok);
		if (ok) {
			codes << val;
		} else {
			strs << comps[i];
		}
	}
	// piCout << codes << strs;
}


int versionLabelValue(PIString s) {
	int ret = -10000;
	if (s.isEmpty()) return 0;
	if (s.startsWith("pre"_a)) {
		s.cutLeft(3);
		ret -= 1;
	}
	if (s.startsWith("rc"_a)) {
		s.cutLeft(2);
		ret += s.toInt();
	}
	if (s.startsWith("r"_a)) {
		s.cutLeft(1);
		ret += 10000 + s.toInt();
	}
	if (s == "alpha"_a) ret -= 4;
	if (s == "beta"_a) ret -= 2;
	return ret;
}


//! \relatesalso PIString
//! \~\details
//! \~english
//! This function parse version to number codes and labels. Then it
//! compare no more than "components" codes. If there is no difference, compare
//! labels. Each label has corresponding integer value, so
//! "prealpha" < "alpha" < "prebeta" < "beta" < "rc[N]" < "" < "r[N]".
//! Example:
//! \~russian
//! Этот метод разбирает версии на числовые части и метку. Затем сравнивает
//! не более чем "components" частей. Если различий нет, то сравниваются
//! метки. Каждой метке соответствует своё значение так, что
//! "prealpha" < "alpha" < "prebeta" < "beta" < "rc[N]" < "" < "r[N]".
//! Пример:
//! \~\code
//! piCout << versionCompare("1.0.0_rc2-999", "1.0.1_rc2-999"); // -1, <
//! piCout << versionCompare("1.0.0", "0.9.2"); // 1, >
//! piCout << versionCompare("1.0.0_r1", "1.0.0"); // 1, >
//! piCout << versionCompare("1.0.0_r1", "1.0.0", 3); // 0, =
//! piCout << versionCompare("1.0.0_r2", "1.0.0", 3); // 0, =
//! piCout << versionCompare(".2-alpha", "0.2_alpha"); // 0, =
//! piCout << versionCompare("1_prebeta", "1.0_alpha"); // 1, >
//! \endcode
//! \~\return
//! \~english
//! * 0 - equal
//! * 1 - v0 > v1
//! * -1 - v0 < v1
//! \~russian
//! * 0 - равны
//! * 1 - v0 > v1
//! * -1 - v0 < v1
int versionCompare(const PIString & v0, const PIString & v1, int components) {
	PIStringList strs[2];
	PIVector<int> codes[2];
	parseVersion(v0.toLowerCase(), codes[0], strs[0]);
	parseVersion(v1.toLowerCase(), codes[1], strs[1]);
	// piCout << codes[0] << strs[0];
	int mc = piMaxi(codes[0].size_s(), codes[1].size_s());
	if (codes[0].size_s() < mc) codes[0].resize(mc, 0);
	if (codes[1].size_s() < mc) codes[1].resize(mc, 0);
	mc = piMaxi(strs[0].size_s(), strs[1].size_s());
	if (strs[0].size_s() < mc) strs[0].resize(mc, "");
	if (strs[1].size_s() < mc) strs[1].resize(mc, "");
	int comps = piMini(components, codes[0].size_s(), codes[1].size_s());
	if (comps < 1) return (v0 == v1 ? 0 : (v0 > v1 ? 1 : -1));
	for (int c = 0; c < comps; ++c) {
		if (codes[0][c] > codes[1][c]) return 1;
		if (codes[0][c] < codes[1][c]) return -1;
	}
	mc = piClampi(mc, 0, components - comps);
	for (int c = 0; c < mc; ++c) {
		int lv0 = versionLabelValue(strs[0][c]);
		int lv1 = versionLabelValue(strs[1][c]);
		if (lv0 > lv1) return 1;
		if (lv0 < lv1) return -1;
	}
	return 0;
}


//! \relatesalso PIString
//! \~\details
//! \~english
//! Parse version as described in \a versionCompare() and returns
//! classic view of codes and labels: major.minor.revision[-build][_label].
//! Example:
//! \~russian
//! Разбирает версию по описанию \a versionCompare() и возвращает
//! классическое представление версии и метки: major.minor.revision[-build][_label].
//! Пример:
//! \~\code
//! piCout << versionNormalize(""); // 0.0.0
//! piCout << versionNormalize("1"); // 1.0.0
//! piCout << versionNormalize("1.2"); // 1.2.0
//! piCout << versionNormalize("1.2.3"); // 1.2.3
//! piCout << versionNormalize("1.2+rc1.99"); // 1.2.99_rc1
//! piCout << versionNormalize("1.2-alpha"); // 1.2.0_alpha
//! piCout << versionNormalize("1..4_rc2-999"); // 1.0.4-999_rc2
//! \endcode
PIString versionNormalize(const PIString & v) {
	PIStringList strs;
	PIVector<int> codes;
	parseVersion(v.toLowerCase(), codes, strs);
	PIString ret;
	for (int i = 0; i < codes.size_s(); ++i) {
		if (i > 0) {
			if (i < 3)
				ret += '.';
			else
				ret += '-';
		}
		ret += PIString::fromNumber(codes[i]);
	}
	for (int i = 0; i < strs.size_s(); ++i) {
		ret += '_';
		ret += strs[i];
	}
	return ret;
}
