/*
    QGLView
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "loader_ase.h"


void LoaderASE::initASEMesh(GLObjectBase * o) {
	QVector<GLfloat> &vertices(o->VBO().vertices()), &normals(o->VBO().normals()), &uvs(o->VBO().texcoords());
	QVector<Vector3d> &points(o->points), &puvws(o->puvws), &fnormals(o->normals);
	QVector<Vector3i> & faces(o->faces);
	Vector3d pos = Vector3d(o->pos());
	bool has_uv = !puvws.isEmpty(), has_norms = !fnormals.isEmpty();
	Vector3i cf;
	Vector3d v0, v1, v2, cn0, cn1, cn2;
	int ni = 0;
	for (int i = 0; i < points.size(); ++i)
		points[i] -= pos;
	if (!has_norms) {
		fnormals.resize(faces.size() * 3);
		for (int i = 0; i < faces.size(); ++i) {
			cf           = faces[i];
			v0           = points[cf.p0];
			v1           = points[cf.p1];
			v2           = points[cf.p2];
			cn0          = ((v1 - v0) * (v2 - v0)).normalized();
			fnormals[ni] = cn0;
			++ni;
			fnormals[ni] = cn0;
			++ni;
			fnormals[ni] = cn0;
			++ni;
		}
	}
	int fcnt = faces.size() * 3;
	vertices.resize(fcnt * 3);
	normals.resize(vertices.size());
	if (has_uv) uvs.resize(fcnt * 2);
	int ind = 0, induv = 0;
	qDebug() << "init ase" << faces.size() << "faces";
	ni = 0;
	for (int i = 0; i < faces.size(); ++i) {
		cf  = faces[i];
		v0  = points[cf.p0];
		v1  = points[cf.p1];
		v2  = points[cf.p2];
		cn0 = fnormals[ni];
		++ni;
		cn1 = fnormals[ni];
		++ni;
		cn2 = fnormals[ni];
		++ni;
		vertices[ind] = v0.x;
		normals[ind]  = cn0.x;
		++ind;
		vertices[ind] = v0.y;
		normals[ind]  = cn0.y;
		++ind;
		vertices[ind] = v0.z;
		normals[ind]  = cn0.z;
		++ind;
		vertices[ind] = v1.x;
		normals[ind]  = cn1.x;
		++ind;
		vertices[ind] = v1.y;
		normals[ind]  = cn1.y;
		++ind;
		vertices[ind] = v1.z;
		normals[ind]  = cn1.z;
		++ind;
		vertices[ind] = v2.x;
		normals[ind]  = cn2.x;
		++ind;
		vertices[ind] = v2.y;
		normals[ind]  = cn2.y;
		++ind;
		vertices[ind] = v2.z;
		normals[ind]  = cn2.z;
		++ind;
		if (has_uv) {
			uvs[induv] = puvws[cf.p0].x;
			++induv;
			uvs[induv] = puvws[cf.p0].y;
			++induv;
			uvs[induv] = puvws[cf.p1].x;
			++induv;
			uvs[induv] = puvws[cf.p1].y;
			++induv;
			uvs[induv] = puvws[cf.p2].x;
			++induv;
			uvs[induv] = puvws[cf.p2].y;
			++induv;
		}
	}
}


GLObjectBase * loadFromASEFile(const QString & filepath, float scale) {
	QFile f(filepath);
	if (!f.exists()) {
		qDebug() << "[Loader ASE] Error: can`t open" << filepath;
		return nullptr;
	}
	f.open(QIODevice::ReadOnly);
	// QVector<Material> materials;
	GLObjectBase *root = new GLObjectBase(), *co = nullptr;
	root->setName(QFileInfo(f).baseName());
	QTextStream stream(&f);
	QVector<Material> materials;
	QVector<Vector3d> points, puvws;
	QVector<Vector3i> faces, uvws;
	QVector<Vector3d> normals;
	Vector3d cv;
	int mst = -1; //, mat_ind;
	qint64 pst;
	QString line, cname;


	/// Parse materials
	while (mst < 0 && !stream.atEnd()) {
		line = stream.readLine();
		mst  = line.indexOf("MATERIAL_LIST {");
	}
	line = stream.readLine().trimmed();
	mst  = line.indexOf("MATERIAL_COUNT");
	materials.resize(line.right(line.length() - mst - 14).toInt());
	// qDebug() << materials.size() << "materials";
	for (int i = 0; i < materials.size(); ++i) {
		materials[i].map_diffuse.bitmap_id = 0;
		mst                                = -1;
		while (mst < 0 && !stream.atEnd()) {
			line = stream.readLine();
			mst  = line.indexOf("MATERIAL " + QString::number(i) + " {");
		}
		/// Parse material i
		while (line != "}" && !stream.atEnd()) {
			line = stream.readLine().trimmed();
			if (line.left(17) == "*MATERIAL_DIFFUSE") {
				materials[i].color_diffuse = colorFromString(line.right(line.length() - 18));
				continue;
			} // qDebug() << "diffuse " << i << " = " << colorFromString(line.right(line.length() - 18));
			if (line.left(18) == "*MATERIAL_SPECULAR") {
				materials[i].color_specular = colorFromString(line.right(line.length() - 19));
				continue;
			} // qDebug() << "specular " << i << " = " << colorFromString(line.right(line.length() - 19));
			if (line.left(23) == "*MATERIAL_SHINESTRENGTH") {
				materials[i].map_specular.color_amount = line.right(line.length() - 24).toFloat();
				continue;
			}
			if (line.left(15) == "*MATERIAL_SHINE") {
				materials[i].map_specularity.color_amount = 2.f / expf(line.right(line.length() - 16).toFloat());
				continue;
			}
			if (line.left(22) == "*MATERIAL_TRANSPARENCY") {
				materials[i].transparency = line.right(line.length() - 23).toFloat();
				continue;
			}
			if (line.left(12) == "*MAP_DIFFUSE") {
				line = stream.readLine().trimmed();
				while (line.left(11) != "*MAP_AMOUNT" && !stream.atEnd())
					line = stream.readLine().trimmed();
				materials[i].map_normal.color_amount = line.right(line.length() - 12).toFloat();
				while (line.left(7) != "*BITMAP" && !stream.atEnd())
					line = stream.readLine().trimmed();
				materials[i].map_diffuse.bitmap_path = line.mid(9, line.length() - 10);
				/*if (!materials[i].diffuse.bitmap_path.isEmpty()) {
				    materials[i].diffuse.bitmap_id = currentQGLView->bindTexture(QImage(materials[i].diffuse.bitmap_path));
				    parent->textures << materials[i].diffuse.bitmap_id;
				}
				qDebug() << materials[i].diffuse.bitmap_path << ", bind to" << materials[i].diffuse.bitmap_id;*/
				while (line != "}" && !stream.atEnd())
					line = stream.readLine().trimmed();
				line = "";
				continue;
			}
			if (line.left(9) == "*MAP_BUMP") {
				line = stream.readLine().trimmed();
				while (line.left(11) != "*MAP_AMOUNT" && !stream.atEnd())
					line = stream.readLine().trimmed();
				materials[i].map_normal.color_amount = line.right(line.length() - 12).toFloat();
				// qDebug() << "bump amount" << materials[i].bump.color_amount;
				while (line.left(7) != "*BITMAP" && !stream.atEnd())
					line = stream.readLine().trimmed();
				materials[i].map_normal.bitmap_path = line.mid(9, line.length() - 10);
				/*if (!materials[i].bump.bitmap_path.isEmpty()) {
				    materials[i].bump.bitmap_id = currentQGLView->bindTexture(QImage(materials[i].bump.bitmap_path));
				    parent->textures << materials[i].bump.bitmap_id;
				}
				qDebug() << materials[i].bump.bitmap_path << ", bind to" << materials[i].bump.bitmap_id;*/
				while (line != "}" && !stream.atEnd())
					line = stream.readLine().trimmed();
				line = "";
				continue;
			}
		}
	}
	// bs << materials;
	/// Geometry objects
	int cotype = 0;
	mst        = -1;
	while (mst < 0 && !stream.atEnd()) {
		line = stream.readLine();
		if (line.indexOf("GEOMOBJECT {") >= 0 || line.indexOf("LIGHTOBJECT {") >= 0) {
			if (line.indexOf("GEOMOBJECT {") >= 0) cotype = 0;
			if (line.indexOf("LIGHTOBJECT {") >= 0) cotype = 1;
			mst = -1;
			if (co != nullptr) {
				co->points  = points;
				co->faces   = faces;
				co->normals = normals;
				co->uvws    = uvws;
				LoaderASE::initASEMesh(co);
				root->addChild(co);
			}
			co = new GLObjectBase();
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("NODE_NAME");
			}
			cname = line.right(line.length() - mst - 10);
			co->setName(cname.mid(1, cname.length() - 2));
			qDebug() << co->name();
		}
		mst = -1;
		switch (cotype) {
		case 0:
			// qDebug() << "object";
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH {");
			}
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_NUMVERTEX ");
			}
			points.resize(line.right(line.length() - mst - 15).toInt());
			// qDebug() << points.size() << "vertices";
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_NUMFACES ");
			}
			faces.resize(line.right(line.length() - mst - 14).toInt());
			normals.resize(faces.size() * 3);
			// qDebug() << faces.size() << "faces";
			// uvws.resize(faces.size());
			/// Points
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_VERTEX_LIST {");
			}
			for (int i = 0; i < points.size(); ++i) {
				line      = stream.readLine().trimmed();
				mst       = line.indexOf("MESH_VERTEX");
				points[i] = Vector3d(line.right(line.length() - mst - 17)) * scale;
				// qDebug() << points[i];
			}
			/// Faces
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_FACE_LIST {");
			}
			for (int i = 0; i < faces.size(); ++i) {
				line        = stream.readLine().trimmed();
				mst         = line.indexOf("MESH_FACE");
				line        = line.right(line.length() - mst - 15);
				mst         = line.indexOf("A:");
				line        = line.right(line.length() - mst - 2);
				mst         = line.indexOf("B:");
				faces[i].p0 = line.left(mst).toInt();
				line        = line.right(line.length() - mst - 2);
				mst         = line.indexOf("C:");
				faces[i].p1 = line.left(mst).toInt();
				line        = line.right(line.length() - mst - 2);
				mst         = line.indexOf("AB");
				faces[i].p2 = line.left(mst).toInt();
				// qDebug() << faces[i];
			}
			/// Texture coordinates
			mst = -1;
			pst = stream.pos();
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_NUMTVERTEX ");
			}
			if (mst >= 0) {
				puvws.resize(line.right(line.length() - mst - 16).toInt());
				// qDebug() << puvws.size() << "tvertices";
				mst = -1;
				while (mst < 0 && !stream.atEnd()) {
					line = stream.readLine();
					mst  = line.indexOf("MESH_TVERTLIST {");
				}
				for (int i = 0; i < puvws.size(); ++i) {
					line     = stream.readLine().trimmed();
					mst      = line.indexOf("MESH_TVERT");
					line     = line.right(line.length() - mst - 10);
					mst      = line.indexOf("\t");
					line     = line.right(line.length() - mst - 1);
					puvws[i] = Vector3d(line);
				}
				mst = -1;
				while (mst < 0 && !stream.atEnd()) {
					line = stream.readLine();
					mst  = line.indexOf("MESH_NUMTVFACES ");
				}
				uvws.resize(line.right(line.length() - mst - 16).toInt());
				mst = -1;
				while (mst < 0 && !stream.atEnd()) {
					line = stream.readLine();
					mst  = line.indexOf("MESH_TFACELIST {");
				}
				for (int i = 0; i < uvws.size(); ++i) {
					line    = stream.readLine().trimmed();
					mst     = line.indexOf("MESH_TFACE");
					line    = line.right(line.length() - mst - 10);
					mst     = line.indexOf("\t");
					line    = line.right(line.length() - mst - 1);
					uvws[i] = Vector3i(line);
				}
			} else {
				uvws.clear();
				uvws.resize(faces.size());
				stream.seek(pst);
			}
			if (puvws.size() <= 0) puvws.resize(1);
			/// Normals
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_NORMALS {");
			}
			for (int i = 0; i < faces.size(); ++i) {
				line               = stream.readLine();
				line               = stream.readLine().trimmed();
				mst                = line.indexOf("	");
				line               = line.right(line.length() - mst - 1);
				normals[i * 3]     = Vector3d(line);
				line               = stream.readLine().trimmed();
				mst                = line.indexOf("	");
				line               = line.right(line.length() - mst - 1);
				normals[i * 3 + 1] = Vector3d(line);
				line               = stream.readLine().trimmed();
				mst                = line.indexOf("	");
				line               = line.right(line.length() - mst - 1);
				normals[i * 3 + 2] = Vector3d(line);
				// qDebug() << normals[i][0] << normals[i][1] << normals[i][2];
			}
			/// Material index
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MATERIAL_REF ");
			}
			// mat_ind = line.right(line.length() - mst - 13).toInt();
			// qDebug() << mat_ind.back();
			if (points.size() == 0 || faces.size() == 0) {
				mst = -1;
				continue;
			}

			/// Compiling into GLList
			/*glNewList(model, GL_COMPILE);
			if (mat_ind < 0 || mat_ind >= materials.size()) {
			    mat_diffuse[0] = cfr;
			    mat_diffuse[1] = cfg;
			    mat_diffuse[2] = cfb;
			    glColor3f(cfr, cfg, cfb);
			    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
			    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_none);
			    glMaterialf(GL_FRONT, GL_SHININESS, 0.);
			} else {
			    materials[mat_ind].apply();
			    //parent->material_ = materials[mat_ind];
			}
			glBegin(GL_TRIANGLES);
			if (normals_) {
			    for (int i = 0; i < faces.size(); ++i) {
			        glNormal3d(normals[i][0].x, normals[i][0].y, normals[i][0].z);
			        glTexCoord3d(puvws[uvws[i].p0].x, puvws[uvws[i].p0].y, puvws[uvws[i].p0].z);
			        cv = points[faces[i].p0] * scale; glVertex3d(cv.x, cv.y, cv.z);
			        glNormal3d(normals[i][1].x, normals[i][1].y, normals[i][1].z);
			        glTexCoord3d(puvws[uvws[i].p1].x, puvws[uvws[i].p1].y, puvws[uvws[i].p1].z);
			        cv = points[faces[i].p1] * scale; glVertex3d(cv.x, cv.y, cv.z);
			        glNormal3d(normals[i][2].x, normals[i][2].y, normals[i][2].z);
			        glTexCoord3d(puvws[uvws[i].p2].x, puvws[uvws[i].p2].y, puvws[uvws[i].p2].z);
			        cv = points[faces[i].p2] * scale; glVertex3d(cv.x, cv.y, cv.z);
			    }
			} else {
			    for (int i = 0; i < faces.size(); ++i) {
			        glTexCoord3d(puvws[uvws[i].p0].x, puvws[uvws[i].p0].y, puvws[uvws[i].p0].z);
			        cv = points[faces[i].p0] * scale; glVertex3d(cv.x, cv.y, cv.z);
			        glTexCoord3d(puvws[uvws[i].p1].x, puvws[uvws[i].p1].y, puvws[uvws[i].p1].z);
			        cv = points[faces[i].p1] * scale; glVertex3d(cv.x, cv.y, cv.z);
			        glTexCoord3d(puvws[uvws[i].p2].x, puvws[uvws[i].p2].y, puvws[uvws[i].p2].z);
			        cv = points[faces[i].p2] * scale; glVertex3d(cv.x, cv.y, cv.z);
			    }
			}
			glEnd();*/

			///// Save binary
			// bs << mat_ind << points << faces << puvws << uvws << normals;
			break;
		case 1:
			qDebug() << "light";
			mst = -1;
			while (mst < 0 && !stream.atEnd()) {
				line = stream.readLine();
				mst  = line.indexOf("MESH_NORMALS {");
			}
			break;
		}
		/// Continue
		mst = -1;
	}
	f.close();
	if (co != nullptr) {
		co->points  = points;
		co->faces   = faces;
		co->normals = normals;
		co->uvws    = uvws;
		LoaderASE::initASEMesh(co);
		root->addChild(co);
	}
	/*parent->points = points;
	parent->puvws = puvws;
	parent->faces = faces;
	parent->uvws = uvws;
	parent->normals = normals;
	return model;*/
	return root;
}
