module coneneko.modeldata;
import std.stream, std.cstream, std.zlib;
import coneneko.math, coneneko.rgba, coneneko.serializer, coneneko.pcnta, coneneko.clothpcnta;

///
interface Translator : Serializer
{
	void initialize(ModelData owner); ///
}

/// 汎用、必要に応じて拡張する予定
class ModelData : Serializer
{
	invariant()
	{
		assert(_translator !is null);
		foreach (v; textures) assert(v !is null);
	}
	
	Rgba[] textures;
	BasicPcnta base;
	BasicPcnta[size_t] options;
	MorphPcnta[size_t] morphs;
	ClothPcnta[size_t] clothes;
	MotionData[] motions; ///
	SkeletonData skeleton; ///
	private Translator _translator;
	
	///
	this()
	{
		_translator = new ZlibTranslator(); // invariant回避用
		initialize();
	}
	
	void initialize()
	{
		textures = null;
		base = null;
		options = null;
		morphs = null;
		clothes = null;
		motions = null;
		skeleton = null;
		translator = new ZlibTranslator();
	}
	
	void translator(Translator a) ///
	{
		a.initialize(this);
		_translator = a;
	}
	
	Translator translator() ///
	{
		return _translator;
	}
	
	void serialize(Stream writer)
	{
		_translator.serialize(writer);
	}
	
	void deserialize(Stream reader)
	{
		_translator.deserialize(reader);
	}
}

/// ModelDataのデフォルト
class ZlibTranslator : Translator
{
	private ModelData owner;
	void initialize(ModelData owner) { this.owner = owner; }
	private const HEADER = "ZlibTranslator";
	
	void serialize(Stream writer)
	in
	{
		assert(owner !is null);
	}
	body
	{
		auto buffer = new MemoryStream();
		
		buffer.write(owner.textures.length);
		foreach (v; owner.textures) v.serialize(buffer);
		
		if (owner.base !is null)
		{
			buffer.write(cast(ubyte)true);
			owner.base.serialize(buffer);
		}
		else buffer.write(cast(ubyte)false);
		
		writeAnyMap(buffer, owner.options);
		writeAnyMap(buffer, owner.morphs);
		writeAnyMap(buffer, owner.clothes);
		
		buffer.write(owner.motions.length);
		foreach (v; owner.motions) v.serialize(buffer);
		
		if (owner.skeleton !is null)
		{
			buffer.write(cast(ubyte)true);
			owner.skeleton.serialize(buffer);
		}
		else buffer.write(cast(ubyte)false);
		
		auto compressedData = cast(ubyte[])compress(buffer.data);
		with (writer)
		{
			write(cast(char[])HEADER); // d2
			write(compressedData.length);
			write(compressedData);
		}
	}
	
	private void writeAnyMap(T)(Stream writer, T a)
	{
		writer.write(a.length);
		foreach (key; a.keys)
		{
			writer.write(key);
			a[key].serialize(writer);
		}
	}
	
	void deserialize(Stream reader)
	in
	{
		assert(owner !is null);
	}
	body
	{
		ubyte[] compressedData;
		with (reader)
		{
			char[] header;
			reader.read(header);
			if (header != HEADER) throw new Error("ZlibTranslator.deserialize");
			compressedData.length = readSizeT(reader);
			reader.read(compressedData);
		}
		auto buffer = new MemoryStream(cast(ubyte[])uncompress(compressedData));
		
		owner.textures.length = readSizeT(buffer);
		foreach (ref v; owner.textures)
		{
			v = new Rgba();
			v.deserialize(buffer);
		}
		
		ubyte hasBasic;
		buffer.read(hasBasic);
		if (hasBasic != 0)
		{
			owner.base = new BasicPcnta();
			owner.base.deserialize(buffer);
		}
		
		readAnyMap(buffer, owner.options);
		readAnyMap(buffer, owner.morphs);
		readAnyMap(buffer, owner.clothes);
		
		owner.motions.length = readSizeT(buffer);
		foreach (ref v; owner.motions)
		{
			v = new MotionData();
			v.deserialize(buffer);
		}
		
		ubyte hasSkeleton;
		buffer.read(hasSkeleton);
		if (hasSkeleton != 0)
		{
			owner.skeleton = new SkeletonData();
			owner.skeleton.deserialize(buffer);
		}
	}
	
	private size_t readSizeT(Stream reader)
	{
		size_t result;
		reader.read(result);
		return result;
	}
	
	private void readAnyMap(T)(Stream reader, ref T[size_t] a)
	{
		size_t length;
		reader.read(length);
		for (int i = 0; i < length; i++)
		{
			size_t key;
			reader.read(key);
			a[key] = new T();
			a[key].deserialize(reader);
		}
	}
}

///
class MotionData : Serializer
{
	invariant()
	{
		assert(positionMap.length == poseMap.length);
		if (1 <= poseMap.length)
		{
			auto len = poseMap.values[0].length;
			foreach (v; poseMap.values) assert(v.length == len);
		}
	}
	
	alias Vector Quaternion; ///
	alias Quaternion[] Pose; ///
	Vector[size_t] positionMap; ///
	Pose[size_t] poseMap; ///
	private const HEADER = "MotionData";
	
	void serialize(Stream writer)
	{
		with (new SerializeStream(writer))
		{
			write(cast(char[])HEADER); // d2
			write(positionMap.length);
			foreach (key; positionMap.keys)
			{
				write(key);
				write(positionMap[key]);
			}
			write(poseMap.length);
			foreach (key; poseMap.keys)
			{
				write(key);
				write(poseMap[key]);
			}
		}
	}
	
	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();
				positionMap[key] = readVector();
			}
			len = readSizeT();
			for (int i = 0; i < len; i++)
			{
				size_t key = readSizeT();
				poseMap[key] = readVectors();
			}
		}
	}
}

///
class SkeletonData : Serializer
{
	invariant()
	{
		assert(boneTriangles.length / 3 == boneRouteIndicesArray.length);
		
		foreach (i, v; boneRouteIndicesArray)
		{
			assert(v[0] == i);
			assert(v[$ - 1] == 0);
		}
	}
	
	Vector[] boneTriangles; ///
	uint[][] boneRouteIndicesArray; /// 最初はindex最後は0のarray
	private const HEADER = "SkeletonData";
	
	void serialize(Stream writer)
	{
		with (new SerializeStream(writer))
		{
			write(cast(char[])HEADER); // d2
			write(boneTriangles);
			write(boneRouteIndicesArray.length);
			foreach (v; boneRouteIndicesArray) write(v);
		}
	}
	
	void deserialize(Stream reader)
	{
		with (new SerializeStream(reader))
		{
			throwIfHeaderError(HEADER);
			boneTriangles = readVectors();
			boneRouteIndicesArray.length = readSizeT();
			foreach (ref v; boneRouteIndicesArray) v = readUints();
		}
	}
}
