/*
First author tiritomato 2013.

mqsdx is distributed under the GNU Lesser General Public License(LGPL).

support blog (Japanese only)
http://d.hatena.ne.jp/tiri_tomato/
*/

#ifndef __MQSDKPlugIn0x_11_h__
#define __MQSDKPlugIn0x_11_h__

#include <string.h>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <regex>
#include <sstream>
#include <vector>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <MQSetting.h>
#include <MQBasePlugin.h>
#include <MQPlugin.h>

#include "impl/MQ0x_11.hpp"

// Plugin Version Checker
#ifdef MQPLUGIN_VERSION
	#if MQPLUGIN_VERSION < 0x0300
	"MQx" need newer than mqsdk300
	#endif
#else
	"MQx" need newer than mqsdk300
#endif

namespace MQ0x {
	
/*!	@addtogroup MQ0xの一覧
	@{
*/

	/*!
@brief 組込型配列からハッシュコードを生成するクラス

@tparam T_OUT ハッシュコードの戻り値型を指定
@tparam default_seed ハッシュコードのデフォルトの初期値を指定。省略時は整数の17を使用します。

@code
	// 文字列のハッシュコードを取得する
	DWORD hashOfString = CollectionHashCode<DWORD>("ソース配列（文字列）");
	
	// short配列のハッシュコードを取得する
	const short array[] = { 0, 1, 2, 3 };
	unsigned int hashOfString = CollectionHashCode<unsigned int>(array, sizeof(array)/sizeof(array[0]));
@endcode

配列の各要素に対して次のような計算を繰り返してハッシュコードを生成します。四則演算に対応した組込型で、とくに浮動小数点ではない整数系の処理に向きます。
@code
	T_OUT hashCode = seed;

	(配列要素総当りループ) {
		hashCode = hashCode * ((sizeof(T_IN)*8)-1) ^ 配列要素;
	}
	return hashCode;
@endcode
	*/
	template <typename T_OUT, const size_t default_seed = 17> struct CollectionHashCode {
	public:
		///@brief T_OUTにキャストされたデフォルトの初期値
		static const T_OUT defaultSeed = (T_OUT)default_seed;
		///@brief デフォルトコンストラクタ
		CollectionHashCode( const T_OUT seed = defaultSeed ) : m_val( seed ) {}
		/*!
		@brief 配列の先頭ポインタと個数を指定してハッシュコードを生成
		@tparam T_IN ソース配列の型を指定
		@note pSrcがNULLまたは配列数が0である場合、与えられた初期値がそのままハッシュコードとして記録されます。
		*/
		template <typename T_IN> CollectionHashCode( const T_IN *pSrc, const size_t ct, const T_OUT seed = defaultSeed ) : m_val(seed) {
			if ( pSrc != NULL ) for ( size_t i = 0; i < ct; i++ ) m_val = (m_val * (T_OUT)(sizeof(T_IN) * 8 - 1)) ^ pSrc[i];
		}
		/*!
		@brief 文字列を指定してハッシュコードを生成
		@note pSrcがNULLまたは長さが0の空の文字列である場合、与えられた初期値がそのままハッシュコードとして記録されます。
		*/
		CollectionHashCode( const char *pSrc, const T_OUT seed = defaultSeed ) : m_val(FromTrailCodeCollection(pSrc,'\0',seed)) {}
		/*!
		@brief ワイド文字列を指定してハッシュコードを生成
		@note pSrcがNULLまたは長さが0の空の文字列である場合、与えられた初期値がそのままハッシュコードとして記録されます。
		*/
		CollectionHashCode( const wchar_t *pSrc, const T_OUT seed = defaultSeed ) : m_val(FromTrailCodeCollection(pSrc,L'\0',seed)) {}
		/*!
		@brief 配列の先頭ポインタと配列の終端値を指定してハッシュコードを生成
		@tparam T_IN ソース配列の型を指定
		@note pSrcがNULLまたは、最初のデータで終端する空の配列である場合、与えられた初期値がそのままハッシュコードとして記録されます。
		*/
		template <typename T_IN> static CollectionHashCode FromTrailCodeCollection( const T_IN *pSrc, const T_IN trailCode, const T_OUT seed = defaultSeed ) {
			CollectionHashCode ret(seed);
			if ( pSrc != NULL ) while ( (*pSrc) != trailCode ) ret.m_val = (ret.m_val * (T_OUT)(sizeof(T_IN) * 8 - 1)) ^ (*(pSrc++));
			return ret;
		}
		///@brief 暗黙のキャストを介して戻り値を取得
		operator T_OUT() { return m_val; }
	private:
		T_OUT m_val;
	};
	
