#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 "../BaseMesh.h"
#include "../../Path.h"

#include "../../Utility/StringRef.h"

#include <shlwapi.h>

using namespace std;



namespace lib_geo
{


void ObjReaderCache::SwapToMesh(ObjMesh& mesh)
{
	mesh.m_Positions.swap(verts);
	mesh.m_Normals.swap(normals);
	mesh.m_UVs.swap(uvs);
	mesh.m_Faces.swap(faces);
	mesh.m_Polylines.swap(lines);
}


// ファイル読込
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;
	
	if( !LoadObjMain( mesh , ifs , true ) )
		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 )
{
	ObjReaderCache cache;

	for(;;)
	{
		if( ist.eof() )
			break;

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

		if( s[0] == 'v' )
		{
			if(s.size() == 1)
			{
				ReadVertLine( ist , cache.verts );
			}
			else if( s[1] == 't' )
			{
				ReadUVLine( ist , cache.uvs );
			}
			else if( s[1] == 'n' )
			{
				ReadNormLine( ist , cache.normals );
			}
		}
		else if( s == "f" )
		{
			ReadFaceLine( ist , cache );
		}
		else if( s == "o" )
		{
			ReadObjectSplitLine( ist , mesh , cache );
		}
		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 , cache.lines );
		}
		else
		{
			ReadToNextLine( ist );
		}
	}

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

	cache.SwapToMesh(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 , vector<lm::vec3f>& verts )
{
	lm::vec3f vert;
	ist >> vert.x >> vert.y >> vert.z;
	verts.push_back( vert );
}

void ObjMeshReader::ReadNormLine( istream& ist , vector<lm::vec3f>& normals )
{
	lm::vec3f norm;
	ist >> norm.x >> norm.y >> norm.z;
	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;
	iss >> uv.x >> uv.y;
	uvs.push_back(uv);
}

void ToRefAry(std::string& s, std::vector<util::StringRef>& vs)
{
	vs.reserve(3);

	bool IsInWord = false;
	char* str_begin = NULL;
	for(size_t i = 0; i < s.size(); ++i)
	{
		char* c = &s[i];

		bool IsNoChar = ((*c) == ' ' || (*c) == '\t' || (*c) == '\n');
		if(IsInWord)
		{
			if(IsNoChar)
			{
				vs.push_back(util::StringRef(str_begin, c));
				IsInWord = false;
			}
		}
		else
		{
			if(!IsNoChar)
			{
				str_begin = c;
				IsInWord = true;
			}
		}
	}

	if(IsInWord)
	{
		char* str_last = &s[s.size()-1];
		++str_last;
		vs.push_back(util::StringRef(str_begin, str_last));
	}
}

void ObjMeshReader::ReadFaceLine( istream& ist , ObjReaderCache& cache )
{
	vector<ObjFace>& faces = cache.faces;

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

	string sl;
	getline( ist , sl );
	sl.push_back(' ');

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

	face.m_IdxV.resize(vs.size());
	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;

		int cnt_val = 1;
		char* str_top[3];
		str_top[0] = &s[0];
		for( size_t j = 0 ; j < slen ; j++ )
		{
			if( s[j] == '/' )
			{
				str_top[cnt_val] = &s[j+1];
				s[j] = '\0';
				cnt_val++;
			}
		}

		{
			int idx = atoi( str_top[0] );
			if (idx < 0)
				idx = (int)cache.verts.size() + idx + 1;

			face.m_IdxV[i] = idx - 1;
		}

		if( cnt_val >= 2 && str_top[1][0] != '\0' )
		{
			int idx = atoi( str_top[1] );
			if (idx < 0)
				idx = (int)cache.uvs.size() + idx + 1;

			face.m_IdxUV.push_back( idx - 1 );
		}

		if( cnt_val >= 3 )
		{
			int idx = atoi( str_top[2] );
			if (idx < 0)
				idx = (int)cache.normals.size() + idx + 1;

			face.m_IdxN.push_back( 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;
		}
	}
	sl.push_back(' ');

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

	line.m_IdxV.resize(vs.size());
	for(size_t i = 0; i < vs.size(); ++i)
	{
		char* s = vs[i].GetBegin();
		char* e = vs[i].GetEnd();
		(*e) = '\0';
		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 , const ObjReaderCache& cache )
{
	if (!mesh.m_Objects.empty())
		SetLastObjectVertNum(mesh, cache);

	SubObject new_object;
	new_object.m_VertRange.Offset = (int)cache.verts.size();
	new_object.m_NormRange.Offset = (int)cache.normals.size();
	new_object.m_UVRange.Offset   = (int)cache.uvs.size();
	new_object.m_FaceRange.Offset = (int)cache.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 );
}


