#include "StorageInfo.h"

#include "Logger.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <math.h>


// 생성자
CStorageInfo::CStorageInfo()
{
	// 멤버변수 초기화
	m_vecStorageInfo.clear();
}

// 소멸자
CStorageInfo::~CStorageInfo()
{
	m_vecStorageInfo.clear();
}


// 전달받은 경로로부터.. 상위 Path 정보를 추출하여 전달한다.
// 오류 발생시 [out] 값인 strUpperPath 가 empty 된다.
void CStorageInfo::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;
}


// 초기화 함수
// - szRoot : Content 저장 최상위 경로 정보.
bool CStorageInfo::Init( const char * szRoot )
{
	// 전달받은 최상위 경로 정보에 대한 Check
	if( szRoot == NULL || strlen( szRoot ) <= 0 )
	{
		LOG( LERR, "storage root path not valid.");
		return false;
	}
	
	// 추출된 정보 저장을 위한 임시변수
	int iMountCount = 0;
	struct statfs * pMountBuf = NULL;
	std::vector< struct stStorageInfo > vecStorageAll;
	std::vector< struct stStorageInfo >::iterator it;
	struct stStorageInfo stStorage;

	// 추출 정보 저장 및 멤버 변수 초기화
	vecStorageAll.clear();
	m_vecStorageInfo.clear();

	// Mount 정보 추출
	// - MNT_WAIT flag 사용시 정확한 정보를 추출, 하지만 block 발생 가능
	// - 따라서 항상 정확한 정보를 추출할 필요는 없으며.. block 이 발생하지 않도록
	// - MNT_NOWAIT flag 사용한다.
	iMountCount = getmntinfo( &pMountBuf, MNT_NOWAIT );
	if( iMountCount == 0 )
	{
		// 정보 추출 실패시 오류 내역 로깅
		int errorNum = errno;
		LOG( LERR, "Storage info init error. getmntinfo() fail[%d]. [%d][%s]"
			 , iMountCount, errorNum, strerror( errorNum ) );
		return false;
	}

	// 추출된 mount 관련 모든 정보를 임시 변수에 저장
	for( int i = 0; i < iMountCount; i++ )
	{
		stStorage.strName			= pMountBuf[i].f_mntonname;
		stStorage.strFsTypeName		= pMountBuf[i].f_fstypename;
		stStorage.ulUsedByte		= ( pMountBuf[i].f_blocks - pMountBuf[i].f_bfree ) *  pMountBuf[i].f_bsize;
		stStorage.ulAvailableByte	= pMountBuf[i].f_bavail *  pMountBuf[i].f_bsize;
		stStorage.usage = 0;

		vecStorageAll.push_back( stStorage );
	}

	// 전달받은 mount 최상위 경로와 동일한 명칭의 mount 가 존재하는 검사
	// - 이럴 경우는 거의 없는데.. 혹시나 해서 기능 탑재함.
	for( it = vecStorageAll.begin(); it < vecStorageAll.end(); it++ )
	{
		if( strcmp( szRoot, it->strName.c_str() ) == 0 )
		{
			m_vecStorageInfo.push_back( *it );
		}
	}

	// 전달받은 최상위 mount 정보와 동일한 명칭의 mount 가 존재하지 않는 경우..
	// - sub path 부분을 제외한 후 다시 장비 Mount 정보와 비교한다. 
	// - 장비 mount 정보를 찾을 때까지 본 비교를 반복하여 수행한다.
	if( m_vecStorageInfo.empty() == true )
	{
		std::string strPreviousPath = szRoot;
		std::string strCurrentPath = szRoot;

		do
		{
			// 수정된 경로 정보만을 가지고 부분 비교를 수행....
			for( it = vecStorageAll.begin(); it < vecStorageAll.end(); it++ )
			{
				// 부분 비교 처리
				if( strncmp( strCurrentPath.c_str(), it->strName.c_str(), strlen( strCurrentPath.c_str() ) ) == 0 )
				{
					m_vecStorageInfo.push_back( *it );
				}
			}

			// 위에서 정보 추출이 실패한 경우..상위 Path 정보를 추출한다. ( /user2/dav_storage, /stg/node0/aa/bb 등으로 설정된 경우를 위해 )
			if( m_vecStorageInfo.empty() == true )
			{
				GetUpperPath( strPreviousPath, strCurrentPath );
				if( strCurrentPath.empty() == true )
				{
					// 상위 path 가 더이상 유효하지 않은 경우.
					break;
				}

				strPreviousPath = strCurrentPath;
			}

		} while( m_vecStorageInfo.empty() == true );
	}

	// 임시 vector 에 저장된 내용 초기화
	vecStorageAll.clear();

	// 최종
	if( m_vecStorageInfo.empty() == true )
	{
		LOG( LERR, "Storage info init error. root path[%s] not found in system mount.", szRoot );
		return false;
	}

	// 추출 성공시
	// - Storage 사용률 정보 계산해서 저장해 놓는다.
	for( it = m_vecStorageInfo.begin(); it < m_vecStorageInfo.end(); it++ )
	{
		if( it->ulAvailableByte + it->ulUsedByte == 0 )
			it->usage = 100;
		else
		{
			// 사용률은 정수로 구하며... 나머지가 존재할 경우... 올림 처리한다.
			it->usage = ( int ) ceil( ( double ) it->ulUsedByte / ( it->ulUsedByte + it->ulAvailableByte ) * 100.0 );
		}
	}

	return true;
}