	/*!
	@brief MQSetting代行クラス
	@code
		PluginBase<MQCommandPlugin> some_plugin;
		{
			SettingProxy setting = some_plugin.Setting(); // Setting取得
			setting.Load( ... );
			setting.Save( ... );
			setting.Close(); // 省略可能（デストラクタ経由でもClose()は実行されます）
		}
	@endcode
	SettingProxyのコピーは禁止されています(notcopyable)
	*/
	struct SettingProxy {
	public:
		//!@brief SettingProxyクラスの生成ハンドル型
		struct Handle {
			friend struct SettingProxy;
			template <typename T_MQPLUGIN> friend class PluginBase;
			static Handle Empty() { return Handle(NULL); }
		public:
			Handle( MQBasePlugin* plugin ) { this->plugin = plugin; }
			MQBasePlugin* plugin;
		};
		SettingProxy( Handle handle );
		~SettingProxy() { Close(); }
		void Close();
		bool Load(const char *name, bool& value, bool default_value=false);
		bool Load(const char *name, int& value, int default_value=0);
		bool Load(const char *name, unsigned int& value, unsigned int default_value=0);
		bool Load(const char *name, float& value, float default_value=0.0f);
		bool Load(const char *name, std::string& value, std::string default_value="");
		bool Save(const char *name, const bool& value);
		bool Save(const char *name, const int& value);
		bool Save(const char *name, const unsigned int& value);
		bool Save(const char *name, const float& value);
		bool Save(const char *name, const char* value);
		bool Save(const char *name, const std::string& value);
	private:
		SettingProxy(const SettingProxy&);				// kill copy
		SettingProxy& operator=(const SettingProxy&);	// kill copy
		MQBasePlugin* m_plugin;
		MQSetting* m_setting;
	};

	/*!
	@brief MQBasePluginを継承したクラスに、基本的な拡張を行うテンプレートラッパークラス
	@tparam T_MQPLUGIN MQBasePluginを継承する型

@par

このクラスはコンストラクタで製造者名、プラグイン名、表示名を指定する事で以下の実装を自動化します。
- GetPlugInID()
- GetPlugInName()
- EnumString()
- MQSetting::Open()およびClose()

@par

@code
PluginBase<MQCommandPlugin> plugin("tiritomato", "SamplePlugin Copyright(C) 2013, tiritomato.", "SamplePlugin");
{
	SettingProxy setting = plugin.Setting();
	setting.Load(...);
	setting.Save(...);
	setting.Close(); // このClose()はSettingProxyのデストラクタに任せる事も出来ます。
}
@endcode

@par

ID決定の自動化には文字列のハッシュコードが採用されています。
- 製造者ID＝製造者名（productName）のハッシュコード
- プラグインID＝プラグイン名（GetPlugInName()で取得できる文字列）のハッシュコード
	
@par

ハッシュコード算出は典型的に「ハッシュコード = ハッシュコード * (入力型(文字列の場合char型)サイズのビット数-1) ^ 配列要素」という計算を、
配列要素の個数だけ繰り返すロジックが採用されています。このテンプレートクラスで最終的にラップするのか、このテンプレートクラスをベースクラスにして継承して拡張するのかは自由です。
	*/
	template <typename T_MQPLUGIN> class PluginBase : public T_MQPLUGIN {
	public:
		PluginBase( const char* productName, const char* pluginFullName, const char* pluginString ) :
			m_productName( productName ),
			m_pluginFullName( pluginFullName ),
			m_pluginString( pluginString ),
			m_idProduct( CollectionHashCode<DWORD>(productName) ),
			m_idPlugin( CollectionHashCode<DWORD>(pluginFullName) )
		{
		}

		PluginBase( const char* productName, const char* pluginFullName, const char* pluginString,
			const DWORD idProduct, const DWORD idPlugin	) :
			m_productName( productName ),
			m_pluginFullName( pluginFullName ),
			m_pluginString( pluginString ),
			m_idProduct( idProduct ),
			m_idPlugin( idPlugin )
		{
		}

		//! プラグインIDを返す。
		virtual void GetPlugInID(DWORD *Product, DWORD *ID) {
			*Product = m_idProduct;
			*ID = m_idPlugin;
		}
		//! プラグイン名を返す。
		virtual const char *GetPlugInName() { return m_pluginFullName.c_str(); }
		//! ボタンやメニューに表示される文字列を返す。
		virtual const char *EnumString() { return m_pluginString.c_str(); }
		//! 製造者名を返す（独自に追加）
		const char* GetProductName() { return m_productName.c_str(); }
		//! 継承された時に使う明示的キャスト
		MQBasePlugin* BasePlugin() { return this; }
		//! SettingProxy(MQSettingの代替)をオープンするハンドルを取得
		SettingProxy::Handle Setting() { return SettingProxy::Handle(BasePlugin()); }
		/// @brief プロダクトIDを取得する
		DWORD ProductID() { return m_idProduct; }
		/// @brief プラグインIDを取得する
		DWORD PluginID() { return m_idPlugin; }
	protected:
		const std::string m_productName;
		const std::string m_pluginFullName;
		const std::string m_pluginString;
		const DWORD m_idProduct;
		const DWORD m_idPlugin;
	};

