/***************************************************************************
                Logger.cpp
                -----------------------------------------
    begin      : 2011/10/26
    copyright  : (C) 2005 SolutionBox Inc.
    author     : Service 1 Team
    email      : svc1@solbox.com
    version    : 1.0
    
    CopyRight(C) 2005 SolutionBox Inc. All Rights reserved.
    Redistribution and use in source and binary forms, with or with out
    modification, are not permitted in outside of SolutionBox Inc.
***************************************************************************/
#include "Logger.h"

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define MAX_BUFFER_SIZE		2048  // 임시 버퍼 최대 크기
#define DEFAULT_LOG_LEVEL	LINF

using namespace std;

CLogger* CLogger::m_pInstance = NULL;
bool CLogger::m_bIsInitialized = false;
pthread_mutex_t CLogger::m_mutex = PTHREAD_MUTEX_INITIALIZER;


CLogger::CLogger( string programName, string logDir, int logLevel )
{
	m_szProgramName = programName;
	m_szLogDir = logDir;

	if( IsValidLogLevel( logLevel ) == true )
	{
		m_nLogLevel = logLevel;
	}
	else
	{
		m_nLogLevel = DEFAULT_LOG_LEVEL;
	}

	MakeLogLevelString();
}

void CLogger::MakeLogLevelString()
{
	m_vectorLogLevelString.push_back( "EMR" );
	m_vectorLogLevelString.push_back( "ALT" );
	m_vectorLogLevelString.push_back( "CRT" );
	m_vectorLogLevelString.push_back( "ERR" );
	m_vectorLogLevelString.push_back( "WAR" );
	m_vectorLogLevelString.push_back( "NOT" );
	m_vectorLogLevelString.push_back( "INF" );
	m_vectorLogLevelString.push_back( "DBG" );
	m_vectorLogLevelString.push_back( "DEV" );
	m_vectorLogLevelString.push_back( "DEV1" );
	m_vectorLogLevelString.push_back( "DEV2" );
}

CLogger::~CLogger()
{
	m_vectorLogLevelString.clear();
}

bool CLogger::Init( string programName, string logDir, int logLevel )
{
	if( m_bIsInitialized == true )
	{
		return true;
	}

    pthread_mutex_lock( &m_mutex );

	// 변수 유효성 검사.
	if( programName.empty() == true || logDir.empty() == true )
	{
		pthread_mutex_unlock( &m_mutex );
		return false;
	}

	if( IsValidLogLevel( logLevel ) == false )
	{
		pthread_mutex_unlock( &m_mutex );
		return false;
	}

	if( MakeLogDir( programName, logDir ) == false )
	{
		pthread_mutex_unlock( &m_mutex );
		return false;
	}

	if( CLogger::m_pInstance != NULL )
	{
		delete CLogger::m_pInstance;
		CLogger::m_pInstance = NULL;
	}

	CLogger::m_pInstance = new CLogger( programName, logDir, logLevel );
	m_bIsInitialized = true;

    pthread_mutex_unlock( &m_mutex );

	return true;
}

bool CLogger::IsValidLogLevel( int logLevel )
{
	return ( ( logLevel < 0 || logLevel > MAX_LOG_LEVEL   ) ? false : true  );
}

bool CLogger::SetLogLevel( int logLevel )
{
	if( IsValidLogLevel( logLevel ) == false )
	{
		return false;
	}

	m_nLogLevel = logLevel;

	return true;
}

bool CLogger::MakeLogDir( string programName, string logDir )
{
	string path = logDir + "/" + programName;

	struct stat dirStat;

	// 해당 이름을 가진 파일 또는 디렉토리가 존재하고
	if( lstat( path.c_str(), &dirStat ) == 0 )
	{
		// 해당 이름이 디렉토리인 경우 
		if( S_ISDIR( dirStat.st_mode ) == true )
		{
			return true;
		}		
		else
		{
			cerr << "exist file with the same name as log path(" << path << ")." << endl;
			return false;
		}
	}

	// 디렉토리 가 존재하지 않는 경우 디렉토리 생성 시도
	string cmd = "mkdir -p " + path;
	system( cmd.c_str() );

	if( IsDirectory( path ) == false )
	{
		cerr << "can't make log directory. path=" << path << "." << endl;
		return false;
	}

	return true;
}

