#include "StdAfx.h"
#include "npcscript.h"

#include "NPC_Common.h"
#include "FileLoader.h"
#include "tokenizer.h"
#include "Stage_Common.h"
#include "BaseObject_Common.h" 

#include "Token.h"
#include "Parser.h"

#ifdef _CLIENT
#include "GameResourceManager.h"
#endif



cNPCScript* cNPCScript::mpNPCScript = NULL;



cNPCScript::cNPCScript()
{
	/// 싱글톤
	if( mpNPCScript )
	{
		assert(NULL);
	}
	else
	{
		mpNPCScript = this;
	}

	mpNPCListMap = new cPointHashMap( 999 );
	mpNPCRegenMap = new cPointHashMap( 999 );
	mpModelNumberMap = new cPointHashMap( 10 );
	mpNpcFuncMap = new cNpcFuncMap( 100 );
}



cNPCScript::~cNPCScript()
{
}

bool cNPCScript::Init( )
{

	/// NPC 고유 리스트 로드
	if( !LoadNPCList() )
	{
		assert(NULL);
		return false;
	}

#ifndef _CLIENT

	for( unsigned short mapNumber = MAP_MIN ; mapNumber <= MAP_MAX ; ++mapNumber )
	{
		/// 맵에 위치한 NPC 로드
		if( !LoadNPCRegen( mapNumber ) )
		{
			assert(NULL);
			return false;
		}
	}

	if( LoadNPCStore() == false )
	{
		assert(NULL);
		return false;
	}

#endif

	return true;
}

void cNPCScript::Release()
{
	if( mpNpcFuncMap )
	{
		cNpcFuncMap::cIterator i = mpNpcFuncMap->Begin();
		cNpcFuncMap::cIterator end = mpNpcFuncMap->End();
		for( ; i != end; ++i )
		{
			sNpcFuncData* data = (sNpcFuncData*)(i->mSecond);
			SAFE_DELETE( data );
		}
		mpNpcFuncMap->Clear();
	}
	
	SAFE_DELETE( mpNpcFuncMap );
	ReleaseModelNumber();
	ReleaseNPCRegen();
	ReleaseNPCList();
}



bool cNPCScript::LoadNPCList()
{

	cFileLoader loader;
	cString pathName = "./Script/Resource/NPCList.txt";

	if( loader.Open( pathName, true ) == false )
	{
		assert( 0 && "failed to load NPCList.txt" );
		return false;
	}

	cTokenizer tokenizer( loader.GetBufferPtr(), loader.GetSize(), " \t\r\n", pathName.Cstr() );
	cString str;

	while( tokenizer.IsEnd() == false )
	{
		/// NPC 번호
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		unsigned long npcClassIdx = str.ToInt(); 

		/// NPC 이름
		if( tokenizer.GetNext( &str ) == false )	{ return false; }

		TCHAR name[32];
#ifdef _CLIENT
		_stprintf( name, _T( "%s" ), GAMERESOURCEMAN->GetNpcName( str.ToInt() ) ); 
#else
		_stprintf( name, _T( "%d" ), str.ToInt() );
#endif

		/// NPC 직업 인덱스
		if( tokenizer.GetNext( &str ) == false )	{ return false; }

		TCHAR jobName[32];
#ifdef _CLIENT
		_stprintf( jobName, _T( "%s" ), GAMERESOURCEMAN->GetNpcName( str.ToInt() ) ); 
#else
		_stprintf( jobName, _T( "%d" ), str.ToInt() );
#endif

		/// NPC 애니메이션 파일 번호(KFM)]
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		unsigned char modelIndex = static_cast<unsigned char>(str.ToInt()); 

		/// NPC 번호
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		unsigned long jobIconLarge = str.ToInt(); 

		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		unsigned long jobIconSmall = str.ToInt(); 

		sNPCList* pList = new sNPCList;

		pList->mNpcClassIdx = npcClassIdx; 
		_stprintf( pList->mName, _T( "%s" ), name );
		_stprintf( pList->mJobName, _T( "%s" ), jobName );
		pList->mModelIndex = modelIndex; 
		pList->mJobIconIndexLarge = jobIconLarge;
		pList->mJobIconIndexSmall = jobIconSmall;

		/// 해쉬에 NPC 별로 기록
		if( mpNPCListMap->Insert( pList->mNpcClassIdx, pList ) == false )
		{
			assert(0);
			return false;
		}
	}

	return true;

}



sNPCList* cNPCScript::GetNPCList( unsigned long idx )
{
	sNPCList*	NPClist;
	NPClist = (sNPCList*)mpNPCListMap->GetAt( idx );

	if( !NPClist )
	{
		assert(NULL);
		return NULL;
	}

	return NPClist;
}



