/*
	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 "piincludes_p.h"
#include "pistring.h"
#include "pistringlist.h"
#ifdef PIP_ICU
#  define U_NOEXCEPT
#  include "unicode/ucnv.h"
#endif
#ifdef WINDOWS
#  include <stringapiset.h>
#endif
#include <wchar.h>
#ifdef ANDROID
#  if __ANDROID_API__ < 21
#    define wctomb(s, wc) wcrtomb(s, wc, NULL)
#    define mbtowc(pwc, s, n) mbrtowc(pwc, s, n, NULL)
#  endif
#endif

/*! \class PIString
 * \brief String class
 * \details PIP use this class for use string information.
 * 
 * \section PIString_sec0 Synopsis
 * This class based on \a PIVector to store information.
 * String is a sequence of \a PIChar and can contain multibyte
 * symbols. Therefore real memory size of string is symbols count * 4.
 * String can be constucted from many types of data and can be converted
 * to many types. There are man operators and handly functions to use
 * string as you wish.
 * 
 * \section PIString_sec1 To/from data convertions
 * Most common constructor is \a PIString(const char * str), where "str"
 * is null-terminated string, e.g. \c "string". This is 7 chars with last char = 0.
 * Also you can constructs \a PIString from single \a PIChar, \a PIByteArray,
 * other \a PIString or sequency of the same characters with custom length.\n \n
 * This class has implicit conversions to <tt>const char * </tt> and 
 * \c std::string. Also there are functions to make same convertions:
 * * \a data() - to <tt>const char * </tt>,
 * * \a stdString() - to \c std::string,
 * * \a toByteArray() - to \a PIByteArray.
 * 
 * \section PIString_sec2 Numeric operations
 * You can get symbolic representation of any numeric value with function
 * \a setNumber(any integer value, int base = 10, bool * ok = 0). Default 
 * arguments are set for decimal base system, but you can choose any system
 * from 2 to 40. There are the same static functions \a fromNumber(), that
 * returns \a PIString. \n
 * Also there is function \a setReadableSize() which is set human-readable
 * size in bytes, Kb, Mb, Gb or Pb. Static analog is \a readableSize().
 * 
 */


/*! \fn int versionCompare(const PIString & v0, const PIString & v1, int components = 6)
 * \relatesalso PIString
 * \brief Compare two version strings in free notation and returns 0, -1 or 1
 * \details 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" < "rcN" < "" < "rN".
 * Example:
 * \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_r1", "1.0.0", 3); // 0
 * piCout << versionCompare(".2-alpha", "0.2_alpha"); // 0
 * piCout << versionCompare("1_prebeta", "1.0_alpha"); // 1
 * \endcode
 * \return
 * * 0 - equal
 * * 1 - v0 > v1
 * * -1 - v0 < v1
 *
 *
 * \fn PIString versionNormalize(const PIString & v)
 * \relatesalso PIString
 * \brief Converts version string in free notation to classic view
 * \details Parse version as described in \a versionCompare() and
 * returns classic view of codes and labels: major.minor.revision[-build][_label].
 * Example:
 * \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
 *
 */


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

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


#ifndef CC_VC
#  define pisprintf(f, v) char ch[256]; memset(ch, 0, 256); sprintf(ch, f, v);
#else
#  define pisprintf(f, v) char ch[256]; memset(ch, 0, 256); sprintf_s(ch, 256, f, v);
#endif
#ifdef ANDROID
//int wctomb(char * c, wchar_t w) {*c = ((char * )&w)[0]; return 1;}
#endif
PIString PIString::itos(const int num) {pisprintf("%d", num); return PIString(ch);}
PIString PIString::ltos(const long num) {pisprintf("%ld", num); return PIString(ch);}
PIString PIString::lltos(const llong num) {pisprintf("%lld", num); return PIString(ch);}
PIString PIString::uitos(const uint num) {pisprintf("%u", num); return PIString(ch);}
PIString PIString::ultos(const ulong num) {pisprintf("%lu", num); return PIString(ch);}
PIString PIString::ulltos(const ullong num) {pisprintf("%llu", num); return PIString(ch);}
PIString PIString::ftos(const float num, char format, int precision) {
	char f[8] = "%.";
	int wr = sprintf(&(f[2]), "%d", precision);
	f[2 + wr] = format;
	f[3 + wr] = 0;
	pisprintf(f, num);
	return PIString(ch);
}
PIString PIString::dtos(const double num, char format, int precision) {
	char f[8] = "%.";
	int wr = sprintf(&(f[2]), "%d", precision);
	f[2 + wr] = format;
	f[3 + wr] = 0;
	pisprintf(f, num);
	return PIString(ch);
}
#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 = PIStringAscii("0x");
	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;
	int 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) {
	if (s <= 0) return;
	int sz;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(codepage, &e);
	if (cc) {
		UChar * ucs = new UChar[s];
		memset(ucs, 0, s * sizeof(UChar));
		e = (UErrorCode)0;
		int sz = ucnv_toUChars(cc, ucs, s, c, s, &e);
		//printf("appendFromChars %d -> %d\n", s, sz);
		//printf("PIString %d -> %d\n", c[0], ucs[0]);
		reserve(size_s() + sz);
		for (int i = 0; i < sz; ++i)
			push_back(PIChar(ucs[i]));
		delete[] ucs;
		ucnv_close(cc);
		return;
	}
#else
#  ifdef WINDOWS
	sz = MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, 0, 0);
	if (sz <= 0) return;
	int old_sz = size_s();
	enlarge(sz);
	MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, (LPWSTR)PIDeque<PIChar>::data(old_sz), sz);
	return;
	//printf("request %d\n", sz);
