module coneneko.mqotranslator;
import
	std.stream,
	std.math,
	std.stdio,
	std.string,
	std.conv,
	coneneko.modeldata,
	coneneko.mqo,
	coneneko.math,
	coneneko.rgba,
	coneneko.pcnta,
	coneneko.clothpcnta;

class MqoTextures
{
	private const string[] fileNames;
	
	this(MqoMaterial[] materials)
	out
	{
		assert(fileNames.length != 0);
	}
	body
	{
		bool[string] map;
		foreach (v; materials) map[v.tex] = true;
		fileNames = map.keys;
		fileNames.sort;
		if (fileNames.length == 0) fileNames ~= "";
	}
	
	size_t getIndex(string key)
	{
		foreach (i, v; fileNames) if (v == key) return i;
		throw new Error("MqoTexture.getIndex: " ~ key);
	}
	
	Rgba[] createRgbaArray()
	{
		Rgba[] result;
		auto white = new WhiteRgba();
		foreach (v; fileNames) result ~= v == "" ? white : correctTextureSize(new Rgba(v));
		return result;
	}
}

///
class MqoTranslator : MqoSmoothNormal, Translator
{
	protected ModelData owner;
	protected MqoTextures textures;
	
	void initialize(ModelData owner)
	{
		this.owner = owner;
	}
	
	override void deserialize(Stream reader)
	in
	{
		assert(owner !is null);
	}
	body
	{
		super.deserialize(reader);
		textures = new MqoTextures(material);
		owner.textures = textures.createRgbaArray();
		owner.base = createBase();
	}
	
	private BasicPcnta createBase()
	{
		auto result = new BasicPcnta();
		foreach (v; new Iteration())
		{
			if (!v.getObject().visible) continue;
			result.addPcnt(
				textures.getIndex(v.getTextureFileName()),
				v.getPosition(),
				v.getColor(),
				v.getNormal(),
				v.getTexCoord()
			);
		}
		return result;
	}
	
	class Iteration // コストは低いので多用してかまわない
	{
		int opApply(int delegate(ref Iterator) dg)
		{
			Iterator i = new Iterator(0, 0, 0);
			for (i.objectIndex = 0; i.objectIndex < object.length; i.objectIndex++)
			{
				auto object = object[i.objectIndex];
				for (i.faceIndex = 0; i.faceIndex < object.face.length; i.faceIndex++)
				{
					auto face = object.face[i.faceIndex];
					if (face.vlength == 2) continue;
					foreach (v; face.vlength == 3 ? [0, 1, 2] : [0, 1, 2, 0, 2, 3])
					{
						i.vIndex = v;
						if (auto r = dg(i)) return r;
					}
				}
			}
			return 0;
		}
	}
}

/**
	メタセコイア、オブジェクトの名前objname, objnameは任意
	option ... objname[uint index]
	morph ... objname[uint index, uint t]
	cloth ... cloth(uint index, float spring, float resistance)
	indexは1以上、clothはanchorでindexはoptionに対して
*/
class MqoExTranslator : MqoTranslator
{
	void deserialize(Stream reader)
	{
		super.deserialize(reader);
		owner.options = createOptions();
		owner.morphs = createMorphs();
		owner.clothes = createClothes();
	}
	
	private BasicPcnta[size_t] createOptions()
	{
		BasicPcnta[size_t] result;
		foreach (v; new Iteration())
		{
			auto index = getOptionIndex(v.getObject().name);
			if (index == -1) continue;
			if (index == 0 || index <= -2) throw new Error("MqoExTranslator.createOptions");
			if (!(index in result)) result[index] = new BasicPcnta();
			result[index].addPcnt(
				textures.getIndex(v.getTextureFileName()),
				v.getPosition(),
				v.getColor(),
				v.getNormal(),
				v.getTexCoord()
			);
		}
		return result;
	}
	
	unittest
	{
		assert(getOptionIndex("hoge") == -1);
		assert(getOptionIndex("hoge[0]") == 0);
		assert(getOptionIndex("obj1[0]") == 0);
		assert(getOptionIndex("[1]") == 1);
		assert(getOptionIndex("obj1[0, 1]") == -1);
		assert(getOptionIndex("[a]") == -1);
		assert(getOptionIndex("[l]") == -1);
	}
	
