#include "PhysicalDeleteThread.h"

#include "Logger.h"
#include "ProcessConfig.h"
#include "../ftsd/FtsdSocketControl.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>


// 매 Loop 마다 Sleep 시간 정보 정의 ( sec 단위)
#define SLEEP_TIME_DEFAULT		10 
#define SLEEP_TIME_MAX			20	

// unlink 처리 실패시 retry queue 에 얼마동안 대기할 것인지 시간 (sec 단위)
#define RETRY_STANDBY_MAX_TIME	1800

// unlink 처리 실패시 retry 가능 횟수, 최초 실패 후 2회까지 RETRY 한다는 의미
#define FAIL_MAX_COUNT		3


CPhysicalDeleteThread::CPhysicalDeleteThread( CDataQueue<CRebalanceData> * const pQueueDeleteStandby
	, CProcessMonitoringThread * pMonitoringThread )
	: m_pQueueDeleteStandby( pQueueDeleteStandby ), m_pMonitoringThread( pMonitoringThread )
{
	// 멤버변수 초기화

	m_threadHandle = 0;		// thread handle 초기화

	m_uSleep = SLEEP_TIME_DEFAULT;

	// 내부 retry queue 의 최대 크기 설정 (장기 보관 땜시 기본 설정의 2배)
	m_queueDeleteRetry.SetMaxSize( 2 * (CProcessConfig::GetInstance()->GetMaxQueueSize()) );
}

CPhysicalDeleteThread::~CPhysicalDeleteThread()
{
	// thread 가 동작 중인 경우.. thread 중지 처리
	if( m_threadHandle != 0 )
	{
		pthread_cancel( m_threadHandle );
	}

	sleep( 1 );	// thread 종료 잠시 대기

	// 혹시나 해서 정석대로 NULL 처리 추가함.
	if( m_pQueueDeleteStandby != NULL )
		m_pQueueDeleteStandby = NULL;
	if( m_pMonitoringThread != NULL )
		m_pMonitoringThread = NULL;
}


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

