//
//  Copyright (C) 2009-2012 Artyom Beilis (Tonkikh)
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOSTER_LOGGER_H
#define BOOSTER_LOGGER_H

#include <booster/config.h>
#include <iosfwd>
#include <booster/auto_ptr_inc.h>
#include <string>
#include <booster/copy_ptr.h>
#include <booster/hold_ptr.h>
#include <booster/noncopyable.h>

/*! \file */ 


namespace booster {

template<typename T>
class shared_ptr;
template<typename T>
class weak_ptr;

///
/// \brief This namespace includes Booster.Log related classes
///

namespace log {
	
	///
	/// This enum defined severity level of logged messages
	/// 
	typedef enum {
		emergency	= 0,
		alert		= 10,
		critical	= 20,
		error		= 30,
		warning		= 40,
		notice		= 50,
		info		= 60,
		debug		= 70,
		all		= 100
	} level_type;

	
	///
	/// \brief This class represents a single message that should be written to log
	///
	/// Note, it is some generalization of data that allows you to format any generic
	/// data, it generally used by logging sinks rather then by end users
	///
	class BOOSTER_API message {
	public:
		///
		/// Create message with severity level \a l, sent from module \a m in file
		/// \a name in line \a line
		///
		message(level_type l,char const *m,char const *name,int line);
	
		///
		/// Default constructable
		///	
		message();

		~message();
		/// Copy message
		message(message &);
		/// Assign the  message
		message &operator=(message &);

		///
		/// Get the severity level for the message
		///
		level_type level() const;
		///
		/// Get the module name
		///
		char const *module() const;
		///
		/// Get the file name
		///
		char const *file_name() const;
		///
		/// Get the number of line in the file
		///
		int file_line() const;
		///
		/// Get the actual message that should be written
		///
		std::string log_message() const;

		///
		/// Get the output stream that consumes the message
		///
		std::ostream &out();
	private:
		level_type level_;
		char const *module_;
		char const *file_name_;
		int file_line_;

		std::auto_ptr<std::ostringstream> message_;

		struct data;
		copy_ptr<data> d;
	};

	///
	/// \brief This is the abstract interface to general sink - the consumer
	/// of the logged messages
	///
	class BOOSTER_API sink : public noncopyable {
	public:
		///
		/// Send the message \a m to log
		///
		/// Note: it does not have to be thread safe - in terms that only
		/// one message is sent each time and it is promised that are 
		/// not sent in parallel
		///
		virtual void log(message const &m) = 0;
		virtual ~sink() {}
	};

	///
	/// \brief This is the central class that manages all logging operations.
	///
	/// This is singleton, you access it only via instance() static member function.
	///
	/// This class is thread safe
	///
	class BOOSTER_API logger : public noncopyable {
	public:
		///
		/// Get the instance of the logger
		///
		static logger &instance();
		
		///
		/// Test if the message with severity level \a level of module \a module should
		/// be logged. This is generally called before preparing a message in order
		/// to prevent logging messages that shouldn't be logged
		///
		bool should_be_logged(level_type level,char const *module);
		
		///
		/// Set specific severity level \a level for a module \a module. It would
		/// be logged with different logging level then others.
		///
		/// Note: \a module string should remain valid all the time this log level
		/// is used. It is better to use static strings for these purposes
		///
		void set_log_level(level_type level,char const *module);

		///
		/// Reset \a module's logging severity to default
		///
		void reset_log_level(char const *module);
		///
		/// Set default logging level severity
		///
		void set_default_level(level_type level);

		///
		/// Add new logging sink - the target that receives messages.
		/// It is defined by it's pointer.
		///
		/// If you plan to remove it, keep the pointer on such logger.
		///
		void add_sink(shared_ptr<sink> const &s);

		///
		/// Remove a logging sink using its pointer. Note it is enough to keep
		/// weak pointer on it allowing the logger to destroy the sink.
		///
		void remove_sink(weak_ptr<sink> const &s);

		///
		/// Remove all logging sinks from the logger
		///
		void remove_all_sinks();

		///
		/// Send a message to the log, note, the message is sent regardless of its
		/// severity, use should_be_logged before you create the message
		///
		void log(message const &);

		///
		/// Get convert the severity level to string
		///
		static char const *level_to_string(level_type level);
		///
		/// Convert string to severity level, if the string is invalid, std::invalid_argument is thrown
		///
		static level_type string_to_level(std::string const &);

	private:
		
		struct entry {
			char const *module;
			level_type level;
		};

		static const int max_entries_size_ = 1024;
		level_type default_level_;
		entry entries_[max_entries_size_];
		int entries_size_;
		
		struct data;
		hold_ptr<data> d;

		logger();
		~logger();
	};

	///
	/// \brief This namespace includes various output devices (sinks) for logger.
	///
	/// It includes several standard devices that can be used in most applications
	///
	namespace sinks {
		
		///
		/// Convert a logging message to a string with all the data that can be
		/// written to file, the displayed time is local time
		///
		BOOSTER_API std::string format_plain_text_message(message const &msg);

		///
		/// Convert a logging message to a string with all the data that can be
		/// written to file, the displayed time is GMT+\a timezone_offset.
		/// timezone_offset should be represented in seconds, for example
		/// for timezone GMT+2:00 it should be 7200
		///
		BOOSTER_API std::string format_plain_text_message_tz(message const &msg,int timezone_offset = 0);