#  else
	wchar_t wc;
	mbtowc(0,0,0); // reset mbtowc
	//qDebug() << "FromChars ...";
	while (s>0) {
		//qDebug() << "0" << s;
		sz = mbtowc(&wc, c, s);
		//qDebug() << "1" << sz;
		if (sz < 1) break;
		push_back(PIChar(int(wc)));
		c += sz; s -= sz;
		//qDebug() << "2" << c;
	}
	//qDebug() << "FromChars done" << size();
#  endif
#endif
}


PIString PIString::fromConsole(const char * s) {
	int l = 0;
	while (s[l] != '\0') ++l;
	PIString ret;
	if (l > 0) ret.appendFromChars(s, l, __sysoemname__);
	return ret;

}


PIString PIString::fromSystem(const char * s) {
	int l = 0;
	while (s[l] != '\0') ++l;
	PIString ret;
	if (l > 0) ret.appendFromChars(s, l, __syslocname__);
	return ret;
}


PIString PIString::fromUTF8(const char * s) {
	int l = 0;
	while (s[l] != '\0') ++l;
	PIString ret;
	if (l > 0) ret.appendFromChars(s, l, __utf8name__);
	return ret;
}


PIString PIString::fromUTF8(const PIByteArray & ba) {
	PIString ret;
	if (ba.isEmpty()) return ret;
	ret.appendFromChars((const char*)ba.data(), ba.size(), __utf8name__);
	return ret;
}


PIString PIString::fromAscii(const char * s) {
	PIString ret;
	int l = 0;
	while (s[l] != '\0') {
		ret.push_back(PIChar(s[l]));
		++l;
	}
	return ret;
}


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


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


PIString PIString::readableSize(llong bytes) {
	PIString s;
	s.setReadableSize(bytes);
	return s;
}


void PIString::buildData(const char * cp) const {
	data_.clear();
	int sz = 0;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(cp, &e);
	if (cc) {
		char uc[8];
		data_.reserve(size_s());
		for (int i = 0; i < size_s(); ++i) {
			if (at(i).isAscii())
				data_.push_back(uchar(at(i).unicode16Code()));
			else {
				e = (UErrorCode)0;
				sz = ucnv_fromUChars(cc, uc, 8, (const UChar*)(PIDeque<PIChar>::data(i)), 1, &e);
				for (int j = 0; j < sz; ++j)
					data_.push_back(uc[j]);
			}
		}
		ucnv_close(cc);
		data_.push_back('\0');
		return;
	}
#else
#  ifdef WINDOWS
	sz = WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)PIDeque<PIChar>::data(), PIDeque<PIChar>::size_s(), 0, 0, NULL, NULL);
	//printf("WideCharToMultiByte %d %d\n", (uint)(uintptr_t)cp, sz);
	if (sz <= 0) {
		//printf("WideCharToMultiByte erro %d\n", GetLastError());
		data_.push_back(uchar('\0'));
		return;
	}
	data_.resize(sz);
	WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)PIDeque<PIChar>::data(), PIDeque<PIChar>::size_s(), (LPSTR)data_.data(), data_.size_s(), NULL, NULL);
	data_.push_back(uchar('\0'));
	return;
