/***************************************************************************
                Logger.h
                -----------------------------------------
    begin      : 2011/10/26
    copyright  : (C) 2005 SolutionBox Inc.
    author     : Service 1 Team
    email      : svc1@solbox.com
    version    : 3.2.0.805
    
    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.
***************************************************************************/
#ifndef __LOGGER_H__
#define __LOGGER_H__

#include <string>
#include <vector>
#include <pthread.h>

///@brief  로그 레벨 정의
#define LEMR		0	/* system is or will be unusable if situation is not resolved */
#define LALT		1	/* immediate action required */
#define LCRT		2	/* critical situations */
#define LERR		3	/* error conditions */
#define LWAR		4	/* recoverable errors */
#define LNOT		5	/* unusual situation that merits investigation */
#define LINF		6	/* information messages */
#define LDBG		7	/* verbose data for debugging */
#define LDEV		8	/* verbose data for developer */
#define LDEV1		9	/* start or end the function of application level */
#define LDEV2		10	/* start or end the function of application level */

#define MAX_LOG_LEVEL LDEV2


///@brief Log 정보를 파일로 저장하기 위한 Class.
///1. 가변 format 으로 전달된 로그 관련 정보를 Log Level 에 따라 로그 파일에 아래의 4가지 형식으로 저장한다.
///
///   1.1 Prefix로 [hh:mm:ss]와 [ClassName::FunctionName]이 추가된 로그
///       LOG( level, format, ... ) 매크로 사용.
///       예) [17:43:40] [DBG] log level debug [LoggerTestTestLogger]
///
///   1.2 Prefix로 [hh:mm:ss]이 추가된 로그
///       _LOG( level, format, ... ) 매크로 사용
///       예) [17:43:40] [EMR] log level emergency
///
///   1.3 Prefix가 없는 로그
///       _LOG_( level, format, ... ) 매크로 사용
///       예) log level emergency
///
///   1.4 Hex 로그.
///       LOG_HEX( level, data, size ) 매크로 사용
///       예) 00010203 04050607  08090A0B 0C0D0E0F
///
///2. Logger 객체 초기화시 전달된 Log Level 정보보다 전달받은 Log Level 정보가 큰 경우 해당 로그는 파일로 저장되지 않는다.
///
///3. 로그 파일은 매 일단위로 저장파일이 변경된다.
///
///4. 로그 저장을 위한 program 경로가 존재하지 않는 경우 자동 생성 처리된다.
///
///5. 로그 저장방식은 매 저장로그마다 open-close 로 처리된다.
///
///6. 파일로 기록시 Log Level 에 대한 정보도 함께 기록된다.
///
///7. 싱글톤으로 작성되었고, CLogger::Init(...)시에 쓰레드 안정성을 제공한다.
///
///8. 동적으로 로그 레벨을 변경할 수 있는 인터페이스를 제공한다.
///
class CLogger
{
// Attributes
private:
	///@brief 싱글톤 객체 인트턴스.
	static CLogger* m_pInstance;

	///@brief 싱글톤 객체 초기화 여부.
	static bool m_bIsInitialized;

	///@brief 싱글톤 객체 초기화시 스레드 안정성을 위한 뮤텍스.
	static pthread_mutex_t m_mutex;

	///@brief 프로그램 이름. 로그 파일 경로를 만들 때 사용.
	std::string m_szProgramName;

	///@brief 공통 로그 디렉토리 이름. 로그 파일 경로를 만들 때 사용.
	std::string m_szLogDir;

	///@brief 로그 레벨.
	int m_nLogLevel;

	///@brief 로그 레벨에 대응되는 문자열 정보를 저장.
	std::vector<std::string> m_vectorLogLevelString;

protected:

public:
	///@brief 로그 형식을 지정.
	typedef enum
	{
		PREFIX_NONE = 0,	/// 클래스 설명의 1.3에 해당
		PREFIX_DATE,		/// 클래스 설명의 1.2에 해당
		PREFIX_FUNCTION,	/// 클래스 설명의 1.1에 해당
	} LOG_PREFIX;

// Operations
private:
	///@brief 생성자.
	///       프로그램 이름, 공통 로그 디렉토리, 로그 레벨을 저장하고 
	///       로그 레벨에 대응되는 문자열 정보를 만든다.
	///@param programName [in] 프로그램 이름
	///@param logDir [in] 공통 로그 디렉토리 경로
	///@param logLevel [in] 로그 레벨
	CLogger( std::string programName, std::string logDir, int logLevel );

