#include "stdafx.h"
#include "ObjMesh.h"
#include "ObjMeshIO.h"
#include "ObjMaterialReader.h"
#include "IOUtility.h"

#include <vector>
#include <vector>
#include <map>
#include <string>

#include <fstream>
#include <sstream>

#include <C2/util/container_cast.h>
#include <C2/util/string_util.h>

#include <LibGeo/Mesh/BaseMesh.h>
#include <LibGeo/Path.h>
#include <LibGeo/Utility/StringRef.h>

#include <shlwapi.h>

using namespace std;



namespace lib_geo
{


// ファイル読込
bool ObjMeshReader::Load( ObjMesh& mesh , const string& filename )
{
	InitializeWorkBuffer();

	m_LoadWorkDirPath = Path::GetParentDirPath(filename);

	ifstream ifs( filename.c_str() );
	if( !ifs.is_open() )
		return false;

	try
	{
		if( !LoadObjMain( mesh , ifs , true ) )
			return false;
	}
	catch(...)
	{
		return false;
	}

	return true;
}

//! ストリームからの読み込み
//! マテリアルファイルは読み込まない
bool ObjMeshReader::LoadStream( ObjMesh& mesh , istream& ist )
{
	return LoadObjMain( mesh , ist , false );
}

bool ObjMeshReader::LoadObjMain(ObjMesh& mesh, istream& ist, bool LoadMaterialFile)
{
	for (;;)
	{
		if (ist.eof())
			break;

		string s;
		ist >> s;
		if (s.empty())
			continue;

		if (s[0] == 'v')
		{
			if (s.size() == 1)
			{
				ReadVertLine(ist, mesh);
			}
			else if (s[1] == 't')
			{
				ReadUVLine(ist, mesh.m_UVs);
			}
			else if (s[1] == 'n')
			{
				ReadNormLine(ist, mesh.m_Normals);
			}
		}
		else if (s == "f")
		{
			ReadFaceLine(ist, mesh);
		}
		else if (s == "o")
		{
			ReadObjectSplitLine( ist , mesh );
		}
		else if (s == "g")
		{
			ReadGroupSplitLine( ist , mesh );
		}
		else if (s == "usemtl")
		{
			ReadMaterialSelectLine( ist , mesh );
		}
		else if (s == "mtllib")
		{
			if (LoadMaterialFile)
			{
				ReadMaterialGroup( ist , mesh );
			}
			else
			{
				ReadToNextLine(ist);
			}
		}
		else if ( s == "l")
		{
			ReadPolylineLine(ist, mesh.m_Polylines);
		}
		else
		{
			ReadToNextLine(ist);
		}
	}

	if (!mesh.m_Objects.empty())
		SetLastObjectVertNum(mesh);

	return true;
}

void ObjMeshReader::InitializeWorkBuffer(void)
{
	m_MatIdxMap.clear();
	m_PrimaryMatIdx = -1;
	
	m_GroupIdxMap.clear();
	m_PrimaryGroupIdx = 0;

	m_LoadWorkDirPath.clear();
}

void ObjMeshReader::ReadVertLine(istream& ist, ObjMesh& mesh)
{
	lm::vec3f v;

	getline(ist, m_ReadCache);

	std::vector<util::StringRef> refs;
	util::StringRef::ToRefAry(m_ReadCache, refs);
	if (refs.size() < 3)
		return;

	v.x = (float)atof(refs[0].GetBegin());
	v.y = (float)atof(refs[1].GetBegin());
	v.z = (float)atof(refs[2].GetBegin());

	mesh.m_Verts.push_back(v);

	if (refs.size() >= 6)
	{
		if (refs[3].GetBegin()[0] == '#')
			return;

		mesh.m_Col.resize(mesh.m_Verts.size(), lgr::color3b(0, 0, 0));
		lgr::color3f& c= mesh.m_Col.back();

		c.r() = (float)atof(refs[3].GetBegin()) / 255.0f;
		c.g() = (float)atof(refs[4].GetBegin()) / 255.0f;
		c.b() = (float)atof(refs[5].GetBegin()) / 255.0f;
	}
}

void ObjMeshReader::ReadNormLine( istream& ist , vector<lm::vec3f>& normals )
{
	lm::vec3f norm;
	ReadVec(ist, norm);
	normals.push_back(norm);
}

void ObjMeshReader::ReadUVLine( istream& ist , vector<lm::vec2f>& uvs )
{
	string s;
	getline( ist , s );
	istringstream iss( s.c_str() );

	lm::vec2f uv;
	ReadVec(iss, uv);
	uvs.push_back(uv);
}

void ObjMeshReader::ReadFaceLine( istream& ist , ObjMesh& mesh )
{
	vector<ObjFace>& faces = mesh.m_Faces;

	faces.push_back(ObjFace());
	ObjFace& face = faces.back();

	string sl;
	getline(ist, sl);

	std::vector<util::StringRef> vs;
	util::StringRef::ToRefAry(sl, vs, 3);

	for (size_t i = 0; i < vs.size(); ++i)
	{
		size_t slen = vs[i].GetLength();
		char* s = vs[i].GetBegin();
		if (!(s[0] >= '1' && s[0] <= '9') && s[0] != '-')
			break;

		FidSeparator separator;
		separator.toSeparator(s, slen);

		{
			int idx = atoi( separator.strTop[0] );
			if (idx < 0)
				idx = (int)mesh.m_Verts.size() + idx + 1;

			face.m_IdxV.Add(mesh.m_FaceBuf.m_FaceIdxV, idx - 1);
		}

		if (separator.cntVal >= 2 && separator.strTop[1][0] != '\0')
		{
			int idx = atoi(separator.strTop[1]);
			if (idx < 0)
				idx = (int)mesh.m_UVs.size() + idx + 1;

			face.m_IdxUV.Add(mesh.m_FaceBuf.m_FaceIdxUV, idx - 1);
		}

		if (separator.cntVal >= 3)
		{
			int idx = atoi(separator.strTop[2]);
			if (idx < 0)
				idx = (int)mesh.m_Normals.size() + idx + 1;

			face.m_IdxN.Add(mesh.m_FaceBuf.m_FaceIdxN, idx - 1);
		}
	}

	face.m_GroupIdx = m_PrimaryGroupIdx;
	face.m_MatNameIdx = m_PrimaryMatIdx;
}

void ObjMeshReader::ReadPolylineLine( std::istream& ist , std::vector<ObjPolyline>& lines )
{
	lines.push_back(ObjPolyline());
	ObjPolyline& line = lines.back();

	string sl;
	for(;;)
	{
		string s;
		getline( ist , s );
		if(!CheckAndModifyContinueToNextLine(s))
		{
			sl += s;
			break;
		}
		else
		{
			sl += s;
		}
	}

	std::vector<util::StringRef> vs;
	util::StringRef::ToRefAry(sl, vs, 3);

	line.m_IdxV.resize(vs.size());
	for (size_t i = 0; i < vs.size(); ++i)
	{
		char* s = vs[i].GetBegin();
		line.m_IdxV[i] = atoi(s) - 1;
	}

	line.m_GroupIdx = m_PrimaryGroupIdx;
}

bool ObjMeshReader::CheckAndModifyContinueToNextLine(std::string& s) const
{
	size_t last_char = 0;
	for(size_t i = s.size(); i > 0; --i)
	{
		char c = s[i];
		if(c == ' ' || c == '\t')
			continue;

		last_char = i - 1;
		break;
	}

	if(last_char == 0)
		return false;

	if(s[last_char] != '\\')
		return false;

	s.resize(last_char);
	return true;
}

void ObjMeshReader::ReadObjectSplitLine( std::istream& ist , ObjMesh& mesh )
{
	if (!mesh.m_Objects.empty())
		SetLastObjectVertNum(mesh);

	SubObject new_object;
	new_object.m_VertRange.Offset = (int)mesh.m_Verts.size();
	new_object.m_NormRange.Offset = (int)mesh.m_Normals.size();
	new_object.m_UVRange.Offset   = (int)mesh.m_UVs.size();
	new_object.m_FaceRange.Offset = (int)mesh.m_Faces.size();

	GetLineWithTrimed( ist , new_object.m_Name );

	mesh.m_Objects.push_back( new_object );
}

void ObjMeshReader::ReadGroupSplitLine( istream& ist , ObjMesh& mesh )
{
	string grp_name;
	GetLineWithTrimed( ist , grp_name );

	map<string, int>::iterator found = m_GroupIdxMap.find(grp_name);
	if( found != m_GroupIdxMap.end() )
	{
		m_PrimaryGroupIdx = found->second;
	}
	else
	{
		int new_idx = static_cast<int>( mesh.m_Groups.size() );
		mesh.m_Groups.push_back( ObjGroupInfo() );
		mesh.m_Groups.back().m_Name = grp_name;

		m_GroupIdxMap.insert( pair<string, int>( grp_name , new_idx ) );
		m_PrimaryGroupIdx = new_idx;
	}
}

void ObjMeshReader::ReadMaterialSelectLine( istream& ist , ObjMesh& mesh )
{
	string mat_name;
	GetLineWithTrimed( ist , mat_name );
	
	map<string, int>::iterator found = m_MatIdxMap.find(mat_name);
	if( found != m_MatIdxMap.end() )
	{
		m_PrimaryMatIdx = found->second;
	}
	else
	{
		int new_idx = static_cast<int>( m_MatIdxMap.size() );
		m_MatIdxMap[mat_name] = new_idx;
		m_PrimaryMatIdx = new_idx;

		mesh.m_MaterialNames.push_back( mat_name );
	}
}

void ObjMeshReader::ReadMaterialGroup( istream& ist , ObjMesh& mesh )
{
	string mat_filename;
	GetLineWithTrimed( ist , mat_filename );

	ObjMaterialGroup materials;
	ObjMaterialReader reader(m_LoadWorkDirPath);
	if(!reader.ReadMaterialFile(mat_filename, materials))
		return;

	mesh.m_MaterialGroups.push_back( materials );
}

void ObjMeshReader::SetLastObjectVertNum(ObjMesh& mesh)
{
	SubObject& last_obj = mesh.m_Objects.back();
	last_obj.m_VertRange.NumElems = (int)mesh.m_Verts.size()   - last_obj.m_VertRange.Offset;
	last_obj.m_NormRange.NumElems = (int)mesh.m_Normals.size() - last_obj.m_NormRange.Offset;
	last_obj.m_UVRange.NumElems   = (int)mesh.m_UVs.size()     - last_obj.m_UVRange.Offset;
	last_obj.m_FaceRange.NumElems = (int)mesh.m_Faces.size()   - last_obj.m_FaceRange.Offset;
}


}