	/*!	@name グローバル
		@{ */

	//! @brief 各種の名前取得関数において、可変長バッファサイズの最低初期値のデフォルトを定義。
	static const std::vector<char>::size_type GetNameInitialBufferSize = 2;

	//! @brief ドキュメントからマテリアルを削除。
	//! @return 引数がNULLまたはユニークIDの一致するマテリアルが見つからない場合は削除を行わずfalseを返します。それ以外はtrue
	inline bool DeleteMaterial( const MQDocument doc, const MQMaterial mat ) {
		int index = prv_impl::GetIdentifiedIndex(doc, mat);
		if ( index < 0 ) return false;
		doc->DeleteMaterial(index); return true;
	}
	//! @brief ドキュメントからオブジェクトを削除。
	//! @return 引数がNULLまたはユニークIDの一致するオブジェクトが見つからない場合は削除を行わずfalseを返します。それ以外はtrue
	inline bool DeleteObject( const MQDocument doc, const MQObject obj ) {
		int index = prv_impl::GetIdentifiedIndex(doc, obj);
		if ( index < 0 ) return false;
		doc->DeleteObject(index); return true;
	}
	/*!	@brief マテリアルクローンのための、カウントアップされた重複しない別名候補を取得
		@return 少なくとも探査時点で重複しない別名候補文字列。引数にNULLが指定されるなどエラー時には空の文字列が返る。
		@par 別名候補の命名規則
		例）obj > obj1 | obj00 > obj01 | obj0.99 > obj0.100
		- 重複は回避される
		- 元の名前の末尾に数字が無い場合は'1'が付与される。
		- 元の名前の末尾に0-9の数字列がある場合は、+1される。ただし0-9以外、例えば小数点などは数値形式としては考慮されない。
		- クローン候補名の末尾数字の桁数は、元の名前の末尾の数字列と同じかそれよりも一桁増える。
		.
		@attention MQSDK Rev3.10からMQDocument::GetUnusedMaterialName()が追加されました。この関数は下位互換、および今後のMQx独自仕様として残されます。
		@date 2013.02.04 MQSDK Rev3.10対応
		*/
	inline std::string GetCountUpCloneableUniqueName( const MQDocument doc, const MQMaterial src, std::vector<char>* const buf = NULL ) {
		return prv_impl::GetCountUpCloneableUniqueName(doc,src,buf);
	}
	/*!	@brief オブジェクトクローンのための、カウントアップされた重複しない別名候補を取得
		@return 少なくとも探査時点で重複しない別名候補文字列。引数にNULLが指定されるなどエラー時には空の文字列が返る。
		@par 別名候補の命名規則
		例）obj > obj1 | obj00 > obj01 | obj0.99 > obj0.100
		- 重複は回避される
		- 元の名前の末尾に数字が無い場合は'1'が付与される。
		- 元の名前の末尾に0-9の数字列がある場合は、+1される。ただし0-9以外、例えば小数点などは数値形式としては考慮されない。
		- クローン候補名の末尾数字の桁数は、元の名前の末尾の数字列と同じかそれよりも一桁増える。
		.
		@attention MQSDK Rev3.10からMQDocument::GetUnuseObjectName()が追加されました。この関数は下位互換、および今後のMQx独自仕様として残されます。
		@date 2013.02.04 MQSDK Rev3.10対応
	*/
	inline std::string GetCountUpCloneableUniqueName( const MQDocument doc, const MQObject src, std::vector<char>* const buf = NULL ) {
		return prv_impl::GetCountUpCloneableUniqueName(doc,src,buf);
	}
	//! @brief ドキュメントに登録されているマテリアルを名前で検索する
	//! @return 見つかったマテリアル。見つからない場合はNULL。
	inline MQMaterial GetMaterial( const MQDocument doc, const char* name ) { return prv_impl::GetNamed<MQMaterial>( doc, name ); }
	//! @brief ドキュメントに登録されているマテリアルをユニークIDで検索する
	//! @return 見つかったマテリアル。見つからない場合はNULL。
	//! @attention MQSDK Rev3.10からMQDocument::GetMaterialFromUniqueID()が追加されました。この関数は下位互換のために残されています。
	//!	@date 2013.02.04 MQSDK Rev3.10対応
	inline MQMaterial GetMaterial( const MQDocument doc, const UINT id ) {
		// メタセコイア本体にバグの疑いがあり、旧実装をキープ。
		/*
		#if 0x0310 <= MQPLUGIN_VERSION
			if ( doc != NULL ) return doc->GetMaterialFromUniqueID(id);
			return NULL;
		#else
			return prv_impl::GetIdentified<MQMaterial>( doc, id );
		#endif*/
		return prv_impl::GetIdentified<MQMaterial>( doc, id );
	}
	
