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

#include <QDomDocument>
#include <QUrl>


Material LoaderDAE::materialByName(const QVector<Material> & materials, const QString & name) {
	foreach(const Material & m, materials) {
		// qDebug() << m.name << " ??? " << name;
		if (m.name == name) return m;
	}
	return Material();
}


QColor readXMLColor(QDomElement n) {
	QStringList sl(n.firstChildElement("color").firstChild().nodeValue().trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	if (sl.size() >= 3)
		return QColor::fromRgbF(sl[0].toDouble(), sl[1].toDouble(), sl[2].toDouble(), sl.size() >= 4 ? sl[3].toDouble() : 1.);
	return QColor();
}


QVector4D readXMLVector(QDomElement n) {
	QStringList sl(n.firstChild().nodeValue().trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	if (sl.size() == 3) return QVector4D(sl[0].toFloat(), sl[1].toFloat(), sl[2].toFloat(), 0.);
	if (sl.size() == 4) return QVector4D(sl[0].toFloat(), sl[1].toFloat(), sl[2].toFloat(), sl[3].toFloat());
	return QVector4D();
}


QMatrix4x4 readXMLMatrix(QDomElement n) {
	QStringList sl(n.firstChild().nodeValue().trimmed().split(" "));
	sl.removeAll("");
	sl.removeAll(" ");
	if (sl.size() != 16) return QMatrix4x4();
	QMatrix4x4 m;
	int ind = -1;
	for (int r = 0; r < 4; ++r)
		for (int c = 0; c < 4; ++c)
			m(r, c) = sl[++ind].toFloat();
	return m;
}


float readXMLFloat(QDomElement n) {
	return n.firstChildElement("float").firstChild().nodeValue().toFloat();
}


QString readXMLTexture(QDomElement n, QDomElement prof, QDomElement li) {
	QString tex = n.firstChildElement("texture").attribute("texture");
	if (tex.isEmpty()) return QString();
	QString tag;
	QDomNodeList elist = prof.elementsByTagName("newparam");
	if (elist.isEmpty()) {
		tag = tex;
	} else {
		bool found = false;
		int cnt    = 0;
		while (!tex.isEmpty() && !found && cnt < 10) {
			found = false;
			cnt++;
			for (int i = 0; i < elist.count(); ++i) {
				QDomNode dn = elist.at(i);
				if (dn.attributes().namedItem("sid").nodeValue() == tex) {
					// qDebug() << "found!";
					if (dn.firstChild().nodeName() == "sampler2D") {
						tex = dn.firstChildElement("sampler2D").firstChildElement("source").firstChild().nodeValue();
						break;
					}
					if (dn.firstChild().nodeName() == "surface") {
						tag = dn.firstChildElement("surface").firstChildElement("init_from").firstChild().nodeValue();
						// qDebug() << tex << "->" << tag;
						tex.clear();
						found = true;
						break;
					}
				}
			}
		}
		if (cnt == 10) return QString();
	}
	// qDebug() << tag;
	if (tag.isEmpty()) return QString();
	elist = li.elementsByTagName("image");
	for (int i = 0; i < elist.count(); ++i) {
		QDomElement dn = elist.at(i).toElement();
		if (dn.attribute("id") == tag) {
			tex = dn.firstChildElement("init_from").firstChild().nodeValue();
			tex.replace("\\", "/");
			if (tex.startsWith("file:") && tex.mid(5, 3) != "///") tex.insert(6, "/");
			// qDebug() << "found" << tex << QUrl(tex).toLocalFile();
			tex = QUrl(tex).toLocalFile();
			if (tex == "/") tex.clear();
			return tex;
		}
	}
	return QString();
}


QVector<Material> LoaderDAE::readMaterials(QDomElement le, QDomElement li, bool fbx) {
	QVector<Material> ret;
	QDomNodeList elist = le.elementsByTagName("effect");
	for (int i = 0; i < elist.count(); ++i) {
		QDomNode dn = elist.at(i);
		Material mat;
		mat.name         = dn.attributes().namedItem("id").nodeValue();
		QDomElement prof = dn.firstChildElement("profile_COMMON");
		QDomNode pn      = prof.firstChildElement("technique").firstChild();
		QColor col;
		QString text;

		col = readXMLColor(pn.firstChildElement("emission"));
		if (col.isValid()) mat.color_self_illumination = col;
		col = readXMLColor(pn.firstChildElement("diffuse"));
		if (col.isValid()) mat.color_diffuse = col;
		col = readXMLColor(pn.firstChildElement("specular"));
		if (col.isValid()) mat.color_specular = col;
		mat.map_specularity.color_amount = 2.f / expf(readXMLFloat(pn.firstChildElement("shininess")));
		mat.transparency                 = readXMLFloat(pn.firstChildElement("transparency"));
		if (!fbx) mat.transparency = 1.f - mat.transparency;
		text = readXMLTexture(pn.firstChildElement("diffuse"), prof, li);
		if (!text.isEmpty()) mat.map_diffuse.bitmap_path = text;
		text = readXMLTexture(pn.firstChildElement("diffuse"), prof, li);
		if (!text.isEmpty()) mat.map_diffuse.bitmap_path = text;

		pn   = prof.firstChildElement("technique").firstChildElement("extra").firstChild();
		text = readXMLTexture(pn.firstChildElement("bump"), prof, li);
		if (!text.isEmpty()) mat.map_normal.bitmap_path = text;

		ret << mat;
		qDebug() << "** Material" << mat.name;
		qDebug() << "   emission" << mat.color_self_illumination;
		qDebug() << "   diffuse" << mat.color_diffuse;
		qDebug() << "   specular" << mat.color_specular;
		qDebug() << "   transparency" << mat.transparency;
	}
	return ret;
}


QMatrix4x4 readXMLTransformations(QDomElement n) {
	QMatrix4x4 tm;
	QDomNodeList trl = n.childNodes();
	for (int i = 0; i < trl.count(); ++i) {
		QDomElement dt = trl.at(i).toElement();
	}
	return tm;
}


void readScene(QDomElement n, QMatrix4x4 cm, QVector<QPair<QPair<QString, QString>, QMatrix4x4>> & ret, QString last_name = QString()) {
	QDomNodeList evsl = n.childNodes();
	if (n.hasAttribute("name")) last_name = n.attribute("name");
	for (int i = 0; i < evsl.count(); ++i) {
		QDomElement dt = evsl.at(i).toElement();
		QVector4D v;
		// qDebug() << dt.nodeName();
		if (dt.nodeName() == "translate") {
			v = readXMLVector(dt);
			cm.translate(v.toVector3D());
			continue;
		}
		if (dt.nodeName() == "rotate") {
			v = readXMLVector(dt);
			cm.rotate(v.w(), v.toVector3D());
			continue;
		}
		if (dt.nodeName() == "scale") {
			v = readXMLVector(dt);
			cm.scale(v.toVector3D());
			continue;
		}
		if (dt.nodeName() == "matrix") {
			QMatrix4x4 m = readXMLMatrix(dt);
			cm *= m;
			continue;
		}
		if (dt.nodeName() == "node") {
			readScene(dt, cm, ret, last_name);
			continue;
		}
		if (dt.nodeName() == "instance_geometry" || dt.nodeName() == "instance_light") {
			QString gid = dt.attribute("url");
			if (gid.startsWith("#")) gid.remove(0, 1);
			// qDebug() << "matrix" << gid << cm;
			ret << QPair<QPair<QString, QString>, QMatrix4x4>(QPair<QString, QString>(gid, last_name), cm);
			continue;
		}
		// qDebug() << name << m;
	}
	ret << QPair<QPair<QString, QString>, QMatrix4x4>(QPair<QString, QString>("", last_name), cm);
}


GLObjectBase * loadFromDAEFile(const QString & filepath, float scale) {
	QFile f(filepath);
	if (!f.exists()) {
		qDebug() << "[Loader DAE] Error: can`t open \"" + filepath + "\"";
		return nullptr;
	}
	QDomDocument dom(filepath);
	if (!dom.setContent(&f)) {
		qDebug() << "[Loader DAE] Error: can`t parse \"" + filepath + "\"";
		return nullptr;
	}
	// qDebug() << "parse" << tm.elapsed();
	QDomElement maine = dom.firstChildElement("COLLADA");
	bool fbx          = maine.firstChildElement("asset")
	               .firstChildElement("contributor")
	               .firstChildElement("authoring_tool")
	               .firstChild()
	               .nodeValue()
	               .startsWith("FBX");
	QVector<Material> materials =
		LoaderDAE::readMaterials(maine.firstChildElement("library_effects"), maine.firstChildElement("library_images"), fbx);
	GLObjectBase *root = new GLObjectBase(), *co = nullptr;
	QMap<QString, QVector<GLObjectBase *>> objects;

	QMap<QString, QString> mat_names;
	QDomElement mvse  = maine.firstChildElement("library_visual_scenes").firstChildElement("visual_scene");
	QDomNodeList evsl = mvse.elementsByTagName("instance_material");
	QDomNodeList matl = maine.firstChildElement("library_materials").elementsByTagName("material");
	for (int i = 0; i < evsl.count(); ++i) {
		QDomElement dn = evsl.at(i).toElement();
		QString tn     = dn.attribute("target");
		if (tn.startsWith("#")) tn.remove(0, 1);
		for (int j = 0; j < matl.count(); ++j) {
			QDomElement dm = matl.at(j).toElement();
			if (dm.attribute("id") == tn) {
				QString en = dm.firstChildElement("instance_effect").attribute("url");
				if (en.startsWith("#")) en.remove(0, 1);
				mat_names[dn.attribute("symbol")] = en;
				// qDebug() << dn.attribute("symbol") << "->" << en;
			}
		}
	}

	QDomNodeList elist = maine.firstChildElement("library_geometries").elementsByTagName("geometry");
	for (int i = 0; i < elist.count(); ++i) {
		QDomNode dn  = elist.at(i);
		QString name = dn.attributes().namedItem("name").nodeValue();
		QString gid  = dn.attributes().namedItem("id").nodeValue();
		if (name.isEmpty()) continue;
		dn = dn.firstChildElement("mesh");
		QMap<QString, QString> source_names;
		QMap<QString, QVector<Vector3d>> source_data;
		QDomNodeList esrc = dn.toElement().elementsByTagName("source");
		for (int j = 0; j < esrc.count(); ++j) {
			QDomNode ds        = esrc.at(j);
			QString id         = ds.attributes().namedItem("id").nodeValue();
			QDomNodeList evert = dn.toElement().elementsByTagName("vertices");
			for (int k = 0; k < evert.count(); ++k) {
				QDomNode dv = evert.at(k);
				QString vid = dv.attributes().namedItem("id").nodeValue();
				if (dv.firstChildElement("input").attribute("source") == ("#" + id)) source_names[vid] = id;
				// qDebug() << "  found source sin" << vid;
			}
			QVector<Vector3d> & sd(source_data[id]);
			int stride   = ds.firstChildElement("technique_common").firstChildElement("accessor").attribute("stride").toInt();
			QString astr = ds.firstChildElement("float_array").firstChild().nodeValue().trimmed();
			astr.replace("\n", " ");
			astr.remove("\r");
			QStringList sl = astr.split(" ");
			sl.removeAll("");
			sl.removeAll(" ");
			for (int c = 0; c < sl.size(); c += stride) {
				Vector3d v;
				if (stride >= 1) v.x = sl[c].toFloat();
				if (stride >= 2) v.y = sl[c + 1].toFloat();
				if (stride >= 3) v.z = sl[c + 2].toFloat();
				sd << v;
			}
			// qDebug() << "  found source" << id << "stride =" << stride << ":" << sd;
			// qDebug() << "  readed" << sd.size();
		}
		QDomNodeList etr = dn.toElement().elementsByTagName("triangles");
		// QMatrix4x4 m = matrices.value(gid);
		// qDebug() << "found geom" << name;
		QVector<GLObjectBase *> ol;
		for (int j = 0; j < etr.count(); ++j) {
			QDomElement ds  = etr.at(j).toElement();
			QString matname = mat_names[ds.attribute("material")];
			QVector<int> p;
			QStringList psl = ds.firstChildElement("p").firstChild().nodeValue().trimmed().split(" ");
			foreach(const QString & s, psl)
				p << s.toInt();
			QDomNodeList einp = ds.elementsByTagName("input");
			int pbv           = einp.count(); //, tc = qMin(ds.attribute("count").toInt(), p.size() / pbv);
			co                = new GLObjectBase();
			co->setName(name + "_" + QString::number(j));
			// co->setTransform(m);
			co->material() = LoaderDAE::materialByName(materials, matname);
			qDebug() << "  tri" << co->material().name << matname;
			QVector<GLfloat> &vertices(co->VBO().vertices()), &normals(co->VBO().normals()), &uvs(co->VBO().texcoords());
			for (int k = 0; k < einp.count(); ++k) {
				QDomElement di = einp.at(k).toElement();
				QString src = di.attribute("source"), sem = di.attribute("semantic").toLower();
				int offset              = di.attribute("offset").toInt();
				QVector<GLfloat> * curv = nullptr;
				int pccnt               = 0;
				if (sem == "vertex") {
					curv  = &vertices;
					pccnt = 3;
				}
				if (sem == "normal") {
					curv  = &normals;
					pccnt = 3;
				}
				if (sem == "texcoord") {
					curv  = &uvs;
					pccnt = 2;
				}
				if (curv == nullptr) continue;
				if (src.startsWith("#")) src.remove(0, 1);
				QVector<Vector3d> & data(source_data[source_names.value(src, src)]);
				for (int ii = offset; ii < p.size(); ii += pbv) {
					if ((p[ii] >= 0) && (p[ii] < data.size())) {
						Vector3d v = data[p[ii]];
						(*curv) << v.x << v.y;
						if (pccnt == 3) (*curv) << v.z;
					}
				}
				// qDebug() << "    input" << sem << "from" << data.size() << "->" << (*curv) << pbv;
			}
			// qDebug() << "geom" << gid << co;
			ol << co;
		}
		objects[gid] = ol;
	}

	elist = maine.firstChildElement("library_lights").elementsByTagName("light");
	for (int i = 0; i < elist.count(); ++i) {
		QDomElement dn = elist.at(i).toElement();
		QString name   = dn.attributes().namedItem("name").nodeValue();
		QString gid    = dn.attributes().namedItem("id").nodeValue();
		if (name.isEmpty() || name == "EnvironmentAmbientLight") continue;
		QDomElement dl = dn.firstChildElement("technique_common").firstChild().toElement();
		Light * lo     = new Light();
		if (dl.nodeName() == "point")
			lo->light_type = Light::Omni;
		else if (dl.nodeName() == "spot")
			lo->light_type = Light::Cone;
		else {
			delete lo;
			continue;
		}
		lo->setColor(readXMLColor(dl));
		QDomNodeList ml = dn.elementsByTagName("multiplier");
		if (!ml.isEmpty()) lo->intensity = ml.at(0).firstChild().nodeValue().toFloat();
		ml = dn.elementsByTagName("intensity");
		if (!ml.isEmpty()) lo->intensity = ml.at(0).firstChild().nodeValue().toFloat();
		lo->setColor(lo->color() / lo->intensity);
		QString sv;
		sv = dl.firstChildElement("constant_attenuation").firstChild().nodeValue();
		if (!sv.isEmpty()) lo->decay_const = sv.toFloat();
		sv = dl.firstChildElement("linear_attenuation").firstChild().nodeValue();
		if (!sv.isEmpty()) lo->decay_linear = sv.toFloat();
		sv = dl.firstChildElement("quadratic_attenuation").firstChild().nodeValue();
		if (!sv.isEmpty()) lo->decay_quadratic = sv.toFloat();
		/// lo->setTransform(matrices.value(name));
		if (lo->light_type == Light::Cone) {
			ml = dn.elementsByTagName("decay_falloff");
			if (!ml.isEmpty()) lo->angle_end = ml.at(0).firstChild().nodeValue().toFloat();
			ml = dn.elementsByTagName("hotspot_beam");
			if (!ml.isEmpty()) lo->angle_start = ml.at(0).firstChild().nodeValue().toFloat();
		}
		QVector<GLObjectBase *> ol;
		ol << lo;
		objects[gid] = ol;
		// qDebug() << "light" << name;
	}
	// qDebug() << "readed" << objects.size();

	QVector<QPair<QPair<QString, QString>, QMatrix4x4>> scene;
	readScene(mvse, QMatrix4x4(), scene);
	for (int i = 0; i < scene.size(); ++i) {
		QPair<QPair<QString, QString>, QMatrix4x4> so = scene[i];
		if (so.first.first.isEmpty()) continue;
		QVector<GLObjectBase *> ol = objects.value(so.first.first);
		foreach(GLObjectBase * o, ol) {
			o = o->clone();
			o->setName(so.first.second);
			o->setTransform(so.second);
			root->addChild(o);
			// qDebug() << "  add" << so.first.second << o->name();
		}
		// qDebug() << "add" << so.first << ol.size();
	}

	for (int i = 0; i < root->childCount(); ++i) {
		GLObjectBase * o = root->child(i);
		if (o->type() == GLObjectBase::glLight) {
			Light * l = (Light *)o;
			if (l->light_type == Light::Directional || l->light_type == Light::Cone) {
				QString tn = l->name() + ".Target";
				// qDebug() << "search target" << tn;
				for (int s = 0; s < scene.size(); ++s) {
					QPair<QPair<QString, QString>, QMatrix4x4> so = scene[s];
					if (so.first.second == tn) {
						// qDebug() << "found target" << tn;
						QVector3D tp = so.second.column(3).toVector3D();
						l->direction = (tp - l->pos()).normalized();
						// qDebug() << "dir" << l->direction;
					}
				}
			}
		}
	}

	QList<QVector<GLObjectBase *>> dol = objects.values();
	for (int i = 0; i < dol.size(); ++i)
		for (int j = 0; j < dol[i].size(); ++j)
			delete dol[i][j];

	root->setScale(0.001f);
	qDebug() << "[Loader DAE] Loaded" << root->childCount() << "objects from" << filepath;
	return root;
}
