/*
    PIP - Platform Independent Primitives
    JSON class
    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/>.
*/
#include "pijson.h"


//! \~\class PIJSON pijson.h
//! \~\details
//! \~english \section PIJSON_sec0 Synopsis
//! \~russian \section PIJSON_sec0 Краткий обзор
//! \~english
//!
//! \~russian
//! JSON - это древовидная структура, каждый элемент которой может быть либо
//! парой имя:значение, либо массивом, либо объектом, т.е. именованным
//! списком элементов. Корневой элемент JSON может быть либо массивом,
//! либо объектом.
//!
//! Массивы заключены в квадратные скобки [], их элементы не имеют имени
//! и разделяются запятыми.
//!
//! Объекты заключены в фигурные скобки {}, их элементы имеют имя
//! и разделяются запятыми.
//!
//!
//! \~english \section PIJSON_sec1 PIJSON tree
//! \~russian \section PIJSON_sec1 Дерево PIJSON
//! \~english
//!
//! \~russian
//! %PIJSON представляет собой элемент дерева JSON. Каждый элемент имеет тип (\a type())
//! и может иметь имя (\a name()). Если это конечный элемент,то он будет иметь значение,
//! доступное через \a value(), \a toBool(), \a toInt(), \a toDouble() или \a toString().
//!
//! Если элемент преставляет собой массив, то его размер доступен через \a size(),
//! а элементы массива через целочисленный оператор []. Весь массив доступен через \a array().
//!
//! Если элемент преставляет собой объект, то его размер доступен через \a size(),
//! а элементы объекта через строковый оператор []. Проверить наличие элемента по имени можно
//! с помощью \a contains(). Весь объект доступен через \a object().
//!
//! Создать дерево из текстового представления JSON можно с помощью \a fromJSON(), а
//! преобразовать в текст с помощью \a toJSON().
//!
//!
//! \~english \section PIJSON_sec2 PIJSON creation
//! \~russian \section PIJSON_sec2 Создание PIJSON
//! \~english
//!
//! \~russian
//! Для создания нового дерева необходимо лишь создать пустой корневой PIJSON и заполнить его
//! значениями, массивами или объектами с помощью целочисленного или строкового оператора [].
//! В зависимости от типа аргумента элемент преобразуется либо в массив, либо в объект.
//!
//! При приравнивании PIJSON к какому-либо значению, его тип автоматически установится в нужный.
//! При обращении на запись целочисленным оператором [] размер массива автоматически увеличится
//! при необходимости.
//!
//!
//! \~english \section PIJSON_sec3 Mask/unmask
//! \~russian \section PIJSON_sec3 Маскирование/размаскирование
//! \~english
//!
//! \~russian
//! Строковые значения в стандарте JSON могут иметь в явном виде только печатные символы,
//! спецсимволы и юникод должны быть преобразованы маскированием, т.е., например, вместо
//! символа новой строки должно быть "\n", а не-ASCII символы должны быть в виде "\uXXXX".
//!
//! Оператор вывода в PICout не выполняет маскирования строк.
//!
//! Методы \a toJSON() и \a fromJSON() маскируют и размаскируют строковые поля.
//!
//!
//! \~english \section PIJSON_sec4 Examples
//! \~russian \section PIJSON_sec4 Примеры
//! \~english
//!
//! \~russian
//! Чтение:
//! \~\code
//! PIJSON json = PIJSON::fromJSON(
//! "["
//! "  true,"
//! "  10,"
//! "  {"
//! "    \"num\":1.E-2,"
//! "    \"str\":\"text\""
//! "  },"
//! "]");
//! piCout << json;
//! piCout << json.toJSON();
//! \endcode
//! \~\code
//! [
//!   true,
//!   10,
//!   {
//!     "num": 1.e-2,
//!     "str": "text"
//!   }
//! ]
//!
//! [true,10,{"num":1.e-2,"str":"text"}]
//! \endcode
//! Разбор:
//! \~\code
//! PIJSON json = PIJSON::fromJSON(
//! "["
//! "  true,"
//! "  10,"
//! "  {"
//! "    \"num\":1.E-2,"
//! "    \"str\":\"text\""
//! "  },"
//! "]");
//! piCout << "top-level:";
//! for (const auto & i: json.array())
//! 	piCout << i.value();
//! piCout << "[2][\"str\"]:";
//! piCout << json[2]["str"].toString();
//! \endcode
//! \~\code
//! top-level:
//! PIVariant(Bool, 1)
//! PIVariant(String, 10)
//! PIVariant(Invalid)
//! [2]["str"]:
//! text
//! \endcode
//!
//! Создание:\n
//! Простой массив
//! \~\code
//! PIJSON json;
//! json[0] = -1;
//! json[1] = "text";
//! json[3] = false;
//! piCout << json.toJSON();
//! \endcode
//! \~\code
//! [-1,"text","",false]
//! \endcode
//!
//! Массив объектов
//! \~\code
//! struct {
//! 	PIString name;
//! 	PIString surname;
//! 	PIString email;
//! } persons[] = {
//! 	{"Ivan", "Ivanov", "ivan@pip.ru"},
//! 	{"Igor", "Igorevich", "igor@pip.ru"},
//! 	{"Andrey", "Andreevich", "andrey@pip.ru"}
//! };
//! PIJSON obj;
//! PIJSON json;
//! int index = 0;
//! for (const auto & p: persons) {
//! 	obj["index"] = index++;
//! 	obj["name"] = p.name;
//! 	obj["surname"] = p.surname;
//! 	obj["email"] = p.email;
//! 	json << obj;
//! }
//! piCout << json;
//! \endcode
//! \~\code
//! [
//!   {
//!     "name": "Ivan",
//!     "email": "ivan@pip.ru",
//!     "index": 0,
//!     "surname": "Ivanov"
//!   },
//!   {
//!     "name": "Igor",
//!     "email": "igor@pip.ru",
//!     "index": 1,
//!     "surname": "Igorevich"
//!   },
//!   {
//!     "name": "Andrey",
//!     "email": "andrey@pip.ru",
//!     "index": 2,
//!     "surname": "Andreevich"
//!   }
//! ]
//! \endcode
//!