bool CLogger::IsDirectory( string path )
{
    struct stat dirStat;

    if( lstat ( path.c_str(), &dirStat ) != 0 )
    {
		cerr << "Log path not valid. Check path [" << path << "][" << errno << "][" << strerror(errno) << "]" << endl;
        return false;
    }   

    // 해당 정보가 Directory 가 아닌 경우
    if( S_ISDIR( dirStat.st_mode ) == false )
    {
        return false;
    }

    return true;
}

void CLogger::Exit()
{
	if( m_bIsInitialized == false )
	{
		return;
	}

    pthread_mutex_lock( &m_mutex );

	if( CLogger::m_pInstance != NULL )
	{
		delete CLogger::m_pInstance;
		CLogger::m_pInstance = NULL;
		m_bIsInitialized = false;
	}

    pthread_mutex_unlock( &m_mutex );
}

CLogger* CLogger::GetInstance()
{
	return CLogger::m_pInstance;
}

string CLogger::GetLogFilename( struct tm &timeNow )
{
	char timeStr[256];
	snprintf( timeStr, (size_t)256, "%04d%02d%02d.log", timeNow.tm_year+1900, timeNow.tm_mon+1, timeNow.tm_mday);

	// Make File Name
	string filename = m_szLogDir + "/" + m_szProgramName + "/" + m_szProgramName + "_" + string( timeStr );
	
	return filename;
}

bool CLogger::Write( int logLevel, const char * fmt, ...)
{
	// 가변 인자 처리
	va_list args;
	char buffer[MAX_BUFFER_SIZE];
	va_start( args, fmt );
	if( vsnprintf( buffer, MAX_BUFFER_SIZE, fmt, args) < 0 )
	{
		va_end( args );
		return false;
	}
	va_end(args);

	return Write( logLevel, PREFIX_DATE, NULL, 0, buffer );
}

bool CLogger::WriteNoPrefix( int logLevel, const char * fmt, ...)
{
	// 가변 인자 처리
	va_list args;
	char buffer[MAX_BUFFER_SIZE];
	va_start( args, fmt );
	if( vsnprintf( buffer, MAX_BUFFER_SIZE, fmt, args) < 0 )
	{
		va_end( args );
		return false;
	}
	va_end(args);

	return Write( logLevel, PREFIX_NONE, NULL, 0, buffer );

	return false;
}

// CHG 2012-08-16 huibong
// 가변인자를 사용하는 Write 함수 다중 정의로 인해...
// Complier 에서 인수 갯수 및 Type 이 동일할 경우 다른 함수를 가르키는 현상이 발견됨.
// 이를 해결하기 위해 Write 함수에 대한 다중 정의를 제거토록 함수명을 명확하게 변경처리함.
// 함수명 : Write -> WriteWithFunc 으로 변경 처리
bool CLogger::WriteWithFunc( int logLevel, const char* filename, const char* funcname, int lineNum, const char * fmt, ...)
{
	if( filename == NULL || funcname == NULL || lineNum < 0 || fmt == NULL )
	{
		return false;
	}

	// 가변 인자 처리
	va_list args;
	char buffer[MAX_BUFFER_SIZE];
	va_start( args, fmt );
	if( vsnprintf( buffer, MAX_BUFFER_SIZE, fmt, args) < 0 )
	{
		va_end( args );
		return false;
	}
	va_end(args);


	char className[255]; 
	string funcPrefix = GetClassName( className, filename );
	funcPrefix += "::" + string( funcname ) + "()";

	return Write( logLevel, PREFIX_FUNCTION, funcPrefix.c_str(), lineNum, buffer );
}

const char* CLogger::GetClassName( char* className,  const char* filename )
{
	if( className == NULL )
	{
		cout << "[ERROR] The input argument 'className' is NULL." << endl;
		return NULL;
	}

	if( filename == NULL )
	{
		cout << "[ERROR] The input argument 'filename' is NULL." << endl;
		strcpy( className, "NULL" );
		return className;
	}

	int filenameSize = strlen( filename );
	memcpy( className, filename, filenameSize ); 

	// find end position
	char endCharacter = '.';
	int	 endPos		  = filenameSize - 1;
	for( ; endPos > 0; --endPos )
	{
		if( className[endPos] == endCharacter )
		{
			break;
		}
	}

	if( endPos == 0 )
	{
		cout << "[ERROR] endPos=0" << endl;
		strcpy( className, "NULL" );
		return className;
	}

	className[endPos] = '\0';

	// find start position
	char startCharacter = '/';
	int  startPos		= endPos - 1;
	for( ; startPos > 0; --startPos )
	{
		if( className[startPos] == startCharacter )
		{
			break;
		}
	}

	if( startPos != 0 )
	{
		startPos += 1;
	}

	return (className + startPos );
}

