// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "platform/assert.h"

#include "platform/unicode.h"
#include "vm/double_conversion.h"
#include "vm/json_writer.h"
#include "vm/object.h"

namespace dart {

class MaybeOnStackBuffer {
 public:
  explicit MaybeOnStackBuffer(intptr_t size) {
    if (size > kOnStackBufferCapacity) {
      p_ = reinterpret_cast<char*>(malloc(size));
    } else {
      p_ = &buffer_[0];
    }
  }
  ~MaybeOnStackBuffer() {
    if (p_ != &buffer_[0]) free(p_);
  }

  char* p() { return p_; }

 private:
  static constexpr intptr_t kOnStackBufferCapacity = 4096;
  char* p_;
  char buffer_[kOnStackBufferCapacity];
};

JSONWriter::JSONWriter(intptr_t buf_size)
    : open_objects_(0), buffer_(buf_size) {}

void JSONWriter::AppendBytes(const uint8_t* buffer, intptr_t buffer_length) {
  buffer_.AddRaw(buffer, buffer_length);
}

static const char base64_digits[65] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char base64_pad = '=';

void JSONWriter::AppendBytesInBase64(const uint8_t* bytes, intptr_t length) {
  ASSERT(bytes != nullptr);
  intptr_t odd_bits = length % 3;
  intptr_t even_bits = length - odd_bits;
  for (intptr_t i = 0; i < even_bits; i += 3) {
    intptr_t triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
    buffer_.AddChar(base64_digits[triplet >> 18]);
    buffer_.AddChar(base64_digits[(triplet >> 12) & 63]);
    buffer_.AddChar(base64_digits[(triplet >> 6) & 63]);
    buffer_.AddChar(base64_digits[triplet & 63]);
  }
  if (odd_bits == 1) {
    intptr_t triplet = bytes[even_bits] << 16;
    buffer_.AddChar(base64_digits[triplet >> 18]);
    buffer_.AddChar(base64_digits[(triplet >> 12) & 63]);
    buffer_.AddChar(base64_pad);
    buffer_.AddChar(base64_pad);
  } else if (odd_bits == 2) {
    intptr_t triplet = (bytes[even_bits] << 16) | (bytes[even_bits + 1] << 8);
    buffer_.AddChar(base64_digits[triplet >> 18]);
    buffer_.AddChar(base64_digits[(triplet >> 12) & 63]);
    buffer_.AddChar(base64_digits[(triplet >> 6) & 63]);
    buffer_.AddChar(base64_pad);
  }
}

void JSONWriter::AppendSerializedObject(const char* serialized_object) {
  PrintCommaIfNeeded();
  buffer_.AddString(serialized_object);
}

void JSONWriter::AppendSerializedObject(const char* property_name,
                                        const char* serialized_object) {
  PrintCommaIfNeeded();
  PrintPropertyName(property_name);
  buffer_.AddString(serialized_object);
}

void JSONWriter::Clear() {
  buffer_.Clear();
  open_objects_ = 0;
}

void JSONWriter::OpenObject(const char* property_name) {
  PrintCommaIfNeeded();
  open_objects_++;
  if (property_name != nullptr) {
    PrintPropertyName(property_name);
  }
  buffer_.AddChar('{');
}

void JSONWriter::UncloseObject() {
  intptr_t len = buffer_.length();
  ASSERT(len > 0);
  ASSERT(buffer_.buffer()[len - 1] == '}');
  open_objects_++;
  buffer_.set_length(len - 1);
}

void JSONWriter::CloseObject() {
  ASSERT(open_objects_ > 0);
  open_objects_--;
  buffer_.AddChar('}');
}

void JSONWriter::OpenArray(const char* property_name) {
  PrintCommaIfNeeded();
  if (property_name != nullptr) {
    PrintPropertyName(property_name);
  }
  open_objects_++;
  buffer_.AddChar('[');
}

void JSONWriter::CloseArray() {
  ASSERT(open_objects_ > 0);
  open_objects_--;
  buffer_.AddChar(']');
}

void JSONWriter::PrintValueNull() {
  PrintCommaIfNeeded();
  buffer_.Printf("null");
}

void JSONWriter::PrintValueBool(bool b) {
  PrintCommaIfNeeded();
  buffer_.Printf("%s", b ? "true" : "false");
}

void JSONWriter::PrintValue(intptr_t i) {
  EnsureIntegerIsRepresentableInJavaScript(static_cast<int64_t>(i));
  PrintCommaIfNeeded();
  buffer_.Printf("%" Pd "", i);
}

void JSONWriter::PrintValue64(int64_t i) {
  EnsureIntegerIsRepresentableInJavaScript(i);
  PrintCommaIfNeeded();
  buffer_.Printf("%" Pd64 "", i);
}

void JSONWriter::PrintValue(double d) {
  // Max length of a double in characters (including \0).
  // See double_conversion.cc.
  const size_t kBufferLen = 25;
  char buffer[kBufferLen];
  DoubleToCString(d, buffer, kBufferLen);
  PrintCommaIfNeeded();
  buffer_.Printf("%s", buffer);
}

void JSONWriter::PrintValueBase64(const uint8_t* bytes, intptr_t length) {
  PrintCommaIfNeeded();
  buffer_.AddChar('"');
  AppendBytesInBase64(bytes, length);
  buffer_.AddChar('"');
}

void JSONWriter::PrintValue(const char* s) {
  PrintCommaIfNeeded();
  buffer_.AddChar('"');
  AddEscapedUTF8String(s);
  buffer_.AddChar('"');
}

void JSONWriter::PrintValue(const char* s, intptr_t i) {
  PrintCommaIfNeeded();
  buffer_.AddChar('"');
  AddEscapedUTF8String(s, i);
  buffer_.AddChar('"');
}

bool JSONWriter::PrintValueStr(const String& s,
                               intptr_t offset,
                               intptr_t count) {
  PrintCommaIfNeeded();
  buffer_.AddChar('"');
  bool did_truncate = AddDartString(s, offset, count);
  buffer_.AddChar('"');
  return did_truncate;
}

void JSONWriter::PrintValueNoEscape(const char* s) {
  PrintCommaIfNeeded();
  buffer_.Printf("%s", s);
}

void JSONWriter::PrintfValue(const char* format, ...) {
  va_list args;
  va_start(args, format);
  VPrintfValue(format, args);
  va_end(args);
}

void JSONWriter::VPrintfValue(const char* format, va_list args) {
  PrintCommaIfNeeded();

  va_list measure_args;
  va_copy(measure_args, args);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  va_end(measure_args);

  MaybeOnStackBuffer mosb(len + 1);
  char* p = mosb.p();

  va_list print_args;
  va_copy(print_args, args);
  intptr_t len2 = Utils::VSNPrint(p, len + 1, format, print_args);
  va_end(print_args);
  ASSERT(len == len2);

  buffer_.AddChar('"');
  AddEscapedUTF8String(p, len);
  buffer_.AddChar('"');
}

void JSONWriter::PrintPropertyBool(const char* name, bool b) {
  PrintPropertyName(name);
  PrintValueBool(b);
}

void JSONWriter::PrintProperty(const char* name, intptr_t i) {
  PrintPropertyName(name);
  PrintValue(i);
}

void JSONWriter::PrintProperty64(const char* name, int64_t i) {
  PrintPropertyName(name);
  PrintValue64(i);
}

void JSONWriter::PrintProperty(const char* name, double d) {
  PrintPropertyName(name);
  PrintValue(d);
}

void JSONWriter::PrintProperty(const char* name, const char* s) {
  PrintPropertyName(name);
  PrintValue(s);
}

void JSONWriter::PrintPropertyBase64(const char* name,
                                     const uint8_t* b,
                                     intptr_t len) {
  PrintPropertyName(name);
  PrintValueBase64(b, len);
}

bool JSONWriter::PrintPropertyStr(const char* name,
                                  const String& s,
                                  intptr_t offset,
                                  intptr_t count) {
  PrintPropertyName(name);
  return PrintValueStr(s, offset, count);
}

void JSONWriter::PrintPropertyNoEscape(const char* name, const char* s) {
  PrintPropertyName(name);
  PrintValueNoEscape(s);
}

void JSONWriter::PrintfProperty(const char* name, const char* format, ...) {
  va_list args;
  va_start(args, format);
  VPrintfProperty(name, format, args);
  va_end(args);
}

void JSONWriter::VPrintfProperty(const char* name,
                                 const char* format,
                                 va_list args) {
  PrintPropertyName(name);

  va_list measure_args;
  va_copy(measure_args, args);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, measure_args);
  va_end(measure_args);

