/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header CLog
 * Interface for a circular, permanent log file.
 */

#ifndef __CLog_h__
#define __CLog_h__	1

#include <stdarg.h>		// for inline functions

#include <DirectoryServiceCore/PrivateTypes.h>
#include <DirectoryServiceCore/CString.h>
#include <DirectoryServiceCore/CFile.h>

class DSMutexSemaphore;

//-----------------------------------------------------------------------------
//	* Logging Flags
//
//		The user may turn logging on or off, and select from the type of logging
//		desired. Certain types of logging information are NOOPs if the
//		compile-time symbol DEBUG is not defined.
//
//-----------------------------------------------------------------------------

enum eLogFlags {
	kLogNone		= 0x00000000,			// Log no information
	kLogMeta		= 0x00000001,			// Info about the log itself
	kLogApplication	= 0x00000002,			// Application info

	// Listener
	kLogListener	= 0x00000010,

	// Handler
	kLogHandler		= 0x00000100,
	kLogMsgQueue	= 0x00000200,

	// Threads
	kLogThreads		= 0x00001000,			// Information about Threads

	// Endpoint
	kLogEndpoint	= 0x00002000,

	// Plugin
	kLogPlugin		= 0x00004000,

	// Proxy
	kLogConnection	= 0x00008000,

	// Transactions
	kLogMsgTrans	= 0x00010000,

	// TCP Endpoint
	kLogTCPEndpoint	= 0x00020000,

	// Performance Stats
	kLogPerformanceStats = 0x00040000,

	// Assert
	kLogAssert		= 0xF0000000,

	// Everything
	kLogEverything	= 0x7FFFFFFF
};

// log types

typedef enum {
	keServerLog		= 1,
	keErrorLog,
	keDebugLog,
	keInfoLog
} eLogType;


//-----------------------------------------------------------------------------
//	* CLog: a little more than your basic log class.
//
//	This class is responsible for writing CStrings to permanent storage.
//	Limits can be placed on the size of the log file, in which case the class
//	will "wrap", writing over the oldest data. When the object is destroyed,
//	it will properly resequence the file.
//-----------------------------------------------------------------------------

class CLog
{
public:
	/**** Typedefs, enums, and constants. ****/
	// Constructor constants.
	enum {
		kFileWrap		= 0x1,
		kComments		= 0x2,
		kRollLog		= 0x4,
		kTimeDateStamp	= 0x20,
		kThreadInfo		= 0x80
	};

	enum {
		kLengthUnlimited	= -1UL,
		kLengthReasonable	= 0x8000,	// 32K
		kTypeDefault		= 'TEXT',
		kCreatorSimpleText	= 'ttxt',
		kCreatorCodeWarrior	= 'CWIE',
		kCreatorBBEdit		= 'R*ch'
	};

	// Function prototype for append hook.
	typedef void	(*AppendHook) ( const CString &line );

public:
	// Static methods
	static sInt32	Initialize			(	OptionBits	srvrFlags		= kLogEverything,
											OptionBits	errFlags		= kLogEverything,
											OptionBits	debugFlags		= kLogMeta,
											OptionBits	infoFlags		= kLogMeta,
											bool		inOpenDbgLog	= false,
											bool		inOpenInfoLog	= false );
	static void		Deinitialize		( void );
	static void		StartLogging		( eLogType inWhichLog, uInt32 inFlag );
	static void		StopLogging			( eLogType inWhichLog, uInt32 inFlag );
	static void		ToggleLogging		( eLogType inWhichLog, uInt32 inFlag );
	static bool		IsLogging			( eLogType inWhichLog, uInt32 inFlag );
	static void		StartDebugLog		( void );
	static void		StopDebugLog		( void );
	static void		StartErrorLog		( void );
	static void		StopErrorLog		( void );
	static void		StartInfoLog		( void );
	static void		StopInfoLog			( void );
	static CLog*	GetServerLog		( void );
	static CLog*	GetErrorLog			( void );
	static CLog*	GetDebugLog			( void );
	static CLog*	GetInfoLog			( void );

public:
				CLog (	const char		*file,
						uInt32			maxLength	= kLengthUnlimited,
						OptionBits		flags		= kThreadInfo,
						OSType			type		= kTypeDefault,
						OSType			creator		= kCreatorSimpleText );
	virtual		~CLog ( void );

	// New methods.
	virtual void	GetInfo		(	CFileSpec	&fileSpec,
									uInt32		&startOffset,
									uInt32		&dataLength,
									bool		&hasWrapped );

	virtual void	SetMaxLength	( uInt32	maxLength );
	virtual OSErr	Append			( const CString &line );
	virtual OSErr	ClearLog		( void );
	virtual void	AddHook			( AppendHook newHook );

