#include "TargetFhsInfo.h"

CTargetFhsInfo::CTargetFhsInfo( std::string szHostName )
	: m_szHostName( szHostName )
	  , m_szIp( szHostName )
	  , m_bTransferSucess( true )
{

}

CTargetFhsInfo::~CTargetFhsInfo()
{
	m_socketControl.Close();
}

/// @brief 대상 FHS로 접속을 시도한다.
bool CTargetFhsInfo::MakeConnection( bool bUseInternalIp, CIPCache * pIpCache )
{
	// 내부망을 사용하지 않는 경우
	if( bUseInternalIp == false )
	{
		// m_szIp 저장된 정보를 이용하여 접속 시도처리.
		if( m_socketControl.ConnectTarget( m_szHostName, SVC_LISTEN_PORT ) == false )
		{
			m_bTransferSucess = false;
			//m_szErrorMessage = "Can't connect to target FHS [" + m_szHostName + "]";			
			m_szErrorMessage = "connection fail by hostname";			
			return false;
		}
		else
		{
			m_bTransferSucess = true;
			return true;
		}
	}

	// 내부망을 사용하는 경우
	// 1. Cache 검색 처리
	// 2. Cache 에 존재하지 않는 경우
	//    - 기존 외부망 정보를 이용하여 접속 시도
	//    - 내부망 IP 정보 요청 및 Cache 처리
	//    - 내부망 IP 로 접속 시도
	//    - 내부망 IP 접속 실패시 다시 외부망으로 연결 처리
	// 3. Cache 에 존재하면 해당 정보를 이용하여 접속 시도
	//    - 내부망 접속 실패시 외부망으로 연결 처리.
	
	std::string szTempIp;

	// Cache 에 대상 FHS 정보가 존재하는 경우
	if( pIpCache->GetPrivateIP( m_szHostName, szTempIp ) == true )
	{		
		// 접속 시도해서 성공하면 OK
		if( m_socketControl.ConnectTarget( szTempIp, SVC_LISTEN_PORT ) == true )
		{
			// 멤버변수의 m_szIp 정보를 변경 처리
			m_szIp = szTempIp;
			m_bTransferSucess = true;
			return true;
		}
	}

	// 그외의 경우 
	// - Cache 에 저장된 정보로 접속 시도 실패시
	// - Cache 에 저장된 정보가 없는 경우.

	// 일단 외부망으로 접속 시도
	if( m_socketControl.ConnectTarget( m_szHostName, SVC_LISTEN_PORT ) == false )
	{
		// 외부망 접속 실패시 => 더이상 방법이 없으므로 실패 처리
		m_socketControl.Close();

		m_bTransferSucess = false;
		//m_szErrorMessage = "Can't connect to target FHS[" + m_szHostName + "]";		
		m_szErrorMessage = "connection fail by hostname";		
		return false;
	}

	// 외부망 접속이 성공한 경우.
	// - 내부망 IP 정보 요청 처리.	
	if(	m_socketControl.GetLocalIpInfo( szTempIp ) == false )
	{
		// 내부망 IP 요청 처리 실패시.
		// 현재의 접속된 외부망이 유효한지 확인하여
		// 유효한 경우 외부망 접속을 그냥 사용
		// 유효하지 않은 경우 에러를 반환.
		
		if( m_socketControl.GetConnectionStatus() == false )
		{
			m_bTransferSucess = false;
			//m_szErrorMessage = "Can't connect to target FHS[" + m_szHostName + "]";			
			m_szErrorMessage = "connection fail by hostname";			
			return false;
		}
		else
		{
			m_bTransferSucess = true;
			return true;
		}
	}

	// 외부망을 통해 내부망 정보 수신을 성공한 경우.
	// 내부망 정보로 접속 시도.
	m_socketControl.Close();
	if( m_socketControl.ConnectTarget( szTempIp, SVC_LISTEN_PORT ) == true )
	{
		// 내부망 접속이 성공한 경우.
		// 해당 정보를 Cache 및 멤버 변수에 설정.
		m_szIp = szTempIp;
		pIpCache->SetPrivateIP( m_szHostName, szTempIp);
		m_bTransferSucess = true;
		return true;
	}

	// 전달받은 내부망 정보로 접속이 실패한 경우
	// 다시 외부망으로 접속 시도.
	m_socketControl.Close();
	if( m_socketControl.ConnectTarget( m_szHostName, SVC_LISTEN_PORT ) == false )
	{
		// 외부망 접속 실패시 => 더이상 방법이 없으므로 실패 처리
		m_socketControl.Close();

		m_bTransferSucess = false;
		m_szErrorMessage = "connection fail by hostname";
		return false;
	}
	else
	{
		// 마지막 외부망으로 접속이 성공한 경우.
		m_szIp = m_szHostName;
		m_bTransferSucess = true;
		return true;
	}
}

