//--------------------------------------------------------------------------------------------------
// <copyright file="LqrSMACLogger.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// Declares the LiquidRobotics::SMACFramework::Logger class, which is a base class for classes that
// implement data logs.
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------------------------

#ifndef __LqrSMACLogger_H__
#define __LqrSMACLogger_H__

#include "LqrSMACSharedLib.h"
#include "LqrSMACLockableConcept.h"
#include <stdarg.h>
#include <algorithm>
#include <iosfwd>
#include <sstream>
#include <string>
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/utility.hpp>

namespace LiquidRobotics {
namespace SMACFramework {

// Logger
//
// This class is a base for derivations that implement concrete logging
// by implementing the abstract methods:
//
//		void Write(const LogType logType, const char* data);
//		void Format(const LogType logType, const char* format, ...);
//		void FormatV(const LogType logType, const char* format, va_list argptr);
//
// After construction, logging can be done via the base class by calling
// one of the three abstract methods or by using any of the log type
// specific public members:
//
//		LogStream Emergency;
//		LogStream Alert;
//		LogStream Critical;
//		LogStream Error;
//		LogStream Warning;
//		LogStream Notice;
//		LogStream Info;
//		LogStream Debug;
//
// For example:
//
//		int eventNumber = ...
//		Logger& logger = ...
//
//		logger.Notice << "Event #" << eventNumber << " posted" << endl;
//
// Adding "endl" causes the data to be posted (until then, data accumulates.)
//
//
// ** Multi-threaded considerations **
//
// The abstract methods are expected to execute atomically by locking the
// the mutex (mtx) directly, by constructing a Lock object or by calling
// the lock() and unlock() members directly.
//
// Callers must lock the logger object by constructing a Lock when using
// streaming mode since multiple operations are performed when assembling
// a line:
//
//		int eventNumber = ...
//		Logger& logger = ...
//
//		{
//			Logger::Lock lock(logger);
//			logger.Notice << "Event #" << eventNumber << " posted" << endl;
//		}
//
class DummyLogger;
typedef boost::shared_ptr<DummyLogger> DummyLoggerPtr;

#define SMF_LOGGER_TYPEDEFS() \
	typedef LiquidRobotics::SMACFramework::Logger Logger

class SHARED_LIB_PUBLIC Logger
: public LockableConceptMutex
, private boost::noncopyable
{
public:

	typedef boost::shared_ptr<Logger> Ptr;
	typedef boost::weak_ptr<Logger> WPtr;
	typedef std::string string;

	// This enumeration is patterned off of the syslog standards;
	// the lower the value, the more urgent the log item is
	enum LogType
	{
		LT_NONE,					// logging is disabled
		LT_EMERGENCY,				// system is not usable
		LT_ALERT,					// immediate action needed
		LT_CRITICAL,				// critical condition
		LT_ERROR,					// error condition
		LT_WARNING,					// warning condition
		LT_NOTICE,					// normal but significant condition
		LT_INFO,					// information condition
		LT_DEBUG					// debug condition
	};

	static void FromString(const string& inVal, LogType& outVal);
	static void ToString(const LogType& inVal, string& outVal);

	// IStringBuf
	//
	// A string buffer used to temporarily store line data before
	// displaying it
	class IStringBuf
	{
	public:

		virtual ~IStringBuf() { }
		virtual bool IsEmpty() const = 0;
		virtual string ReadAndReset() = 0;
		virtual void Store(char data) = 0;
	};

	typedef boost::shared_ptr<IStringBuf> IStringBufPtr;

	// LogContainer
	//
	// The log container is used as part of streaming log output;
	// it accumulates output and reports it to the logger object
	// at the end of each line.  It is constructed using the log type
	// and a reference to the logger.
	class LogContainer
	{
	public:

		typedef char char_type;

		LogContainer(Logger& _logger, LogType _lt);

		void Write(const char_type* pData, std::streamsize n);

	private:

		void Process(const char_type c);

		Logger& logger;
		const LogType lt;
		bool cr;
		IStringBufPtr stringbuf;
	};

	// Sink
	//
	// This template class is part of the boost pattern for
	// output streaming; it provides a standardized interface
	// to the container (T)
	template<typename T, typename C = char>
	class Sink
	{
	public:

		typedef C char_type;
		typedef boost::iostreams::sink_tag category;

		Sink(T& container) : container_(container) { }

		std::streamsize write(const char_type* s, std::streamsize n)
		{
			container_.Write(s, n);
			return n;
		}

		T& container() { return container_; }

	private:

		T& container_;
	};

	typedef Sink<LogContainer> LogSink;							// concrete Sink instance used with LogStream
	typedef boost::iostreams::stream<LogSink> LogStream;		// a stream class that outputs to a logger

	class Locked
	{
	public:

		Locked(Logger::Ptr ptr);
		Locked(Logger& ref);
		~Locked();

		Logger& operator*();
		Logger* operator->();

	private:

		Logger* ptrRef;
		Logger::Ptr ptrCopy;
		LockPtr lockPtr;
		DummyLoggerPtr dummyLog;
	};

	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef boost::signals2::signal<void (Logger& sender, LogType logType, const string& data)> ReportSignalType;

	Logger();
	virtual ~Logger();

	virtual void Write(const LogType logType, const char* data) = 0;
	virtual void Format(const LogType logType, const char* format, ...) = 0;
	virtual void FormatV(const LogType logType, const char* format, va_list argptr) = 0;
	virtual bool LogEnabled(const LogType logType) const = 0;
	virtual void SetLevel(LogType newLevel) = 0;
	virtual ScopedConnectionPtr SubscribeReportSignal(ReportSignalType::slot_type subscriberCallback);

	virtual const char* LogPrefix() const;
	virtual void LogPrefix(const char* newValue);

protected:

	// containers for each logging type
	LogContainer EmergencyContainer;
	LogContainer AlertContainer;
	LogContainer CriticalContainer;
	LogContainer ErrorContainer;
	LogContainer WarningContainer;
	LogContainer NoticeContainer;
	LogContainer InfoContainer;
	LogContainer DebugContainer;

public:

	LogStream Emergency;				// streams to logger at LT_EMERGENCY priority
	LogStream Alert;					// streams to logger at LT_ALERT priority
	LogStream Critical;					// streams to logger at LT_CRITICAL priority
	LogStream Error;					// streams to logger at LT_ERROR priority
	LogStream Warning;					// streams to logger at LT_WARNING priority
	LogStream Notice;					// streams to logger at LT_NOTICE priority
	LogStream Info;						// streams to logger at LT_INFO priority
	LogStream Debug;					// streams to logger at LT_DEBUG priority

protected:

	std::string logPrefix;				// the prefix configured for each line
	ReportSignalType reportSignal;		// used to generate callback events
};

}
}

#endif
