module coneneko.mqo;
import std.stream, std.string, std.file, std.conv, std.cstream, std.windows.charset, std.math;
import coneneko.math, coneneko.serializer, coneneko.rgba;

private alias Vector Vector3;
private alias Vector Vector2;

class MqoScene : Serializer
{
	unittest
	{
		auto s = new MemoryStream();
		s.writefln("Scene {");
		s.writefln("\tpos 34.7461 -17.6904 1500.0000");
		s.writefln("\tlookat 0.0000 0.0000 0.0000");
		s.writefln("\thead -0.1136");
		s.writefln("\tpich 0.1536");
		s.writefln("\tortho 0");
		s.writefln("\tzoom2 3.2257");
		s.writefln("\tamb 0.250 0.250 0.250");
		s.writefln("}");
		s.seekSet(0);
		auto scene = new MqoScene();
		scene.deserialize(s);
		auto d = new MemoryStream();
		scene.serialize(d);
		assert(s.size == d.size);
		assert(s.data == d.data);
	}
	
	Vector3 pos, lookat;
	float head, pich;
	int ortho;
	float zoom2;
	Vector3 amb;
	
	void serialize(Stream writer)
	{
		const V = "\t%s %.4f %.4f %.4f";
		const F = "\t%s %.4f";
		with (writer)
		{
			writefln("Scene {");
			writefln(V, "pos", pos.x, pos.y, pos.z);
			writefln(V, "lookat", lookat.x, lookat.y, lookat.z);
			writefln(F, "head", head);
			writefln(F, "pich", pich);
			writefln("\t%s %d", "ortho", ortho);
			writefln(F, "zoom2", zoom2);
			writefln("\t%s %.3f %.3f %.3f", "amb", amb.x, amb.y, amb.z);
			writefln("}");
		}
	}
	
	void deserialize(Stream reader)
	{
		while ("Scene {" != reader.readLine())
		{
			if (reader.eof) throw new Error("MqoScene.deserialize: not Scene");
		}
		auto t = new MqoToken();
		while (true)
		{
			auto line = cast(string)reader.readLine(); // d2
			if (line == "}") break;
			t ~= line.split();
		}
		pos = t.getv3("pos");
		lookat = t.getv3("lookat");
		head = t.getf("head");
		pich = t.getf("pich");
		ortho = t.geti("ortho");
		zoom2 = t.getf("zoom2");
		amb = t.getv3("amb");
	}
}

class MqoMaterial : Serializer
{
	unittest
	{
		auto s = new MemoryStream();
		s.writefln("\t\"test0.png\" shader(3) col(1.000 1.000 1.000 1.000) dif(0.800) amb(0.600) emi(0.000) spc(0.000) power(5.00) tex(\"test0.png\")");
		s.writefln("\t\"mat1\" shader(3) col(0.349 0.863 1.000 1.000) dif(0.800) amb(0.600) emi(0.000) spc(0.000) power(5.00)");
		auto m0 = new MqoMaterial();
		auto m1 = new MqoMaterial();
		s.seekSet(0);
		m0.deserialize(s);
		m1.deserialize(s);
		auto d = new MemoryStream();
		m0.serialize(d);
		m1.serialize(d);
		assert(s.size == d.size);
		assert(s.data == d.data);
	}
	
	string name;
	int shader, vcol;
	Vector col;
	float dif, amb, emi, spc, power;
	string tex, aplane, bump;
	int proj_type;
	Vector3 proj_pos, proj_scale, proj_angle;
	
