/* 
 * Copyright (C) 2012 Yee Young Han <websearch@naver.com> (http://blog.naver.com/websearch)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include "HttpCallBack.h"
#include "HttpStatusCode.h"
#include "FileUtility.h"
#include "Directory.h"
#include "Log.h"
#include "UserMap.h"
#include "RtpThread.h"
#include "MemoryDebug.h"

CHttpStack gclsHttpStack;
CHttpCallBack	gclsHttpCallBack;

CHttpCallBack::CHttpCallBack() : m_bStop(false)
{
}

CHttpCallBack::~CHttpCallBack()
{
}

/**
 * @ingroup TestWebRtc
 * @brief HTTP ¿äÃ» ¼ö½Å ÀÌº¥Æ® callback
 * @param pclsRequest		HTTP ¿äÃ» ¸Þ½ÃÁö
 * @param pclsResponse	HTTP ÀÀ´ä ¸Þ½ÃÁö - ÀÀ¿ë¿¡¼­ ÀúÀåÇÑ´Ù.
 * @returns ÀÀ¿ë¿¡¼­ HTTP ÀÀ´ä ¸Þ½ÃÁö¸¦ Á¤»óÀûÀ¸·Î »ý¼ºÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpCallBack::RecvHttpRequest( CHttpMessage * pclsRequest, CHttpMessage * pclsResponse )
{
	std::string strPath = m_strDocumentRoot;
	std::string strExt;

	//CLog::Print( LOG_DEBUG, "req uri[%s]", pclsRequest->m_strReqUri.c_str() );

	// º¸¾È»ó .. À» Æ÷ÇÔÇÑ URL À» ¹«½ÃÇÑ´Ù.
	if( strstr( pclsRequest->m_strReqUri.c_str(), ".." ) )
	{
		pclsResponse->m_iStatusCode = HTTP_NOT_FOUND;
		return true;
	}

#ifdef _DEBUG
	// ¸Þ¸ð¸® ´©¼ö °Ë»ç¸¦ À§ÇØ¼­ exit.html À» ¼ö½ÅÇÏ¸é ÇÁ·Î±×·¥À» Á¾·áÇÑ´Ù.
	if( !strcmp( pclsRequest->m_strReqUri.c_str(), "/exit.html" ) )
	{
		pclsResponse->m_iStatusCode = HTTP_NOT_FOUND;
		m_bStop = true;
		return true;
	}
#endif

	if( !strcmp( pclsRequest->m_strReqUri.c_str(), "/" ) )
	{
		CDirectory::AppendName( strPath, "index.html" );
	}
	else
	{
#ifdef WIN32
		ReplaceString( pclsRequest->m_strReqUri, "/", "\\" );
#endif

		strPath.append( pclsRequest->m_strReqUri );
	}

	if( IsExistFile( strPath.c_str() ) == false )
	{
		pclsResponse->m_iStatusCode = HTTP_NOT_FOUND;
		return true;
	}

	// ÆÄÀÏº° Content-Type À» ¼³Á¤ÇÑ´Ù.
	GetFileExt( strPath.c_str(), strExt );
	const char * pszExt = strExt.c_str();
	
	if( !strcmp( pszExt, "html" ) || !strcmp( pszExt, "htm" ) )
	{
		pclsResponse->m_strContentType = "text/html";
	}
	else if( !strcmp( pszExt, "css" ) )
	{
		pclsResponse->m_strContentType = "text/css";
	}
	else if( !strcmp( pszExt, "js" ) )
	{
		pclsResponse->m_strContentType = "text/javascript";
	}
	else if( !strcmp( pszExt, "png" ) || !strcmp( pszExt, "gif" ) )
	{
		pclsResponse->m_strContentType = "image/";
		pclsResponse->m_strContentType.append( pszExt );
	}
	else if( !strcmp( pszExt, "jpg" ) || !strcmp( pszExt, "jpeg" ) )
	{
		pclsResponse->m_strContentType = "image/jpeg";
	}
	else
	{
		pclsResponse->m_iStatusCode = HTTP_NOT_FOUND;
		return true;
	}

	// ÆÄÀÏÀ» ÀÐ¾î¼­ HTTP body ¿¡ ÀúÀåÇÑ´Ù.
	FILE * fd = fopen( strPath.c_str(), "rb" );
	if( fd == NULL )
	{
		pclsResponse->m_iStatusCode = HTTP_NOT_FOUND;
		return true;
	}

	int n;
	char szBuf[8192];

	while( ( n = fread( szBuf, 1, sizeof(szBuf), fd ) ) > 0 )
	{
		pclsResponse->m_strBody.append( szBuf, n );
	}

	fclose( fd );

	pclsResponse->m_iStatusCode = HTTP_OK;

	return true;
}

/**
 * @ingroup TestWebRtc
 * @brief WebSocket Å¬¶óÀÌ¾ðÆ® TCP ¿¬°á ½ÃÀÛ ÀÌº¥Æ® callback
 * @param pszClientIp WebSocket Å¬¶óÀÌ¾ðÆ® IP ÁÖ¼Ò
 * @param iClientPort WebSocket Å¬¶óÀÌ¾ðÆ® Æ÷Æ® ¹øÈ£
 */
