#include "StdAfx.h"
#include "nlib_include.h"

using namespace nlib;




static NicoLiveHeartBeatContainer heartBeatContainer;
static CRITICAL_SECTION heartBeatCs;



//
//マクロ
//
#define XMLSPACE L" \t\r\n"

#define parseAttr(indexPtr,ans,ane,avs,ave)\
	(ans) = (indexPtr);\
	(ane) = wcspbrk((ans),XMLSPACE L"=");\
	(avs) = wcsstr((ane),L"\"") + 1;\
	(ave) = wcsstr((avs),L"\"");\
	(indexPtr) = (ave) + 1

#define skipXMLSPACE(indexPtr)\
	for(;(*(indexPtr)) == L' ' || (*(indexPtr)) == '\t' || (*(indexPtr)) == '\r' || (*(indexPtr)) == '\n';indexPtr++)

#define partString(ptr,start,end)\
	ptr = start;\
	end[0] = L'\0'


#define convertXMLTEXT(buf,basePtr,convPtr,endPtr)\
	(buf) = (basePtr);\
	for(;(basePtr) < (endPtr);(basePtr)++,(convPtr)++){\
		if(wcsncmp((basePtr),L"&amp;",wcslen(L"&amp;")) == 0){\
			(convPtr)[0] = L'&';\
			(basePtr) += wcslen(L"&amp;") - 1;\
		} else if(wcsncmp((basePtr),L"&lt;",wcslen(L"&lt;")) == 0){\
			(convPtr)[0] = L'<';\
			(basePtr) += wcslen(L"&lt;") - 1;\
		} else if(wcsncmp((basePtr),L"&gt;",wcslen(L"&gt;")) == 0){\
			(convPtr)[0] = L'>';\
			(basePtr) += wcslen(L"&gt;") - 1;\
		} else if(wcsncmp((basePtr),L"&quot;",wcslen(L"&quot;")) == 0){\
			(convPtr)[0] = L'\"';\
			(basePtr) += wcslen(L"&quot;") - 1;\
		} else if(wcsncmp((basePtr),L"&apos;",wcslen(L"&apos;")) == 0){\
			(convPtr)[0] = L'\'';\
			(basePtr) += wcslen(L"&apos;") - 1;\
		} else {\
			(convPtr)[0] = (basePtr)[0];\
		}\
	}\
	(convPtr)[0] = L'\0'

#define getBaseVpos(server_time,base_time)	(((server_time) - (base_time)) * 100)

#define setMsec(mSec)\
	timeBeginPeriod(1);\
	(mSec) = timeGetTime();\
	timeEndPeriod(1)

#define NicoLiveStream_setVpos(nl,attr)\
	(nl).chatManager.baseVpos = getBaseVpos((wcstoul((attr),NULL,0)),((nl).playerStatus.stream.base_time));\
	setMsec((nl).chatManager.commentStartTime)




//
//プライベート関数
//



///
///プレイヤーステータス取得
///
//static INLINE NLIB_RESULT GetPlayerStatus(NicoLivePlayerStatus_P pPlayerStatus,LPCTSTR userSession,HINTERNET hConnect,LPVOID buffer);
static INLINE NLIB_RESULT GetPlayerStatus(NicoLivePlayerStatus_P pPlayerStatus,LPCTSTR userSession,HINTERNET hConnect);

///
///nicoLiveHistory取得
///
static INLINE NLIB_RESULT GetNicoLiveHistory(NicoLiveStream_P self,HINTERNET hConnect);


///
///生主情報取得
///
static INLINE NLIB_RESULT GetPublishStatus(NicoLivePublishStatus_P self,LPCTSTR userSession,HINTERNET hConnect);









///
///チャット受信ループ開始関数
///
static unsigned int WINAPI NicoLiveRecvChat(void* lpBuffer);

///
///ハートビート送信ループ関数
///
static unsigned int WINAPI  NicoLiveHeartBeatThread(void* lpBuffer);


#ifdef USE_NLIB_KEEPALIVE

///
///キープアライブ送信ループ関数
///
static unsigned int WINAPI  NicoLiveKeapALiveThread(void* lpBuffer);

#endif //USE_NLIB_KEEPALIVE




///
///チャット受信ループ関数
///
static BOOL NicoLiveRecvChatLoop(NicoLiveStream_P self);

///
///ループ
///
static unsigned int loopCommon(NicoLiveStream_P self,WSAEVENT sendEvent,WSAEVENT resetEvent ,NicoLiveOnSend nicoLiveOnSend,LPDWORD time);

///
///コメント解析関数
///
static VOID  NicoLiveParseComment(NicoLiveStream_P self,LPTSTR commentBuf,UINT_PTR endSize,LPTSTR* mailCountBuf,SIZE_T* mailCountBufSize,SIZE_T* commentBufSize);

static void NicoLiveError(void *buf);

static void NicoLiveSelfDisconnect(void *buf);

static VOID OnSendHeartBeat(NicoLiveStream_P self);

static VOID OnSendKeepAllive(NicoLiveStream_P self);
///
///ソケット取得関数
///
static INLINE SOCKET GetConectedSocket(LPCTSTR nordName,LPCTSTR port,int socktype,int addr_famiry,int protocol);

/*
StreamStatus_P NicoLiveStream_getStreamStatus(NicoLiveStream_P self){

	return &self->playerStatus.stream;
}
*/
BOOL Initialize_NicoLive(){





	//ハートビートコンテナ初期化
	ZeroMemory(&heartBeatContainer,sizeof(heartBeatContainer));
	heartBeatContainer.is_restrictSize = sizeof(TCHAR) * LENGTH_1024;
	heartBeatContainer.is_restrictBuff = (LPTSTR)calloc(1,heartBeatContainer.is_restrictSize);
	heartBeatContainer.heartBeat.is_restrict = heartBeatContainer.is_restrictBuff;

	heartBeatContainer.ticketSize = sizeof(TCHAR) * LENGTH_1024;
	heartBeatContainer.ticketBuff = (LPTSTR)calloc(1,heartBeatContainer.ticketSize);
	heartBeatContainer.heartBeat.ticket = heartBeatContainer.ticketBuff;

	InitializeCriticalSection(&heartBeatCs);


	return TRUE;



}


extern BOOL Finalize_NicoLive(){


	DeleteCriticalSection(&heartBeatCs);

	//パーサ後処理
	free(heartBeatContainer.is_restrictBuff);
	free(heartBeatContainer.ticketBuff);


	






	return TRUE;
}


NicoLiveStream_P NicoLiveStream_new(){
	//生成&初期化
	NicoLiveStream_P self = (NicoLiveStream_P)calloc(1,sizeof(NicoLiveStream));
	
	//初期値の設定
	self->chatManager.onHeartBeatSend = OnSendHeartBeat;

	

	InitializeCriticalSection(&self->sessionManager.cs);

	self->chatManager.heartBeatTime = 60000;
	self->chatManager.heartBeatFlag = TRUE;

	

	self->isConnecting = FALSE;

	self->chatManager.endEvent = WSACreateEvent();

	self->chatManager.startEvent = WSACreateEvent();

	self->chatManager.deleteEvent = WSACreateEvent();

	self->chatManager.setHeartBeatTimeEvent = WSACreateEvent();


#ifdef USE_NLIB_KEEPALIVE

	self->chatManager.onKeepAlliveSend = OnSendKeepAllive;

	self->chatManager.keepAliveTime = 900000;

	self->chatManager.keapALiveFlag = FALSE;

	self->chatManager.setKeepAliveTimeEvent = WSACreateEvent();

	self->chatManager.resetEvents[KEEPALLIVERESETEVENT] = WSACreateEvent();

#endif //USE_NLIB_KEEPALIVE

	self->chatManager.resetEvents[0] = WSACreateEvent();

	self->chatManager.resetEvents[1] = WSACreateEvent();

	





	//スレッドの開始
	self->chatManager.hRecvChatHandle = (HANDLE)_beginthreadex(NULL,0,NicoLiveRecvChat,self,0,&self->chatManager.recvChatThreadID);

	self->chatManager.hHeartBeatHandle = (HANDLE)_beginthreadex(NULL,0,NicoLiveHeartBeatThread,self,0,&self->chatManager.heartBeatThreadID);

#ifdef USE_NLIB_KEEPALIVE
	self->chatManager.hKeapALiveHandle = (HANDLE)_beginthreadex(NULL,0,NicoLiveKeapALiveThread,self,0,&self->chatManager.keapALiveThreadID);
#endif

	return self;
}



//
//ニコニコ生放送関連関数
///////////////////////////////////////////////////

