/*! \file pibinarystream.h
 * \ingroup Serialization
 * \~\brief
 * \~english Binary serialization interface
 * \~russian Интерфейс бинарной сериализации
*/
/*
	PIP - Platform Independent Primitives
	Binary serialization interface
	Ivan Pelipenko peri4ko@yandex.ru

	This program 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 program 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 program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIBINARYSTREAM_H
#define PIBINARYSTREAM_H

#include "pimemoryblock.h"
#include "pibitarray.h"
#include "pimap.h"
#include "pivector2d.h"

#define PIP_BINARY_STREAM

#define BINARY_STREAM_FRIEND(T) \
	template<typename P> friend PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const T & v); \
	template<typename P> friend PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s,       T & v);
#define BINARY_STREAM_WRITE(T) \
	template<typename P> inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const T & v)
#define BINARY_STREAM_READ(T) \
	template<typename P> inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s,       T & v)


//! \ingroup Serialization
//! \~\brief
//! \~english Binary serialization interface.
//! \~russian Интерфейс бинарной сериализации.
//! \~\details
//! \~english In your class you should implement this methods:
//! \~russian В производном классе вы должны реализовать следующие методы:
//! \~\code
//! bool binaryStreamAppendImp  (const void * d, size_t s);
//! bool binaryStreamTakeImp    (void * d, size_t s);
//! ssize_t binaryStreamSizeImp () const;
//! \endcode
//! \~english Function binaryStreamSizeImp should return -1 if size unknown.
//! \~russian Функция binaryStreamSizeImp должна возвращать -1 если нет информации о размере.
//! \~english See details \ref iostream.
//! \~russian Подробнее \ref iostream.
template<typename P>
class PIBinaryStream {
public:
	//! \~russian Записать данные
	bool binaryStreamAppend(const void * d, size_t s) {
		if (!static_cast<P*>(this)->binaryStreamAppendImp(d, s)) {
			return false;
			printf("[PIBinaryStream] binaryStreamAppend() error\n");
		}
		return true;
	}
	//! \~russian Прочитать данные
	bool binaryStreamTake(void * d, size_t s) {
		if (!static_cast<P*>(this)->binaryStreamTakeImp(d, s)) {
			return false;
			printf("[PIBinaryStream] binaryStreamTake() error\n");
		}
		return true;
	}

	//! \~russian Узнать оставшийся размер
	//!\~\details
	//!\~russian Возвращает -1 если нет информации о размере
	ssize_t binaryStreamSize() const {
		return static_cast<const P*>(this)->binaryStreamSizeImp();
	}

	//! \~russian Записать данные
	template<typename T>
	void binaryStreamAppend(T v) {binaryStreamAppend(&v, sizeof(v));}

	//! \~russian Прочитать int
	int binaryStreamTakeInt() {
		int r = 0;
		binaryStreamTake(&r, sizeof(r));
		return r;
	}
};


// helper class to detect default operators
template<typename P>
class PIBinaryStreamTrivialRef {
public:
	PIBinaryStreamTrivialRef(PIBinaryStream<P> & s): p(s) {}
	PIBinaryStream<P> & p;
};


template<typename P, typename T> inline PIBinaryStream<P> & operator <<(PIBinaryStreamTrivialRef<P> s, const T & v) {
	s.p << v;
	return s.p;
}
template<typename P, typename T> inline PIBinaryStream<P> & operator >>(PIBinaryStreamTrivialRef<P> s,       T & v) {
	s.p >> v;
	return s.p;
}

template<typename P> inline PIBinaryStream<P> & operator <<(PIBinaryStreamTrivialRef<P> s, const PIMemoryBlock v) {
	s.p << v;
	return s.p;
}
template<typename P> inline PIBinaryStream<P> & operator >>(PIBinaryStreamTrivialRef<P> s,       PIMemoryBlock v) {
	s.p >> v;
	return s.p;
}


// specify types
template<typename P> inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const bool    v) {
	s.binaryStreamAppend((uchar)v);
	return s;
}
template<typename P> inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s,       bool  & v) {
	uchar c;
	s.binaryStreamTake(&c, sizeof(c));
	v = c;
	return s;
}

template<typename P> inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIMemoryBlock v) {
	s.binaryStreamAppend(v.data(), v.size());
	return s;
}
template<typename P> inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s,       PIMemoryBlock v) {
	s.binaryStreamTake(v.data(), v.size());
	return s;
}




// store simple types


template<typename P, typename T,
typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const T & v) {
	//piCout << "<< enum";
	s.binaryStreamAppend((int)v);
	return s;
}


template<typename P, typename T,
typename std::enable_if<!std::is_enum<T>::value, int>::type = 0,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStreamTrivialRef<P> operator <<(PIBinaryStream<P> & s, const T & v) {
	s.binaryStreamAppend(&v, sizeof(v));
	return s;
}


//! \~english Store operator for PIVector of any trivial copyable type
//! \~russian Оператор сохранения для PIVector тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector<T> & v) {
	//piCout << "<< vector trivial default";
	s.binaryStreamAppend((int)v.size());
	s.binaryStreamAppend(v.data(), v.size() * sizeof(T));
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector<T> & v) {
	//piCout << "<< vector trivial custom";
	s.binaryStreamAppend((int)v.size());
	for (size_t i = 0; i < v.size(); ++i) s << v[i];
	return s;
}


//! \~english Store operator for PIDeque of any trivial copyable type
//! \~russian Оператор сохранения для PIDeque тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIDeque<T> & v) {
	//piCout << "<< deque trivial default";
	s.binaryStreamAppend((int)v.size());
	s.binaryStreamAppend(v.data(), v.size() * sizeof(T));
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIDeque<T> & v) {
	//piCout << "<< deque trivial custom";
	s.binaryStreamAppend((int)v.size());
	for (size_t i = 0; i < v.size(); ++i) s << v[i];
	return s;
}


//! \~english Store operator for PIVector2D of any trivial copyable type
//! \~russian Оператор сохранения для PIVector2D тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector2D<T> & v) {
	//piCout << "<< vector2d trivial default";
	s.binaryStreamAppend((int)v.rows());
	s.binaryStreamAppend((int)v.cols());
	s.binaryStreamAppend(v.data(), v.size() * sizeof(T));
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() << std::declval<const T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector2D<T> & v) {
	//piCout << "<< vector2d trivial custom";
	s.binaryStreamAppend((int)v.rows());
	s.binaryStreamAppend((int)v.cols());
	s << v.toPlainVector();
	return s;
}


//! \~english Store operator
//! \~russian Оператор сохранения
template<typename P>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIBitArray & v) {s << v.size_ << v.data_; return s;}


//! \~english Store operator
//! \~russian Оператор сохранения
template<typename P, typename Type0, typename Type1>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIPair<Type0, Type1> & v) {s << v.first << v.second; return s;}




// restore simple types


template<typename P, typename T,
typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, T & v) {
	//piCout << ">> enum";
	v = (T)s.binaryStreamTakeInt();
	return s;
}


template<typename P, typename T,
typename std::enable_if<!std::is_enum<T>::value, int>::type = 0,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStreamTrivialRef<P> operator >>(PIBinaryStream<P> & s, T & v) {
	if (!s.binaryStreamTake(&v, sizeof(v))) {
		printf("error with %s\n", __PIP_TYPENAME__(T));
		assert(false);
	}
	return s;
}


//! \~english Restore operator for PIVector of any trivial copyable type
//! \~russian Оператор извлечения для PIVector тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector<T> & v) {
	//piCout << ">> vector trivial default";
	int sz = s.binaryStreamTakeInt();
	v._resizeRaw(sz);
	if (!s.binaryStreamTake(v.data(), sz * sizeof(T))) {
		printf("error with PIVector<%s>\n", __PIP_TYPENAME__(T));
		assert(false);
	}
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector<T> & v) {
	//piCout << ">> vector trivial custom";
	int sz = s.binaryStreamTakeInt();
	v._resizeRaw(sz);
	for (int i = 0; i < sz; ++i) s >> v[i];
	return s;
}


//! \~english Restore operator for PIDeque of any trivial copyable type
//! \~russian Оператор извлечения для PIDeque тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIDeque<T> & v) {
	//piCout << ">> deque trivial default";
	int sz = s.binaryStreamTakeInt();
	v._resizeRaw(sz);
	if (!s.binaryStreamTake(v.data(), sz * sizeof(T))) {
		printf("error with PIDeque<%s>\n", __PIP_TYPENAME__(T));
		assert(false);
	}
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIDeque<T> & v) {
	//piCout << ">> deque trivial custom";
	int sz = s.binaryStreamTakeInt();
	v._resizeRaw(sz);
	for (int i = 0; i < sz; ++i) s >> v[i];
	return s;
}


//! \~english Restore operator for PIVector2D of any trivial copyable type
//! \~russian Оператор извлечения для PIVector2D тривиальных типов
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if< std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector2D<T> & v) {
	//piCout << ">> vector2d trivial default";
	int r, c;
	r = s.binaryStreamTakeInt();
	c = s.binaryStreamTakeInt();
	v._resizeRaw(r, c);
	if (!s.binaryStreamTake(v.data(), v.size() * sizeof(T))) {
		printf("error with PIVector2D<%s>\n", __PIP_TYPENAME__(T));
		assert(false);
	}
	return s;
}
template<typename P, typename T,
typename std::enable_if< std::is_trivially_copyable<T>::value, int>::type = 0,
typename std::enable_if<!std::is_same<decltype(std::declval<PIBinaryStream<P>&>() >> std::declval<T &>()), PIBinaryStreamTrivialRef<P>>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector2D<T> & v) {
	//piCout << ">> vector2d trivial custom";
	int r, c;
	PIVector<T> tmp;
	r = s.binaryStreamTakeInt();
	c = s.binaryStreamTakeInt();
	s >> tmp;
	v = PIVector2D<T>(r, c, tmp);
	return s;
}


//! \~english Restore operator
//! \~russian Оператор извлечения
template<typename P>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIBitArray & v) {s >> v.size_ >> v.data_; return s;}


//! \~english Restore operator
//! \~russian Оператор извлечения
template<typename P, typename Type0, typename Type1>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIPair<Type0, Type1> & v) {s >> v.first >> v.second; return s;}




// store complex types


//! \~english Store operator for PIVector of any compound type
//! \~russian Оператор сохранения для PIVector сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector<T> & v) {
	//piCout << "<< vector complex";
	s.binaryStreamAppend(int(v.size_s()));
	for (size_t i = 0; i < v.size(); ++i) s << v[i];
	return s;
}


//! \~english Store operator for PIDeque of any compound type
//! \~russian Оператор сохранения для PIDeque сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIDeque<T> & v) {
	//piCout << "<< deque complex";
	s.binaryStreamAppend(int(v.size_s()));
	for (size_t i = 0; i < v.size(); ++i) s << v[i];
	return s;
}


//! \~english Store operator for PIVector2D of any compound type
//! \~russian Оператор сохранения для PIVector2D сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIVector2D<T> & v) {
	//piCout << "<< vector2d complex";
	s.binaryStreamAppend(int(v.rows()));
	s.binaryStreamAppend(int(v.cols()));
	s << v.toPlainVector();
	return s;
}




// restore complex types


//! \~english Restore operator for PIVector of any compound type
//! \~russian Оператор извлечения для PIVector сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector<T> & v) {
	//piCout << ">> vector complex";
	/*if (s.size_s() < 4) {
		printf("error with PIVector<%s>\n", __PIP_TYPENAME__(T));
		assert(s.size_s() >= 4);
	}*/
	v.resize(s.binaryStreamTakeInt());
	for (size_t i = 0; i < v.size(); ++i) s >> v[i];
	return s;
}


