#include "StorageStatus.h"

#include "Logger.h"

#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <mntent.h>
#include <sys/vfs.h>


// Storage Check 주기 ( sec )
#define STORAGE_CHECK_INTERVAL  10

// 최소 보존 Storage 용량. ( 10G )
#define LIMIT_STORAGE_SIZE      10737418240LL      


// 시스템에서 추출된 Mount 정보를 저장하기 위한 struct...
struct systme_mount_info {

	std::string mount_name;                  // Mount Name..
	unsigned long long free_byte;            // free byte
};



// 생성자
CStorageStatus::CStorageStatus( std::string strRootPath )
{
	// 멤버변수 초기화
	m_nFreeSize = 0;
	m_threadHandle = 0;

	// Mutex Lock init.
	pthread_mutex_init( &m_mutexLock, NULL );	

	// 경로값에서 마지막 문자가 '/' 인 경우.. 제거 처리한다.
	//  - 업무 Flow 관련 경로 처리 문제 사전 해결
	//  - 시스템에서 추출된 mount 정보와 비교 처리시 정상 비교가 될 수 있도록..
	if( strRootPath.length() >  1 )
	{
		if( strRootPath.at( strRootPath.length() - 1 ) == '/' )
		{
			m_strRootPath = strRootPath.substr( 0, strRootPath.length() - 1 );
		}
		else
			m_strRootPath = strRootPath;
	}
	else
		m_strRootPath = strRootPath;	
}

