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

#include <LibGeo/Mesh/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_Col.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_VPI.vX = VertPropCount;
				else if (type == "y")
					m_Header.m_VPI.vY = VertPropCount;
				else if (type == "z")
					m_Header.m_VPI.vZ = VertPropCount;
				else if (type == "nx")
					m_Header.m_VPI.nX = VertPropCount;
				else if (type == "ny")
					m_Header.m_VPI.nY = VertPropCount;
				else if (type == "nz")
					m_Header.m_VPI.nZ = VertPropCount;
				else if (type == "red")
					m_Header.m_VPI.cR = VertPropCount;
				else if (type == "green")
					m_Header.m_VPI.cG = VertPropCount;
				else if (type == "blue")
					m_Header.m_VPI.cB = VertPropCount;
				else if (type == "alpha")
					m_Header.m_VPI.cA = 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 ")));
		}
	}
}

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 hasNorm = m_Header.HasNormal();
	bool hasCol = m_Header.HasCol();

	m_Verts.resize(m_Header.m_NumVerts);
	if (hasNorm)
		m_Normals.resize(m_Header.m_NumVerts);
	if (hasCol)
		m_Col.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 (!is_little_endian)
			ToBigEndian(m_Verts[i]);

		if (hasNorm)
		{
			ist.read((char*)m_Normals[i].v(), sizeof(float) * 3);
			if (!is_little_endian)
				ToBigEndian(m_Normals[i]);
		}

		if (hasCol)
		{
			int nc = m_Header.NumColorElems();
			ist.read((char*)m_Col[i].v(), sizeof(char) * nc);
		}
	}

	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);
	if (m_Header.HasCol())
		m_Col.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)
		{
			if (j == m_Header.m_VPI.vX)
				is >> m_Verts[i].x;
			else if (j == m_Header.m_VPI.vY)
				is >> m_Verts[i].y;
			else if (j == m_Header.m_VPI.vZ)
				is >> m_Verts[i].z;
			else if (j == m_Header.m_VPI.nX)
				is >> m_Normals[i].x;
			else if (j == m_Header.m_VPI.nY)
				is >> m_Normals[i].y;
			else if (j == m_Header.m_VPI.nZ)
				is >> m_Normals[i].z;
			else if (j == m_Header.m_VPI.cR)
				m_Col[i].x = ReadColorElem(is);
			else if (j == m_Header.m_VPI.cG)
				m_Col[i].y = ReadColorElem(is);
			else if (j == m_Header.m_VPI.cB)
				m_Col[i].z = ReadColorElem(is);
			else if (j == m_Header.m_VPI.cA)
				m_Col[i].w = ReadColorElem(is);
		}
	}

	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;
}

unsigned char PlyMesh::ReadColorElem(std::istream& is)
{
	int i;
	is >> i;
	return (unsigned char)i;
}

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_Col.resize(m_Col.size());
	for (size_t i = 0; i < m_Col.size(); ++i)
	{
		o_mesh.m_Col[i].x = (float)m_Col[i].x / 255.0f;
		o_mesh.m_Col[i].y = (float)m_Col[i].y / 255.0f;
		o_mesh.m_Col[i].z = (float)m_Col[i].z / 255.0f;
	}

	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;
}


}
}
