#include "SocketControl.h"
#include "FimngdData.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <iterator>
#include <iostream>
#include <stdio.h>

using namespace std;

/// @brief 생성자.
CSocketControl::CSocketControl( )
	: CBaseSocket( SOCKET_NOT_VALID )
	  , m_nPacketHeaderLen ( sizeof(m_packetHeader))
	  , m_nPacketDataLen( 0 )
{
}

/// @brief 소멸자
CSocketControl::~CSocketControl()
{
	// 소멸자 Socket 명시적 Close 처리.
	Close();
}

/// @brief 전달받은 Target 으로 Socket 접속을 수행
/// @param szTarget [in] 접속 대상 Host name 또는 IP
/// @param nPort	[in] 접속 Port
/// @return 접속 성공시 true, 실패시 false 반환. ( 오류 내역은 로깅처리됨.)
bool CSocketControl::ConnectTarget( const std::string& szTarget, int nPort )
{
	return Connect( szTarget, nPort );
}


/// @brief Packet Header 정보를 Log 파일에 Logging 처리
void CSocketControl::PrintHeaderToLog(struct FileTransferPacketHeader packetHeader)
{
	m_packetHeader = packetHeader;
	_LOG( LINF, "------------------------------------" );
	_LOG( LINF, "stx				[%02x]", m_packetHeader.stx );
	_LOG( LINF, "type				[%02x]", m_packetHeader.type );
	_LOG( LINF, "command			[%02x][%02x][%02x][%02x]" , m_packetHeader.command[0], m_packetHeader.command[1] , m_packetHeader.command[2], m_packetHeader.command[3] );
	_LOG( LINF, "result				[%02x][%02x][%02x][%02x]" , m_packetHeader.result[0], m_packetHeader.result[1] , m_packetHeader.result[2], m_packetHeader.result[3] );
	_LOG( LINF, "data_length		[%u]", m_nPacketDataLen );
	_LOG( LINF, "protocol version	[%02x]", m_packetHeader.proto_version );
	_LOG( LINF, "extend_code		[%02x]", m_packetHeader.extend_code );

	_LOG( LINF, "------------------------------------" );
}

bool CSocketControl::SendIntenalCache(const std::string& szRCID, 
								const std::string& szFileName, 
								const std::string& szHostName, 
								uint32_t nServiceID,
								uint64_t nFileSize, 
								int nFDCount)
{
	if( IsValidSocket() == false )
		return false;
	
	// 정보 요청을 위한 Packet Header 생성.
	struct FileTransferPacketHeader stHeader;
	memset(&stHeader, 0x00, sizeof(struct FileTransferPacketHeader));

	stHeader.stx			= RC_RMCD_HEADER_STX_CODE;
	stHeader.type			= HEADER_TYPE_REQUEST; 
	stHeader.command[2]		= CONTROL_FILE_CACHE;
	stHeader.data_length	= htonl( sizeof(struct internal_cache_body) );
	stHeader.proto_version	= 0x00;

	struct internal_cache_body stBody;
	memset(&stBody, 0x00, sizeof(struct internal_cache_body));

	snprintf(stBody.szRCID			,sizeof(stBody.szRCID)-1			,"%s", szRCID.c_str());
	snprintf(stBody.szFileNameHash  ,sizeof(stBody.szFileNameHash)-1	,"%s", szFileName.c_str());
	snprintf(stBody.szHostname		,sizeof(stBody.szHostname)-1		,"%s", szHostName.c_str());
	snprintf(stBody.szServiceID		,sizeof(stBody.szServiceID)-1		,"%d", nServiceID);
	snprintf(stBody.szContentLength ,sizeof(stBody.szContentLength)-1	,"%ld", nFileSize );
	snprintf(stBody.szFDCount		,sizeof(stBody.szFDCount)-1			,"%d", nFDCount);


	_LOG( LDBG, "------------------------------------" );
	_LOG( LDBG, "szRCID          : %s", stBody.szRCID );
	_LOG( LDBG, "szFileNameHash  : %s", stBody.szFileNameHash ) ;
	_LOG( LDBG, "szHostname      : %s", stBody.szHostname ) ;
	_LOG( LDBG, "szServiceID     : %s", stBody.szServiceID ) ;
	_LOG( LDBG, "szContentLength : %s", stBody.szContentLength ) ;
	_LOG( LDBG, "szFDCount       : %s", stBody.szFDCount ) ;
	_LOG( LDBG, "------------------------------------" );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "File Replication request send failed.");
		PrintHeaderToLog( stHeader );
		return false;
	}

	// Packet Data 부분 전송
	if( WriteN( &stBody, sizeof(struct internal_cache_body) ) != sizeof(struct internal_cache_body) )
	{
		LOG( LERR, "File Replication request send failed.");

		return false;
	}


	return true;
}