// 소멸자
CStorageStatus::~CStorageStatus()
{
	pthread_mutex_destroy( &m_mutexLock );

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

// Thread 기동 함수
// - storage 정보 추출 시작.
bool CStorageStatus::Start()
{
	int nRet = ::pthread_create( &m_threadHandle, NULL, CStorageStatus::threadFunc, this );
	if( nRet != 0 )
	{
		// Thread 생성 실패시
		int errorNum = errno;
		LOG( LERR, "Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

// Available size (byte) 정보를 반환한다.
// - 실제 Free 용량에서 최소 보존 용량을 뺀 나머지 값을 반환한다.
unsigned long long CStorageStatus::GetAvailableSize()
{
	unsigned long long result;

	pthread_mutex_lock( &m_mutexLock );
	
	if( m_nFreeSize <= LIMIT_STORAGE_SIZE )
		result = 0;	
	else
		result = m_nFreeSize - LIMIT_STORAGE_SIZE;
	
	pthread_mutex_unlock( &m_mutexLock );

	return result;
}


void CStorageStatus::SetFreeSize( unsigned long long value )
{
	pthread_mutex_lock( &m_mutexLock );
	
	m_nFreeSize = value;

	pthread_mutex_unlock( &m_mutexLock );

	if( value < ( LIMIT_STORAGE_SIZE * 2 ) )
	{
		_LOG( LINF, "Worker[%d]: CStorageStatus free size [%s][%llu]", getpid(), m_strRootPath.c_str(), value );
	}

	return;
}


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

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

		sleep( STORAGE_CHECK_INTERVAL );
	}

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

	return NULL;
}


// 실제 Free Size 추출 수행 함수.
void CStorageStatus::Execute()
{
	// 1. 장비의 Mount 정보를 추출한다.	
	std::vector<struct systme_mount_info> vecMount;
		
	struct mntent   stMnt;
	struct mntent * pMnt;
	char tempBuf[512];

	FILE * fp = setmntent( "/etc/mtab", "r" );
	if( fp != NULL )
	{
		// 임시 변수
		struct systme_mount_info stMountInfo;

		while( ( pMnt = getmntent_r( fp, &stMnt, tempBuf, sizeof( tempBuf ) ) ) != NULL )
		{
			struct statfs statFsBuf;

			if( ( stMnt.mnt_dir != NULL ) && ( statfs( stMnt.mnt_dir, &statFsBuf ) == 0 ) )
			{
				stMountInfo.mount_name = stMnt.mnt_dir;
				stMountInfo.free_byte = statFsBuf.f_bavail * ( unsigned long long )statFsBuf.f_bsize;

				vecMount.push_back( stMountInfo );

				//LOG( LDBG, "system mount name[%s] free byte[%llu]", stMountInfo.mount_name.c_str(), stMountInfo.free_byte );
			}
		}

		// open 된 fp close 처리.
		endmntent( fp );
	}

	if( vecMount.empty() == true )
	{
		// 추출된 결과가 없는 경우...
		SetFreeSize( 0 );
		return;
	}
	
	// 2. 최상위 경로 정보와 동일한 Mount 정보가 존재하는지 확인
	std::vector<struct systme_mount_info>::iterator it;
	for( it = vecMount.begin(); it < vecMount.end(); it++ )
	{
		// 동일한 경로가 존재하는 경우.
		if( strcmp( m_strRootPath.c_str(), it->mount_name.c_str() ) == 0 )
		{
			SetFreeSize( it->free_byte );
			return;
		}
	}
			
	// 3. 위의 2번 항목에서 mount 정보와 동일한 경로를 찾지 못한 경우...	
	// - 현재 경로의 Upper path 를 추출하여.. 추출된 Mount 경로와 비교 처리.
	std::string strCurrentPath = m_strRootPath;
	std::string strUpperPath;

	while( 1 )
	{
		GetUpperPath( strCurrentPath, strUpperPath );
		if( strUpperPath.empty() == true )
		{
			// 추출된 상위 경로가 존재하지 않는 경우.
			// - 해당 경로의 Mount 정보 추출 실패시 오류 로깅 및 0 으로 설정 처리
			LOG( LERR, "CStorageStatus mount info search fail. path[%s]", m_strRootPath.c_str() );
			SetFreeSize( 0 );
			return;
		}

		// 추출된 상위 경로에 대해 비교 mount 정보 비교 처리.
		for( it = vecMount.begin(); it < vecMount.end(); it++ )
		{
			// 동일한 경로가 존재하는 경우.
			if( strcmp( strUpperPath.c_str(), it->mount_name.c_str() ) == 0 )
			{
				SetFreeSize( it->free_byte );
				return;
			}
		}

		// 만약 추출된 상위 경로와 맞는 Mount 정보가 없는 경우...
		// - 더 최상위 경로를 추출하여 재시도.
		strCurrentPath = strUpperPath;
	}

	// 4. 최종 동일한 mount 정보를 찾지 못한 경우...
	// - 0 으로 설정 처리.
	LOG( LERR, "CStorageStatus mount info full search fail. path[%s]", m_strRootPath.c_str() );
	SetFreeSize( 0 );

	return;
}


// 전달받은 경로로부터.. 상위 Path 정보를 추출하여 전달한다.
// 오류 발생시 [out] 값인 strUpperPath 가 empty 된다.
void CStorageStatus::GetUpperPath( const std::string & strSourcePath, std::string & strUpperPath )
{
	// 인자로 전달되는 strSourcePath 끝에.. '/' 가 이미 제거되었다고 판단하고 작업을 수행한다.
	std::string::size_type pos = strSourcePath.rfind( '/' );
	if( pos == std::string::npos )
	{
		// 찾지 못한 경우...
		// out 값을 empty 처리시킨다.
		LOG( LNOT, "upper path not found. source[%s]", strSourcePath.c_str() );
		strUpperPath.clear();

		return;
	}

	// 찾은 경우..
	strUpperPath = strSourcePath.substr( 0, pos );

	// 추출된 경로가... 최상위 '/' 인 경우.. 유효하지 않으므로 오류로 처리한다.
	if( strcmp( strUpperPath.c_str(), "/" ) == 0 )
	{
		LOG( LNOT, "upper path not vaild that root path. [%s]", strUpperPath.c_str() );
		strUpperPath.clear();
	}

	return;
}