PIJSON PIJSON::newObject(const PIVariantMap & fields) {
	PIJSON ret;
	ret.c_type = Object;
	auto it    = fields.makeIterator();
	while (it.next())
		ret[it.key()] = it.value();
	return ret;
}


PIJSON PIJSON::newArray(const PIVariantVector & fields) {
	PIJSON ret;
	ret.c_type = Array;
	for (const auto & i: fields)
		ret << i;
	return ret;
}


PIJSON PIJSON::newString(const PIString & v) {
	PIJSON ret;
	ret = v;
	return ret;
}


const PIVector<PIJSON> & PIJSON::array() const {
	if (!isArray()) {
		return nullEntry().c_array;
	} else {
		return c_array;
	}
}


const PIMap<PIString, PIJSON> & PIJSON::object() const {
	if (!isObject()) {
		return nullEntry().c_object;
	} else {
		return c_object;
	}
}


//! \details
//! \~english
//! If "v" type is boolean set type to \a PIJSON::Boolean.\n
//! If "v" type is any numeric set type to \a PIJSON::Number.\n
//! If "v" type is string set type to \a PIJSON::String.\n
//! In case of any other type set element type to \a PIJSON::Invalid.
//! \~russian
//! Если тип "v" логический, то устанавливает тип в \a PIJSON::Boolean.\n
//! Если тип "v" любой численный, то устанавливает тип в \a PIJSON::Number.\n
//! Если тип "v" строковый, то устанавливает тип в \a PIJSON::String.\n
//! Если тип "v" любой другой, то устанавливает тип в \a PIJSON::Invalid.
void PIJSON::setValue(const PIVariant & v) {
	c_value = v;
	switch (v.type()) {
	case PIVariant::pivBool: c_type = Boolean; break;
	case PIVariant::pivUChar:
	case PIVariant::pivShort:
	case PIVariant::pivUShort:
	case PIVariant::pivInt:
	case PIVariant::pivUInt:
	case PIVariant::pivLLong:
	case PIVariant::pivULLong:
	case PIVariant::pivFloat:
	case PIVariant::pivDouble:
	case PIVariant::pivLDouble: c_type = Number; break;
	case PIVariant::pivString: c_type = String; break;
	default: c_type = Invalid; break;
	}
}


void PIJSON::clear() {
	c_type  = Invalid;
	c_value = PIVariant();
	c_name.clear();
	c_object.clear();
	c_array.clear();
}


