//----------------------------------------------------------------------------------
// <copyright file="LqrSMACStdOutLogger.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>
//   Implementation of StdOutLogger, a Logger-derived class that implements logging
//   to a file descriptor or a pipe.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#include "LqrSMACStdOutLogger.h"
#include "LqrSMACStdOutLoggerSettings.h"
#include "LqrSMACException.h"
#include "LqrSMACPrintfBuffer.h"
#include <sstream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include "nsextend.h"

using namespace std;
using namespace boost;
//using namespace boost::posix_time;

namespace LiquidRobotics {
namespace SMACFramework {

StdOutLogger::StdOutLogger(const char* processName_, int fd_, LogType level_)
: level(level_)
, fd(fd_)
, pipefd(-1)
, processName(processName_)
{
	hostName = GetHostName();
}

StdOutLogger::StdOutLogger(const char* processName_, const char* pipeName_, LogType level_)
: level(level_)
, fd(-1)
, pipeName(pipeName_)
, pipefd(-1)
, processName(processName_)
{
	hostName = GetHostName();
}

StdOutLogger::StdOutLogger(const StdOutLoggerSettings& _settings)
: level(_settings.GetLogLevel())
, fd(_settings.IsFileDescriptorBased() ? _settings.GetFileDescriptor() : -1)
, pipeName(_settings.IsPipeBased() ? _settings.GetPipeName() : "")
, pipefd(-1)
, processName(_settings.GetPipeName())
{
	hostName = GetHostName();
}

StdOutLogger::~StdOutLogger()
{
	if (pipefd != -1)
		close(pipefd);
}

bool StdOutLogger::IsOpen(const LogType logType) const
{
	return (level != LT_NONE) && (logType <= level) && IsConfigured();
}

bool StdOutLogger::IsConfigured() const
{
	return (fd != -1) || !!pipeName.length();
}

void StdOutLogger::Write(const char* data)
{
	Lock lock_(*this);			// synchronize this object

	// if the log parameters are reasonable and the data is present and not empty ...
	if (IsConfigured() && data && *data)
	{
		int err;				// holds value of errno

		// if a generic file descriptor was passed in, try to write
		// to it and don't care if it fails
		if (fd != -1)
			WriteRetry(fd, data, strlen(data), err);

		// otherwise (must be a pipe) ...
		else
		{
			// if the pipe handle is closed ...
			if (pipefd == -1)
			{
				// attempt to open the pipe, but don't wait for a reader to have
				// the pipe open
				do
				{
					pipefd = open(pipeName.c_str(), O_WRONLY | O_NDELAY);
					err = errno;
				} while((pipefd == -1) && (err == EINTR));

				// if the pipe is now open, try to change it back to a blocking pipe
				if (pipefd != -1)
				{
					int status;

					do
					{
						status = fcntl(pipefd, F_GETFL);
						err = errno;
					} while((status == -1) && (err == EINTR));

					if (status != -1)
					{
						int status2;

						do
						{
							status2 = fcntl(pipefd, F_SETFL, status & ~O_NDELAY);
							err = errno;
						} while((status2 == -1) && (err == EINTR));
					}
				}
			}

			// now if the pipe is open, write the data to the pipe; close the
			// pipe if there was an error
			if (pipefd != -1)
			{
				WriteRetry(pipefd, (const void*) data, strlen(data), err);

				if (err != 0)
				{
					close(pipefd);
					pipefd = -1;
				}
			}
		}
	}
}

int StdOutLogger::WriteRetry(int fd, const void* data, int dataLen, int& err)
{
	int status = 0;

	err = 0;

	if (dataLen > 0)
	{
		do
		{
			status = write(fd, data, dataLen);
			err = (status == -1) ? errno : 0;
		} while(err == EINTR);
	}

	return status;
}

void StdOutLogger::Write(const LogType logType, const char* data)
{
	if (data)
	{
		stringstream sstm;
		sstm << GetLineHeader();

		if (logPrefix.length())
			sstm << " " << logPrefix << ":";

		sstm << " " << data << endl;

		reportSignal(*this, logType, sstm.str());

		if (IsOpen(logType))
			Write(sstm.str().c_str());
	}
}

void StdOutLogger::Format(const LogType logType, const char* format, ...)
{
	if (IsOpen(logType))
	{
		va_list argptr;

		va_start(argptr, format);
		FormatV(logType, format, argptr);
		va_end(argptr);
	}
}

void StdOutLogger::FormatV(const LogType logType, const char* format, va_list argptr)
{
	if (IsOpen(logType))
		Write(PrintfBuffer(format, argptr).AsPtr());
}

std::string StdOutLogger::GetTimestamp() const
{
#if 0
	using namespace boost::gregorian;

	stringstream result;
	ptime currentTime = microsec_clock::local_time();
	greg_year_month_day ymd = currentTime.date().year_month_day();
	ptime::time_duration_type tod = currentTime.time_of_day();

	result << ultos(ymd.year,10,4) << "/" << ultos(ymd.month.as_number(),10,2) << "/" << ultos(ymd.day,10,2) << " "
	       << ultos(tod.hours(),10,2) << ":" << ultos(tod.minutes(),10,2) << ":" << ultos(tod.seconds(),10,2) << "." << ultos(tod.total_milliseconds() % 1000000,10,6);

	//string returnValue = to_simple_string(currentTime);
	string returnValue = result.str();
	return returnValue;
#else
	stringstream result;

	timeval tv;
	if (gettimeofday(&tv, NULL) == -1)
		tv.tv_sec = tv.tv_usec = 0;

	tm comp;
	localtime_r(&tv.tv_sec, &comp);

	result << ultos(comp.tm_year+1900,10,4) << "/" << ultos(comp.tm_mon+1,10,2) << "/" << ultos(comp.tm_mday,10,2) << " "
	       << ultos(comp.tm_hour,10,2) << ":" << ultos(comp.tm_min,10,2) << ":" << ultos(comp.tm_sec,10,2)
	       << "." << ultos(tv.tv_usec,10,6);

	string returnValue = result.str();
	return returnValue;
#endif
}

std::string StdOutLogger::GetHostName() const
{
	char buffer[128];

	if (gethostname(buffer, sizeof(buffer)-1) == 0)
		return buffer;
	else
		return "-";
}

std::string StdOutLogger::GetLineHeader() const
{
	stringstream sstm;
	sstm << GetTimestamp() << " " << hostName << " " << processName << "[" << getpid() << "]";

	return sstm.str();
}

bool StdOutLogger::LogEnabled(const LogType logType) const
{
	return IsOpen(logType);
}

void StdOutLogger::SetLevel(LogType newLevel)
{
	level = newLevel;
}

}
}
