/*
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/
*/

#pragma once
/// @cond
namespace MQ0x { namespace { namespace prv_impl {

	template <typename T_MQOBJECT>
	struct MQIndexedObjectIFBase {
		const std::function<int (const MQDocument)> count_function;
		const std::function<T_MQOBJECT(const MQDocument, int)> get_function;
	protected:
		MQIndexedObjectIFBase() {}
		MQIndexedObjectIFBase(	const std::function<int (const MQDocument)> _count_function,
								const std::function<T_MQOBJECT(const MQDocument, int)> _get_function ) :
		count_function( _count_function ), get_function( _get_function ) {}
	};
	template <typename T_MQOBJECT>
	struct MQIndexedObjectIF : public MQIndexedObjectIFBase<T_MQOBJECT> {
	private: MQIndexedObjectIF() : MQIndexedObjectIFBase() {}
	};
	template <>
	struct MQIndexedObjectIF<MQObject> : public MQIndexedObjectIFBase<MQObject> {
		MQIndexedObjectIF () : MQIndexedObjectIFBase (
			[](const MQDocument doc) { return doc->GetObjectCount(); },
			[](const MQDocument doc, int index) { return doc->GetObject(index); } ) {}
	};
	template <>
	struct MQIndexedObjectIF<MQMaterial> : public MQIndexedObjectIFBase<MQMaterial> {
		MQIndexedObjectIF () : MQIndexedObjectIFBase (
			[](const MQDocument doc) { return doc->GetMaterialCount(); },
			[](const MQDocument doc, int index) { return doc->GetMaterial(index); } ) {}
	};
	template <typename T_MQOBJECT>
	struct MQIndexObjectCollection {
	private:
		MQIndexObjectCollection();
		const MQIndexedObjectIF<T_MQOBJECT> functionObject;
		const MQDocument m_doc;
	public:
		MQIndexObjectCollection( const MQDocument doc ) : functionObject(), m_doc( doc ) {}
		int Count() {
			if ( m_doc == NULL ) return 0;
			return functionObject.count_function( m_doc );
		}
		T_MQOBJECT operator [] ( int index ) {
			if ( m_doc == NULL ) return 0;
			return functionObject.get_function( m_doc, index );
		}
	};
	
