/*
 * Copyright (c) 2008 - 2010
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE included in
 * the packaging of this file.
 */

#include "config.h"

#include <string.h>

#ifdef OF_HAVE_POLL
# include <poll.h>
#endif

#import "OFSocketObserver.h"
#import "OFDataArray.h"
#import "OFDictionary.h"
#import "OFSocket.h"
#import "OFTCPSocket.h"
#import "OFNumber.h"
#import "OFAutoreleasePool.h"
#import "OFExceptions.h"

@implementation OFSocketObserver
+ socketObserver
{
	return [[[self alloc] init] autorelease];
}

- init
{
	self = [super init];

#ifdef OF_HAVE_POLL
	fds = [[OFDataArray alloc] initWithItemSize: sizeof(struct pollfd)];
#else
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
#endif
	fdToSocket = [[OFMutableDictionary alloc] init];

	return self;
}

- (void)dealloc
{
	[delegate release];
#ifdef OF_HAVE_POLL
	[fds release];
#endif
	[fdToSocket release];

	[super dealloc];
}

- (OFObject <OFSocketObserverDelegate>*)delegate
{
	return [[delegate retain] autorelease];
}

- (void)setDelegate: (OFObject <OFSocketObserverDelegate>*)delegate_
{
	[delegate_ retain];
	[delegate release];
	delegate = delegate_;
}

#ifdef OF_HAVE_POLL
- (void)_addSocket: (OFSocket*)sock
	withEvents: (short)events
{
	struct pollfd *fds_c = [fds cArray];
	size_t i, count = [fds count];
	BOOL found = NO;

	for (i = 0; i < count; i++) {
		if (fds_c[i].fd == sock->sock) {
			fds_c[i].events |= events;
			found = YES;
		}
	}

	if (!found) {
		OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
		struct pollfd p = { sock->sock, events, 0 };
		[fds addItem: &p];
		[fdToSocket setObject: sock
			       forKey: [OFNumber numberWithInt: sock->sock]];
		[pool release];
	}
}

- (void)_removeSocket: (OFSocket*)sock
	   withEvents: (short)events
{
	struct pollfd *fds_c = [fds cArray];
	size_t i, nfds = [fds count];

	for (i = 0; i < nfds; i++) {
		if (fds_c[i].fd == sock->sock) {
			OFAutoreleasePool *pool;

			fds_c[i].events &= ~events;

			if (fds_c[i].events != 0)
				return;

			pool = [[OFAutoreleasePool alloc] init];

			[fds removeItemAtIndex: i];
			[fdToSocket removeObjectForKey:
			    [OFNumber numberWithInt: sock->sock]];

			[pool release];
		}
	}
}
#else
- (void)_addSocket: (OFSocket*)sock
	 withFDSet: (fd_set*)fdset
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];

	if (sock->sock >= FD_SETSIZE)
		@throw [OFOutOfRangeException newWithClass: isa];

	FD_SET(sock->sock, fdset);

	if (sock->sock >= nfds)
		nfds = sock->sock + 1;

	[fdToSocket setObject: sock
		       forKey: [OFNumber numberWithInt: sock->sock]];

	[pool release];
}

- (void)_removeSocket: (OFSocket*)sock
	    withFDSet: (fd_set*)fdset
{
	if (sock->sock >= FD_SETSIZE)
		@throw [OFOutOfRangeException newWithClass: isa];

	FD_CLR(sock->sock, fdset);

	if (!FD_ISSET(sock->sock, &readfds) &&
	    !FD_ISSET(sock->sock, &writefds)) {
		OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];

		[fdToSocket removeObjectForKey:
		    [OFNumber numberWithInt: sock->sock]];

		[pool release];
	}
}
#endif

- (void)addSocketToObserveForIncomingConnections: (OFTCPSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _addSocket: sock
	      withEvents: POLLIN];
#else
	[self _addSocket: sock
	       withFDSet: &readfds];
#endif
}

- (void)addSocketToObserveForReading: (OFSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _addSocket: sock
	      withEvents: POLLIN];
#else
	[self _addSocket: sock
	       withFDSet: &readfds];
#endif
}

- (void)addSocketToObserveForWriting: (OFSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _addSocket: sock
	      withEvents: POLLOUT];
#else
	[self _addSocket: sock
	       withFDSet: &writefds];
#endif
}

- (void)removeSocketToObserveForIncomingConnections: (OFTCPSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _removeSocket: sock
		 withEvents: POLLIN];
#else
	[self _removeSocket: sock
		  withFDSet: &readfds];
#endif
}

- (void)removeSocketToObserveForReading: (OFSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _removeSocket: sock
		 withEvents: POLLIN];
#else
	[self _removeSocket: sock
		  withFDSet: &readfds];
#endif
}

- (void)removeSocketToObserveForWriting: (OFSocket*)sock
{
#ifdef OF_HAVE_POLL
	[self _removeSocket: sock
		 withEvents: POLLOUT];
#else
	[self _removeSocket: sock
		  withFDSet: &writefds];
#endif
}

- (void)observe
{
	[self observeWithTimeout: -1];
}

- (BOOL)observeWithTimeout: (int)timeout
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
#ifdef OF_HAVE_POLL
	struct pollfd *fds_c = [fds cArray];
	size_t i, nfds = [fds count];

	if (poll(fds_c, nfds, timeout) < 1)
		return NO;

	for (i = 0; i < nfds; i++) {
		OFNumber *num;
		OFSocket *sock;

		if (fds_c[i].revents & POLLIN) {
			num = [OFNumber numberWithInt: fds_c[i].fd];
			sock = [fdToSocket objectForKey: num];

			if (sock->listening)
				[delegate socketDidReceiveIncomingConnection:
				    (OFTCPSocket*)sock];
			else
				[delegate socketDidBecomeReadyForReading: sock];
		}

		if (fds_c[i].revents & POLLOUT) {
			num = [OFNumber numberWithInt: fds_c[i].fd];
			sock = [fdToSocket objectForKey: num];
			[delegate socketDidBecomeReadyForReading: sock];
		}

		fds_c[i].revents = 0;
	}
#else
	fd_set readfds_;
	fd_set writefds_;
	fd_set exceptfds_;
	struct timeval tv;
	OFEnumerator *enumerator;
	OFSocket *sock;

	readfds_ = readfds;
	writefds_ = writefds;
	FD_ZERO(&exceptfds_);

	if (select(nfds, &readfds_, &writefds_, &exceptfds_,
	    (timeout != -1 ? &tv : NULL)) < 1)
		return NO;

	enumerator = [[[fdToSocket copy] autorelease] objectEnumerator];

	while ((sock = [enumerator nextObject]) != nil) {
		if (FD_ISSET(sock->sock, &readfds_)) {
			if (sock->listening)
				[delegate socketDidReceiveIncomingConnection:
				    (OFTCPSocket*)sock];
			else
				[delegate socketDidBecomeReadyForReading: sock];
		}

		if (FD_ISSET(sock->sock, &writefds_))
			[delegate socketDidBecomeReadyForWriting: sock];
	}
#endif
	[pool release];

	return YES;
}
@end

@implementation OFObject (OFSocketObserverDelegate)
- (void)socketDidReceiveIncomingConnection: (OFTCPSocket*)sock
{
}

- (void)socketDidBecomeReadyForReading: (OFSocket*)sock
{
}

- (void)socketDidBecomeReadyForWriting: (OFSocket*)sock
{
}
@end
