///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_SERIALIZATION_CLASSES_H
#define CPPCMS_SERIALIZATION_CLASSES_H

#include <cppcms/defs.h>
#include <booster/copy_ptr.h>
#include <string>
#include <booster/backtrace.h>

#include <booster/traits/enable_if.h>
#include <booster/traits/is_base_of.h>

namespace cppcms {

	#ifdef CPPCMS_DOXYGEN_DOCS
	///
	/// \brief Special traits class that describes how to serialize various 
	/// objects that are not defived from serializable_base.
	///
	template<typename Object>
	struct archive_traits
	{
		///
		/// Save object to archive function
		/// 
		static void save(Object const &d,archive &a);
		///
		/// Load object from archive function
		///
		static void load(Object &d,archive &a);
	};
	
	#else
	 
	template<typename Object,typename Enable = void>
	struct archive_traits;
	#endif


	///
	/// \brief Error thrown in case of serialization error
	///
	class archive_error : public booster::runtime_error {
	public:
		archive_error(std::string const &e) : booster::runtime_error("cppcms::archive_error: " + e)
		{
		}
	};


	///
	/// \brief Class that represents a binary archive that can be stored in persistent storage or
	/// transfered.
	/// 
	class CPPCMS_API archive {
	public:

		///
		/// Reserve some memory before we write actual data
		///
		void reserve(size_t size);

		///
		/// Write a chunk of size len to archive
		///
		void write_chunk(void const *begin,size_t len);

		///
		/// Read a chunk of size len from archive
		///
		void read_chunk(void *begin,size_t len);
		
		///
		/// Get the size of the next chunk that can be read
		///
		size_t next_chunk_size();

		///
		/// Get if we got to the end of archive while reading
		///
		bool eof();


		///
		/// Read next chunk as std::string 
		///
		std::string read_chunk_as_string();

		///
		/// Operations mode on archive
		///
		typedef enum {
			save_to_archive,
			load_from_archive
		} mode_type; 

		///
		/// Set IO mode, resets pointer 
		///
		void mode(mode_type m);

		///
		/// Get IO mode
		///
		mode_type mode();

		///
		/// Reset IO pointer
		///
		void reset();

		///
		/// Get serialized object memory
		///
		std::string str();

		///
		/// Set serialized object memory, sets mode to load_from_archive
		///
		void str(std::string const &str);

		///
		/// Create new archive, by default in save_to_archive mode
		///
		archive();

		///
		/// Destructor
		///
		~archive();

		///
		/// Copy archive (avoid it)
		///
		archive(archive const &);

		///
		/// Assign archive (avoid it)
		///
		archive const &operator=(archive const &);

	private:
		std::string buffer_;
		size_t ptr_;
		mode_type mode_;	

		struct _data;
		booster::copy_ptr<_data> d;

	};

	///
	/// Operator that performs load or store \a object operations
	/// on archive \a 
	///
	template<typename Archivable>
	archive & operator &(archive &a,Archivable &object)
	{
		if(a.mode() == archive::save_to_archive)
			archive_traits<Archivable>::save(object,a);
		else
			archive_traits<Archivable>::load(object,a);
		return a;
	}

	///
	/// Operator that saves an object to the archive
	///
	template<typename Archivable>
	archive & operator <<(archive &a,Archivable const &object)
	{
		archive_traits<Archivable>::save(object,a);
		return a;
	}

	///
	/// Operator that loads an object from the archive
	///
	template<typename Archivable>
	archive & operator >>(archive &a,Archivable &object)
	{
		archive_traits<Archivable>::load(object,a);
		return a;
	}

	
	
	
	///
	/// \brief Base abstract class for object that can be serialized into std::string
	///
	class serializable_base {
	public:
		///
		/// Load object from archive
		///
		virtual void load(archive &serialized_object) = 0;
		///
		/// Save object to archive
		///
		virtual void save(archive &serialized_object) const = 0;

		virtual ~serializable_base()
		{
		}
	};

	///
	/// \brief Abstract class for serialization object
	/// 
	class serializable : public serializable_base {
	public:

		///
		/// Abstract function that should be implemented for correct serialization
		/// of an object, it allows implementing only one function for load and save instead of two.
		///
		/// For example:
		///
		/// \code
		///  
		///  struct persone : public serializable {
		///    double age;
		///    std::string name;
		///    std::vector<std::string> kids_names;
		///
		///    void serialize(archive &a) 
		///    {
		///         a & age & name & kids_names;
		///    }
		///  };
		/// \endcode
		///
		 
		virtual void serialize(archive &a) = 0;
		///
		/// Calls serialize member functions
		///
		virtual void load(archive &a)
		{
			serialize(a);
		}
		///
		/// Const-casts and calls serialize member function
		///
		virtual void save(archive &a) const
		{
			const_cast<serializable *>(this)->serialize(a);
		}
	};


	#ifdef CPPCMS_DOXYGEN_DOCS
	
	///
	/// \brief This is the traits class for serialization traits.
	///
	/// It allows user to use
	/// arbitrary serialization libraries or use its own serialization rules,
	/// for example you can use boost::serialization.
	///
	/// For this purpose user should specialize the serialization_traits struct for his type.
	///
	/// For example: We want to allow a serialization of a point class:
	///
	/// \code
	///
	//// namespace cppcms {
	///    template<>
	///    struct serialization_traits<point> {
	///  
	///      void load(std::string const &serialized_object,point &real_object)
	///      {
	///	   std::stringstream ss(serialized_object);
	///	   ss >> real_object.x >> real_object.y;
	///	 }
	///	 void save(point const &real_object,std::string &serialized_object)
	///	 {
	///	   std::stringstream ss;
	///	   ss << real_object.x << ' ' << real_object.y;
	///	   serialized_object = ss.str();
	///	 }
	///    };
	///  } // cppcms
	///
	/// \endcode
	///

	template<typename Object>
	struct serialization_traits {
		///
		/// Load \a real_object from the \a serialized_object representation (std::string)
		///
		static void load(std::string const &serialized_object,Object &real_object);

		///
		/// Save \a real_object to the \a serialized_object representation (std::string)
		///
		static void save(Object const &real_object,std::string &serialized_object);
	};
	
	#else

	template<typename Object,typename Enable = void>
	struct serialization_traits;

	///
	/// \brief Traits for serializable objects - converts object to and from string
	/// using archive class
	///
	template<typename D>
	struct serialization_traits<D,typename booster::enable_if<booster::is_base_of<serializable_base,D> >::type> {
		///
		/// Convert string to object
		///
		static void load(std::string const &serialized_object,serializable_base &real_object)
		{
			archive a;
			a.str(serialized_object);
			real_object.load(a);
		}
		///
		/// Convert object to string
		///
		static void save(serializable_base const &real_object,std::string &serialized_object)
		{
			archive a;
			real_object.save(a);
			serialized_object = a.str();
		}
	};


	#endif


} /// cppcms
#endif