bool CPhysicalDeleteThread::Start()
{
	// 멤버 변수 유효성 check
	if(  m_pQueueDeleteStandby == NULL )
	{
		LOG( LERR, "PhysicalDeleteThread: queue object pointer not valid." );
		return false;
	}
	if( m_pMonitoringThread == NULL )
	{
		LOG( LERR, "PhysicalDeleteThread: CProcessMonitoringThread object pointer not valid." );
		return false;
	}

	// 작업 thread 생성
	int nResult = pthread_create( &m_threadHandle, NULL, CPhysicalDeleteThread::threadFunc, this );
	if( nResult != 0 )
	{
		// thread 생성 실패
		int errorNum = errno;
		LOG( LERR, "PhysicalDeleteThread: Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

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

void * CPhysicalDeleteThread::threadFunc( void * arg )
{
	CPhysicalDeleteThread * pObject = reinterpret_cast<CPhysicalDeleteThread *>(arg);
	pthread_detach( pthread_self() );

	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		// 지정된 주기로 실행
		sleep( pObject->m_uSleep );
	}

	// thread 종료시 handle 정보 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

void CPhysicalDeleteThread::Execute()
{
	// retry queue 및 delete queue 가 비어 있다먼.. MAX sleep 한다.
	if( m_queueDeleteRetry.GetSize() == 0 && m_pQueueDeleteStandby->GetSize() == 0 )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LDBG, "[DELETE ] standby, retry queue empty. max sleep %u sec.", m_uSleep );
		return;
	}

	_LOG( LINF, "[DELETE ] start." );

	CRebalanceData data;

	while( 1 )
	{
		// 삭제 처리할 content 를 queue 에서 pop 하여 가져온다.
		// pop 할 content 가 없으면.. sleep time 조정 후 loop 종료
		if( Pop( data ) == false )
		{
			m_uSleep = SLEEP_TIME_MAX;

			_LOG( LINF, "[DELETE ] proper content not found. max sleep %u sec.", m_uSleep );
			break;
		}

		// ftsd 와 통신을 통해 unlink 처리를 수행한다.

		if( Delete( data ) == true )
		{
			// unlink 성공
			// -> Origin content 를 삭제한 경우 Process 모니터링 모듈에 삭제한 FHS, size 정보 전송
			// -> move 처리 실패로 삭제한 내역은 Process 모니터링 모듈에 전송하지 않는다.
			if( data.m_uMoveResult == MOVE_OK )
			{
				// Process monitoring 모듈에 삭제한 FHS, size 관련 정보 전달 기능 추가
				m_pMonitoringThread->UpdateDeletedInfo( data );
			}
		}
		else
		{
			// unlink 실패
			// -> Retry queue 에 다시 넣던지.. 아니면 버리던지 한다.
			data.m_uDeleteFailCount +=1;
			if( data.m_uDeleteFailCount > FAIL_MAX_COUNT )
			{
				// 재시도 횟수 초과인 경우... 로깅 후 버린다.
				if( data.m_uMoveResult == MOVE_OK )
				{
					_LOG( LERR, "[DELETE ] unlink retry count over.[origin %s %s %llu]"
						, data.m_szOriginHost.c_str()
						, data.m_szOriginFileNameHash.c_str()
						, data.m_uOriginContentLength );
				}
				else
				{
					_LOG( LERR, "[DELETE ] unlink retry count over.[target %s %s %llu]"
						, data.m_szMoveHost.c_str()
						, data.m_szMoveFileNameHash.c_str()
						, data.m_uMoveContentLength );
				}
			}
			else
			{
				// retry queue 에 대한 push 처리한다.
				data.m_timeLastDeleteFail = time( 0 );

				if( m_queueDeleteRetry.Push( data ) == false )
				{
					// push 실패 로깅 후 버린다.
					if( data.m_uMoveResult == MOVE_OK )
					{
						_LOG( LERR, "[DELETE ] unlink retry queue push fail.[origin %s size:%llu %s]"
							, data.m_szOriginHost.c_str()
							, data.m_uOriginContentLength
							, data.m_szOriginFileNameHash.c_str() );
					}
					else
					{
						_LOG( LERR, "[DELETE ] unlink retry queue push fail.[target %s size:%llu %s]"
							, data.m_szMoveHost.c_str()
							, data.m_uMoveContentLength
							, data.m_szMoveFileNameHash.c_str() );
					}
				}
			}

		} // unlink 실패

	} // while(1)

	return;
}

bool CPhysicalDeleteThread::Pop( CRebalanceData & data )
{
	time_t timeNow;

	// 현재 시간 정보 추출
	time( &timeNow );

	// 우선 retry queue 에서 대상이 존재하는지 확인하여 pop 처리
	while( m_queueDeleteRetry.GetSize() > 0 )
	{
		data = m_queueDeleteRetry.Front();

		// 재시도 횟수값 확인
		// - 실제로는 queue 에 push 처리할 때 확인해서 처리하지만....
		// - 혹시 오류가 발생할 수 있어 방어 목적으로 확인한다.
		if( data.m_uDeleteFailCount > FAIL_MAX_COUNT )
		{
			// 재시도 횟수가 over 된 경우 버리고.. 다시 pop 
			m_queueDeleteRetry.Pop();
			continue;
		}

		// delete 처리 관련 마지막 실패 시각을 확인하여...
		// 지정된 대기 시간이 안 된 경우... 
		if( (timeNow - data.m_timeLastDeleteFail) < RETRY_STANDBY_MAX_TIME )
		{
			// retry queue 검색을 중단한다.
			break;
		}

		// retry queue 에서 해당 content 를 POP 하고...
		// delete 작업을 수행하도록 true 를 반환한다.
		m_queueDeleteRetry.Pop();

		return true;
	}

	// 삭제 대기 queue 에서 pop 처리 수행
	while( m_pQueueDeleteStandby->GetSize() > 0 )
	{
		data = m_pQueueDeleteStandby->Front();
		m_pQueueDeleteStandby->Pop();

		// 해당 객체의 처리 결과를 확인하여 삭제 작업이 필요없는 content 인 경우....
		// 버리고 다시 pop 한다.
		if( data.m_uMoveResult == MOVE_ERROR_CREATE )
		{
			continue;
		}

		return true;
	}

	// queue 에서 적합한 content 를 pop 하지 못한 경우... false 반환.
	return false;
}

bool CPhysicalDeleteThread::Delete( CRebalanceData & data )
{
	// ftsd 와 통신할 socket 객체 생성
	CFtsdSocketControl ftsd;

	// 작업 대상 content 정보를 저장할 변수
	std::string szHost;
	std::string szFileNameHash;
	unsigned long long uContentLength;
	unsigned long long uResourceId;

	// 전달받은 content 의 처리 결과를 확인하여..어떤 물리 파일을 삭제할 것인지 판단하여 처리
	if( data.m_uMoveResult == MOVE_OK )
	{
		// origin 의 content 를 삭제하도록 한다.
		szHost			= data.m_szOriginHost;
		szFileNameHash	= data.m_szOriginFileNameHash;
		uContentLength	= data.m_uOriginContentLength;
		uResourceId     = data.m_uResourceId;
	}
	else
	{
		// MOVE 처리 실패로.. target fhs 에 잘못 생성된 content 를 삭제해야 하는 경우..
		szHost			= data.m_szMoveHost;
		szFileNameHash	= data.m_szMoveFileNameHash;
		uContentLength	= data.m_uMoveContentLength;
		uResourceId     = 0;
	}

	// FHS 의 ftsd 에 접속
	if( ftsd.ConnectTarget( szHost, (CProcessConfig::GetInstance()->GetFhsTransferDaemonPort()) ) == false )
	{
		_LOG( LERR, "[DELETE ] fhs: %s (%s) connect fail."
			, szHost.c_str(), (data.m_uMoveResult == MOVE_OK ? "origin" : "target") );
		return false;
	}

	// 접속한 ftsd 로 unlink 처리 요청 전달.
	if( ftsd.SendFileUnlinkRequest( szFileNameHash, uContentLength, true ) == false )
	{
		_LOG( LERR, "[DELETE ] unlink request send fail. [%s %s id:%llu size:%llu %s]"
			, (data.m_uMoveResult == MOVE_OK ? "origin" : "target")
			, szHost.c_str(), uResourceId, uContentLength, szFileNameHash.c_str() );
		return false;
	}

	// 정보 로깅
	_LOG( LINF, "[DELETE ] unlink request send ok. [%s %s id:%llu size:%llu %s]"
		, (data.m_uMoveResult == MOVE_OK ? "origin" : "target")
		, szHost.c_str(), uResourceId, uContentLength, szFileNameHash.c_str() );


	int nTimeout = 5;		// 응답 대기 시간 (5 sec)
	int nResult = 0;
	bool bResultSuccess;	// 처리 결과의 성공, 실패 여부를 저장하기 위한 변수
	std::string szErrorMessage;

	// ftsd 로 부터 응답 수신 대기
	while( 1 )
	{
		// nTimeout 에 지정된 시간동안 응답 대기 시도
		nResult = ftsd.GetFileUnlinkResult( nTimeout, bResultSuccess, szErrorMessage );

		if( nResult == 1 )
		{
			// 요청에 대한 처리 결과 수신
			if( bResultSuccess == true )
			{
				// 요청에 대한 처리가 정상적으로 처리된 경우
				_LOG( LINF, "[DELETE ] unlink ok. [%s %s id:%llu size:%llu %s]"
					, (data.m_uMoveResult == MOVE_OK ? "origin" : "target")
					, szHost.c_str(), uResourceId, uContentLength, szFileNameHash.c_str() );

				return true;
			}
			else
			{
				// 오류 발생시 
				_LOG( LERR, "[DELETE ] unlink error.[%s %s id:%llu size:%llu %s] [%s]"
					, (data.m_uMoveResult == MOVE_OK ? "origin" : "target")
					, szHost.c_str(), uResourceId, uContentLength, szFileNameHash.c_str(), szErrorMessage.c_str() );

				return false;
			}
			
			// 응답 대기 루프 종료
			break;
		}
		else if( nResult == -1 )
		{
			// socket 오류, 접속 종료 등이 발생한 경우
			_LOG( LERR, "[DELETE ] ftsd reponse wait fail.[%s %s]"
				, szHost.c_str(), szFileNameHash.c_str() );
			break;
		}
		else if( nResult == 0 )
		{
			// 지정된 시간 동안 응답대기 중 처리 결과 정보가 아직 수신되지 않은 경우
			// -> Allive Check 패킷 한번 쏘고 다시 Loop 로
			if( ftsd.SendAliveCheck() == false )
			{
				// alive 전송 실패
				// -> socket 종료 또는 오류 발생한 경우
				_LOG( LERR, "[DELETE ] ftsd reponse wait fail2.[%s %s]"
					, szHost.c_str(), szFileNameHash.c_str() );
				break;
			}
			// 응답대기
			continue;
		}
		else
		{
			// unlink 요청에 대한 응답 패킷이 아닌 경우
			// -> 무시하고 다시 대기
			continue;
		}
	}

	return false;
}