	virtual long			Lock	( void );
	virtual void			UnLock	( void );


protected:
	// Class globals
	static OptionBits		fSrvrLogFlags;
	static OptionBits		fErrLogFlags;
	static OptionBits		fDbgLogFlags;
	static OptionBits		fInfoLogFlags;
	static CLog			   *fServerLog;
	static CLog			   *fErrorLog;
	static CLog			   *fDebugLog;
	static CLog			   *fInfoLog;
	static CString		   *fServerLogName;
	static CString		   *fErrorLogName;
	static CString		   *fDebugLogName;
	static CString		   *fInfoLogName;

	// Instance data
	CFileSpec			fFileSpec;		// Necessary for file moves after resequencing
	CFile			   *fFile;
	uInt32				fFlags;
	uInt32				fMaxLength;
	uInt32				fOffset;
	uInt32				fLength;
	AppendHook			fHooks[ 8 ];

	DSMutexSemaphore	   *fLock;

};


//-----------------------------------------------------------------------------
//	* Preprocessor Macros
//-----------------------------------------------------------------------------
#pragma mark **** CLog Convenience Macros ****

// assertion macros.
// Moved MailAssert from CThread.h

#if (defined(APP_DEBUG) || defined(DEBUG))

#define MyAssert(condition) \
			if (!(condition)) { ASSERTLOG(kLogEverything,"***** ASSERTION FAILED! *****"); }

#define SignalIf_(test) \
			if (test) { ASSERTLOG(kLogEverything, "***** Signal a failed test! *****"); }

#else		 // (defined(APP_DEBUG) || defined(DEBUG))

#define MyAssert(condtion)
#define SignalIf_(test)

#endif	// (defined(APP_DEBUG) || defined(DEBUG))


inline void SrvrLog ( long lType, const char *szpPattern, ... )
{
	if ( CLog::GetServerLog() != nil )
	{
		va_list	args;
		va_start( args, szpPattern );
		if ( CLog::IsLogging( keServerLog, lType ) )
		{
			CLog::GetServerLog()->Append( CString( szpPattern, args ) );
		}
	}
} // SrvrLog


inline void ErrLog ( long lType, const char *szpPattern, ... )
{
	if ( CLog::GetErrorLog() == nil )
	{
		CLog::StartErrorLog(); //create error log on demand
	}
	if ( CLog::GetErrorLog() != nil )
	{
		va_list	args;
		va_start( args, szpPattern );
		if ( CLog::IsLogging( keErrorLog, lType ) )
		{
			CLog::GetErrorLog()->Append( CString( szpPattern, args ) );
		}
	}
} // ErrLog


inline void DbgLog ( long lType, const char *szpPattern, ... )
{
	if ( CLog::GetDebugLog() != nil )
	{
		// Just in case it was deleted while I was waiting for it
		if ( CLog::GetDebugLog() != nil )
		{
			va_list	args;
			va_start( args, szpPattern );
			if ( CLog::IsLogging( keDebugLog, lType ) )
			{
				CLog::GetDebugLog()->Append( CString( szpPattern, args ) );
			}
		}
	}
} // DbgLog


inline void InfoLog ( long lType, const char *szpPattern, ... )
{
	if ( CLog::GetInfoLog() != nil )
	{
		// Just in case it was deleted while I was waiting for it
		if ( CLog::GetInfoLog() != nil )
		{
			va_list	args;
			va_start( args, szpPattern );
			if ( CLog::IsLogging( keInfoLog, lType ) )
			{
				CLog::GetInfoLog()->Append( CString( szpPattern, args ) );
			}
		}
	}
} // GetInfoLog


// Server log
#define SRVRLOG( flg, p0 )	::SrvrLog( flg, p0 );
#define SRVRLOG1( flg, p0, p1 )	::SrvrLog( flg, p0, p1 );
#define SRVRLOG2( flg, p0, p1, p2 )	::SrvrLog( flg, p0, p1, p2 );
#define SRVRLOG3( flg, p0, p1, p2, p3 )	::SrvrLog( flg, p0, p1, p2, p3 );
#define SRVRLOG4( flg, p0, p1, p2, p3, p4 )	::SrvrLog( flg, p0, p1, p2, p3, p4 );
#define SRVRLOG5( flg, p0, p1, p2, p3, p4, p5 )	::SrvrLog( flg, p0, p1, p2, p3, p4, p5 );
#define SRVRLOG6( flg, p0, p1, p2, p3, p4, p5, p6 )	::SrvrLog( flg, p0, p1, p2, p3, p4, p5, p6 );
#define SRVRLOG7( flg, p0, p1, p2, p3, p4, p5, p6, p7 )	::SrvrLog( flg, p0, p1, p2, p3, p4, p5, p6, p7 );
#define SRVRLOG8( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 )	::SrvrLog( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 );

