#include "acl_stdafx.hpp"
#ifndef ACL_PREPARE_COMPILE
#include "acl_cpp/stdlib/log.hpp"
#include "acl_cpp/stdlib/util.hpp"
#include "acl_cpp/stdlib/thread.hpp"
#include "acl_cpp/event/event_mutex.hpp"
#endif

namespace acl
{

event_mutex::event_mutex(bool recursive /* = true */)
: recursive_(recursive)
, nested_(0)
, count_(0)
, tid_(0)
{
	ACL_SOCKET fds[2];

	if (acl_sane_socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
		logger_fatal("socketpair error=%s", last_serror());
	}

	in_  = fds[0];
	out_ = fds[1];
//	acl_tcp_nodelay(in_, 1);
//	acl_tcp_nodelay(out_, 1);
}

event_mutex::~event_mutex(void)
{
	acl_socket_close(in_);
	acl_socket_close(out_);
}

#define	DUMMY	1

bool event_mutex::lock(void)
{
	if (++count_ == 1) {
		tid_ = thread::self();
		return true;
	}

	if (recursive_ && tid_ == thread::self()) {
		nested_++;
		count_--;
		return true;
	}

	int n, timeout = -1;

	while (true) {
#ifdef ACL_UNIX
		if (acl_read_poll_wait(in_, timeout) < 0) {
#else
		if (acl_read_select_wait(in_, timeout) < 0) {
#endif
			if (errno == ACL_ETIMEDOUT) {
				continue;
			}

			--count_;
			logger_error("read wait error=%s", last_serror());
			return false;
		}

		if (acl_socket_read(in_, &n, sizeof(n), 0, NULL, NULL) > 0) {
			break;
		}

		int e = last_error();
#if ACL_EWOULDBLOCK == ACL_EAGAIN
		if (e != ACL_EINTR && e != ACL_EWOULDBLOCK) {
#else
		if (e != ACL_EINTR && e != ACL_EWOULDBLOCK && e != ACL_EAGAIN) {
#endif
			logger_error("write notify error=%s", last_serror());
			return false;
		}
	}

	if (n != DUMMY) {
		logger_error("invalid read=%d(!=%d)", n, DUMMY);
		return false;
	}

	tid_ = thread::self();
	return true;
}

bool event_mutex::unlock(void)
{
	if (tid_ != thread::self()) {
		logger_error("current thread=%lu, mutex's owner=%lu",
			(unsigned long) thread::self(), tid_);
		return false;
	}

	if (nested_ > 0) {
		nested_--;
		return true;
	}

	tid_ = 0;

	if (--count_ == 0) {
		return true;
	}

	acl_assert(count_.value() > 0);

	static int n = DUMMY;
	while (true) {
		if (acl_socket_write(out_, &n, sizeof(n), 0, NULL, NULL) > 0) {
			break;
		}

		int e = last_error();
#if ACL_EWOULDBLOCK == ACL_EAGAIN
		if (e != ACL_EINTR && e != ACL_EWOULDBLOCK) {
#else
		if (e != ACL_EINTR && e != ACL_EWOULDBLOCK && e != ACL_EAGAIN) {
#endif
			logger_error("write notify error=%s", last_serror());
			return false;
		}
	}

	return true;
}

} // namespace acl
