#include "CacheThread.h"

#include <errno.h>
#include <time.h>
#include <string.h>

#include "Logger.h"
#include "FtsdSocketControl.h"
#include "ServiceConfig.h"


// ftsd 로 부터 처리 결과 수신을 위한 Total 대기 시간 정의 ( sec )
#define FHS_RESPONSE_TOTAL_TIMEOUT  3600   // ( sec, 1 hour )

// ftsd 로 부터 응답 수신 확인을 위한 socket read timeout (sec)
#define FHS_RESPONSE_CHECK_TIMEOUT  3      // 3 sec


CCacheThread::CCacheThread( CProcessStatus* pProcessStatus
	, CCacheJobList* pJobList
	, CDataQueue<CCacheData>* pQueueCacheComplete
	, CCacheData& dataCache )
	: m_pProcessStatus( pProcessStatus )
	, m_pCacheJob( pJobList )
	, m_pQueueCacheComplete( pQueueCacheComplete )
	, m_dataCache( dataCache )
{
	// 멤버 변수 초기화
	m_threadHandle = 0;
}


CCacheThread::~CCacheThread()
{
	// Thread 동작 정지 처리
	// - 만약 Thread 가 이미 종료된 경우 m_threadHandle 이 다른 Thread Handle 일 수 있으므로 
	//   업무 Flow 수정시 주의할 것
	if( m_threadHandle != 0 )
		pthread_cancel( m_threadHandle );    
}

