// == LICENSE INFORMATION ==
/*
 * First author tiritomato 2013.
 * This program is distributed under the GNU General Public License(GPL).
 * support blog (Japanese only) http://d.hatena.ne.jp/tiri_tomato/
 */
// == LICENSE INFORMATION ==

#pragma once

#include "../UVTexIntegra.hpp"

namespace UVTexIntegra {
	namespace CustomControls {

		private ref struct MaterialDrawData {
		public:
			bool isCurrent;
			System::Drawing::RectangleF rect;
			System::Drawing::Brush^ matBrush;
			System::Drawing::Pen^ matPen;
			MaterialDrawData( System::Drawing::RectangleF rc, System::Drawing::Brush^ brush, System::Drawing::Pen^ pen  ) :
				rect(rc), matBrush(brush), matPen(pen) {}
			~MaterialDrawData() { this->!MaterialDrawData(); }
			!MaterialDrawData() {
				if ( matBrush != nullptr ) { delete matBrush; matBrush = nullptr; }
				if ( matPen != nullptr ) { delete matPen; matPen = nullptr; }
			}
		};

	}
}

#include "TextureSetForm.hpp"
#include "VertexColorCustomBakeForm.hpp"
#include "MainEditForm.hpp"

namespace UVTexIntegra
{
	namespace CustomControls
	{
		//! @addtogroup UVTexIntegra-CustomControls名前空間
		//! @{

		//! @brief 変換処理の定義と変換済みのMQ0x::Polygon::UVFaceBufferを保有するクラスです。
		//! @details MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタを実装しており、MQ0x::Polygon::UVFaceBuffer::AddObject()に自分自身を渡します。
		struct UVTransformedBuffer
		{
			//! @brief UVTransformedBufferの構築時に使用する変換ファンクタです。
			struct TransformFunctor
			{
				//! @brief コンストラクタ
				TransformFunctor( IDocumentFormat^ doc ) : m_doc(doc)
				{
				}

				//! @brief MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタ
				bool operator()( MQCoordinate* const uv_coorinates, DWORD* const colors,
					const MQObject obj, const int count_of_points, const int face_index, const UINT face_uid ) const
				{
					IDocumentFormat^ docData = static_cast<IDocumentFormat^>(m_doc);
					if ( docData == nullptr ) return false;
					const MQDocument mqdoc = docData->MQDocument;
					if ( mqdoc == NULL ) return false;

					const MQMaterial mat = mqdoc->GetMaterial( obj->GetFaceMaterial( face_index ) );
					EditData^ data = nullptr; if ( mat != NULL ) data = docData->ItemFromID( mat->GetUniqueID() );
					if ( data == nullptr ) return false;
			
					const double pad = std::abs(data->Padding);
					const UVCoordinate vMul((data->ScaleU - pad * 2.0) * 2.0, (data->ScaleV - pad * 2.0) * 2.0 );
					const UVCoordinate vAdd((data->MoveU + pad) * 2.0 - 1.0, (data->MoveV + pad) * 2.0 - 1.0 );
					for ( int ct = 0; ct < count_of_points; ct++ )
					{
						uv_coorinates[ct].u = float( double(uv_coorinates[ct].u) * vMul.u + vAdd.u );
						uv_coorinates[ct].v = float( double(uv_coorinates[ct].v) * vMul.v + vAdd.v );
					}
					return true;
				}
			private:
				const gcroot<IDocumentFormat^> m_doc;
			};
			
			//! @brief デフォルトコンストラクタ
			UVTransformedBuffer() : m_uvBuffer(), m_doc(nullptr) {}
			
			//! @brief メタセコイアオブジェクトをバッファに追加します。追加時にUVTransformedBuffer::TransformFunctorを介してUV座標変換処理を行います。 
			void AddObject(const MQObject obj, bool isRecursive)
			{
				IDocumentFormat^ doc = Document();
				if (doc == nullptr) return;
				AddObject(obj, isRecursive, TransformFunctor(static_cast<IDocumentFormat^>(m_doc)));
			}
			
			//! @brief IDocumentFormatを指定して、内部のMQ0x::Polygon::UVFaceBufferをクリアします。
			void Clear( IDocumentFormat^ doc, const bool isCompact = false )
			{
				m_doc = doc;
				m_uvBuffer.Clear(isCompact);
			}
			
			//! @brief 内部のMQ0x::Polygon::UVFaceBufferの参照を読み取り専用で取得します。
			const MQ0x::Polygon::UVFaceBuffer& UVFaceBuffer() const
			{
				return m_uvBuffer;
			}

		protected:

