//===--------------------- JSON.cpp -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "JSON.h"

// C includes
#include <assert.h>
#include <limits.h>

// C++ includes
#include "lldb/Host/StringConvert.h"
#include <iomanip>
#include <sstream>

using namespace lldb_private;

std::string JSONString::json_string_quote_metachars(const std::string &s) {
  if (s.find('"') == std::string::npos)
    return s;

  std::string output;
  const size_t s_size = s.size();
  const char *s_chars = s.c_str();
  for (size_t i = 0; i < s_size; i++) {
    unsigned char ch = *(s_chars + i);
    if (ch == '"') {
      output.push_back('\\');
    }
    output.push_back(ch);
  }
  return output;
}

JSONString::JSONString() : JSONValue(JSONValue::Kind::String), m_data() {}

JSONString::JSONString(const char *s)
    : JSONValue(JSONValue::Kind::String), m_data(s ? s : "") {}

JSONString::JSONString(const std::string &s)
    : JSONValue(JSONValue::Kind::String), m_data(s) {}

void JSONString::Write(std::ostream &s) {
  s << "\"" << json_string_quote_metachars(m_data).c_str() << "\"";
}

uint64_t JSONNumber::GetAsUnsigned() const {
  switch (m_data_type) {
  case DataType::Unsigned:
    return m_data.m_unsigned;
  case DataType::Signed:
    return (uint64_t)m_data.m_signed;
  case DataType::Double:
    return (uint64_t)m_data.m_double;
  }
}

int64_t JSONNumber::GetAsSigned() const {
  switch (m_data_type) {
  case DataType::Unsigned:
    return (int64_t)m_data.m_unsigned;
  case DataType::Signed:
    return m_data.m_signed;
  case DataType::Double:
    return (int64_t)m_data.m_double;
  }
}

double JSONNumber::GetAsDouble() const {
  switch (m_data_type) {
  case DataType::Unsigned:
    return (double)m_data.m_unsigned;
  case DataType::Signed:
    return (double)m_data.m_signed;
  case DataType::Double:
    return m_data.m_double;
  }
}

void JSONNumber::Write(std::ostream &s) {
  switch (m_data_type) {
  case DataType::Unsigned:
    s << m_data.m_unsigned;
    break;
  case DataType::Signed:
    s << m_data.m_signed;
    break;
  case DataType::Double:
    // Set max precision to emulate %g.
    s << std::setprecision(std::numeric_limits<double>::digits10 + 1);
    s << m_data.m_double;
    break;
  }
}

JSONTrue::JSONTrue() : JSONValue(JSONValue::Kind::True) {}

void JSONTrue::Write(std::ostream &s) { s << "true"; }

JSONFalse::JSONFalse() : JSONValue(JSONValue::Kind::False) {}

void JSONFalse::Write(std::ostream &s) { s << "false"; }

JSONNull::JSONNull() : JSONValue(JSONValue::Kind::Null) {}

void JSONNull::Write(std::ostream &s) { s << "null"; }

JSONObject::JSONObject() : JSONValue(JSONValue::Kind::Object) {}

void JSONObject::Write(std::ostream &s) {
  bool first = true;
  s << '{';
  auto iter = m_elements.begin(), end = m_elements.end();
  for (; iter != end; iter++) {
    if (first)
      first = false;
    else
      s << ',';
    JSONString key(iter->first);
    JSONValue::SP value(iter->second);
    key.Write(s);
    s << ':';
    value->Write(s);
  }
  s << '}';
}

bool JSONObject::SetObject(const std::string &key, JSONValue::SP value) {
  if (key.empty() || nullptr == value.get())
    return false;
  m_elements[key] = value;
  return true;
}

JSONValue::SP JSONObject::GetObject(const std::string &key) const {
  auto iter = m_elements.find(key), end = m_elements.end();
  if (iter == end)
    return JSONValue::SP();
  return iter->second;
}

bool JSONObject::GetObjectAsBool(const std::string &key, bool &value) const {
  auto value_sp = GetObject(key);
  if (!value_sp) {
    // The given key doesn't exist, so we have no value.
    return false;
  }

  if (JSONTrue::classof(value_sp.get())) {
    // We have the value, and it is true.
    value = true;
    return true;
  } else if (JSONFalse::classof(value_sp.get())) {
    // We have the value, and it is false.
    value = false;
    return true;
  } else {
    // We don't have a valid bool value for the given key.
    return false;
  }
}

bool JSONObject::GetObjectAsString(const std::string &key,
                                   std::string &value) const {
  auto value_sp = GetObject(key);
  if (!value_sp) {
    // The given key doesn't exist, so we have no value.
    return false;
  }

  if (!JSONString::classof(value_sp.get()))
    return false;

  value = static_cast<JSONString *>(value_sp.get())->GetData();
  return true;
}

JSONArray::JSONArray() : JSONValue(JSONValue::Kind::Array) {}