VOID NicoLiveStream_delete(NicoLiveStream_P* self){

	NicoLiveStream_disConnect((*self));

	WSASetEvent((*self)->chatManager.deleteEvent);

	if(INVALID_HANDLE_VALUE  != (*self)->chatManager.hRecvChatHandle){
		WaitForSingleObject( (*self)->chatManager.hRecvChatHandle, INFINITE );
		CloseHandle((*self)->chatManager.hRecvChatHandle);
	}

	if(INVALID_HANDLE_VALUE != (*self)->chatManager.hHeartBeatHandle){

		WaitForSingleObject((*self)->chatManager.hHeartBeatHandle,INFINITE);
		CloseHandle((*self)->chatManager.hHeartBeatHandle);
	}

	if(INVALID_HANDLE_VALUE != (*self)->chatManager.hKeapALiveHandle){

		WaitForSingleObject((*self)->chatManager.hKeapALiveHandle,INFINITE);
		CloseHandle((*self)->chatManager.hKeapALiveHandle);

	}

	

	

	

	//後処理&破棄

	DeleteCriticalSection(&(*self)->sessionManager.cs);

	WSACloseEvent((*self)->chatManager.setHeartBeatTimeEvent);
#ifdef USE_NLIB_KEEPALIVE
	WSACloseEvent((*self)->chatManager.setKeepAliveTimeEvent);
	WSACloseEvent((*self)->chatManager.resetEvents[KEEPALLIVERESETEVENT]);
#endif
	WSACloseEvent((*self)->chatManager.endEvent);

	WSACloseEvent((*self)->chatManager.startEvent);

	WSACloseEvent((*self)->chatManager.deleteEvent);

	WSACloseEvent((*self)->chatManager.resetEvents[0]);

	WSACloseEvent((*self)->chatManager.resetEvents[1]);

	




	free((*self));

	(*self) = (NicoLiveStream_P)NULL;

	return;
}


///
///ハートビートの間隔を設定（ミリ秒)
///
VOID NicoLiveStream_setHeartBeatMsec(NicoLiveStream_P self,DWORD msec){

	
	self->chatManager.heartBeatTime  = msec;
	WSASetEvent(self->chatManager.setHeartBeatTimeEvent);
	return;
}

///
///ハートビートの間隔を取得(ミリ秒)
///
DWORD NicoLiveStream_getHeartBeatMsec(NicoLiveStream_P self){

	return self->chatManager.heartBeatTime;


}

///
///ハートビート有効・無効設定
///
VOID NicoLiveStream_setHeartBeatFlag(NicoLiveStream_P self,BOOL flag){

	self->chatManager.heartBeatFlag = flag;

}


///
///ハートビート有効フラグ取得
///
BOOL NicoLiveStream_getHeartBeatFlag(NicoLiveStream_P self){

	return self->chatManager.heartBeatFlag;


}


///
///キープアライブの間隔を設定(ミリ秒)
///
VOID NicoLiveStream_setKeapALiveMsec(NicoLiveStream_P self,DWORD msec){

#ifdef USE_NLIB_KEEPALIVE
	self->chatManager.keepAliveTime = msec;
	WSASetEvent(self->chatManager.setKeepAliveTimeEvent);
#endif
	return;

}

///
///キープアライブの間隔を取得(ミリ秒)
///
DWORD NicoLiveStream_getKeapALiveMsec(NicoLiveStream_P self){

#ifdef USE_NLIB_KEEPALIVE
	return self->chatManager.keepAliveTime;
#else 
	return 0;
#endif

}


///
///キープアライブ有効・無効設定
///
VOID NicoLiveStream_setKeapALiveFlag(NicoLiveStream_P self,BOOL flag){

#ifdef USE_NLIB_KEEPALIVE
	self->chatManager.keapALiveFlag = flag;
#endif
}


///
///キープアライブフラグ取得
///
BOOL NicoLiveStream_getKeapALiveFlag(NicoLiveStream_P self){

#ifdef USE_NLIB_KEEPALIVE
	return self->chatManager.keapALiveFlag;
#else 
	return FALSE;
#endif

}

VOID NicoLiveStrream_setResFrom(NicoLiveStream_P self,INT_PTR res){



	self->res_from = (res < 0 || res > 1000) ? 0 : ((-1) * res);

}

INT_PTR NicoLiveStream_getResFrom(NicoLiveStream_P self){

	return (-1) * self->res_from;
}

NLIB_RESULT NicoLiveStream_connect(NicoLiveStream_P self,NicoVideoAuth_P nicoVideoAuth,LPCTSTR url,NicoRecvCallBack callback,LPVOID option){

#define NICOLIVE_CONNECTIONSTREAM_COOKIE_COUNT		1
#define LIVENO_LENGTH								LENGTH_NICOLIVEID

	NLIB_RESULT rslt = NLIB_ERR_CODE_NOTSET;







	//urlから接続番号を抽出
	LPCTSTR pInputLiveNo = url;
	LPCTSTR psrech = wcsstr(url,L"http://");
	LPTSTR pInputLiveNoEnd;
	TCHAR liveNo[LIVENO_LENGTH] = {0};
	

	if(self == NULL)goto end;

	BOOL sleepFlag = self->isConnecting;
	
	//前につないでいた放送を切断
	NicoLiveStream_disConnect(self);

	if(sleepFlag == TRUE){
		Sleep(1000);
	}

	if(psrech != NULL){

		psrech += 7;

		for(;psrech != NULL;psrech = wcsstr(psrech +1,L"/")){

			pInputLiveNo = psrech + 1;


		}
	}


	_tcstol(pInputLiveNo + 2,&pInputLiveNoEnd,10);

	if(pInputLiveNo == NULL){

		rslt = NLIB_LIVENO_IS_NULL;
		goto end;

	}else if(pInputLiveNoEnd - pInputLiveNo + 2 >= LIVENO_LENGTH - 1){

		rslt = NLIB_LIVENO_FAILED_TOOBIG;
		goto end;

	} else if((wcsncmp(pInputLiveNo,TEXT("lv"),2) == 0)||(( wcsncmp(pInputLiveNo,TEXT("co"),2) == 0) || (wcsncmp(pInputLiveNo,TEXT("ch"),2) == 0) )){


		wcsncpy(liveNo,pInputLiveNo,pInputLiveNoEnd - pInputLiveNo);


	} else if(_wtoi(pInputLiveNo) != 0){

		wcscpy(liveNo,L"lv");
		wcsncat(liveNo,pInputLiveNo,pInputLiveNoEnd - pInputLiveNo);

	} else {

		rslt = NLIB_LIVENO_FAILED_UNDEFEINE;
		goto end;


	}




	wcsncpy(self->userSession,L"Cookie: ",sizeof(self->userSession) / sizeof(self->userSession[0]) - 1);
	wcsncat(self->userSession,nicoVideoAuth->userSession,sizeof(self->userSession) / sizeof(self->userSession[0]) - 1);
	self->userSession[sizeof(self->userSession) / sizeof(self->userSession[0]) - 1] = L'\0';




	{
		TCHAR objectName[LENGTH_256] = {L"api/getplayerstatus?v="};
		wcsncat(objectName,liveNo,sizeof(objectName) / sizeof(objectName[0]));



		
		self->sessionManager.getPlayerStatusSession = WinHttpOpenRequest(hWatchLiveNicoConnect,L"GET",objectName,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);

		if(self->sessionManager.getPlayerStatusSession == NULL){

			goto connecterr;

		}
		
		
	}

	//プレイヤーステータス取得
	EnterCriticalSection(&self->sessionManager.cs);
	rslt = GetPlayerStatus(&self->playerStatus,self->userSession,self->sessionManager.getPlayerStatusSession);

	
	LeaveCriticalSection(&self->sessionManager.cs);

	if(rslt != NLIB_ERR_OK){

		goto connecterr;
	}


	

	//生放送オブジェクトに各引数を設定

	self->callBack = callback;
	self->option = option;


	WSASetEvent(self->chatManager.startEvent);


	if(self->playerStatus.stream.is_owner == TRUE){
		Sleep(1000);
		TCHAR objectName[LENGTH_256] = {L"api/getpublishstatus?v="};
		wcsncat(objectName,self->playerStatus.stream.id,sizeof(objectName) / sizeof(objectName[0]));
		
		self->sessionManager.getPublishStatusSession = WinHttpOpenRequest(hWatchLiveNicoConnect,L"GET",objectName,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);

		if(self->sessionManager.getPublishStatusSession == NULL){
			goto connecterr;
		}
		EnterCriticalSection(&self->sessionManager.cs);
		rslt = GetPublishStatus(&self->publishStatus,self->userSession,self->sessionManager.getPublishStatusSession);
		LeaveCriticalSection(&self->sessionManager.cs);
	}

	if(rslt != NLIB_ERR_OK){

		goto connecterr;

	}

	self->isConnecting = TRUE;


	rslt = NLIB_ERR_OK;

	
end:
	

	return rslt;

connecterr:

	NicoLiveStream_disConnect(self);
	goto end;
}