	void serialize(Stream writer)
	{
		writer.writef(
			"\t\"%s\" shader(%d)",
			name,
			shader
		);
		if (vcol != 0) writer.writef(" vcol(%d)", vcol);
		writer.writef(
			" col(%.3f %.3f %.3f %.3f) dif(%.3f) amb(%.3f) emi(%.3f) spc(%.3f) power(%.2f)",
			col.x, col.y, col.z, col.w,
			dif,
			amb,
			emi,
			spc,
			power
		);
		if (tex != "") writer.writef(" tex(\"%s\")", tex);
		if (aplane != "") writer.writef(" aplane(\"%s\")", aplane);
		if (bump != "") writer.writef(" bump(\"%s\")", bump);
		if (proj_type != 0)
		{
			writer.writef(
				"proj_type(%d) proj_pos(%.3f %.3f %.3f) proj_scale(%.3f %.3f %.3f) proj_angle(%.3f %.3f %.3f)",
				proj_type,
				proj_pos.x, proj_pos.y, proj_pos.z,
				proj_scale.x, proj_scale.y, proj_scale.z,
				proj_angle.x, proj_angle.y, proj_angle.z
			);
		}
		writer.writefln("");
	}
	
	void deserialize(Stream reader)
	{
		auto line = fromMBSz(reader.readLine().toStringz()); // d1
		//auto line = fromMBSz(cast(invariant)reader.readLine().toStringz()); // d2
		name = line.split("\"")[1];
		line = line.split("\" ")[1];
		line = line.replace("(", " ");
		line = line.replace(")", " ");
		auto t = new MqoToken();
		t ~= line.split();
		shader = t.geti("shader");
		vcol = t.geti("vcol");
		col = t.getv4("col");
		dif = t.getf("dif");
		amb = t.getf("amb");
		emi = t.getf("emi");
		spc = t.getf("spc");
		power = t.getf("power");
		tex = t.gets("tex");
		aplane = t.gets("aplane");
		bump = t.gets("bump");
		proj_type = t.geti("proj_type");
		proj_pos = t.getv3("proj_pos");
		proj_scale = t.getv3("proj_scale");
		proj_angle = t.getv3("proj_angle");
	}
}

class MqoObject : Serializer // 厳密に書いてない
{
	unittest
	{
		auto s = new MemoryStream();
		s.writefln(`Object "obj1" {`);
		s.writefln("\tvisible 15");
		s.writefln("\tlocking 0");
		s.writefln("\tshading 1");
		s.writefln("\tfacet 59.5");
		s.writefln("\tcolor 0.898 0.498 0.698");
		s.writefln("\tcolor_type 0");
		s.writefln("\tvertex 1 {");
		s.writefln("\t\t0.0000 23.9150 0.0000");
		s.writefln("\t}");
		s.writefln("\tface 1 {");
		s.writefln("\t\t3 V(67 58 59) M(1)");
		s.writefln("\t}");
		s.writefln("}");
		s.seekSet(0);
		auto mo = new MqoObject();
		mo.deserialize(s);
		auto d = new MemoryStream();
		mo.serialize(d);
		assert(s.size == d.size);
		assert(s.data == d.data);
	}
	
	string name;
	int depth, folding;
	Vector3 scale, rotation, translation;
	int patch, segment, visible, locking, shading;
	float facet;
	Vector3 color;
	int color_type, mirror, mirror_axis;
	float mirror_dis;
	int lathe, lathe_axis, lathe_seg;
	Vector3[] vertex;
	MqoFace[] face;
	
	void serialize(Stream writer)
	{
		writer.writefln("Object \"%s\" {", name);
		
		void puti(string name, int a) { writer.writefln("\t%s %d", name, a); }
		if (patch != 0) puti("patch", patch);
		if (patch == 3) puti("segment", segment);
		puti("visible", visible);
		puti("locking", locking);
		puti("shading", shading);
		writer.writefln("\tfacet %.1f", facet);
		writer.writefln("\tcolor %.3f %.3f %.3f", color.x, color.y, color.z);
		puti("color_type", color_type);
		if (mirror != 0) puti("mirror", mirror);
		if (mirror != 0) puti("mirror_axis", mirror_axis);
		if (mirror == 2 && mirror_dis != 0.0) writer.writefln("\tmirror_dis %.3f", mirror_dis);
		if (lathe != 0)
		{
			puti("lathe", lathe);
			puti("lathe_axis", lathe_axis);
			puti("lathe_seg", lathe_seg);
		}
		
		writer.writefln("\tvertex %d {", vertex.length);
		foreach (v; vertex) writer.writefln("\t\t%.4f %.4f %.4f", v.x, v.y, v.z);
		writer.writefln("\t}");
		
		writer.writefln("\tface %d {", face.length);
		foreach (v; face) v.serialize(writer);
		writer.writefln("\t}");
		
		writer.writefln("}");
	}
	
