/*
 *  IXSelectInterruptPipe.cpp
 *  Author: Benjamin Sergeant
 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 */

//
// On UNIX we use pipes to wake up select. There is no way to do that
// on Windows so this file is compiled out on Windows.
//
#ifndef _WIN32

#include "IXSelectInterruptPipe.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sstream>
#include <string.h> // for strerror
#include <unistd.h> // for write

namespace ix
{
	// File descriptor at index 0 in _fildes is the read end of the pipe
	// File descriptor at index 1 in _fildes is the write end of the pipe
	const int SelectInterruptPipe::kPipeReadIndex = 0;
	const int SelectInterruptPipe::kPipeWriteIndex = 1;

	SelectInterruptPipe::SelectInterruptPipe()
	{
		_fildes[kPipeReadIndex] = -1;
		_fildes[kPipeWriteIndex] = -1;
	}

	SelectInterruptPipe::~SelectInterruptPipe()
	{
		if (-1 != _fildes[kPipeReadIndex]) {
			::close(_fildes[kPipeReadIndex]);
		}
		if (-1 != _fildes[kPipeWriteIndex]) {
			::close(_fildes[kPipeWriteIndex]);
		}
		_fildes[kPipeReadIndex] = -1;
		_fildes[kPipeWriteIndex] = -1;
	}

	bool SelectInterruptPipe::init(std::string& errorMsg)
	{
		std::lock_guard<std::mutex> lock(_fildesMutex);

		// calling init twice is a programming error
		assert(_fildes[kPipeReadIndex] == -1);
		assert(_fildes[kPipeWriteIndex] == -1);

		if (pipe(_fildes) < 0)
		{
			std::stringstream ss;
			ss << "SelectInterruptPipe::init() failed in pipe() call"
			   << " : " << strerror(errno);
			errorMsg = ss.str();
			return false;
		}

		if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
		{
			std::stringstream ss;
			ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
			   << " : " << strerror(errno);
			errorMsg = ss.str();

			_fildes[kPipeReadIndex] = -1;
			_fildes[kPipeWriteIndex] = -1;
			return false;
		}

		if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
		{
			std::stringstream ss;
			ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
			   << " : " << strerror(errno);
			errorMsg = ss.str();

			_fildes[kPipeReadIndex] = -1;
			_fildes[kPipeWriteIndex] = -1;
			return false;
		}

#ifdef F_SETNOSIGPIPE
		if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
		{
			std::stringstream ss;
			ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
			   << " : " << strerror(errno);
			errorMsg = ss.str();

			_fildes[kPipeReadIndex] = -1;
			_fildes[kPipeWriteIndex] = -1;
			return false;
		}

		if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
		{
			std::stringstream ss;
			ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
			   << " : " << strerror(errno);
			errorMsg = ss.str();

			_fildes[kPipeReadIndex] = -1;
			_fildes[kPipeWriteIndex] = -1;
			return false;
		}
#endif

		return true;
	}

	bool SelectInterruptPipe::notify(uint64_t value)
	{
		std::lock_guard<std::mutex> lock(_fildesMutex);

		int fd = _fildes[kPipeWriteIndex];
		if (fd == -1) return false;

		ssize_t ret = -1;
		do
		{
			ret = ::write(fd, &value, sizeof(value));
		} while (ret == -1 && errno == EINTR);

		// we should write 8 bytes for an uint64_t
		return ret == 8;
	}

	uint64_t SelectInterruptPipe::read()
	{
		std::lock_guard<std::mutex> lock(_fildesMutex);

		int fd = _fildes[kPipeReadIndex];

		uint64_t value = 0;

		ssize_t readret = -1;
		do
		{
			readret = ::read(fd, &value, sizeof(value));
		} while (readret == -1 && errno == EINTR);

		return value;
	}

	bool SelectInterruptPipe::clear()
	{
		return true;
	}

	int SelectInterruptPipe::getFd() const
	{
		std::lock_guard<std::mutex> lock(_fildesMutex);

		return _fildes[kPipeReadIndex];
	}
}

#endif