//! \~english Restore operator for PIDeque of any compound type
//! \~russian Оператор извлечения для PIDeque сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIDeque<T> & v) {
	//piCout << ">> deque complex";
	/*if (s.size_s() < 4) {
		printf("error with PIDeque<%s>\n", __PIP_TYPENAME__(T));
		assert(s.size_s() >= 4);
	}*/
	v.resize(s.binaryStreamTakeInt());
	for (size_t i = 0; i < v.size(); ++i) s >> v[i];
	return s;
}


//! \~english Restore operator for PIVector2D of any compound type
//! \~russian Оператор извлечения для PIVector2D сложных типов
template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIVector2D<T> & v) {
	//piCout << ">> vector2d complex";
	/*if (s.size_s() < 8) {
		printf("error with PIVecto2Dr<%s>\n", __PIP_TYPENAME__(T));
		assert(s.size_s() >= 8);
	}*/
	int r, c;
	PIVector<T> tmp;
	r = s.binaryStreamTakeInt();
	c = s.binaryStreamTakeInt();
	s >> tmp;
	v = PIVector2D<T>(r, c, tmp);
	return s;
}




// other types


//! \~english Store operator
//! \~russian Оператор сохранения
template <typename P, typename Key, typename T>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const PIMap<Key, T> & v) {
	s.binaryStreamAppend((int)v.pim_index.size_s());
	for (uint i = 0; i < v.size(); ++i) {
		s.binaryStreamAppend((int)v.pim_index[i].index);
		s << v.pim_index[i].key;
	}
	s << v.pim_content;
	return s;
}