	///@brief 소멸자.
	///       로그 레벨에 대응되는 문자열 정보를 저장하고 있는 
	///       벡터 m_vectorLogLevelString을 clear 시킴. 
	virtual ~CLogger();

	///@brief 로그 레벨에 대응되는 문자열을 만든다.
	///@param none.
	///@return none.
	void MakeLogLevelString();

	///@brief 로그 파일이 위치할 실제 로그 디렉토리를 생성한다.
	///       생성할 디렉토리 경로는 'logDir/programName'이 된다.
	///@param programName [in]  프로그램 이름 
	///@param logDir [in] 공통 로그 디렉토리 경로
	///@return  디렉토리가 이미 존재하거나 생성 성공하면 true, 
	///         해당 경로가 존재하지만 디렉토리가 아니거나, 디렉토리 생성 실패하면 false 반환.
	static bool MakeLogDir( std::string programName, std::string logDir );

	///@brief 해당 경로가 디렉토리 인지 아닌지 판단.
	///@param path [in] 디렉토리 인지 아닌지 판단할 경로.
	///@return 해당 경로가 디렉토이면 true,
	///        경로가 존재하지 않거나 디렉토리가 아니면 false 반환.
	static bool IsDirectory( std::string path );

	///@brief 로그 레벨이 올바른지 판별.
	///@param logLevel [in]
	///@return 올바른 로그 레벨이면 true, 그렇지 않으면 false 반환.
	static bool IsValidLogLevel( int logLevel );

	///@brief 시간 정보를 입력 받아 로그 파일 이름을 만든다.
	///       로그 파일 이름 형식 : 프로그램명_YYYYMMDD.log
	///param timeNow [in] 현재 시간 정보.
	///return 로그 파일 이름.
	std::string GetLogFilename( struct tm &timeNow );

	///@brief 로그를 남기는는 클래스가 정의된 파일의 이름에서 클래스명을 추출한다.
	///       쓰레드 안정성을 보장한다.
	///@param className [out] 파일 이름에서 추출된 클래스명
	///@param filename [in] 파일 이름.
	///@return 클래스명 추출이 성공하면 클래스명 문자열의 포인터, 추출 실패하면 NULL.
	const char* GetClassName( char* className, const char* filename );

	///@brief 인자 logPrefix에 따라 적절한 형식으로 로그 파일에 로그를 저장한다.
	///       인자 logLevel이 설정된 로그 레벨보다 높으면 로그를 출력하지 않는다. 
	///@param logLevel [in] 로그 레벨
	///@param logPrefix [in] 로그 프리픽스 종류.
	///@functionName [in] ClassName::FunctionNmae() 형식의 문자열.
	///@lineNum [in] 라인 번호.
	///@log [in] 출력하고자 하는 로그 내용.
	bool Write( int logLevel, int logPrefix, const char* functionName, int lineNum, const char* log );

protected:

public:
	///@brief 싱글톤 객체 m_pInstance를 생성하고 인자 정보로 로그 디렉토리를 만든다.
	///@param programName [in] 프로그램 이름
	///@param logDir [in] 공통 로그 디렉토리 경로
	///@param logLevel [in] 로그 레벨
	///@return 로그 디렉토리를 만들고 싱글톤 객체를 생성했으면 true,
	///        로그 디렉토리를 만들지 못 했거나 인자 값이 올바르지 않으면 false 반환.
	static bool Init( std::string programName, std::string logDir, int logLevel );

	///@brief 싱글톤 객체 m_pInstance를 delete 한다.
	///@param none.
	///@return none.
	static void Exit();

	///@brief 싱글톤 객체 m_pInstance를 반환한다.
	///@param none.
	///@return CLogger 객체의 인스턴스.
	static CLogger* GetInstance();

