#include"LoadLWO.h"
#include"../FunctionEnum.h"
#include"ModelFileLoader/CLightWaveObject.h"

#include"../../auxiliary/CString.h"
#include"../../auxiliary/Debug/CTrace.h"

#include<map>

namespace Maid
{


using namespace StorageFunction;




struct PRIMITIVEMAP
{
	int					SurfaceNo;
	MySTL::vector<int>	PolygonNoList;
};

typedef MySTL::vector<PRIMITIVEMAP> PRIMITIVEMAPLIST;

PRIMITIVEMAPLIST CreatePrimitiveList( const MySTL::vector<CLightWaveObject::LAYER::PTAG>& ptaglist )
{
	typedef MySTL::map<int,MySTL::vector<int> > DATABASE;
	DATABASE data;

	for( int i=0; i<(int)ptaglist.size(); ++i )
	{
		const CLightWaveObject::LAYER::PTAG& ptag = ptaglist[i];

		if( ptag.Type!=CLightWaveObject::LAYER::PTAG::SURF ) { continue; }

		for( int j=0; j<(int)ptag.MapList.size(); ++j )
		{
			const CLightWaveObject::LAYER::PTAG::MAP& m = ptag.MapList[j];

			data[ m.TypeNo ].push_back(m.PolygonNo);
		}
	}

	//	これで map<サーフェス名,ポリゴン配列> のマップができた

	MySTL::vector<PRIMITIVEMAP> ret;

	for( DATABASE::iterator ite=data.begin(); ite!=data.end(); ++ite )
	{
		PRIMITIVEMAP map;

		map.SurfaceNo	  = ite->first;
		map.PolygonNoList = ite->second;
		ret.push_back( map );
	}

	return ret;
}

typedef MySTL::vector<VECTOR3DF> NORMALLIST;

NORMALLIST CreateNormal( const CLightWaveObject::LAYER& layer )
{
	//	ポリゴンの実態であるFACEを探しておく

	const CLightWaveObject::LAYER::POLS* pFace;
	for( int i = 0; i<(int)layer.Polygon.size(); ++i )
	{
		const CLightWaveObject::LAYER::POLS& poly = layer.Polygon[i];

		if( poly.Type!=CLightWaveObject::LAYER::POLS::FACE ) { continue; }

		pFace = &poly;
		break;
	}

	MySTL::vector<VECTOR3DF> norm(layer.PointList.size(),VECTOR3DF(0,0,0));

	//	↓各頂点が全ポリゴンで何回出てきているか？
	MySTL::vector<int> count( layer.PointList.size(), 0 );

	const CLightWaveObject::LAYER::PNTS& pnts = layer.PointList;

	for( int i=0; i<(int)layer.PolygonTagMapping.size(); ++i )
	{
		const CLightWaveObject::LAYER::PTAG& ptag = layer.PolygonTagMapping[i];

		if( ptag.Type!=CLightWaveObject::LAYER::PTAG::SURF ) { continue; }

		for( int j=0; j<(int)ptag.MapList.size(); ++j )
		{
			const CLightWaveObject::LAYER::PTAG::MAP& m = ptag.MapList[j];
			const CLightWaveObject::LAYER::POLS::INDEX& Polygon = pFace->Polygon[m.PolygonNo];

			MAID_ASSERT( Polygon.size()!=3, "３角形以外のポリゴンが見つかりました" );

			// Polygon の法線を求める
			// 求め方は http://www.dstorm.co.jp/products/lw8/developer/docs/normal.htm

			const VECTOR3DF v1(pnts[Polygon[0]],    pnts[Polygon[1]]);
			const VECTOR3DF v2(pnts[Polygon.back()],pnts[Polygon[0]]);
			const VECTOR3DF n = VectorCross(v1,v2);

			for( int k=0; k<(int)Polygon.size(); ++k )
			{
				const int no = Polygon[k];	//	←座標番号
				norm [no] += n;
				count[no] += 1;
			}
		}
	}

	//	全部出したので、平均を出す
	for( int i=0; i<(int)count.size(); ++i )
	{
		norm [i] /= float(count[i]);
		norm [i].Normalize();
		norm [i] *= -1;
	}

	return norm;
}

MySTL::vector<COLOR_A08R08G08B08I> CreateColor( const CLightWaveObject::LAYER& layer )
{
	MySTL::vector<COLOR_A32R32G32B32F> color(layer.PointList.size(),COLOR_A32R32G32B32F(1,1,1,1) );

	//	VMAP があったらそれを設定
	//	複数枚あることを考えて、全部乗算で行う

	const MySTL::vector<CLightWaveObject::LAYER::VMAP>& vmap = layer.VertexMap;

	bool IsEmpty = true;

	for( int i=0; i<(int)vmap.size(); ++i )
	{
		const CLightWaveObject::LAYER::VMAP& v = vmap[i];

		if( v.Type==CLightWaveObject::LAYER::VMAP::RGB )
		{
			IsEmpty = false;
			MySTL::map<int,CLightWaveObject::LAYER::VMAP::DATA>::const_iterator	ite;

			for( ite=v.Map.begin(); ite!=v.Map.end(); ++ite )
			{
				const int no = ite->first;
				const MySTL::vector<float>& val = ite->second.Value;

				color[no].SetR( color[no].GetR() * val[0] );
				color[no].SetG( color[no].GetG() * val[1] );
				color[no].SetB( color[no].GetB() * val[2] );
			}
		}
		ef( v.Type==CLightWaveObject::LAYER::VMAP::RGBA )
		{
			IsEmpty = false;
			for( int j=0; j<(int)v.Map.size(); ++j )
			{
				MySTL::map<int,CLightWaveObject::LAYER::VMAP::DATA>::const_iterator	ite;

				for( ite=v.Map.begin(); ite!=v.Map.end(); ++ite )
				{
					const int no = ite->first;
					const MySTL::vector<float>& val = ite->second.Value;

					color[no].SetR( color[no].GetR() * val[0] );
					color[no].SetG( color[no].GetG() * val[1] );
					color[no].SetB( color[no].GetB() * val[2] );
					color[no].SetA( color[no].GetA() * val[3] );
				}
			}
		}
	}

	if( IsEmpty ) { return MySTL::vector<COLOR_A08R08G08B08I>(); }

	MySTL::vector<COLOR_A08R08G08B08I> ret(color.size());

	for( int i=0; i<(int)color.size(); ++i )
	{
		ret[i] = color[i].ConvertA08R08G08B08I();
	}

	return ret;
}

const CLightWaveObject::SURFACE& FindSurface( int SurfaceNo, const CLightWaveObject::DOCUMENT& doc )
{
	const mstring name = doc.TagList[SurfaceNo];

	for( int i=0; i<(int)doc.SurfaceList.size(); ++i )
	{
		const CLightWaveObject::SURFACE& surf = doc.SurfaceList[i];
		if( surf.Name==name ) { return surf; }
	}

	MAID_ASSERT( true, "ここはおかしい" );
	return doc.SurfaceList[0];
}

const CLightWaveObject::LAYER::POLS& FindFaceList( const CLightWaveObject::LAYER& layer )
{
	for( int i=0; i<(int)layer.Polygon.size(); ++i )
	{
		if( layer.Polygon[i].Type == CLightWaveObject::LAYER::POLS::FACE ) { return layer.Polygon[i]; }
	}

	//	１レイヤー内に必ず１つは FACE があるはず
	MAID_ASSERT( true, "これはおかしい" );
	return layer.Polygon[0];
}


mstring FindBlockIMAP_UVMAPNAME( const CLightWaveObject::SURFACE& surf,
									   CLightWaveObject::SURFACE::BLOCK::BASE::CHAN chan )
{
	for( int i=0; i<(int)surf.BlockList.size(); ++i )
	{
		const CLightWaveObject::SURFACE::BLOCK& block = surf.BlockList[i];
		if( block.Base.Type!=CLightWaveObject::SURFACE::BLOCK::BASE::IMAP ) { continue; }
		if( block.Base.Channel!=chan ) { continue; }

		return block.ImageMap.UVMapName;
	}

	return mstring();
}

const CLightWaveObject::LAYER::VMAP& FindColorUV( const CLightWaveObject::SURFACE& surf, const CLightWaveObject::LAYER& layer )
{
	const mstring name = FindBlockIMAP_UVMAPNAME( surf, CLightWaveObject::SURFACE::BLOCK::BASE::COLR );

	if( !name.empty() )
	{
		for( int i=0; i<(int)layer.VertexMap.size(); ++i )
		{
			const CLightWaveObject::LAYER::VMAP& vmap = layer.VertexMap[i];

			if( vmap.Type!=CLightWaveObject::LAYER::VMAP::TXUV ) { continue; }
			if( vmap.Name==name ) { return vmap; }
		}
	}

	static CLightWaveObject::LAYER::VMAP ret;
	return ret;
}

const CLightWaveObject::LAYER::VMAD& FindColorUVD( const CLightWaveObject::SURFACE& surf, const CLightWaveObject::LAYER& layer )
{
	const mstring name = FindBlockIMAP_UVMAPNAME( surf, CLightWaveObject::SURFACE::BLOCK::BASE::COLR );

	if( !name.empty() )
	{
		for( int i=0; i<(int)layer.VertexMap.size(); ++i )
		{
			const CLightWaveObject::LAYER::VMAD& vmad = layer.DisVertexMap[i];

			if( vmad.Type!=CLightWaveObject::LAYER::VMAD::TXUV ) { continue; }
			if( vmad.Name!=name ) { continue; }

			return vmad;
		}
	}

	static CLightWaveObject::LAYER::VMAD ret;
	return ret;
}



const MySTL::vector<float>& FindVMAPValue( const CLightWaveObject::LAYER::VMAP& vmap, int ver )
{
	MySTL::map<int,CLightWaveObject::LAYER::VMAP::DATA>::const_iterator ite = vmap.Map.find(ver);
	
	if( ite!=vmap.Map.end() )
	{
		return ite->second.Value;
	}

	static MySTL::vector<float> ret;
	return ret;
}


const MySTL::vector<float>& FindVMADValue( const CLightWaveObject::LAYER::VMAD& vmad, int ver, int poly )
{
	for( int i=0; i<(int)vmad.Map.size(); ++i )
	{
		if( vmad.Map[i].VertexNo  != ver  ) { continue; }
		if( vmad.Map[i].PolygonNo != poly ) { continue; }

		return vmad.Map[i].Value;
	}

	static MySTL::vector<float> ret;
	return ret;
}

int FindBlockIMAP_ClipNo( const CLightWaveObject::SURFACE& surf,
						   CLightWaveObject::SURFACE::BLOCK::BASE::CHAN chan )
{
	for( int i=0; i<(int)surf.BlockList.size(); ++i )
	{
		const CLightWaveObject::SURFACE::BLOCK& block = surf.BlockList[i];
		if( block.Base.Type!=CLightWaveObject::SURFACE::BLOCK::BASE::IMAP ) { continue; }
		if( block.Base.Channel!=chan ) { continue; }

		return block.ImageMap.ImageNo;
	}

	return -1;
}

int FindColorTextureNo( const CLightWaveObject::SURFACE& surf )
{
	const int no = FindBlockIMAP_ClipNo( surf, CLightWaveObject::SURFACE::BLOCK::BASE::COLR );

	if( no<0 ) { return -1; }

	return no;
}






struct LWO_BINARY
{
	struct MESH
	{
		MySTL::vector<POINT3DF>				Point;
		MySTL::vector<VECTOR3DF>			Normal;

