//-----------------------------------------------------------------------------------------------
// <copyright file="LqrSMACLogger.cpp" 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>
// Implements the LiquidRobotics::SMACFramework::Logger class, which is a base class for classes
// that implement data logs.
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------------------

#include "LqrSMACLogger.h"
#include <sstream>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace boost;

namespace LiquidRobotics {
namespace SMACFramework {

const struct { string s; Logger::LogType lt; } logTypeTable[] =
{
		{ "LT_NONE",		Logger::LT_NONE },
		{ "LT_EMERGENCY",	Logger::LT_EMERGENCY },
		{ "LT_ALERT", 		Logger::LT_ALERT },
		{ "LT_CRITICAL", 	Logger::LT_CRITICAL },
		{ "LT_ERROR", 		Logger::LT_ERROR },
		{ "LT_WARNING", 	Logger::LT_WARNING },
		{ "LT_NOTICE", 		Logger::LT_NOTICE },
		{ "LT_INFO", 		Logger::LT_INFO },
		{ "LT_DEBUG", 		Logger::LT_DEBUG }
};

const unsigned numLogTypeTableItems = sizeof(logTypeTable)/sizeof(logTypeTable[0]);

void Logger::FromString(const string& inVal, LogType& outVal)
{
	for (unsigned idx = 0; idx < numLogTypeTableItems; ++idx)
		if (boost::iequals(logTypeTable[idx].s, inVal))
		{
			outVal = logTypeTable[idx].lt;
			return;
		}

	stringstream msg;
	msg << "No match for log type '" << inVal << "'";

	throw invalid_argument(msg.str());
}

void Logger::ToString(const LogType& inVal, string& outVal)
{
	for (unsigned idx = 0; idx < numLogTypeTableItems; ++idx)
		if (logTypeTable[idx].lt == inVal)
		{
			outVal = logTypeTable[idx].s;
			return;
		}

	stringstream msg;
	msg << "Value " << ((int) inVal) << " is not a valid log type";

	throw invalid_argument(msg.str());
}


Logger::Logger()
: EmergencyContainer(*this, LT_EMERGENCY)
, AlertContainer(*this, LT_ALERT)
, CriticalContainer(*this, LT_CRITICAL)
, ErrorContainer(*this, LT_ERROR)
, WarningContainer(*this, LT_WARNING)
, NoticeContainer(*this, LT_NOTICE)
, InfoContainer(*this, LT_INFO)
, DebugContainer(*this, LT_DEBUG)
, Emergency(EmergencyContainer)
, Alert(AlertContainer)
, Critical(CriticalContainer)
, Error(ErrorContainer)
, Warning(WarningContainer)
, Notice(NoticeContainer)
, Info(InfoContainer)
, Debug(DebugContainer)
{
}

Logger::~Logger()
{
}

const char* Logger::LogPrefix() const
{
	return logPrefix.c_str();
}

void Logger::LogPrefix(const char* newValue)
{
	Lock lock_(*this);

	if (newValue)
		logPrefix = newValue;
	else
		logPrefix.clear();
}

class StringBuf : public Logger::IStringBuf
{
public:

	~StringBuf()
	{
	}

	bool IsEmpty() const
	{
		const streampos offs = const_cast<stringstream&>(buf).tellp();
		return offs == 0;
	}

	Logger::string ReadAndReset()
	{
		string result = buf.str();

		buf.str("");
		return result;
	}

	void Store(char data)
	{
		buf << data;
	}

private:

	stringstream buf;
};

Logger::LogContainer::LogContainer(Logger& _logger, LogType _lt)
: logger(_logger)
, lt(_lt)
, cr(false)
{
	stringbuf.reset(new StringBuf);
}

void Logger::LogContainer::Write(const char_type* pData, streamsize n)
{
	Logger::Lock lock(logger);

	while(n--)	Process(*pData++);
}

void Logger::LogContainer::Process(const char_type c)
{
	Logger::Lock lock(logger);
	bool nl = false;

	if (c == 13)
	{
		cr = true;
		nl = true;
	}
	else if (c == 10)
	{
		if (cr)
			cr = false;
		else
			nl = true;
	}
	else
	{
		cr = false;
		stringbuf->Store(c);
	}

	const bool isEmpty = stringbuf->IsEmpty();

	if (nl && !isEmpty)
	{
		string lineData = stringbuf->ReadAndReset();

		logger.Write(lt, lineData.c_str());
	}
}

Logger::ScopedConnectionPtr Logger::SubscribeReportSignal(ReportSignalType::slot_type subscriberCallback)
{
	ScopedConnectionPtr result;
	result.reset(new ScopedConnection(reportSignal.connect(subscriberCallback)));
	return result;
}

}
}