  MaybeOnStackBuffer mosb(len + 1);
  char* p = mosb.p();

  va_list print_args;
  va_copy(print_args, args);
  intptr_t len2 = Utils::VSNPrint(p, len + 1, format, print_args);
  va_end(print_args);
  ASSERT(len == len2);

  buffer_.AddChar('"');
  AddEscapedUTF8String(p, len);
  buffer_.AddChar('"');
}

void JSONWriter::Steal(char** buffer, intptr_t* buffer_length) {
  ASSERT(buffer != nullptr);
  ASSERT(buffer_length != nullptr);
  *buffer_length = buffer_.length();
  *buffer = buffer_.Steal();
}

void JSONWriter::PrintPropertyName(const char* name) {
  ASSERT(name != nullptr);
  PrintCommaIfNeeded();
  buffer_.AddChar('"');
  AddEscapedUTF8String(name);
  buffer_.AddChar('"');
  buffer_.AddChar(':');
}

void JSONWriter::PrintNewline() {
  buffer_.AddChar('\n');
}

void JSONWriter::PrintCommaIfNeeded() {
  if (NeedComma()) {
    buffer_.AddChar(',');
  }
}

bool JSONWriter::NeedComma() {
  const char* buffer = buffer_.buffer();
  intptr_t length = buffer_.length();
  if (length == 0) {
    return false;
  }
  char ch = buffer[length - 1];
  return (ch != '[') && (ch != '{') && (ch != ':') && (ch != ',');
}