void JSONArray::Write(std::ostream &s) {
  bool first = true;
  s << '[';
  auto iter = m_elements.begin(), end = m_elements.end();
  for (; iter != end; iter++) {
    if (first)
      first = false;
    else
      s << ',';
    (*iter)->Write(s);
  }
  s << ']';
}

bool JSONArray::SetObject(Index i, JSONValue::SP value) {
  if (value.get() == nullptr)
    return false;
  if (i < m_elements.size()) {
    m_elements[i] = value;
    return true;
  }
  if (i == m_elements.size()) {
    m_elements.push_back(value);
    return true;
  }
  return false;
}

bool JSONArray::AppendObject(JSONValue::SP value) {
  if (value.get() == nullptr)
    return false;
  m_elements.push_back(value);
  return true;
}

JSONValue::SP JSONArray::GetObject(Index i) {
  if (i < m_elements.size())
    return m_elements[i];
  return JSONValue::SP();
}

JSONArray::Size JSONArray::GetNumElements() { return m_elements.size(); }

JSONParser::JSONParser(const char *cstr) : StdStringExtractor(cstr) {}

JSONParser::Token JSONParser::GetToken(std::string &value) {
  std::ostringstream error;

  value.clear();
  SkipSpaces();
  const uint64_t start_index = m_index;
  const char ch = GetChar();
  switch (ch) {
  case '{':
    return Token::ObjectStart;
  case '}':
    return Token::ObjectEnd;
  case '[':
    return Token::ArrayStart;
  case ']':
    return Token::ArrayEnd;
  case ',':
    return Token::Comma;
  case ':':
    return Token::Colon;
  case '\0':
    return Token::EndOfFile;
  case 't':
    if (GetChar() == 'r')
      if (GetChar() == 'u')
        if (GetChar() == 'e')
          return Token::True;
    break;

  case 'f':
    if (GetChar() == 'a')
      if (GetChar() == 'l')
        if (GetChar() == 's')
          if (GetChar() == 'e')
            return Token::False;
    break;

  case 'n':
    if (GetChar() == 'u')
      if (GetChar() == 'l')
        if (GetChar() == 'l')
          return Token::Null;
    break;

  case '"': {
    while (1) {
      bool was_escaped = false;
      int escaped_ch = GetEscapedChar(was_escaped);
      if (escaped_ch == -1) {
        error << "error: an error occurred getting a character from offset "
              << start_index;
        value = error.str();
        return Token::Status;

      } else {
        const bool is_end_quote = escaped_ch == '"';
        const bool is_null = escaped_ch == 0;
        if (was_escaped || (!is_end_quote && !is_null)) {
          if (CHAR_MIN <= escaped_ch && escaped_ch <= CHAR_MAX) {
            value.append(1, (char)escaped_ch);
          } else {
            error << "error: wide character support is needed for unicode "
                     "character 0x"
                  << std::setprecision(4) << std::hex << escaped_ch;
            error << " at offset " << start_index;
            value = error.str();
            return Token::Status;
          }
        } else if (is_end_quote) {
          return Token::String;
        } else if (is_null) {
          value = "error: missing end quote for string";
          return Token::Status;
        }
      }
    }
  } break;

  case '-':
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9': {
    bool done = false;
    bool got_decimal_point = false;
    uint64_t exp_index = 0;
    bool got_int_digits = (ch >= '0') && (ch <= '9');
    bool got_frac_digits = false;
    bool got_exp_digits = false;
    while (!done) {
      const char next_ch = PeekChar();
      switch (next_ch) {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        if (exp_index != 0) {
          got_exp_digits = true;
        } else if (got_decimal_point) {
          got_frac_digits = true;
        } else {
          got_int_digits = true;
        }
        ++m_index; // Skip this character
        break;

      case '.':
        if (got_decimal_point) {
          error << "error: extra decimal point found at offset " << start_index;
          value = error.str();
          return Token::Status;
        } else {
          got_decimal_point = true;
          ++m_index; // Skip this character
        }
        break;

      case 'e':
      case 'E':
        if (exp_index != 0) {
          error << "error: extra exponent character found at offset "
                << start_index;
          value = error.str();
          return Token::Status;
        } else {
          exp_index = m_index;
          ++m_index; // Skip this character
        }
        break;

      case '+':
      case '-':
        // The '+' and '-' can only come after an exponent character...
        if (exp_index == m_index - 1) {
          ++m_index; // Skip the exponent sign character
        } else {
          error << "error: unexpected " << next_ch << " character at offset "
                << start_index;
          value = error.str();
          return Token::Status;
        }
        break;

      default:
        done = true;
        break;
      }
    }

    if (m_index > start_index) {
      value = m_packet.substr(start_index, m_index - start_index);
      if (got_decimal_point) {
        if (exp_index != 0) {
          // We have an exponent, make sure we got exponent digits
          if (got_exp_digits) {
            return Token::Float;
          } else {
            error << "error: got exponent character but no exponent digits at "
                     "offset in float value \""
                  << value.c_str() << "\"";
            value = error.str();
            return Token::Status;
          }
        } else {
          // No exponent, but we need at least one decimal after the decimal
          // point
          if (got_frac_digits) {
            return Token::Float;
          } else {
            error << "error: no digits after decimal point \"" << value.c_str()
                  << "\"";
            value = error.str();
            return Token::Status;
          }
        }
      } else {
        // No decimal point
        if (got_int_digits) {
          // We need at least some integer digits to make an integer
          return Token::Integer;
        } else {
          error << "error: no digits negate sign \"" << value.c_str() << "\"";
          value = error.str();
          return Token::Status;
        }
      }
    } else {
      error << "error: invalid number found at offset " << start_index;
      value = error.str();
      return Token::Status;
    }
  } break;
  default:
    break;
  }
  error << "error: failed to parse token at offset " << start_index
        << " (around character '" << ch << "')";
  value = error.str();
  return Token::Status;
}

