/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmSDKAPI.h"

//golaem core includes
#include <glmAssert.h>
#include <glmLog.h>
#include <glmTrianglePartition.h>
#include <glmStaticArray.h>
#include <glmTriangle3.h>
#include <glmAngle.h>
#include <glmPODArrayOfArray.h>

//stl includes
#include <fstream>
#include "glmList.h"
#include "glmMap.h"
#include "glmSet.h"
#include "glmDeque.h"
#include <iostream>

namespace glm
{
	/** 
	* \brief Classe de gestion d'un fichier de sortie de données au format Raw. Se base sur la sérialisation des objets
	*
	* \ingroup Utils
	*
	* \author Golaem <dev@golaem.com>
	*/
	class GLM_SDK_API GseOStream 
	{
	public:
		/** 
		* \brief Construct from a given file.
		*/
		GseOStream(const glm::GlmString & fileName);

		/** 
		* \brief Construct from a given output stream
		*/
		GseOStream(std::ostream & stream);

		/**
		* \brief Destructor.
		*/
		~GseOStream() ;

		/**
		* \brief Flush the stream.
		*/
		void flush();

		/** 
		* \brief Write raw data in the stream.
		*/
		template <class T>
		void write(const T & value);

		/** 
		* \brief Write an inspectable instance in the stream.
		*/
		template <class T>
		void writeInspectable(const T& value);

		std::ostream* getOStream() { return _stream; }

	private:
		/**
		* \brief Forbidden copy constructor.
		*/
		GseOStream(const GseOStream & );

		/**
		* \brief Forbidden assignment operator.
		*/
		GseOStream & operator=(const GseOStream & );

		std::ostream * _stream ;
		bool _delete ;
	} ;

	/**
	* \name output operators
	* \ingroup streams
	*/
	//@{
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Uuid& value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const bool & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, float value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const double & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const long double & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, int const & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, unsigned int const & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, char const & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, unsigned char const & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::GlmString & value);

	template <class T1, class T2>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const std::pair<T1,T2> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const IndexTriplet<T> & value);

	template <class T1, class T2>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::GlmMap<T1,T2> & value);

	template <class T, unsigned int size, bool empty>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const StaticArray<T,size,empty> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::Array<T> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::PODArray<T> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::PODArrayOfArray<T> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::GlmDeque<T> & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, glm::GlmList<T> const & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const glm::GlmSet<T> & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Vector2 & value);
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Vector3 & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, Angle value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Segment2 & value);
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Segment3 & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const Triangle3 & value);

	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const TrianglePartition & value);

	template <class T>
	GLM_SDK_API GseOStream & operator<<(GseOStream & rawFile, const T& value);
	//@}

	//*****************************************************************************
	// GseOStream inline definitions
	//*****************************************************************************

	//-----------------------------------------------------------------------------
	// write
	//-----------------------------------------------------------------------------
	template <class T>
	void GseOStream::write(const T & value)
	{
		_stream->write((char*)(&value), sizeof(T)) ;
	}

	//-----------------------------------------------------------------------------
	// write (IInspectable)
	//-----------------------------------------------------------------------------
	template <class T>
	void GseOStream::writeInspectable(const T& value)
	{
		BinaryStream binaryStream;
		binaryStream.pack(value);
		(*_stream) << binaryStream.getStream().rdbuf();
	}

	//*****************************************************************************
	// operator>> inline definitions
	//*****************************************************************************

	//-----------------------------------------------------------------------------
	// std::pair
	//-----------------------------------------------------------------------------
	template <class T1, class T2>
	inline GseOStream & operator<<(GseOStream & rawFile, const std::pair<T1,T2> & value)
	{
		rawFile<<value.first ;
		rawFile<<value.second ;
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// IndexTriplet
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const IndexTriplet<T> & value)
	{
		rawFile<<value[0];
		rawFile<<value[1];
		rawFile<<value[2];
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::GlmMap
	//-----------------------------------------------------------------------------
	template <class T1, class T2>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::GlmMap<T1,T2> & value)
	{
		unsigned int size = (unsigned int)value.size() ;
		rawFile<<size ;
		for(typename glm::GlmMap<T1, T2>::const_iterator it=value.begin() ; it!=value.end() ; ++it)
		{
			rawFile<<it->first ;
			rawFile<<it->second ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// StaticArray
	//-----------------------------------------------------------------------------
	template <class T, unsigned int size, bool empty>
	inline GseOStream & operator<<(GseOStream & rawFile, const StaticArray<T,size,empty> & value)
	{
		rawFile<<value.size();
		for(unsigned int cpt=0 ; cpt<value.size() ; cpt++)
		{
			rawFile<<value[cpt] ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::Array
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::Array<T> & value)
	{
		unsigned int size = (unsigned int)value.size() ;
		rawFile<<size ;
		for(unsigned int cpt=0 ; cpt<size ; cpt++)
		{
			rawFile<<value[cpt] ;
		}
		return rawFile ;
	}
	
	//-----------------------------------------------------------------------------
	// glm::GlmDeque
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::GlmDeque<T> & value)
	{
		unsigned int size = (unsigned int)value.size() ;
		rawFile<<size ;
		for(unsigned int cpt=0 ; cpt<size ; cpt++)
		{
			rawFile<<value[cpt] ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::GlmList
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::GlmList<T> & value)
	{
		unsigned int size = (unsigned int)value.size() ;
		rawFile<<size ;
		//for(int cpt=0 ; cpt<size ; cpt++)
		for(typename glm::GlmList<T>::const_iterator it=value.begin() ; it!=value.end() ; ++it)
		{
			rawFile<<(*it) ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::GlmSet
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::GlmSet<T> & value)
	{
		unsigned int size = (unsigned int)value.size() ;
		rawFile<<size ;
		for(typename glm::GlmSet<T>::const_iterator it=value.begin(), end=value.end() ; it!=end ; ++it)
		{
			rawFile<<(*it) ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::PODArray
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::PODArray<T> & value)
	{
		rawFile<<(unsigned int)value.size() ;
		for(unsigned int i = 0 ; i < value.size() ; ++i)
		{
			rawFile<<value[i];
		}
		return rawFile ;
	}	

	//-----------------------------------------------------------------------------
	// glm::PODArrayOfArray
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream & operator<<(GseOStream & rawFile, const glm::PODArrayOfArray<T> & value)
	{
		const unsigned int arrayCount = static_cast<unsigned int>(value.size_arrays());
		rawFile << arrayCount;

		for(unsigned int arrayIndex = 0 ; arrayIndex < arrayCount ; ++arrayIndex)
		{
			const unsigned int itemCount = static_cast<unsigned int>(value.size_items(arrayIndex));
			rawFile << itemCount;

			for(unsigned int itemIndex = 0 ; itemIndex < itemCount ; ++itemIndex)
			{
				const T& item = value.at(arrayIndex, itemIndex);
				rawFile << item;
			}
		}

		return rawFile;
	}

	//-----------------------------------------------------------------------------
	// IInspectable
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseOStream& operator<<(GseOStream& rawFile, const T& value)
	{
		rawFile.writeInspectable(value);
		return rawFile;
	}
}