NLIB_RESULT NicoLiveStream_sendHeartBeat(NicoLiveStream_P self){

	
	NLIB_RESULT rslt = NLIB_ERR_CODE_NOTSET;

	if(self->isConnecting == FALSE){
		rslt = NLIB_ERR_NOT_LIVECONNECT;
		goto end;
	}
	


	EnterCriticalSection(&self->sessionManager.cs);
	{

		const UINT_PTR buflen = LENGTH_65536;
		LPVOID buffer = malloc(buflen * sizeof(TCHAR));
	 
		LPTSTR objectName = (LPTSTR)buffer;
		LPTSTR indexPtr;
		//LPTSTR sendBuffer = (LPTSTR)&objectName[LENGTH_4096];
		LPSTR recvBuffer = (LPSTR)&objectName[LENGTH_4096];
		DWORD readSize;
		DWORD statusCode;
		DWORD statusCodeSize = sizeof(statusCode);

		wcscpy(objectName,L"/api/heartbeat?v=");
		indexPtr = objectName + wcslen(objectName);
		wcscpy(indexPtr,self->playerStatus.stream.id);


	
		if(self->sessionManager.heartBeatSession == NULL){
			self->sessionManager.heartBeatSession = WinHttpOpenRequest(hWatchLiveNicoConnect,L"GET",objectName,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);

			if(self->sessionManager.heartBeatSession == NULL){
				goto connectend;
			}
		
		}
		if(WinHttpSendRequest(self->sessionManager.heartBeatSession,self->userSession,-1,WINHTTP_NO_REQUEST_DATA,0,0,0) == FALSE){

			rslt = NLIB_ERR_FAILED_NETWORK;
			goto connectend;

		}

		if(WinHttpReceiveResponse(self->sessionManager.heartBeatSession,NULL) == FALSE){

			rslt = NLIB_HEARTBEAT_FAILED_CONNECT;
			goto connectend;

		}


		if (WinHttpQueryHeaders(self->sessionManager.heartBeatSession,WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,WINHTTP_HEADER_NAME_BY_INDEX,&statusCode,&statusCodeSize,WINHTTP_NO_HEADER_INDEX) == FALSE){
			rslt = NLIB_HEARTBEAT_FAILED_CONNECT;
			goto connectend;
		}


		if (HTTP_STATUS_OK != statusCode){
			rslt = NLIB_HEARTBEAT_FAILED_CONNECT;
			goto connectend;
		}


		//recvBuffer = (LPSTR)sendBuffer;


		if(ReadHttpBody(self->sessionManager.heartBeatSession,(LPBYTE)recvBuffer,buflen,&readSize) == FALSE){

			rslt = NLIB_HEARTBEAT_FAILED_CONNECT;
			goto connectend;

		}

		{
			
			
			EnterCriticalSection(&heartBeatCs);

			XMLParse(&heartBeatContainer,recvBuffer,NicoLiveHeartBeat_ElementStartHandler,NicoLiveHeartBeat_ElementEndHandler,NicoLiveHeartBeat_CharacterDataHandler);

			self->chatManager.baseVpos = getBaseVpos(heartBeatContainer.heartBeat.time, self->playerStatus.stream.base_time);
			setMsec(self->chatManager.commentStartTime);

			self->callBack(NICOLIVE_EVENT_SEND_HEARTBEAT,self,self->option,(NICOLIVE_PARAM)&heartBeatContainer.heartBeat,(NICOLIVE_PARAM)NULL);

			ZeroMemory(&heartBeatContainer.heartBeat,sizeof(heartBeatContainer.heartBeat));

			ZeroMemory(heartBeatContainer.is_restrictBuff,heartBeatContainer.is_restrictSize);

			ZeroMemory(heartBeatContainer.ticketBuff,heartBeatContainer.ticketSize);

			heartBeatContainer.heartBeat.is_restrict = heartBeatContainer.is_restrictBuff;

			heartBeatContainer.heartBeat.ticket = heartBeatContainer.ticketBuff;

			LeaveCriticalSection(&heartBeatCs);


		}

		rslt = NLIB_ERR_OK;
connectend:
		free(buffer);
		//ZeroMemory(self->sessionManager.buffer,sizeof(self->sessionManager.buffer));
		LeaveCriticalSection(&self->sessionManager.cs);


		

	}
end:
	

	return rslt;
}

VOID NicoLiveStream_disConnect(NicoLiveStream_P self){

	if(self == NULL)goto end;

	DWORD waitRslt = WSA_WAIT_TIMEOUT;

	do{
		while(WSAResetEvent(self->chatManager.startEvent) == FALSE);
		while(WSASetEvent(self->chatManager.endEvent)== FALSE);

	



		waitRslt = WSAWaitForMultipleEvents(sizeof(self->chatManager.resetEvents) / sizeof(self->chatManager.resetEvents[0]),self->chatManager.resetEvents,TRUE,3000,FALSE);
	} while(waitRslt == WSA_WAIT_TIMEOUT);


	//nicoLiveStream_Initialize(*self);
	ZeroMemory(&self->playerStatus,sizeof(self->playerStatus));
	WSAResetEvent(self->chatManager.endEvent);
	
	WinHttpCloseHandle(self->sessionManager.getPlayerStatusSession);
	self->sessionManager.getPlayerStatusSession = NULL;
	WinHttpCloseHandle(self->sessionManager.heartBeatSession);
	self->sessionManager.heartBeatSession = NULL;
	WinHttpCloseHandle(self->sessionManager.getPublishStatusSession);
	self->sessionManager.getPublishStatusSession = NULL;


	self->isConnecting = FALSE;


end:


	return;



}


#define NICOLIVE_SENDCHATBUFLEN			LENGTH_4096
#define NICOLIVE_SENDFORMAT				"<chat thread=\"%d\" ticket=\"%S\"  postkey=\"%s\" user_id=\"%S\" premium=\"%d\" locale=\"%S\" vpos=\"%ld\"%s>%S</chat>"