	private static int getOptionIndex(string name)
	{
		auto s0 = name.removechars("]");
		auto s1 = s0.split("[");
		if (s1.length < 1) return -1;
		if (!s1[$ - 1].isNumeric()) return -1;
		if (s1[$ - 1] == "l") return -1; // なぜかlだけ通り抜ける
		return s1[$ - 1].toInt();
	}
	
	private MorphPcnta[size_t] createMorphs()
	{
		MorphPcnta[size_t] result;
		uint index, target;
		foreach (v; new Iteration())
		{
			if (!getMorphIndex(v.getObject().name, index, target)) continue;
			if (index == 0) throw new Error("MqoExTranslator.createMorphs");
			if (!(index in result)) result[index] = new MorphPcnta();
			result[index][target].addPcnt(
				textures.getIndex(v.getTextureFileName()),
				v.getPosition(),
				v.getColor(),
				v.getNormal(),
				v.getTexCoord()
			);
		}
		return result;
	}
	
	unittest
	{
		uint index, target;
		assert(!getMorphIndex("hoge", index, target));
		assert(getMorphIndex("hoge[1, 2]", index, target));
		assert(index == 1 && target == 2);
		assert(getMorphIndex("obj1[0, 59]", index, target));
		assert(index == 0 && target == 59);
		assert(getMorphIndex("[3, 4]", index, target));
		assert(index == 3 && target == 4);
		assert(!getMorphIndex("obj1[0]", index, target));
	}
	
	private static bool getMorphIndex(string name, out uint index, out uint target)
	{
		auto s0 = name.replace("[", " ");
		auto s1 = s0.replace("]", " ");
		auto s2 = s1.removechars(",");
		auto s3 = s2.split();
		if (s3.length < 2) return false;
		if (!s3[$ - 1].isNumeric() || !s3[$ - 2].isNumeric()) return false;
		target = s3[$ - 1].toUint();
		index = s3[$ - 2].toUint();
		return true;
	}
	
	private ClothPcnta[size_t] createClothes()
	{
		ClothPcntaBuilder[size_t] builders;
		foreach (i; new Iteration())
		{
			float spring, resistance;
			auto index = getClothIndex(i.getObject().name, spring, resistance);
			if (index == -1) continue;
			if (index == 0 || index <= -2) throw new Error("MqoExTranslator.createClothes");
			if (!(index in builders))
			{
				builders[index] = new ClothPcntaBuilder(spring, resistance, owner.options[index]);
				owner.options.remove(index);
			}
			builders[index].areaTriangles ~= i.getPosition();
		}
		ClothPcnta[size_t] result;
		foreach (v; builders.keys) result[v] = builders[v].build();
		return result;
	}
	
	unittest
	{
		float spring, resistance;
		assert(getClothIndex("hoge(0, 1, 2)", spring, resistance) == -1);
		assert(getClothIndex("cloth(1, 1.5, 2.6)", spring, resistance) == 1);
		assert(spring == 1.5f && resistance == 2.6f);
	}
	
	private static int getClothIndex(string name, out float spring, out float resistance)
	{
		auto a = name.replace("(", " ");
		a = a.replace(")", " ");
		a = a.removechars(",");
		auto b = a.split();
		if (b.length != 4) return -1;
		if (b[0] != "cloth" || !b[1].isNumeric() || !b[2].isNumeric() || !b[3].isNumeric()) return -1;
		spring = b[2].toFloat();
		resistance = b[3].toFloat();
		return b[1].toInt();
	}
}

class ClothPcntaBuilder
{
	private const float spring, resistance;
	private BasicPcnta basic;
	Vector[] areaTriangles;
	
	this(float spring, float resistance, BasicPcnta basic)
	{
		this.spring = spring;
		this.resistance = resistance;
		this.basic = basic;
	}
	
	ClothPcnta build()
	{
		return new ClothPcnta(
			basic,
			spring,
			resistance,
			areaTriangles
		);
	}
}