//! \~english Restore operator
//! \~russian Оператор извлечения
template <typename P, typename Key, typename T>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, PIMap<Key, T> & v) {
	/*if (s.size_s() < 4) {
		printf("error with PIMap<%s, %s>\n", __PIP_TYPENAME__(Key), __PIP_TYPENAME__(T));
		assert(s.size_s() >= 4);
	}*/
	int sz = s.binaryStreamTakeInt(); v.pim_index.resize(sz);
	int ind = 0;
	for (int i = 0; i < sz; ++i) {
		ind = s.binaryStreamTakeInt();
		s >> v.pim_index[i].key;
		v.pim_index[i].index = ind;
	}
	s >> v.pim_content;
	if (v.pim_content.size_s() != v.pim_index.size_s()) {
		piCout << "Warning: loaded invalid PIMap, clear";
		v.clear();
	}
	return s;
}




// non-defined complex types


template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator <<(PIBinaryStream<P> & s, const T & ) {
	static_assert(std::is_trivially_copyable<T>::value, "[PIBinaryStream] Error: using undeclared operator << for complex type!");
	return s;
}

template<typename P, typename T, typename std::enable_if<!std::is_trivially_copyable<T>::value, int>::type = 0>
inline PIBinaryStream<P> & operator >>(PIBinaryStream<P> & s, T & ) {
	static_assert(std::is_trivially_copyable<T>::value, "[PIBinaryStream] Error: using undeclared operator >> for complex type!");
	return s;
}




#endif