void CHttpCallBack::WebSocketConnected( const char * pszClientIp, int iClientPort )
{
	printf( "WebSocket[%s:%d] connected\n", pszClientIp, iClientPort );
}

/**
 * @ingroup TestWebRtc
 * @brief WebSocket Å¬¶óÀÌ¾ðÆ® TCP ¿¬°á Á¾·á ÀÌº¥Æ® callback
 * @param pszClientIp WebSocket Å¬¶óÀÌ¾ðÆ® IP ÁÖ¼Ò
 * @param iClientPort WebSocket Å¬¶óÀÌ¾ðÆ® Æ÷Æ® ¹øÈ£
 */
void CHttpCallBack::WebSocketClosed( const char * pszClientIp, int iClientPort )
{
	printf( "WebSocket[%s:%d] closed\n", pszClientIp, iClientPort );

	std::string strUserId;
	CUserInfo clsUserInfo;

	if( gclsUserMap.SelectUserId( pszClientIp, iClientPort, strUserId ) )
	{
		if( gclsUserMap.Select( strUserId.c_str(), clsUserInfo ) )
		{
			gclsUserMap.Delete( strUserId.c_str() );
		}
	}
}

/**
 * @ingroup TestWebRtc
 * @brief WebSocket Å¬¶óÀÌ¾ðÆ® µ¥ÀÌÅÍ ¼ö½Å ÀÌº¥Æ® callback
 * @param pszClientIp WebSocket Å¬¶óÀÌ¾ðÆ® IP ÁÖ¼Ò
 * @param iClientPort WebSocket Å¬¶óÀÌ¾ðÆ® Æ÷Æ® ¹øÈ£
 * @param strData			WebSocket Å¬¶óÀÌ¾ðÆ®°¡ Àü¼ÛÇÑ µ¥ÀÌÅÍ
 * @param pclsSession	HTTP ¼¼¼Ç Á¤º¸
 * @returns WebSocket Å¬¶óÀÌ¾ðÆ® ¿¬°áÀ» À¯ÁöÇÏ·Á¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpCallBack::WebSocketData( const char * pszClientIp, int iClientPort, std::string & strData, CHttpStackSession * pclsSession )
{
	CLog::Print( LOG_NETWORK, "WebSocket[%s:%d] recv[%s]", pszClientIp, iClientPort, strData.c_str() );
	printf( "WebSocket[%s:%d] recv[%s]\n", pszClientIp, iClientPort, strData.c_str() );

	STRING_VECTOR clsList;

	SplitString( strData.c_str(), clsList, '|' );

	int iCount = clsList.size();

	if( iCount < 2 )
	{
		return false;
	}

	bool bReq = true;
	if( strcmp( clsList[0].c_str(), "req" ) ) bReq = false;

	const char * pszCommand = clsList[1].c_str();
	std::string strUserId;

	if( !strcmp( pszCommand, "register" ) )
	{
		if( iCount < 5 )
		{
			printf( "register request arg is not correct\n" );
			return false;
		}

		if( gclsUserMap.Insert( clsList[2].c_str(), clsList[3].c_str(), clsList[4].c_str(), pszClientIp, iClientPort, pclsSession->m_strUserAgent.c_str() ) == false )
		{
			Send( pszClientIp, iClientPort, "res|register|500" );
		}
		else
		{
			Send( pszClientIp, iClientPort, "res|register|200" );
		}
	}
	else if( !strcmp( pszCommand, "invite" ) )
	{
		if( bReq )
		{
			if( iCount < 4 )
			{
				printf( "invite request arg is not correct\n" );
				return false;
			}

			const char * pszToId = clsList[2].c_str();
			const char * pszSdp = clsList[3].c_str();
			CUserInfo clsUserInfo;

			CRtpThreadArg * pclsRtpArg = new CRtpThreadArg();
			if( pclsRtpArg == NULL )
			{
				Send( pszClientIp, iClientPort, "res|invite|500" );
				return true;
			}

			pclsRtpArg->m_strUserId = strUserId;
			pclsRtpArg->m_strToId = pszToId;
			pclsRtpArg->m_strSdp = pszSdp;
			pclsRtpArg->m_bStartCall = true;

			if( pclsRtpArg->CreateSocket() == false || 
					gclsUserMap.Update( strUserId.c_str(), pclsRtpArg, false ) == false ||
					StartRtpThread( pclsRtpArg ) == false )
			{
				delete pclsRtpArg;
				Send( pszClientIp, iClientPort, "res|invite|500" );
				return true;
			}
		}
		else
		{
			if( iCount < 3 )
			{
				printf( "invite response arg is not correct\n" );
				return false;
			}

			CUserInfo clsUserInfo;

			if( gclsUserMap.SelectUserId( pszClientIp, iClientPort, strUserId ) == false || gclsUserMap.Select( strUserId.c_str(), clsUserInfo ) == false )
			{
				return true;
			}

			int iStatus = atoi( clsList[2].c_str() );

			if( iStatus == 200 && iCount >= 4 )
			{
				CRtpThreadArg * pclsRtpArg = new CRtpThreadArg();
				if( pclsRtpArg == NULL )
				{
					Send( pszClientIp, iClientPort, "req|bye" );
					return true;
				}

				const char * pszSdp = clsList[3].c_str();

				pclsRtpArg->m_strUserId = strUserId;
				pclsRtpArg->m_strSdp = pszSdp;
				pclsRtpArg->m_bStartCall = false;

				if( pclsRtpArg->CreateSocket() == false || 
						gclsUserMap.Update( strUserId.c_str(), pclsRtpArg, false ) == false ||
						StartRtpThread( pclsRtpArg ) == false )
				{
					delete pclsRtpArg;
					Send( pszClientIp, iClientPort, "req|bye" );
					return true;
				}
			}
		}
	}
	else if( !strcmp( pszCommand, "bye" ) )
	{
		std::string strUserId;
		CUserInfo clsUserInfo;

		if( gclsUserMap.SelectUserId( pszClientIp, iClientPort, strUserId ) == false || gclsUserMap.Select( strUserId.c_str(), clsUserInfo ) == false )
		{
			Send( pszClientIp, iClientPort, "res|invite|403" );
			return true;
		}

		if( clsUserInfo.m_pclsRtpArg )
		{
			clsUserInfo.m_pclsRtpArg->m_bStop = true;
			gclsUserMap.Update( strUserId.c_str(), clsUserInfo.m_pclsRtpArg, true );
		}
	}

	return true;
}

/**
 * @ingroup TestWebRtc
 * @brief WebSocket Å¬¶óÀÌ¾ðÆ®·Î ÆÐÅ¶À» Àü¼ÛÇÑ´Ù.
 * @param pszClientIp WebSocket Å¬¶óÀÌ¾ðÆ® IP ÁÖ¼Ò
 * @param iClientPort WebSocket Å¬¶óÀÌ¾ðÆ® Æ÷Æ® ¹øÈ£
 * @param fmt					Àü¼Û ¹®ÀÚ¿­
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CHttpCallBack::Send( const char * pszClientIp, int iClientPort, const char * fmt, ... )
{
	va_list	ap;
	char		szBuf[8192];
	int			iBufLen;

	va_start( ap, fmt );
	iBufLen = vsnprintf( szBuf, sizeof(szBuf)-1, fmt, ap );
	va_end( ap );

	if( gclsHttpStack.SendWebSocketPacket( pszClientIp, iClientPort, szBuf, iBufLen ) )
	{
		printf( "WebSocket[%s:%d] send[%s]\n", pszClientIp, iClientPort, szBuf );
		CLog::Print( LOG_NETWORK, "WebSocket[%s:%d] send[%s]", pszClientIp, iClientPort, szBuf );
		return true;
	}

	return false;
}