NLIB_RESULT NicoLiveStream_sendChat(NicoLiveStream_P self,LPCTSTR chatbuf,LPCTSTR *mail,LPCTSTR *extends){

	NLIB_RESULT rslt = NLIB_ERR_CODE_NOTSET;
	EnterCriticalSection(&self->sessionManager.cs);
	
	{
		const UINT_PTR buflen = LENGTH_65536 * 4;
		//LPVOID chunk = self->sessionManager.buffer;
		LPVOID chunk = malloc(buflen *sizeof(TCHAR));
		LPSTR postKey;
		LPSTR postKeyBuf = (LPSTR)chunk;
		LPTSTR postKeyReqBuf = (LPTSTR)&postKeyBuf[NICOLIVE_SENDCHATBUFLEN];
		LPTSTR cookieBuf = &postKeyReqBuf[NICOLIVE_SENDCHATBUFLEN];
		DWORD statusCode;
		DWORD statusCodeSize = sizeof(statusCode);
		DWORD readSize;

		//HINTERNET hWatchLiveNicoVideo = NULL;
		HINTERNET hGetPostKeySession = NULL;
		

		if(self->isConnecting == FALSE){
			rslt = NLIB_ERR_NOT_LIVECONNECT;
			goto errorend;
		}
		if(chatbuf == NULL || wcslen(chatbuf) >= LENGTH_1024){

			rslt = NLIB_ERR_BUFFER_TOOLITTLE;
			goto errorend;

		}


		//postkey取得
		_swprintf(postKeyReqBuf,L"api/getpostkey?thread=%u&block_no=%u",self->playerStatus.ms.thread,self->chatManager.chatNo / 100);

		//hWatchLiveNicoVideo = WinHttpConnect(hHttpSession,WTEXT(WATCH_LIVE_NICO_VIDEO_DOMEINNAME),INTERNET_DEFAULT_HTTP_PORT,0);
		hGetPostKeySession = WinHttpOpenRequest(hWatchLiveNicoConnect,L"GET",postKeyReqBuf,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);


		//_swprintf(cookieBuf,L"Cookie: %s; %s",self->userSession,self->nicoLiveHistory);
		_swprintf(cookieBuf,L"Cookie: %s",self->userSession);

		if(WinHttpSendRequest(hGetPostKeySession,self->userSession,-1,WINHTTP_NO_REQUEST_DATA,0,0,0) == FALSE){

			rslt = NLIB_ERR_LIVE_GETPOSTKEY_FAILED;
			goto errorend;

		}


		if(WinHttpReceiveResponse(hGetPostKeySession,NULL) == FALSE){

			rslt = NLIB_ERR_LIVE_GETPOSTKEY_FAILED;
			goto errorend;

		}


		if (WinHttpQueryHeaders(hGetPostKeySession,WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,WINHTTP_HEADER_NAME_BY_INDEX,&statusCode,&statusCodeSize,WINHTTP_NO_HEADER_INDEX) == FALSE){
			rslt = NLIB_ERR_LIVE_GETPOSTKEY_FAILED;
			goto errorend;
		}


		if (HTTP_STATUS_OK != statusCode){
			rslt = NLIB_ERR_LIVE_GETPOSTKEY_FAILED;
			goto errorend;
		}





		if(ReadHttpBody(hGetPostKeySession,(LPBYTE)postKeyBuf,buflen,&readSize) == FALSE){

			rslt = NLIB_ERR_LIVE_GETPOSTKEY_FAILED;
			goto errorend;

		}
		postKeyBuf[readSize] = '\0';
		postKey = strstr(postKeyBuf,"=") + 1;


		{
			LPSTR buffer = (LPSTR)&cookieBuf[NICOLIVE_SENDCHATBUFLEN];
			LPSTR indexPtr;
			LPSTR mailBuf;
			LPSTR extendBuf;
		
			//mailコマンド作成
			if(mail == NULL || mail[0] == NULL){

				mailBuf = "";

			} else {
				INT_PTR len;
				mailBuf = (LPSTR)postKeyReqBuf;
				indexPtr = mailBuf;
				strcpy(indexPtr,"mail=\"");
				indexPtr += strlen(indexPtr);

				len = NICOLIVE_SENDCHATBUFLEN - (indexPtr - mailBuf) - 1;
				if(GetLenToMB(CP_UTF8,mail[0]) >= len){
					rslt = NLIB_ERR_BUFFER_TOOLITTLE;
					goto errorend;

				}

				
					WideToMB(CP_UTF8,mail[0],indexPtr,len);
					indexPtr += strlen(indexPtr);


					for(mail++;mail[0] != NULL;mail++){

						indexPtr[0] = ' ';
						indexPtr++;

						len =  NICOLIVE_SENDCHATBUFLEN - (indexPtr - mailBuf) - 1;

						if(GetLenToMB(CP_UTF8,mail[0]) >= len){
							rslt = NLIB_ERR_BUFFER_TOOLITTLE;
							goto errorend;

						}

						WideToMB(CP_UTF8,mail[0],indexPtr,len);
						indexPtr += strlen(indexPtr);

					}
				
				strcpy(indexPtr,"\"");




			}

			//拡張属性作成
			if(extends == NULL || extends[0] == NULL){

				extendBuf = "";

			} else {
				INT_PTR len;
				extendBuf  =(LPSTR)cookieBuf;
				indexPtr = extendBuf;


				len = NICOLIVE_SENDCHATBUFLEN - (indexPtr - extendBuf) - 1;

				if(GetLenToMB(CP_UTF8,extends[0]) >= len){
					rslt = NLIB_ERR_BUFFER_TOOLITTLE;
					goto errorend;

				}
				WideToMB(CP_UTF8,extends[0],indexPtr,len);

				indexPtr += strlen(indexPtr);

				strcpy(indexPtr,"=\"");
				indexPtr += strlen(indexPtr);


				len = NICOLIVE_SENDCHATBUFLEN - (indexPtr - extendBuf) - 1;

				if(GetLenToMB(CP_UTF8,extends[1]) >= len){
					rslt = NLIB_ERR_BUFFER_TOOLITTLE;
					goto errorend;

				}
				WideToMB(CP_UTF8,extends[1],indexPtr,len);


				for(extends+= 2;extends[0] != NULL;extends+=2){

					indexPtr[0] = ' ';
					indexPtr++;

					len = NICOLIVE_SENDCHATBUFLEN - (indexPtr - extendBuf) - 1;

					if(GetLenToMB(CP_UTF8,extends[0]) >= len){
						rslt = NLIB_ERR_BUFFER_TOOLITTLE;
						goto errorend;

					}
					WideToMB(CP_UTF8,extends[0],indexPtr,len);

					indexPtr += strlen(indexPtr);

					indexPtr[0] = '=';
					indexPtr++;


					len = NICOLIVE_SENDCHATBUFLEN - (indexPtr - extendBuf) - 1;

					if(GetLenToMB(CP_UTF8,extends[1]) >= len){
						rslt = NLIB_ERR_BUFFER_TOOLITTLE;
						goto errorend;

					}
					WideToMB(CP_UTF8,extends[1],indexPtr,len);



				}
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\"");



			}

			//チャット送信コメント作成
			{
				indexPtr = buffer;
				//本当はこうしたい:"<chat thread=\"%d\" ticket=\"%S\"  postkey=\"%s\" user_id=\"%S\" premium=\"%d\" locale=\"%S\" vpos=\"%ld\"%s>%S</chat>"
				strcpy(indexPtr,"<chat thread=\"");
				indexPtr += strlen(indexPtr);
				sprintf(indexPtr,"%u",self->playerStatus.ms.thread);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\" ticket=\"");
				indexPtr += strlen(indexPtr);
				WideToMB(CP_UTF8,self->ticket,indexPtr,NICOLIVE_SENDCHATBUFLEN - (indexPtr - buffer) - 1);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\"  postkey=\"");
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,postKey);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\" user_id=\"");
				indexPtr += strlen(indexPtr);
				WideToMB(CP_UTF8,self->playerStatus.user.user_id,indexPtr,NICOLIVE_SENDCHATBUFLEN - (indexPtr - buffer) - 1);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\" premium=\"");
				indexPtr += strlen(indexPtr);
				sprintf(indexPtr,"%u",self->playerStatus.user.is_premium);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\" locale=\"");
				indexPtr += strlen(indexPtr);
				WideToMB(CP_UTF8,self->playerStatus.user.userDomain,indexPtr,NICOLIVE_SENDCHATBUFLEN - (indexPtr - buffer) - 1);
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\" vpos=\"");
				indexPtr += strlen(indexPtr);
			
			
				timeBeginPeriod(1);
				sprintf(indexPtr,"%u",(self->chatManager.baseVpos + ((timeGetTime() - self->chatManager.commentStartTime) / 10)));
				timeEndPeriod(1);
			
				indexPtr += strlen(indexPtr);
				strcpy(indexPtr,"\"");
				indexPtr += strlen(indexPtr);

				if(strlen(mailBuf) > 0 && strlen(extendBuf) > 0){

					indexPtr[0] = ' ';
					indexPtr++;
					strcpy(indexPtr,mailBuf);
					indexPtr += strlen(indexPtr);

					indexPtr[0] = ' ';
					indexPtr++;

					strcpy(indexPtr,extendBuf);
					indexPtr += strlen(indexPtr);


				} else if(strlen(mailBuf) > 0){
					indexPtr[0] = ' ';
					indexPtr++;
					strcpy(indexPtr,mailBuf);
					indexPtr += strlen(indexPtr);

				} else if(strlen(extendBuf) > 0){
					indexPtr[0] = ' ';
					indexPtr++;
					strcpy(indexPtr,extendBuf);
					indexPtr += strlen(indexPtr);
				}
				strcpy(indexPtr,">");
				indexPtr += strlen(indexPtr);
				{
					LPTSTR sendBuf = (LPTSTR)&buffer[NICOLIVE_SENDCHATBUFLEN * 4];
					LPTSTR sendPtr = sendBuf;
					LPCTSTR endPtr= chatbuf + wcslen(chatbuf);


					//convertTEXT(indexPtr,chatbuf,indexPtr,endPtr);

					for(;(chatbuf) <= (endPtr);(chatbuf)++){
						switch(chatbuf[0]){
						case L'&':
							wcscpy((sendBuf),L"&amp;");
							(sendBuf) += wcslen(sendBuf);
							break;
						case L'<':
							wcscpy((sendBuf),L"&lt;");
							(sendBuf) += wcslen(sendBuf);
							break;
						case L'>':
							wcscpy((sendBuf),L"&gt;");
							(sendBuf) += wcslen(sendBuf);
							break;
						case L'\"':
							wcscpy((sendBuf),L"&quot;");
							(sendBuf) += wcslen(sendBuf);
							break;
						case L'\'':
							wcscpy((sendBuf),L"&apos;");
							(sendBuf) += wcslen(sendBuf);
							break;
						default:
							sendBuf[0] = chatbuf[0];
							(sendBuf)++;
							break;
						}
					}


					WideToMB(CP_UTF8,sendPtr,indexPtr,NICOLIVE_SENDCHATBUFLEN * 4 - (indexPtr - buffer) - 1);
					indexPtr += strlen(indexPtr);
				
				}
				strcpy(indexPtr,"</chat>");
				indexPtr[strlen(indexPtr)] = '\0';



				{
					INT_PTR sendLen = 0;
					INT_PTR sentLen = 0;
					INT_PTR max = strlen(buffer) + 1;

					do{
						sendLen = send(self->chatManager.sock,buffer,max - sentLen,0);
						sentLen += sendLen;
					} while(sendLen > 0);

				}
			}
		}

		rslt = NLIB_ERR_OK;
		
