#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, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : 
  Base(stream, 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(kNullType);   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_
