//==============================================================================
/*! \file
 * OpenMesh Toolkit for mesh analysis    \n
 * Copyright (c) 2010 by Rostislav Hulik     \n
 *
 * Author:  Rostislav Hulik, rosta.hulik@gmail.com  \n
 * Date:    2010/10/22                          \n
 *
 * This file is part of software developed for support of Rostislav Hulik's dissertation thesis at dcgm-robotics@FIT group.
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this file.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Description:
 * - Extension of OpenMesh SR_store.hh, which adds mds::mod::Channel export/import capabilities
 * - Code is modified from OpenMesh
 */

#ifndef _OM_STORE_RESTORE_H
#define _OM_STORE_RESTORE_H

#include <OpenMesh\Core\IO\SR_store.hh>
#include <MDSTk/Module/mdsChannel.h>


namespace OpenMesh {
namespace IO {

/**
 * Template, which extends binary writer of T objects
 * Extension done for adding support of mds::mod::CChannel
 * @tparam T Datatype for serialization
 */
template < typename T > struct binaryExt : binary<T>
{
	/**
	 * Method must be implemented for each specialized type (storing in the channel)
	 */
	static size_t store( mds::mod::CChannel&, const value_type&, bool)
	{
		X; 
		return 0; 
	}

	/**
	 * Method must be implemented for each specialized type (restoring from the channel)
	 */
	static size_t restore( mds::mod::CChannel&, value_type&, bool)
	{
		X; 
		return 0; 
	}
};

/**
 * Macro for definition of all scalar types (WARNING, types like long are not implemented)
 * @todo Write an override for unisigned long and other types, which are different on 32/64 bit architecture. See OpenMesh sr_binary_spec.hh
 */
#define SIMPLE_BINARY_EXT( T )																		\
	template <> struct binaryExt< T >																\
	{																								\
		typedef T value_type;																		\
		static const bool is_streamable = true;														\
																									\
		static size_t size_of(const value_type&) { return sizeof(value_type); }						\
		static size_t size_of(void) { return sizeof(value_type); }									\
		static size_t store(std::ostream& _os, const value_type& _val, bool _swap=false) {			\
			value_type tmp = _val;																	\
			if (_swap) reverse_byte_order(tmp);														\
			_os.write( (const char*)&tmp, sizeof(value_type) );										\
			return _os.good() ? sizeof(value_type) : 0;												\
		}			     																			\
																									\
		static size_t restore( std::istream& _is, value_type& _val,	bool _swap=false) {				\
			_is.read( (char*)&_val, sizeof(value_type) );											\
			if (_swap) reverse_byte_order(_val);													\
			return _is.good() ? sizeof(value_type) : 0;												\
		}																							\
																									\
		static size_t store( mds::mod::CChannel& _os, const value_type& _val, bool _swap=false) {	\
			value_type tmp = _val;																	\
			if (_swap) reverse_byte_order(tmp);														\
			_os.write( (const char*)&tmp, sizeof(value_type) );										\
			return sizeof(value_type);																\
		}																							\
																									\
		static size_t restore( mds::mod::CChannel& _is, value_type& _val, bool _swap=false) {		\
			_is.read( (char*)&_val, sizeof(value_type) );											\
			if (_swap) reverse_byte_order(_val);													\
			return sizeof(value_type);																\
    }																								\
}

/**
 * BinaryExt specialization for bool datatype
 */
SIMPLE_BINARY_EXT(bool);
/**
 * BinaryExt specialization for float datatype
 */
SIMPLE_BINARY_EXT(float);
/**
 * BinaryExt specialization for double datatype
 */
SIMPLE_BINARY_EXT(double);
/**
 * BinaryExt specialization for long double datatype
 */
SIMPLE_BINARY_EXT(long double);
/**
 * BinaryExt specialization for int8_t datatype
 */
SIMPLE_BINARY_EXT(int8_t);
/**
 * BinaryExt specialization for int16_t datatype
 */
SIMPLE_BINARY_EXT(int16_t);
/**
 * BinaryExt specialization for int32_t datatype
 */
SIMPLE_BINARY_EXT(int32_t);
/**
 * BinaryExt specialization for int64_t datatype
 */
SIMPLE_BINARY_EXT(int64_t);
/**
 * BinaryExt specialization for uint8_t datatype
 */
SIMPLE_BINARY_EXT(uint8_t);
/**
 * BinaryExt specialization for uint16_t datatype
 */
SIMPLE_BINARY_EXT(uint16_t);
/**
 * BinaryExt specialization for uint32_t datatype
 */
SIMPLE_BINARY_EXT(uint32_t);
/**
 * BinaryExt specialization for uint64_t datatype
 */
SIMPLE_BINARY_EXT(uint64_t);

/**
 * Macro for definition of all OpenMesh::VectorT types 
 */
#define VECTORT_BINARY_EXT( T )																		\
	template <> struct binaryExt< T > {																\
		typedef T value_type;																		\
		static const bool is_streamable = true;														\
		static size_t size_of(void) { return sizeof(value_type); }									\
		static size_t size_of(const value_type&) { return size_of(); }								\
		static size_t store( std::ostream& _os, const value_type& _val, bool _swap=false) {         \
			value_type tmp = _val;																	\
			size_t i, b = size_of(_val), N = value_type::size_;										\
			if (_swap) for (i=0; i<N; ++i)															\
			reverse_byte_order( tmp[i] );															\
			_os.write( (const char*)&tmp[0], b );													\
			return _os.good() ? b : 0;																\
		}																							\
																									\
		static size_t restore( std::istream& _is, value_type& _val, bool _swap=false) {             \
			size_t i, N=value_type::size_;															\
			size_t b = N * sizeof(value_type::value_type);											\
			_is.read( (char*)&_val[0], b );															\
			if (_swap) for (i=0; i<N; ++i)															\
			reverse_byte_order( _val[i] );															\
			return _is.good() ? b : 0;																\
		}																							\
																									\
		static size_t store( mds::mod::CChannel& _os, const value_type& _val, bool _swap=false) {	\
			value_type tmp = _val;																	\
			size_t i, b = size_of(_val), N = value_type::size_;										\
			if (_swap) for (i=0; i<N; ++i)															\
			reverse_byte_order( tmp[i] );															\
			_os.write( (const char*)&tmp[0], b );													\
			return b;																				\
		}																							\
																									\
		static size_t restore( mds::mod::CChannel& _is, value_type& _val, bool _swap=false) {       \
			size_t i, N=value_type::size_;															\
			size_t b = N * sizeof(value_type::value_type);											\
			_is.read( (char*)&_val[0], b );															\
			if (_swap) for (i=0; i<N; ++i)															\
			reverse_byte_order( _val[i] );															\
			return b;																				\
		}																							\
	}

/**
 * BinaryExt specialization macro for N dimensional vectors
 */
#define VECTORTS_BINARY_EXT( N )		\
   VECTORT_BINARY_EXT( Vec##N##c  );	\
   VECTORT_BINARY_EXT( Vec##N##uc );	\
   VECTORT_BINARY_EXT( Vec##N##s  );	\
   VECTORT_BINARY_EXT( Vec##N##us );	\
   VECTORT_BINARY_EXT( Vec##N##i  );	\
   VECTORT_BINARY_EXT( Vec##N##ui );	\
   VECTORT_BINARY_EXT( Vec##N##f  );	\
   VECTORT_BINARY_EXT( Vec##N##d  );

/**
 * BinaryExt specialization for 1 dimensional vectors
 */
VECTORTS_BINARY_EXT( 1 );
/**
 * BinaryExt specialization for 2 dimensional vectors
 */
VECTORTS_BINARY_EXT( 2 );
/**
 * BinaryExt specialization for 3 dimensional vectors
 */
VECTORTS_BINARY_EXT( 3 );
/**
 * BinaryExt specialization for 4 dimensional vectors
 */
VECTORTS_BINARY_EXT( 4 );
/**
 * BinaryExt specialization for 6 dimensional vectors
 */
VECTORTS_BINARY_EXT( 6 );




////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * binaryExt specialization for std::string
 */
template <> struct binaryExt< std::string > 
{
	typedef std::string value_type;
	typedef uint16_t    length_t;

	static const bool is_streamable = true;
	/**
	 * @return size of this entity
	 */
	static size_t size_of() { return UnknownSize; }
	/**
	 * @return size of this entity
	 */
	static size_t size_of(const value_type &_v) { return sizeof(length_t) + _v.size(); }

	/**
	 * Method for storing a string into stream
	 * @param _os MDSTk output channel
	 * @param _v Value to store
	 * @param _swap Swap bytes flag
	 * @returns Number of saved bytes
	 */
	static size_t store(mds::mod::CChannel& _os, const value_type& _v, bool _swap=false)
	{
		#if defined(OM_CC_GCC) && (OM_CC_VERSION < 30000)
			if (_v.size() < Utils::NumLimitsT<length_t>::max() )
		#else
			if (_v.size() < std::numeric_limits<length_t>::max() )
		#endif
		{
			length_t len = _v.size();
			if (_swap) reverse_byte_order(len);

			size_t bytes = binaryExt<length_t>::store( _os, len, _swap );
			_os.write( _v.data(), len );
			return len+bytes;
		}
		throw std::runtime_error("Cannot store string longer than 64Kb");
	}

	/**
	 * Method for restoring a string from stream
	 * @param _is MDSTk intput channel
	 * @param _v Value to restore
	 * @param _swap Swap bytes flag
	 * @returns Number of restored bytes
	 */
	static size_t restore(mds::mod::CChannel& _is, value_type& _val, bool _swap=false)
	{
		length_t len;
		size_t   bytes = binaryExt<length_t>::restore( _is, len, _swap );
		if (_swap)
			reverse_byte_order(len);
		_val.resize(len);
		_is.read( const_cast<char*>(_val.data()), len );

		return len+bytes;
	}
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * binaryExt specialization for OpenMesh::Attributes::StatusInfo
 */
template <> struct binaryExt<OpenMesh::Attributes::StatusInfo>
{
	typedef OpenMesh::Attributes::StatusInfo value_type;
	typedef value_type::value_type           status_t;

	static const bool is_streamable = true;
	/**
	 * @return size of this entity
	 */
	static size_t size_of() { return sizeof(status_t); }
	/**
	 * @return size of this entity
	 */
	static size_t size_of(const value_type&) { return size_of(); }
	/**
	 * @return Number of bytes in this entity (entire, all elements)
	 */
	static size_t n_bytes(size_t _n_elem)
	{ 
		return _n_elem*sizeof(status_t); 
	}

	/**
	 * Method for storing a Status info into stream
	 * @param _os MDSTk output channel
	 * @param _v Value to store
	 * @param _swap Swap bytes flag
	 * @returns Number of saved bytes
	 */
	static size_t store(mds::mod::CChannel& _os, const value_type& _v, bool _swap=false)
	{
		status_t v=_v.bits();
		return binaryExt<status_t>::store(_os, v, _swap);
	}

	/**
	 * Method for restoring a string from stream
	 * @param _is MDSTk intput channel
	 * @param _v Value to restore
	 * @param _swap Swap bytes flag
	 * @returns Number of restored bytes
	 */
	static size_t restore(mds::mod::CChannel& _is, value_type& _v, bool _swap=false)
	{
		status_t v;
		size_t   b = binaryExt<status_t>::restore(_is, v, _swap);
		_v.set_bits(v);
		return b;
	}
};

}// namespace IO
}// namespace OpenMesh

#endif