void JSONWriter::EnsureIntegerIsRepresentableInJavaScript(int64_t i) {
#ifdef DEBUG
  if (!Utils::IsJavaScriptInt(i)) {
    OS::PrintErr(
        "JSONWriter::EnsureIntegerIsRepresentableInJavaScript failed on "
        "%" Pd64 "\n",
        i);
    UNREACHABLE();
  }
#endif
}

void JSONWriter::AddEscapedUTF8String(const char* s) {
  if (s == nullptr) {
    return;
  }
  intptr_t len = strlen(s);
  AddEscapedUTF8String(s, len);
}

void JSONWriter::AddEscapedUTF8String(const char* s, intptr_t len) {
  if (s == nullptr) {
    return;
  }
  buffer_.AddEscapedUTF8(s, len);
}

bool JSONWriter::AddDartString(const String& s,
                               intptr_t offset,
                               intptr_t count) {
  intptr_t length = s.Length();
  ASSERT(offset >= 0);
  if (offset > length) {
    offset = length;
  }
  if (!Utils::RangeCheck(offset, count, length)) {
    count = length - offset;
  }

  if (count > 0) {  // Avoid asserts about harmless out-of-bounds index.
    NoSafepointScope no_safepoint;
    if (s.IsOneByteString()) {
      buffer_.AddEscapedLatin1(OneByteString::CharAddr(s, offset), count);
    } else if (s.IsTwoByteString()) {
      buffer_.AddEscapedUTF16(TwoByteString::CharAddr(s, offset), count);
    } else {
      UNREACHABLE();
    }
  }

  // Return value indicates whether the string is truncated.
  intptr_t limit = offset + count;
  return (offset > 0) || (limit < length);
}

}  // namespace dart