void cNPCScript::ReleaseNPCList()
{
	if( mpNPCListMap )
	{
		sNPCList* pInfo = NULL;
		cPointHashMap::cIterator i = mpNPCListMap->Begin();
		cPointHashMap::cIterator end = mpNPCListMap->End();
		for( ; i != end; ++i )
		{
			pInfo = (sNPCList*)((*i).mSecond);
			if( pInfo )
			{
				delete pInfo;
				pInfo = NULL;
			}
		}
		mpNPCListMap->Clear();
		SAFE_DELETE( mpNPCListMap );
	}
}



bool cNPCScript::LoadNPCRegen( unsigned short mapNumber )
{
	cFileLoader loader;
	cString pathName;
	pathName.Format( "./Script/Resource/NPCRegen%d.txt", mapNumber );
	unsigned long insertidx = 1;

	if( loader.Open( pathName, true ) == false )
	{
		/// 파일을 읽지 못하면 정상리턴
		return true;
	}

	if( loader.GetSize() == 0 )
		return true;

	cTokenizer tokenizer( loader.GetBufferPtr(), loader.GetSize(), " \t\r\n", pathName.Cstr() );
	cString str;

	cHashSet* pMondelNumberHashSet = new cHashSet;

	while( tokenizer.IsEnd() == false )
	{
		/// NPC 번호(고유번호 아님-종류번호)
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		unsigned long npcClassIdx = str.ToInt();

		/// NPC 생성위치 x
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		float posX = str.ToFloat(); 

		/// NPC 생성위치 y
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		float posY = str.ToFloat();

		/// NPC 방향
		if( tokenizer.GetNext( &str ) == false )	{ return false; }
		float direction = str.ToFloat();

		sNPCRegen* pList = new sNPCRegen;

		pList->mNpcClassIdx = npcClassIdx;	
		pList->mMapNumber = mapNumber;
		pList->mPosX = posX; 	
		pList->mPosY = posY;
		pList->mDirection = S_ToRadian( direction );

		/// npc 고유번호가 맵넘버 자리까지 침범하는것을 방지
		if( MAPNUMBER_UNIT <= insertidx )
		{
			assert(NULL);
			return false;
		}

		/// NPC 고유 인덱스를 겹치지 않게 하기위해 맵넘버를 포함해 고유번호를 만들었다.
		if( mpNPCRegenMap->Insert( ( mapNumber * MAPNUMBER_UNIT ) + insertidx, pList ) == false )
		{
			assert(0);
			return false;
		}
		++insertidx;

		/// 클라이언트에 loadstage시점에 먼저 데이타를 읽어와야 할 npc 목록 기록
		if( pMondelNumberHashSet->GetCount( pList->mNpcClassIdx ) == 0 )
		{
			pMondelNumberHashSet->Insert( pList->mNpcClassIdx );
		}
	}

	if( mpModelNumberMap->Insert( mapNumber, pMondelNumberHashSet ) == false )
	{
		assert(NULL);
		return false;
	}

	return true;
}



sNPCRegen* cNPCScript::GetNPCRegen( unsigned long idx )
{

	sNPCRegen*	pNPCRegen;
	pNPCRegen = (sNPCRegen*)mpNPCRegenMap->GetAt( idx );

	/// 없는 경우 NPC를 삭제 한경우
	if( !pNPCRegen )
	{
		return NULL;
	}

	return pNPCRegen;

}



void cNPCScript::ReleaseNPCRegen()
{
	if( mpNPCRegenMap )
	{
		sNPCRegen* pInfo = NULL;
		cPointHashMap::cIterator i = mpNPCRegenMap->Begin();
		cPointHashMap::cIterator end = mpNPCRegenMap->End();
		for( ; i != end; ++i )
		{
			pInfo = (sNPCRegen*)((*i).mSecond);
			if( pInfo )
			{
				delete pInfo;
				pInfo = NULL;
			}
		}
		mpNPCRegenMap->Clear();
		SAFE_DELETE( mpNPCRegenMap );
	}

}



void cNPCScript::ReleaseModelNumber()
{
	if( mpModelNumberMap )
	{
		cHashSet* pInfo = NULL;
		cPointHashMap::cIterator i = mpModelNumberMap->Begin();
		cPointHashMap::cIterator end = mpModelNumberMap->End();
		for( ; i != end; ++i )
		{
			pInfo = (cHashSet*)(*i).mSecond;
			if( pInfo )
			{
				SAFE_DELETE(pInfo);
			}
		}
		mpModelNumberMap->Clear();
		SAFE_DELETE( mpModelNumberMap );
	}
}


