#ifndef RAPIDJSON_PRETTYWRITER_H_
#define RAPIDJSON_PRETTYWRITER_H_

#include "writer.h"

namespace rapidjson {

//! Writer with indentation and spacing.
/*!
	\tparam Stream Type of ouptut stream.
	\tparam Encoding Encoding of both source strings and output.
	\tparam Allocator Type of allocator for allocating memory of stack.
*/
template<typename Stream, typename Encoding = UTF8<>, typename Allocator = MemoryPoolAllocator<> >
class PrettyWriter : public Writer<Stream, Encoding, Allocator> {
public:
	typedef Writer<Stream, Encoding, Allocator> Base;
	typedef typename Base::Ch Ch;

	//! Constructor
	/*! \param stream Output stream.
		\param allocator User supplied allocator. If it is null, it will create a private one.
		\param levelDepth Initial capacity of
	*/
	PrettyWriter(Stream& stream, int precision = 20, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) :
		Base(stream, precision, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {}

	//! Set custom indentation.
	/*! \param indentChar		Character for indentation. Must be whitespace character (' ', '\t', '\n', '\r').
		\param indentCharCount	Number of indent characters for each indentation level.
		\note The default indentation is 4 spaces.
	*/
	PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) {
		RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r');
		indentChar_ = indentChar;
		indentCharCount_ = indentCharCount;
		return *this;
	}

	//@name Implementation of Handler.
	//@{

	PrettyWriter& Null_()				{ PrettyPrefix(kNull_Type);   Base::WriteNull_();			return *this; }
	PrettyWriter& Bool_(bool b)			{ PrettyPrefix(b ? kTrueType : kFalseType); Base::WriteBool_(b); return *this; }
	PrettyWriter& Int(int i)			{ PrettyPrefix(kNumberType); Base::WriteInt(i);			return *this; }
	PrettyWriter& Uint(unsigned u)		{ PrettyPrefix(kNumberType); Base::WriteUint(u);		return *this; }
	PrettyWriter& Int64(int64_t i64)	{ PrettyPrefix(kNumberType); Base::WriteInt64(i64);		return *this; }
	PrettyWriter& Uint64(uint64_t u64)	{ PrettyPrefix(kNumberType); Base::WriteUint64(u64);	return *this; }
	PrettyWriter& Double(double d)		{ PrettyPrefix(kNumberType); Base::WriteDouble(d);		return *this; }

	PrettyWriter& String(const Ch* str, SizeType length, bool copy = false) {
		(void)copy;
		PrettyPrefix(kStringType);
		Base::WriteString(str, length);
		return *this;
	}

	PrettyWriter& StartObject() {
		PrettyPrefix(kObjectType);
		new (Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(false);
		Base::WriteStartObject();
		return *this;
	}

	PrettyWriter& EndObject(SizeType memberCount = 0) {
		(void)memberCount;
		RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level));
		RAPIDJSON_ASSERT(!Base::level_stack_.template Top<typename Base::Level>()->inArray);
		bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;

		if (!empty) {
			Base::stream_.Put('\n');
			WriteIndent();
		}
		Base::WriteEndObject();
		return *this;
	}

	PrettyWriter& StartArray() {
		PrettyPrefix(kArrayType);
		new (Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(true);
		Base::WriteStartArray();
		return *this;
	}

	PrettyWriter& EndArray(SizeType memberCount = 0) {
		(void)memberCount;
		RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level));
		RAPIDJSON_ASSERT(Base::level_stack_.template Top<typename Base::Level>()->inArray);
		bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;

		if (!empty) {
			Base::stream_.Put('\n');
			WriteIndent();
		}
		Base::WriteEndArray();
		return *this;
	}

	//@}

	//! Simpler but slower overload.
	PrettyWriter& String(const Ch* str) { return String(str, internal::StrLen(str)); }

protected:
	void PrettyPrefix(Type type) {
		(void)type;
		if (Base::level_stack_.GetSize() != 0) { // this value is not at root
			typename Base::Level* level = Base::level_stack_.template Top<typename Base::Level>();

			if (level->inArray) {
				if (level->valueCount > 0) {
					Base::stream_.Put(','); // add comma if it is not the first element in array
					Base::stream_.Put('\n');
				}
				else
					Base::stream_.Put('\n');
				WriteIndent();
			}
			else {	// in object
				if (level->valueCount > 0) {
					if (level->valueCount % 2 == 0) {
						Base::stream_.Put(',');
						Base::stream_.Put('\n');
					}
					else {
						Base::stream_.Put(':');
						Base::stream_.Put(' ');
					}
				}
				else
					Base::stream_.Put('\n');

				if (level->valueCount % 2 == 0)
					WriteIndent();
			}
			if (!level->inArray && level->valueCount % 2 == 0)
				RAPIDJSON_ASSERT(type == kStringType);  // if it's in object, then even number should be a name
			level->valueCount++;
		}
		else
			RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType);
	}

	void WriteIndent()  {
		size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_;
		PutN(Base::stream_, indentChar_, count);
	}

	Ch indentChar_;
	unsigned indentCharCount_;
};

} // namespace rapidjson

#endif // RAPIDJSON_RAPIDJSON_H_
