/*
    PIP - Platform Independent Primitives
    Unicode char
    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"
#ifdef PIP_ICU
#  define U_NOEXCEPT
#  include "unicode/ucnv.h"
#  include "unicode/ustring.h"
#endif
#ifdef WINDOWS
#  include <stringapiset.h>
#  include <winnls.h>
#endif
char * __syslocname__ = 0;
char * __sysoemname__ = 0;
char * __utf8name__   = 0;
#ifdef BLACKBERRY
#  include <ctype.h>
#endif
#include <wchar.h>

//! \class PIChar pichar.h
//! \~\details
//! \~english
//! This class is wrapper around UTF16.
//! There are many contructors and information functions
//!
//! \~russian
//! %PIChar хранит один сивол в UTF16. Имеет много контрукторов, геттеров в различные
//! кодировки (системную, консольную, UTF8) и информационных функций.
//!


ushort charFromCodepage(const char * c, int size, const char * codepage, int * taken = 0) {
	if (!c || size <= 0) return 0;
	if (uchar(c[0]) < 0x80) return c[0];
	int ret;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(codepage, &e);
	if (cc) {
		UChar uc(0);
		e   = (UErrorCode)0;
		ret = ucnv_toUChars(cc, &uc, 1, c, size, &e);
		// printf("PIChar   %d -> %d\n", c[0], uc);
		if (taken) *taken = ret;
		ucnv_close(cc);
		return ushort(uc);
	}
#else
#  ifdef WINDOWS
	wchar_t buffer;
	ret = MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, size, &buffer, 1);
	if (ret <= 0) return 0;
	if (taken) *taken = ret;
	return buffer;
#  else
	mbstate_t state;
	piZeroMemory(state);
	wchar_t wc;
	ret = mbrtowc(&wc, c, size, &state);
	// printf("mbtowc = %d\n", ret);
	// piCout << errorString();
	if (ret < 1) return 0;
	return ushort(wc);
#  endif
#endif
	return ushort(c[0]);
}


int charCompare(const PIChar & f, const PIChar & s) {
	if (f.isAscii() && s.isAscii()) return strncmp(f.toCharPtr(), s.toCharPtr(), 1);
	return
#ifdef PIP_ICU
		u_strCompare((const UChar *)f.toWCharPtr(), 1, (const UChar *)s.toWCharPtr(), 1, 0);
#else
#  ifdef WINDOWS
		CompareStringW(LOCALE_USER_DEFAULT, 0, (PCNZWCH)f.toWCharPtr(), 1, (PCNZWCH)s.toWCharPtr(), 1) - 2;
#  else
		wcsncmp((const wchar_t *)f.toWCharPtr(), (const wchar_t *)s.toWCharPtr(), 1);
#  endif
#endif
}


bool winIsCharType(const ushort * ch, int type) {
#ifdef WINDOWS
	WORD attr = 0;
	if (GetStringTypeW(CT_CTYPE1, (LPCWCH)ch, 1, &attr) == 0) return false;
	return ((attr & type) == type);
#endif
	return false;
}


PIChar PIChar::fromConsole(char c) {
	PIChar ret;
	ret.ch = charFromCodepage(&c, 1, __sysoemname__);
	return ret;
}


PIChar PIChar::fromSystem(char c) {
	PIChar ret;
	ret.ch = charFromCodepage(&c, 1, __syslocname__);
	return ret;
}


PIChar PIChar::fromSystem(const char * c) {
	PIChar ret;
	int l = 0;
	while (c[l] != '\0' && l < 4)
		++l;
	ret.ch = charFromCodepage(c, l, __syslocname__);
	return ret;
}


PIChar PIChar::fromUTF8(const char * c) {
	PIChar ret;
	int l = 0;
	while (c[l] != '\0' && l < 4)
		++l;
	ret.ch = charFromCodepage(c, l, __utf8name__);
	return ret;
}


bool PIChar::operator==(const PIChar & o) const {
	return ch == o.ch;
}


bool PIChar::operator>(const PIChar & o) const {
	return charCompare(*this, o) > 0;
}


bool PIChar::operator<(const PIChar & o) const {
	return charCompare(*this, o) < 0;
}


bool PIChar::operator>=(const PIChar & o) const {
	return charCompare(*this, o) >= 0;
}


bool PIChar::operator<=(const PIChar & o) const {
	return charCompare(*this, o) <= 0;
}


bool PIChar::isDigit() const {
	if (isAscii()) return isdigit(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_DIGIT);
#else
	return iswdigit(ch) != 0;
#endif
}


bool PIChar::isHex() const {
	if (isAscii()) return isxdigit(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_XDIGIT);
#else
	return iswxdigit(ch) != 0;
#endif
}


bool PIChar::isGraphical() const {
	if (isAscii()) return isgraph(ch) != 0;
#ifdef WINDOWS
	return !winIsCharType(&ch, C1_CNTRL);
#else
	return iswgraph(ch) != 0;
#endif
}


bool PIChar::isControl() const {
	if (isAscii()) return iscntrl(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_CNTRL);
#else
	return iswcntrl(ch) != 0;
#endif
}


bool PIChar::isLower() const {
	if (isAscii()) return islower(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_LOWER);
#else
	return iswlower(ch) != 0;
#endif
}


bool PIChar::isUpper() const {
	if (isAscii()) return isupper(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_UPPER);
#else
	return iswupper(ch) != 0;
#endif
}


bool PIChar::isPrint() const {
	if (isAscii()) return isprint(ch) != 0;
#ifdef WINDOWS
	return !winIsCharType(&ch, C1_CNTRL);
#else
	return iswprint(ch) != 0;
#endif
}


bool PIChar::isSpace() const {
	if (isAscii()) return isspace(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_SPACE);
#else
	return iswspace(ch) != 0;
#endif
}


bool PIChar::isAlpha() const {
	if (isAscii()) return isalpha(ch) != 0;
#ifdef WINDOWS
	return winIsCharType(&ch, C1_ALPHA);
#else
	return iswalpha(ch) != 0;
#endif
}


bool PIChar::isAscii() const {
	return isascii(ch) != 0;
}


const wchar_t * PIChar::toWCharPtr() const {
	return reinterpret_cast<const wchar_t *>(&ch);
}


const char * PIChar::toCharPtr() const {
	return reinterpret_cast<const char *>(&ch);
}


wchar_t PIChar::toWChar() const {
	return wchar_t(ch);
}


char PIChar::toConsole1Byte() const {
	if (ch < 0x80) return ch;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(__sysoemname__, &e);
	if (cc) {
		char uc[8];
		e = (UErrorCode)0;
		ucnv_fromUChars(cc, uc, 8, (const UChar *)(&ch), 1, &e);
		ucnv_close(cc);
		return uc[0];
	}
#endif
#ifdef WINDOWS
	char ret[4] = {0, 0, 0, 0};
	WideCharToMultiByte(CP_OEMCP, 0, (LPCWCH)&ch, 1, ret, 4, NULL, NULL);
	return ret[0];
#endif
	return toAscii();
}


char PIChar::toSystem() const {
	if (ch < 0x80) return ch;
#ifdef PIP_ICU
	UErrorCode e((UErrorCode)0);
	UConverter * cc = ucnv_open(__syslocname__, &e);
	if (cc) {
		char uc[8];
		e = (UErrorCode)0;
		ucnv_fromUChars(cc, uc, 8, (const UChar *)(&ch), 1, &e);
		ucnv_close(cc);
		return uc[0];
	}
#endif
#ifdef WINDOWS
	char ret[4] = {0, 0, 0, 0};
	WideCharToMultiByte(CP_ACP, 0, (LPCWCH)&ch, 1, ret, 4, NULL, NULL);
	return ret[0];
#endif
	return toAscii();
}


PIChar PIChar::toUpper() const {
	if (isAscii()) return PIChar((ushort)toupper(ch));
#ifdef PIP_ICU
	UChar c(0);
	UErrorCode e((UErrorCode)0);
	u_strToUpper(&c, 1, (const UChar *)(&ch), 1, 0, &e);
	return PIChar((ushort)c);
#else
#  ifdef WINDOWS
	ushort wc = 0;
	if (LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, (LPCWSTR)&ch, 1, (LPWSTR)&wc, 1) == 1) return PIChar(wc);
#  endif
#endif
	return PIChar((ushort)towupper(ch));
}


PIChar PIChar::toLower() const {
	if (isAscii()) return PIChar((ushort)tolower(ch));
#ifdef PIP_ICU
	UChar c(0);
	UErrorCode e((UErrorCode)0);
	u_strToLower(&c, 1, (const UChar *)(&ch), 1, 0, &e);
	return PIChar((ushort)c);
#else
#  ifdef WINDOWS
	ushort wc = 0;
	if (LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, (LPCWSTR)&ch, 1, (LPWSTR)&wc, 1) == 1) return PIChar(wc);
#  endif
#endif
	return PIChar((ushort)towlower(ch));
}


PICout operator<<(PICout s, const PIChar & v) {
	s.space();
	s.saveAndSetControls(0);
	if (v.isAscii())
		s << char(v.ch);
	else {
#ifdef PIP_ICU
		UErrorCode e((UErrorCode)0);
		UConverter * cc = ucnv_open(__syslocname__, &e);
		if (cc) {
			char uc[8];
			piZeroMemory(uc, 8);
			e = (UErrorCode)0;
			ucnv_fromUChars(cc, uc, 8, (const UChar *)(&v.ch), 1, &e);
			ucnv_close(cc);
			s << uc;
		} else
#endif
#ifdef WINDOWS
			s << v.toSystem();
#else
		s << PIString(v);
#endif
	}
	s.restoreControls();
	return s;
}
