/***************************************************************************
*                                                                          *
*  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 <glmAngle.h>
#include <glmTriangle3.h>
#include <glmPODArrayOfArray.h>

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

namespace glm
{
	/** 
	* \brief Definition of a binary input stream for the gse (golaem spatial environment) file format.
	*
	* \ingroup Utils
	*
	* \author Golaem <dev@golaem.com>
	*/
	class GLM_SDK_API GseIStream 
	{
	public:
		/** 
		* \brief Construct from a given file.
		*/
		GseIStream(const glm::GlmString & fileName);

		/** 
		* \brief Construct from a given input stream
		*/
		GseIStream(std::istream & stream);

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

		/**
		* \brief Check if the stream is valid.
		*/
		bool isValid() const;

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

		/** 
		* \brief Read inspectable data in the stream.
		*/
		template <class T>
		void readInspectable(T& value); 

		/** 
		* \brief Check if the end of the stream is reached.
		*/
		bool eof();

		std::istream* getIStream() { return _stream; }

	private:

		/**
		* \brief Forbidden copy constructor.
		*/
		GseIStream(const GseIStream &);

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

		std::istream * _stream ;

		bool _delete ;
	} ;

	/**
	* \name input operators
	* \ingroup streams
	*/
	//@{
	glm::GseIStream & operator>>(glm::GseIStream & rawFile, Uuid& value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, bool & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, float & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, double & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, long double & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, int & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, unsigned int & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, char & value);

	glm::GseIStream & operator>>(glm::GseIStream & rawFile, unsigned char & value);

	GseIStream & operator>>(glm::GseIStream & rawFile, glm::GlmString & value);

	template <class T1, class T2>
	GseIStream & operator>>(glm::GseIStream & rawFile, std::pair<T1,T2> & value);

	template <class T>
	GseIStream & operator>>(GseIStream & rawFile, IndexTriplet<T> & value);

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

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

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

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

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

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

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

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

	GseIStream & operator>>(GseIStream & rawFile, Vector2 & value);
	GseIStream & operator>>(GseIStream & rawFile, Vector3 & value);
	
	GseIStream & operator>>(GseIStream & rawFile, Angle & value);

	GseIStream & operator>>(GseIStream & rawFile, Segment2 & value);
	GseIStream & operator>>(GseIStream & rawFile, Segment3 & value);

	GseIStream & operator>>(GseIStream & rawFile, Triangle3 & value);

	GseIStream & operator>>(GseIStream & rawFile, TrianglePartition & value);

	template <class T>
	GseIStream & operator>>(glm::GseIStream & rawFile, T& value);
	//@}

	//*****************************************************************************
	// GseIStream inline definitions
	//*****************************************************************************

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

	//-----------------------------------------------------------------------------
	// read IInspectable.
	//-----------------------------------------------------------------------------
	template <class T>
	void GseIStream::readInspectable(T& value)
	{
		BinaryStream binaryStream;
		binaryStream.getStream().str("");
		binaryStream.getStream().clear();
		std::istream::pos_type position = _stream->tellg();
		binaryStream.getStream() << _stream->rdbuf();

		//1st Pass
		binaryStream.setFirstPass(true);
		binaryStream.getStream().clear();
		binaryStream.getStream().seekg(0);
		binaryStream.read(value);

		//2nd Pass
		binaryStream.setFirstPass(false);
		binaryStream.getStream().clear();
		binaryStream.getStream().seekg(0);
		binaryStream.read(value);

		position += binaryStream.getStream().tellg();

		_stream->seekg(position);
	}

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

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

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

	//-----------------------------------------------------------------------------
	// glm::GlmMap
	//-----------------------------------------------------------------------------
	template <class T1, class T2>
	inline GseIStream & operator>>(GseIStream & rawFile, glm::GlmMap<T1,T2> & value)
	{
		unsigned int size ;
		rawFile>>size ;
		for(unsigned int cpt=0 ; cpt<size ; cpt++)
		{
			T1 tmp ;
			rawFile>>tmp ;
			rawFile>>value[tmp] ;
		}
		return rawFile ;
	}

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

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

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

	//-----------------------------------------------------------------------------
	// glm::PODArrayOfArray
	//-----------------------------------------------------------------------------
	template <class T>
	GseIStream & operator>>(GseIStream & rawFile, glm::PODArrayOfArray<T> & value)
	{
		unsigned int arrayCount;
		rawFile >> arrayCount;

		//pre-pass reading all arrays size value to compute total amount of items
		size_t itemCountAll = 0;
		
		std::istream* istream = rawFile.getIStream();
		std::istream::pos_type positionStart = istream->tellg();

		std::stringstream prepassStream(std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
		prepassStream << istream->rdbuf();

		for(unsigned int arrayIndex = 0 ; arrayIndex < arrayCount ; ++arrayIndex)
		{
			unsigned int itemCount;
			prepassStream.read(reinterpret_cast<char*>(&itemCount), sizeof(unsigned int));

			itemCountAll += itemCount;
			prepassStream.ignore(itemCount * sizeof(T));
		}

		//set stream back to start
		istream->seekg(positionStart);

		//actual pass to read items value
		value.reserve(arrayCount, itemCountAll);

		for(unsigned int arrayIndex = 0 ; arrayIndex < arrayCount ; ++arrayIndex)
		{
			unsigned int itemCount;
			rawFile >> itemCount;

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

		return rawFile ;
	}

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

	//-----------------------------------------------------------------------------
	// glm::GlmList
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseIStream & operator>>(GseIStream & rawFile, glm::GlmList<T> & value)
	{
		unsigned int size ;
		rawFile>>size ;
		for(unsigned int cpt=0 ; cpt<size ; cpt++)
		{
			value.push_back(T()) ;
			rawFile>>value.back() ;
		}
		return rawFile ;
	}

	//-----------------------------------------------------------------------------
	// glm::GlmSet
	//-----------------------------------------------------------------------------
	template <class T>
	inline GseIStream & operator>>(GseIStream & rawFile, glm::GlmSet<T> & value)
	{
		unsigned int size ;
		rawFile>>size ;
		for(unsigned int cpt=0 ; cpt<size ; ++cpt)
		{
			T temp ;
			rawFile>>temp ;
			value.insert(temp) ;
		}
		return rawFile ;
	}
}
