//
// Object.h
//
// $Id$
//
// Library: JSON
// Package: JSON
// Module:  Object
//
// Definition of the Object class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#ifndef JSON_Object_INCLUDED
#define JSON_Object_INCLUDED


#include "Poco/JSON/JSON.h"
#include "Poco/JSON/Array.h"
#include "Poco/JSON/Stringifier.h"
#include "Poco/SharedPtr.h"
#include "Poco/Dynamic/Var.h"
#include "Poco/Dynamic/Struct.h"
#include "Poco/Nullable.h"
#include <map>
#include <vector>
#include <deque>
#include <iostream>
#include <sstream>


namespace Poco {
namespace JSON {


class JSON_API Object
	/// Represents a JSON object. JSON object provides a representation
	/// based on shared pointers and optimized for performance. It is possible to 
	/// convert object to DynamicStruct. Conversion requires copying and therefore
	/// has performance penalty; the benefit is in improved syntax, eg:
	/// 
	///    std::string json = "{ \"test\" : { \"property\" : \"value\" } }";
	///    Parser parser;
	///    Var result = parser.parse(json);
	/// 
	///    // use pointers to avoid copying
	///    Object::Ptr object = result.extract<Object::Ptr>();
	///    Var test = object->get("test"); // holds { "property" : "value" }
	///    Object::Ptr subObject = test.extract<Object::Ptr>();
	///    test = subObject->get("property");
	///    std::string val = test.toString(); // val holds "value"
	/// 
	///    // copy/convert to Poco::DynamicStruct
	///    Poco::DynamicStruct ds = *object;
	///    val = ds["test"]["property"]; // val holds "value"
	/// 
{
public:
	typedef SharedPtr<Object>                   Ptr;
	typedef std::map<std::string, Dynamic::Var> ValueMap;
	typedef ValueMap::value_type                ValueType;
	typedef ValueMap::iterator                  Iterator;
	typedef ValueMap::const_iterator            ConstIterator;

	Object(bool preserveInsertionOrder = false);
		/// Default constructor. If preserveInsertionOrder, object
		/// will preserve the items insertion order. Otherwise, items
		/// will be sorted by keys.

	Object(const Object& copy);
		/// Copy constructor. Struct is not copied to keep the operation as
		/// efficient as possible (when needed, it will be generated upon request).

	virtual ~Object();
		/// Destroys Object.

	Iterator begin()
	{
		return _values.begin();
	}

	ConstIterator begin() const
	{
		return _values.begin();
	}

	Iterator end()
	{
		return _values.end();
	}

	ConstIterator end() const
	{
		return _values.end();
	}

	Dynamic::Var get(const std::string& key) const;
		/// Retrieves a property. An empty value is
		/// returned when the property doesn't exist.

	Array::Ptr getArray(const std::string& key) const;
		/// Returns a SharedPtr to an array when the property
		/// is an array. An empty SharedPtr is returned when
		/// the element doesn't exist or is not an array.

	Object::Ptr getObject(const std::string& key) const;
		/// Returns a SharedPtr to an object when the property
		/// is an object. An empty SharedPtr is returned when
		/// the property doesn't exist or is not an object

	template<typename T>
	T getValue(const std::string& key) const
		/// Retrieves the property with the given name and will
		/// try to convert the value to the given template type.
		/// The convert<T> method of Dynamic is called
		/// which can also throw exceptions for invalid values.
		/// Note: This will not work for an array or an object.
	{
		Dynamic::Var value = get(key);
		return value.convert<T>();
	}

	template<typename T>
	Poco::Nullable<T> getNullableValue(const std::string& key) const
		/// Retrieves the property with the given name and will
		/// try to convert the value to the given template type.
		/// Returns null if isNull.
		/// The convert<T> method of Dynamic is called
		/// which can also throw exceptions for invalid values.
		/// Note: This will not work for an array or an object.
	{
		if (isNull(key))
			return Poco::Nullable<T>();

		Dynamic::Var value = get(key);
		return value.convert<T>();
	}

	void getNames(std::vector<std::string>& names) const;
		/// Returns all property names

	bool has(const std::string& key) const;
		/// Returns true when the given property exists

	bool isArray(const std::string& key) const;
		/// Returns true when the given property contains an array

	bool isArray(ConstIterator& it) const;
		/// Returns true when the given property contains an array

	bool isNull(const std::string& key) const;
		/// Returns true when the given property contains a null value

	bool isObject(const std::string& key) const;
		/// Returns true when the given property contains an object

	bool isObject(ConstIterator& it) const;
		/// Returns true when the given property contains an object

	template<typename T>
	T optValue(const std::string& key, const T& def) const
		/// Returns the value of a property when the property exists
		/// and can be converted to the given type. Otherwise
		/// def will be returned.
	{
		T value = def;
		ValueMap::const_iterator it = _values.find(key);
		if (it != _values.end() && ! it->second.isEmpty() )
		{
			try
			{
				value = it->second.convert<T>();
			}
			catch(...)
			{
				// The default value will be returned
			}
		}
		return value;
	}

	std::size_t size() const;
		/// Returns the number of properties

	void set(const std::string& key, const Dynamic::Var& value);
		/// Sets a new value

	void stringify(std::ostream& out, unsigned int indent = 0, int step = -1) const;
		/// Prints the object to out stream. When indent is 0, the object
		/// will be printed on a single line without indentation.

	void remove(const std::string& key);
		/// Removes the property with the given key

	static Poco::DynamicStruct makeStruct(const Object::Ptr& obj);
		/// Utility function for creation of struct.

	operator const Poco::DynamicStruct& () const;
		/// Cast operator to Poco::DynamiStruct.

	void clear();
		/// Clears the contents of the object. Insertion order 
		/// preservation property is left intact.

private:
	template <typename C>
	void doStringify(const C& container, std::ostream& out, unsigned int indent, unsigned int step) const
	{
		out << '{';

		if (indent > 0) out << std::endl;
		
		typename C::const_iterator it = container.begin();
		typename C::const_iterator end = container.end();
		for (; it != end;)
		{
			for(unsigned int i = 0; i < indent; i++) out << ' ';

			Stringifier::stringify(getKey(it), out);
			out << ((indent > 0) ? " : " : ":");

			Stringifier::stringify(getValue(it), out, indent + step, step);

			if (++it != container.end()) out << ',';

			if (step > 0) out << std::endl;
		}

		if (indent >= step) indent -= step;

		for (unsigned int i = 0; i < indent; i++)
			out << ' ';

		out << '}';
	}

	typedef std::deque<const std::string*> KeyPtrList;
	typedef Poco::DynamicStruct::Ptr       StructPtr;

	const std::string& getKey(ValueMap::const_iterator& it) const;
	const Dynamic::Var& getValue(ValueMap::const_iterator& it) const;
	const std::string& getKey(KeyPtrList::const_iterator& it) const;
	const Dynamic::Var& getValue(KeyPtrList::const_iterator& it) const;

	ValueMap          _values;
	KeyPtrList        _keys;
	bool              _preserveInsOrder;
	mutable StructPtr _pStruct;
};


inline bool Object::has(const std::string& key) const
{
	ValueMap::const_iterator it = _values.find(key);
	return it != _values.end();
}


inline bool Object::isArray(const std::string& key) const
{
	ValueMap::const_iterator it = _values.find(key);
	return isArray(it);
}


inline bool Object::isArray(ConstIterator& it) const
{
	return it != _values.end() && it->second.type() == typeid(Array::Ptr);
}


inline bool Object::isNull(const std::string& key) const
{
	ValueMap::const_iterator it = _values.find(key);
	return it == _values.end() || it->second.isEmpty();
}


inline bool Object::isObject(const std::string& key) const
{
	ValueMap::const_iterator it = _values.find(key);
	return isObject(it);
}


inline bool Object::isObject(ConstIterator& it) const
{
	return it != _values.end() && it->second.type() == typeid(Object::Ptr);
}


inline std::size_t Object::size() const
{
	return static_cast<std::size_t>(_values.size());
}


inline void Object::remove(const std::string& key)
{
	_values.erase(key);
}


inline const std::string& Object::getKey(ValueMap::const_iterator& it) const
{
	return it->first;
}


inline const Dynamic::Var& Object::getValue(ValueMap::const_iterator& it) const
{
	return it->second;
}


inline const Dynamic::Var& Object::getValue(KeyPtrList::const_iterator& it) const
{
	ValueMap::const_iterator itv = _values.find(**it);
	if (itv != _values.end())
		return itv->second;
	else
		throw Poco::NotFoundException();
}


}} // Namespace Poco::JSON


