#include "stdafx.h"
#include "PLYMesh.h"

#include "../BaseMesh.h"

#include <fstream>
#include <sstream>


namespace lib_geo
{
namespace ply
{


int ToInt(const std::string& str)
{
	std::istringstream is(str);
	int i;
	is >> i;
	return i;
}

void ToBigEndian(int& i)
{
	char* c = (char*)&i;
	std::swap(c[0], c[3]);
	std::swap(c[1], c[2]);
}

void ToBigEndian(float& f)
{
	char* c = (char*)&f;
	std::swap(c[0], c[3]);
	std::swap(c[1], c[2]);
}

void ToBigEndian(lm::vec3f& v)
{
	ToBigEndian(v.x);
	ToBigEndian(v.y);
	ToBigEndian(v.z);
}


void PlyMesh::Clear(void)
{
	m_Header.Clear();
	m_Verts.clear();
	m_Normals.clear();
	m_Faces.clear();
}


bool PlyMesh::Load( std::istream& ist )
{
	Clear();

	if(!ReadHeader(ist))
		return false;

	if(m_Header.m_FormatType == "ascii 1.0")
		return ReadAscii(ist);
	else if(m_Header.m_FormatType == "binary_little_endian 1.0")
		return ReadBinary(ist, true);
	else if(m_Header.m_FormatType == "binary_big_endian 1.0")
		return ReadBinary(ist, false);

	return false;
}

bool PlyMesh::Load( const std::string& i_Filename )
{
	std::ifstream ifs( i_Filename.c_str() , std::ios::binary );
	if( !ifs.is_open() )
		return false;

	return Load( ifs );
}


bool PlyMesh::ReadHeader(std::istream& ist)
{
	std::string s;

	// check header
	if(!GetNextLine(ist, s))
		return false;

	if(s != "ply")
		return false;

	bool IsInVertProp = false;
	int VertPropCount = 0;

	for(;;)
	{
		if(!GetNextLine(ist, s))
			return false;

		if(CheckHeader(s, "end_header"))
			return true;

		if(IsInVertProp)
		{
			if(CheckHeader(s, "property "))
			{
				std::string type = s.substr(s.find_last_of(" ") + 1);

				if(type == "x")
					m_Header.m_VertPropIdx_X = VertPropCount;
				else if(type == "y")
					m_Header.m_VertPropIdx_Y = VertPropCount;
				else if(type == "z")
					m_Header.m_VertPropIdx_Z = VertPropCount;
				else if(type == "nx")
					m_Header.m_VertPropIdx_nX = VertPropCount;
				else if(type == "ny")
					m_Header.m_VertPropIdx_nY = VertPropCount;
				else if(type == "nz")
					m_Header.m_VertPropIdx_nZ = VertPropCount;

				VertPropCount++;
				continue;
			}

			IsInVertProp = false;
		}

		if(CheckHeader(s, "format"))
		{
			m_Header.m_FormatType = s.substr(strlen("format "));
		}
		else if(CheckHeader(s, "comment"))
		{
			m_Header.m_Comments.push_back(s.substr(strlen("comment ")));
		}
		else if(CheckHeader(s, "element vertex"))
		{
			m_Header.m_NumVerts = ToInt(s.substr(strlen("element vertex ")));
			IsInVertProp = true;
		}
		else if(CheckHeader(s, "element face"))
		{
			m_Header.m_NumFaces = ToInt(s.substr(strlen("element face ")));
		}
	}

	return false;
}

bool PlyMesh::CheckHeader(const std::string& line, const char* header) const
{
	if(line == header)
		return true;

	for(size_t i = 0; i < line.length(); ++i)
	{
		if(header[i] == '\0')
			return true;

		if(line[i] != header[i])
			return false;
	}

	return false;
}


bool PlyMesh::ReadBinary( std::istream& ist , bool is_little_endian)
{
	bool has_norm = m_Header.HasNormal();

	m_Verts.resize(m_Header.m_NumVerts);
	if(has_norm)
		m_Normals.resize(m_Header.m_NumVerts);

	for(size_t i = 0; i < m_Header.m_NumVerts; ++i)
	{
		ist.read((char*)m_Verts[i].v(), sizeof(float) * 3);

		if(has_norm)
			ist.read((char*)m_Normals[i].v(), sizeof(float) * 3);

		if(!is_little_endian)
			ToBigEndian(m_Verts[i]);
	}

	m_Faces.resize(m_Header.m_NumFaces);
	for(size_t i = 0; i < m_Header.m_NumFaces; ++i)
	{
		unsigned char nf;
		ist.read((char*)&nf, sizeof(unsigned char));

		m_Faces[i].m_Vid.resize((int)nf);
		for(int j = 0; j < (int)nf; ++j)
		{
			ist.read((char*)&m_Faces[i].m_Vid[j], sizeof(int));
			if(!is_little_endian)
				ToBigEndian(m_Faces[i].m_Vid[j]);
		}
	}

	return true;
}


bool PlyMesh::ReadAscii( std::istream& ist )
{
	m_Verts.resize(m_Header.m_NumVerts);
	if(m_Header.HasNormal())
		m_Normals.resize(m_Header.m_NumVerts);

	int max_vprop = m_Header.GetMaxPropIdx();

	for(size_t i = 0; i < m_Header.m_NumVerts; ++i)
	{
		std::string s;
		std::getline(ist, s);

		std::istringstream is(s);

		for(int j = 0; j < max_vprop; ++j)
		{
			float f;
			is >> f;

			if(j == m_Header.m_VertPropIdx_X)
			{
				m_Verts[i].x = f;
			}
			else if(j == m_Header.m_VertPropIdx_Y)
			{
				m_Verts[i].y = f;
			}
			else if(j == m_Header.m_VertPropIdx_Z)
			{
				m_Verts[i].z = f;
			}
			else if(j == m_Header.m_VertPropIdx_nX)
			{
				m_Normals[i].x = f;
			}
			else if(j == m_Header.m_VertPropIdx_nY)
			{
				m_Normals[i].y = f;
			}
			else if(j == m_Header.m_VertPropIdx_nZ)
			{
				m_Normals[i].z = f;
			}
		}
	}

	m_Faces.resize(m_Header.m_NumFaces);
	for(size_t i = 0; i < m_Header.m_NumFaces; ++i)
	{
		int num_v;
		ist >> num_v;
		m_Faces[i].m_Vid.resize(num_v);
		for(int j = 0; j < num_v; ++j)
		{
			ist >> m_Faces[i].m_Vid[j];
		}
	}

	return true;
}

bool PlyMesh::GetNextLine(std::istream& ist, std::string& s) const
{
	while(!ist.eof())
	{
		std::getline(ist, s);
		if(!s.empty())
			return true;
	}

	return false;
}


bool PlyMesh::Save( std::ostream& ost ) const
{
	return false;
}

bool PlyMesh::Save( const std::string& i_Filename ) const
{
	std::ofstream ofs( i_Filename.c_str() , std::ios::binary );
	if( !ofs.is_open() )
		return false;

	return Save( ofs );
}

bool PlyMesh::ConvertToBaseMesh( BaseMesh& o_mesh ) const
{
	o_mesh.Clear();

	o_mesh.m_Verts = m_Verts;

	o_mesh.m_Faces.resize(m_Faces.size());
	for(size_t i = 0; i < m_Faces.size(); ++i)
	{
		o_mesh.m_Faces[i].m_VertIds = m_Faces[i].m_Vid;
	}

	return true;
}


}
}
