/*
    PIP - Platform Independent Primitives
    Private PIP wait object
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "piwaitevent_p.h"
#ifdef WINDOWS
// #  ifdef _WIN32_WINNT
// #    undef _WIN32_WINNT
// #    define _WIN32_WINNT 0x0600
// #  endif
#  include <synchapi.h>
#else
#  include <errno.h>
#  include <fcntl.h>
#  include <sys/ioctl.h>
#endif
#include "pistring.h"


PIWaitEvent::~PIWaitEvent() {
	destroy();
}


void PIWaitEvent::create() {
	destroy();
#ifdef WINDOWS
	event = CreateEventA(NULL, TRUE, FALSE, NULL);
	if (!event) {
		piCout << "Error with CreateEventA:" << errorString();
	}
#else
	for (int i = 0; i < 3; ++i)
		piZeroMemory(fds[i]);
	if (::pipe(pipe_fd) < 0) {
		piCout << "Error with pipe:" << errorString();
	} else {
		fcntl(pipe_fd[ReadEnd], F_SETFL, O_NONBLOCK);
		fcntl(pipe_fd[WriteEnd], F_SETFL, O_NONBLOCK);
	}
#endif
}


void PIWaitEvent::destroy() {
#ifdef WINDOWS
	if (event) {
		CloseHandle(event);
		event = NULL;
	}
#else
	for (int i = 0; i < 2; ++i) {
		if (pipe_fd[i] != -1) {
			::close(pipe_fd[i]);
			pipe_fd[i] = -1;
		}
	}
#endif
}


bool PIWaitEvent::wait(int fd, CheckRole role) {
	if (!isCreate()) return false;
#ifdef WINDOWS
	DWORD ret = WaitForSingleObjectEx(event, INFINITE, TRUE);
	ResetEvent(event);
	if (ret == WAIT_IO_COMPLETION || ret == WAIT_FAILED) return false;
#else
	if (fd == -1) return false;
	int nfds     = piMaxi(pipe_fd[ReadEnd], fd) + 1;
	int fd_index = role;
	for (int i = 0; i < 3; ++i)
		FD_ZERO(&(fds[i]));
	FD_SET(pipe_fd[ReadEnd], &(fds[CheckRead]));
	FD_SET(fd, &(fds[CheckExeption]));
	if (fd_index != CheckExeption) FD_SET(fd, &(fds[fd_index]));
	int sr = ::select(nfds, &(fds[CheckRead]), &(fds[CheckWrite]), &(fds[CheckExeption]), nullptr);
	if (sr < 0) return false;
	errorClear();
	int buf = 0;
	while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0) {}
	// piCout << "wait result" << sr << FD_ISSET(fd, &(fds[CheckExeption])) << FD_ISSET(fd, &(fds[fd_index]));
	if (errno == EBADF || errno == EINTR) return false;
	if (FD_ISSET(fd, &(fds[CheckExeption]))) return true;
	return FD_ISSET(fd, &(fds[fd_index]));
#endif
	return true;
}


bool PIWaitEvent::sleep(int us) {
	if (!isCreate()) return false;
#ifdef WINDOWS
	DWORD ret = WaitForSingleObjectEx(event, us / 1000, TRUE);
	ResetEvent(event);
	return ret == WAIT_TIMEOUT;
#else
	int nfds = pipe_fd[ReadEnd] + 1;
	FD_ZERO(&(fds[CheckRead]));
	FD_SET(pipe_fd[ReadEnd], &(fds[CheckRead]));
	timeval timeout;
	timeout.tv_sec  = us / 1000000;
	timeout.tv_usec = us % 1000000;
	int ret         = ::select(nfds, &(fds[CheckRead]), nullptr, nullptr, &timeout);
	int buf         = 0;
	while (::read(pipe_fd[ReadEnd], &buf, sizeof(buf)) > 0)
		;
	return ret == 0;
#endif
}


void PIWaitEvent::interrupt() {
	if (!isCreate()) return;
#ifdef WINDOWS
	SetEvent(event);
#else
	auto _r = ::write(pipe_fd[WriteEnd], "", 1);
	NO_UNUSED(_r);
#endif
}


bool PIWaitEvent::isCreate() const {
#ifdef WINDOWS
	return event;
#else
	return pipe_fd[ReadEnd] != -1;
#endif
}


void * PIWaitEvent::getEvent() const {
#ifdef WINDOWS
	return event;
#else
	return nullptr;
#endif
}