	void deserialize(Stream reader)
	{
		auto line = cast(string)reader.readLine(); // d2
		if (line.split()[0] != "Object") throw new Exception("not Object");
		name = fromMBSz(getObjName(line).toStringz()); // d1
		//name = fromMBSz(cast(invariant)getObjName(line).toStringz()); // d2
		assert(name != "");
		
		auto t = new MqoToken();
		while (true)
		{
			line = cast(string)reader.readLine(); // d2
			if (line.split()[0] == "vertex") break;
			t ~= line.split();
		}
		
		depth = t.geti("depth");
		folding = t.geti("folding");
		scale = t.getv3("scale");
		rotation = t.getv3("rotation");
		translation = t.getv3("translation");
		patch = t.geti("patch");
		segment = t.geti("segment");
		visible = t.geti("visible");
		locking = t.geti("locking");
		shading = t.geti("shading");
		facet = t.getf("facet");
		color = t.getv3("color");
		color_type = t.geti("color_type");
		mirror = t.geti("mirror");
		mirror_axis = t.geti("mirror_axis");
		mirror_dis = t.getf("mirror_dis");
		lathe = t.geti("lathe");
		lathe_axis = t.geti("lathe_axis");
		lathe_seg = t.geti("lathe_seg");
		
		vertex = readVertices(line, reader);
		
		auto faceLine = (cast(string)reader.readLine()).split(); // d2
		if (faceLine[0] != "face") throw new Error("not face");
		face = new MqoFace[faceLine[1].toInt()];
		foreach (ref v; face)
		{
			v = new MqoFace();
			v.deserialize(reader);
		}
		if ("\t}" != reader.readLine()) throw new Error("not face end");
		
		if ("}" != reader.readLine()) throw new Error("not Object end");
	}
	
	unittest
	{
		assert("obj1[0]" == getObjName(`Object "obj1[0]" {`));
		assert("obj1[0, 0]" == getObjName(`Object "obj1[0, 0]" {`));
	}
	
	private static string getObjName(string a)
	{
		return a[8..$ - 3];
	}
	
	private Vector[] readVertices(string vertexLine, Stream reader)
	{
		auto result = new Vector[vertexLine.split()[1].toInt()];
		for (int i = 0; i < result.length; i++)
		{
			auto a = (cast(string)reader.readLine()).split(); // d2
			result[i] = vector(a[0].toFloat(), a[1].toFloat(), a[2].toFloat());
		}
		if ("\t}" != reader.readLine()) throw new Error("not vertex end");
		return result;
	}
}

class MqoFace : Serializer
{
	unittest
	{
		auto s = new MemoryStream();
		s.writefln("\t\t3 V(0 2 1) M(0) UV(0.06250 0.00000 0.12500 0.12500 0.00000 0.12500)");
		s.writefln("\t\t4 V(1 2 10 9) M(0) UV(0.00000 0.12500 0.12500 0.12500 0.12500 0.25000 0.00000 0.25000)");
		s.writefln("\t\t3 V(67 58 59) M(1)");
		auto a = new MqoFace();
		auto b = new MqoFace();
		auto c = new MqoFace();
		s.seekSet(0);
		a.deserialize(s);
		b.deserialize(s);
		c.deserialize(s);
		auto d = new MemoryStream();
		a.serialize(d);
		b.serialize(d);
		c.serialize(d);
		assert(s.size == d.size);
		assert(s.data == d.data);
	}
	
