//---------------------------------------------------------------------------------------------
// <copyright file="LqrSMACPipes.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 Pipes class, which creates a set of pipes and handles closing of the pipes on
// destruction.
// </summary>
//
// <owner>Mike Cookson</owner>
//---------------------------------------------------------------------------------------------

#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACPipes.h"
#include <sstream>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace SMACFramework {

IMPLEMENT_INNER_EXCEPTION(Pipes, Interrupted, std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(Pipes, PipeIsClosed, std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(Pipes, SystemError, std::runtime_error);

Pipes::Pipes(bool create)
{
	Init();

	if (create)
		DefaultPipes();
}

Pipes::Pipes(const Pipes& other)
{
	Init();
	Copy(0, other);
	Copy(1, other);
}

Pipes::~Pipes()
{
	Close();
}

Pipes& Pipes::operator=(const Pipes& other)
{
	Lock lock(*this);

	if (this != &other)
	{
		Close();
		Copy(0, other);
		Copy(1, other);
	}

	return *this;
}

bool Pipes::ReadPipeOpen() const
{
	Lock lock(const_cast<Pipes&>(*this));
	return IsOpen(0);
}

bool Pipes::WritePipeOpen() const
{
	Lock lock(const_cast<Pipes&>(*this));
	return IsOpen(1);
}

void Pipes::Init()
{
	fds[0] = fds[1] = -1;
}

bool Pipes::IsOpen(int idx) const
{
	return fds[idx] != -1;
}

void Pipes::Close()
{
	Lock lock(*this);
	CloseIfOpen(fds[0]);
	CloseIfOpen(fds[1]);
}

int Pipes::DupFd(int fd)
{
	int newFd = dup(fd);
	int errSave = errno;

	if ((newFd == -1) && (errSave == EINTR))
		throw Interrupted("Pipes dup() was interrupted");
	else if (newFd == -1)
	{
		stringstream msg;
		msg << "Pipes dup(" << fd << ") returned error#" << errSave << " '" << GetErrorDescription(errSave) << "'";

		throw SystemError(msg.str());
	}

	return newFd;
}

void Pipes::CloseReadPipe()
{
	Lock lock(*this);
	CloseIfOpen(fds[0]);
}

void Pipes::CloseWritePipe()
{
	Lock lock(*this);
	CloseIfOpen(fds[1]);
}

void Pipes::Copy(int idx, const Pipes& other)
{
	if (other.fds[idx] == -1)
		fds[idx] = -1;
	else
		fds[idx] = DupFd(other.fds[idx]);
}

void Pipes::DefaultPipes()
{
	int status;
	int errSave;

	do
	{
		status = pipe(fds);
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	if (status == -1)
	{
		stringstream msg;
		msg << "Pipes pipe() returned error#" << errSave << " '" << GetErrorDescription(errSave) << "'";
		throw SystemError(msg.str());
	}
}

int Pipes::ReadPipe() const
{
	Lock lock(const_cast<Pipes&>(*this));

	if (!ReadPipeOpen())
		throw PipeIsClosed("The read pipe is closed");

	return fds[0];
}

int Pipes::WritePipe() const
{
	Lock lock(const_cast<Pipes&>(*this));

	if (!WritePipeOpen())
		throw PipeIsClosed("The write pipe is closed");

	return fds[1];
}

void Pipes::Create()
{
	Lock lock(*this);

	Close();
	DefaultPipes();
}

void Pipes::CloseIfOpen(int& fd)
{
	if (fd != -1)
	{
		close(fd);
		fd = -1;
	}
}

void Pipes::MakeCloseOnExec(int fd, bool set)
{
	int flags = CheckedFcntl(fd, F_GETFD);
	int newFlags = flags;

	if (set)
		newFlags |= FD_CLOEXEC;
	else
		newFlags &= ~FD_CLOEXEC;

	if (flags != newFlags)
		CheckedFcntl(fd, F_SETFD, newFlags);
}

void Pipes::MakeNonBlocking(int fd, bool set)
{
	int flags = CheckedFcntl(fd, F_GETFL);
	int newFlags = flags;

	if (set)
		newFlags |= O_NONBLOCK;
	else
		newFlags &= ~O_NONBLOCK;

	if (newFlags != flags)
		CheckedFcntl(fd, F_SETFL, newFlags);
}

int Pipes::CheckedFcntl(int fd, int cmd)
{
	int status = fcntl(fd, cmd, 0);
	int errSave = errno;

	if (status == -1)
	{
		stringstream msg;
		msg << "Pipes fcntl(" << fd << "," << cmd << ") ";

		if (errSave == EINTR)
		{
			msg << "was interrupted";
			throw Interrupted(msg.str());
		}
		else
		{
			msg << "failed with error#" << errSave << " '" << GetErrorDescription(errSave) << "'";
			throw SystemError(msg.str());
		}
	}

	return status;
}

int Pipes::CheckedFcntl(int fd, int cmd, long arg)
{
	int status = fcntl(fd, cmd, arg);
	int errSave = errno;

	if (status == -1)
	{
		stringstream msg;
		msg << "Pipes fcntl(" << fd << "," << cmd << "," << arg << ") ";

		if (errSave == EINTR)
		{
			msg << "was interrupted";
			throw Interrupted(msg.str());
		}
		else
		{
			msg << "failed with error#" << errSave << " '" << GetErrorDescription(errSave) << "'";
			throw SystemError(msg.str());
		}
	}

	return status;
}

}
}
