/*! \file pitextstream.h
 * \ingroup Text
 * \~\brief
 * \~english Text serialization functionality over PIBinaryStream
 * \~russian Функциональность текстовой сериализации поверх PIBinaryStream
 */
/*
    PIP - Platform Independent Primitives
    Text serialization functionality over PIBinaryStream
    Ivan Pelipenko peri4ko@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/>.
*/

#ifndef PITEXTSTREAM_H
#define PITEXTSTREAM_H

#include "pistring.h"


//! \~\ingroup Text
//! \~\brief
//! \~english Text serialization functionality over PIBinaryStream.
//! \~russian Функциональность текстовой сериализации поверх PIBinaryStream.
template<typename P>
class PITextStream {
public:
	//! \~english Floating-point numbers write format
	//! \~russian Формат записи чисел с плавающей точкой
	enum FloatFormat {
		DecimalFormat /** \~english Decimal format, "*.*" \~russian Десятичный формат, "*.*" */                = 'f',
		ExponentFormat /** \~english Exponential format, "*e+-<E>" \~russian Экспонентный формат, "*e+-<E>" */ = 'e'
	};

	//! \~english String encoding
	//! \~russian Кодировка строк
	enum Encoding {
		System /** \~english System encoding \~russian Системная кодировка */,
		UTF8 /** \~english UTF-8 encoding \~russian Кодировка UTF-8 */,
	};

	//! \~english Construct text stream binded to "stream_"
	//! \~russian Возвращает привязанный к "stream_" текстовый поток
	PITextStream(PIBinaryStream<P> * stream_) { setStream(stream_); }

	//! \~english Returns binded PIBinaryStream
	//! \~russian Возвращает привязанный PIBinaryStream
	PIBinaryStream<P> * stream() const { return s; }
	void setStream(PIBinaryStream<P> * stream_) {
		s      = stream_;
		is_end = false;
	}

	//! \~english Returns if end of stream reached
	//! \~russian Возвращает достигнут ли конец потока
	bool isEnd() const { return is_end; }

	//! \~english Returns read/write encoding
	//! \~russian Возвращает кодировку чтения/записи
	Encoding encoding() const { return enc; }

	//! \~english Set read/write encoding, default UTF8
	//! \~russian Устанавливает кодировку чтения/записи, по умолчанию UTF8
	void setEncoding(Encoding e) { enc = e; }

	//! \~english Returns float numbers write format
	//! \~russian Возвращает формат записи чисел с плавающей точкой
	FloatFormat floatFormat() const { return format_; }

	//! \~english Set float numbers write format, default DecimalFormat
	//! \~russian Устанавливает формат записи чисел с плавающей точкой, по умолчанию DecimalFormat
	void setFloatFormat(FloatFormat format) { format_ = format; }

	//! \~english Returns float numbers write precision
	//! \~russian Возвращает точность записи чисел с плавающей точкой
	int floatPrecision() const { return prec_; }

	//! \~english Set float numbers write precision to "prec_" digits, default 5
	//! \~russian Устанавливает точность записи чисел с плавающей точкой, по умолчанию 5
	void setFloatPrecision(int prec) { prec_ = prec; }

	//! \~english Append space
	//! \~russian Добавляет пробел
	PITextStream<P> & space() {
		s->binaryStreamAppend(' ');
		return *this;
	}

	//! \~english Append new line
	//! \~russian Добавляет новую строку
	PITextStream<P> & newLine() {
		s->binaryStreamAppend('\n');
		return *this;
	}

	//! \~english Append "v" string
	//! \~russian Добавляет строку "v"
	void append(const PIString & v) {
		if (v.isEmpty()) return;
		PIByteArray d;
		switch (enc) {
		case System: d = v.toSystem(); break;
		case UTF8: d = v.toUTF8(); break;
		}
		s->binaryStreamAppend(d.data(), d.size());
	}

	//! \~english Append "v" as ASCII
	//! \~russian Добавляет "v" как ASCII
	void append(const PIConstChars & v) {
		if (!v.isEmpty()) s->binaryStreamAppend(v.data(), v.size());
	}

	//! \~english Append "v" char as character
	//! \~russian Добавляет "v" как символ
	void append(char v) { s->binaryStreamAppend(v); }

	//! \~english Append "v" as ASCII
	//! \~russian Добавляет "v" как ASCII
	void append(const char * v) { append(PIConstChars(v)); }

	//! \~english Append boolean, "true" of "false"
	//! \~russian Добавляет логическое, "true" of "false"
	void append(bool v) { append(v ? "true" : "false"); }

	//! \~english Append integer
	//! \~russian Добавляет целое
	void append(int v) { append(PIString::fromNumber(v)); }

	//! \~english Append integer
	//! \~russian Добавляет целое
	void append(llong v) { append(PIString::fromNumber(v)); }

	//! \~english Append floating-point number, using \a floatFormat() and \a floatPrecision()
	//! \~russian Добавляет число с плавающей точкой, используя \a floatFormat() и \a floatPrecision()
	void append(float v) { append(PIString::fromNumber(v, (char)format_, prec_)); }

	//! \~english Append floating-point number, using \a floatFormat() and \a floatPrecision()
	//! \~russian Добавляет число с плавающей точкой, используя \a floatFormat() и \a floatPrecision()
	void append(double v) { append(PIString::fromNumber(v, (char)format_, prec_)); }


