#pragma once

// a helper macro to detect C++11 compliant compilers
#if __cplusplus >= 201103L
#define __cplusplus11
#endif

// STL containers
#include <string>
#include <vector>
#include <map>

// additional C++11 headers
#ifdef __cplusplus11
#include <mutex>
#include <initializer_list>
#endif

class JSON {
    // forward declaration to friend this class
    public:
        class iterator;
        class const_iterator;

#ifdef __cplusplus11
    private:
        /// mutex to guard payload
        static std::mutex _token;
#endif

    public:
        /// possible types of a JSON object
        typedef enum {
            array, object, null, string, boolean, number, number_float
        } json_t;

    public:
        /// a type for an object
        typedef std::map<std::string, JSON> object_t;
        /// a type for an array
        typedef std::vector<JSON> array_t;
        /// a type for a string
        typedef std::string string_t;
        /// a type for a Boolean
        typedef bool boolean_t;
        /// a type for an integer number
        typedef int number_t;
        /// a type for a floating point number
        typedef double number_float_t;

        /// a JSON value
        union value {
            /// array as pointer to array_t
            array_t* array;
            /// object as pointer to object_t
            object_t* object;
            /// string as pointer to string_t
            string_t* string;
            /// Boolean
            boolean_t boolean;
            /// number (integer)
            number_t number;
            /// number (float)
            number_float_t number_float;

            /// default constructor
            value() {}
            /// constructor for arrays
            value(array_t* array): array(array) {}
            /// constructor for objects
            value(object_t* object): object(object) {}
            /// constructor for strings
            value(string_t* string): string(string) {}
            /// constructor for Booleans
            value(boolean_t boolean) : boolean(boolean) {}
            /// constructor for numbers (integer)
            value(number_t number) : number(number) {}
            /// constructor for numbers (float)
            value(number_float_t number_float) : number_float(number_float) {}
        };

    private:
        /// the type of this object
        json_t _type;

        /// the payload
        value _value;
        
#ifdef __cplusplus11
        /// a type for array initialization
        typedef std::initializer_list<JSON> array_init_t;
#endif

    public:
        /// create a null object
        JSON();
        /// create an object according to given type
        JSON(json_t);
        /// create a string object from a C++ string
        JSON(const std::string&);
        /// create a string object from a C string
        JSON(char*);
        /// create a string object from a C string
        JSON(const char*);
        /// create a Boolean object
        JSON(const bool);
        /// create a number object
        JSON(const int);
        /// create a number object
        JSON(const double);
        /// create an array
        JSON(array_t);
        /// create an object
        JSON(object_t);
#ifdef __cplusplus11
        /// create from an initializer list (to an array)
        JSON(array_init_t);
#endif

        /// copy constructor
        JSON(const JSON&);

#ifdef __cplusplus11
        /// move constructor
        JSON(JSON&&);
#endif

        /// copy assignment
#ifdef __cplusplus11
        JSON& operator=(JSON);
#else
        JSON& operator=(const JSON&);
#endif

        /// destructor
        ~JSON();

        /// implicit conversion to string representation
        operator const std::string() const;
        /// implicit conversion to integer (only for numbers)
        operator int() const;
        /// implicit conversion to double (only for numbers)
        operator double() const;
        /// implicit conversion to Boolean (only for Booleans)
        operator bool() const;
        /// implicit conversion to JSON vector (not for objects)
        operator std::vector<JSON>() const;
        /// implicit conversion to JSON map (only for objects)
        operator std::map<std::string, JSON>() const;

        /// write to stream
        friend std::ostream& operator<<(std::ostream& o, const JSON& j) {
            o << j.toString();
            return o;
        }
        /// write to stream
        friend std::ostream& operator>>(const JSON& j, std::ostream& o) {
            o << j.toString();
            return o;
        }

        /// read from stream
        friend std::istream& operator>>(std::istream& i, JSON& j) {
            parser(i).parse(j);
            return i;
        }
        /// read from stream
        friend std::istream& operator<<(JSON& j, std::istream& i) {
            parser(i).parse(j);
            return i;
        }

        /// explicit conversion to string representation (C++ style)
        const std::string toString() const;