void CTargetFhsInfo::PrintToConnectionInfo()
{
	_LOG( LINF, "FHS Target host[%s] IP[%s] Connect[%s]"
			, m_szHostName.c_str(), m_szIp.c_str()
			, ( m_socketControl.GetConnectionStatus() == true ? "YES" : "NO"));
}

/// @brief 대상 FHS 로 접속이 유효한지 여부를 판단하기 위한 함수.
bool CTargetFhsInfo::IsAvailable()
{
	if( m_socketControl.GetConnectionStatus() == true && m_bTransferSucess == true )
		return true;
	else
		return false;
}

/// @brief 대상 FHS 로 파일 전송 시작을 위한 패킷 전송.
bool CTargetFhsInfo::ReadyToFileTransfer( std::string& szFileFullName, unsigned long long nFileSize, std::string& szTargetTranId, char command )
{
	// 전송할 준비가 안된 경우.
	if( m_bTransferSucess == false )
		return false;
	
	// 전달받은 파일 정보에서 현재의 Mount 정보를 제거한다.	
	// 방식은 뒤에서 / 문자 두개를 찾아서 처리 
	size_t pos = szFileFullName.rfind( '/' );
	pos = szFileFullName.rfind('/', pos-1);
	std::string szFileName = szFileFullName.substr( pos+1 );

	// 2010-05-07 ADD huibong
	// 인자로 전달받은 szTargetTranId 값의 존재 여부를 확인하여 
	// 해당 값이 존재하는 경우 기존 Tran ID 정보를 전달받은 Tran ID 정보를 변환처리하여 전달한다. ( RC 간 전송처리시 사용)
	if( szTargetTranId.size() > 0 )
	{
		pos = szFileName.rfind( '/' );
		std::string szTempFileName = szFileName.substr ( pos+1 );

		szFileName = szTargetTranId + "/" + szTempFileName;
	}


	// Socket Control로 파일 전송에 대한 요청 패킷 전송.
	if( m_socketControl.SendFileReceiveRequest( szFileName, nFileSize, command) == false )
	{
		// 요청 패킷 전달 실패시.
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't send packet.[file transfer request][" + m_szHostName + "]";
		return false;
	}

	// 요청에 대한 응답 대기.
	if( m_socketControl.GetFileReceiveResponse( command, m_bTransferSucess, m_szErrorMessage ) == false )
	{
		// 응답패킷 수신 실패시
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't receive packet.[file transfer response][" + m_szHostName + "]";
		return false;
	}

	// 현재 파일 전송이 가능한지 응답 결과를 보고 판단한다.
	if( m_bTransferSucess == false )
		return false;

	// 파일 전송 시작 패킷 전송.
	if( m_socketControl.SendFileStartRequest() == false )
	{
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't send packet.[file transfer start packet][" + m_szHostName + "]";
		return false;
	}

	return true;
}

/// @brief 파일 Data 의 전송을 처리하기 위한 함수
bool CTargetFhsInfo::SendBufferToFile(const void * buffer, size_t & size )
{
	if( m_bTransferSucess == false )
		return false;

	// 전달받은 패킷을 해당 socket 으로 전송 처리.
	int nWrite = m_socketControl.WriteN( buffer, size );
	if( nWrite != (int) size )
	{
		// 오류 발생시 : WriteN 함수 반환값 0: Socket Closed, -1: 오류 발생시
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't send packet.[file data send fail.][" + m_szHostName + "]";

		LOG( LERR, "File data write fail.[%d/%zu][%s]" , nWrite, size, m_szHostName.c_str());

		return false;
	}

	return true;
}