int JSONParser::GetEscapedChar(bool &was_escaped) {
  was_escaped = false;
  const char ch = GetChar();
  if (ch == '\\') {
    was_escaped = true;
    const char ch2 = GetChar();
    switch (ch2) {
    case '"':
    case '\\':
    case '/':
    default:
      break;

    case 'b':
      return '\b';
    case 'f':
      return '\f';
    case 'n':
      return '\n';
    case 'r':
      return '\r';
    case 't':
      return '\t';
    case 'u': {
      const int hi_byte = DecodeHexU8();
      const int lo_byte = DecodeHexU8();
      if (hi_byte >= 0 && lo_byte >= 0)
        return hi_byte << 8 | lo_byte;
      return -1;
    } break;
    }
    return ch2;
  }
  return ch;
}

JSONValue::SP JSONParser::ParseJSONObject() {
  // The "JSONParser::Token::ObjectStart" token should have already been
  // consumed
  // by the time this function is called
  std::unique_ptr<JSONObject> dict_up(new JSONObject());

  std::string value;
  std::string key;
  while (1) {
    JSONParser::Token token = GetToken(value);

    if (token == JSONParser::Token::String) {
      key.swap(value);
      token = GetToken(value);
      if (token == JSONParser::Token::Colon) {
        JSONValue::SP value_sp = ParseJSONValue();
        if (value_sp)
          dict_up->SetObject(key, value_sp);
        else
          break;
      }
    } else if (token == JSONParser::Token::ObjectEnd) {
      return JSONValue::SP(dict_up.release());
    } else if (token == JSONParser::Token::Comma) {
      continue;
    } else {
      break;
    }
  }
  return JSONValue::SP();
}

JSONValue::SP JSONParser::ParseJSONArray() {
  // The "JSONParser::Token::ObjectStart" token should have already been
  // consumed
  // by the time this function is called
  std::unique_ptr<JSONArray> array_up(new JSONArray());

  std::string value;
  std::string key;
  while (1) {
    JSONValue::SP value_sp = ParseJSONValue();
    if (value_sp)
      array_up->AppendObject(value_sp);
    else
      break;

    JSONParser::Token token = GetToken(value);
    if (token == JSONParser::Token::Comma) {
      continue;
    } else if (token == JSONParser::Token::ArrayEnd) {
      return JSONValue::SP(array_up.release());
    } else {
      break;
    }
  }
  return JSONValue::SP();
}

JSONValue::SP JSONParser::ParseJSONValue() {
  std::string value;
  const JSONParser::Token token = GetToken(value);
  switch (token) {
  case JSONParser::Token::ObjectStart:
    return ParseJSONObject();

  case JSONParser::Token::ArrayStart:
    return ParseJSONArray();

  case JSONParser::Token::Integer: {
    if (value.front() == '-') {
      bool success = false;
      int64_t sval = StringConvert::ToSInt64(value.c_str(), 0, 0, &success);
      if (success)
        return JSONValue::SP(new JSONNumber(sval));
    } else {
      bool success = false;
      uint64_t uval = StringConvert::ToUInt64(value.c_str(), 0, 0, &success);
      if (success)
        return JSONValue::SP(new JSONNumber(uval));
    }
  } break;

  case JSONParser::Token::Float: {
    bool success = false;
    double val = StringConvert::ToDouble(value.c_str(), 0.0, &success);
    if (success)
      return JSONValue::SP(new JSONNumber(val));
  } break;

  case JSONParser::Token::String:
    return JSONValue::SP(new JSONString(value));

  case JSONParser::Token::True:
    return JSONValue::SP(new JSONTrue());

  case JSONParser::Token::False:
    return JSONValue::SP(new JSONFalse());

  case JSONParser::Token::Null:
    return JSONValue::SP(new JSONNull());

  default:
    break;
  }
  return JSONValue::SP();
}
