/** @file

  Implementation of JSON formatting functions.

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#include <iomanip>
#include <sstream>

#include "json_utils.h"

namespace
{
/** Write the content of buf to jsonfile between prevIdx (inclusive) and idx (not
 * inclusive).
 *
 * This function is used to help deal with escaped characters with a low number
 * of write calls. This is meant to be used like so: the caller inspects every
 * character in a buffer, doing one of two things with each character:
 *
 *  - The character does not need to be escaped. In this case, no call to
 *  write_buffered_content is made and the idx is advanced and prevIdx is not
 *  advanced. Eventually this character will be written once the contiguous set
 *  of non-escaped characters is collected.
 *
 *  - The character needs to be escaped. In this case, it will call this
 *  function. Anything between prevIdx and up to (but not including) idx is
 *  written. The caller then writes the escaped sequence for the character
 *  pointed to by idx ("\\t" instead of '\t', for instance). Then prevIdx is set
 *  past the escaped character.
 *
 * Finally, once all characters are inspected, a final write_buffered_content
 * call is made with idx set to one past the last character in buf. This
 * results in all characters between prevIdx and the last character in buf
 * (including that character) to be written.
 *
 * @param[in] buf The buffer containing characters that need to be written to jsonfile.
 *
 * @param[in,out] prevIdx The pointer to the beginning of the set of characters
 * in buf that have not been written yet. This is always updated before
 * function exit to one past idx.
 *
 * @param[in] idx The current character in buf being inspected.
 *
 * @param[out] jsonfile The stream to conditionally write buffer content to.
 */
inline void
write_buffered_context(char const *buf, int64_t &prevIdx, int64_t idx, std::ostream &jsonfile)
{
  if (prevIdx < idx) {
    jsonfile.write(buf + prevIdx, idx - prevIdx);
  }
  prevIdx = idx + 1;
}

int
esc_json_out(const char *buf, int64_t len, std::ostream &jsonfile)
{
  if (buf == nullptr) {
    return 0;
  }
  int64_t idx = 0, prevIdx = 0;
  // For an explanation of the algorithm here, see the doxygen comment for
  // write_buffered_content.
  for (idx = 0; idx < len; idx++) {
    char c = *(buf + idx);
    switch (c) {
    case '"':
    case '\\': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\" << c;
      break;
    }
    case '\b': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\b";
      break;
    }
    case '\f': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\f";
      break;
    }
    case '\n': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\n";
      break;
    }
    case '\r': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\r";
      break;
    }
    case '\t': {
      write_buffered_context(buf, prevIdx, idx, jsonfile);
      jsonfile << "\\t";
      break;
    }
    default: {
      if (c <= '\x1f') {
        auto const original_flags = jsonfile.flags();
        write_buffered_context(buf, prevIdx, idx, jsonfile);
        jsonfile << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(c);
        jsonfile.flags(original_flags);
      }
      break;
      // else: The character does not need to be escaped. Do not call
      // write_buffered_content so nothing is written and prevIdx remains
      // pointing to the first character that needs to be written on the next
      // call to write_buffered_content.
    }
    }
  }
  // Finally, call write_buffered_content to write out any data that has not
  // been written yet.
  write_buffered_context(buf, prevIdx, idx, jsonfile);

  return len;
}

} // anonymous namespace

namespace traffic_dump
{
std::string
json_entry(std::string_view name, std::string_view value)
{
  return std::string{"\""} + escape_json(name) + "\":\"" + escape_json(value) + "\"";
}

std::string
json_entry(std::string_view name, char const *value, int64_t size)
{
  return std::string{"\""} + escape_json(name) + "\":\"" + escape_json(value, size) + "\"";
}

std::string
json_entry_array(std::string_view name, std::string_view value)
{
  return std::string{"[\""} + escape_json(name) + "\",\"" + escape_json(value) + "\"]";
}

std::string
escape_json(std::string_view s)
{
  std::ostringstream o;
  esc_json_out(s.data(), s.length(), o);
  return o.str();
}

std::string
escape_json(char const *buf, int64_t size)
{
  std::ostringstream o;
  esc_json_out(buf, size, o);
  return o.str();
}

} // namespace traffic_dump