// 멤버 vector 에 저장된 경로의 Total Size, Total availsize, Max usage 정보를 반환.
bool CStorageInfo::GetStorageSizeInfo( uint64_t & ulFhsTotal, uint64_t & ulFhsAvailable, int & iMaxUsage )
{
	// storage 정보가 비어 있는 경우
	if( m_vecStorageInfo.empty() == true )
		return false;

	// output 인자 초기화.
	ulFhsTotal = 0;
	ulFhsAvailable = 0;
	iMaxUsage = 0;

	// total : 각 mount 의 used + available size 합계.
	// available : 각 mount 의 합계.
	// 최대 storage 사용률 정보 추출 
	
	std::vector< struct stStorageInfo >::iterator it;
	for( it = m_vecStorageInfo.begin(); it < m_vecStorageInfo.end(); it++ )
	{
		// 전체 용량, 사용 가능 용량 합산
		ulFhsTotal = ulFhsTotal + it->ulUsedByte + it->ulAvailableByte;
		ulFhsAvailable = ulFhsAvailable + it->ulAvailableByte;

		// 최대 사용률 정보 추출
		if( it->usage > iMaxUsage )
		{
			iMaxUsage = it->usage;
		}

		// 2018-02-06 NEW huibong Storage 사용률이 90% 초과하는 경우 해당 Storage 의 정보를 로깅 처리한다.(#31701)
		if( it->usage > 90 )
		{
			_LOG( LINF, "[Storage Info] storage usage: %s  %3d %%, used[%14ju], avail[%14ju]."
				  , it->strName.c_str(), it->usage, it->ulUsedByte, it->ulAvailableByte );
		}
		else
		{
			_LOG( LDBG, "%s used[%14ju] avail[%14ju] usage[%3d %%]"
				  , it->strName.c_str(), it->ulUsedByte, it->ulAvailableByte, it->usage );
		}
	}

	return true;
}


// 멤버 vector 에 저장된 mount 경로명 정보를 반환
bool CStorageInfo::GetStoragePath( std::vector<std::string>& vecPath )
{
	// storage 정보가 비어 있는 경우
	if( m_vecStorageInfo.empty() == true )
		return false;

	// output 인자 초기화.
	vecPath.clear();
	
	std::vector< struct stStorageInfo >::iterator it;
	for( it = m_vecStorageInfo.begin(); it < m_vecStorageInfo.end(); it++ )
	{
		vecPath.push_back( it->strName );
	}

	return true;
}


// 멤버 vector 에 저장된 storage 정보 중에 ZFS file system 정보 존재하는지 Check
bool CStorageInfo::IsExistZFS()
{
	// storage 정보가 비어 있는 경우 - ZFS file system 없는 것으로 처리
	if( m_vecStorageInfo.empty() == true )
		return false;

	std::vector< struct stStorageInfo >::iterator it;
	for( it = m_vecStorageInfo.begin(); it < m_vecStorageInfo.end(); it++ )
	{
		if( strcasecmp( "zfs", it->strFsTypeName.c_str() ) == 0 )
		{
			// zfs filesystem 이 존재하는 경우...
			// - 나머지는 check 할 필요 없이 그냥 true 반환.
			return true;
		}
	}

	// zfs filesystem 이 없는 경우
	return false;
}