			//! @brief メタセコイアオブジェクトをバッファに追加します。追加時のUV座標変換処理ファンクタを指定します。
			//! @tparam FUNCTOR MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタを指定してください。
			template <class FUNCTOR> void AddObject(const MQObject obj, bool isRecursive, FUNCTOR& functor)
			{
				if (obj != NULL)
				{
					if (isRecursive) {
						IDocumentFormat^ doc = Document();
						if (doc == nullptr) return;
						m_uvBuffer.AddObjectRecursive(doc->MQDocument, obj, functor);
					}
					else m_uvBuffer.AddObject(obj, functor);
				}
			}
			
			//! @brief 設定されたDocumentインスタンスを取得します。なお、Documentの指定はClear()メソッドで行います。
			IDocumentFormat^ Document() const
			{
				return static_cast<IDocumentFormat^>(m_doc);
			}

		private:
			MQ0x::Polygon::UVFaceBuffer m_uvBuffer;
			//gcroot<MainEditForm::Document^> m_doc;
			gcroot<IDocumentFormat^> m_doc;
		};

		//! @brief 変換処理の定義と変換済みのMQ0x::Polygon::UVFaceBufferを保有するクラスです。
		//! @details MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタを実装しており、MQ0x::Polygon::UVFaceBuffer::AddObject()に自分自身を渡します。
		struct UVNormalTransformedBuffer : public UVTransformedBuffer
		{
			//! @brief UVNormalTransformedBufferの構築時に使用する変換ファンクタです。
			struct TransformFunctor : private UVTransformedBuffer::TransformFunctor
			{
				//! @brief コンストラクタ
				TransformFunctor(UVNormalTransformedBuffer& owner)
					: UVTransformedBuffer::TransformFunctor(owner.Document()), m_owner(owner)
				{
				}

				//! @brief MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタ
				//! @details オーナーのUVNormalTransformedBufferの内部C#形式バッファに書き加えていきます。
				bool operator()( MQCoordinate* const uv_coorinates, DWORD* const colors,
					const MQObject obj, const int count_of_points, const int face_index, const UINT face_uid ) const
				{
					bool ret = UVTransformedBuffer::TransformFunctor::operator()(uv_coorinates,colors,obj,count_of_points,face_index,face_uid);
					if (ret)
					{
						array<Scripting::ScriptMain::Face::Point^>^ points = gcnew array<Scripting::ScriptMain::Face::Point^>(count_of_points);
						if (obj != m_owner.m_normal_buffer.MQObject()) m_owner.m_normal_buffer.Clear(obj);
						int v_index_buffer[4];
						obj->GetFacePointArray( face_index, v_index_buffer );
						for ( int ctFacePoint = 0; ctFacePoint < count_of_points; ctFacePoint++ )
						{
							const MQPoint& vertexNormal = m_owner.m_normal_buffer.VertexNormal( MQ0x::Polygon::VertexNormalBuffer::size_type(v_index_buffer[ctFacePoint]) );
							points[ctFacePoint] = gcnew Scripting::ScriptMain::Face::Point(
								uv_coorinates[ctFacePoint].u, uv_coorinates[ctFacePoint].v,
								MQCLIEX::FromMQVertexColor( colors[ctFacePoint] ),
								vertexNormal.x, vertexNormal.y, vertexNormal.z
							);
						}
						const MQPoint& faceNormal = m_owner.m_normal_buffer.FaceNormal( MQ0x::Polygon::FaceNormalBuffer::size_type(face_index) );
						m_owner.m_faces->Add( gcnew Scripting::ScriptMain::Face( face_uid, faceNormal.x, faceNormal.y, faceNormal.z, points ) );
					}
					return ret;
				}

