#ifndef INTERNAL_JSONNODE_H
#define INTERNAL_JSONNODE_H

#include "JSONDebug.h"
#include "JSONChildren.h"
#include "JSONMemory.h"
#include "JSONGlobals.h"
#ifdef JSON_DEBUG
    #include <climits>  //to check int value
#endif
#include "JSONSharedString.h"

#ifdef JSON_LESS_MEMORY
    #ifdef __GNUC__
	   #pragma pack(push, 1)
    #elif _MSC_VER
	   #pragma pack(push, internalJSONNode_pack, 1)
    #endif
#endif

/*
    This class is the work horse of libjson, it handles all of the
    functinality of JSONNode.  This object is reference counted for
    speed and memory reasons.

    If JSON_REF_COUNT is not on, this internal structure still has an important
    purpose, as it can be passed around by JSONNoders that are flagged as temporary
*/

class JSONNode;  //forward declaration

#ifndef JSON_LIBRARY
    #define DECL_SET_INTEGER(type) void Set(type) json_nothrow json_write_priority; void Set(unsigned type) json_nothrow json_write_priority;
    #define DECL_CAST_OP(type) operator type() const json_nothrow; operator unsigned type() const json_nothrow;
#endif

#ifdef JSON_MUTEX_CALLBACKS
    #define initializeMutex(x) ,mylock(x)
#else
    #define initializeMutex(x)
#endif

#if defined(JSON_PREPARSE) || !defined(JSON_READ_PRIORITY)
    #define SetFetched(b) (void)0
    #define Fetch() (void)0
    #define initializeFetch(x)
#else
    #define initializeFetch(x) ,fetched(x)
#endif

#ifdef JSON_REF_COUNT
    #define initializeRefCount(x) ,refcount(x)
#else
    #define initializeRefCount(x)
#endif

#ifdef JSON_COMMENTS
    #define initializeComment(x) ,_comment(x)
#else
    #define initializeComment(x)
#endif

#ifdef JSON_LESS_MEMORY
    #define CHILDREN _value.Children
    #define DELETE_CHILDREN()\
	   if (isContainer()){\
		  jsonChildren::deleteChildren(CHILDREN);\
	   }
    #define CHILDREN_TO_NULL() (void)0
    #define initializeChildren(x)
#else
    #define CHILDREN Children
    #define DELETE_CHILDREN()\
	   if (CHILDREN != 0) jsonChildren::deleteChildren(CHILDREN);
    #define CHILDREN_TO_NULL() CHILDREN = 0
    #define makeNotContainer() (void)0
    #define makeContainer() if (!CHILDREN) CHILDREN = jsonChildren::newChildren()
    #define initializeChildren(x) ,CHILDREN(x)
#endif

class internalJSONNode {
public:
	LIBJSON_OBJECT(internalJSONNode)
    internalJSONNode(char mytype = JSON_NULL) json_nothrow json_hot;
    #ifdef JSON_READ_PRIORITY
	   internalJSONNode(const json_string & unparsed) json_nothrow json_hot;
	   internalJSONNode(const json_string & name_t, const json_string & value_t) json_nothrow json_read_priority;
    #endif
    internalJSONNode(const internalJSONNode & orig) json_nothrow json_hot;
    internalJSONNode & operator = (const internalJSONNode &) json_nothrow json_hot;
    ~internalJSONNode(void) json_nothrow json_hot;

    static internalJSONNode * newInternal(char mytype = JSON_NULL) json_hot;
    #ifdef JSON_READ_PRIORITY
	   static internalJSONNode * newInternal(const json_string & unparsed) json_hot;
	   static internalJSONNode * newInternal(const json_string & name_t, const json_string & value_t) json_hot;
    #endif
    static internalJSONNode * newInternal(const internalJSONNode & orig) json_hot;  //not copyable, only by this class
    static void deleteInternal(internalJSONNode * ptr) json_nothrow json_hot;

    json_index_t size(void) const json_nothrow json_read_priority;
    bool empty(void) const json_nothrow;
    unsigned char type(void) const json_nothrow json_read_priority;

    json_string name(void) const json_nothrow json_read_priority;
    void setname(const json_string & newname) json_nothrow json_write_priority;
    #ifdef JSON_COMMENTS
	   void setcomment(const json_string & comment) json_nothrow;
	   json_string getcomment(void) const json_nothrow;
    #endif

    #if !defined(JSON_PREPARSE) && defined(JSON_READ_PRIORITY)
	   void preparse(void) json_nothrow;
    #endif

    #ifdef JSON_LIBRARY
	   void push_back(JSONNode * node) json_nothrow;
    #else
	   void push_back(const JSONNode & node) json_nothrow;
    #endif
    void reserve(json_index_t siz) json_nothrow;
    void push_front(const JSONNode & node) json_nothrow;
    JSONNode * pop_back(json_index_t pos) json_nothrow;
    JSONNode * pop_back(const json_string & name_t) json_nothrow;
    #ifdef JSON_CASE_INSENSITIVE_FUNCTIONS
	   JSONNode * pop_back_nocase(const json_string & name_t) json_nothrow;
    #endif

