//==============================================================================
/*! \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/23                          \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:
 * - Class derrived from OpenMesh OM reader
 * - Extends functionality for export into MDSTk Channel
 * - Most of code is modified OpenMesh
 */

#include <OMToolkit\IO\OMReaderExt.h>

namespace OpenMesh {
namespace IO {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Stream Reader for MDSTk input in binary format
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read(mds::mod::CChannel& _is, BaseImporter& _bi, Options& _opt)
{
	// check whether importer can give us an OpenMesh BaseKernel
	if (!_bi.kernel()) return false;

	_opt += Options::Binary; // only binary format supported!

	if (!_is.isConnected())
	{
		omerr() << "[OMReader] : cannot read from stream " << std::endl;
		return false;
	}

	// Pass stream to read method, remember result
	bool result = read_binary(_is, _bi, _opt);

	if(result) _opt += Options::Binary;

	return result;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Read ascii representation of this format
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @return True, if reading was successful
// @todo Write this method - for now, as in OpenMesh, ascii method is not functional
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_ascii( mds::mod::CChannel& /* _is */, BaseImporter& /* _bi */,	Options& /* _opt */) const
{
  return false;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Read binary representation of this format
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary( mds::mod::CChannel& _is, BaseImporter& _bi, Options& _opt) const
{
	bool swap = _opt.check(Options::Swap) || (Endian::local() == Endian::MSB);

	// intialize byte counter
	bytes_ = 0;

	bytes_ += restore( _is, header_, swap );

	size_t data_bytes;

	while( _is.isConnected() )
	{
		bytes_ += restore( _is, chunk_header_, swap );

		if ( !_is.isConnected() ) break;

		// Is this a named property restore the name
		if ( chunk_header_.name_ )
		{
			OMFormat::Chunk::PropertyName pn;
			bytes_ += restore( _is, property_name_, swap );
		}

		// Read in the property data. If it is an anonymous or unknown named
		// property, then skip data.
		data_bytes = bytes_;
		switch( chunk_header_.entity_ )
		{
			case OMFormat::Chunk::Entity_Vertex:
				if (!read_binary_vertex_chunk( _is, _bi, _opt, swap )) return false;
				break;
			case OMFormat::Chunk::Entity_Face:
				if (!read_binary_face_chunk( _is, _bi, _opt, swap )) return false;
				break;
			case OMFormat::Chunk::Entity_Edge:
				if (!read_binary_edge_chunk( _is, _bi, _opt, swap )) return false;
				break;
			case OMFormat::Chunk::Entity_Halfedge:
				if (!read_binary_halfedge_chunk( _is, _bi, _opt, swap )) return false;
				break;
			case OMFormat::Chunk::Entity_Mesh:
				if (!read_binary_mesh_chunk( _is, _bi, _opt, swap )) return false;
				break;
			default:
				return false;
		}
		data_bytes = bytes_ - data_bytes;
	}
  // File was successfully parsed.
  return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts vertex information
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary_vertex_chunk(mds::mod::CChannel &_is, BaseImporter &_bi, Options &_opt, bool _swap) const
{
	using OMFormat::Chunk;

	//assert( chunk_header_.entity_ == Chunk::Entity_Vertex );

	OpenMesh::Vec3f  v3f;
	OpenMesh::Vec2f  v2f;
	OpenMesh::Vec3uc v3uc; // rgb

	OMFormat::Chunk::PropertyName custom_prop;

	size_t vidx=0;
	switch (chunk_header_.type_)
	{
		case Chunk::Type_Pos:
			//assert( OMFormat::dimensions(chunk_header_) == size_t(OpenMesh::Vec3f::dim()) );

			for (; vidx < header_.n_vertices_ && _is.isConnected(); ++vidx)
			{
				bytes_ += vector_restore( _is, v3f, _swap );
				_bi.add_vertex(v3f);
			}
			break;

		case Chunk::Type_Normal:
			//assert( OMFormat::dimensions(chunk_header_) == size_t(OpenMesh::Vec3f::dim()) );

			_opt += Options::VertexNormal;
			for (; vidx < header_.n_vertices_ && _is.isConnected(); ++vidx)
			{
				bytes_ += vector_restore( _is, v3f, _swap );
				_bi.set_normal(VertexHandle(vidx), v3f);
			}
			break;

		case Chunk::Type_Texcoord:
			//assert( OMFormat::dimensions(chunk_header_) == size_t(OpenMesh::Vec2f::dim()) );

			_opt += Options::VertexTexCoord;
			for (; vidx < header_.n_vertices_ && _is.isConnected(); ++vidx)
			{
				bytes_ += vector_restore( _is, v2f, _swap );
				_bi.set_texcoord(VertexHandle(vidx), v2f);
			}
			break;

		case Chunk::Type_Color:
			//assert( OMFormat::dimensions(chunk_header_) == 3 );

			_opt += Options::VertexColor;
			for (; vidx < header_.n_vertices_ && _is.isConnected(); ++vidx)
			{
				bytes_ += vector_restore( _is, v3uc, _swap );
				_bi.set_color( VertexHandle(vidx), v3uc );
			}
			break;

		case Chunk::Type_Custom:
			bytes_ += restore_binary_custom_data( _is, _bi.kernel()->_get_vprop( property_name_ ), header_.n_vertices_, _swap );

			vidx = header_.n_vertices_;
			break;

		default: // skip unknown chunks
		{
			omerr() << "Unknown chunk type ignored!\n";
			size_t size_of = header_.n_vertices_ * OMFormat::vector_size(chunk_header_);
			_is.skip( size_of );
			bytes_ += size_of;
		}
	}

	// all chunk data has been read..?!
	return vidx == header_.n_vertices_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts face information
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary_face_chunk(mds::mod::CChannel &_is, BaseImporter &_bi, Options &_opt, bool _swap ) const
{
	using OMFormat::Chunk;

	//assert( chunk_header_.entity_ == Chunk::Entity_Face );

	size_t fidx=0;
	OpenMesh::Vec3f  v3f;  // normal
	OpenMesh::Vec3uc v3uc; // rgb

	switch( chunk_header_.type_ )
	{
		case Chunk::Type_Topology:
		{
			BaseImporter::VHandles  vhandles;
			size_t                  nV = 0;
			size_t                  vidx = 0;

			switch( header_.mesh_ )
			{
				case 'T': nV = 3; break;
				case 'Q': nV = 4; break;
			}

			for (; fidx < header_.n_faces_; ++fidx)
			{
 				if ( header_.mesh_ == 'P' )
 					bytes_ += restore( _is, nV, Chunk::Integer_16, _swap );

				vhandles.clear();
				for (size_t j=0; j<nV; ++j)
				{
					bytes_ += restore( _is, vidx, Chunk::Integer_Size(chunk_header_.bits_), _swap );

					vhandles.push_back(VertexHandle(vidx));	
				}

				_bi.add_face(vhandles);
			}
		}
		break;

		case Chunk::Type_Normal:
			//assert( OMFormat::dimensions(chunk_header_) == size_t(OpenMesh::Vec3f::dim()) );

			_opt += Options::FaceNormal;
			for (; fidx < header_.n_faces_ && _is.isConnected(); ++fidx)
			{
				bytes_ += vector_restore( _is, v3f, _swap );
				_bi.set_normal(FaceHandle(fidx), v3f);
			}
			break;

		case Chunk::Type_Color:
			//assert( OMFormat::dimensions(chunk_header_) == 3 );

			_opt += Options::FaceColor;
			for (; fidx < header_.n_faces_ && _is.isConnected(); ++fidx)
			{
				bytes_ += vector_restore( _is, v3uc, _swap );
				_bi.set_color( FaceHandle(fidx), v3uc );
			}
			break;

		case Chunk::Type_Custom:

			bytes_ += restore_binary_custom_data( _is, _bi.kernel()->_get_fprop( property_name_ ), header_.n_faces_, _swap );
			fidx = header_.n_faces_;
			break;

		default: // skip unknown chunks
		{
			omerr() << "Unknown chunk type ignore!\n";
			size_t size_of = OMFormat::chunk_data_size(header_, chunk_header_);
			_is.skip( size_of );
			bytes_ += size_of;
		}
	}
	return fidx == header_.n_faces_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts edge information
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary_edge_chunk( mds::mod::CChannel &_is, BaseImporter &_bi, Options &, bool _swap ) const
{
	using OMFormat::Chunk;
	//assert( chunk_header_.entity_ == Chunk::Entity_Edge );

	size_t b=bytes_;

	switch( chunk_header_.type_ )
	{
	    case Chunk::Type_Custom:
			bytes_ += restore_binary_custom_data( _is, _bi.kernel()->_get_eprop( property_name_ ), header_.n_edges_, _swap );
			break;

		default:
			// skip unknown type
			size_t size_of = OMFormat::chunk_data_size(header_, chunk_header_);
			_is.skip( size_of );
			bytes_ += size_of;
	}

	return b < bytes_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts half edge information
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary_halfedge_chunk( mds::mod::CChannel &_is, BaseImporter &_bi, Options &, bool _swap ) const
{
	using OMFormat::Chunk;

	//assert( chunk_header_.entity_ == Chunk::Entity_Halfedge );

	size_t b = bytes_;

	switch( chunk_header_.type_ )
	{
		case Chunk::Type_Custom:
			bytes_ += restore_binary_custom_data( _is, _bi.kernel()->_get_hprop( property_name_ ), 2*header_.n_edges_, _swap );
			break;

		default:
			// skip unknown chunk
			omerr() << "Unknown chunk type ignored!\n";
			size_t size_of = OMFormat::chunk_data_size(header_, chunk_header_);
			_is.skip( size_of );
			bytes_ += size_of;
	}
	return b < bytes_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts mesh information
// @param _is Input MDSTk channel
// @param _bi Base importer used for reading
// @param _opt Options for writing
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool OMReaderExt::read_binary_mesh_chunk(mds::mod::CChannel &_is, BaseImporter &_bi, Options &, bool _swap ) const
{
	using OMFormat::Chunk;

	//assert( chunk_header_.entity_ == Chunk::Entity_Mesh );

	size_t b = bytes_;

	switch( chunk_header_.type_ )
	{
	    case Chunk::Type_Custom:
			bytes_ += restore_binary_custom_data( _is, _bi.kernel()->_get_mprop( property_name_ ), 1, _swap );
			break;

		default:
			// skip unknown chunk
			size_t size_of = OMFormat::chunk_data_size(header_, chunk_header_);
			_is.skip( size_of );
			bytes_ += size_of;
	}

	return b < bytes_;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Method reads binary channel and extracts custom data
// @param _is Input MDSTk channel
// @param _bp Base property used for reading
// @param _n_elem Number of elements to read
// @param _swap Swap bytes flag
// @return True, if reading was successful
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t OMReaderExt::restore_binary_custom_data( mds::mod::CChannel& _is, BaseProperty* _bp, size_t _n_elem, bool _swap) const
{
	//assert( !_bp || (_bp->name() == property_name_) );

	using OMFormat::Chunk;

	size_t              bytes = 0;
	Chunk::esize_t      block_size;
	Chunk::PropertyName custom_prop;

	bytes += binaryExt<Chunk::esize_t>::restore( _is, block_size, _swap );


	if ( _bp )
	{
		#if defined(OM_DEBUG)
		size_t b;
		#endif
		size_t n_bytes = _bp->size_of( _n_elem );

		if ( ((n_bytes == BaseProperty::UnknownSize) || (n_bytes == block_size)) &&
			(_bp->element_size() == BaseProperty::UnknownSize ||
			(_n_elem * _bp->element_size() == block_size) ) )
		{
			// get real size
			int size_chunk = 0;
			_is.read((char*)&size_chunk, sizeof(int));

			std::stringstream stream(std::ios_base::binary | std::ios_base::in | std::ios_base::out);
			char *byteField = new char[size_chunk];
			_is.read(byteField, size_chunk);
			stream.write(byteField, size_chunk);

		delete byteField;
			#if defined(OM_DEBUG)
				bytes += (b=_bp->restore( stream, _swap ));
			#else
				bytes += _bp->restore( stream, _swap );
			#endif
			
			

			#if defined(OM_DEBUG)
				//assert( block_size == b );
			#endif
			
			//assert( block_size == _bp->size_of() );
			block_size = 0;
		}
		else
		{
			omerr() << "Warning! Property " << _bp->name() << " not loaded: " << "Mismatching data sizes!n";
		}
	}

	if (block_size)
	{
		_is.skip( block_size );
		bytes  += block_size;
	}

	return bytes;
}



} // namespace IO
} // namespace OpenMesh