				/*
				//! @brief MQ0x::Polygon::UVFaceBuffer::Functors::BasicUVTransformFunctorタイプのファンクタ
				bool operator()( MQCoordinate* const uv_coorinates, DWORD* const colors,
					const MQObject obj, const int count_of_points, const int face_index, const UINT face_uid ) const
				{
					bool ret = UVTransformedBuffer::TransformFunctor::operator()(uv_coorinates,colors,obj,count_of_points,face_index,face_uid);
					if (ret)
					{
						Scripting::ScriptMain^ scriptMain = static_cast<Scripting::ScriptMain^>(m_owner.m_scriptMain);
						if (scriptMain != nullptr)
						{
							array<OpenTK::Vector2>^ coordinates = gcnew array<OpenTK::Vector2>(count_of_points);
							array<System::Drawing::Color>^ coordinateColors = gcnew array<System::Drawing::Color>(count_of_points);
							array<OpenTK::Vector3>^ normals = gcnew array<OpenTK::Vector3>(count_of_points);

							if (obj != m_owner.m_normal_buffer.MQObject()) m_owner.m_normal_buffer.Clear(obj);

							int v_index_buffer[4];
							obj->GetFacePointArray( face_index, v_index_buffer );
							for ( int ctFacePoint = 0; ctFacePoint < count_of_points; ctFacePoint++ )
							{
								const MQPoint& vertexNormal = m_owner.m_normal_buffer.VertexNormal( MQ0x::Polygon::VertexNormalBuffer::size_type(v_index_buffer[ctFacePoint]) );
								coordinates[ctFacePoint] = OpenTK::Vector2( uv_coorinates[ctFacePoint].u, uv_coorinates[ctFacePoint].v );
								coordinateColors[ctFacePoint] = MQCLIEX::FromMQVertexColor( colors[ctFacePoint] );
								normals[ctFacePoint] = OpenTK::Vector3( vertexNormal.x, vertexNormal.y, vertexNormal.z );
							}
							const MQPoint& faceNormal = m_owner.m_normal_buffer.FaceNormal( MQ0x::Polygon::FaceNormalBuffer::size_type(face_index) );

							//if ( scriptMain->CustomUVOperation(coordinates, coordinateColors, OpenTK::Vector3(faceNormal.x, faceNormal.y, faceNormal.z), normals ) )
							//{
							//	for ( int ctFacePoint = 0; ctFacePoint < count_of_points; ctFacePoint++ )
							//	{
							//		uv_coorinates[ctFacePoint].u = coordinates[ctFacePoint].X;
							//		uv_coorinates[ctFacePoint].v = coordinates[ctFacePoint].Y;
							//		colors[ctFacePoint] = MQCLIEX::ArgbToMQVertexColor(
							//			coordinateColors[ctFacePoint].A,
							//			coordinateColors[ctFacePoint].R,
							//			coordinateColors[ctFacePoint].G,
							//			coordinateColors[ctFacePoint].B );
							//	}
							//}
						}
					}
					return ret;
				}
				*/
			private:
				UVNormalTransformedBuffer& m_owner;
			};
			
			//! @cond
			friend struct TransformFunctor;
			//! @endcond

			//! @brief メタセコイアオブジェクトをバッファに追加します。
			//! @details 追加時にUVNormalTransformedBuffer::TransformFunctorを介して基本的なUV座標変換処理を行い、内部のC#形式の面情報バッファにプレ変換済みの情報を追加します。
			void AddObject(const MQObject obj, bool isRecursive)
			{
				IDocumentFormat^ doc = Document();
				if (doc == nullptr) return;
				UVTransformedBuffer::AddObject(obj, isRecursive, TransformFunctor(*this));
			}

			//! @brief IDocumentFormatを指定して、内部のMQ0x::Polygon::UVFaceBufferをクリアします。
			void Clear( IDocumentFormat^ doc, Scripting::ScriptMain^ script, const bool isCompact = false )
			{
				//m_scriptMain = script;
				m_normal_buffer.Clear(NULL, isCompact);
				if (static_cast<System::Collections::Generic::List<Scripting::ScriptMain::Face^>^>(m_faces) != nullptr) m_faces->Clear();
				else m_faces = gcnew System::Collections::Generic::List<Scripting::ScriptMain::Face^>();
				UVTransformedBuffer::Clear(doc, isCompact);
			}

			//! @brief 内部のC#形式の面情報バッファを、ScriptMainインスタンスに送って変換処理を仕上げます。
			//! @details ScriptMainインスタンスに渡されるデータはディープコピーされ、ScriptMainから返される値もまた、戻す時にディープコピーされます。
			//! @return 変換済みのFace配列を返します。渡されたscriptMainがnullまたは内部のC#形式バッファが未Clear状態でnullである場合、nullを返します。
			CustomControls::Drawing::FaceBuffer^ GetUVOperationed( Scripting::ScriptMain^ scriptMain )
			{
				if (static_cast<System::Collections::Generic::List<Scripting::ScriptMain::Face^>^>(m_faces) == nullptr) return nullptr;
				if (scriptMain == nullptr) return nullptr;
				array<Scripting::ScriptMain::Face^>^ ary = m_faces->ToArray();
				scriptMain->OnUVOperation(ary);
				return gcnew CustomControls::Drawing::FaceBuffer(ary);
			}

			//! @brief 内部のMQ0x::Polygon::VertexNormalBufferの参照を読み取り専用で取得します。
			const MQ0x::Polygon::VertexNormalBuffer& VertexNormalBuffer() const { return m_normal_buffer; }

		private:
			void Clear( IDocumentFormat^ doc, const bool isCompact = false ); // kill
			MQ0x::Polygon::VertexNormalBuffer m_normal_buffer;
			//gcroot<Scripting::ScriptMain^> m_scriptMain;
			gcroot<System::Collections::Generic::List<Scripting::ScriptMain::Face^>^> m_faces;
		};
	
		//! @}
	}
}