errorend:
	
		WinHttpCloseHandle(hGetPostKeySession);
		free(chunk);
		//ZeroMemory(self->sessionManager.buffer,sizeof(self->sessionManager.buffer));
		LeaveCriticalSection(&self->sessionManager.cs);
		}



	return rslt;

}

NLIB_RESULT NicoLiveStream_sendOwnerChat(NicoLiveStream_P self,LPCTSTR chatbuf,LPCTSTR *mail,LPCTSTR *extends){

	static const LPCTSTR OWNERCHAT_REQUEST = TEXT("api/broadcast/%s?body=%s%s&token=%s%s");
	NLIB_RESULT rslt = NLIB_ERR_CODE_NOTSET;
	LPVOID buffer = malloc(LENGTH_65536 * sizeof(TCHAR));
	LPTSTR requestBuffer = (LPTSTR)buffer;
	const UINT_PTR buflen = LENGTH_8192;
	LPTSTR mailBuf = (LPTSTR)&requestBuffer[buflen];
	LPTSTR extendsBuf = (LPTSTR)&mailBuf[buflen];
	LPSTR recvBuffer = (LPSTR)&extendsBuf[buflen];
	
	DWORD readSize;
	DWORD statusCode;
	DWORD statusCodeSize = sizeof(statusCode);
	UINT_PTR reqLength = _tcslen(chatbuf);
	HINTERNET hRequest = NULL;

	if(self == NULL){

		goto streamnullerr;

	}

	if(self->playerStatus.stream.is_owner == FALSE){

		goto notownererr;

	}

	for(LPCTSTR *mailStart = mail;mailStart[0] != NULL;++mailStart){

		reqLength += _tcslen(mailStart[0]);

	}

	LPCTSTR *extendsArrayIndex = extends;
	for(;extendsArrayIndex[0] != NULL;extendsArrayIndex+=2){

		reqLength += _tcslen(extendsArrayIndex[0]);
		reqLength += _tcslen(extendsArrayIndex[1]);
	}

	if(reqLength > buflen / 2){

		goto buffererr;

	}
	LPTSTR mailIndexPtr = mailBuf;
	if(mail[0] == NULL){
		_tcscpy(mailIndexPtr,TEXT(""));

	} else {

		_tcscpy(mailIndexPtr,TEXT("&mail="));
		mailIndexPtr += _tcslen(mailIndexPtr);

	}
	for(LPCTSTR *mailArrayIndex = mail;mailArrayIndex[0] != NULL;++mailArrayIndex){

		_tcscpy(mailIndexPtr,mailArrayIndex[0]);
		mailIndexPtr += _tcslen(mailIndexPtr);

		if(mailArrayIndex[1] != NULL){

			mailIndexPtr[0] = TEXT(' ');
			++mailIndexPtr;

		}

	}

	LPTSTR extendsIndexPtr = extendsBuf;
	_tcscpy(extendsIndexPtr,TEXT(""));
	for(LPCTSTR *extendsArrayIndex2 = extends;extendsArrayIndex2[0] != NULL;extendsArrayIndex2+=2){

		
		extendsIndexPtr[0] = TEXT('&');
		++extendsIndexPtr;

		
		_tcscpy(extendsIndexPtr,extendsArrayIndex2[0]);
		extendsIndexPtr += _tcslen(extendsIndexPtr);
		extendsIndexPtr[0] = TEXT('=');
		++extendsIndexPtr;
		_tcscpy(extendsIndexPtr,extendsArrayIndex2[1]);
		extendsIndexPtr += _tcslen(extendsIndexPtr);

		
	}

	_stprintf(requestBuffer,OWNERCHAT_REQUEST,self->playerStatus.stream.id,chatbuf,mailBuf,self->publishStatus.token,extendsBuf);

	::EnterCriticalSection(&self->sessionManager.cs);
	hRequest = WinHttpOpenRequest(hWatchLiveNicoConnect,L"GET",requestBuffer,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);

	if(WinHttpSendRequest(hRequest,self->userSession,-1,WINHTTP_NO_REQUEST_DATA,0,0,0) == FALSE){

		goto connecterr;

	}

	if(WinHttpReceiveResponse(hRequest,NULL) == FALSE){

		goto connecterr;
	}


	if (WinHttpQueryHeaders(hRequest,WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,WINHTTP_HEADER_NAME_BY_INDEX,&statusCode,&statusCodeSize,WINHTTP_NO_HEADER_INDEX) == FALSE){
		goto connecterr;
	}


	if (HTTP_STATUS_OK != statusCode){
		goto connecterr;
	}


	//recvBuffer = (LPSTR)sendBuffer;


	if(ReadHttpBody(hRequest,(LPBYTE)recvBuffer,buflen,&readSize) == FALSE){

		goto connecterr;

	}

	rslt = NLIB_ERR_OK;

end:
	free(buffer);
	WinHttpCloseHandle(hRequest);
	::LeaveCriticalSection(&self->sessionManager.cs);
	return rslt;


connecterr:

	rslt = NLIB_FAILED_CONNECT;
	goto end;


buffererr:

	rslt = NLIB_ERR_BUFFER_TOOLITTLE;
	goto end;


streamnullerr:
	rslt = NLIB_ERR_STREAM_IS_NULL;
	goto end;

notownererr:
	rslt = NLIB_ERR_YOUARE_NOTOWNER;
	goto end;
}





//
//インライン関数
//



//NLIB_RESULT GetPlayerStatus(NicoLivePlayerStatus_P self,LPCTSTR userSession,HINTERNET hConnect,LPVOID buffer){
NLIB_RESULT GetPlayerStatus(NicoLivePlayerStatus_P self,LPCTSTR userSession,HINTERNET hConnect){


	return HttpXMLParse(self,userSession,hConnect,NPlayerStatus_ElementStartHandler,NPlayerStatus_ElementEndHandler,NPlayerStatus_CharacterDataHandler);

}

static INLINE NLIB_RESULT GetPublishStatus(NicoLivePublishStatus_P self,LPCTSTR userSession,HINTERNET hConnect){


	return HttpXMLParse(self,userSession,hConnect,NPublishStatus_ElementStartHandler,NPublishStatus_ElementEndHandler,NPublishStatus_CharacterDataHandler);

}


#define GETNICOLIVEHIS_OBJNAMELENGTH	LENGTH_256
#define GETNICOLIVEHIS_COOKIELENGTH		LENGTH_512

#ifdef USE_NLIB_LIVEHISTORY

static INLINE NLIB_RESULT GetNicoLiveHistory(NicoLiveStream_P self,HINTERNET hConnect){

	NLIB_RESULT rslt = NLIB_ERR_OK;

	EnterCriticalSection(&self->sessionManager.cs);
	{
	
	HINTERNET hStreamBrowse = NULL;


	TCHAR objectName[GETNICOLIVEHIS_OBJNAMELENGTH] = {L"/watch/"};
	TCHAR cookie[GETNICOLIVEHIS_COOKIELENGTH] = {L"Cookie: "};
	DWORD statusCode;
	DWORD statusCodeSize = sizeof(statusCode);
	DWORD chunkSize = LENGTH_65536;
	LPVOID chunkMemory =self->sessionManager.buffer;
	


	LPTSTR buffer = (LPTSTR)chunkMemory;



	if((GETNICOLIVEHIS_OBJNAMELENGTH - wcslen(objectName) <= wcslen(self->playerStatus.stream.id)) || GETNICOLIVEHIS_COOKIELENGTH - wcslen(cookie) <= wcslen(self->userSession)){

		rslt = NLIB_ERR_BUFFER_TOOLITTLE;
		goto end;
	} 

	wcscat(objectName,self->playerStatus.stream.id);
	wcscat(cookie,self->userSession);



	

	hStreamBrowse = WinHttpOpenRequest(hNicoVideoConnect,L"GET",objectName,L"1.1",(LPCTSTR)WINHTTP_NO_REFERER,(LPCTSTR*)WINHTTP_DEFAULT_ACCEPT_TYPES,0);




	if(WinHttpSendRequest(hStreamBrowse,cookie,-1,WINHTTP_NO_REQUEST_DATA,0,0,0) == FALSE){

		rslt = NLIB_FAILED_CONNECT;
		goto end;

	}

	if(WinHttpReceiveResponse(hStreamBrowse,NULL) == FALSE){

		rslt = NLIB_FAILED_CONNECT;
		goto end;

	}

	if (WinHttpQueryHeaders(hStreamBrowse,WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,WINHTTP_HEADER_NAME_BY_INDEX,&statusCode,&statusCodeSize,WINHTTP_NO_HEADER_INDEX) == FALSE){
		rslt = NLIB_FAILED_CONNECT;
		goto end;
	}


	if (HTTP_STATUS_OK != statusCode){
		rslt = NLIB_FAILED_CONNECT;
		goto end;
	}



	if (WinHttpQueryHeaders(hStreamBrowse,WINHTTP_QUERY_SET_COOKIE,WINHTTP_HEADER_NAME_BY_INDEX,buffer,&chunkSize,WINHTTP_NO_HEADER_INDEX) == FALSE){
		rslt = NLIB_FAILED_CONNECT;
		goto end;
	}

	{
		LPTSTR start =wcsstr(buffer,L"nicolivehistory=");

		LPTSTR end = wcsstr(start,L";");

		UINT_PTR length = end - start;

		if(length >= sizeof(self->nicoLiveHistory)){
			rslt = NLIB_ERR_BUFFER_TOOLITTLE;
			goto end;

		}

		wcsncpy(self->nicoLiveHistory,start,length);
		self->nicoLiveHistory[length] = L'\0';

	}
end:

	WinHttpCloseHandle(hStreamBrowse);

	}
	ZeroMemory(self->sessionManager.buffer,sizeof(self->sessionManager.buffer));
	LeaveCriticalSection(&self->sessionManager.cs);


	return rslt;



}
#endif