bool cNPCScript::LoadNPCStore()
{
	/// 파일 로딩
	cFileLoader loader;
	cString pathName;
	pathName.Format( "./Script/Resource/NPC_Func.txt" );

	if( loader.Open( pathName, true ) == false )
	{
		return false;
	}

	/// 렉서에 밥주고 파싱
	cToken token;
	cNpcStore lexer( loader.GetBufferPtr(), loader.GetSize() );
	cParser parser( &lexer, pathName );

	while( lexer.IsEnd() == false )
	{
		lexer.GetNextToken( &token );

		switch( token.mType )
		{
		case eTOKEN_ERROR:
			return false;
		case eTOKEN_NULL:
			continue;
		case eTOKEN_NPC_TELLING:
			{
				/// Npc 대화 인덱스
				unsigned int i = parser.ParseInt();

				/// 세부로딩
				sNpcFuncData* data = new sNpcFuncData;
				memset( data, 0, sizeof(data) );

				LoadNpcStoreDetail( parser, data );

				/// Npc 대화 배열에 추가
				if( mpNpcFuncMap->Insert( i, data ) == false )
				{
					assert( 0 && "failed to insert mpNpcFuncMap, maybe already exist" );
					return false;
				}
			}
			break;
		default:
			assert( 0 && "invalid token" );
			return false;
		}
	}
	return true;
}


unsigned long cNPCScript::LoadNpcStoreDetail( cParser& parser, sNpcFuncData* data )
{
	if( parser.ExpectTokenString( "{" ) == false )
		return false;

	cToken token;
	cLexer* lexer = parser.GetLexer();

	unsigned long npcType = NPC_FUNC_NULL;
	unsigned long openCnt = 1;
	int& count = data->mFuncCount;

	while( lexer->GetNextToken( &token ) )
	{
		if( token == "{" )
			++openCnt;

		if( token == "}" )
			--openCnt;

		if( openCnt == 0 )
			break;

		switch( token.mType )
		{
		case eTOKEN_STORE:
			{
				data->mFunc[count] = NPC_FUNC_STORE;
				assert( count < NPC_FUNC_MAX );

				if( count < NPC_FUNC_MAX )
					count++;
			}
			break;
		case eTOKEN_WAREHOUSE:
			{
				data->mFunc[count] = NPC_FUNC_WAREHOUSE;
				assert( count < NPC_FUNC_MAX );

				if( count < NPC_FUNC_MAX )
					count++;
			}
			break;
		case eTOKEN_SKILLSTORE:
			{
				data->mFunc[count] = NPC_FUNC_SKILLSTORE;
				assert( count < NPC_FUNC_MAX );

				if( count < NPC_FUNC_MAX )
					count++;
			}
			break;
		case eTOKEN_GUILD:
			{
				data->mFunc[count] = NPC_FUNC_GUILD;
				assert( count < NPC_FUNC_MAX );

				if( count < NPC_FUNC_MAX )
					count++;
			}
			break;
		case eTOKEN_TAROT:
			{
				data->mFunc[count] = NPC_FUNC_TAROT;
				assert( count < NPC_FUNC_MAX );

				if( count < NPC_FUNC_MAX )
					count++;

				/// 타로 요금정보
				parser.ParseInt();
				data->mTarotPrice = (unsigned long)parser.ParseInt();
			}
			break;
		}
	}

	return npcType;
}

bool cNPCScript::IsHaveNpcFunc( unsigned long classIdx, unsigned int func )
{
	sNpcFuncData* data = (sNpcFuncData*)mpNpcFuncMap->GetAt( classIdx );
	if( !data )
	{
		assert(0);
		return false;
	}

	for( int i = 0; i < data->mFuncCount; ++i )
	{
		if( data->mFunc[i] == func )
			return true;
	}
	return false;
}

sNpcFuncData* cNPCScript::GetNpcFuncData( unsigned long classIdx )
{
	return (sNpcFuncData*)mpNpcFuncMap->GetAt( classIdx );
}

cNpcStore::cNpcStore( const char* buffer, unsigned int size )
: cLexer( buffer, size )
{
	BindKeyword( "npc_telling", eTOKEN_NPC_TELLING );

	BindKeyword( "store", eTOKEN_STORE );
	BindKeyword( "warehouse", eTOKEN_WAREHOUSE );
	BindKeyword( "skillstore", eTOKEN_SKILLSTORE );
	BindKeyword( "default", eTOKEN_NONE );
	BindKeyword( "disjoint", eTOKEN_NONE );
	BindKeyword( "guild", eTOKEN_GUILD );
	BindKeyword( "tarot", eTOKEN_TAROT );
	BindKeyword( "spread", eTOKEN_NONE );
	BindKeyword( "quest", eTOKEN_NONE );

	BindKeyword( "text", eTOKEN_NONE );
	BindKeyword( "page", eTOKEN_NONE );

	BindKeyword( "new", eTOKEN_NONE );
	BindKeyword( "play", eTOKEN_NONE );
	BindKeyword( "complete", eTOKEN_NONE );

	BindKeyword( "answer", eTOKEN_NONE );
	BindKeyword( "sound", eTOKEN_NONE );
	BindKeyword( "scene", eTOKEN_NONE );

	BindKeyword( "next", eTOKEN_NONE );
	BindKeyword( "ANSWER_YES", eTOKEN_NONE );
	BindKeyword( "ANSWER_REWARD", eTOKEN_NONE );
}