	unittest
	{
		auto s = new MemoryStream();
		s.writefln("\t\t3 V(637 636 638) M(0) UV(0.90787 0.14123 0.91298 0.15459 0.90787 0.14123) COL(4279111182 4282861383 4282729797)");
		s.writefln("\t\t4 V(640 639 637 638) M(0) UV(0.90536 0.14085 0.90536 0.14085 0.90787 0.14123 0.90787 0.14123) COL(4282598211 4279308561 4279111182 4282729797)");
		auto a = new MqoFace();
		auto b = new MqoFace();
		s.seekSet(0);
		a.deserialize(s);
		b.deserialize(s);
		assert(a.vlength == 3);
		assert(a.V == [637, 636, 638]);
		assert(b.vlength == 4);
		assert(b.V == [640, 639, 637, 638]);
	}
	
	invariant()
	{
		assert(vlength == 0 || vlength == 2 || vlength == 3 || vlength == 4);
		assert(vlength == V.length);
		assert(vlength == UV.length);
	}
	
	int vlength;
	int[] V;
	int M = -1;
	Vector2[] UV;
	
	void serialize(Stream writer)
	{
		writer.writef("\t\t%d V(%d %d", vlength, V[0], V[1]);
		if (3 <= vlength) writer.writef(" %d", V[2]);
		if (4 == vlength) writer.writef(" %d", V[3]);
		writer.writef(")");
		if (M == -1 || vlength == 2)
		{
			writer.writefln("");
			return;
		}
		writer.writef(" M(%d)", M);
		if (vector(0, 0) == UV[0] && UV[0] == UV[1] && UV[1] == UV[2])
		{
			if (vlength == 3 || UV[2] == UV[3])
			{
				writer.writefln("");
				return;
			}
		}
		writer.writef(
			" UV(%.5f %.5f %.5f %.5f %.5f %.5f",
			UV[0].x, UV[0].y,
			UV[1].x, UV[1].y,
			UV[2].x, UV[2].y
		);
		if (vlength == 4) writer.writef(" %.5f %.5f", UV[3].x, UV[3].y);
		writer.writefln(")");
	}
	
	void deserialize(Stream reader)
	{
		auto line = cast(string)reader.readLine(); // d2
		line = line.replace("(", " ");
		line = line.replace(")", " ");
		auto t = new MqoToken();
		t ~= line.split();
		vlength = t[0].toInt();
		V = t.getia("V", vlength);
		M = t.geti("M");
		UV = t.getuv(vlength);
	}
}

class Mqo : Serializer
{
	MqoScene scene;
	MqoMaterial[] material;
	MqoObject[] object;
	
	void serialize(Stream writer)
	{
		writer.writefln("Metasequoia Document");
		writer.writefln("Format Text Ver 1.0");
		writer.writefln("");
		scene.serialize(writer);
		writer.writefln("Material %d {", material.length);
		foreach (v; material) v.serialize(writer);
		writer.writefln("}");
		foreach (v; object) v.serialize(writer);
		// Blobは書かない
		writer.writefln("Eof");
	}
	
	void deserialize(Stream reader)
	{
		if ("Metasequoia Document" != reader.readLine()) throw new Error("not mqo");
		reader.readLine();
		reader.readLine();
		scene = new MqoScene();
		scene.deserialize(reader);
		auto c = reader.getc();
		if (c == 'B') // BackImage
		{
			while ("}" != reader.readLine()) {}
			c = reader.getc();
		}
		if (c == 'M') // Material
		{
			material = new MqoMaterial[(cast(string)reader.readLine()).split()[1].toInt()]; // d2
			foreach (ref v; material)
			{
				v = new MqoMaterial();
				v.deserialize(reader);
			}
			if ("}" != reader.readLine()) throw new Error("not Material end");
			c = reader.getc();
		}
		while (c == 'O') // Object
		{
			reader.ungetc(c);
			object ~= new MqoObject();
			object[$ - 1].deserialize(reader);
			c = reader.getc();
		}
		// Blobは読まない
	}
}

private class MqoToken
{
	private string[] list;
	void opCatAssign(string[] a) { list ~= a; }
	string opIndex(size_t i) { return list[i]; }
	
	private int find(string name)
	{
		foreach (i, v; list)
		{
			if (name == v) return i;
		}
		return -1;
	}
	