	template <typename T_MQOBJECT> inline
	std::string GetCountUpCloneableUniqueName( const MQDocument doc, const T_MQOBJECT srcObj, std::vector<char>* const buf = NULL )
	{
		if ( buf == NULL ) {
			std::vector<char> tmp;
			return GetCountUpCloneableUniqueName( doc, srcObj, &tmp );
		}
		
		std::string ret;
		if ( doc != NULL && srcObj != NULL )
		{
			unsigned long trailNum = 0;
			std::string prefix;
			std::regex re("[0-9]+$");
			std::match_results<const char *> results;
			GetNamedName( srcObj, buf );
			if ( std::regex_search( &(*buf)[0], results, re, std::regex_constants::match_default ) ) {
				if ( 0 < results.str().length() ) {
					char* end = NULL;
					trailNum = ::strtoul( results.str().c_str(), &end, 10 );
				}
				prefix = results.prefix().str();
			}
			else prefix = &(*buf)[0];
			do // find unique name
			{
				std::stringstream strstr;
				strstr << std::setw( results.str().length() ) << std::setfill('0') << (trailNum += 1);
				ret = prefix + strstr.str();
			}
			while ( GetNamed<T_MQOBJECT>(doc, ret.c_str() ) != NULL );
		}
		return ret;
	}
	/// @brief バッファに名前を書き込む実装部。エラーチェックしないので注意
	template <typename T_MQNAMED> inline
	size_t GetNamedNameImplement( const T_MQNAMED obj, std::vector<char>* const buf, const std::vector<char>::size_type initial_size )
	{
		// vector char pattern
		std::vector<char>::size_type _initial_size = std::max(initial_size, (std::vector<char>::size_type)2); // 2 = one char + null char space
		for ( buf->resize( std::max( buf->size(), _initial_size ), '\0' ); true; buf->resize( buf->size() * 2, '\0' ) )
		{
			obj->GetName( &(*buf)[0], buf->size() );
			if ( (*buf)[buf->size() - 2] == '\0' ) return strlen(&(*buf)[0]);
		}
		return 0;
	}
	/// @brief バッファに名前を書き込む。実装はGetNamedNameImplementに、こっち側ではエラーチェックだけやってる。
	/// @param obj 名前を取得するオブジェクト。NULL指定すると文字列取得など一切処理が行われず-1が返る。
	/// @param buf 書き込み先バッファアドレス。NULL省略すると勝手に一時バッファを作って文字数だけ計測
	/// @return 取得された文字数
	template <typename T_MQNAMED>
	size_t GetNamedName(	const T_MQNAMED obj, std::vector<char>* const buf = NULL,
							const std::vector<char>::size_type initial_size = GetNameInitialBufferSize )
	{
		if ( obj == NULL ) return -1;
		else if ( buf == NULL ) {
			std::vector<char> tmp;
			return GetNamedNameImplement( obj, &tmp, initial_size );
		}
		else return GetNamedNameImplement( obj, buf, initial_size );
	}
	template <typename T_MQOBJECT> inline std::string GetName( const T_MQOBJECT objNamed ) {
		if ( objNamed == NULL ) return "";
		std::vector<char> buf;
		prv_impl::GetNamedName( objNamed, &buf );
		return &buf[0];
	}
	template <typename T_MQNAMED> inline T_MQNAMED GetNamed( const MQDocument doc, const char* name ) {
		if ( doc != NULL && name != NULL ) {
			MQIndexObjectCollection<T_MQNAMED> namedObjects(doc);
			std::vector<char> buf;
			for ( int index = 0, ctObject = namedObjects.Count(); index < ctObject; index++ ) {
				T_MQNAMED obj = namedObjects[index];
				if ( obj != NULL ) {
					GetNamedName(obj, &buf);
					if ( ::strcmp(&buf[0], name) == 0 ) return obj;
				}
			}
		}
		return NULL;
	}
	template <typename T_MQNAMED> inline int GetNamedIndex( const MQDocument doc, const char* name ) {
		if ( doc != NULL && name != NULL ) {
			MQIndexObjectCollection<T_MQNAMED> namedObjects(doc);
			std::vector<char> buf;
			for ( int index = 0, ctObject = namedObjects.Count(); index < ctObject; index++ ) {
				T_MQNAMED obj = namedObjects[index];
				if ( obj != NULL ) {
					GetNamedName(obj, &buf);
					if ( ::strcmp(&buf[0], name) == 0 ) return index;
				}
			}
		}
		return -1;
	}
	template <typename T_MQIDENTIFIED> inline
	T_MQIDENTIFIED GetIdentified( const MQDocument doc, const UINT id )
	{
		if ( doc != NULL ) {
			MQIndexObjectCollection<T_MQIDENTIFIED> identifiedObjects(doc);
			for ( int index = 0, ctObject = identifiedObjects.Count(); index < ctObject; index++ ) {
				T_MQIDENTIFIED obj = identifiedObjects[index];
				if ( obj != NULL && (obj->GetUniqueID() == id) ) return obj;
			}
		}
		return NULL;
	}
	template <typename T_MQIDENTIFIED> inline
	int GetIdentifiedIndex( const MQDocument doc, const UINT id )
	{
		if ( doc != NULL ) {
			MQIndexObjectCollection<T_MQIDENTIFIED> identifiedObjects(doc);
			for ( int index = 0, ctObject = identifiedObjects.Count(); index < ctObject; index++ ) {
				T_MQIDENTIFIED obj = identifiedObjects[index];
				if ( obj != NULL && (obj->GetUniqueID() == id) ) return index;
			}
		}
		return -1;
	}
	template <typename T_MQIDENTIFIED> inline
	int GetIdentifiedIndex( const MQDocument doc, const T_MQIDENTIFIED src )
	{
		if ( (doc != NULL) && (src!=NULL) ) return GetIdentifiedIndex<T_MQIDENTIFIED>( doc, src->GetUniqueID() );
		return -1;
	}
	inline void GetMaterialTextureName( std::vector<char>* buf, const MQMaterial mat, DWORD map_type = MQMAPPING_TEXTURE ) {
		if ( mat == NULL ) return;
		// vector char pattern
		const std::vector<char>::size_type _initial_size =
#ifdef _DEBUG
			2; /* 2 = one char + null char space*/
#else
			MAX_PATH;
#endif
		for ( buf->resize( std::max( buf->size(), _initial_size ), '\0' ); true; buf->resize( buf->size() * 2, '\0' ) )
		{
			switch ( map_type ) {
			case MQMAPPING_TEXTURE:
				mat->GetTextureName( &(*buf)[0], buf->size() );
				break;
			case MQMAPPING_ALPHA:
				mat->GetAlphaName( &(*buf)[0], buf->size() );
				break;
			case MQMAPPING_BUMP:
				mat->GetBumpName( &(*buf)[0], buf->size() );
				break;
			default:
				return;
			}
			if ( (*buf)[buf->size() - 2] == '\0' ) return;
		}
	}

#if 0x0310 <= MQPLUGIN_VERSION
	template <typename T_MQOBJECT> void get_unused_name_function(const MQDocument, char*, int, const char*);
	template <> void get_unused_name_function<MQMaterial>(const MQDocument doc, char* buffer, int buffer_size, const char* base_name) {
		doc->GetUnusedMaterialName(buffer,buffer_size,base_name);
	}
	template <> void get_unused_name_function<MQObject>(const MQDocument doc, char* buffer, int buffer_size, const char* base_name) {
		doc->GetUnusedObjectName(buffer,buffer_size,base_name);
	}
	/// @brief バッファに未使用名を書き込む実装部。エラーチェックしないので注意
	template <typename T_MQNAMED> inline
	size_t GetUnusedNameImplement( const MQDocument doc, std::vector<char>* const buf, const std::vector<char>::size_type initial_size, const char* base_name )
	{
		std::vector<char>::size_type _initial_size = std::max(initial_size, (std::vector<char>::size_type)2); // 2 = one char + null char space
		for ( buf->resize( std::max( buf->size(), _initial_size ), '\0' ); true; buf->resize( buf->size() * 2, '\0' ) )
		{
			get_unused_name_function<T_MQNAMED>( doc, &(*buf)[0], buf->size(), base_name );
			if ( (*buf)[buf->size() - 2] == '\0' ) return strlen(&(*buf)[0]);
		}
		return 0;
	}
	/// @brief バッファに未使用名を書き込む。実装はGetUnusedNameImplementに、こっち側ではエラーチェックだけやってる。
	/// @param obj 名前を取得するオブジェクト。NULL指定すると文字列取得など一切処理が行われず-1が返る。
	/// @param buf 書き込み先バッファアドレス。NULL省略すると勝手に一時バッファを作って文字数だけ計測
	/// @return 取得された文字数
	template <typename T_MQNAMED> inline
	size_t GetUnusedName(	const MQDocument doc, const char* base_name, std::vector<char>* const buf = NULL,
							const std::vector<char>::size_type initial_size = GetNameInitialBufferSize )
	{
		if ( doc == NULL ) return -1;
		else if ( buf == NULL ) {
			std::vector<char> tmp;
			return GetUnusedNameImplement<T_MQNAMED>( doc, &tmp, initial_size, base_name );
		}
		return GetUnusedNameImplement<T_MQNAMED>( doc, buf, initial_size, base_name );
	}
	template <typename T_MQNAMED> inline
	std::string GetUnusedNameString( const MQDocument doc, const char* base_name = NULL ) {
		if ( doc == NULL ) return "";
		std::vector<char> tmp;
		if ( GetUnusedName<T_MQNAMED>( doc, base_name, &tmp ) <= 0 ) return "";
		return &tmp[0];
	}
#endif

}}}
///@endcond