    JSONNode * at(json_index_t pos) json_nothrow;
    //These return ** because pop_back needs them
    JSONNode ** at(const json_string & name_t) json_nothrow;
    #ifdef JSON_CASE_INSENSITIVE_FUNCTIONS
	   JSONNode ** at_nocase(const json_string & name_t) json_nothrow;
    #endif

    void Set(const json_string & val) json_nothrow json_write_priority;
    #ifdef JSON_LIBRARY
	   void Set(json_number val) json_nothrow json_write_priority;
	   void Set(json_int_t val) json_nothrow json_write_priority;
	   operator json_int_t() const json_nothrow;
	   operator json_number() const json_nothrow;
    #else
	   DECL_SET_INTEGER(char)
	   DECL_SET_INTEGER(short)
	   DECL_SET_INTEGER(int)
	   DECL_SET_INTEGER(long)
	   #ifndef JSON_ISO_STRICT
		  DECL_SET_INTEGER(long long)
		  void Set(long double val) json_nothrow json_write_priority;
	   #endif
	   void Set(float val) json_nothrow json_write_priority;
	   void Set(double val) json_nothrow json_write_priority;


	   DECL_CAST_OP(char)
	   DECL_CAST_OP(short)
	   DECL_CAST_OP(int)
	   DECL_CAST_OP(long)
	   #ifndef JSON_ISO_STRICT
		  DECL_CAST_OP(long long)
		  operator long double() const json_nothrow;
	   #endif
	   operator float() const json_nothrow;
	   operator double() const json_nothrow;
    #endif
    operator json_string()const json_nothrow;
    operator bool() const json_nothrow;
    void Set(bool val) json_nothrow;

    bool IsEqualTo(const json_string & val) const json_nothrow;
    bool IsEqualTo(bool val) const json_nothrow;
    bool IsEqualTo(const internalJSONNode * val) const json_nothrow;

    template<typename T>
    bool IsEqualToNum(T val) const json_nothrow;

    internalJSONNode * incRef(void) json_nothrow;
    #ifdef JSON_REF_COUNT
	   void decRef(void) json_nothrow json_hot;
	   bool hasNoReferences(void) json_nothrow json_hot;
    #endif
    internalJSONNode * makeUnique(void) json_nothrow json_hot;

    JSONNode ** begin(void) const json_nothrow;
    JSONNode ** end(void) const json_nothrow;
    bool Fetched(void) const json_nothrow json_hot;
    #ifdef JSON_MUTEX_CALLBACKS
	   void _set_mutex(void * mutex, bool unset = true) json_nothrow json_cold;
	   void _unset_mutex(void) json_nothrow json_cold;
    #endif

    #ifdef JSON_WRITE_PRIORITY
		void DumpRawString(json_string & output) const json_nothrow json_write_priority;
	   void WriteName(bool formatted, bool arrayChild, json_string & output) const json_nothrow json_write_priority;
	   #ifdef JSON_ARRAY_SIZE_ON_ONE_LINE
		  void WriteChildrenOneLine(unsigned int indent, json_string & output) const json_nothrow json_write_priority;
	   #endif
	   void WriteChildren(unsigned int indent, json_string & output) const json_nothrow json_write_priority;
	   void WriteComment(unsigned int indent, json_string & output) const json_nothrow json_write_priority;
	   void Write(unsigned int indent, bool arrayChild, json_string & output) const json_nothrow json_write_priority;
    #endif


    inline bool isContainer(void) const json_nothrow {
	   return (_type == JSON_NODE || _type == JSON_ARRAY);
    }
    inline bool isNotContainer(void) const json_nothrow {
	   return (_type != JSON_NODE && _type != JSON_ARRAY);
    }

    #ifdef JSON_LESS_MEMORY
	   inline void makeNotContainer(void){
		  if (isContainer()){
			 jsonChildren::deleteChildren(CHILDREN);
		  }
	   }
	   inline void makeContainer(void){
		  if (isNotContainer()){
			 CHILDREN = jsonChildren::newChildren();
		  }
	   }
    #endif

    void Nullify(void) const json_nothrow;

    #if !defined(JSON_PREPARSE) && defined(JSON_READ_PRIORITY)
	   void SetFetched(bool val) const json_nothrow json_hot;
	   void Fetch(void) const json_nothrow json_hot;  //it's const because it doesn't change the VALUE of the function
    #endif

