text: // Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors
  // Distributed under MIT license, or public domain if desired and
  // recognized in your jurisdiction.
  // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
  
  #if !defined(JSON_IS_AMALGAMATION)
  #include <json/writer.h>
  #include "json_tool.h"
  #endif // if !defined(JSON_IS_AMALGAMATION)
  #include <iomanip>
  #include <memory>
  #include <sstream>
  #include <utility>
  #include <set>
  #include <cassert>
  #include <cstring>
  #include <cstdio>
  
  #if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0
  #include <float.h>
  #define isfinite _finite
  #elif defined(__sun) && defined(__SVR4) //Solaris
  #if !defined(isfinite)
  #include <ieeefp.h>
  #define isfinite finite
  #endif
  #elif defined(_AIX)
  #if !defined(isfinite)
  #include <math.h>
  #define isfinite finite
  #endif
  #elif defined(__hpux)
  #if !defined(isfinite)
  #if defined(__ia64) && !defined(finite)
  #define isfinite(x) ((sizeof(x) == sizeof(float) ? \
                       _Isfinitef(x) : _IsFinite(x)))
  #else
  #include <math.h>
  #define isfinite finite
  #endif
  #endif
  #else
  #include <cmath>
  #if !(defined(__QNXNTO__)) // QNX already defines isfinite
  #define isfinite std::isfinite
  #endif
  #endif
  
  #if defined(_MSC_VER)
  #if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
  #define snprintf sprintf_s
  #elif _MSC_VER >= 1900 // VC++ 14.0 and above
  #define snprintf std::snprintf
  #else
  #define snprintf _snprintf
  #endif
  #elif defined(__ANDROID__) || defined(__QNXNTO__)
  #define snprintf snprintf
  #elif __cplusplus >= 201103L
  #if !defined(__MINGW32__) && !defined(__CYGWIN__)
  #define snprintf std::snprintf
  #endif
  #endif
  
  #if defined(__BORLANDC__)  
  #include <float.h>
  #define isfinite _finite
  #define snprintf _snprintf
  #endif
  
  #if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
  // Disable warning about strdup being deprecated.
  #pragma warning(disable : 4996)
  #endif
  
  namespace Json {
  
  #if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
  typedef std::unique_ptr<StreamWriter> StreamWriterPtr;
  #else
  typedef std::auto_ptr<StreamWriter>   StreamWriterPtr;
  #endif
  
  JSONCPP_STRING valueToString(LargestInt value) {
    UIntToStringBuffer buffer;
    char* current = buffer + sizeof(buffer);
    if (value == Value::minLargestInt) {
      uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
      *--current = '-';
    } else if (value < 0) {
      uintToString(LargestUInt(-value), current);
      *--current = '-';
    } else {
      uintToString(LargestUInt(value), current);
    }
    assert(current >= buffer);
    return current;
  }
  
  JSONCPP_STRING valueToString(LargestUInt value) {
    UIntToStringBuffer buffer;
    char* current = buffer + sizeof(buffer);
    uintToString(value, current);
    assert(current >= buffer);
    return current;
  }
  
  #if defined(JSON_HAS_INT64)
  
  JSONCPP_STRING valueToString(Int value) {
    return valueToString(LargestInt(value));
  }
  
  JSONCPP_STRING valueToString(UInt value) {
    return valueToString(LargestUInt(value));
  }
  
  #endif // # if defined(JSON_HAS_INT64)
  
  namespace {
  JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision, PrecisionType precisionType) {
    // Allocate a buffer that is more than large enough to store the 16 digits of
    // precision requested below.
    char buffer[36];
    int len = -1;
  
    char formatString[15];
    if (precisionType == PrecisionType::significantDigits) {
      snprintf(formatString, sizeof(formatString), "%%.%ug", precision);
    } else {
      snprintf(formatString, sizeof(formatString), "%%.%uf", precision);
    }
  
    // Print into the buffer. We need not request the alternative representation
    // that always has a decimal point because JSON doesn't distinguish the
    // concepts of reals and integers.
    if (isfinite(value)) {
      len = snprintf(buffer, sizeof(buffer), formatString, value);
      fixNumericLocale(buffer, buffer + len);
      // to delete use-less too much zeros in the end of string
      if (precisionType == PrecisionType::decimalPlaces) {
        fixZerosInTheEnd(buffer, buffer + len);
      }
  
      // try to ensure we preserve the fact that this was given to us as a double on input
      if (!strchr(buffer, '.') && !strchr(buffer, 'e')) {
        strcat(buffer, ".0");
      }
  
    } else {
      // IEEE standard states that NaN values will not compare to themselves
      if (value != value) {
        len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null");
      } else if (value < 0) {
        len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999");
      } else {
        len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999");
      }
    }
    assert(len >= 0);
    return buffer;
  }
  }
  
  JSONCPP_STRING valueToString(double value, unsigned int precision, PrecisionType precisionType) {
    return valueToString(value, false, precision, precisionType);
  }
  
  JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; }
  
  static bool isAnyCharRequiredQuoting(char const* s, size_t n) {
    assert(s || !n);
  
    char const* const end = s + n;
    for (char const* cur = s; cur < end; ++cur) {
      if (*cur == '\\' || *cur == '\"' || *cur < ' '
        || static_cast<unsigned char>(*cur) < 0x80)
        return true;
    }
    return false;
  }
  
  static unsigned int utf8ToCodepoint(const char*& s, const char* e) {
    const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;
  
    unsigned int firstByte = static_cast<unsigned char>(*s);
  
    if (firstByte < 0x80)
      return firstByte;
  
    if (firstByte < 0xE0) {
      if (e - s < 2)
        return REPLACEMENT_CHARACTER;
  
      unsigned int calculated = ((firstByte & 0x1F) << 6)
        | (static_cast<unsigned int>(s[1]) & 0x3F);
      s += 1;
      // oversized encoded characters are invalid
      return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
    }
  
    if (firstByte < 0xF0) {
      if (e - s < 3)
        return REPLACEMENT_CHARACTER;
  
      unsigned int calculated = ((firstByte & 0x0F) << 12)
        | ((static_cast<unsigned int>(s[1]) & 0x3F) << 6)
        |  (static_cast<unsigned int>(s[2]) & 0x3F);
      s += 2;
      // surrogates aren't valid codepoints itself
      // shouldn't be UTF-8 encoded
      if (calculated >= 0xD800 && calculated <= 0xDFFF)
        return REPLACEMENT_CHARACTER;
      // oversized encoded characters are invalid
      return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
    }
  
    if (firstByte < 0xF8) {
      if (e - s < 4)
        return REPLACEMENT_CHARACTER;
  
      unsigned int calculated = ((firstByte & 0x07) << 18)
        | ((static_cast<unsigned int>(s[1]) & 0x3F) << 12)
        | ((static_cast<unsigned int>(s[2]) & 0x3F) << 6)
        |  (static_cast<unsigned int>(s[3]) & 0x3F);
      s += 3;
      // oversized encoded characters are invalid
      return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
    }
  
    return REPLACEMENT_CHARACTER;
  }
  
  static const char hex2[] =
    "000102030405060708090a0b0c0d0e0f"
    "101112131415161718191a1b1c1d1e1f"
    "202122232425262728292a2b2c2d2e2f"
    "303132333435363738393a3b3c3d3e3f"
    "404142434445464748494a4b4c4d4e4f"
    "505152535455565758595a5b5c5d5e5f"
    "606162636465666768696a6b6c6d6e6f"
    "707172737475767778797a7b7c7d7e7f"
    "808182838485868788898a8b8c8d8e8f"
    "909192939495969798999a9b9c9d9e9f"
    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
  
  static JSONCPP_STRING toHex16Bit(unsigned int x) {
    const unsigned int hi = (x >> 8) & 0xff;
    const unsigned int lo = x & 0xff;
    JSONCPP_STRING result(4, ' ');
    result[0] = hex2[2 * hi];
    result[1] = hex2[2 * hi + 1];
    result[2] = hex2[2 * lo];
    result[3] = hex2[2 * lo + 1];
    return result;
  }
  
  static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) {
    if (value == NULL)
      return "";
  
    if (!isAnyCharRequiredQuoting(value, length))
      return JSONCPP_STRING("\"") + value + "\"";
    // We have to walk value and escape any special characters.
    // Appending to JSONCPP_STRING is not efficient, but this should be rare.
    // (Note: forward slashes are *not* rare, but I am not escaping them.)
    JSONCPP_STRING::size_type maxsize =
        length * 2 + 3; // allescaped+quotes+NULL
    JSONCPP_STRING result;
    result.reserve(maxsize); // to avoid lots of mallocs
    result += "\"";
    char const* end = value + length;
    for (const char* c = value; c != end; ++c) {
      switch (*c) {
      case '\"':
        result += "\\\"";
        break;
      case '\\':
        result += "\\\\";
        break;
      case '\b':
        result += "\\b";
        break;
      case '\f':
        result += "\\f";
        break;
      case '\n':
        result += "\\n";
        break;
      case '\r':
        result += "\\r";
        break;
      case '\t':
        result += "\\t";
        break;
      // case '/':
      // Even though \/ is considered a legal escape in JSON, a bare
      // slash is also legal, so I see no reason to escape it.
      // (I hope I am not misunderstanding something.)
      // blep notes: actually escaping \/ may be useful in javascript to avoid </
      // sequence.
      // Should add a flag to allow this compatibility mode and prevent this
      // sequence from occurring.
      default: {
          unsigned int cp = utf8ToCodepoint(c, end);
          // don't escape non-control characters
          // (short escape sequence are applied above)
          if (cp < 0x80 && cp >= 0x20)
            result += static_cast<char>(cp);
          else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane
            result += "\\u";
            result += toHex16Bit(cp);
          }
          else { // codepoint is not in Basic Multilingual Plane
                 // convert to surrogate pair first
            cp -= 0x10000;
            result += "\\u";
            result += toHex16Bit((cp >> 10) + 0xD800);
            result += "\\u";
            result += toHex16Bit((cp & 0x3FF) + 0xDC00);
          }
        }
        break;
      }
    }
    result += "\"";
    return result;
  }
  
  JSONCPP_STRING valueToQuotedString(const char* value) {
    return valueToQuotedStringN(value, static_cast<unsigned int>(strlen(value)));
  }
  
  // Class Writer
  // //////////////////////////////////////////////////////////////////
  Writer::~Writer() {}
  
  // Class FastWriter
  // //////////////////////////////////////////////////////////////////
  
  FastWriter::FastWriter()
      : yamlCompatibilityEnabled_(false), dropNullPlaceholders_(false),
        omitEndingLineFeed_(false) {}
  
  void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; }
  
  void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
  
  void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }
  
  JSONCPP_STRING FastWriter::write(const Value& root) {
    document_.clear();
    writeValue(root);
    if (!omitEndingLineFeed_)
      document_ += "\n";
    return document_;
  }
  
  void FastWriter::writeValue(const Value& value) {
    switch (value.type()) {
    case nullValue:
      if (!dropNullPlaceholders_)
        document_ += "null";
      break;
    case intValue:
      document_ += valueToString(value.asLargestInt());
      break;
    case uintValue:
      document_ += valueToString(value.asLargestUInt());
      break;
    case realValue:
      document_ += valueToString(value.asDouble());
      break;
    case stringValue:
    {
      // Is NULL possible for value.string_? No.
      char const* str;
      char const* end;
      bool ok = value.getString(&str, &end);
      if (ok) document_ += valueToQuotedStringN(str, static_cast<unsigned>(end-str));
      break;
    }
    case booleanValue:
      document_ += valueToString(value.asBool());
      break;
    case arrayValue: {
      document_ += '[';
      ArrayIndex size = value.size();
      for (ArrayIndex index = 0; index < size; ++index) {
        if (index > 0)
          document_ += ',';
        writeValue(value[index]);
      }
      document_ += ']';
    } break;
    case objectValue: {
      Value::Members members(value.getMemberNames());
      document_ += '{';
      for (Value::Members::iterator it = members.begin(); it != members.end();
           ++it) {
        const JSONCPP_STRING& name = *it;
        if (it != members.begin())
          document_ += ',';
        document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
        document_ += yamlCompatibilityEnabled_ ? ": " : ":";
        writeValue(value[name]);
      }
      document_ += '}';
    } break;
    }
  }
  
  // Class StyledWriter
  // //////////////////////////////////////////////////////////////////
  
  StyledWriter::StyledWriter()
      : rightMargin_(74), indentSize_(3), addChildValues_() {}
  
  JSONCPP_STRING StyledWriter::write(const Value& root) {
    document_.clear();
    addChildValues_ = false;
    indentString_.clear();
    writeCommentBeforeValue(root);
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    document_ += "\n";
    return document_;
  }
  
  void StyledWriter::writeValue(const Value& value) {
    switch (value.type()) {
    case nullValue:
      pushValue("null");
      break;
    case intValue:
      pushValue(valueToString(value.asLargestInt()));
      break;
    case uintValue:
      pushValue(valueToString(value.asLargestUInt()));
      break;
    case realValue:
      pushValue(valueToString(value.asDouble()));
      break;
    case stringValue:
    {
      // Is NULL possible for value.string_? No.
      char const* str;
      char const* end;
      bool ok = value.getString(&str, &end);
      if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
      else pushValue("");
      break;
    }
    case booleanValue:
      pushValue(valueToString(value.asBool()));
      break;
    case arrayValue:
      writeArrayValue(value);
      break;
    case objectValue: {
      Value::Members members(value.getMemberNames());
      if (members.empty())
        pushValue("{}");
      else {
        writeWithIndent("{");
        indent();
        Value::Members::iterator it = members.begin();
        for (;;) {
          const JSONCPP_STRING& name = *it;
          const Value& childValue = value[name];
          writeCommentBeforeValue(childValue);
          writeWithIndent(valueToQuotedString(name.c_str()));
          document_ += " : ";
          writeValue(childValue);
          if (++it == members.end()) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          document_ += ',';
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("}");
      }
    } break;
    }
  }
  
  void StyledWriter::writeArrayValue(const Value& value) {
    unsigned size = value.size();
    if (size == 0)
      pushValue("[]");
    else {
      bool isArrayMultiLine = isMultilineArray(value);
      if (isArrayMultiLine) {
        writeWithIndent("[");
        indent();
        bool hasChildValue = !childValues_.empty();
        unsigned index = 0;
        for (;;) {
          const Value& childValue = value[index];
          writeCommentBeforeValue(childValue);
          if (hasChildValue)
            writeWithIndent(childValues_[index]);
          else {
            writeIndent();
            writeValue(childValue);
          }
          if (++index == size) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          document_ += ',';
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("]");
      } else // output on a single line
      {
        assert(childValues_.size() == size);
        document_ += "[ ";
        for (unsigned index = 0; index < size; ++index) {
          if (index > 0)
            document_ += ", ";
          document_ += childValues_[index];
        }
        document_ += " ]";
      }
    }
  }
  
  bool StyledWriter::isMultilineArray(const Value& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
      const Value& childValue = value[index];
      isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
                          childValue.size() > 0);
    }
    if (!isMultiLine) // check if line length > max line length
    {
      childValues_.reserve(size);
      addChildValues_ = true;
      ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
      for (ArrayIndex index = 0; index < size; ++index) {
        if (hasCommentForValue(value[index])) {
          isMultiLine = true;
        }
        writeValue(value[index]);
        lineLength += static_cast<ArrayIndex>(childValues_[index].length());
      }
      addChildValues_ = false;
      isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
  }
  
  void StyledWriter::pushValue(const JSONCPP_STRING& value) {
    if (addChildValues_)
      childValues_.push_back(value);
    else
      document_ += value;
  }
  
  void StyledWriter::writeIndent() {
    if (!document_.empty()) {
      char last = document_[document_.length() - 1];
      if (last == ' ') // already indented
        return;
      if (last != '\n') // Comments may add new-line
        document_ += '\n';
    }
    document_ += indentString_;
  }
  
  void StyledWriter::writeWithIndent(const JSONCPP_STRING& value) {
    writeIndent();
    document_ += value;
  }
  
  void StyledWriter::indent() { indentString_ += JSONCPP_STRING(indentSize_, ' '); }
  
  void StyledWriter::unindent() {
    assert(indentString_.size() >= indentSize_);
    indentString_.resize(indentString_.size() - indentSize_);
  }
  
  void StyledWriter::writeCommentBeforeValue(const Value& root) {
    if (!root.hasComment(commentBefore))
      return;
  
    document_ += "\n";
    writeIndent();
    const JSONCPP_STRING& comment = root.getComment(commentBefore);
    JSONCPP_STRING::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
      document_ += *iter;
      if (*iter == '\n' &&
         ((iter+1) != comment.end() && *(iter + 1) == '/'))
        writeIndent();
      ++iter;
    }
  
    // Comments are stripped of trailing newlines, so add one here
    document_ += "\n";
  }
  
  void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
    if (root.hasComment(commentAfterOnSameLine))
      document_ += " " + root.getComment(commentAfterOnSameLine);
  
    if (root.hasComment(commentAfter)) {
      document_ += "\n";
      document_ += root.getComment(commentAfter);
      document_ += "\n";
    }
  }
  
  bool StyledWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) ||
           value.hasComment(commentAfterOnSameLine) ||
           value.hasComment(commentAfter);
  }
  
  // Class StyledStreamWriter
  // //////////////////////////////////////////////////////////////////
  
  StyledStreamWriter::StyledStreamWriter(JSONCPP_STRING indentation)
      : document_(NULL), rightMargin_(74), indentation_(indentation),
        addChildValues_(), indented_(false)
  {}
  
  void StyledStreamWriter::write(JSONCPP_OSTREAM& out, const Value& root) {
    document_ = &out;
    addChildValues_ = false;
    indentString_.clear();
    indented_ = true;
    writeCommentBeforeValue(root);
    if (!indented_) writeIndent();
    indented_ = true;
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    *document_ << "\n";
    document_ = NULL; // Forget the stream, for safety.
  }
  
  void StyledStreamWriter::writeValue(const Value& value) {
    switch (value.type()) {
    case nullValue:
      pushValue("null");
      break;
    case intValue:
      pushValue(valueToString(value.asLargestInt()));
      break;
    case uintValue:
      pushValue(valueToString(value.asLargestUInt()));
      break;
    case realValue:
      pushValue(valueToString(value.asDouble()));
      break;
    case stringValue:
    {
      // Is NULL possible for value.string_? No.
      char const* str;
      char const* end;
      bool ok = value.getString(&str, &end);
      if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
      else pushValue("");
      break;
    }
    case booleanValue:
      pushValue(valueToString(value.asBool()));
      break;
    case arrayValue:
      writeArrayValue(value);
      break;
    case objectValue: {
      Value::Members members(value.getMemberNames());
      if (members.empty())
        pushValue("{}");
      else {
        writeWithIndent("{");
        indent();
        Value::Members::iterator it = members.begin();
        for (;;) {
          const JSONCPP_STRING& name = *it;
          const Value& childValue = value[name];
          writeCommentBeforeValue(childValue);
          writeWithIndent(valueToQuotedString(name.c_str()));
          *document_ << " : ";
          writeValue(childValue);
          if (++it == members.end()) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          *document_ << ",";
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("}");
      }
    } break;
    }
  }
  
  void StyledStreamWriter::writeArrayValue(const Value& value) {
    unsigned size = value.size();
    if (size == 0)
      pushValue("[]");
    else {
      bool isArrayMultiLine = isMultilineArray(value);
      if (isArrayMultiLine) {
        writeWithIndent("[");
        indent();
        bool hasChildValue = !childValues_.empty();
        unsigned index = 0;
        for (;;) {
          const Value& childValue = value[index];
          writeCommentBeforeValue(childValue);
          if (hasChildValue)
            writeWithIndent(childValues_[index]);
          else {
            if (!indented_) writeIndent();
            indented_ = true;
            writeValue(childValue);
            indented_ = false;
          }
          if (++index == size) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          *document_ << ",";
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("]");
      } else // output on a single line
      {
        assert(childValues_.size() == size);
        *document_ << "[ ";
        for (unsigned index = 0; index < size; ++index) {
          if (index > 0)
            *document_ << ", ";
          *document_ << childValues_[index];
        }
        *document_ << " ]";
      }
    }
  }
  
  bool StyledStreamWriter::isMultilineArray(const Value& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
      const Value& childValue = value[index];
      isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
                          childValue.size() > 0);
    }
    if (!isMultiLine) // check if line length > max line length
    {
      childValues_.reserve(size);
      addChildValues_ = true;
      ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
      for (ArrayIndex index = 0; index < size; ++index) {
        if (hasCommentForValue(value[index])) {
          isMultiLine = true;
        }
        writeValue(value[index]);
        lineLength += static_cast<ArrayIndex>(childValues_[index].length());
      }
      addChildValues_ = false;
      isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
  }
  
  void StyledStreamWriter::pushValue(const JSONCPP_STRING& value) {
    if (addChildValues_)
      childValues_.push_back(value);
    else
      *document_ << value;
  }
  
  void StyledStreamWriter::writeIndent() {
    // blep intended this to look at the so-far-written string
    // to determine whether we are already indented, but
    // with a stream we cannot do that. So we rely on some saved state.
    // The caller checks indented_.
    *document_ << '\n' << indentString_;
  }
  
  void StyledStreamWriter::writeWithIndent(const JSONCPP_STRING& value) {
    if (!indented_) writeIndent();
    *document_ << value;
    indented_ = false;
  }
  
  void StyledStreamWriter::indent() { indentString_ += indentation_; }
  
  void StyledStreamWriter::unindent() {
    assert(indentString_.size() >= indentation_.size());
    indentString_.resize(indentString_.size() - indentation_.size());
  }
  
  void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
    if (!root.hasComment(commentBefore))
      return;
  
    if (!indented_) writeIndent();
    const JSONCPP_STRING& comment = root.getComment(commentBefore);
    JSONCPP_STRING::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
      *document_ << *iter;
      if (*iter == '\n' &&
         ((iter+1) != comment.end() && *(iter + 1) == '/'))
        // writeIndent();  // would include newline
        *document_ << indentString_;
      ++iter;
    }
    indented_ = false;
  }
  
  void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
    if (root.hasComment(commentAfterOnSameLine))
      *document_ << ' ' << root.getComment(commentAfterOnSameLine);
  
    if (root.hasComment(commentAfter)) {
      writeIndent();
      *document_ << root.getComment(commentAfter);
    }
    indented_ = false;
  }
  
  bool StyledStreamWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) ||
           value.hasComment(commentAfterOnSameLine) ||
           value.hasComment(commentAfter);
  }
  
  //////////////////////////
  // BuiltStyledStreamWriter
  
  /// Scoped enums are not available until C++11.
  struct CommentStyle {
    /// Decide whether to write comments.
    enum Enum {
      None,  ///< Drop all comments.
      Most,  ///< Recover odd behavior of previous versions (not implemented yet).
      All  ///< Keep all comments.
    };
  };
  
  struct BuiltStyledStreamWriter : public StreamWriter
  {
    BuiltStyledStreamWriter(
        JSONCPP_STRING const& indentation,
        CommentStyle::Enum cs,
        JSONCPP_STRING const& colonSymbol,
        JSONCPP_STRING const& nullSymbol,
        JSONCPP_STRING const& endingLineFeedSymbol,
        bool useSpecialFloats,
        unsigned int precision,
        PrecisionType precisionType);
    int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE;
  private:
    void writeValue(Value const& value);
    void writeArrayValue(Value const& value);
    bool isMultilineArray(Value const& value);
    void pushValue(JSONCPP_STRING const& value);
    void writeIndent();
    void writeWithIndent(JSONCPP_STRING const& value);
    void indent();
    void unindent();
    void writeCommentBeforeValue(Value const& root);
    void writeCommentAfterValueOnSameLine(Value const& root);
    static bool hasCommentForValue(const Value& value);
  
    typedef std::vector<JSONCPP_STRING> ChildValues;
  
    ChildValues childValues_;
    JSONCPP_STRING indentString_;
    unsigned int rightMargin_;
    JSONCPP_STRING indentation_;
    CommentStyle::Enum cs_;
    JSONCPP_STRING colonSymbol_;
    JSONCPP_STRING nullSymbol_;
    JSONCPP_STRING endingLineFeedSymbol_;
    bool addChildValues_ : 1;
    bool indented_ : 1;
    bool useSpecialFloats_ : 1;
    unsigned int precision_;
    PrecisionType precisionType_;
  };
  BuiltStyledStreamWriter::BuiltStyledStreamWriter(
        JSONCPP_STRING const& indentation,
        CommentStyle::Enum cs,
        JSONCPP_STRING const& colonSymbol,
        JSONCPP_STRING const& nullSymbol,
        JSONCPP_STRING const& endingLineFeedSymbol,
        bool useSpecialFloats,
        unsigned int precision,
        PrecisionType precisionType)
    : rightMargin_(74)
    , indentation_(indentation)
    , cs_(cs)
    , colonSymbol_(colonSymbol)
    , nullSymbol_(nullSymbol)
    , endingLineFeedSymbol_(endingLineFeedSymbol)
    , addChildValues_(false)
    , indented_(false)
    , useSpecialFloats_(useSpecialFloats)
    , precision_(precision)
    , precisionType_(precisionType)
  {
  }
  int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout)
  {
    sout_ = sout;
    addChildValues_ = false;
    indented_ = true;
    indentString_.clear();
    writeCommentBeforeValue(root);
    if (!indented_) writeIndent();
    indented_ = true;
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    *sout_ << endingLineFeedSymbol_;
    sout_ = NULL;
    return 0;
  }
  void BuiltStyledStreamWriter::writeValue(Value const& value) {
    switch (value.type()) {
    case nullValue:
      pushValue(nullSymbol_);
      break;
    case intValue:
      pushValue(valueToString(value.asLargestInt()));
      break;
    case uintValue:
      pushValue(valueToString(value.asLargestUInt()));
      break;
    case realValue:
      pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, precisionType_));
      break;
    case stringValue:
    {
      // Is NULL is possible for value.string_? No.
      char const* str;
      char const* end;
      bool ok = value.getString(&str, &end);
      if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
      else pushValue("");
      break;
    }
    case booleanValue:
      pushValue(valueToString(value.asBool()));
      break;
    case arrayValue:
      writeArrayValue(value);
      break;
    case objectValue: {
      Value::Members members(value.getMemberNames());
      if (members.empty())
        pushValue("{}");
      else {
        writeWithIndent("{");
        indent();
        Value::Members::iterator it = members.begin();
        for (;;) {
          JSONCPP_STRING const& name = *it;
          Value const& childValue = value[name];
          writeCommentBeforeValue(childValue);
          writeWithIndent(valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length())));
          *sout_ << colonSymbol_;
          writeValue(childValue);
          if (++it == members.end()) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          *sout_ << ",";
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("}");
      }
    } break;
    }
  }
  
  void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
    unsigned size = value.size();
    if (size == 0)
      pushValue("[]");
    else {
      bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);
      if (isMultiLine) {
        writeWithIndent("[");
        indent();
        bool hasChildValue = !childValues_.empty();
        unsigned index = 0;
        for (;;) {
          Value const& childValue = value[index];
          writeCommentBeforeValue(childValue);
          if (hasChildValue)
            writeWithIndent(childValues_[index]);
          else {
            if (!indented_) writeIndent();
            indented_ = true;
            writeValue(childValue);
            indented_ = false;
          }
          if (++index == size) {
            writeCommentAfterValueOnSameLine(childValue);
            break;
          }
          *sout_ << ",";
          writeCommentAfterValueOnSameLine(childValue);
        }
        unindent();
        writeWithIndent("]");
      } else // output on a single line
      {
        assert(childValues_.size() == size);
        *sout_ << "[";
        if (!indentation_.empty()) *sout_ << " ";
        for (unsigned index = 0; index < size; ++index) {
          if (index > 0)
            *sout_ << ((!indentation_.empty()) ? ", " : ",");
          *sout_ << childValues_[index];
        }
        if (!indentation_.empty()) *sout_ << " ";
        *sout_ << "]";
      }
    }
  }
  
  bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
      Value const& childValue = value[index];
      isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
                          childValue.size() > 0);
    }
    if (!isMultiLine) // check if line length > max line length
    {
      childValues_.reserve(size);
      addChildValues_ = true;
      ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
      for (ArrayIndex index = 0; index < size; ++index) {
        if (hasCommentForValue(value[index])) {
          isMultiLine = true;
        }
        writeValue(value[index]);
        lineLength += static_cast<ArrayIndex>(childValues_[index].length());
      }
      addChildValues_ = false;
      isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
  }
  
  void BuiltStyledStreamWriter::pushValue(JSONCPP_STRING const& value) {
    if (addChildValues_)
      childValues_.push_back(value);
    else
      *sout_ << value;
  }
  
  void BuiltStyledStreamWriter::writeIndent() {
    // blep intended this to look at the so-far-written string
    // to determine whether we are already indented, but
    // with a stream we cannot do that. So we rely on some saved state.
    // The caller checks indented_.
  
    if (!indentation_.empty()) {
      // In this case, drop newlines too.
      *sout_ << '\n' << indentString_;
    }
  }
  
  void BuiltStyledStreamWriter::writeWithIndent(JSONCPP_STRING const& value) {
    if (!indented_) writeIndent();
    *sout_ << value;
    indented_ = false;
  }
  
  void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
  
  void BuiltStyledStreamWriter::unindent() {
    assert(indentString_.size() >= indentation_.size());
    indentString_.resize(indentString_.size() - indentation_.size());
  }
  
  void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
    if (cs_ == CommentStyle::None) return;
    if (!root.hasComment(commentBefore))
      return;
  
    if (!indented_) writeIndent();
    const JSONCPP_STRING& comment = root.getComment(commentBefore);
    JSONCPP_STRING::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
      *sout_ << *iter;
      if (*iter == '\n' &&
         ((iter+1) != comment.end() && *(iter + 1) == '/'))
        // writeIndent();  // would write extra newline
        *sout_ << indentString_;
      ++iter;
    }
    indented_ = false;
  }
  
  void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
    if (cs_ == CommentStyle::None) return;
    if (root.hasComment(commentAfterOnSameLine))
      *sout_ << " " + root.getComment(commentAfterOnSameLine);
  
    if (root.hasComment(commentAfter)) {
      writeIndent();
      *sout_ << root.getComment(commentAfter);
    }
  }
  
  // static
  bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) ||
           value.hasComment(commentAfterOnSameLine) ||
           value.hasComment(commentAfter);
  }
  
  ///////////////
  // StreamWriter
  
  StreamWriter::StreamWriter()
      : sout_(NULL)
  {
  }
  StreamWriter::~StreamWriter()
  {
  }
  StreamWriter::Factory::~Factory()
  {}
  StreamWriterBuilder::StreamWriterBuilder()
  {
    setDefaults(&settings_);
  }
  StreamWriterBuilder::~StreamWriterBuilder()
  {}
  StreamWriter* StreamWriterBuilder::newStreamWriter() const
  {
    JSONCPP_STRING indentation = settings_["indentation"].asString();
    JSONCPP_STRING cs_str = settings_["commentStyle"].asString();
    JSONCPP_STRING pt_str = settings_["precisionType"].asString();
    bool eyc = settings_["enableYAMLCompatibility"].asBool();
    bool dnp = settings_["dropNullPlaceholders"].asBool();
    bool usf = settings_["useSpecialFloats"].asBool(); 
    unsigned int pre = settings_["precision"].asUInt();
    CommentStyle::Enum cs = CommentStyle::All;
    if (cs_str == "All") {
      cs = CommentStyle::All;
    } else if (cs_str == "None") {
      cs = CommentStyle::None;
    } else {
      throwRuntimeError("commentStyle must be 'All' or 'None'");
    }
    PrecisionType precisionType(significantDigits);
    if (pt_str == "significant") {
      precisionType = PrecisionType::significantDigits;
    } else if (pt_str == "decimal") {
      precisionType = PrecisionType::decimalPlaces;
    } else {
      throwRuntimeError("precisionType must be 'significant' or 'decimal'");
    }
    JSONCPP_STRING colonSymbol = " : ";
    if (eyc) {
      colonSymbol = ": ";
    } else if (indentation.empty()) {
      colonSymbol = ":";
    }
    JSONCPP_STRING nullSymbol = "null";
    if (dnp) {
      nullSymbol.clear();
    }
    if (pre > 17) pre = 17;
    JSONCPP_STRING endingLineFeedSymbol;
    return new BuiltStyledStreamWriter(
        indentation, cs,
        colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre, precisionType);
  }
  static void getValidWriterKeys(std::set<JSONCPP_STRING>* valid_keys)
  {
    valid_keys->clear();
    valid_keys->insert("indentation");
    valid_keys->insert("commentStyle");
    valid_keys->insert("enableYAMLCompatibility");
    valid_keys->insert("dropNullPlaceholders");
    valid_keys->insert("useSpecialFloats");
    valid_keys->insert("precision");
    valid_keys->insert("precisionType");
  }
  bool StreamWriterBuilder::validate(Json::Value* invalid) const
  {
    Json::Value my_invalid;
    if (!invalid) invalid = &my_invalid;  // so we do not need to test for NULL
    Json::Value& inv = *invalid;
    std::set<JSONCPP_STRING> valid_keys;
    getValidWriterKeys(&valid_keys);
    Value::Members keys = settings_.getMemberNames();
    size_t n = keys.size();
    for (size_t i = 0; i < n; ++i) {
      JSONCPP_STRING const& key = keys[i];
      if (valid_keys.find(key) == valid_keys.end()) {
        inv[key] = settings_[key];
      }
    }
    return 0u == inv.size();
  }
  Value& StreamWriterBuilder::operator[](JSONCPP_STRING key)
  {
    return settings_[key];
  }
  // static
  void StreamWriterBuilder::setDefaults(Json::Value* settings)
  {
    //! [StreamWriterBuilderDefaults]
    (*settings)["commentStyle"] = "All";
    (*settings)["indentation"] = "\t";
    (*settings)["enableYAMLCompatibility"] = false;
    (*settings)["dropNullPlaceholders"] = false;
    (*settings)["useSpecialFloats"] = false;
    (*settings)["precision"] = 17;
    (*settings)["precisionType"] = "significant";
    //! [StreamWriterBuilderDefaults]
  }
  
  JSONCPP_STRING writeString(StreamWriter::Factory const& builder, Value const& root) {
    JSONCPP_OSTRINGSTREAM sout;
    StreamWriterPtr const writer(builder.newStreamWriter());
    writer->write(root, &sout);
    return sout.str();
  }
  
  JSONCPP_OSTREAM& operator<<(JSONCPP_OSTREAM& sout, Value const& root) {
    StreamWriterBuilder builder;
    StreamWriterPtr const writer(builder.newStreamWriter());
    writer->write(root, &sout);
    return sout;
  }
  
  } // namespace Json
  