// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#ifndef GOOGLE_PROTOBUF_JSON_INTERNAL_WRITER_H__
#define GOOGLE_PROTOBUF_JSON_INTERNAL_WRITER_H__

#include <cstddef>
#include <cstdint>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>

#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/io/strtod.h"
#include "google/protobuf/io/zero_copy_sink.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/stubs/status_macros.h"

// Must be included last.
#include "google/protobuf/port_def.inc"

namespace google {
namespace protobuf {
namespace json_internal {
struct WriterOptions {
  // Whether to add spaces, line breaks and indentation to make the JSON output
  // easy to read.
  bool add_whitespace = false;
  // Whether to always print fields which do not support presence if they would
  // otherwise be omitted, namely:
  // - Implicit presence fields set to their 0 value
  // - Empty lists and maps
  bool always_print_fields_with_no_presence = false;
  // Whether to always print enums as ints. By default they are rendered as
  // strings.
  bool always_print_enums_as_ints = false;
  // Whether to preserve proto field names
  bool preserve_proto_field_names = false;
  // If set, int64 values that can be represented exactly as a double are
  // printed without quotes.
  bool unquote_int64_if_possible = false;
  // The original parser used by json_util2 accepted a number of non-standard
  // options. Setting this flag enables them.
  //
  // What those extensions were is explicitly not documented, beyond what exists
  // in the unit tests; we intend to remove this setting eventually. See
  // b/234868512.
  bool allow_legacy_nonconformant_behavior = false;
};

template <typename Tuple, typename F, size_t... i>
void EachInner(const Tuple& value, F f, std::index_sequence<i...>) {
  int ignored[] = {
      (f(std::get<i>(value)), 0)...};  // NOLINT(readability/braces)
  (void)ignored;
}

// Executes f on each element of value.
template <typename Tuple, typename F>
void Each(const Tuple& value, F f) {
  EachInner(value, f,
            std::make_index_sequence<std::tuple_size<Tuple>::value>());
}

// See JsonWriter::Write().
template <typename... T>
struct Quoted {
  std::tuple<T...> value;
};

// Because this is not C++17 yet, we cannot add a deduction guide.
template <typename... T>
static Quoted<T...> MakeQuoted(T... t) {
  return Quoted<T...>{std::make_tuple(t...)};
}

class JsonWriter {
 public:
  JsonWriter(io::ZeroCopyOutputStream* out, WriterOptions options)
      : sink_(out), options_(options) {}

  const WriterOptions& options() const { return options_; }

  void Push() { ++indent_; }
  void Pop() { --indent_; }

  // The many overloads of Write() will write a value to the underlying stream.
  // Some values may want to be quoted; the Quoted<> type will automatically add
  // quotes and escape sequences.
  //
  // Note that Write() is not implemented for 64-bit integers, since they
  // cannot be crisply represented without quotes; use MakeQuoted for that.

  void Write(absl::string_view str) { sink_.Append(str.data(), str.size()); }

  void Write(char c) { sink_.Append(&c, 1); }

  // The precision on this and the following function are completely made-up,
  // in an attempt to match the behavior of the ESF parser.
  void Write(double val) {
    if (!MaybeWriteSpecialFp(val)) {
      Write(io::SimpleDtoa(val));
    }
  }

  void Write(float val) {
    if (!MaybeWriteSpecialFp(val)) {
      Write(io::SimpleFtoa(val));
    }
  }

  void Write(int32_t val) {
    char buf[22];
    int len = absl::SNPrintF(buf, sizeof(buf), "%d", val);
    absl::string_view view(buf, static_cast<size_t>(len));
    Write(view);
  }

  void Write(uint32_t val) {
    char buf[22];
    int len = absl::SNPrintF(buf, sizeof(buf), "%d", val);
    absl::string_view view(buf, static_cast<size_t>(len));
    Write(view);
  }

  void Write(int64_t val) {
    char buf[22];
    int len = absl::SNPrintF(buf, sizeof(buf), "%d", val);
    absl::string_view view(buf, static_cast<size_t>(len));
    Write(view);
  }

  void Write(uint64_t val) {
    char buf[22];
    int len = absl::SNPrintF(buf, sizeof(buf), "%d", val);
    absl::string_view view(buf, static_cast<size_t>(len));
    Write(view);
  }

  template <typename... Ts>
  void Write(Quoted<Ts...> val) {
    Write('"');
    Each(val.value, [this](auto x) { this->WriteQuoted(x); });
    Write('"');
  }

  template <typename... Ts>
  auto Write(Ts... args) ->
      // This bit of SFINAE avoids this function being called with one argument,
      // so the other overloads of Write() can be picked up instead.
      typename std::enable_if<sizeof...(Ts) != 1, void>::type {
    Each(std::make_tuple(args...), [this](auto x) { this->Write(x); });
  }

  void Whitespace(absl::string_view ws) {
    if (options_.add_whitespace) {
      Write(ws);
    }
  }

  void NewLine() {
    Whitespace("\n");
    for (int i = 0; i < indent_; ++i) {
      Whitespace(" ");
    }
  }

  void WriteComma(bool& is_first) {
    if (is_first) {
      is_first = false;
      return;
    }
    Write(",");
  }

  void WriteBase64(absl::string_view str);

  // Returns a buffer that can be re-used throughout a writing session as
  // variable-length scratch space.
  std::string& ScratchBuf() { return scratch_buf_; }

 private:
  template <typename T>
  void WriteQuoted(T val) {
    Write(val);
  }

  void WriteQuoted(absl::string_view val) { WriteEscapedUtf8(val); }

  // Tries to write a non-finite double if necessary; returns false if
  // nothing was written.
  bool MaybeWriteSpecialFp(double val);

  void WriteEscapedUtf8(absl::string_view str);
  void WriteUEscape(uint16_t val);

  io::zc_sink_internal::ZeroCopyStreamByteSink sink_;
  WriterOptions options_;
  int indent_ = 0;

  std::string scratch_buf_;
};
}  // namespace json_internal
}  // namespace protobuf
}  // namespace google

#include "google/protobuf/port_undef.inc"
#endif  // GOOGLE_PROTOBUF_JSON_INTERNAL_WRITER_H__