    #ifdef JSON_READ_PRIORITY
	   void FetchString(void) const json_nothrow json_read_priority;
	   void FetchNode(void) const json_nothrow json_read_priority;
	   void FetchArray(void) const json_nothrow json_read_priority;
    #endif
    void FetchNumber(void) const json_nothrow json_read_priority;

    #ifdef JSON_CASE_INSENSITIVE_FUNCTIONS
	   static bool AreEqualNoCase(const json_char * ch_one, const json_char * ch_two) json_nothrow json_read_priority;
    #endif

    inline void clearname(void) json_nothrow {
	   clearString(_name);
    }

    #ifdef JSON_DEBUG
	   #ifndef JSON_LIBRARY
		  JSONNode Dump(size_t & totalmemory) const json_nothrow;
		  JSONNode DumpMutex(void) const json_nothrow;
	   #endif
    #endif


    mutable unsigned char _type BITS(3);

    json_string _name;
    mutable bool _name_encoded BITS(1);

    mutable json_string _string;   //these are both mutable because the string can change when it's fetched
    mutable bool _string_encoded BITS(1);

    //the value of the json
    union value_union_t {
	   bool _bool BITS(1);
	   json_number _number;
	   #ifdef JSON_LESS_MEMORY
		  jsonChildren * Children;
	   #endif
    };
    mutable value_union_t _value; //internal structure changes depending on type

    #ifdef JSON_MUTEX_CALLBACKS
	   void * mylock;
    #endif

    #ifdef JSON_REF_COUNT
	   size_t refcount PACKED(20);
    #endif

    #if !defined(JSON_PREPARSE) && defined(JSON_READ_PRIORITY)
	   mutable bool fetched BITS(1);
    #endif

    #ifdef JSON_COMMENTS
	   json_string _comment;
    #endif

    #ifndef JSON_LESS_MEMORY
	   jsonChildren * CHILDREN;
    #endif
};

inline internalJSONNode::internalJSONNode(char mytype) json_nothrow : _type(mytype), _name(), _name_encoded(), _string(), _string_encoded(), _value()
    initializeMutex(0)
    initializeRefCount(1)
    initializeFetch(true)
    initializeComment(json_global(EMPTY_JSON_STRING))
    initializeChildren((_type == JSON_NODE || _type == JSON_ARRAY) ? jsonChildren::newChildren() : 0){

    LIBJSON_CTOR;

    #ifdef JSON_LESS_MEMORY
	   //if not less memory, its in the initialization list
	   if (isContainer()){
		  CHILDREN = jsonChildren::newChildren();
	   }
    #endif
}

inline internalJSONNode * internalJSONNode::incRef(void) json_nothrow {
    #ifdef JSON_REF_COUNT
	   ++refcount;
	   return this;
    #else
	   return makeUnique();
    #endif
}

inline json_index_t internalJSONNode::size(void) const json_nothrow {
    if (isNotContainer()) return 0;
    Fetch();
    return CHILDREN -> size();
}

inline bool internalJSONNode::empty(void) const json_nothrow {
    if (isNotContainer()) return true;
    Fetch();
    return CHILDREN -> empty();
}

inline unsigned char internalJSONNode::type(void) const json_nothrow {
    return _type;
}

inline json_string internalJSONNode::name(void) const json_nothrow {
    return _name;
}

inline void internalJSONNode::setname(const json_string & newname) json_nothrow {
    #ifdef JSON_LESS_MEMORY
	   JSON_ASSERT(newname.capacity() == newname.length(), JSON_TEXT("name object too large"));
    #endif
    _name = newname;
    _name_encoded = true;
}

#ifdef JSON_COMMENTS
    inline void internalJSONNode::setcomment(const json_string & comment) json_nothrow {
	   _comment = comment;
    }

    inline json_string internalJSONNode::getcomment(void) const json_nothrow {
	   return _comment;
    }
#endif

inline bool internalJSONNode::IsEqualTo(const json_string & val) const json_nothrow {
    if (type() != JSON_STRING) return false;
    Fetch();
    return _string == val;
}

inline bool internalJSONNode::IsEqualTo(bool val) const json_nothrow {
    if (type() != JSON_BOOL) return false;
    Fetch();
    return val == _value._bool;
}

template<typename T>
inline bool internalJSONNode::IsEqualToNum(T val) const json_nothrow {
    if (type() != JSON_NUMBER) return false;
    Fetch();
    return (json_number)val == _value._number;
}

#ifdef JSON_REF_COUNT
    inline void internalJSONNode::decRef(void) json_nothrow {
	   JSON_ASSERT(refcount != 0, JSON_TEXT("decRef on a 0 refcount internal"));
	   --refcount;
    }

    inline bool internalJSONNode::hasNoReferences(void) json_nothrow {
	   return refcount == 0;
    }
#endif

