/*
    PIP - Platform Independent Primitives
    PIValueTree conversions
    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 "pivaluetree_conversions.h"

#include "pifile.h"
#include "piiostream.h"
#include "piiostring.h"
#include "pijson.h"
#include "pipropertystorage.h"

const char _attribute_[] = "_attribute_";

PIString unmask(const PIString & str) {
	PIString ret(str);
	ret.unmask("#\\");
	if (ret.isNotEmpty())
		if (ret[0] == '\\') ret.remove(0);
	return ret;
}

PIString mask(const PIString & str) {
	PIString ret = str;
	if (ret.isEmpty()) return ret;
	ret.mask("#\\");
	if (ret[0].isSpace()) ret.insert(0, '\\');
	return ret;
}

PIString overrideFile(PIString path) {
	if (path.isEmpty()) return {};
	PIFile::FileInfo fi(path);
	auto ext = fi.extension();
	path.insert(path.size_s() - ext.size_s() - (ext.isEmpty() ? 0 : 1), ".override");
	return path;
}


PIValueTree PIValueTreeConversions::fromPropertyStorage(const PIPropertyStorage & ps) {
	PIValueTree ret;
	for (const auto & i: ps) {
		PIValueTree v;
		v.setName(i.name);
		v.setValue(i.value);
		v.setComment(i.comment);
		ret.addChild(v);
	}
	return ret;
}


PIValueTree PIValueTreeConversions::fromVariantMap(const PIVariantMap & vm) {
	PIValueTree ret;
	for (const auto & i: vm) {
		PIValueTree v;
		v.setName(i.first);
		v.setValue(i.second);
		ret.addChild(v);
	}
	return ret;
}


PIValueTree fromJSONTree(const PIJSON & json) {
	PIValueTree ret;
	ret.setName(json["name"].toString());
	ret.setComment(json["comment"].toString());
	PIString value_str = json["value"].toString();
	PIVariant value    = PIVariant::fromType(json["type"].toString());
	if (value_str.isNotEmpty()) {
		if (value.isValid())
			value.setValueFromString(value_str);
		else
			value = value_str;
	}
	ret.setValue(value);
	const PIJSON & attr(json["attributes"]);
	for (const auto & i: attr.object())
		ret.attributes()[i.first] = i.second.value();
	bool is_array = ret.isArray();
	int chindex   = 0;
	const PIJSON & chl(json["children"]);
	for (const auto & i: chl.array()) {
		PIValueTree c = fromJSONTree(i);
		if (is_array) c.setName(PIString::fromNumber(chindex++));
		ret.addChild(c);
	}
	return ret;
}


PIValueTree PIValueTreeConversions::fromJSON(const PIJSON & json) {
	PIValueTree ret;
	if (json.isObject()) ret = fromJSONTree(json);
	if (json.isArray()) {
		for (const auto & i: json.array())
			ret.addChild(fromJSONTree(i));
	}
	return ret;
}


PIValueTree prepareFromText(const PIValueTree & root) {
	PIValueTree ret;
	ret.setName(root.name());
	ret.setComment(root.comment());
	if (root.contains(_attribute_)) {
		for (const auto & a: root.child(_attribute_).children())
			ret.attributes()[a.name()] = a.value().toString();
	}
	PIVariant value = PIVariant::fromType(ret.attribute("type", PIVariant::typeName<PIString>()).toString());
	value.setValueFromString(root.value().toString());
	ret.setValue(value);
	for (const auto & c: root.children()) {
		if (c.name() != _attribute_) ret[c.name()] = prepareFromText(c);
	}
	return ret;
}


PIValueTree PIValueTreeConversions::fromText(PIIODevice * device) {
	PIValueTree ret;
	PIMap<PIString, PIString> substitutions;
	if (!device) return ret;
	PIString base_path;
	if (device->isTypeOf<PIFile>()) base_path = PIFile::FileInfo(device->path()).dir().replaceAll('\\', '/');
	PIIOTextStream ts(device);
	PIString line, comm;
	PIVariant value;
	PIString type;
	PIStringList prefix, path;
	while (!ts.isEnd()) {
		PIString l = ts.readLine().trim();
		if (l.endsWith(" \\")) {
			line += l.cutRight(2).trim() + "\n";
			continue;
		}
		line += l;
		// piCout << "L" << line.replacedAll("\n", "\\n");
		if (line.size() < 2) {
			line.clear();
			continue;
		}
		if (line.startsWith('[')) {
			prefix = line.inBrackets('[', ']').trim().split('.');
			line.clear();
			continue;
		}
		if (line.startsWith('#')) {
			line.clear();
			continue;
		}
		type = PIVariant::typeName<PIString>();
		comm.clear();
		int ind = -1, cind = -1;
		for (;;) {
			ind = line.find('#', ind + 1);
			if (ind < 0) break;
			if (ind > 0)
				if (line[ind - 1] == '\\') continue;
			cind = ind;
			break;
		}
		if (cind >= 0) {
			comm       = line.takeMid(cind + 1);
			bool typed = false;
			if (comm.isNotEmpty()) {
				if (comm.size() == 1)
					typed = true;
				else if (comm[0].isAlpha() && comm[1].isSpace())
					typed = true;
			}
			if (typed) {
				switch (comm.takeLeft(1)[0].toAscii()) {
				case 'b': type = PIVariant::typeName<bool>(); break;
				case 'n': type = PIVariant::typeName<int>(); break;
				case 'f': type = PIVariant::typeName<double>(); break;
				case 'l': type = PIVariant::typeName<PIStringList>(); break;
				case 'c': type = PIVariant::typeName<PIVariantTypes::Color>(); break;
				case 'p':
				case 'v': type = PIVariant::typeName<PIPointd>(); break;
				case 'r':
				case 'a': type = PIVariant::typeName<PIRectd>(); break;
				default: break;
				}
			}
			comm.trim();
			line.cutRight(1).trim();
		}
		for (const auto & s: substitutions)
			line.replaceAll("${" + s.first + "}", s.second);
		ind = line.find('=');
		if (ind > 0) {
			path = prefix;
			path << line.takeLeft(ind).split('.').trim();
			line.cutLeft(1).trim();
			if (path.front() == "include") {
				PIString include = line.trimmed();
				if (!PIFile::FileInfo(include).isAbsolute()) {
					include = base_path + "/" + include.replaceAll('\\', '/');
					include.replaceAll("//", '/');
				}
				PIValueTree inc_vt = PIValueTreeConversions::fromTextFile(include);
				inc_vt.forEachRecursive(
					[&substitutions](const PIValueTree & v, const PIString & fn) { substitutions[fn] = v.value().toString(); });
				path.clear();
			}
		} else {
			line.clear();
			continue;
		}
		// piCout << path << line << comm;
		if (path.isEmpty()) {
			line.clear();
			continue;
		}
		PIValueTree & leaf(ret[path]);
		leaf.setComment(comm);
		leaf.setValue(unmask(line));
		substitutions[path.join('.')] = leaf.value().toString();
		if (!path.contains(_attribute_))
			if (!leaf.contains({_attribute_, "type"})) leaf[_attribute_].addChild({"type", type});
		line.clear();
	}
	return prepareFromText(ret);
	// return ret;
}


PIJSON toJSONTree(const PIValueTree & root, PIValueTreeConversions::Options options) {
	PIJSON ret = PIJSON::newObject();
	if (root.name().isNotEmpty()) ret["name"] = root.name();
	if (root.comment().isNotEmpty() && options[PIValueTreeConversions::WithComment]) ret["comment"] = root.comment();
	if (root.value().isValid()) {
		if (options[PIValueTreeConversions::WithType]) ret["type"] = root.value().typeName();
		ret["value"] = root.value().toString();
	}
	if (root.attributes().isNotEmpty() && options[PIValueTreeConversions::WithAttributes]) {
		PIJSON j;
		for (const auto & a: root.attributes())
			j[a.first] = a.second.toString();
		ret["attributes"] = j;
	}
	if (root.hasChildren()) {
		PIJSON j;
		for (const auto & c: root.children())
			j << toJSONTree(c, options);
		ret["children"] = j;
	}
	return ret;
}


PIJSON PIValueTreeConversions::toJSON(const PIValueTree & root, Options options) {
	if (options[IncludeRoot]) return toJSONTree(root, options);
	PIJSON ret = PIJSON::newArray();
	for (const auto & c: root.children())
		ret << toJSONTree(c, options);
	return ret;
}


PIString toTextTreeAttributes(const PIValueTree & root, PIString prefix, PIValueTreeConversions::Options options) {
	PIString ret;
	for (const auto & a: root.attributes()) {
		if (a.first == "type") continue;
		ret += prefix + _attribute_ + "." + a.first;
		ret += " = " + mask(a.second.toString());
		ret += '\n';
	}
	if (options[PIValueTreeConversions::WithType]) {
		ret += prefix + _attribute_ + ".type";
		ret += " = " + root.value().typeName();
		ret += '\n';
	}
	return ret;
}


PIString toTextTree(const PIValueTree & root, PIString prefix, PIValueTreeConversions::Options options) {
	PIString ret;
	if (prefix.isNotEmpty()) prefix += '.';
	prefix += root.name();
	if (root.hasChildren()) {
		ret += "\n[" + prefix + "]\n";
		if (root.isArray() && options[PIValueTreeConversions::WithAttributes]) ret += toTextTreeAttributes(root, "", options);
		for (const auto & c: root.children()) {
			if (c.hasChildren()) continue;
			ret += toTextTree(c, prefix, options);
		}
		for (const auto & c: root.children()) {
			if (!c.hasChildren()) continue;
			ret += toTextTree(c, prefix, options);
		}
	} else {
		if (root.value().isValid()) {
			ret += root.name() + " = ";
			ret += mask(root.value().toString()).replacedAll('\n', " \\\n");
		}
		if (root.comment().isNotEmpty() && options[PIValueTreeConversions::WithComment]) {
			ret += " # ";
			ret += root.comment();
		}
		ret += "\n";
		if (options[PIValueTreeConversions::WithAttributes]) ret += toTextTreeAttributes(root, root.name() + ".", options);
	}

	return ret;
}


PIString PIValueTreeConversions::toText(const PIValueTree & root, Options options) {
	PIString ret;
	for (const auto & c: root.children()) {
		if (c.hasChildren()) continue;
		ret += toTextTree(c, {}, options);
	}
	for (const auto & c: root.children()) {
		if (!c.hasChildren()) continue;
		ret += toTextTree(c, {}, options);
	}
	return ret;
}


PIValueTree PIValueTreeConversions::fromText(const PIString & str) {
	PIIOString dev(str);
	return fromText(&dev);
}


PIValueTree PIValueTreeConversions::fromJSONFile(const PIString & path) {
	auto ret = PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(PIFile::readAll(path))));
	auto ofp = overrideFile(path);
	if (PIFile::isExists(ofp)) {
		auto override_vt = PIValueTreeConversions::fromJSON(PIJSON::fromJSON(PIString::fromUTF8(PIFile::readAll(ofp))));
		ret.merge(override_vt);
	}
	return ret;
}


PIValueTree PIValueTreeConversions::fromTextFile(const PIString & path) {
	PIFile f(path, PIIODevice::ReadOnly);
	auto ret = PIValueTreeConversions::fromText(&f);
	auto ofp = overrideFile(path);
	if (PIFile::isExists(ofp)) {
		PIFile of(ofp, PIIODevice::ReadOnly);
		auto override_vt = PIValueTreeConversions::fromText(&of);
		ret.merge(override_vt);
	}
	return ret;
}


bool PIValueTreeConversions::toJSONFile(const PIString & path, const PIValueTree & root, Options options) {
	auto d      = toJSON(root, options).toJSON(PIJSON::Tree).toUTF8();
	int written = PIFile::writeAll(path, d);
	return written == d.size_s();
}


bool PIValueTreeConversions::toTextFile(const PIString & path, const PIValueTree & root, Options options) {
	auto d      = toText(root, options).toUTF8();
	int written = PIFile::writeAll(path, d);
	return written == d.size_s();
}