	/// @cond
	std::string GetMaterialTextureName( const MQMaterial, DWORD );
	/// @endcond

	/// @brief 透明テクスチャのファイルパスを取得する。 GetMaterialTextureName() の map_type に MQMAPPING_ALPHA を指定するのと同じです。
	inline std::string GetMaterialAlphaName( const MQMaterial mat ) { return GetMaterialTextureName(mat,MQMAPPING_ALPHA); }
	/// @brief 凸凹テクスチャのファイルパスを取得する。 GetMaterialTextureName() の map_type に MQMAPPING_BUMP を指定するのと同じです。
	inline std::string GetMaterialBumpName( const MQMaterial mat ) { return GetMaterialTextureName(mat,MQMAPPING_BUMP); }
	/// @brief ドキュメントに登録されているマテリアルを名前で検索する。
	/// @return 見つかったマテリアルのインデックス。見つからない場合は-1
	inline int GetMaterialIndex( const MQDocument doc, const char* name ) { return prv_impl::GetNamedIndex<MQMaterial>( doc, name ); }
	/// @brief ドキュメントに登録されているマテリアルをユニークIDで検索する。
	/// @return 見つかったマテリアルのインデックス。見つからない場合は-1
	inline int GetMaterialIndex( const MQDocument doc, const UINT id ) { return prv_impl::GetIdentifiedIndex<MQMaterial>( doc, id ); }
/*!
@brief テクスチャのファイルパスを取得する。
@param map_type 取得するテクスチャの種類を指定する
- MQMAPPING_TEXTURE / MQMaterial::GetTextureName()を呼び出して模様画像パスを取得する
- MQMAPPING_ALPHA / MQMaterial::GetAlphaName()を呼び出して透明画像パスを取得する
- MQMAPPING_BUMP / MQMaterial::GetBumpName()を呼び出して凸凹画像パスを取得する
@return マッピングのファイル名を相対パスで取得します（元々絶対パスで指定されている場合は絶対パスが返るそうです）。元々画像が設定されていない、あるいはmatにNULLを指定するなどなんらかの失敗時には、空の文字列が返ります。
@details 得られたファイル名を絶対パスに変換するにはMQDocument::FindMappingFile()を使用します。
*/
	inline std::string GetMaterialTextureName( const MQMaterial mat, const DWORD map_type = MQMAPPING_TEXTURE ) {
		if ( mat != NULL )
		{
			std::vector<char> buf;
			prv_impl::GetMaterialTextureName(&buf, mat, map_type);
			return std::string(&buf[0]);
		}
		return "";
	}
	/// @brief マテリアルの名前を取得する。
	/// @return マテリアルの名前。引数にNULLが指定されるなど、エラー時には空の文字列。
	inline std::string GetName( const MQMaterial mat ){ return prv_impl::GetName( mat ); }
	/// @brief オブジェクトの名前を取得する
	/// @return オブジェクトの名前。引数にNULLが指定されるなど、エラー時には空の文字列。
	inline std::string GetName( const MQObject obj ){ return prv_impl::GetName( obj ); }
	//! @brief ドキュメントに登録されているオブジェクトを名前で検索する
	//! @return 見つかったオブジェクト。見つからない場合はNULL。
	inline MQObject GetObject( const MQDocument doc, const char* name ) { return prv_impl::GetNamed<MQObject>( doc, name ); }
	//! @brief ドキュメントに登録されているオブジェクトをユニークIDで検索する
	//! @return 見つかったオブジェクト。見つからない場合はNULL。
	//! @attention MQSDK Rev3.10からMQDocument::GetObjectFromUniqueID()が追加されました。この関数は下位互換のために残されています。
	//!	@date 2013.02.04 MQSDK Rev3.10対応
	inline MQObject GetObject( const MQDocument doc, const UINT id ) {
		#if 0x0310 <= MQPLUGIN_VERSION
			if ( doc != NULL ) return doc->GetObjectFromUniqueID(id);
			return NULL;
		#else
			return prv_impl::GetIdentified<MQObject>(doc, id );
		#endif
	}
	//! @brief ドキュメントに登録されているオブジェクトを名前で検索する
	/// @return 見つかったオブジェクトのインデックス。見つからない場合は-1
	inline int GetObjectIndex( const MQDocument doc, const char* name ) { return prv_impl::GetNamedIndex<MQObject>( doc, name ); }
	/// @brief ドキュメントに登録されているオブジェクトをユニークIDで検索する。
	/// @return 見つかったオブジェクトのインデックス。見つからない場合は-1
	inline int GetObjectIndex( const MQDocument doc, const UINT id ) { return prv_impl::GetIdentifiedIndex<MQObject>(doc, id ); }

#if 0x0310 <= MQPLUGIN_VERSION