bool CLogger::Write( int logLevel, int logPrefix, const char* functionName, int lineNum, const char* log )
{
	/// 유효한 로그 레벨이 아니면 로깅하지 않음.
	if( IsValidLogLevel( logLevel ) == false )
	{
		return false;
	}

	// Log Level 검사 : 지정된 Level 이상인 경우 로깅하지 않음.
	if( logLevel > m_nLogLevel )
	{
		return false;
	}

	if( log == NULL )
	{
		return false;
	}

	// Get Current Data & Time
	time_t now = time( NULL );
	struct tm timeNow;
	localtime_r( &now, &timeNow );

	string filename = GetLogFilename( timeNow );
	
	// Log file open
	FILE* pFile = NULL;
	pFile = fopen( filename.c_str(), "a+" );

	if( pFile == NULL )
	{
		cerr << "Log file open fail.[" << filename.c_str() << "][" << errno << "][" << strerror(errno) << "]" << endl;
		return false;
	}

	// 시간 정보처리
	char timeStr[256];
	snprintf( timeStr, (size_t)256, "[%02d:%02d:%02d]"
			, timeNow.tm_hour, timeNow.tm_min, timeNow.tm_sec );

	// Log Level 문자열 검색
	string szLevel = m_vectorLogLevelString[ logLevel ];

	switch( logPrefix )
	{
		case PREFIX_DATE:
			/// 형식 예: [15:47:41] [DBG] sample log message.
			fprintf( pFile, "%s [%-4s] %s\n", timeStr, szLevel.c_str(), log );
			break;

		case PREFIX_FUNCTION:
			if( functionName == NULL )
			{
				fclose( pFile );
				return false;
			}
			/// 형식 예: [15:47:41] [DBG] sample log message. [SomeClass::SomeFunction():12]
			fprintf( pFile, "%s [%-4s] %s [%s:%d]\n", timeStr, szLevel.c_str(), log, functionName, lineNum );
			break;

		case PREFIX_NONE: 
			/// 형식 예: sample log message.
			fprintf( pFile, "%s\n", log );
			break;
	}
	// Write to log file
	fflush( pFile );

	// 종료 처리.
	fclose( pFile );
	return true;
}

bool CLogger::WriteHex( int logLevel, const unsigned char* data, const int size )
{
	/// 유효한 로그 레벨이 아니면 로깅하지 않음.
	if( IsValidLogLevel( logLevel ) == false )
	{
		return false;
	}

	// Log Level 검사 : 지정된 Level 이상인 경우 로깅하지 않음.
	if( logLevel > m_nLogLevel )
	{
		return false;
	}

	if( data == NULL )
	{
		return false;
	}

	if( size <= 0 )
	{
		return false;
	}

	// Get Current Data & Time
	time_t now = time( NULL );
	struct tm timeNow;
	localtime_r( &now, &timeNow );

	string filename = GetLogFilename( timeNow );
	
	// Log file open
	FILE* pFile = NULL;
	pFile = fopen( filename.c_str(), "a+" );

	if( pFile == NULL )
	{
		cerr << "Log file open fail.[" << filename.c_str() << "][" << errno << "][" << strerror(errno) << "]" << endl;
		return false;
	}

	for( int i = 0; i < size; ++i )
	{
		fprintf( pFile, "%02X", data[i] );

		if( i == 0 )
		{
			continue;
		}

		if( ( (i+1) % 8 ) == 0 )
		{
			fprintf( pFile, " " );
		}

		if( ( (i+1) % 16 ) == 0 )
		{
			fprintf( pFile, " " );
		}

		if( ( (i+1) % 32 ) == 0 )
		{
			fprintf( pFile, "\n" );
		}
	}
	fprintf( pFile, "\n" );
	fflush( pFile );

	// 종료 처리.
	fclose( pFile );

	return false;
}
