/*
	AramakiOnline
	Copyright (C) 2008 superbacker

	This program is free software; you can redistribute it and/or modify it under the terms
	of the GNU General Public License as published by the Free Software Foundation;
	either version 3 of the License, or (at your option) any later version.

	This program 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 General Public License for more details.

	You should have received a copy of the GNU General Public License along with this
	program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "Model.h"

Model::Model(const wxString &fileName) {
	parseObject(fileName);
}

Model::~Model() {
	//テクスチャを削除
	for (Textures::iterator i=textures.begin();i!=textures.end();++i) delete *i;
}

void Model::parseObject(const wxString &fileName) {
	//オブジェクトファイルをパース
	wxFile file(fileName.mb_str());
	wxString buffer;
	char *writeBuf;
	wxStringTokenizer lineTokenizer;
	Vertexes vertexes;
	TexturePoints texturePoints;
	Materials materials;
	NormalVectors normalVectors;

	if (!file.IsOpened()) wxLogFatalError(fileName+wxT("の読み込みに失敗"));

	writeBuf=buffer.GetWriteBuf(file.Length());
	file.Read(writeBuf,file.Length());
	buffer.UngetWriteBuf();

	lineTokenizer.SetString(buffer,wxT("\n"));

	displayList.compile();

	while(lineTokenizer.HasMoreTokens()) {
		wxStringTokenizer spaceTokenizer(lineTokenizer.GetNextToken(),wxT(" "));

		//行の最初はデータの種類
		wxString type=spaceTokenizer.GetNextToken();

		if (type=="#") {
			//コメント
			continue;
		} else if (type=="mtllib") {
			//マテリアルライブラリ指定
			parseMaterialLibrary(spaceTokenizer,materials);
		} else if (type=="v") {
			//頂点
			parseVertex(spaceTokenizer,vertexes);
		} else if (type=="vt") {
			//テクスチャ座標
			parseTextureVertex(spaceTokenizer,texturePoints);
		} else if (type=="vn") {
			//法線ベクトル
			parseNormalVector(spaceTokenizer,normalVectors);
		} else if (type=="f") {
			//面
			parseFace(spaceTokenizer,vertexes,texturePoints,normalVectors);
		} else if (type=="usemtl") {
			//マテリアル指定
			parseUseMaterial(spaceTokenizer,materials);
		}
	}

	materials.clear();

	displayList.end();
}

void Model::parseMaterial(const wxString &fileName,Materials &materials) {
	//マテリアルライブラリをパース
	wxFile file(fileName.mb_str());
	wxString buffer;
	char *writeBuf;
	wxStringTokenizer lineTokenizer;
	Material *currentMaterial=NULL;

	if (!file.IsOpened()) wxLogFatalError(fileName+wxT("の読み込みに失敗"));

	writeBuf=buffer.GetWriteBuf(file.Length());
	file.Read(writeBuf,file.Length());
	buffer.UngetWriteBuf();

	lineTokenizer.SetString(buffer,wxT("\n"));

	while(lineTokenizer.HasMoreTokens()) {
		wxStringTokenizer spaceTokenizer(lineTokenizer.GetNextToken(),wxT(" "));

		//行の最初はデータの種類
		wxString type=spaceTokenizer.GetNextToken();

		if (type=="#") {
			//コメント
			continue;
		} else if (type=="newmtl") {
			//新規マテリアル
			currentMaterial=&parseNewMaterial(spaceTokenizer,materials);
		} else if (type=="Ka") {
			//周囲光
			parseAmbient(spaceTokenizer,*currentMaterial);
		} else 	if (type=="Kd") {
			//拡散光
			parseDiffuse(spaceTokenizer,*currentMaterial);
		} else if (type=="Ks") {
			//反射光
			parseSpecular(spaceTokenizer,*currentMaterial);
		} else if (type=="Ns") {
			//反射の強さ
			parseShininess(spaceTokenizer,*currentMaterial);
		} else if (type=="map_Kd") {
			//テクスチャ
			parseTexture(spaceTokenizer,*currentMaterial);
		}
	}
}

void Model::parseMaterialLibrary(wxStringTokenizer &tokenizer,Materials &materials) {
	//mtllib (マテリアルライブラリファイル名)
	materials.clear();
	parseMaterial(tokenizer.GetNextToken(),materials);
}

void Model::parseVertex(wxStringTokenizer &tokenizer,Vertexes &vertexes) {
	//v (頂点X) (頂点Y) (頂点Z)
	Point3d vertex;

	tokenizer.GetNextToken().ToDouble(&vertex.x);
	tokenizer.GetNextToken().ToDouble(&vertex.y);
	tokenizer.GetNextToken().ToDouble(&vertex.z);

	vertexes.push_back(vertex);
}

void Model::parseTextureVertex(wxStringTokenizer &tokenizer,TexturePoints &texturePoints) {
	//vt (テクスチャX) (テクスチャY)
	TexturePoint texturePoint;

	tokenizer.GetNextToken().ToDouble(&texturePoint.x);
	tokenizer.GetNextToken().ToDouble(&texturePoint.y);

	texturePoints.push_back(texturePoint);
}

void Model::parseNormalVector(wxStringTokenizer &tokenizer,NormalVectors &normalVectors) {
	//vn (法線ベクトルX) (法線ベクトルY) (法線ベクトルZ)
	Point3d normalVector;

	tokenizer.GetNextToken().ToDouble(&normalVector.x);
	tokenizer.GetNextToken().ToDouble(&normalVector.y);
	tokenizer.GetNextToken().ToDouble(&normalVector.z);

	normalVectors.push_back(normalVector);
}

void Model::parseFace(wxStringTokenizer &tokenizer,Vertexes &vertexes,TexturePoints &texturePoints,NormalVectors &normalVectors) {
	//f (頂点番号/テクスチャ番号/頂点法線ベクトル番号)*頂点数
	//f (頂点番号/テクスチャ番号)*頂点数
	//f (頂点番号//頂点法線ベクトル番号)*頂点数
	//f 頂点番号*頂点数
	Face face;

	switch(tokenizer.CountTokens()) {
	case 3:
		//三角形
		glBegin(GL_TRIANGLES);
		break;
	case 4:
		//四角形
		glBegin(GL_QUADS);
		break;
	default:
		//多角形
		glBegin(GL_POLYGON);
		break;
	}

	while(tokenizer.HasMoreTokens()) {
		long vertexNum=0,textureNum=0,normalVectorNum=0;
		wxStringTokenizer slashTokenizer(tokenizer.GetNextToken(),wxT("/"));
		Point3d *vertex;
		wxString textureNumString;

		//頂点番号
		slashTokenizer.GetNextToken().ToLong(&vertexNum);
		vertex=&vertexes[vertexNum-1];

		face.vertexes.push_back(*vertex);

		if (slashTokenizer.HasMoreTokens()) {
			//テクスチャ番号
			textureNumString=slashTokenizer.GetNextToken();

			if (!textureNumString.IsEmpty()) {
				textureNumString.ToLong(&textureNum);
				glTexCoord2d(texturePoints[textureNum-1].x,texturePoints[textureNum-1].y);
			}

			if (slashTokenizer.HasMoreTokens()) {
				//法線番号
				slashTokenizer.GetNextToken().ToLong(&normalVectorNum);
				glNormal3d(normalVectors[normalVectorNum-1].x,normalVectors[normalVectorNum-1].y,normalVectors[normalVectorNum-1].z);
			} else {
				//法線がない
				glNormal3d(0,1,0);
			}
		}

		glVertex3d(vertex->x,vertex->y,vertex->z);
	}

	glEnd();

	//面法線を計算しておく
	face.normalVector.normalVector(face.vertexes[0],face.vertexes[1],face.vertexes[2]);

	faces.push_back(face);
}

void Model::parseUseMaterial(wxStringTokenizer &tokenizer,Materials &materials) {
	//usemtl (マテリアル名)
	Material &material=materials[tokenizer.GetNextToken()];

	//マテリアルを反映
	glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,material.ambient);
	glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,material.diffuse);
	glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,material.specular);
	glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,material.shininess);

	if (material.texture) {
		glEnable(GL_TEXTURE_2D);
		material.texture->bind();
	} else {
		glDisable(GL_TEXTURE_2D);
	}

}

Model::Material &Model::parseNewMaterial(wxStringTokenizer &tokenizer,Materials &materials) {
	//存在しないキーを参照すると自動的に作成される
	Material &newMaterial=materials[tokenizer.GetNextToken()];

	newMaterial.texture=NULL;

	return newMaterial;
}

void Model::parseAmbient(wxStringTokenizer &tokenizer,Material &material) {
	//Ka R G B
	double tmp;

	for (int i=0;i<3;++i) {
		tokenizer.GetNextToken().ToDouble(&tmp);
		material.ambient[i]=tmp;
	}

	material.ambient[3]=1.0;
}

void Model::parseDiffuse(wxStringTokenizer &tokenizer,Material &material) {
	//Kd R G B
	double tmp;

	for (int i=0;i<3;++i) {
		tokenizer.GetNextToken().ToDouble(&tmp);
		material.diffuse[i]=tmp;
	}

	material.diffuse[3]=1.0;
}

void Model::parseSpecular(wxStringTokenizer &tokenizer,Material &material) {
	//Ks R G B
	double tmp;

	for (int i=0;i<3;++i) {
		tokenizer.GetNextToken().ToDouble(&tmp);
		material.specular[i]=tmp;
	}

	material.specular[3]=1.0;
}

void Model::parseShininess(wxStringTokenizer &tokenizer,Material &material) {
	//Ns 反射の強さ
	double tmp;

	tokenizer.GetNextToken().ToDouble(&tmp);
	material.shininess=tmp;
}

void Model::parseTexture(wxStringTokenizer &tokenizer,Material &material) {
	//map_Kd ファイル名
	material.texture=new Texture(tokenizer.GetNextToken());
	textures.push_back(material.texture);
}

void Model::draw() const {
	displayList.call();
}

bool Model::collide(const Point3d &lineStart,const Point3d &lineEnd,Point3d &crossPoint,Face &hitFace) const {
	for (Faces::const_iterator i=faces.begin();i!=faces.end();++i) {
		if (i->collide(lineStart,lineEnd,crossPoint)) {
			//面に当たった
			hitFace=*i;

			return true;
		}
	}

	return false;
}