bool CSocketControl::SendAliveFhs(const std::string& szRCID, const std::string& szHostName)
{
	if( IsValidSocket() == false )
		return false;
	
	// 정보 요청을 위한 Packet Header 생성.
	struct FileTransferPacketHeader stHeader;
	memset(&stHeader, 0x00, sizeof(struct FileTransferPacketHeader));

	stHeader.stx			= RC_MOND_HEADER_STX_CODE;
	stHeader.type			= HEADER_TYPE_REQUEST; 
	stHeader.command[1]		= ALIVE_FHS_REQUEST;
	stHeader.data_length	= htonl( sizeof(struct alive_fhs_request_body) );
	stHeader.proto_version	= 0x00;

	struct alive_fhs_request_body stBody;
	memset(&stBody, 0x00, sizeof(struct alive_fhs_request_body));

	snprintf(stBody.szRCID			,sizeof(stBody.szRCID)-1			,"%s", szRCID.c_str());
	snprintf(stBody.szHostname		,sizeof(stBody.szHostname)-1		,"%s", szHostName.c_str());

	LOG( LDEV, "------------------------------------" );
	_LOG( LDEV, "	szRCID          : %s", stBody.szRCID );
	_LOG( LDEV, "	szHostname      : %s", stBody.szHostname ) ;
	LOG( LDEV, "------------------------------------" );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "Alive FHS request send failed.");
		PrintHeaderToLog( stHeader );
		return false;
	}

	// Packet Data 부분 전송
	if( WriteN( &stBody, sizeof(struct alive_fhs_request_body) ) != sizeof(struct alive_fhs_request_body) )
	{
		LOG( LERR, "Alive FHS request send failed.");

		return false;
	}

	return true;
}

