#include "RcSyncdClientSocket.h"

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


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

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

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

// rc_syncd 로 sync 명령 전달.
// - CReqMasterData class 멤버변수 수정시 본 함수 수정 필요함.		
bool CRcSyncdClientSocket::SendSyncRequest( CReqMasterData& request )
{
	if( IsValidSocket() == false )
		return false;
	
	// 정보 요청을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_REQUEST;
	stHeader.command[0] = CLIENT_COMMAND_SYNC;

	// Data 부분의 길이를 계산한다.
	unsigned int nTemp = 0;
	nTemp = 4;                                     // sync_type
	nTemp += ( 4 + request.start_time.size() );    // start_time
	nTemp += ( 4 + request.end_time.size() );      // end_time
	nTemp += ( 4 + request.one_service.size() );   // one_service

	stHeader.data_length = htonl( nTemp );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "request[CLIENT_COMMAND_SYNC] send fail to rc_syncd. " );
		return false;
	}
	else
	{
		// Data 부분 전송
		// 1. sync_type
		nTemp = htonl( request.sync_type );
		WriteN( &nTemp, 4 );
		// 2. start_time
		SendString( request.start_time );
		// 3. end_time
		SendString( request.end_time );
		// 4. one_service
		SendString( request.one_service );
	}

	LOG( LDBG, "request[CLIENT_COMMAND_SYNC] send OK. type[%d] time[%s]~[%s] svc[%s]"
		 , request.sync_type, request.start_time.c_str(), request.end_time.c_str()
		 , (request.one_service.empty() == true ? "ALL" : request.one_service.c_str()) );
	
	return true;
}


// rc_syncd 로 부터 sync 요청에 대한 결과 수신.
int CRcSyncdClientSocket::GetSyncResult( int nTimeout, bool& bSuccess, std::string& strMessage )
{
	if( IsValidSocket() == false )
		return -1;

	int nRead;
	bool isAlivePacket;

	do
	{
		isAlivePacket = false;

		// Socket 으로 부터 Packet Header 부분 수신.
		// 수신된 정보는 멤버변수에 저장처리.
		nRead = ReadNTimeout( &m_packetHeader, m_nPacketHeaderLen, nTimeout );

		// nRead  0: Socket Closed
		//       -1: error 
		//       -2: Timeout 이므로
		if( nRead == -2 )
			return 0;		// Timeout 반환.
		else if( nRead == 0 || nRead == -1 )
			return -1;		// Socket Close 또는 오류 발생시

		// STX code 검사.
		if( m_packetHeader.stx != HEADER_STX_CODE )
		{
			LOG( LERR, "Not valid stx code." );

			return -1;
		}
		
		// Data Length 부분 값을 멤버 변수에 저장처리.
		m_nPacketDataLen = ntohl( m_packetHeader.data_length );

		// Alive Check Packet 인 경우 해당 패킷은 무시처리 후 다시 수신 처리한다.
		if( m_packetHeader.type == HEADER_TYPE_REQUEST
			&& m_packetHeader.command[0] == COMMON_ALIVE_CHECK
			&& m_nPacketDataLen == 0 )
		{
			isAlivePacket = true;
		}

	} while( isAlivePacket == true );

	// Alive Packet 이 아닌 경우 해당 Packet 에 대한 응답패킷인지 검사.
	if( m_packetHeader.type != HEADER_TYPE_RESPONSE
		|| m_packetHeader.command[0] != CLIENT_COMMAND_SYNC )
	{
		// 다른 패킷이 들어온 경우

		// ** 정석대로라면 그냥 냅둬어 수신부에서 처리해야 하지만
		// 현재 수신 처리부가 없어 당장 문제가 생길수 있으므로 
		// 데이터 부분까지 수신하여 임시버퍼에 저장처리 해 놓는다. 
		GetPacketData( m_nPacketDataLen );
		return 2;
	}
	
	// Sync 요청에 대한 응답패킷인 경우.

	// 1. 성공, 실패 여부 판단.
	if( m_packetHeader.result[0] != HEADER_RESULT_SUCCESS )
		bSuccess = false;
	else
		bSuccess = true;

	// 2. Data 부분에 대한 처리
	// - Message 부분이 존재할 수 있으므로..
	// 성공/실패 구분 없이 모두 Data 부분 수신 처리한다.
	if( m_nPacketDataLen > 0 )
	{
		if( GetPacketData( m_nPacketDataLen, strMessage ) == false )
		{
			LOG( LERR, "data body receive failed." );
			return -1;
		}	
	}
	
	return 1;
}


