/*

Miranda NG: the free IM client for Microsoft* Windows*

Copyright (�) 2012-16 Miranda NG project (http://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "stdafx.h"

#include "JSONNode.h"
#include "JSONNode.inl"

#ifdef JSON_WRITER
#include "JSONWorker.h"

const static json_string WRITER_EMPTY;
#ifndef JSON_NEWLINE
	const static json_string NEW_LINE(JSON_TEXT("\n"));
#else
	const static json_string NEW_LINE(JSON_TEXT(JSON_NEWLINE));
#endif

#ifdef JSON_INDENT
	const static json_string INDENT(JSON_TEXT(JSON_INDENT));

	inline json_string makeIndent(unsigned int amount){
		if (amount == 0xFFFFFFFF) return WRITER_EMPTY;
		json_string result;
		result.reserve(amount);
		for(unsigned int i=0; i < amount; ++i){
			result += INDENT;
		}
		return result;
	}
#else
	inline json_string makeIndent(unsigned int amount){
		if (amount == 0xFFFFFFFF) return WRITER_EMPTY;
		return json_string(amount, JSON_TEXT('\t'));
	}
#endif

json_string internalJSONNode::WriteName(bool formatted, bool arrayChild) const {
	if (arrayChild){
		return WRITER_EMPTY ;
	} else {
		return JSON_TEXT("\"") + JSONWorker::UnfixString(_name, _name_encoded) + ((formatted) ? JSON_TEXT("\" : ") : JSON_TEXT("\":"));
	}
}

json_string internalJSONNode::WriteChildren(unsigned int indent){
	//Iterate through the children and write them
	if (Children.empty()) return WRITER_EMPTY;

	json_string indent_plus_one;
	json_string indent_this;
	json_string res;
	//handle whether or not it's formatted JSON
	if (indent != 0xFFFFFFFF){  //it's formatted, make the indentation strings
		indent_this = NEW_LINE + makeIndent(indent);
		indent_plus_one = NEW_LINE + makeIndent(++indent);
	}
	//else it's not formatted, leave the indentation strings empty
	const size_t size_minus_one = Children.size() - 1;
	size_t i=0;
	json_foreach(Children, it){
		res += indent_plus_one + (*it) -> internal -> Write(indent, type() == JSON_ARRAY);
		if (i < size_minus_one) res += JSON_TEXT(",");  //the last one does not get a comma, but all of the others do
		++i;
	}
	return res + indent_this;
}

#ifdef JSON_COMMENTS
	#ifdef JSON_WRITE_BASH_COMMENTS
		const static json_string SINGLELINE(JSON_TEXT("#"));
	#else
		const static json_string SINGLELINE(JSON_TEXT("//"));
	#endif

	json_string internalJSONNode::WriteComment(unsigned int indent) const {
		if (indent == 0xFFFFFFFF) return WRITER_EMPTY;
		if (_comment.empty()) return WRITER_EMPTY;
		size_t pos = _comment.find(JSON_TEXT('\n'));
		if (pos == json_string::npos){  //Single line comment
			return NEW_LINE + makeIndent(indent) + SINGLELINE + _comment + NEW_LINE + makeIndent(indent);
		}

		/*
		Multiline comments
		*/
		#if defined(JSON_WRITE_BASH_COMMENTS) || defined(JSON_WRITE_SINGLE_LINE_COMMENTS)
			json_string result(NEW_LINE + makeIndent(indent));
		#else
			json_string result(NEW_LINE + makeIndent(indent) + JSON_TEXT("/*") + NEW_LINE + makeIndent(indent + 1));
		#endif
		size_t old = 0;
		while(pos != json_string::npos){
			if (pos && _comment[pos - 1] == JSON_TEXT('\r')) --pos;
			#if defined(JSON_WRITE_BASH_COMMENTS) || defined(JSON_WRITE_SINGLE_LINE_COMMENTS)
				result += SINGLELINE;
			#endif
			result += _comment.substr(old, pos - old) + NEW_LINE;
			#if defined(JSON_WRITE_BASH_COMMENTS) || defined(JSON_WRITE_SINGLE_LINE_COMMENTS)
				result += makeIndent(indent);
			#else
				result += makeIndent(indent + 1);
			#endif
			old = (_comment[pos] == JSON_TEXT('\r')) ? pos + 2 : pos + 1;
			pos = _comment.find(JSON_TEXT('\n'), old);
		}
		#if defined(JSON_WRITE_BASH_COMMENTS) || defined(JSON_WRITE_SINGLE_LINE_COMMENTS)
			result += SINGLELINE;
		#endif
		result += _comment.substr(old, pos - old) + NEW_LINE + makeIndent(indent);
		#if defined(JSON_WRITE_BASH_COMMENTS) || defined(JSON_WRITE_SINGLE_LINE_COMMENTS)
			return result;
		#else
			return result + JSON_TEXT("*/") + NEW_LINE + makeIndent(indent);
		#endif
	}
#else
	json_string internalJSONNode::WriteComment(unsigned int) const {
		return WRITER_EMPTY;
	}
#endif

json_string internalJSONNode::Write(unsigned int indent, bool arrayChild){
	const bool formatted = indent != 0xFFFFFFFF;

	#ifndef JSON_PREPARSE
		if (!(formatted || fetched)) {  //It's not formatted or fetched, just do a raw dump
			return WriteComment(indent) + WriteName(false, arrayChild) + _string;
		}
	#endif

	//It's either formatted or fetched
	switch (type()) {
		case JSON_NODE:	//got members, write the members
			Fetch();
			return WriteComment(indent) + WriteName(formatted, arrayChild) + JSON_TEXT("{") + WriteChildren(indent) + JSON_TEXT("}");
		case JSON_ARRAY:		//write out the child nodes int he array
			Fetch();
			return WriteComment(indent) + WriteName(formatted, arrayChild) + JSON_TEXT("[") + WriteChildren(indent) + JSON_TEXT("]");
		case JSON_NUMBER:	//write out a literal, without quotes
		case JSON_NULL:
		case JSON_BOOL:
			return WriteComment(indent) + WriteName(formatted, arrayChild) + _string;
	}

	JSON_ASSERT_SAFE(type() == JSON_STRING, JSON_TEXT("Writing an unknown JSON node type"), return JSON_TEXT(""););
	//If it go here, then it's a json_string
	#ifndef JSON_PREPARSE
		if (fetched) return WriteComment(indent) + WriteName(formatted, arrayChild) + JSON_TEXT("\"") + JSONWorker::UnfixString(_string, _string_encoded) + JSON_TEXT("\"");  //It's already been fetched, meaning that it's unescaped
		return WriteComment(indent) + WriteName(formatted, arrayChild) + _string;  //it hasn't yet been fetched, so it's already unescaped, just do a dump
	#else
		return WriteComment(indent) + WriteName(formatted, arrayChild) + JSON_TEXT("\"") + JSONWorker::UnfixString(_string, _string_encoded) + JSON_TEXT("\"");
	#endif
}
#endif