		struct PRIMITIVE
		{
			MySTL::vector<POINT2DF>		ColorUV;
			MySTL::vector<unt32>		IndexList;	//	トライアングルリストで入る

			int MaterialNo;
		};

		MySTL::vector<PRIMITIVE>	Primitive;

	//	頂点カラーは放置ヽ(´∀｀)ノ
	//	MySTL::vector<COLOR_A08R08G08B08I>	Color;

	/*	//	ボーン関係も当面放置ヽ(´∀｀)ノ
			//	頂点ウェイト
			MySTL::vector<float>	Weghit[] =
			{
				MySTL::vector<float>(Point.size(), 0.0f),
				MySTL::vector<float>(Point.size(), 0.0f),
				MySTL::vector<float>(Point.size(), 0.0f),
				MySTL::vector<float>(Point.size(), 0.0f),
			};
			//	ウェイトインデックス
			//	WeghitIndex[VertexNo] [0]==0xFF の場合、未使用
			MySTL::vector<unt32>	WeghitIndex(Point.size(),0xFFFFFFFF);
	*/

	};

	struct MATERIAL
	{
		enum
		{
			TEXTURE_NONE  = -1,	//	この属性にはテクスチャがない
			ENVELOPE_NONE = -1,	//	この属性にはエンベロープがない
		};

