//-----------------------------------------------------------------------------------------------
// <copyright file="LqrSMACSemaphore.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 the Semaphore class, which wraps a Linux semaphore and provides support for
//  the Boost Lock concept
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------------------

#include "LqrSMACSemaphore.h"
#include "LqrSMACGetErrorDescription.h"
#include <iostream>
#include <string>
#include <sstream>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace SMACFramework {

IMPLEMENT_INNER_EXCEPTION(Semaphore, SystemError, std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(Semaphore, Timeout, std::runtime_error);

Semaphore::Semaphore(unsigned value, bool shared)
: pSem(SEM_FAILED)
, semCreated(false)
{
	memset(&sem, '\0', sizeof(sem));

	int status;

	do
	{
		status = sem_init(&sem, shared ? 1 : 0, value);
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
		DoThrow(errno, "sem_init");

	semCreated = true;
}

Semaphore::Semaphore(const string& semName, bool existing, bool overwriteExisting, unsigned value, mode_t mode)
: pSem(SEM_FAILED)
, semCreated(false)
{
	memset(&sem, '\0', sizeof(sem));

	do
	{
		int oflag = 0;

		if (!existing)							oflag |= O_CREAT;
		if (!existing && !overwriteExisting)	oflag |= O_EXCL;

		pSem = sem_open(semName.c_str(), oflag, mode, value);
	} while((pSem == SEM_FAILED) && (errno == EINTR));

	if (pSem == SEM_FAILED)
		DoThrow(errno, "sem_open");
}

Semaphore::~Semaphore()
{
	if (semCreated)
		sem_destroy(&sem);

	if (pSem != SEM_FAILED)
		sem_close(pSem);
}

sem_t* Semaphore::GetSemPtr()
{
	if (pSem != SEM_FAILED)
		return pSem;

	else if (semCreated)
		return &sem;

	else
		throw runtime_error("Semaphore::GetSem: neither sem nor pSem has been successfully initialized!");
}

void Semaphore::DeleteNamedInstance(const string& instanceName)
{
	int status;

	do
	{
		status = sem_unlink(instanceName.c_str());
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
		DoThrow(errno, "sem_unlink");
}

int Semaphore::GetCount()
{
	int status;
	int sval = -1;

	do
	{
		status = sem_getvalue(GetSemPtr(), &sval);
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
		DoThrow(errno, "sem_getvalue");

	return sval;
}

void Semaphore::SetCount(int newCount)
{
	if (newCount < 0)
		throw invalid_argument("Semaphore::SetCount() cannot take a newCount value less than 0");

	int currentCount;

	do
	{
		currentCount = GetCount();

		if (currentCount < 0)
		{
			stringstream msg;
			msg << "In Semaphore::SetCount(), Semaphore::GetCount() unexpectedly returned a value (" << currentCount << " less than 0";
			throw runtime_error(msg.str());
		}

		if (currentCount < newCount)
			Post();
		else if (currentCount > newCount)
			if (!TryWait())
			{
				throw runtime_error("In Semaphore::SetCount(), TryWait() failed while there were additional counts to remove");
			}

	} while(currentCount != newCount);
}

void Semaphore::Post()
{
	int status;

	do
	{
		status = sem_post(GetSemPtr());
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
		DoThrow(errno, "sem_post");
}

bool Semaphore::TimedWait(unsigned millisec, bool throwOnTimeout)
{
	timespec to = GetTimeSpec(millisec);
    return TimedWait(to, throwOnTimeout);
}

bool Semaphore::TimedWait(const timespec& to, bool throwOnTimeout)
{
	int status;

	do
	{
		status = sem_timedwait(GetSemPtr(), &to);
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
	{
		if ((errno == ETIMEDOUT) && throwOnTimeout)
			throw Timeout("Semaphore: a timeout occurred");

		else if (errno != ETIMEDOUT)
			DoThrow(errno, "sem_timedwait");
	}

	return (status != -1);
}

bool Semaphore::TryWait()
{
	int status;
	bool success = false;

	do
	{
		status = sem_trywait(GetSemPtr());
	} while((status == -1) && (errno == EINTR));

	if ((status == -1) && (errno != EAGAIN))
		DoThrow(errno, "sem_trywait");
	else
		success = (status != -1);

	return success;
}

void Semaphore::Wait()
{
	int status;

	do
	{
		status = sem_wait(GetSemPtr());
	} while((status == -1) && (errno == EINTR));

	if (status == -1)
		DoThrow(errno, "sem_wait");
}

void Semaphore::DoThrow(int err, const string& api)
{
	stringstream msg;
	msg << "Semaphore: " << api << " returned error #" << err << ": '" << GetErrorDescription(err) << "'";

	throw SystemError(msg.str());
}

timespec Semaphore::GetTimeSpec(unsigned milliseconds)
{
	timespec to;
	const long NS = 1000000000L;

	if (clock_gettime(CLOCK_REALTIME, &to) == -1)
		DoThrow(errno, "clock_gettime");

	//cout << "GetTimeSpec(" << milliseconds << ") tv_sec=" << to.tv_sec << " tv_nsec=" << to.tv_nsec;

	to.tv_nsec += NS/1000*(milliseconds%1000);

	if (to.tv_nsec >= NS)
	{
		to.tv_sec += to.tv_nsec/NS;
		to.tv_nsec %= NS;
	}

	to.tv_sec += milliseconds/((time_t) 1000);
	//cout << " -> tv_sec=" << to.tv_sec << " tv_nsec=" << to.tv_nsec << endl;
	return to;
}



// Lockable concept used internally and usable by external actors
void Semaphore::lock()
{
	Wait();
}

bool Semaphore::try_lock()
{
	return TryWait();
}

void Semaphore::unlock()
{
	Post();
}

bool Semaphore::timed_lock(boost::system_time const& abs_time)
{
    using namespace boost::posix_time;
    bool result;
    time_duration dur = abs_time-from_time_t(0);

    if (dur.is_special() || dur.is_negative())
        result = try_lock();
    else
    {
        timespec to;
        to.tv_sec = dur.total_seconds();
        to.tv_nsec = (dur-seconds(to.tv_sec)).total_nanoseconds();
        result = TimedWait(to, false);
    }

    return result;
}

}
}