// Alive Check 를 위한 패킷 전송 => 해당 패킷에 대한 응답은 필요없으므로 수신시 자동 무시처리됨.
// @return 전송 성공시 true, 실패시 false 반환.
bool CRcSyncdClientSocket::SendAliveCheck()
{
	if( IsValidSocket() == false )
		return false;

	// 정보 요청을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_REQUEST;
	stHeader.command[0] = COMMON_ALIVE_CHECK;

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
		return false;

	return true;
}

// Packet Header 정보를 Log 파일에 Logging 처리
void CRcSyncdClientSocket::PrintHeaderToLog()
{
	_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, "extend_code [%02x][%02x]", m_packetHeader.extend_code[0], m_packetHeader.extend_code[1] );
	_LOG( LINF, "------------------------------------" );
}


//////////////////////////////////////////////////////////////////////////////////


// Packet 정보 중 String 정보를 전송하기 위한 함수.
bool CRcSyncdClientSocket::SendString( const std::string& strData )
{
	if( IsValidSocket() == false )
		return false;

	// 크기값을 저장하기 위한 임시 변수.
	unsigned int nSize = 0;

	// String Data 전송
	nSize = htonl( strData.size() );
	WriteN( &nSize, 4 );
	if( strData.size() > 0 )
		WriteN( strData.c_str(), strData.size() );

	return true;
}

// socket 에서 지정된 크기만큼의 데이터를 읽어 출력변수에 저장처리.
// @param size [in] read 할 데이터 크기
// @param value [out] 읽은 데이터를 저장할 string 변수
// @return On success return true, otherwise return false.
bool CRcSyncdClientSocket::GetPacketData( unsigned int& size, std::string& value )
{
	// 1. 수신할 크기가 임시버퍼보다 큰 경우 새로운 버퍼를 생성한다.
	BYTE * pBuffer = NULL;
	bool bNewBufferCreated = false;
	if( size > DEFAULT_SOCKET_TEMP_BUFFER_SIZE )
	{
		pBuffer = new BYTE[size];	// 신규 메모리 할당.
		bNewBufferCreated = true;
	}
	else
		pBuffer = m_tempBuffer;

	// 2. 데이터 수신 처리.
	int nRead = ReadNTimeout( pBuffer, size );

	// 오류 발생시
	if( nRead <= 0 )
	{
		// 신규로 생성된 버퍼인 경우 메모리 해제 처리.
		if( bNewBufferCreated == true )
			delete[] pBuffer;

		return false;
	}

	// 3. 수신 데이터 저장 처리 - 기존 데이터 뒤에 붙여 준다=> 이게 사용하기 편함.
	value.append( (char *)pBuffer, size );

	// 신규로 생성된 버퍼인 경우 메모리 해제 처리.
	if( bNewBufferCreated == true )
		delete[] pBuffer;

	return true;
}

// socket 에서 지정된 크기만큼의 데이터를 읽어 내부 임시버퍼인 m_tempBuffer 에 저장처리.
// @param size [in] read 할 데이터 크기
// @return On success return true, otherwise return false.
bool CRcSyncdClientSocket::GetPacketData( unsigned int size )
{
	// 1. 수신할 크기가 임시버퍼보다 큰 경우 오류 반환.
	if( size > DEFAULT_SOCKET_TEMP_BUFFER_SIZE )
		return false;

	// 2. 데이터 수신 처리.
	int nRead = ReadNTimeout( m_tempBuffer, size );

	// 오류 발생시
	if( nRead <= 0 )
		return false;

	// 3. 정상인 경우는 ReadNTimeout 함수가 무조건 요청 크기만큼 read 처리하므로 정상수신임.
	return true;
}


// 전달받은 데이터를 unsigned int 형으로 변환처리 및 Endian 변환
unsigned int CRcSyncdClientSocket::GetDataToUInt( BYTE * pValue, bool bConvertEndian )
{
	BYTE tempBuffer[4];
	unsigned int result;

	memcpy(tempBuffer, pValue, 4);
	unsigned int * pInt = (unsigned int *)tempBuffer;

	// Endian 변환처리.
	if( bConvertEndian == true )
	{
		result = ntohl( *pInt );
	}
	else
		result = *pInt;

	return result;
}

// pValue 에 저장된 데이터를 unsigned long long (64Byte) 형으로 변환처리.
unsigned long long CRcSyncdClientSocket::GetDataToUInt64( BYTE * pValue )
{
	unsigned long long * pResult;
	BYTE tempBuffer[8];
	memcpy(tempBuffer, pValue, 8);

	pResult = (unsigned long long *)tempBuffer;

	return  *pResult;
}