		int		ColorTextureNo;
		float	ColorRDefault;
		//	int ColorREnvelopeNo;
		float	ColorGDefault;
		//	int ColorGEnvelopeNo;
		float	ColorBDefault;
		//	int ColorBEnvelopeNo;

		float Trance;
	};

	struct TEXTURE
	{
		int No;
		mstring FilePath;
	};

	MySTL::vector<MESH>		Mesh;
	MySTL::vector<MATERIAL>	Material;
	MySTL::vector<TEXTURE>	Texture;
};



template<class T>
SPBINARY VectorConvert( const MySTL::vector<T>& Src )
{
	if( Src.empty() ) { return SPBINARY(); }

	const int len = (int)Src.size() * sizeof(T);

	SPBINARY pRet( new MySTL::vector<unt08> );

	pRet->resize( len );

	MySTL::vector<unt08>& Dst = *pRet;

	::memcpy( &(Dst[0]), &(Src[0]), len );

	return pRet;
}




SPSTORAGEFUNCTION_RETURNPARAM LoadLWO( const mstring& FileName, const SPSTORAGEFUNCTION_FUNCTIONPARAM& pParam )
{
	CLightWaveObject lwo;

	lwo.Load( FileName );

	//	読み終わったので変換作業
	CLightWaveObject::DOCUMENT& doc = lwo.m_Document;

	LWO_BINARY Binary;
	
	//	テクスチャ名の作成
	for( int i=0; i<(int)doc.ClipList.size(); ++i )
	{
		const CLightWaveObject::CLIP& clip = doc.ClipList[i];

		LWO_BINARY::TEXTURE tex;

		tex.No = clip.ID;
		tex.FilePath = clip.StillImage;

		Binary.Texture.push_back( tex );
	}


	//	マテリアルの作成
	for( int i=0; i<(int)doc.SurfaceList.size(); ++i )
	{
		const CLightWaveObject::SURFACE& surf = doc.SurfaceList[i];

		LWO_BINARY::MATERIAL mat;

		mat.ColorRDefault = surf.Color.Value.GetR();
		mat.ColorGDefault = surf.Color.Value.GetG();
		mat.ColorBDefault = surf.Color.Value.GetB();
		mat.ColorTextureNo= FindColorTextureNo( surf );

		mat.Trance = surf.Trans.Value;
		Binary.Material.push_back( mat );
	}


	Binary.Mesh.resize( doc.LayerList.size() );



	int MeshNo = 0;

	for( MySTL::list<CLightWaveObject::LAYER>::iterator ite = doc.LayerList.begin();
				ite!=doc.LayerList.end(); ++ite, ++MeshNo )
	{
		const CLightWaveObject::LAYER& layer = *ite;

		LWO_BINARY::MESH& Mesh = Binary.Mesh[MeshNo];
		//	座標データ
		Mesh.Point = layer.PointList;

		//	法線データの完成
		Mesh.Normal = CreateNormal( layer );

		//	ここで頂点カラーの作成
		//	ここでウェイト＆インデックスの作成


		//	マテリアルとポリゴンの関係がソートされた
		const PRIMITIVEMAPLIST PrimitiveMap = CreatePrimitiveList( layer.PolygonTagMapping );
		const CLightWaveObject::LAYER::POLS& Face = FindFaceList( layer );

		Mesh.Primitive.resize( PrimitiveMap.size() );
		for( int mno=0; mno<(int)PrimitiveMap.size(); ++mno )
		{
			const PRIMITIVEMAP& map = PrimitiveMap[mno];
			const CLightWaveObject::SURFACE& surf = FindSurface( map.SurfaceNo, doc );

			LWO_BINARY::MESH::PRIMITIVE& pri = Mesh.Primitive[mno];

			pri.MaterialNo = map.SurfaceNo;

			const CLightWaveObject::LAYER::VMAP& ColorUVP = FindColorUV( surf, layer );
			const CLightWaveObject::LAYER::VMAD& ColorUVD = FindColorUVD( surf, layer );

			const bool IsColorTex = FindColorTextureNo( surf ) >= 0;

			for( int j=0; j<(int)map.PolygonNoList.size(); ++j )
			{
				const int pno = map.PolygonNoList[j];	//	これがポリゴン番号
				const CLightWaveObject::LAYER::POLS::INDEX& IndexList = Face.Polygon[pno];
				
				for( int k=0; k<(int)IndexList.size(); ++k )
				{
					int vno = IndexList[k];	//	これが頂点番号

					bool IsCreate = false;

					// ColorUV,AmbientUV,DiffuseUV,SpecularUV
					// に値を入れていく
					POINT2DF c,a,d,u; // 今回はColorのみ

					// ColorUV 編
					if( IsColorTex )
					{
						//	vmap txuv があるならそれ
						const MySTL::vector<float>& vmap = FindVMAPValue(ColorUVP,vno);
						const MySTL::vector<float>& vmad = FindVMADValue(ColorUVD,vno,pno);

						if( !vmap.empty() )
						{
							if( !vmad.empty() ) {
								//	だけど vmad もあるなら頂点を新しく作る
								c.x = vmad[0]; c.y = vmad[1]; IsCreate = true;
							}else				{
								c.x = vmap[0]; c.y = vmap[1];
							}

						}else
						{
							// vmap がない場合は
							// http://www.dstorm.co.jp/products/lw8/developer/docs/uv.htm
							// を元に計算
							//	だけど、メンドイので今は未実装（ぉ
							MAID_ASSERT( true, MAIDTEXT("UV MAP のない頂点が見つかりました") );
						}
					}

					//	頂点を作る指示があったら作る
					//	そうでなくても作成済み頂点を見て、サーフェスが違うなら、新しく作る
					if( IsCreate )
					{
						Mesh.Point.push_back( Mesh.Point[vno] );
						Mesh.Normal.push_back( Mesh.Normal[vno] );
						vno = (int)Mesh.Point.size()-1;
					}

					//	各種データを設定へ
					if( IsColorTex )
					{
						if( (int)pri.ColorUV.size() <= vno) { pri.ColorUV.resize(vno+1); }
						pri.ColorUV[vno] = c;
					}
					
					// インデックスをセット
					pri.IndexList.push_back(vno);
				}
			}
		}

	}

/*		データが変換できたかデバッグ出力
	{
		Maid::CTrace d(__FILE__,__LINE__);

		for( int i=0; i<(int)MESHDATA.size(); ++i )
		{
			const MESH& mesh = MESHDATA[i];

			d << "layer--------\n";
			d << "point\n";
			for( int j=0; j<(int)mesh.Point.size(); ++j )
			{
				const POINT3DF& pos = mesh.Point[j];
				d << j << ":" << pos.x << ","<< pos.y << ","<< pos.z << "\n";
			}
			d << "normal\n";
			for( int j=0; j<(int)mesh.Normal.size(); ++j )
			{
				const VECTOR3DF& norm = mesh.Normal[j];
				d << j << ":" << norm.x << ","<< norm.y << ","<< norm.z << "\n";
			}

			for( int j=0; j<(int)mesh.Material.size(); ++j )
			{
				const MESH::MATERIAL& mat = mesh.Material[j];
				d << "material\n";

				d << "color\n";
				d << "R" << mat.Color.GetR() << " " 
				  << "G" << mat.Color.GetG() << " "
				  << "B" << mat.Color.GetB() << "\n";

				if( !mat.ColorTextureName.empty() )
				{
					d << "texture\n";

					for( int k=0; k<(int)mat.ColorUV.size(); ++k )
					{
						const POINT2DF& uv = mat.ColorUV[k];
						d << k << ":" << uv.x << ","<< uv.y << "\n";
					}

				}

				d << "index\n";
				for( int k=0; k<(int)mat.IndexList.size(); ++k )
				{
					const unt32 no = mat.IndexList[k];
					d << no <<",";
				}
			}
		}
	}
*/


	//	戻り値に変換作業
	//	MESH の各種データを SPBINARY にしたらコピーしなくてもすむんですが
	//	バイト計算＆キャストしないといけないのがメンドイのでガマン(´ω｀)
	boost::shared_ptr<LoadLWOReturn>	pRet( new LoadLWOReturn );

	for( int i=0; i<(int)Binary.Mesh.size(); ++i )
	{
		const LWO_BINARY::MESH& mesh = Binary.Mesh[i];
		LoadLWOReturn::LAYER layer;

		layer.VertexCount  = (int)mesh.Point.size();
		layer.pPoint  = VectorConvert( mesh.Point );
		layer.pNormal = VectorConvert( mesh.Normal );

		const bool Is32Index = mesh.Point.size() > 0xFFFF;

		layer.IndexStride = Is32Index ? 4 : 2;

		for( int j=0; j<(int)mesh.Primitive.size(); ++j )
		{
			const LWO_BINARY::MESH::PRIMITIVE& src = mesh.Primitive[j];
			LoadLWOReturn::LAYER::PRIMITIVE pri;

			if( Is32Index )
			{
				pri.pIndex   = VectorConvert( src.IndexList );
			}else
			{
				pri.pIndex.reset( new MySTL::vector<unt08> );
				pri.pIndex->resize( src.IndexList.size() * 2 ); // 2==sizeof(unt16)

				MySTL::vector<unt08>& Dst = *(pri.pIndex);
				unt16* pDst = (unt16*)(&Dst[0]);

				for( int i=0; i<(int)src.IndexList.size(); ++i )
				{
					pDst[i] = (unt16)src.IndexList[i];
				}
			}

			pri.MaterialNo = src.MaterialNo;
			pri.pColorUV   = VectorConvert( src.ColorUV );

			layer.Primitive.push_back( pri );
		}

		pRet->Layer.push_back( layer );
	}

	for( int i=0; i<(int)Binary.Material.size(); ++i )
	{
		const LWO_BINARY::MATERIAL& src = Binary.Material[i];
		LoadLWOReturn::MATERIAL dst;

		dst.ColorTextureNo= src.ColorTextureNo;
		dst.ColorRDefault = src.ColorRDefault;
		dst.ColorGDefault = src.ColorGDefault;
		dst.ColorBDefault = src.ColorBDefault;
		dst.Trance = src.Trance;

		pRet->Material.push_back( dst );
	}

	for( int i=0; i<(int)Binary.Texture.size(); ++i )
	{
		const LWO_BINARY::TEXTURE& src = Binary.Texture[i];
		LoadLWOReturn::TEXTURE dst;

		dst.No = src.No;
		dst.FilePath = src.FilePath;

		pRet->Texture.push_back( dst );
	}

	return pRet;
}









}