/**************************************************************************
 Copyright:
      (C) 2008 - 2012  Alexander Shaduri <ashaduri 'at' gmail.com>

 License:

 This software is provided 'as-is', without any express or implied
 warranty.  In no event will the authors be held liable for any damages
 arising from the use of this software.

 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:

 1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 3. This notice may not be removed or altered from any source distribution.
***************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup rmn
/// \weakgroup rmn
/// @{

#ifndef RMN_RESOURCE_DATA_ANY_H
#define RMN_RESOURCE_DATA_ANY_H

#include <string>
#include <iosfwd>  // std::ostream

#include "hz/exceptions.h"  // THROW_FATAL
#include "hz/any_type.h"
#include "hz/hz_config.h"  // DISABLE_RTTI, RMN_TYPE_TRACKING (global_macros.h)

#include "resource_data_types.h"
#include "resource_exception.h"


/**
\file
Any-type data for resource_node
*/


namespace rmn {



/// Helper class for operator\<\< with std::ostream.
template<class T>
struct ResourceDataAnyDumper {
	ResourceDataAnyDumper(T* o) : obj(o) { }

	/// Dump resource data into \c os
	void dump(std::ostream& os) const
	{
		os << obj->data_.to_stream();
	}

	T* obj;  ///< ResourceDataAny object
};



/// Output the data into \c os
template <class T>
inline std::ostream& operator<<(std::ostream& os, const ResourceDataAnyDumper<T>& dumper)
{
	dumper.dump(os);
	return os;
}




/// Resource data which can hold variables of any type.
template<class LockingPolicy>
class ResourceDataAny {

	typedef ResourceDataAny<LockingPolicy> self_type;  ///< Self type

	public:

		/// Constructor
		ResourceDataAny()
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			: type_(T_EMPTY)
#endif
		{ }


		/// Copy data from \c src node
		template<class T>
		bool copy_data_from(const T& src)
		{
			if (!src)
				return false;

			data_ = src->data_;
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			type_ = src->get_type();
#endif
			return true;
		}


		/// Check whether data is empty
		bool data_is_empty() const
		{
			return data_.empty();
		}


		/// Clear the data, making it empty
		void clear_data()
		{
			data_.clear();
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			type_ = T_EMPTY;
#endif
		}


		/// Set data of any type
		template<typename T>
		inline bool set_data(T data)
		{
			data_ = data;
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			type_ = node_data_type_by_real<T>::type;
#endif
			return true;
		}


		/// const char* -\> std::string specialization
		inline bool set_data(const char* data)
		{
			data_ = std::string(data);
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			type_ = node_data_type_by_real<std::string>::type;
#endif
			return true;
		}


		/// \fn bool data_is_type() const
		/// Check whether data is of type \c T.
		/// This function is available only if either RTTI or type tracking is enabled.
#if !(defined DISABLE_RTTI && DISABLE_RTTI)
		template<typename T>
		inline bool data_is_type() const
		{
			// template is needed for gcc 3.3
			return data_.template is_type<T>();  // won't work without RTTI! If empty, reacts to void only.
		}
#elif defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
		template<typename T>
		inline bool data_is_type() const
		{
			return node_data_type_by_real<T>::type == type_;
		}
#endif



#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
		/// Check whether data is of type \c type.
		/// This function is available only if type tracking is enabled.
		inline bool data_is_type(node_data_type type) const
		{
			return type == type_;
		}

		/// Get data type.
		/// This function is available only if type tracking is enabled.
		inline node_data_type get_type() const
		{
			return type_;
		}
#endif


		/// Get data of type \c T.
		/// \return false if casting failed, or if it's empty or invalid type.
		template<typename T>
		inline bool get_data(T& put_it_here) const
		{
#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
			if (node_data_type_by_real<T>::type != type_)
				return false;
#endif
			return data_.get(put_it_here);  // returns false if empty or invalid type
		}


		/// Return a copy of data of type \c T.
		/// \throw rmn::empty_data_retrieval Data is empty
		/// \throw rmn::type_mismatch Type mismatch
		template<typename T>
		T get_data() const
		{
			if (data_.empty())
				THROW_FATAL(empty_data_retrieval());

#if defined DISABLE_RTTI && defined RMN_TYPE_TRACKING
			if (node_data_type_by_real<T>::type != type_)
				THROW_FATAL(type_mismatch());
#endif
			try {
				// template is needed for gcc 3.3
				return data_.template get<T>();  // won't work if empty or invalid type
			}
			catch (hz::bad_any_cast& e) {  // convert any_type exception to rmn exception.
				THROW_CUSTOM_BAD_CAST(type_mismatch, data_.type(), typeid(T));
			}
		}



		/// Similar to get_data(), but with looser conversion - can convert between
		/// C++ built-in types and std::string. Uses hz::any_convert<>.
		/// \return false if casting failed, or empty or invalid type.
		template<typename T>
		inline bool convert_data(T& put_it_here) const
		{
			return data_.convert(put_it_here);
		}


		/// Similar to get_data(), but with looser conversion - can convert between
		/// C++ built-in types and std::string. Uses hz::any_convert<>.
		/// \throw rmn::empty_data_retrieval Data is empty
		/// \throw rmn::type_mismatch Type mismatch
		template<typename T>
		T convert_data() const
		{
			if (data_.empty())
				THROW_FATAL(empty_data_retrieval());

#if defined DISABLE_RTTI && defined RMN_TYPE_TRACKING
			node_data_type to = node_data_type_by_real<T>::type;

			// any T_ type is ok except these:
			if (type_ == T_VOIDPTR || type_ == T_UNKNOWN
					|| to == T_VOIDPTR || to == T_UNKNOWN) {
				THROW_FATAL(type_convert_error());
			}
#endif
			try {
				// Note: This throws only if RTTI is enabled.
				// template is needed for gcc 3.3
				return data_.template convert<T>();  // won't work if empty or invalid type
			}
			catch (hz::bad_any_cast& e) {
				THROW_CUSTOM_BAD_CAST(type_convert_error, data_.type(), typeid(T));
			}
		}



		template<class T>
		friend struct ResourceDataAnyDumper;


		/// Return a helper object that can be dumped into ostream.
		inline ResourceDataAnyDumper<const self_type> dump_data_to_stream() const
		{
			return ResourceDataAnyDumper<const self_type>(this);
		}



	private:

		hz::any_type data_;  ///< The data

#if defined RMN_TYPE_TRACKING && RMN_TYPE_TRACKING
		node_data_type type_;  ///< Type of the data
#endif

};






} // namespace







#endif

/// @}