	///@brief Prefix로 [hh:mm:ss]이 추가된 형식으로 로그 저장.
	///@param logLevel [in] 로그 레벨
	///@param fmt [in] 로그 내용 포맷.
	///@param __VAR_ARGS__ [in] 가변 인자.
	///@return 로그 파일에 로그를 저장했으면 true, 그렇지 않으면 false 반환
	bool Write( int logLevel, const char * fmt, ...)
	    __attribute__((format(printf, 3, 4)));

	///@brief Prefix가 없는 로그 저장.
	///@param logLevel [in] 로그 레벨
	///@param fmt [in] 로그 내용 포맷.
	///@param __VAR_ARGS__ [in] 가변 인자.
	///@return 로그 파일에 로그를 저장했으면 true, 그렇지 않으면 false 반환
	bool WriteNoPrefix( int logLevel, const char * fmt, ...)
	    __attribute__((format(printf, 3, 4)));


	// CHG 2012-08-16 huibong
	// 가변인자를 사용하는 Write 함수 다중 정의로 인해...
	// Complier 에서 인수 갯수 및 Type 이 동일할 경우 다른 함수를 가르키는 현상이 발견됨.
	// 이를 해결하기 위해 Write 함수에 대한 다중 정의를 제거토록 함수명을 명확하게 변경처리함.

	///@brief Prefix로 [hh:mm:ss]와 [ClassName::FunctionName:line]이 추가된 형식으로 로그 저장.
	///@param logLevel [in] 로그 레벨
	///@param filename [in] 파일 이름
	///@param funcname [in] 함수 이름
	///@param lineNum [in] 라인 번호
	///@param fmt [in] 로그 내용 포맷.
	///@param __VAR_ARGS__ [in] 가변 인자.
	///@return 로그 파일에 로그를 저장했으면 true, 그렇지 않으면 false 반환
	bool WriteWithFunc( int logLevel, const char* filename, const char* funcname, int lineNum, const char * fmt, ...)
	     __attribute__((format(printf, 6, 7)));

	///@brief 로그를 hex 형식으로 저장.
	///@param logLevel [in] 로그 레벨
	///@param data [in] hex 형식으로 출력할 데이터.
	///@pram size [in] data의 크기.
	///@return 로그 파일에 로그를 저장했으면 true, 그렇지 않으면 false 반환
	bool WriteHex( int logLevel, const unsigned char* data, const int size );

	///@brief 로그을 설정한다. 인자 logLevel이 적절한 값이면 새로운 값으로 변경하고
	///       적절한 값이 아니면 로그 레벨을 변경하지 않는다.
	///@param logLevel [in] 설정할 로그 레벨
	///@return none.
	bool SetLogLevel( int logLevel );

	inline int GetLogLevel() { return m_nLogLevel; };
	inline std::string GetLogDir() { return m_szLogDir + "/" + m_szProgramName; };
};

#define LOG( level, format, ... ) \
	if( CLogger::GetInstance() != NULL ) \
	{ \
		CLogger::GetInstance()->WriteWithFunc( level, __FILE__, __FUNCTION__, __LINE__, format, ##__VA_ARGS__ ); \
	}
	

#define _LOG( level, format, ... ) \
	if( CLogger::GetInstance() != NULL ) \
	{ \
		CLogger::GetInstance()->Write( level, format, ##__VA_ARGS__ ); \
	}
	

#define _LOG_( level, format, ... ) \
	if( CLogger::GetInstance() != NULL ) \
	{ \
		CLogger::GetInstance()->WriteNoPrefix( level, format, ##__VA_ARGS__ ); \
	}
	

#define _LOG_HEX_( level, data, size ) \
	if( CLogger::GetInstance() != NULL ) \
	{ \
		CLogger::GetInstance()->WriteHex( level, (const unsigned char*)data, size ); \
	}



#define FUNC_BEGIN() LOG( LDEV1, "begin" )
#define FUNC_END() LOG( LDEV1, "end" )

#define FRM_BEGIN() LOG( LDEV2, "begin" )
#define FRM_END() LOG( LDEV2, "end" )

#endif // __LOGGER_H__