	int geti(string name)
	{
		auto i = find(name);
		if (i == -1) return 0;
		return list[i + 1].toInt();
	}
	
	float getf(string name)
	{
		auto i = find(name);
		if (i == -1) return 0.0;
		return list[i + 1].toFloat();
	}
	
	unittest
	{
		auto t = new MqoToken();
		t.list ~= split(`shader 3  spc 0.000  power 5.00  tex "test0.png" `);
		assert("test0.png" == t.gets("tex"));
	}
	
	string gets(string name)
	{
		auto i = find(name);
		if (i == -1) return "";
		return list[i + 1].split("\"")[1];
	}
	
	Vector getv4(string name)
	{
		auto i = find(name);
		if (i == -1) return vector();
		return vector(
			list[i + 1].toFloat(),
			list[i + 2].toFloat(),
			list[i + 3].toFloat(),
			list[i + 4].toFloat()
		);
	}
	
	Vector getv3(string name)
	{
		auto i = find(name);
		if (i == -1) return vector();
		return vector(
			list[i + 1].toFloat(),
			list[i + 2].toFloat(),
			list[i + 3].toFloat()
		);
	}
	
	int[] getia(string name, int length)
	{
		auto result = new int[length];
		auto b = find(name);
		if (b == -1) throw new Error(name);
		b += 1;
		for (int i = 0; i < result.length; i++) result[i] = list[b + i].toInt();
		return result;
	}
	
	Vector[] getuv(int length)
	{
		auto result = new Vector[length];
		result[] = vector(0.0, 0.0);
		auto b = find("UV");
		if (b == -1) return result;
		b += 1;
		for (int i = 0; i < result.length; i++)
		{
			result[i] = vector(list[b + 2 * i].toFloat(), list[b + 1 + 2 * i].toFloat());
		}
		return result;
	}
}

class MqoFlatNormal : Mqo
{
	Vector[][][] normal; // normal[objectIndex][faceIndex][vIndex]
	
	override void deserialize(Stream reader)
	out
	{
		assert(checkNormalSize());
	}
	body
	{
		super.deserialize(reader);
		
		normal.length = object.length;
		foreach (i, ref v; normal) v = createObjectNormals(object[i]);
	}
	
	private Vector[][] createObjectNormals(MqoObject obj)
	{
		Vector[][] result;
		result.length = obj.face.length;
		foreach (i, ref v; result) v = createFaceNormals(obj.vertex, obj.face[i].V);
		return result;
	}
	
	private Vector[] createFaceNormals(Vector[] vertices, int[] v)
	{
		auto result = new Vector[v.length];
		try
		{
			switch (v.length)
			{
			case 2: result[] = Vector(0.0, 0.0, 1.0); break;
			case 3: result[] = getNormal(vertices[v[0]], vertices[v[1]], vertices[v[2]]); break;
			case 4:
				auto a = getNormal(vertices[v[0]], vertices[v[1]], vertices[v[2]]);
				auto b = getNormal(vertices[v[1]], vertices[v[2]], vertices[v[3]]);
				auto c = getNormal(vertices[v[2]], vertices[v[3]], vertices[v[0]]);
				auto d = getNormal(vertices[v[3]], vertices[v[0]], vertices[v[1]]);
				assert(scalar(a + b + c + d) != 0);
				result[] = normalize(a + b + c + d);
				break;
			}
		}
		catch (GetNormalException e)
		{
			result[] = Vector(0.0, 0.0, 1.0);
		}
		return result;
	}
	
	class GetNormalException {}
	
	private Vector getNormal(Vector a, Vector b, Vector c)
	{
		auto n = cross(b - a, c - a);
		if (scalar(n) == 0.0) throw new GetNormalException();
		assert(scalar(n) != 0.0, a.toString() ~ b.toString() ~ c.toString());
		return normalize(n);
	}
	
