//===---------------------JSON.h --------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#ifndef utility_JSON_h_
#define utility_JSON_h_

#include "StdStringExtractor.h"

// C includes
#include <inttypes.h>
#include <stdint.h>

// C++ includes
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <vector>

class JSONValue {
public:
  virtual void Write(std::ostream &s) = 0;

  typedef std::shared_ptr<JSONValue> SP;

  enum class Kind { String, Number, True, False, Null, Object, Array };

  JSONValue(Kind k) : m_kind(k) {}

  Kind GetKind() const { return m_kind; }

  virtual ~JSONValue() = default;

private:
  const Kind m_kind;
};

class JSONString : public JSONValue {
public:
  JSONString();
  JSONString(const char *s);
  JSONString(const std::string &s);

  JSONString(const JSONString &s) = delete;
  JSONString &operator=(const JSONString &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONString> SP;

  std::string GetData() { return m_data; }

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::String;
  }

  ~JSONString() override = default;

private:
  static std::string json_string_quote_metachars(const std::string &);

  std::string m_data;
};

class JSONNumber : public JSONValue {
public:
  typedef std::shared_ptr<JSONNumber> SP;

  // We cretae a constructor for all integer and floating point type with using
  // templates and
  // SFINAE to avoid having ambiguous overloads because of the implicit type
  // promotion. If we
  // would have constructors only with int64_t, uint64_t and double types then
  // constructing a
  // JSONNumber from an int32_t (or any other similar type) would fail to
  // compile.

  template <typename T, typename std::enable_if<
                            std::is_integral<T>::value &&
                            std::is_unsigned<T>::value>::type * = nullptr>
  explicit JSONNumber(T u)
      : JSONValue(JSONValue::Kind::Number), m_data_type(DataType::Unsigned) {
    m_data.m_unsigned = u;
  }

  template <typename T,
            typename std::enable_if<std::is_integral<T>::value &&
                                    std::is_signed<T>::value>::type * = nullptr>
  explicit JSONNumber(T s)
      : JSONValue(JSONValue::Kind::Number), m_data_type(DataType::Signed) {
    m_data.m_signed = s;
  }

  template <typename T, typename std::enable_if<
                            std::is_floating_point<T>::value>::type * = nullptr>
  explicit JSONNumber(T d)
      : JSONValue(JSONValue::Kind::Number), m_data_type(DataType::Double) {
    m_data.m_double = d;
  }

  ~JSONNumber() override = default;

  JSONNumber(const JSONNumber &s) = delete;
  JSONNumber &operator=(const JSONNumber &s) = delete;

  void Write(std::ostream &s) override;

  uint64_t GetAsUnsigned() const;

  int64_t GetAsSigned() const;

  double GetAsDouble() const;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::Number;
  }

private:
  enum class DataType : uint8_t { Unsigned, Signed, Double } m_data_type;

  union {
    uint64_t m_unsigned;
    int64_t m_signed;
    double m_double;
  } m_data;
};

class JSONTrue : public JSONValue {
public:
  JSONTrue();

  JSONTrue(const JSONTrue &s) = delete;
  JSONTrue &operator=(const JSONTrue &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONTrue> SP;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::True;
  }

  ~JSONTrue() override = default;
};

class JSONFalse : public JSONValue {
public:
  JSONFalse();

  JSONFalse(const JSONFalse &s) = delete;
  JSONFalse &operator=(const JSONFalse &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONFalse> SP;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::False;
  }

  ~JSONFalse() override = default;
};

class JSONNull : public JSONValue {
public:
  JSONNull();

  JSONNull(const JSONNull &s) = delete;
  JSONNull &operator=(const JSONNull &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONNull> SP;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::Null;
  }

  ~JSONNull() override = default;
};

class JSONObject : public JSONValue {
public:
  JSONObject();

  JSONObject(const JSONObject &s) = delete;
  JSONObject &operator=(const JSONObject &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONObject> SP;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::Object;
  }

  bool SetObject(const std::string &key, JSONValue::SP value);

  JSONValue::SP GetObject(const std::string &key) const;

  // -------------------------------------------------------------------------
  /// Return keyed value as bool
  ///
  /// @param[in] key
  ///     The value of the key to lookup
  ///
  /// @param[out] value
  ///     The value of the key as a bool.  Undefined if the key doesn't
  ///     exist or if the key is not either true or false.
  ///
  /// @return
  ///     true if the key existed as was a bool value; false otherwise.
  ///     Note the return value is *not* the value of the bool, use
  ///     \b value for that.
  // -------------------------------------------------------------------------
  bool GetObjectAsBool(const std::string &key, bool &value) const;

  bool GetObjectAsString(const std::string &key, std::string &value) const;

  ~JSONObject() override = default;

private:
  typedef std::map<std::string, JSONValue::SP> Map;
  typedef Map::iterator Iterator;
  Map m_elements;
};

class JSONArray : public JSONValue {
public:
  JSONArray();

  JSONArray(const JSONArray &s) = delete;
  JSONArray &operator=(const JSONArray &s) = delete;

  void Write(std::ostream &s) override;

  typedef std::shared_ptr<JSONArray> SP;

  static bool classof(const JSONValue *V) {
    return V->GetKind() == JSONValue::Kind::Array;
  }

private:
  typedef std::vector<JSONValue::SP> Vector;
  typedef Vector::iterator Iterator;
  typedef Vector::size_type Index;
  typedef Vector::size_type Size;

public:
  bool SetObject(Index i, JSONValue::SP value);

  bool AppendObject(JSONValue::SP value);

  JSONValue::SP GetObject(Index i);

  Size GetNumElements();

  ~JSONArray() override = default;

  Vector m_elements;
};

class JSONParser : public StdStringExtractor {
public:
  enum Token {
    Invalid,
    Status,
    ObjectStart,
    ObjectEnd,
    ArrayStart,
    ArrayEnd,
    Comma,
    Colon,
    String,
    Integer,
    Float,
    True,
    False,
    Null,
    EndOfFile
  };

  JSONParser(const char *cstr);

  int GetEscapedChar(bool &was_escaped);

  Token GetToken(std::string &value);

  JSONValue::SP ParseJSONValue();

protected:
  JSONValue::SP ParseJSONObject();

  JSONValue::SP ParseJSONArray();
};

#endif // utility_JSON_h_