	/// @brief ドキュメントに登録されていないマテリアル名を取得する（MQDocument::GetUnusedMaterialNameのラッパー関数）。
	/// @return 取得した文字列。docがNULLの時は空の文字列。
	/// @attention MQSDK Rev3.10 から利用可能
	inline std::string GetUnusedMaterialName( const MQDocument doc, const char* base_name = NULL ) {
		return prv_impl::GetUnusedNameString<MQMaterial>( doc, base_name );
	}
	/// @brief ドキュメントに登録されていないマテリアル名を取得する（MQDocument::GetUnusedMaterialNameのラッパー関数）。
	/// @param base_name_material 元の名前を指定するマテリアル。NULLで省略するとbase_nameをNULLにしてMQDocument::GetUnusedMaterialNameを呼び出すのと同じ動作。
	/// @return 取得した文字列。docがNULLの時は空の文字列。
	/// @attention MQSDK Rev3.10 から利用可能
	inline std::string GetUnusedMaterialName( const MQDocument doc, const MQMaterial base_name_material ) {
		if ( base_name_material == NULL ) return prv_impl::GetUnusedNameString<MQMaterial>( doc );
		return prv_impl::GetUnusedNameString<MQMaterial>( doc, GetName( base_name_material ).c_str() );
	}
	/// @brief ドキュメントに登録されていないオブジェクト名を取得する（MQDocument::GetUnusedObjectNameのラッパー関数）。
	/// @return 取得した文字列。docがNULLの時は空の文字列。
	/// @attention MQSDK Rev3.10 から利用可能
	inline std::string GetUnusedObjectName( const MQDocument doc, const char* base_name = NULL ) {
		return prv_impl::GetUnusedNameString<MQObject>( doc, base_name );
	}
	/// @brief ドキュメントに登録されていないオブジェクト名を取得する（MQDocument::GetUnusedObjectNameのラッパー関数）。
	/// @param base_name_object 元の名前を指定するマテリアル。NULLで省略するとbase_nameをNULLにしてMQDocument::GetUnusedObjectNameを呼び出すのと同じ動作。
	/// @return 取得した文字列。docがNULLの時は空の文字列。
	/// @attention MQSDK Rev3.10 から利用可能
	inline std::string GetUnusedObjectName( const MQDocument doc, const MQObject base_name_object ) {
		if ( base_name_object == NULL ) return prv_impl::GetUnusedNameString<MQMaterial>( doc );
		return prv_impl::GetUnusedNameString<MQMaterial>( doc, GetName( base_name_object ).c_str() );
	}
	
#endif

	//! @}

	//! @}

	namespace prv_impl {}; // kill prv_impl access ( to private header implement trap )
};

#endif