int PIJSON::size() const {
	if (isArray()) return c_array.size_s();
	if (isObject()) return c_object.size_s();
	return 0;
}


bool PIJSON::contains(const PIString & key) const {
	if (!isObject()) return false;
	return c_object.contains(key);
}


void PIJSON::resize(int new_size) {
	c_type = Array;
	c_array.resize(new_size, newString());
}


const PIJSON & PIJSON::operator[](int index) const {
	if (!isArray()) return nullEntry();
	return c_array[index];
}


PIJSON & PIJSON::operator[](int index) {
	c_type = Array;
	if (index >= c_array.size_s()) {
		c_array.resize(index + 1, newString());
	}
	PIJSON & ret(c_array[index]);
	return ret;
}


PIJSON & PIJSON::operator<<(const PIJSON & element) {
	c_type = Array;
	c_array << element;
	return *this;
}


PIJSON & PIJSON::operator<<(const PIVariant & element) {
	c_type = Array;
	PIJSON e;
	e = element;
	c_array << e;
	return *this;
}


PIJSON & PIJSON::operator=(const PIJSON & v) {
	c_type   = v.c_type;
	c_value  = v.c_value;
	c_object = v.c_object;
	c_array  = v.c_array;
	return *this;
}


PIJSON PIJSON::operator[](const PIString & key) const {
	if (!isObject()) return nullEntry();
	if (!c_object.contains(key)) return nullEntry();
	return c_object.value(key);
}


PIJSON & PIJSON::operator[](const PIString & key) {
	c_type = Object;
	PIJSON & ret(c_object[key]);
	ret.c_name = key;
	return ret;
}


PIString PIJSON::toJSON(PrintType print_type, bool mask_unicode) const {
	PIString ret;
	print(ret, *this, "", print_type == Tree, true, false, mask_unicode);
	return ret;
}


PIJSON PIJSON::fromJSON(PIString str) {
	return parseValue(str);
}


PIJSON & PIJSON::nullEntry() {
	static PIJSON ret;
	ret.clear();
	return ret;
}


PIString PIJSON::parseName(PIString & s) {
	// piCout << "\n\n\n parseName" << s;
	PIString ret;
	ret = s.takeRange('"', '"');
	s.trim();
	if (s.isEmpty()) return PIString();
	if (s[0] != ':') return PIString();
	s.remove(0).trim();
	return PIJSON::stringUnmask(ret);
}


PIJSON PIJSON::parseValue(PIString & s) {
	PIJSON ret;
	// piCout << "\n\n\n parseValue" << s;
	s.trim();
	if (s.isEmpty()) return ret;
	if (s[0] == '{') {
		ret = parseObject(s.takeRange('{', '}').trim());
	} else if (s[0] == '[') {
		ret = parseArray(s.takeRange('[', ']').trim());
	} else {
		s.trim();
		if (s.startsWith('"')) {
			ret.c_type  = PIJSON::String;
			ret.c_value = PIJSON::stringUnmask(s.takeRange('"', '"'));
		} else {
			PIString value;
			int ind = s.find(',');
			if (ind >= 0) {
				value = s.takeLeft(ind).trim().toLowerCase();
			} else {
				value = s;
				s.clear();
			}
			// piCout << "\n\n\n parseValue value = \"" << value << "\"";
			if (value == "null") {
				ret.c_type = PIJSON::Null;
			} else if (value == "true") {
				ret.c_type  = PIJSON::Boolean;
				ret.c_value = true;
			} else if (value == "false") {
				ret.c_type  = PIJSON::Boolean;
				ret.c_value = false;
			} else {
				ret.c_type  = PIJSON::Number;
				ret.c_value = value;
			}
		}
	}
	return ret;
}


PIJSON PIJSON::parseObject(PIString s) {
	// piCout << "\n\n\n parseObject" << s;
	PIJSON ret;
	ret.c_type = PIJSON::Object;
	while (s.isNotEmpty()) {
		PIString name = PIJSON::stringUnmask(parseName(s));
		PIJSON value  = parseValue(s);
		auto & child(ret.c_object[name]);
		child        = value;
		child.c_name = name;
		s.trim();
		if (s.isEmpty()) break;
		if (s[0] != ',') break;
		s.remove(0);
	}
	return ret;
}