#  else
	wchar_t wc;
	char tc[8];
	wctomb(0, 0);
	for (int i = 0; i < size_s(); ++i) {
		if (at(i).isAscii()) {
			data_.push_back(uchar(at(i).toAscii()));
			continue;
		}
		wc = at(i).toWChar();
		sz = wctomb(tc, wc);
		for (int b = 0; b < sz; ++b)
			data_.push_back(uchar(tc[b]));
	}
	data_.push_back(uchar('\0'));
#  endif
#endif
}


void PIString::trimsubstr(int &st, int &fn) const {
	for (int i = 0; i < length(); ++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 = length() - 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;}
}


const char * PIString::dataConsole() const {
	buildData(__sysoemname__ );
	return (const char *)(data_.data());
}


const char * PIString::dataUTF8() const {
	buildData(__utf8name__);
	return (const char *)(data_.data());
}


const char * PIString::dataAscii() const {
	data_.clear();
	for (int i = 0; i < size_s(); ++i)
		data_.push_back(uchar(at(i).ch));
	data_.push_back(uchar('\0'));
	return (const char *)data_.data();
}


uint PIString::hash() const {
	return piHashData((const uchar*)PIDeque<PIChar>::data(), size() * sizeof(PIChar));
}


PIByteArray PIString::toUTF8() const {
	if (isEmpty()) return data_.resized(0);
	buildData(__utf8name__);
	return data_.resized(data_.size_s() - 1);
}


PIByteArray PIString::toCharset(const char * c) const {
	if (isEmpty()) return data_.resized(0);
	buildData(
#ifdef PIP_ICU
			  c
#else
#  ifdef WINDOWS
			  __utf8name__
#  endif
#endif
				);
	return data_.resized(data_.size_s() - 1);

}


PIString & PIString::operator +=(const char * str) {
	if (!str) return *this;
	int l = 0;
	while (str[l] != '\0') ++l;
	appendFromChars(str, l, __syslocname__);
	return *this;
}


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


PIString & PIString::operator +=(const PIString & str) {
	*((PIDeque<PIChar>*)this) << *((PIDeque<PIChar>*)&str);
	return *this;
}


bool PIString::operator ==(const PIString & str) const {
	uint l = str.size();
	if (size() != l) return false;
	for (uint i = 0; i < l; ++i)
		if (str[i] != at(i))
			return false;
	return true;
}


bool PIString::operator !=(const PIString & str) const {
	uint l = str.size();
	if (size() != l) return true;
	for (uint i = 0; i < l; ++i)
		if (str[i] != at(i))
			return true;
	return false;
}


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


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


PIString PIString::mid(const int start, const int len) const {
	//PIString str;
	int s = start, l = len;
	if (l == 0 || s >= length()) return PIString();
	if (s < 0) {
		l += s;
		s = 0;
	}
	if (l < 0) {
		return PIString(&(at(s)), size_s() - s);
	} else {
		if (l > length() - s)
			l = length() - s;
		return PIString(&(at(s)), l);
	}
	return PIString();
}


PIString & PIString::cutMid(const int start, const int len) {
	int s = start, l = len;
	if (l == 0) return *this;
	if (s < 0) {
		l += s;
		s = 0;
	}
	if (l < 0)
		remove(s, size() - s);
	else {
		if (l > length() - s)
			l = length() - s;
		remove(s, l);
	}
	return *this;
}


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);
}


PIString & PIString::replace(int from, int count, const PIString & with) {
	count = piMini(count, length() - from);
	if (count == with.size_s())
		memcpy(PIDeque<PIChar>::data(from), static_cast<PIDeque<PIChar>>(with).data(), count * sizeof(PIChar));
	else {
		remove(from, count);
		PIDeque<PIChar>::insert(from, with);
	}
	return *this;
}


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;
}


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) PIDeque<PIChar>::insert(i, PIDeque<PIChar>((size_t)dl));
			if (dl < 0) PIDeque<PIChar>::remove(i, -dl);
			memcpy(PIDeque<PIChar>::data(i), &(with.at(0)), with.length() * sizeof(PIChar));
			//i -= l;
		}
	}
	return *this;
}


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) PIDeque<PIChar>::remove(i, dl);
		(*this)[i] = PIChar(with);
		//i -= l;
	}
	return *this;
}


PIString & PIString::replaceAll(const char what, const char with) {
	int l = length();
	for (int i = 0; i < l; ++i) {
		if (at(i) == what)
			(*this)[i] = with;
	}
	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 (at(j + i) != str[j]) {
				match = false;
				break;
			}
		}
		if (!match) continue;
		PIDeque<PIChar>::remove(i, l);
		i -= l;
	}
	return *this;
}


