module coneneko.pcnta;
import coneneko.math, coneneko.serializer, std.stream;

private Vector selectMax(Vector a, Vector b)
{
	return Vector(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
}

private Vector selectMin(Vector a, Vector b)
{
	return Vector(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z));
}

/// pcnta, 34323に固定
class Pcnta : Serializer
{
	Vector[] positions, colors, normals, texCoords, attributes;
	size_t length() { return positions.length; }
	bool hasAttribute() { return attributes.length != 0; }
	private const HEADER = "Pcnta";
	
	bool canUseAsPcntaBuffer()
	{
		auto len = positions.length;
		if (hasAttribute && len != attributes.length) return false;
		return len == colors.length
			&& len == normals.length
			&& len == texCoords.length;
	}
	
	unittest
	{
		auto a = new Pcnta();
		a.addPcnt(vector(0, 1, 2), vector(3, 4, 5, 6), vector(7, 8, 9), vector(10, 11));
		auto s = new MemoryStream();
		a.serialize(s);
		s.seekSet(0);
		auto b = new Pcnta();
		b.deserialize(s);
		assert(a == b);
	}
	
	bool opEquals(Pcnta a)
	{
		return positions == a.positions
			&& colors == a.colors
			&& normals == a.normals
			&& texCoords == a.texCoords
			&& attributes == a.attributes;
	}
	
	void serialize(Stream writer)
	{
		with (new SerializeStream(writer))
		{
			write(cast(char[])HEADER); // d2
			write(positions);
			write(colors);
			write(normals);
			write(texCoords);
			write(hasAttribute);
			if (hasAttribute) write(attributes);
		}
	}
	
	void deserialize(Stream reader)
	{
		with (new SerializeStream(reader))
		{
			throwIfHeaderError(HEADER);
			positions = readVectors();
			colors = readVectors();
			normals = readVectors();
			texCoords = readVectors();
			if (readBool()) attributes = readVectors();
		}
	}
	
	void addPcnt(Vector position, Vector color, Vector normal, Vector texCoord)
	{
		positions ~= position;
		colors ~= color;
		normals ~= normal;
		texCoords ~= texCoord;
	}
	
	Vector max()
	in
	{
		assert(positions.length != 0);
	}
	body
	{
		Vector result = positions[0];
		foreach (v; positions[1..$]) result = selectMax(result, v);
		return result;
	}
	
	Vector min()
	in
	{
		assert(positions.length != 0);
	}
	body
	{
		Vector result = positions[0];
		foreach (v; positions[1..$]) result = selectMin(result, v);
		return result;
	}
}

class BasicPcnta : Serializer
{
	protected Pcnta[size_t] pcntaMap;
	private const HEADER = "BasicPcnta";
	
	Pcnta opIndex(size_t textureIndex)
	{
		return pcntaMap[textureIndex];
	}
	
	size_t[] keys()
	{
		return pcntaMap.keys;
	}
	
	bool isSkinMesh()
	{
		return pcntaMap.length != 0 && pcntaMap.values[0].hasAttribute;
	}
	
	void addPcnt(size_t textureIndex, Vector position, Vector color, Vector normal, Vector texCoord)
	{
		if (!(textureIndex in pcntaMap)) pcntaMap[textureIndex] = new Pcnta();
		pcntaMap[textureIndex].addPcnt(position, color, normal, texCoord);
	}
	
	void addA(size_t textureIndex, Vector attribute)
	{
		if (!(textureIndex in pcntaMap)) pcntaMap[textureIndex] = new Pcnta();
		pcntaMap[textureIndex].attributes ~= attribute;
	}
	
	bool hasAttribute()
	{
		return 1 <= pcntaMap.length && pcntaMap.values[0].hasAttribute;
	}
	
	Vector position()
	{
		return (max + min) * 0.5;
	}
	
	Vector max()
	in
	{
		assert(pcntaMap.length != 0);
	}
	body
	{
		Vector result = pcntaMap.values[0].max;
		foreach (v; pcntaMap.values[1..$]) result = selectMax(result, v.max);
		return result;
	}
	
	Vector min()
	in
	{
		assert(pcntaMap.length != 0);
	}
	body
	{
		Vector result = pcntaMap.values[0].min;
		foreach (v; pcntaMap.values[1..$]) result = selectMin(result, v.min);
		return result;
	}
	
	void serialize(Stream writer)
	{
		writer.write(cast(char[])HEADER); // d2
		writer.write(pcntaMap.length);
		foreach (key; pcntaMap.keys)
		{
			writer.write(key);
			pcntaMap[key].serialize(writer);
		}
	}
	
	void deserialize(Stream reader)
	{
		with (new SerializeStream(reader))
		{
			throwIfHeaderError(HEADER);
			size_t len = readSizeT();
			for (int i = 0; i < len; i++)
			{
				size_t key = readSizeT();
				pcntaMap[key] = new Pcnta();
				pcntaMap[key].deserialize(reader);
			}
		}
	}
}

class MorphPcnta // TODO : Serializer
{
	private BasicPcnta[size_t] bpcntaMap;
	private const HEADER = "MorphPcnta";
	
	BasicPcnta opIndex(size_t target)
	{
		if (!(target in bpcntaMap)) bpcntaMap[target] = new BasicPcnta();
		return bpcntaMap[target];
	}
	
	size_t[] keys()
	{
		return bpcntaMap.keys;
	}
	
	Vector position()
	{
		return (max + min) * 0.5;
	}
	
	Vector max()
	in
	{
		assert(bpcntaMap.length != 0);
	}
	body
	{
		Vector result = bpcntaMap.values[0].max;
		foreach (v; bpcntaMap.values[1..$]) result = selectMax(result, v.max);
		return result;
	}
	
	Vector min()
	in
	{
		assert(bpcntaMap.length != 0);
	}
	body
	{
		Vector result = bpcntaMap.values[0].min;
		foreach (v; bpcntaMap.values[1..$]) result = selectMin(result, v.min);
		return result;
	}
	
	void serialize(Stream writer)
	{
		writer.write(cast(char[])HEADER); // d2
		writer.write(bpcntaMap.length);
		foreach (key; bpcntaMap.keys)
		{
			writer.write(key);
			bpcntaMap[key].serialize(writer);
		}
	}
	
	void deserialize(Stream reader)
	{
		with (new SerializeStream(reader))
		{
			throwIfHeaderError(HEADER);
			size_t len = readSizeT();
			for (int i = 0; i < len; i++)
			{
				size_t key = readSizeT();
				bpcntaMap[key] = new BasicPcnta();
				bpcntaMap[key].deserialize(reader);
			}
		}
	}
}