/// @brief 파일 전송 완료후 전송 결과에 대한 응답패킷을 수신한다.
bool CTargetFhsInfo::GetFileTransferResult()
{
	if( m_bTransferSucess == false )
	{
		LOG( LERR, "[%s] TRANSFER_FILE_END process fail by flag" , m_szHostName.c_str());
		return false;
	}

	// 파일 전송 완료 Packet 전송.
	if( m_socketControl.SendFileEndRequest() == false )
	{
		LOG( LERR, "Cannot send TRANSFER_FILE_END request [%s]", m_szHostName.c_str());

		m_bTransferSucess = false;
		m_szErrorMessage = "Can't send TRANSFER_FILE_END Request [" + m_szHostName + "]";
		return false;
	}

	// 전송 완료에 대한 응답패킷 수신.
	if( m_socketControl.GetFileEndResult( m_bTransferSucess, m_szTransferResultFileName, m_nTransferResultFileSize, m_szErrorMessage) == false )
	{
		// 응답패킷 수신 실패시
		LOG( LERR, "Cannot receive TRANSFER_FILE_END response [%s]", m_szHostName.c_str());

		m_bTransferSucess = false;
		m_szErrorMessage = "Can't receive TRANSFER_FILE_END response [" + m_szHostName + "]";
		return false;
	}

	if( m_bTransferSucess == false )
	{
		LOG( LDBG, "[%s] TRANSFER_FILE_END process COMPLETE TO FAIL[%s]", m_szHostName.c_str(), m_szErrorMessage.c_str());
	}
	else
	{
		LOG( LDBG, "[%s] TRANSFER_FILE_END process COMPLETE TO SUCCESS[%s]", m_szHostName.c_str(), m_szTransferResultFileName.c_str());
	}

	return true;
}


/// @brief 파일 Hash 검사를 요청하고 이에 대한 결과를 반환받는다.
bool CTargetFhsInfo::SendFileHashCheckRequest( int nHashLevel, unsigned long long nFileSize, std::string& szSourceHash)
{
	if( m_bTransferSucess == false )
	{
		LOG( LERR, "[%s] TRANSFER_FILE_HASH process fail by flag", m_szHostName.c_str());
		return false;
	}

	// 만약 전달받은 File Name 이 존재하지 않는 경우 
	if( m_szTransferResultFileName.size() == 0 )
	{
		LOG( LERR, "[%s] TRANSFER_FILE_HASH fail [target file name not valid]", m_szHostName.c_str());
		return false;
	}

	// Hash 검사 요청 메시지 전송.
	if( m_socketControl.SendHashCheckRequest( m_szTransferResultFileName, nFileSize, nHashLevel, szSourceHash ) == false )
	{
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't send packet.[file hash check start packet][" + m_szHostName + "]";

		LOG( LERR, "[%s] TRANSFER_FILE_HASH request send fail [%s]", m_szHostName.c_str(), m_szTransferResultFileName.c_str());
		return false;
	}
	else
	{
		LOG( LDBG, "[%s] TRANSFER_FILE_HASH request send success [%s][%d][%s]", m_szHostName.c_str(), m_szTransferResultFileName.c_str(), nHashLevel, szSourceHash.c_str() );
	}

	return true;
}


bool CTargetFhsInfo::GetFileHashCheckResult()
{
	if( m_bTransferSucess == false )
	{
		LOG( LERR, "[%s] TRANSFER_FILE_HASH process fail by flag", m_szHostName.c_str());
		return false;
	}

	// Hash 검사 완료에 대한 응답 패킷 수신.
	if( m_socketControl.GetHashCheckResult( m_bTransferSucess, m_szErrorMessage) == false )
	{
		// 응답패킷 수신 실패시
		m_bTransferSucess = false;
		m_szErrorMessage = "Can't receive TRANSFER_FILE_HASH response [" + m_szHostName + "]";
		
		LOG( LERR, "Cannot receive TRANSFER_FILE_HASH response [%s][%s]", m_szHostName.c_str(), m_szTransferResultFileName.c_str());
		return false;
	}

	return true;
}


bool CTargetFhsInfo::GetResult( std::string& szHostName, std::string& szFileFullName, std::string& szErrorMessage)
{
	szHostName = m_szHostName;
	szFileFullName = m_szTransferResultFileName;
	szErrorMessage = m_szErrorMessage;

	return m_bTransferSucess;
}