#ifdef USE_NLIB_KEEPALIVE
static unsigned int WINAPI  NicoLiveKeapALiveThread(void* lpBuffer){

	unsigned int rslt = 0;

	NicoLiveStream_P self = (NicoLiveStream_P)lpBuffer;

	rslt = loopCommon(self,self->chatManager.setKeepAliveTimeEvent,self->chatManager.resetEvents[KEEPALLIVERESETEVENT],self->chatManager.onKeepAlliveSend,&self->chatManager.keepAliveTime);

	_endthreadex(rslt);

	return rslt;
}
#endif //USE_NLIB_KEEPALIVE

static unsigned int WINAPI  NicoLiveHeartBeatThread(void* lpBuffer){

	unsigned int rslt = 0;

	NicoLiveStream_P self = (NicoLiveStream_P)lpBuffer;


	rslt = loopCommon(self,self->chatManager.setHeartBeatTimeEvent,self->chatManager.resetEvents[1],self->chatManager.onHeartBeatSend,&self->chatManager.heartBeatTime);



	_endthreadex(rslt);

	return rslt;
}
static unsigned int loopCommon(NicoLiveStream_P self,WSAEVENT sendEvent,WSAEVENT resetEvent ,NicoLiveOnSend nicoLiveOnSend,LPDWORD time){

	unsigned int rslt = 0;
	WSAEVENT events[3] = {self->chatManager.deleteEvent,self->chatManager.startEvent,self->chatManager.endEvent};	//イベント配列
	WSAEVENT streamEvent[2] = {self->chatManager.endEvent,sendEvent};
	DWORD dwResult;																									//イベント受信結果
	DWORD endResult;

	while(TRUE){
restart:
		WSASetEvent(resetEvent);
		
		dwResult = WSAWaitForMultipleEvents(sizeof(events) / sizeof(events[0]),events,FALSE,WSA_INFINITE,FALSE);


		//エラーの場合
		if(dwResult == WSA_WAIT_FAILED){

			goto err;
		}


		switch(dwResult - WSA_WAIT_EVENT_0){

		case 0:

			goto end;

		case 1:
			WSAResetEvent(resetEvent);
			break;

		case 2:
			continue;


		default:

			goto err;

		}

		while(TRUE){
			WSAResetEvent(sendEvent);
			endResult = WSAWaitForMultipleEvents(sizeof(streamEvent) / sizeof(streamEvent[0]),streamEvent,FALSE,*time,FALSE);

			if(endResult == WSA_WAIT_TIMEOUT){

				nicoLiveOnSend(self);
				//dumpln(TEXT("onSend"));

				_heapmin();
			} else{ 
				switch(endResult - WSA_WAIT_EVENT_0){

				case 0:
					goto restart;

				case 1:
					break;

				default:
					goto err;
				}
			} 
		}




	}

end:
	return rslt;

err:
	_beginthread(NicoLiveError,0,self);
	rslt = 1;
	goto end;
}

static VOID OnSendHeartBeat(NicoLiveStream_P self){

	if(self->chatManager.heartBeatFlag){
		NicoLiveStream_sendHeartBeat(self);
	}

}
#ifdef USE_NLIB_KEEPALIVE
static VOID OnSendKeepAllive(NicoLiveStream_P self){
	

	if(self->chatManager.keapALiveFlag && self->playerStatus.stream.is_owner == TRUE){
		LPCTSTR mail[2] = {L"184",NULL};
		NicoLiveStream_sendChat(self,L"/keapalive",mail,NULL);
	}


}
#endif

#define NICOLIVE_RECEVE_CHAT_LENGTH	LENGTH_2048
#define NICOLIVE_REQUEST_CHAT_LENGTH	LENGTH_256
static unsigned int WINAPI NicoLiveRecvChat(void* lpBuffer){


	unsigned int rslt = 0;

	//LPTSTR user_id = NULL;
	//LPTSTR name = NULL;
	//LPTSTR chatBuf = NULL;



	CHAR requestChat[NICOLIVE_REQUEST_CHAT_LENGTH];																	//送信電文用バッファ
	LPCSTR sendFormat = "<thread thread=\"%u\" res_from=\"%d\" version=\"20061206\" />";							//送信電文フォーマット
	NicoLiveStream_P self = (NicoLiveStream_P)lpBuffer;																//ニコ生放送オブジェクト
	INT_PTR sendLen;																								//送信用文字数
	INT_PTR sentLen;																								//送信済み文字数
	WSAEVENT events[3] = {self->chatManager.deleteEvent,self->chatManager.startEvent,self->chatManager.endEvent};	//イベント配列
	DWORD dwResult;																									//イベント受信結果

	

	while(TRUE){

		WSASetEvent(self->chatManager.resetEvents[0]);

		dwResult = WSAWaitForMultipleEvents(sizeof(events) / sizeof(events[0]),events,FALSE,WSA_INFINITE,FALSE);


		//エラーの場合
		if(dwResult == WSA_WAIT_FAILED){

			goto err;
		}


		switch(dwResult - WSA_WAIT_EVENT_0){

		case 0:

			goto end;

		case 1:
			WSAResetEvent(self->chatManager.resetEvents[0]);
			break;

		case 2:
			continue;

		default:

			goto err;

		}


		sendLen = 0;
		sentLen = 0;

		//ソケットの設定
		self->chatManager.sock = GetConectedSocket(self->playerStatus.ms.addr,self->playerStatus.ms.port,SOCK_STREAM,AF_INET,IPPROTO_TCP);


		//コメント受信開始の下準備
		if(self->chatManager.sock == INVALID_SOCKET){
			self->error = NLIB_ERR_FAILED_NETWORK;
			goto err;
		}






		if(NICOLIVE_REQUEST_CHAT_LENGTH <= _scprintf(sendFormat,self->playerStatus.ms.thread,self->res_from)){

			self->error = NLIB_ERR_BUFFER_TOOLITTLE;
			rslt = -1;
			goto err;


		}

		sprintf(requestChat,sendFormat,self->playerStatus.ms.thread,self->res_from);


		//
		//コメント受信要求送信処理
		//
		{
			int len;
			sendLen = strlen(requestChat) + 1;
			do{
				len = send(self->chatManager.sock,requestChat,sendLen,0);
				if(len == SOCKET_ERROR){
					self->error = NLIB_ERR_FAILED_NETWORK;
					rslt = -1;

					goto err;

				}

				sentLen = len;
			}while(sentLen < sendLen);
		}


		if(NicoLiveRecvChatLoop(self) == FALSE){
			goto err;
		}

		
	}




err:

	_beginthread(NicoLiveError,0,self);

end:



	closesocket(self->chatManager.sock);
	_endthreadex(rslt);

	return rslt;





}