bool CSocketControl::GetAliveFhs(std::map<std::string, struct fhs_stat> &mapFhsStat)
{
	unsigned int nFhsCount = 0;
	BYTE tempBuffer[4];
	// Packet Header를 읽는다.
	if( GetPacketHeader( RC_MOND_HEADER_STX_CODE, HEADER_TYPE_RESPONSE, DEFAULT_DATA_RECEIVE_TIMEOUT) <= 0 )
	{
		return false;
	}

	// 전면 수정.... 필요...

	// Data의 크기는 최소 4byte여야 한다.
	if( m_nPacketDataLen >= 4 )
	{
		int nRead = ReadNTimeout( &tempBuffer, 4);
		// 오류 발생시
		if( nRead <= 0 )
		{
			LOG( LERR, "FHS Count Read Fail.");
			return false;
		}

		unsigned int * pInt = (unsigned int *) tempBuffer;
		nFhsCount = ntohl(*pInt);

	} else
	{
		LOG( LERR, "Response packet body size not valid.");
		return false;
	}

	// Data 부분 size를  체크한다.
	if( m_nPacketDataLen != (sizeof( struct fhs_stat_body ) * nFhsCount) + 4 )
	{
		LOG( LERR, "Response packet body size not valid.[%u]/[%lu]"
			, m_nPacketDataLen, (sizeof( struct fhs_stat_body ) * nFhsCount) + 4 );
		return false;
	}

	// fhs count 만큼 loop를 돌며 map 에 추가한다.
	for( unsigned int i = 0; i <  nFhsCount; i++ )
	{
		// 통신 프로토콜로 읽을 구조체...
		struct fhs_stat_body stFhsStatbody; 
		struct fhs_stat stFhsStat;
		int nRead = ReadNTimeout( &stFhsStatbody, sizeof(struct fhs_stat_body) );

		// 오류 발생시
		if( nRead <= 0 )
		{
			LOG( LERR, "Alive FHS Read Fail.");
			mapFhsStat.clear();
			return false;
		}
		string szHostName = stFhsStatbody.szHostname;

		// socket 통신 하는 구조체와 내부 로직에서 사용되는 구조체간에 값을 복사 하기 위함.
		strncpy(stFhsStat.szHostname, stFhsStatbody.szHostname, HOSTNAME_LEN);
		stFhsStat.nActionCode = stFhsStatbody.nActionCode;

		mapFhsStat.insert( std::pair<string, struct fhs_stat>( szHostName, stFhsStat ) );
	}

	return true;
}
// Packet Header 부분의 수신 처리를 위한 함수
// @param timeout [in] 대기시간.
// @retrun 0 : timeout
//         1 : 성공
//        -1 : socket 오류 또는 정의된 Data 가 아닌 경우.
//        -2 : result 가 정상이 아닌경우
int CSocketControl::GetPacketHeader( char stxCode, char typeCode, int timeout )
{
	BYTE tempBuffer[128];

	if( IsValidSocket() == false )
		return -1;

	// Socket 으로 부터 Packet Header 부분 수신.
	// 수신된 정보는 멤버변수에 저장처리.
	/// read 된 데이터의 크기. 0: fd closed, -1: 오류, -2: Timeout
	int nRead = ReadNTimeout( &m_packetHeader, m_nPacketHeaderLen, timeout );
		
	if( nRead == -2 )
	{
		// timeout 발생시
		LOG( LERR, "Socket Read timeout.." );
		return 0;
	}
	else if( nRead <= 0 )
	{
		// timeout 을 제외한 그 밖의 오류 발생시
		// ReadNTimeout() 내에서 로깅 처리 확인
		return -1;
	}
		
	// 정상 Data 수신시.
	// STX code 검사.
	if( m_packetHeader.stx != stxCode )
	{
		LOG( LERR, "Not valid stx code. [%x]", m_packetHeader.stx);
		PrintHeaderToLog( m_packetHeader );
		return -1;
	}

	// Type Code 검사
	if( m_packetHeader.type != typeCode )
	{
		LOG( LERR, "Not valid type code. [%x]", m_packetHeader.type );
		PrintHeaderToLog( m_packetHeader );
		return -1;
	}

	// 경우에 따라 data_length는 0이 될 수도 있다.
	//  ex) 에러메세지가 없는 에러인 경우...
	// Data Length 부분 값을 멤버 변수에 저장처리.
	m_nPacketDataLen = ntohl( m_packetHeader.data_length );

	// result 검사 후 정상적이지 않을 경우 결과 메세지를 출력한다.
	if( m_packetHeader.result[3] != HEADER_RESULT_SUCCESS )
	{
		// rc_mond가 전달 할 준비가 안된경우...
		if( m_packetHeader.result[3] == HEADER_READY_YET )
		{
			LOG( LWAR, "rc_mond ready yet. :[%x]",m_packetHeader.result[3]);
			return -2;
		}

		if( m_nPacketDataLen != 0 )
		{
			nRead = ReadNTimeout( &tempBuffer, m_nPacketDataLen);
			if( nRead == -2 )
			{
				// timeout 발생시
				LOG( LERR, "Socket Read timeout.." );
				return 0;
			}
			// 오류 발생시
			if( nRead <= 0 )
			{
				LOG( LERR, "Socket Read Fail.");
				return -1;
			}

			// 에러메세지가 있는 경우는 로깅 한다.
			string szErrmsg =  (char*)tempBuffer;
			LOG( LWAR, "Result code :[%x] %s",m_packetHeader.result[3],  szErrmsg.c_str());
		} else
		{
			// 없으면 결과 코드만 로깅한다.
			LOG( LWAR, "Result code :[%x]",m_packetHeader.result[3]);
		}

		return -2;
	}

	return 1;
}