	//! \~english Read character
	//! \~russian Читает символ
	char readChar(bool * rok) {
		char ret;
		bool ok = s->binaryStreamTake(&ret, sizeof(ret));
		if (!ok) is_end = true;
		if (rok) *rok = ok;
		return ret;
	}

	//! \~english Read line
	//! \~russian Читает строку
	PIString readLine() {
		PIByteArray ret;
		bool ok = true;
		for (;;) {
			char b = readChar(&ok);
			if (!ok || b == '\n') break;
			if (b != '\r') ret.append((uchar)b);
		}
		return fromBytes(ret);
	}

	//! \~english Read word, skip leading whitespaces, until next whitespace
	//! \~russian Читает слово, пропуская начальные пробельные символы, до следующего пробельного символа
	PIString readWord() {
		static PIConstChars spaces(" \t\n\r");
		return readUntil(spaces);
	}

	//! \~english Read C-word, skip leading and until non C-identifier
	//! \~russian Читает C-слово, пропуская начальные и до следующих символов, не являющихся C-идентификаторами
	PIString readCWord() {
		static PIConstChars chars(" \t\n\r:;%$&#@!?~/*-+=.,\\\"'`[](){}<>");
		return readUntil(chars);
	}

private:
	PIString fromBytes(const PIByteArray & ba) {
		switch (enc) {
		case System: return PIString::fromSystem(ba);
		case UTF8: return PIString::fromUTF8(ba);
		}
		return PIString();
	}
	PIString readUntil(const PIConstChars & chars) {
		// static PIConstChars spaces(" \t\n\r");
		bool ok = true;
		char c  = skipWhile(chars, &ok);
		if (!ok) return PIString();
		PIByteArray ret;
		ret.append((uchar)c);
		for (;;) {
			c = readChar(&ok);
			if (!ok || chars.contains(c)) break;
			ret.append((uchar)c);
		}
		return fromBytes(ret);
	}
	// returns first non-"chars" char
	char skipWhile(const PIConstChars & chars, bool * rok) {
		bool ok = true;
		char c  = 0;
		for (;;) {
			c = readChar(&ok);
			if (!ok || !chars.contains(c)) break;
		}
		if (rok) *rok = ok;
		return c;
	}

	PIBinaryStream<P> * s;
	Encoding enc        = UTF8;
	FloatFormat format_ = DecimalFormat;
	bool is_end         = false;
	int prec_           = 5;
};


//! \~english Returns PITextStream for binary stream "stream"
//! \~russian Возвращает PITextStream для бинарного потока "stream"
template<typename P>
inline PITextStream<P> createPITextStream(PIBinaryStream<P> * stream) {
	return PITextStream<P>(stream);
}


//! \~english Append boolean
//! \~russian Добавляет логическое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, bool v) {
	s.append(v);
	return s;
}

//! \~english Append character
//! \~russian Добавляет символ
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, char v) {
	s.append(v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, uchar v) {
	s.append((int)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, short v) {
	s.append((int)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, ushort v) {
	s.append((int)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, int v) {
	s.append((int)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, uint v) {
	s.append((int)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, llong v) {
	s.append((llong)v);
	return s;
}

//! \~english Append integer
//! \~russian Добавляет целое
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, ullong v) {
	s.append((llong)v);
	return s;
}

//! \~english Append floating-point number
//! \~russian Добавляет число с плавающей точкой
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, float v) {
	s.append(v);
	return s;
}

//! \~english Append floating-point number
//! \~russian Добавляет число с плавающей точкой
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, double v) {
	s.append(v);
	return s;
}


//! \~english Append string
//! \~russian Добавляет строку
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, const char * v) {
	s.append(v);
	return s;
}

//! \~english Append string
//! \~russian Добавляет строку
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, const PIConstChars & v) {
	s.append(v);
	return s;
}

//! \~english Append string
//! \~russian Добавляет строку
template<typename P>
inline PITextStream<P> & operator<<(PITextStream<P> & s, const PIString & v) {
	s.append(v);
	return s;
}


//! \~english Read word as bool
//! \~russian Читает слово как логическое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, bool & v) {
	v = s.readWord().toBool();
	return s;
}

//! \~english Read character
//! \~russian Читает символ
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, char & v) {
	v = s.readChar();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, uchar & v) {
	v = s.readWord().toUInt();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, short & v) {
	v = s.readWord().toInt();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, ushort & v) {
	v = s.readWord().toUInt();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, int & v) {
	v = s.readWord().toInt();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, uint & v) {
	v = s.readWord().toUInt();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, long & v) {
	v = s.readWord().toLong();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, ulong & v) {
	v = s.readWord().toULong();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, llong & v) {
	v = s.readWord().toLLong();
	return s;
}

//! \~english Read word as integer
//! \~russian Читает слово как целое
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, ullong & v) {
	v = s.readWord().toULLong();
	return s;
}

//! \~english Read word as floating-point number
//! \~russian Читает слово как число с плавающей точкой
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, float & v) {
	v = s.readWord().toFloat();
	return s;
}

//! \~english Read word as floating-point number
//! \~russian Читает слово как число с плавающей точкой
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, double & v) {
	v = s.readWord().toDouble();
	return s;
}


//! \~english Read word
//! \~russian Читает слово
template<typename P>
inline PITextStream<P> & operator>>(PITextStream<P> & s, PIString & v) {
	v = s.readWord();
	return s;
}

#endif