namespace Poco {
namespace Dynamic {


template <>
class VarHolderImpl<JSON::Object::Ptr>: public VarHolder
{
public:
	VarHolderImpl(const JSON::Object::Ptr& val): _val(val)
	{
	}

	~VarHolderImpl()
	{
	}

	const std::type_info& type() const
	{
		return typeid(JSON::Object::Ptr);
	}

	void convert(Int8&) const
	{
		throw BadCastException();
	}

	void convert(Int16&) const
	{
		throw BadCastException();
	}

	void convert(Int32&) const
	{
		throw BadCastException();
	}

	void convert(Int64&) const
	{
		throw BadCastException();
	}

	void convert(UInt8&) const
	{
		throw BadCastException();
	}

	void convert(UInt16&) const
	{
		throw BadCastException();
	}

	void convert(UInt32&) const
	{
		throw BadCastException();
	}

	void convert(UInt64&) const
	{
		throw BadCastException();
	}

	void convert(bool& value) const
	{
		value = !_val.isNull() && _val->size() > 0;
	}

	void convert(float&) const
	{
		throw BadCastException();
	}

	void convert(double&) const
	{
		throw BadCastException();
	}

	void convert(char&) const
	{
		throw BadCastException();
	}

	void convert(std::string& s) const
	{
		std::ostringstream oss;
		_val->stringify(oss, 2);
		s = oss.str();
	}