static BOOL NicoLiveRecvChatLoop(NicoLiveStream_P self){


	//
	//コメント受信処理
	//
	BOOL rslt = TRUE;										//結果
	LPTSTR commentBuf = NULL;								//コメントバッファ
	LPTSTR* mailCountBuf = NULL;							//メールポインタバッファ
	INT_PTR recevedLen;										//受信文字列長
	INT_PTR messageBufLen;									//バッファ長



	WSAEVENT hEventArray[2];								//受信・切断を検知するイベントオブジェクトの配列
	WSANETWORKEVENTS netEvents;								//ネットワークイベント
	DWORD dwResult;											//イベントがウェイト関数の結果を格納する変数

	
	SIZE_T commentBufSize = sizeof(TCHAR) * LENGTH_2048;	//コメントバッファ長
	SIZE_T mailCountBufSize = sizeof(LPTSTR) * LENGTH_256;	//メールのポインタ用バッファ長


	WSAEVENT hRecvEvent = NULL;

	//受信準備処理
	self->chatManager.commentCountSum = 0;
	hRecvEvent = WSACreateEvent();
	self->chatManager.recvBuf[0] = '\0';
	WSAEventSelect(self->chatManager.sock, hRecvEvent, FD_READ | FD_CLOSE);
	hEventArray[0] = hRecvEvent;
	hEventArray[1] = self->chatManager.endEvent;

	commentBuf = (LPTSTR)malloc(commentBufSize * sizeof(TCHAR));
	mailCountBuf = (LPTSTR*)malloc(mailCountBufSize * sizeof(TCHAR));

	//切断されるまでループ
	while(1){



		//前のコメント受信時に余った文字列の長さを調べる
		messageBufLen = strlen(self->chatManager.recvBuf);

		//受信か終了イベントを検知するまで待機
		dwResult = WSAWaitForMultipleEvents(sizeof(hEventArray) / sizeof(hEventArray[0]),hEventArray,FALSE,WSA_INFINITE,FALSE);

		//エラーの場合
		if(dwResult == WSA_WAIT_FAILED){

			goto err;
		}



		//コメント受信イベントがシグナルになった場合
		if(dwResult - WSA_WAIT_EVENT_0 == 0){

			WSAEnumNetworkEvents(self->chatManager.sock,hRecvEvent,&netEvents);

			//読取イベントの場合コメントを受信する
			if(netEvents.lNetworkEvents & FD_READ){


				recevedLen = recv(self->chatManager.sock,&self->chatManager.recvBuf[messageBufLen],(sizeof(self->chatManager.recvBuf) - 1) - messageBufLen ,0);

				if(recevedLen == SOCKET_ERROR){
					self->error = NLIB_ERR_FAILED_NETWORK;

					goto err;
				}

				self->chatManager.recvBuf[messageBufLen + recevedLen] = '\0';






				NicoLiveParseComment(self,commentBuf,messageBufLen + recevedLen,mailCountBuf,&mailCountBufSize,&commentBufSize);





			} else if(netEvents.lNetworkEvents && FD_CLOSE){

				goto end;

			}//if(netEvents.lNetworkEvents & FD_READ)

		} else if(dwResult - WSA_WAIT_EVENT_0 == 1){

			goto end;

		}//if(dwResult - WSA_WAIT_EVENT_0 == 0)
	}


err:

	rslt = FALSE;

end:
	self->callBack(NICOLIVE_EVENT_DISCONNECT,self,self->option,(NICOLIVE_PARAM)NULL,(NICOLIVE_PARAM)NULL);
	
	free(commentBuf);
	free(mailCountBuf);
	WSACloseEvent(hRecvEvent);

	return rslt;
}




static VOID  NicoLiveParseComment(NicoLiveStream_P self,LPTSTR commentBuf,UINT_PTR endSize,LPTSTR* mailCountBuf,SIZE_T* mailCountBufSize,SIZE_T* commentBufSize){

	LPTSTR indexPtr;										//解析時の指標ポインタ
	NicoLiveChat nicoLiveChat;								//チャットコメント格納用構造体
	NicoLiveThreadComment nicoLiveThreadComment;			//接続応答コメント格納用構造体
	NicoLiveSendResultComment nicoLiveSendResultComment;	//送信応答コメント格納用構造体
	NicoLiveChat nicoLiveChatBase;							//チャットコメント基底構造体
	NicoLiveThreadComment nicoLiveThreadCommentBase;		//接続応答コメント基底構造体
	NicoLiveSendResultComment nicoLiveSendResultCommentBase;//送信応答コメント基底構造体
	LPTSTR attrNameStart;									//属性名開始ポインタ
	LPTSTR attrNameEnd;										//属性名終了ポインタ
	LPTSTR attrValueStart;									//属性値開始ポインタ
	LPTSTR attrValueEnd;									//属性値終了ポインタ
	INT_PTR nameLen;										//属性の名前の長さ
	LPTSTR mailIndexPtr;									//メール属性値解析用指標ポインタ
	LPTSTR xmlTextPtr;										//xmlテキスト形式のデータ解析時に使用するポインタ
	LPTSTR xmlTextEnd;										//xmlテキスト終了ポインタ
	LPTSTR* mailIndex;										//メールコマンドリストポインタ
	BOOL isReceveChat = FALSE;								//チャット受信フラグ
	SIZE_T commentSizeCh;									//受信したコメントをユニコードに変換するときの長さ
	LPSTR recvBufTmp;										//受信文字列操作用ポインタ
	LPSTR recvEndPoint;										//受信文字列終端ポインタ



	recvBufTmp = &self->chatManager.recvBuf[0];
	recvEndPoint = &self->chatManager.recvBuf[endSize];


	//コメント基底構造体初期化
	nicoLiveChatBase.anonymity = L"";
	nicoLiveChatBase.chatBuf = L"";
	nicoLiveChatBase.date = L"";
	nicoLiveChatBase.locale = L"";
	nicoLiveChatBase.mail = mailCountBuf;
	nicoLiveChatBase.name = L"";
	nicoLiveChatBase.no = L"";
	nicoLiveChatBase.premium = L"";
	nicoLiveChatBase.thread = L"";
	nicoLiveChatBase.user_id = L"";
	nicoLiveChatBase.vpos = L"";
	nicoLiveChatBase.mailCount = 0;



	nicoLiveThreadCommentBase.last_res = L"";
	nicoLiveThreadCommentBase.thread = L"";
	nicoLiveThreadCommentBase.ticket = L"";


	nicoLiveSendResultCommentBase.no = L"";
	nicoLiveSendResultCommentBase.status = L"";
	nicoLiveSendResultCommentBase.thread = L"";

	
	//受信したコメントをひとつのxmlノードごとに解析する
	for (;recvBufTmp + strlen(recvBufTmp) < recvEndPoint;recvBufTmp += strlen(recvBufTmp) + 1){


		commentSizeCh = sizeof(TCHAR) * GetLenToWide(CP_UTF8,recvBufTmp);
		extendMalloc(commentBuf,LPTSTR,*commentBufSize,commentSizeCh);
		MBToWide(CP_UTF8,recvBufTmp,commentBuf,*commentBufSize);

		traceln(TEXT("comment:\r\n%s"),commentBuf);
		//threadノードの場合
		if((indexPtr = wcsstr(commentBuf,L"<thread ")) != NULL){

			indexPtr += wcslen(L"<thread ");
			skipXMLSPACE(indexPtr);




			nicoLiveThreadComment = nicoLiveThreadCommentBase;

			while(wcsncmp(indexPtr,L"/>",wcslen(L"/>")) != 0){
				parseAttr(indexPtr,attrNameStart,attrNameEnd,attrValueStart,attrValueEnd);


				nameLen = attrNameEnd - attrNameStart;
				if(wcsncmp(attrNameStart,L"ticket",nameLen) == 0){


					partString(nicoLiveThreadComment.ticket,attrValueStart,attrValueEnd);
					wcscpy(self->ticket,nicoLiveThreadComment.ticket);

				} else if(wcsncmp(attrNameStart,L"last_res",nameLen) == 0){

					partString(nicoLiveThreadComment.last_res,attrValueStart,attrValueEnd);


				} else if(wcsncmp(attrNameStart,L"thread",nameLen) == 0){

					partString(nicoLiveThreadComment.thread,attrValueStart,attrValueEnd);
				} else if(wcsncmp(attrNameStart,L"server_time",nameLen) == 0){

					NicoLiveStream_setVpos((*self),attrValueStart);
					
					nicoLiveThreadComment.server_time = wcstoul(attrValueStart,NULL,10);
					//partString(nicoLiveThreadComment.server_time,attrValueStart,attrValueEnd);
				}

				skipXMLSPACE(indexPtr);
			}

			

			self->callBack(NICOLIVE_EVENT_RECEVE_CONNECTRESULT,self,self->option,(NICOLIVE_PARAM)&nicoLiveThreadComment,self->chatManager.chatNo);

			//chatノードの場合
		} else if((indexPtr = wcsstr(commentBuf,L"<chat ")) != NULL){

			indexPtr += wcslen(L"<chat ");
			skipXMLSPACE(indexPtr);



			
			nicoLiveChat = nicoLiveChatBase;

			while(wcsncmp(indexPtr,L">",wcslen(L">"))){

				parseAttr(indexPtr,attrNameStart,attrNameEnd,attrValueStart,attrValueEnd);

				nameLen = attrNameEnd - attrNameStart;


				if(wcsncmp(attrNameStart,L"no",nameLen) == 0){

					partString(nicoLiveChat.no,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"date",nameLen) == 0){

					NicoLiveStream_setVpos((*self),attrValueStart);

					partString(nicoLiveChat.date,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"thread",nameLen) == 0){

					partString(nicoLiveChat.thread,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"premium",nameLen) == 0){

					partString(nicoLiveChat.premium,attrValueStart,attrValueEnd);

				}  else if(wcsncmp(attrNameStart,L"anonymity",nameLen) == 0){

					partString(nicoLiveChat.anonymity,attrValueStart,attrValueEnd);


				} else if(wcsncmp(attrNameStart,L"locale",nameLen) == 0){

					partString(nicoLiveChat.locale,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"vpos",nameLen) == 0){

					partString(nicoLiveChat.vpos,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"user_id",nameLen) == 0){

					partString(nicoLiveChat.user_id,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"name",nameLen) == 0){

					partString(nicoLiveChat.name,attrNameStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"mail",nameLen) == 0){

					nicoLiveChat.mailCount = 1;


					for(mailIndexPtr = attrValueStart;mailIndexPtr <= attrValueEnd;mailIndexPtr++){

						if(mailIndexPtr[0] == L' '){

							nicoLiveChat.mailCount++;

						}

					}

					extendMalloc(mailCountBuf,LPTSTR*,*mailCountBufSize,nicoLiveChat.mailCount);

					nicoLiveChat.mail = mailCountBuf;
					mailCountBuf[0] = NULL;

					for(mailIndex = mailCountBuf,mailIndexPtr = attrValueStart,mailIndex[0] = mailIndexPtr;mailIndexPtr < attrValueEnd;mailIndexPtr++){


						if(mailIndexPtr[0] == L' '){
							mailIndex++;
							mailIndex[0] = mailIndexPtr + 1;
							mailIndexPtr[0] = L'\0';


						}

					}

					mailIndexPtr[0] = L'\0';

				}

				skipXMLSPACE(indexPtr);
			}

			indexPtr++;
			xmlTextPtr = indexPtr;
			xmlTextEnd = wcsstr(indexPtr,L"<");
			convertXMLTEXT(nicoLiveChat.chatBuf,indexPtr,xmlTextPtr,xmlTextEnd);



			self->chatManager.chatNo =  wcstoul(nicoLiveChat.no,NULL,10);


			
			self->chatManager.commentCountSum++;
			isReceveChat = TRUE;

			check((_heapchk() == _HEAPOK),TEXT("ヒープエラーです"));

			if((wcscmp(nicoLiveChat.chatBuf,L"/disconnect") == 0 && wcscmp(nicoLiveChat.premium,L"2") == 0)){

				
				_beginthread(NicoLiveSelfDisconnect,0,self);
			}
			self->callBack(NICOLIVE_EVENT_RECEVE_CHAT,self,self->option,(NICOLIVE_PARAM)&nicoLiveChat,self->chatManager.commentCountSum);
			

			//chat_resultノードの場合
		} else if((indexPtr = wcsstr(commentBuf,L"<chat_result ")) != NULL){

			indexPtr += wcslen(L"<chat_result ");

			nicoLiveSendResultComment = nicoLiveSendResultCommentBase;

			while(wcsncmp(indexPtr,L"/>",wcslen(L"/>")) != 0){
				parseAttr(indexPtr,attrNameStart,attrNameEnd,attrValueStart,attrValueEnd);


				nameLen = attrNameEnd - attrNameStart;
				if(wcsncmp(attrNameStart,L"no",nameLen) == 0){


					partString(nicoLiveSendResultComment.no,attrValueStart,attrValueEnd);

				} else if(wcsncmp(attrNameStart,L"status",nameLen) == 0){

					partString(nicoLiveSendResultComment.status,attrValueStart,attrValueEnd);


				} else if(wcsncmp(attrNameStart,L"thread",nameLen) == 0){

					partString(nicoLiveSendResultComment.thread,attrValueStart,attrValueEnd);
				}

				skipXMLSPACE(indexPtr);
			}

#ifdef USE_NLIB_KEEPALIVE
			//キープアライブリセット
			WSASetEvent(self->chatManager.setKeepAliveTimeEvent);
#endif //USE_NLIB_KEEPALIVE

			self->callBack(NICOLIVE_EVENT_RECEVE_CHATSENDRESULT,self,self->option,(NICOLIVE_PARAM)&nicoLiveSendResultComment,(NICOLIVE_PARAM)NULL);

		}

	}

	if(recvBufTmp == recvEndPoint && isReceveChat){
		self->callBack(NICOLIVE_EVENT_RECEVE_CHATSETTLE,self,self->option,self->chatManager.chatNo,self->chatManager.commentCountSum);
	}




	strcpy(&self->chatManager.recvBuf[0],recvBufTmp);










	return;

}