PIString & PIString::insert(int index, const PIString & str) {
	PIDeque<PIChar>::insert(index, *((const PIDeque<PIChar>*)&str));
	return *this;
}


PIString & PIString::elide(int size, float pos) {
	static const PIString s_dotdot = PIStringAscii("..");
	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);
	remove(ls, length() - ns);
	insert(ls, s_dotdot);
	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;
}


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;
}


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::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(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::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 = (*this)[f - 1]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {ok = false; continue;}}
		if (f + wl < tl) {c = (*this)[f + wl]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) {ok = false; continue;}}
		if (ok) return f;
	}
	return -1;
}


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 = (*this)[f - 1]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) {ok = false; continue;}}
		if (f + wl < tl) {c = (*this)[f + wl]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) {ok = false; continue;}}
		if (ok) return f;
	}
	return -1;
}


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::findAny(const PIString & str, const int start) const {
	for (int i = start; i < length(); ++i)
		if (str.contains(at(i)))
			return i;
	return -1;
}


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;
}


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;
}


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


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


bool PIString::toBool() const {
	static const PIString s_true = PIStringAscii("true");
	static const PIString s_yes  = PIStringAscii("yes" );
	static const PIString s_on   = PIStringAscii("on"  );
	static const PIString s_ok   = PIStringAscii("ok"  );
	PIString s(*this);
	s = s.trimmed().toLowerCase();
	if (s == s_true || s == s_yes || s == s_on || s == s_ok) return true;
	if (atof(s.toNativeDecimalPoints().data()) > 0.) return true;
	return false;
}


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;
}


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;
}


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;
}


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;
}


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') {le = i; if (ls < 0) ls = i; phase = 2; break;}
			if (c == '.') {le = i; if (ls < 0) ls = i; phase = 3; 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;
		}
		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::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;
}


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();
}


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


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


PIString PIString::toNativeDecimalPoints() const {
#ifdef HAS_LOCALE
	PIString s(*this);
	if (currentLocale == 0) return s;
	return s.replaceAll('.', currentLocale->decimal_point).replaceAll(',', currentLocale->decimal_point);
#else
	return PIString(*this).replaceAll(',', '.');
#endif
}


char PIString::toChar() const {
	PIString s(toNativeDecimalPoints());
	char v;
	sscanf(s.data(), "%c", &v);
	return v;
}


float PIString::toFloat() const {
	return (float)atof(toNativeDecimalPoints().data());
}


double PIString::toDouble() const {
	return atof(toNativeDecimalPoints().data());
}


ldouble PIString::toLDouble() const {
	return atof(toNativeDecimalPoints().data());
}


PIString & PIString::setReadableSize(llong bytes) {
	clear();
	if (bytes < 1024) {*this += (PIString::fromNumber(bytes) + PIStringAscii(" B")); return *this;}
	double fres = bytes / 1024.;
	llong res = bytes / 1024;
	fres -= res;
	if (res < 1024) {*this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" kB")); return *this;}
	fres = res / 1024.;
	res /= 1024;
	fres -= res;
	if (res < 1024) {*this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" MB")); return *this;}
	fres = res / 1024.;
	res /= 1024;
	fres -= res;
	if (res < 1024) {*this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" GB")); return *this;}
	fres = res / 1024.;
	res /= 1024;
	fres -= res;
	if (res < 1024) {*this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" TB")); return *this;}
	fres = res / 1024.;
	res /= 1024;
	fres -= res;
	*this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" PB"));
	return *this;
}


const static PIString _versionDelims_ = PIStringAscii("._-+");


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, PIStringAscii(".0"));
	}
	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(PIStringAscii("pre"))) {
		s.cutLeft(3);
		ret -= 1;
	}
	if (s.startsWith(PIStringAscii("rc"))) {
		s.cutLeft(2);
		ret += s.toInt();
	}
	if (s.startsWith(PIStringAscii("r"))) {
		s.cutLeft(1);
		ret += 10000 + s.toInt();
	}
	if (s == PIStringAscii("alpha")) ret -= 4;
	if (s == PIStringAscii("beta" )) ret -= 2;
	return ret;
}


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;
}


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;
}


PICout operator <<(PICout s, const PIString & v) {
	s.space();
	s.quote();
	s.setControl(0, true);
	s << v.data();
	s.restoreControl();
	s.quote();
	return s;
}