	void convert(DateTime& /*val*/) const
	{
		//TODO: val = _val;
		throw NotImplementedException("Conversion not implemented: JSON:Object => DateTime");
	}

	void convert(LocalDateTime& /*ldt*/) const
	{
		//TODO: ldt = _val.timestamp();
		throw NotImplementedException("Conversion not implemented: JSON:Object => LocalDateTime");
	}

	void convert(Timestamp& /*ts*/) const
	{
		//TODO: ts = _val.timestamp();
		throw NotImplementedException("Conversion not implemented: JSON:Object => Timestamp");
	}

	VarHolder* clone(Placeholder<VarHolder>* pVarHolder = 0) const
	{
		return cloneHolder(pVarHolder, _val);
	}

	const JSON::Object::Ptr& value() const
	{
		return _val;
	}

	bool isArray() const
	{
		return false;
	}

	bool isInteger() const
	{
		return false;
	}

	bool isSigned() const
	{
		return false;
	}

	bool isNumeric() const
	{
		return false;
	}

	bool isString() const
	{
		return false;
	}

private:
	JSON::Object::Ptr _val;
};


template <>
class VarHolderImpl<JSON::Object>: public VarHolder
{
public:
	VarHolderImpl(const JSON::Object& val): _val(val)
	{
	}

	~VarHolderImpl()
	{
	}

	const std::type_info& type() const
	{
		return typeid(JSON::Object);
	}

	void convert(Int8&) const
	{
		throw BadCastException();
	}

	void convert(Int16&) const
	{
		throw BadCastException();
	}

	void convert(Int32&) const
	{
		throw BadCastException();
	}

	void convert(Int64&) const
	{
		throw BadCastException();
	}

	void convert(UInt8&) const
	{
		throw BadCastException();
	}

	void convert(UInt16&) const
	{
		throw BadCastException();
	}

	void convert(UInt32&) const
	{
		throw BadCastException();
	}

	void convert(UInt64&) const
	{
		throw BadCastException();
	}

	void convert(bool& value) const
	{
		value = _val.size() > 0;
	}

	void convert(float&) const
	{
		throw BadCastException();
	}

	void convert(double&) const
	{
		throw BadCastException();
	}

	void convert(char&) const
	{
		throw BadCastException();
	}

	void convert(std::string& s) const
	{
		std::ostringstream oss;
		_val.stringify(oss, 2);
		s = oss.str();
	}

	void convert(DateTime& /*val*/) const
	{
		//TODO: val = _val;
		throw NotImplementedException("Conversion not implemented: JSON:Object => DateTime");
	}

	void convert(LocalDateTime& /*ldt*/) const
	{
		//TODO: ldt = _val.timestamp();
		throw NotImplementedException("Conversion not implemented: JSON:Object => LocalDateTime");
	}

	void convert(Timestamp& /*ts*/) const
	{
		//TODO: ts = _val.timestamp();
		throw NotImplementedException("Conversion not implemented: JSON:Object => Timestamp");
	}

	VarHolder* clone(Placeholder<VarHolder>* pVarHolder = 0) const
	{
		return cloneHolder(pVarHolder, _val);
	}

	const JSON::Object& value() const
	{
		return _val;
	}

	bool isArray() const
	{
		return false;
	}

	bool isInteger() const
	{
		return false;
	}

	bool isSigned() const
	{
		return false;
	}

	bool isNumeric() const
	{
		return false;
	}

	bool isString() const
	{
		return false;
	}

private:
	JSON::Object _val;
};


}} // namespace Poco::JSON


#endif // JSON_Object_INCLUDED