bool CSocketControl::SendNetworkStat(const std::string& szRCID, const std::string& szHostName, std::vector<struct service_network_stat> &vecNetworkStat)
{
	// 
	int data_length = 0;
	if( IsValidSocket() == false )
		return false;
	
	// 정보 요청을 위한 Packet Header 생성.
	struct FileTransferPacketHeader stHeader;
	memset(&stHeader, 0x00, sizeof(struct FileTransferPacketHeader));

	stHeader.stx			= RC_SSCD_HEADER_STX_CODE;
	stHeader.type			= HEADER_TYPE_REQUEST; 
	stHeader.command[1]		= FHS_NETWORK_STAT_SEND;

	// !!webting body size 구해야 함
	data_length = sizeof(struct network_stat_body_head) + (sizeof(struct service_network_stat) * vecNetworkStat.size() ) ;
	_LOG( LDBG, "body length          : %d, cnt: %d", data_length, (int)vecNetworkStat.size() );
	stHeader.data_length	= htonl( data_length );
	stHeader.proto_version	= 0x01;

	struct network_stat_body_head stBodyHead;
	memset(&stBodyHead, 0x00, sizeof(struct network_stat_body_head));

	snprintf(stBodyHead.szRCID			,sizeof(stBodyHead.szRCID)-1			,"%s", szRCID.c_str());
	snprintf(stBodyHead.szHostname		,sizeof(stBodyHead.szHostname)-1		,"%s", szHostName.c_str());
	stBodyHead.nCount = htonl(vecNetworkStat.size());

	LOG( LDBG, "------------------------------------" );
	LOG( LDBG, "szRCID          : %s", stBodyHead.szRCID );
	LOG( LDBG, "szHostname      : %s", stBodyHead.szHostname ) ;
	LOG( LDBG, "nCount          : %d", (int)vecNetworkStat.size() ) ;
	LOG( LDBG, "------------------------------------" );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "Network Stat header send failed.");
		PrintHeaderToLog(stHeader);
		return false;
	}

	// Packet Data 부분 전송의 헤더 부분을 전송한다.
	if( WriteN( &stBodyHead, sizeof(struct network_stat_body_head) ) != sizeof(struct network_stat_body_head) )
	{
		LOG( LERR, "Network Stat body_header send failed.");

		return false;
	}

	std::vector<struct service_network_stat> tmpvec;
	// body의 나머지 부분을 전송한다.
	std::vector<struct service_network_stat>::iterator it;
	for (it=vecNetworkStat.begin(); it<vecNetworkStat.end(); it++)
	{
		struct service_network_stat objNetStat = *it;
		
		if( WriteN( &objNetStat, sizeof( struct service_network_stat) ) != sizeof( struct service_network_stat) )
		{
			LOG(LERR, "Network Stat body send failed.[t:%d, T:%d]", objNetStat.timestamp, objNetStat.nServiceSeq);
			return false;
		}
	}

	return true;
}

bool CSocketControl::GetNetworkStatResult(int nTimeout)
{
	// Packet Header를 읽는다.
	if (GetPacketHeader(RC_SSCD_HEADER_STX_CODE, HEADER_TYPE_RESPONSE, nTimeout) <= 0)
	{
		LOG(LERR, "Network Stat response code reception failed.");
		return false;
	}

	return true;
}