// thread를 생성하여 Cache 생성 작업을 수행.
bool CCacheThread::Start()
{
	int nRet = ::pthread_create( &m_threadHandle, NULL, CCacheThread::threadFunc, this );
	if( nRet != 0 )
	{
		// Thread 생성 실패시
		int errorNum = errno;
		LOG( LERR, "Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

// 스레드 함수
void* CCacheThread::threadFunc( void* arg )
{
	CCacheThread* pObject = reinterpret_cast<CCacheThread *>( arg );
	pthread_detach( pthread_self() );

	pthread_testcancel();
	// Thread 동작시 Thread Count 1 증가 처리
	pObject->m_pProcessStatus->AddReplicationCount();

	// 실제 작업 수행.
	pObject->Execute();

	// Thread 동작 완료시 Thread Count 1 감소 처리
	pObject->m_pProcessStatus->SubtractReplicationCount();
	pthread_testcancel();


	// NEW 2014-06-25 huibong
	// Thread 종료시 m_threadHandle 값을 초기화 처리.
	// - 소멸자에서 잘못된 Thread 를 종료할 수 있기 때문.
	pObject->m_threadHandle = 0;

	// 객체 자동 delete 처리.
	delete pObject;
	return NULL;
}

// 실제 Cache 생성 작업을 수행
void CCacheThread::Execute()
{
	// 1. FHS 의 ftsd 와 통신 처리를 담당할 socket control 객체 생성.
	CFtsdSocketControl fhs;

	// 2. Source Content를 가진 FHS ftsd 에 접속 수행.
	bool bResult = fhs.ConnectTarget( m_dataCache.m_strSourceHost, CServiceConfig::GetInstance()->GetFhsTransferDaemonPort() );
	if( bResult == false )
	{
		// 원본 FHS 와 접속 실패시
		// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	
		_LOG( LERR, "CCacheThread: source fhs[%s] connect fail."
			, m_dataCache.m_strSourceHost.c_str() );

		m_pCacheJob->UnRegister( m_dataCache );		
		return;
	}

	// 3. 연결된 Source FHS 로 Cache 생성 요청을 전달
	std::vector<std::string> vecTargetFHS;
	vecTargetFHS.push_back( m_dataCache.m_strTargetHost );

	bResult = fhs.SendFileCacheRequest( m_dataCache.m_strSourceFilenameHash
	                                  , m_dataCache.m_requestData.m_uContentLength
		                              , CServiceConfig::GetInstance()->GetHashCheckLevel()
		                              , true
		                              , vecTargetFHS );

	if( bResult == false )
	{
		// Cache 생성 요청 전달 실패시
		// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	
		_LOG( LERR, "CCacheThread: cache request send fail. [%s][%s]"
			, m_dataCache.m_strSourceHost.c_str()
			, m_dataCache.m_strSourceFilenameHash.c_str() );
		
		m_pCacheJob->UnRegister( m_dataCache );
		return;
	}

	// 4. FHS 로 부터 처리 결과를 수신할때 까지 대기
	//    - 이상 상황 발생시 무한 대기 가능성이 존재하므로.. 
	//      미리 정의된 FHS_RESPONSE_TOTAL_TIMEOUT 까지만 대기하도록 한다.

	// response timeout 처리를 위한 변수
	time_t timeStart;
	time_t timeNow;

	// 응답 결과를 저장하기 위한 변수
	int nResult;
	bool bSuccess = false;
	std::string szErrorMessage;

	// 대기 시작 전 현재시간을 구한다.
	timeStart = time( &timeStart );

	// FHS 로 부터 응답 수신 대기
	while( 1 )
	{
		// FHS 로 부터 응답 수신 처리
		// -1 : 연결 종료
		//  0 : Timeout 발생
		//  1 : 응답 수신
		//  2 : 복제 관련 응답이 아닌 다른 응답인 경우.. (발생하지 않지만.. 혹시나)
		nResult = fhs.GetFileCacheResult( FHS_RESPONSE_CHECK_TIMEOUT
			                            , bSuccess
										, szErrorMessage
										, m_mapSuccessFhs
										, m_mapFailFhs );

		if( nResult == 0 )
		{
			// 지정된 시간동안 응답 결과를 수신하지 못한 경우
			// - FHS 와 연결이 정상인지 Alive 체크 호출
			// - 만약 연결이 끝어진 경우.. 다음 loop 에서 -1 연결 종료가 리턴됨.
			if( fhs.SendAliveCheck() == false )
			{
				_LOG( LERR, "CCacheThread: alive check fail.[%s][%s]"
					, m_dataCache.m_strSourceHost.c_str()
					, m_dataCache.m_strSourceFilenameHash.c_str() );
			}

			// Timeout 이 발생했으므로 바로 재시도하지 않고 조금 대기 후 다시 시도.
			sleep( 1 );
		}
		else if( nResult == 1 )
		{
			// FHS 로 부터 응답을 수신한 경우.
			// - Content 이상으로 인해 전송 자체를 하지 못했는지 여부 판단
			if( bSuccess == true )
			{
				// 정상적으로 Cache 생성 처리된 경우..
				_LOG( LDBG, "CCacheThread: result receive.[%s][%s]"
					, m_dataCache.m_strSourceHost.c_str()
					, m_dataCache.m_strSourceFilenameHash.c_str() );

				DoSuccessProcess();
			}
			else
			{
				// Content 이상으로 인해 Cache 원본 Content 자체에 문제가 있는 경우.
				// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	 
				_LOG( LERR, "CCacheThread: error result receive.[%s][%s][%s][%llu]"
					, szErrorMessage.c_str()
					, m_dataCache.m_strSourceHost.c_str()
					, m_dataCache.m_strSourceFilenameHash.c_str()
					, m_dataCache.m_requestData.m_uContentLength );
				
				m_pCacheJob->UnRegister( m_dataCache );
			}

			// 처리 결과를 수신했으므로 loop 종료.
			break;
		}
		else if( nResult == -1 )
		{
			// FHS 와 연결이 끝어진 경우.
			// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	 
			_LOG( LERR, "CCacheThread: fhs connection broken.[%s][%s]"
				, m_dataCache.m_strSourceHost.c_str()
				, m_dataCache.m_strSourceFilenameHash.c_str() );

			m_pCacheJob->UnRegister( m_dataCache );
			
			break;
		}
		else if( nResult == 2 )
		{
			// Cache 관련 응답이 아닌 경우
			// - 무시
			;
		}
		else
		{
			// 기타 정의되지 않은 결과값 반환시
			// - FHS 와 세션 이상이 발생할 수 있으므로 
			// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	 
			_LOG( LERR, "CCacheThread: receive undefine result.[%d][%s][%s]"
				, nResult
				, m_dataCache.m_strSourceHost.c_str()
				, m_dataCache.m_strSourceFilenameHash.c_str() );

			m_pCacheJob->UnRegister( m_dataCache );

			break;
		}

		// Total Reponse 대기 시간 확인
		if( difftime( time( &timeNow ), timeStart ) > FHS_RESPONSE_TOTAL_TIMEOUT )
		{
			// Total Response Timeout 발생시
			// - 그냥 실패로 처리 ( Job 에서 본 항목 제거 처리 후 Thread 종료 )	 			
			_LOG( LERR, "CCacheThread: cache result receive timeout.[%s][%s]"
				, m_dataCache.m_strSourceHost.c_str()
				, m_dataCache.m_strSourceFilenameHash.c_str() );

			m_pCacheJob->UnRegister( m_dataCache );
			break;
		}
	} // while(1)	
}


void CCacheThread::DoSuccessProcess()
{
	// 멤버 변수에 저장된 정보를 처리하기 위한 iterator
	std::map<std::string, std::string>::iterator itSuccessFhs;
	
	// m_mapSuccessFhs 에 저장된 성공 결과에 대한 처리
	// - success fhs host, hash_name 의 정보 저장된 상태
	// - Complete Queue 에 저장.
	for( itSuccessFhs = m_mapSuccessFhs.begin(); itSuccessFhs != m_mapSuccessFhs.end(); ++itSuccessFhs )
	{
		m_dataCache.m_strTargetHost = itSuccessFhs->first;
		m_dataCache.m_strTargetFilenameHash = itSuccessFhs->second;

		// complete queue 에 저장 처리
		// - complete queue 가 full 상태일 수 있어 push 실패 가능성 존재
		// - 하지만... complete queue 에 저장 못할 경우...
		//   FHS 에 생성된 Cache 는 처치 곤란한 쓰레기가 될 수 있으므로....	최대한 push 처리
		// - 만약 최종 push 실패시.. Job 에서 해당 항목 삭제 후 throw 처리한다.
		int nRetry = 0;
		while( m_pQueueCacheComplete->Push( m_dataCache ) == false )
		{
			++nRetry;

			if( nRetry > 3 )
			{
				_LOG( LWAR, "Cache complete queue push fail. throw [%s][%s]"
					, m_dataCache.m_strTargetHost.c_str()
					, m_dataCache.m_strTargetFilenameHash.c_str() );

				// 최종 Job 항목에서 삭제 처리한다.
				m_pCacheJob->UnRegister( m_dataCache );

				break;
			}
			else
			{
				// push 실패시 잠시 대기 후 재시도
				sleep( 1 );
			}
		}
	}

	// m_mapFailFhs 에 저장된 실패 결과에 대한 처리
	// - 로깅만 수행한다.
	if( m_mapFailFhs.empty() == false )
	{
		// 멤버 변수에 저장된 정보를 처리하기 위한 iterator
		// - failed fhs host, error message
		std::map<std::string, std::string>::iterator itFailFhs;

		// 실패 내역에 대한 로깅		
		for( itFailFhs = m_mapFailFhs.begin(); itFailFhs != m_mapFailFhs.end(); ++itFailFhs )
		{
			_LOG( LWAR, "CCacheThread: cache create fail [%s][%s]->[%s][%s]"
				, m_dataCache.m_strSourceHost.c_str()
				, m_dataCache.m_strSourceFilenameHash.c_str()
				, itFailFhs->first.c_str()
				, itFailFhs->second.c_str() );
		}

		// 최종 성공 항목은 존재하지 않고... 실패항목만 존재하는 경우..
		// 실패로 판단하고.. Job 항목에서 제거 처리한다.
		if( m_mapSuccessFhs.empty() == true )
		{
			// 최종 Job 항목에서 삭제 처리한다.
			m_pCacheJob->UnRegister( m_dataCache );
		}
	}

	return;
}