	protected bool checkNormalSize() // mqoの要素とnormalのサイズが一致しているか確認
	{
		if (normal.length != object.length) return false;
		for (int i = 0; i < normal.length; i++)
		{
			if (normal[i].length != object[i].face.length) return false;
			for (int j = 0; j < normal[i].length; j++)
			{
				if (normal[i][j].length != object[i].face[j].vlength) return false;
			}
		}
		return true;
	}
}

class IteratableMqo : MqoFlatNormal
{
	class Iterator
	{
		uint objectIndex, faceIndex, vIndex;
		
		this(uint objectIndex, uint faceIndex, uint vIndex)
		{
			this.objectIndex = objectIndex;
			this.faceIndex = faceIndex;
			this.vIndex = vIndex;
		}
		
		Vector getNormal()
		{
			return normal[objectIndex][faceIndex][vIndex];
		}
		
		void setNormal(Vector a)
		{
			normal[objectIndex][faceIndex][vIndex] = a;
		}
		
		MqoObject getObject()
		{
			return object[objectIndex];
		}
		
		MqoFace getFace()
		{
			return getObject().face[faceIndex];
		}
		
		int getVertexIndex()
		{
			return getFace().V[vIndex];
		}
		
		Vector getTexCoord()
		{
			return getFace().UV[vIndex];
		}
		
		Vector getPosition()
		{
			return getObject().vertex[getVertexIndex()];
		}
		
		Vector getColor()
		{
			return hasMaterial ? material[getFace().M].col : Color.WHITE;
		}
		
		string getTextureFileName()
		{
			return hasMaterial ? material[getFace().M].tex : "";
		}
		
		bool hasMaterial()
		{
			return material.length != 0;
		}
		
		string getMaterialName()
		{
			return hasMaterial ? material[getFace().M].name : "";
		}
	}
}

class MqoSmoothNormal : IteratableMqo
{
	override void deserialize(Stream reader)
	{
		super.deserialize(reader);
		smooth();
	}
	
	private void smooth()
	in
	{
		assert(checkNormalSize());
	}
	body
	{
		for (int i = 0; i < object.length; i++)
		{
			if (object[i].face.length == 0) continue;
			if (object[i].shading == 0) continue; // shading 0 or 1, smoothのoff or on
			foreach (v; createTable(i)) // vertexIndex -> faceIndex, vIndex
			{
				if (v.length <= 1) continue;
				while (smooth(object[i].facet, v)) {}
			}
		}
	}
	
	// 高速に同じindexの頂点を見つけるのに使う
	private Iterator[][] createTable(uint objectIndex)
	{
		Iterator[][] result;
		auto obj = object[objectIndex];
		result.length = obj.vertex.length;
		for (int faceIndex = 0; faceIndex < obj.face.length; faceIndex++)
		{
			auto face = obj.face[faceIndex];
			for (int vIndex = 0; vIndex < face.vlength; vIndex++)
			{
				auto vertexIndex = face.V[vIndex];
				result[vertexIndex] ~= new Iterator(objectIndex, faceIndex, vIndex);
			}
		}
		return result;
	}
	
	// 全ての組み合わせで角度を調べてsmooth、smoothできなかったらfalse
	private bool smooth(float facet, Iterator[] keys)
	{
		for (int i = 0; i < keys.length - 1; i++)
		{
			for (int j = i + 1; j < keys.length; j++)
			{
				auto n1 = keys[i].getNormal();
				auto n2 = keys[j].getNormal();
				if (scalar(n1 + n2) == 0.0) continue; // 真逆
				auto d = dot(n1, n2);
				
				if (d == -1.0) continue; // 真逆
				
				// ある程度近い法線なら飛ばす
				// 1.0に近いほど丸くなるが処理に時間がかかるようになる
				if (d > 0.999) continue; // 0.99だと少し荒い
				
				// facet[0, 180], 角度(n1, n2) <= facetでsmooth
				if (cos(toRadian(facet)) <= d)
				{
					assert(scalar(n1 + n2) != 0);
					auto n3 = normalize(n1 + n2);
					keys[i].setNormal(n3);
					keys[j].setNormal(n3);
					return true;
				}
			}
		}
		return false;
	}
}