bool ObjMeshWriter::Save( const ObjMesh& mesh , const string& filename ) const
{
	ofstream ofs( filename.c_str() );
	if( !ofs.is_open() )
		return false;

	return SaveObjMain( mesh , ofs );
}

bool ObjMeshWriter::SaveStream( const ObjMesh& mesh , ostream& ost ) const
{
	return SaveObjMain( mesh , ost );
}

bool ObjMeshWriter::SaveObjMain( const ObjMesh& mesh , ostream& ost ) const
{
	// 頂点バッファ
	for(size_t i = 0; i < mesh.m_Positions.size(); ++i)
	{
		const lm::vec3f& position = mesh.m_Positions[i];
		ost << "v " << position.x << " " << position.y << " " << position.z << endl;
	}

	// UVバッファ
	for(size_t i = 0; i < mesh.m_UVs.size(); ++i)
	{
		const lm::vec2f& uv = mesh.m_UVs[i];
		ost << "vt " << uv.x << " " << uv.y << endl;
	}

	// 法線バッファ
	for(size_t i = 0; i < mesh.m_Normals.size(); ++i)
	{
		const lm::vec3f& normal = mesh.m_Normals[i];
		ost << "vn " << normal.x << " " << normal.y << " " << normal.z << endl;
	}

	for(size_t i = 0 ; i < mesh.m_Faces.size(); ++i)
	{
		const ObjFace& face = mesh.m_Faces[i];
		if( face.m_IdxV.empty() )
			continue;

		bool has_uv = !face.m_IdxUV.empty();
		bool has_n  = !face.m_IdxN.empty();
		
		ost << "f";
		vector<int>::const_iterator vid  = face.m_IdxV.begin();
		vector<int>::const_iterator nid  = face.m_IdxN.begin();
		vector<int>::const_iterator uvid = face.m_IdxUV.begin();
		for( ; vid != face.m_IdxV.end() ; ++vid )
		{
			if( !has_uv && !has_n )
				ost << " " << (*vid)+1;
			else if( has_uv )
				ost << " " << (*vid)+1 << "/" << (*uvid)+1;
			else if( has_n )
				ost << " " << (*vid)+1 << "//" << (*nid)+1;
			else
				ost << " " << (*vid)+1 << "/" << (*uvid)+1 << "/" << (*nid)+1;

			if( has_n )
				++nid;
			if( has_uv )
				++uvid;
		}
		ost << endl;
	}

	return true;
}


void ObjMeshReader::SetLastObjectVertNum(ObjMesh& mesh, const ObjReaderCache& cache)
{
	SubObject& last_obj = mesh.m_Objects.back();
	last_obj.m_VertRange.NumElems = (int)cache.verts.size()   - last_obj.m_VertRange.Offset;
	last_obj.m_NormRange.NumElems = (int)cache.normals.size() - last_obj.m_NormRange.Offset;
	last_obj.m_UVRange.NumElems   = (int)cache.uvs.size()     - last_obj.m_UVRange.Offset;
	last_obj.m_FaceRange.NumElems = (int)cache.faces.size()   - last_obj.m_FaceRange.Offset;
}


}