        /// add an object/array to an array
        JSON& operator+=(const JSON&);
        /// add a string to an array
        JSON& operator+=(const std::string&);
        /// add a string to an array
        JSON& operator+=(const char*);
        /// add a Boolean to an array
        JSON& operator+=(bool);
        /// add a number to an array
        JSON& operator+=(int);
        /// add a number to an array
        JSON& operator+=(double);

        /// add an object/array to an array
        void push_back(const JSON&);
        /// add a string to an array
        void push_back(const std::string&);
        /// add a string to an array
        void push_back(const char*);
        /// add a Boolean to an array
        void push_back(bool);
        /// add a number to an array
        void push_back(int);
        /// add a number to an array
        void push_back(double);

        /// operator to set an element in an array
        JSON& operator[](int);
        /// operator to get an element in an array
        const JSON& operator[](const int) const;

        /// operator to set an element in an object
        JSON& operator[](const std::string&);
        /// operator to set an element in an object
        JSON& operator[](const char*);
        /// operator to get an element in an object
        const JSON& operator[](const std::string&) const;

        /// return the number of stored values
        size_t size() const;
        /// checks whether object is empty
        bool empty() const;

        /// return the type of the object
        json_t type() const;

        /// find an element in an object (returns end() iterator otherwise)
        iterator find(const std::string&);
        /// find an element in an object (returns end() iterator otherwise)
        const_iterator find(const std::string&) const;
        /// find an element in an object (returns end() iterator otherwise)
        iterator find(const char*);
        /// find an element in an object (returns end() iterator otherwise)
        const_iterator find(const char*) const;

        /// direct access to the underlying payload
        value data();
        /// direct access to the underlying payload
        const value data() const;

        /// lexicographically compares the values
        bool operator==(const JSON&) const;
        /// lexicographically compares the values
        bool operator!=(const JSON&) const;

    private:
        /// return the type as string
        std::string _typename() const;

    public:
        /// an iterator
        class iterator {
                friend class JSON;
                friend class JSON::const_iterator;
            public:
                iterator();
                iterator(JSON*);
                iterator(const iterator&);
                ~iterator();

                iterator& operator=(const iterator&);
                bool operator==(const iterator&) const;
                bool operator!=(const iterator&) const;
                iterator& operator++();
                JSON& operator*() const;
                JSON* operator->() const;

                /// getter for the key (in case of objects)
                std::string key() const;
                /// getter for the value
                JSON& value() const;

            private:
                /// a JSON value
                JSON* _object;
                /// an iterator for JSON arrays
                array_t::iterator* _vi;
                /// an iterator for JSON objects
                object_t::iterator* _oi;
        };

        /// a const iterator
        class const_iterator {
                friend class JSON;
            public:
                const_iterator();
                const_iterator(const JSON*);
                const_iterator(const const_iterator&);
                const_iterator(const iterator&);
                ~const_iterator();

                const_iterator& operator=(const const_iterator&);
                bool operator==(const const_iterator&) const;
                bool operator!=(const const_iterator&) const;
                const_iterator& operator++();
                const JSON& operator*() const;
                const JSON* operator->() const;

                /// getter for the key (in case of objects)
                std::string key() const;
                /// getter for the value
                const JSON& value() const;

            private:
                /// a JSON value
                const JSON* _object;
                /// an iterator for JSON arrays
                array_t::const_iterator* _vi;
                /// an iterator for JSON objects
                object_t::const_iterator* _oi;
        };

    public:
        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;
        const_iterator cbegin() const;
        const_iterator cend() const;

    private:
        /// a helper class to parse a JSON object
        class parser {
            public:
                /// a parser reading from a C string
                parser(char*);
                /// a parser reading from a C++ string
                parser(std::string&);
                /// a parser reading from an input stream
                parser(std::istream&);
                /// destructor of the parser
                ~parser();
                /// parse into a given JSON object
                void parse(JSON&);

            private:
                /// read the next character, stripping whitespace
                bool next();
                /// raise an exception with an error message
                void error(std::string) __attribute__((noreturn));
                /// parse a quoted string
                std::string parseString();
                /// parse a Boolean "true"
                void parseTrue();
                /// parse a Boolean "false"
                void parseFalse();
                /// parse a null object
                void parseNull();
                /// a helper function to expect a certain character
                void expect(char);

                /// the current character
                char _current;
                /// a buffer of the input
                char* _buffer;
                /// the position inside the input buffer
                size_t _pos;
                /// the length of the input buffer
                size_t _length;
        };
};