inline internalJSONNode * internalJSONNode::makeUnique(void) json_nothrow {
    #ifdef JSON_REF_COUNT
	   if (refcount > 1){
		  decRef();
		  return newInternal(*this);
	   }
	   JSON_ASSERT(refcount == 1, JSON_TEXT("makeUnique on a 0 refcount internal"));
	   return this;
    #else
	   return newInternal(*this);
    #endif
}

#if !defined(JSON_PREPARSE) && defined(JSON_READ_PRIORITY)
    inline void internalJSONNode::SetFetched(bool val) const json_nothrow {
	   fetched = val;
    }
#endif

inline bool internalJSONNode::Fetched(void) const json_nothrow {
    #if !defined(JSON_PREPARSE) && defined(JSON_READ_PRIORITY)
	   return fetched;
    #else
	   return true;
    #endif
}

inline JSONNode ** internalJSONNode::begin(void) const json_nothrow {
    JSON_ASSERT_SAFE(isContainer(), json_global(ERROR_NON_CONTAINER) + JSON_TEXT("begin"), return 0;);
    Fetch();
    return CHILDREN -> begin();
}

inline JSONNode ** internalJSONNode::end(void) const json_nothrow {
    JSON_ASSERT_SAFE(isContainer(), json_global(ERROR_NON_CONTAINER) + JSON_TEXT("end"), return 0;);
    Fetch();
    return CHILDREN -> end();
}

inline JSONNode * internalJSONNode::at(json_index_t pos) json_nothrow {
    JSON_ASSERT_SAFE(isContainer(), JSON_TEXT("calling at on non-container type"), return 0;);
    Fetch();
    return (*CHILDREN)[pos];
}

#if defined(JSON_LESS_MEMORY) && defined(__GNUC__)
    inline void internalJSONNode::reserve(json_index_t __attribute__((unused)) siz) json_nothrow
#else
    inline void internalJSONNode::reserve(json_index_t siz) json_nothrow
#endif
{
    JSON_ASSERT_SAFE(isContainer(), json_global(ERROR_NON_CONTAINER) + JSON_TEXT("reserve"), return;);
    Fetch();
    jsonChildren::reserve2(CHILDREN, siz);
}



/*
    cast operators
*/
#ifndef JSON_LIBRARY
    #ifdef JSON_ISO_STRICT
	   #define BASE_CONVERT_TYPE long
    #else
	   #define BASE_CONVERT_TYPE long long
    #endif

    #define IMP_SMALLER_INT_CAST_OP(_type, type_max, type_min)\
	   inline internalJSONNode::operator _type() const json_nothrow {\
		  JSON_ASSERT(_value._number > type_min, _string + json_global(ERROR_LOWER_RANGE) + JSON_TEXT(#_type));\
		  JSON_ASSERT(_value._number < type_max, _string + json_global(ERROR_UPPER_RANGE) + JSON_TEXT(#_type));\
		  JSON_ASSERT(_value._number == (json_number)((_type)(_value._number)), json_string(JSON_TEXT("(")) + json_string(JSON_TEXT(#_type)) + json_string(JSON_TEXT(") will truncate ")) + _string);\
		  return (_type)static_cast<BASE_CONVERT_TYPE>(*this);\
	   }

    IMP_SMALLER_INT_CAST_OP(char, CHAR_MAX, CHAR_MIN)
    IMP_SMALLER_INT_CAST_OP(unsigned char, UCHAR_MAX, 0)
    IMP_SMALLER_INT_CAST_OP(short, SHRT_MAX, SHRT_MIN)
    IMP_SMALLER_INT_CAST_OP(unsigned short, USHRT_MAX, 0)
    IMP_SMALLER_INT_CAST_OP(int, INT_MAX, INT_MIN)
    IMP_SMALLER_INT_CAST_OP(unsigned int, UINT_MAX, 0)

    #ifndef JSON_ISO_STRICT
	   IMP_SMALLER_INT_CAST_OP(long, LONG_MAX, LONG_MIN)
	   IMP_SMALLER_INT_CAST_OP(unsigned long, ULONG_MAX, 0)
    #endif
#endif

inline internalJSONNode::operator json_string() const json_nothrow {
    Fetch();
    return _string;
}


#ifndef JSON_LIBRARY
    #ifndef JSON_ISO_STRICT
	   inline internalJSONNode::operator float() const json_nothrow {
		  return static_cast<float>(static_cast<long double>(*this));
	   }
	   inline internalJSONNode::operator double() const json_nothrow {
		  return static_cast<double>(static_cast<long double>(*this));
	   }
    #else
	   inline internalJSONNode::operator float() const json_nothrow {
		  return static_cast<float>(static_cast<double>(*this));
	   }
    #endif
#endif

#ifdef JSON_LESS_MEMORY
    #ifdef __GNUC__
	   #pragma pack(pop)
    #elif _MSC_VER
	   #pragma pack(pop, internalJSONNode_pack,)
    #endif
#endif
#endif