PIJSON PIJSON::parseArray(PIString s) {
	// piCout << "\n\n\n parseArray" << s;
	PIJSON ret;
	ret.c_type = PIJSON::Array;
	while (s.isNotEmpty()) {
		PIJSON value = parseValue(s);
		ret.c_array << value;
		s.trim();
		if (s.isEmpty()) break;
		if (s[0] != ',') break;
		s.remove(0);
	}
	return ret;
}


PIString PIJSON::stringMask(const PIString & s, bool mask_unicode) {
	PIString ret;
	for (auto c: s) {
		if (!c.isAscii()) {
			if (mask_unicode)
				ret += "\\u" + PIString::fromNumber(c.unicode16Code(), 16).expandLeftTo(4, '0');
			else
				ret += c;
		} else {
			char ca = c.toAscii();
			switch (ca) {
			case '"': ret += "\\\""; break;
			case '\\': ret += "\\\\"; break;
			case '/': ret += "\\/"; break;
			case '\b': ret += "\\b"; break;
			case '\f': ret += "\\f"; break;
			case '\n': ret += "\\n"; break;
			case '\r': ret += "\\r"; break;
			case '\t': ret += "\\t"; break;
			default: ret += ca;
			}
		}
	}
	return ret;
}


PIString PIJSON::stringUnmask(const PIString & s) {
	PIString ret;
	for (int i = 0; i < s.size_s(); ++i) {
		char ca = s[i].toAscii();
		if (ca == '\\') {
			if (i == s.size_s() - 1) continue;
			++i;
			char na = s[i].toAscii();
			switch (na) {
			case '"': ret += '\"'; break;
			case '\\': ret += '\\'; break;
			case '/': ret += '/'; break;
			case 'b': ret += '\b'; break;
			case 'f': ret += '\f'; break;
			case 'n': ret += '\n'; break;
			case 'r': ret += '\r'; break;
			case 't': ret += '\t'; break;
			case 'u':
				ret += PIChar(s.mid(i + 1, 4).toUShort(16));
				i += 4;
				break;
			default: break;
			}
		} else {
			ret += s[i];
		}
	}
	return ret;
}


void PIJSON::print(PIString & s, const PIJSON & v, PIString tab, bool spaces, bool transform, bool comma, bool mask_unicode) {
	if (spaces) s += tab;
	/*
	    switch (v.c_type) {
	    case JSONEntry::Invalid: s << "(Invalid)"; break;
	    case JSONEntry::Null:    s << "(Null)"; break;
	    case JSONEntry::Boolean: s << "(Boolean)"; break;
	    case JSONEntry::Number:  s << "(Number)"; break;
	    case JSONEntry::String:  s << "(String)"; break;
	    case JSONEntry::Object:  s << "(Object)"; break;
	    case JSONEntry::Array:   s << "(Array)"; break;
	    }
	*/
	if (v.name().isNotEmpty()) {
		s += '"' + (transform ? stringMask(v.name(), mask_unicode) : v.name()) + "\":";
		if (spaces) s += ' ';
	}
	switch (v.c_type) {
	case PIJSON::Invalid:
	case PIJSON::Null: s += "null"; break;
	case PIJSON::Boolean: s += PIString::fromBool(v.c_value.toBool()); break;
	case PIJSON::Number: s += v.c_value.toString(); break;
	case PIJSON::String: s += '"' + (transform ? stringMask(v.c_value.toString(), mask_unicode) : v.c_value.toString()) + '"'; break;
	case PIJSON::Object:
		s += "{";
		if (spaces) s += '\n';
		{
			PIString ntab = tab + "  ";
			auto it       = v.c_object.makeIterator();
			int cnt       = 0;
			while (it.next()) {
				print(s, it.value(), ntab, spaces, transform, ++cnt < v.c_object.size_s(), mask_unicode);
			}
		}
		if (spaces) s += tab;
		s += "}";
		break;
	case PIJSON::Array:
		s += "[";
		if (spaces) s += '\n';
		{
			PIString ntab = tab + "  ";
			for (int i = 0; i < v.c_array.size_s(); ++i) {
				print(s, v.c_array[i], ntab, spaces, transform, i < v.c_array.size_s() - 1, mask_unicode);
			}
		}
		if (spaces) s += tab;
		s += "]";
		break;
	}
	if (comma) s += ',';
	if (spaces) s += "\n";
}
