//===---------------------JSON.h --------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef utility_JSON_h_
#define utility_JSON_h_

// This cross-project usage is fine as StringExtractor.h is entirely
// self-contained.
#include "lldb/Utility/StringExtractor.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 StringExtractor
{
public:
    enum Token
    {
        Invalid,
        Error,
        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_