		///
		/// \brief stderr based sink - sends messages to standard error output
		///
		class BOOSTER_API standard_error : public sink {
		public:
			standard_error();
			virtual void log(message const &);
			virtual ~standard_error();
		private:
			struct data;
			hold_ptr<data> d;
		};
	
		///
		/// \brief log sink for a generic std::ostream
		///
		/// \note \a s should be valid as long as the object exists
		///	
		/// \ver{v1_2}
		class BOOSTER_API stream : public sink {
		public:
			///
			/// Create a new sink, \a s output stream
			///
			stream(std::ostream &s);
			///
			/// Log the message to the file
			///
			virtual void log(message const &msg);
			virtual ~stream();
		private:
			std::ostream *out_;
			struct data;
			hold_ptr<data> d;
		};
		
		///
		/// \brief log file based sink - sends messages to log file
		///
		class BOOSTER_API file : public sink {
		public:
			
			///
			/// Flag that can be passed to constructor that specifies that
			/// the log should be appended to the existing file
			///
			/// \ver{v1_2}
			static const int app = -1;

			///
			/// Creates new object but does not open a file
			///
			file();
			
			///
			/// Creates new file sink named \a file_name, if max_files = app,
			/// then no new files created but rather the log is appended to the
			/// existing file
			///
			/// \ver{v1_2}
			file(std::string const &file_name,int max_files = 0);
			
			virtual ~file();

			///
			/// Open log file
			///
			void open(std::string file_name);
			///
			/// Enable file rotation and set the maximal number of files that should be kept.
			/// 
			/// Each time the log opened, the old files are renamed, if there are more files 
			/// then \a limit, the oldest is removed
			///
			void max_files(unsigned limit);
			///
			/// Append to output file rather then create new one.
			///
			void append();
			
			///
			/// Set the time-zone name that should be used in the message.
			///
			/// It should have a format GMT+XX:YY like "GMT+2:00" or "GMT-3".
			/// "GMT" can be used as well
			///
			/// If name is empty local time is used which is the default
			///
			void set_timezone(std::string const &name);

			/// Send message to the log
			virtual void log(message const &);
		private:

			void shift(std::string const &base);
			std::string format_file(std::string const &,int);

			unsigned max_files_;
			BOOSTER_UNUSED_MEMBER size_t max_size_;
			BOOSTER_UNUSED_MEMBER size_t current_size_;
			bool opened_;
			bool append_;
			bool use_local_time_;
			int tz_offset_;
			
			struct data;
			hold_ptr<data> d;
		};

		#ifdef BOOSTER_POSIX
		///
		/// \brief POSIX syslog sink
		///
		/// Available only on POSIX platforms
		///
		class BOOSTER_API syslog : public sink {
		public:
			///
			/// Create a new logger. Note: it does not call openlog, if you want
			/// to provide non-default parameters, you need to call it on your own
			///
			syslog();

			///
			/// Create a new logger and call openlog with  id, opts and facility 
			///
			syslog(std::string const &id,int opts,int facility = 0);
			///
			/// Create a new logger and call openlog with  NULL id , opts and facility 
			///
			syslog(int opts,int facility = 0);
			///
			/// Send the message to the log
			///
			virtual void log(message const &);
			virtual ~syslog();
		private:
			struct data;
			hold_ptr<data> d;
		};
		#endif

	} // sinks


	///
	/// \def BOOSTER_LOG(level,module)
	///
	/// \brief Log a message with severity \a level of the \a module to the log device if applicable.
	///
	/// Notes:
	///
	/// - it first checks of the message should be logged at all, and if not, all the rest is not called
	/// - the message is logged in the destruction of the output stream object
	/// - the parameter \a level should be given as is "warning" or "debug" the namespace flags would
	///   be added withing macro
	/// - It may be more convenient to use BOOSTER_WARNING or BOOSTER_DEBUG macros
	///
	/// Example:
	///
	/// \code
	///   BOOSTER_LOG(warning,"file_system") << "Failed to open file name " << name;
	///   BOOSTER_CRITICAL("core") << "Failed to allocate memory";
	/// \endcode   
	///
	#define BOOSTER_LOG(level,module) 								\
		::booster::log::logger::instance().should_be_logged(::booster::log::level,module)	\
		&& ::booster::log::message(::booster::log::level,module,__FILE__,__LINE__).out()			

	
	/// Same as BOOSTER_LOG(emergency,m)
	#define BOOSTER_EMERG(m)	BOOSTER_LOG(emergency,m)
	/// Same as BOOSTER_LOG(alert,m)
	#define BOOSTER_ALERT(m)	BOOSTER_LOG(alert,m)
	/// Same as BOOSTER_LOG(critical,m)
	#define BOOSTER_CRITICAL(m)	BOOSTER_LOG(critical,m)
	/// Same as BOOSTER_LOG(error,m)
	#define BOOSTER_ERROR(m)	BOOSTER_LOG(error,m)
	/// Same as BOOSTER_LOG(warning,m)
	#define BOOSTER_WARNING(m)	BOOSTER_LOG(warning,m)
	/// Same as BOOSTER_LOG(notice,m)
	#define BOOSTER_NOTICE(m)	BOOSTER_LOG(notice,m)
	/// Same as BOOSTER_LOG(info,m)
	#define BOOSTER_INFO(m)	BOOSTER_LOG(info,m)
	/// Same as BOOSTER_LOG(debug,m)
	#define BOOSTER_DEBUG(m)	BOOSTER_LOG(debug,m)

} // log

} // booster

#endif