// Error log
#define ERRORLOG( flg, p0 )	::ErrLog( flg, p0 );
#define ERRORLOG1( flg, p0, p1 )	::ErrLog( flg, p0, p1 );
#define ERRORLOG2( flg, p0, p1, p2 )	::ErrLog( flg, p0, p1, p2 );
#define ERRORLOG3( flg, p0, p1, p2, p3 )	::ErrLog( flg, p0, p1, p2, p3 );
#define ERRORLOG4( flg, p0, p1, p2, p3, p4 )	::ErrLog( flg, p0, p1, p2, p3, p4 );
#define ERRORLOG5( flg, p0, p1, p2, p3, p4, p5 )	::ErrLog( flg, p0, p1, p2, p3, p4, p5 );
#define ERRORLOG6( flg, p0, p1, p2, p3, p4, p5, p6 )	::ErrLog( flg, p0, p1, p2, p3, p4, p5, p6 );
#define ERRORLOG7( flg, p0, p1, p2, p3, p4, p5, p6, p7 )	::ErrLog( flg, p0, p1, p2, p3, p4, p5, p6, p7 );
#define ERRORLOG8( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 )	::ErrLog( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 );

// Debug log
#define DBGLOG( flg, p0 )	::DbgLog( flg, p0 );
#define DBGLOG1( flg, p0, p1 )	::DbgLog( flg, p0, p1 );
#define DBGLOG2( flg, p0, p1, p2 )	::DbgLog( flg, p0, p1, p2 );
#define DBGLOG3( flg, p0, p1, p2, p3 )	::DbgLog( flg, p0, p1, p2, p3 );
#define DBGLOG4( flg, p0, p1, p2, p3, p4 )	::DbgLog( flg, p0, p1, p2, p3, p4 );
#define DBGLOG5( flg, p0, p1, p2, p3, p4, p5 )	::DbgLog( flg, p0, p1, p2, p3, p4, p5 );
#define DBGLOG6( flg, p0, p1, p2, p3, p4, p5, p6 )	::DbgLog( flg, p0, p1, p2, p3, p4, p5, p6 );
#define DBGLOG7( flg, p0, p1, p2, p3, p4, p5, p6, p7 )	::DbgLog( flg, p0, p1, p2, p3, p4, p5, p6, p7 );
#define DBGLOG8( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 )	::DbgLog( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 );

#define INFOLOG( flg, p0 )	::InfoLog( flg, p0 );
#define INFOLOG1( flg, p0, p1 )	::InfoLog( flg, p0, p1 );
#define INFOLOG2( flg, p0, p1, p2 )	::InfoLog( flg, p0, p1, p2 );
#define INFOLOG3( flg, p0, p1, p2, p3 )	::InfoLog( flg, p0, p1, p2, p3 );
#define INFOLOG4( flg, p0, p1, p2, p3, p4 )	::InfoLog( flg, p0, p1, p2, p3, p4 );
#define INFOLOG5( flg, p0, p1, p2, p3, p4, p5 )	::InfoLog( flg, p0, p1, p2, p3, p4, p5 );
#define INFOLOG6( flg, p0, p1, p2, p3, p4, p5, p6 )	::InfoLog( flg, p0, p1, p2, p3, p4, p5, p6 );
#define INFOLOG7( flg, p0, p1, p2, p3, p4, p5, p6, p7 )	::InfoLog( flg, p0, p1, p2, p3, p4, p5, p6, p7 );
#define INFOLOG8( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 )	::InfoLog( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 );

#ifdef XXXX

#define INFOLOG( flg, p0 )
#define INFOLOG1( flg, p0, p1 )
#define INFOLOG2( flg, p0, p1, p2 )
#define INFOLOG3( flg, p0, p1, p2, p3 )
#define INFOLOG4( flg, p0, p1, p2, p3, p4 )
#define INFOLOG5( flg, p0, p1, p2, p3, p4, p5 )
#define INFOLOG6( flg, p0, p1, p2, p3, p4, p5, p6 )
#define INFOLOG7( flg, p0, p1, p2, p3, p4, p5, p6, p7 )
#define INFOLOG8( flg, p0, p1, p2, p3, p4, p5, p6, p7, p8 )

#endif

#define LogAssert_(test)													\
	do {																	\
		if ( !(test) )														\
		{																	\
			DBGLOG2( kLogAssert, "Assert in %s at %d", __FILE__, __LINE__ );	\
		}																	\
	} while (false)

#define LogErrAssert_(test,err)																	\
	do {																						\
		if ( !(test) )																			\
		{																						\
			DBGLOG3( kLogAssert, "Assert in %s at %d with error = %d.", __FILE__, __LINE__, err );	\
		}																						\
	} while (false)

#endif	// __CLog_h__
