/*
    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_obj.h"


void LoaderOBJ::initOBJMesh(GLObjectBase * o,
                            const QVector<Vector3d> & src_vertices,
                            const QVector<Vector3d> & src_normals,
                            const QVector<Vector3d> & src_texcoords) {
	QVector<GLfloat> &vertices(o->VBO().vertices()), &normals(o->VBO().normals()), &uvs(o->VBO().texcoords());
	QVector<Vector3i> &faces(o->faces), &uvws(o->uvws), &norms(o->norms);
	// Vector3d pos = Vector3d(o->pos());
	bool has_uv = !uvws.isEmpty();
	Vector3i cf, ct, cn;
	Vector3d v[3], t[3], n[3];
	// for (int i = 0; i < points.size(); ++i)
	//	points[i] -= pos;
	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() << "initOBJMesh" << faces.size();
	for (int i = 0; i < faces.size(); ++i) {
		cf            = faces[i];
		ct            = uvws[i];
		cn            = norms[i];
		v[0]          = src_vertices[cf.p0];
		v[1]          = src_vertices[cf.p1];
		v[2]          = src_vertices[cf.p2];
		n[0]          = src_normals[cn.p0];
		n[1]          = src_normals[cn.p1];
		n[2]          = src_normals[cn.p2];
		vertices[ind] = v[0].x;
		normals[ind]  = n[0].x;
		++ind;
		vertices[ind] = v[0].y;
		normals[ind]  = n[0].y;
		++ind;
		vertices[ind] = v[0].z;
		normals[ind]  = n[0].z;
		++ind;
		vertices[ind] = v[1].x;
		normals[ind]  = n[1].x;
		++ind;
		vertices[ind] = v[1].y;
		normals[ind]  = n[1].y;
		++ind;
		vertices[ind] = v[1].z;
		normals[ind]  = n[1].z;
		++ind;
		vertices[ind] = v[2].x;
		normals[ind]  = n[2].x;
		++ind;
		vertices[ind] = v[2].y;
		normals[ind]  = n[2].y;
		++ind;
		vertices[ind] = v[2].z;
		normals[ind]  = n[2].z;
		++ind;
		if (has_uv) {
			if ((ct.p0 >= 0) && (ct.p1 >= 0) && (ct.p2 >= 0)) {
				t[0]       = src_texcoords[ct.p0];
				t[1]       = src_texcoords[ct.p1];
				t[2]       = src_texcoords[ct.p2];
				uvs[induv] = t[0].x;
				++induv;
				uvs[induv] = t[0].y;
				++induv;
				uvs[induv] = t[1].x;
				++induv;
				uvs[induv] = t[1].y;
				++induv;
				uvs[induv] = t[2].x;
				++induv;
				uvs[induv] = t[2].y;
				++induv;
			}
		}
	}
}


Vector3d readVector3d(QString s) {
	Vector3d ret;
	QStringList sl(s.trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	if (sl.size() > 0) ret.x = sl[0].toFloat();
	if (sl.size() > 1) ret.y = sl[1].toFloat();
	if (sl.size() > 2) ret.z = sl[2].toFloat();
	return ret;
}


Vector2d readVector2d(QString s) {
	Vector2d ret;
	QStringList sl(s.trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	if (sl.size() > 0) ret.x = sl[0].toFloat();
	if (sl.size() > 1) ret.y = sl[1].toFloat();
	return ret;
}


QColor readColor(QString s) {
	Vector3d c = readVector3d(s);
	return QColor::fromRgbF(c.x, c.y, c.z);
}


void readFaces(QString s, GLObjectBase * co) {
	QStringList sl(s.trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	static Vector3i inds[4];
	for (int i = 0; i < sl.size(); ++i) {
		inds[i].p0 = inds[i].p1 = inds[i].p2 = 0;
		QStringList sl2(sl[i].split("/"));
		if (sl2.size() > 4) continue;
		inds[i].p0 = sl2[0].toInt();
		inds[i].p1 = sl2[1].toInt();
		inds[i].p2 = sl2[2].toInt();
	}
	if (sl.size() == 3) {
		co->faces << Vector3i(inds[0].p0 - 1, inds[1].p0 - 1, inds[2].p0 - 1);
		co->uvws << Vector3i(inds[0].p1 - 1, inds[1].p1 - 1, inds[2].p1 - 1);
		co->norms << Vector3i(inds[0].p2 - 1, inds[1].p2 - 1, inds[2].p2 - 1);
	}
	if (sl.size() == 4) {
		co->faces << Vector3i(inds[0].p0 - 1, inds[1].p0 - 1, inds[2].p0 - 1);
		co->uvws << Vector3i(inds[0].p1 - 1, inds[1].p1 - 1, inds[2].p1 - 1);
		co->norms << Vector3i(inds[0].p2 - 1, inds[1].p2 - 1, inds[2].p2 - 1);
		co->faces << Vector3i(inds[0].p0 - 1, inds[2].p0 - 1, inds[3].p0 - 1);
		co->uvws << Vector3i(inds[0].p1 - 1, inds[2].p1 - 1, inds[3].p1 - 1);
		co->norms << Vector3i(inds[0].p2 - 1, inds[2].p2 - 1, inds[3].p2 - 1);
	}
}


QVector<Material> readMTL(QString obj_path, QString path) {
	QVector<Material> ret;
	QStringList sp = GLTextureManagerBase::searchPathes();
	sp.prepend(QFileInfo(obj_path).absoluteDir().path());
	QFile f(findFile(path, sp));
	if (!f.open(QIODevice::ReadOnly)) {
		qDebug() << "[Loader OBJ] Warning: can`t open \"" + path + "\"";
		return ret;
	}
	QTextStream stream(&f);
	QString name;
	Material mat;
	while (!stream.atEnd()) {
		QString line = stream.readLine().trimmed();
		if (line.startsWith("newmtl")) {
			if (!mat.name.isEmpty()) ret << mat;
			mat      = Material();
			mat.name = line.mid(6).trimmed();
			continue;
		}
		if (line.startsWith("Kd")) {
			mat.color_diffuse = readColor(line.mid(2).trimmed());
			continue;
		}
		if (line.startsWith("Ke")) {
			mat.color_self_illumination = readColor(line.mid(2).trimmed());
			continue;
		}
		if (line.startsWith("Ks")) {
			Vector3d v                    = readVector3d(line.mid(2).trimmed());
			mat.map_specular.color_amount = v.length();
			float mc                      = qMax(v.x, qMax(v.y, v.z));
			if (mc > 0.f) v /= mc;
			mat.color_specular = QColor::fromRgbF(v.x, v.y, v.z);
			// qDebug() << mat.shine_strength << mat.color_specular;
			continue;
		}
		if (line.startsWith("Ns")) {
			mat.map_specularity.color_amount = 2.f / expf(line.mid(2).trimmed().toFloat());
			continue;
		}
		if (line.startsWith("d")) {
			mat.transparency = 1.f - line.mid(1).trimmed().toFloat();
			continue;
		}
		if (line.startsWith("map_Kd")) {
			mat.map_diffuse.bitmap_path = findFile(line.mid(6).trimmed(), sp);
			continue;
		}
		if (line.startsWith("map_bump")) {
			line = line.mid(8).trimmed();
			if (line.startsWith("-bm")) {
				line                        = line.mid(3).trimmed();
				QString sv                  = line.left(line.indexOf(" "));
				line                        = line.mid(sv.size()).trimmed();
				mat.map_normal.color_amount = sv.toFloat();
			}
			mat.map_normal.bitmap_path = findFile(line, sp);
			// qDebug() << "BUMP" << mat.name << mat.bump_scale << mat.bump.bitmap_path;
			continue;
		}
	}
	if (!mat.name.isEmpty()) ret << mat;
	qDebug() << "load from MTL" << f.fileName() << ret.size() << "materials";
	return ret;
}


Material LoaderOBJ::materialByName(const QVector<Material> & materials, const QString & name) {
	foreach(const Material & m, materials)
		if (m.name == name) return m;
	return Material();
}


GLObjectBase * loadFromOBJFile(const QString & filepath, float scale) {
	QFile f(filepath);
	if (!f.exists()) {
		qDebug() << "[Loader OBJ] Error: can`t open \"" + filepath + "\"";
		return nullptr;
	}
	f.open(QIODevice::ReadOnly);
	QTextStream stream(&f);
	QVector<Vector3d> vertices, normals, texcoords;
	QVector<Material> materials;
	GLObjectBase *root = new GLObjectBase(), *co = nullptr;
	QString name, line, pline;
	root->setName(QFileInfo(f).baseName());
	int cnt = 0;
	while (!stream.atEnd()) {
		pline = line;
		line  = stream.readLine().trimmed();
		if (line.startsWith("mtllib")) {
			materials = readMTL(filepath, line.mid(6).trimmed());
			continue;
		}
		if (line.startsWith("v ")) {
			vertices << readVector3d(line.mid(1));
			continue;
		}
		if (line.startsWith("vt ")) {
			texcoords << readVector3d(line.mid(2));
			continue;
		}
		if (line.startsWith("vn ")) {
			normals << readVector3d(line.mid(2));
			continue;
		}
		if (line.startsWith("g ") || line.startsWith("o ")) {
			if (pline.mid(1) == line.mid(1)) continue;
			name = line.mid(1).trimmed();
			if (co != nullptr) {
				LoaderOBJ::initOBJMesh(co, vertices, normals, texcoords);
				root->addChild(co);
			}
			co = new GLObjectBase();
			co->setName(name);
			// qDebug() << "new object" << co->name();
			continue;
		}
		if (line.startsWith("f ")) {
			readFaces(line.mid(2), co);
			continue;
		}
		if (line.startsWith("usemtl")) {
			if (!co->faces.isEmpty()) {
				LoaderOBJ::initOBJMesh(co, vertices, normals, texcoords);
				root->addChild(co);
				co = new GLObjectBase();
				co->setName(name + "_" + QString::number(cnt++));
			}
			co->material() = LoaderOBJ::materialByName(materials, line.mid(6).trimmed());
			continue;
		}
	}
	if (co != nullptr) {
		LoaderOBJ::initOBJMesh(co, vertices, normals, texcoords);
		root->addChild(co);
	}
	qDebug() << "[Loader OBJ] Loaded" << root->childCount() << "objects from" << filepath;
	return root;
}