static void NicoLiveError(void *buf){

	NicoLiveStream_P nl = (NicoLiveStream_P)buf;

	nl->callBack(NICOLIVE_EVENT_ERR_CONNECT,nl,nl->option,(NICOLIVE_PARAM)NULL,(NICOLIVE_PARAM)NULL);

	NicoLiveStream_disConnect(nl);


	_endthread();
	return;

}

static void NicoLiveSelfDisconnect(void *buf){

	NicoLiveStream_P nl = (NicoLiveStream_P)buf;

	dumpln(TEXT("selfdisconnect"),);

	NicoLiveStream_disConnect(nl);

	_heapmin();


	_endthread();
	return;

}

static INLINE SOCKET GetConectedSocket(LPCTSTR nordName,LPCTSTR port,int socktype,int addr_famiry,int protocol){


	SOCKET rslt = INVALID_SOCKET;


	PADDRINFOW res,res0;
	ADDRINFOW hints;
	ZeroMemory(&hints,sizeof(hints));
	hints.ai_socktype = socktype;
	hints.ai_family = addr_famiry;
	hints.ai_protocol = protocol;
	GetAddrInfoW(nordName,port,&hints,&res0);


	for(res = res0; res != NULL;res = res->ai_next){




		rslt = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
		if(rslt == INVALID_SOCKET){

			continue;
		}









		if(connect(rslt,res->ai_addr,(int)res->ai_addrlen) == SOCKET_ERROR){
			closesocket(rslt);
			continue;
		}






		break;

	}

	if(res == NULL){
		rslt = INVALID_SOCKET;
	}


	//res構造体解放
	FreeAddrInfo(res0);


	return rslt;

}


//
//ストリームステータス関連
//

///
///ストリームステータスのIDを取得
///
NLIB_DECLSPEC LPCTSTR NicoLiveStream_getId(NicoLiveStream_P self){

	return self->playerStatus.stream.id;
}


///
///ストリームステータスのコミュニティ番号を取得
///
NLIB_DECLSPEC LPCTSTR NicoLiveStream_getDefaultCommunity(NicoLiveStream_P self){

	return self->playerStatus.stream.default_community;
}


///
///ストリームステータスのプロバイダタイプを取得
///
PROVIDERTYPE NicoLiveStream_getProviderType(NicoLiveStream_P self){

	return self->playerStatus.stream.provider_type;
}


///
///ストリームステータスのオーナーフラグを取得
///
BOOL NicoLiveStream_isOwner(NicoLiveStream_P self){

	return self->playerStatus.stream.is_owner;
}

///
///ストリームステータスの開始時刻を取得
///
time_t NicoLiveStream_getStartTime(NicoLiveStream_P self){

	return self->playerStatus.stream.start_time;
}

///
///ストリームステータスの終了時刻を取得
///
time_t NicoLiveStream_getEndTime(NicoLiveStream_P self){

	return self->playerStatus.stream.end_time;
}

///
///ストリームステータスの開場時刻を取得
///
time_t NicoLiveStream_getOpenTime(NicoLiveStream_P self){

	return self->playerStatus.stream.open_time;

}

///
///ストリームステータスのベース時刻を取得
///
time_t NicoLiveStream_getBaseTime(NicoLiveStream_P self){

	return self->playerStatus.stream.base_time;
}


///
///ストリームステータスのタイトル取得
///
LPCTSTR NicoLiveStream_getTitle(NicoLiveStream_P self){
#ifdef PLAYERSTATUS_GET_TITLE
	return self->playerStatus.stream.title;
#else
	static const LPCTSTR rslt = TEXT("");
	return rslt;
#endif
}

BOOL NicoLiveStream_isPremium(NicoLiveStream_P self){

	return self->playerStatus.user.is_premium;

}
