/* 
   NSRunLoop.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <config.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <errno.h>

#if HAVE_SYS_TIME_H
# include <sys/time.h>	/* for struct timeval */
#endif

#if HAVE_STRING_H
# include <string.h>
#endif

#if HAVE_MEMORY_H
# include <memory.h>
#endif

#if !HAVE_MEMCPY
# define memcpy(d, s, n)       bcopy((s), (d), (n))
# define memmove(d, s, n)      bcopy((s), (d), (n))
#endif

#if HAVE_LIBC_H
# include <libc.h>
#else
# include <unistd.h>
#endif

#include <Foundation/NSRunLoop.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSTimer.h>
#include <Foundation/NSUtilities.h>

NSString* NSDefaultRunLoopMode = @"NSDefaultRunLoopMode";
NSString* NSConnectionReplyMode = @"NSConnectionReplyMode";
static NSString* allModes = @"NSRunLoopAllModes";

@interface FileDescriptorInfo : NSObject
{
    int fd;
    id handler;
    id userInfo;
}
- initForFD:(int)fd handler:(id)theHandler andUserInfo:(id)userInfo;
- handler;
- userInfo;
@end

@implementation FileDescriptorInfo
- initForFD:(int)_fd handler:(id)theHandler andUserInfo:(id)_userInfo
{
    fd = _fd;
    handler = theHandler;
    userInfo = _userInfo;
    return self;
}

- (int)fd	{ return fd; }
- handler	{ return handler; }
- userInfo	{ return userInfo; }
@end


@interface InputManager : NSObject
{
    NSMutableArray*	connectionsArray;
    NSMutableDictionary* fileDescriptors;
    NSMutableArray*	timers;
}
- (void)addConnection:(NSConnection*)connection;
- (void)removeConnection:(NSConnection*)connection;

- (void)addFileDescriptor:(int)fd
    handler:handler
    userInfo:(id)anArgument;
- (void)removeFileDescriptor:(int)fd;

- (void)addTimer:(NSTimer*)aTimer;

- (NSMutableArray*)connectionsArray;
- (NSMutableDictionary*)fileDescriptors;
- (NSMutableArray*)timers;
@end

@implementation InputManager

- init
{
    connectionsArray = [NSMutableArray new];
    fileDescriptors = [NSMutableDictionary new];
    timers = [NSMutableArray new];
    return [super init];
}

- (void)dealloc
{
    [connectionsArray release];
    [fileDescriptors release];
    [timers release];
    [super dealloc];
}

- (void)addConnection:(NSConnection*)connection
{
    [connectionsArray addObject:connection];
}

- (void)removeConnection:(NSConnection*)connection
{
    [connectionsArray removeObject:connection];
}

- (void)addFileDescriptor:(int)fd
    handler:handler
    userInfo:(id)anArgument
{
    id fdInfo = [[FileDescriptorInfo alloc]
		    initForFD:fd handler:handler andUserInfo:anArgument];
    [fileDescriptors setObject:fdInfo forKey:[NSNumber numberWithInt:fd]];
    [fdInfo release];
}

- (void)removeFileDescriptor:(int)fd
{
    [fileDescriptors removeObjectForKey:[NSNumber numberWithInt:fd]];
}

- (void)addTimer:(NSTimer*)aTimer
{
    if(![aTimer fireDate])
	[timers removeObject:aTimer];
    else
	[timers addObject:aTimer];
}

- (NSMutableArray*)connectionsArray	{ return connectionsArray; }
- (NSMutableDictionary*)fileDescriptors	{ return fileDescriptors; }
- (NSMutableArray*)timers		{ return timers; }

@end /* InputManager */


static Class concreteEventLoopClass = Nil;
static NSMutableArray* runLoopsStack = nil;


@implementation NSRunLoop

+ (void)initialize
{
    concreteEventLoopClass = [NSRunLoop class];
    runLoopsStack = [NSMutableArray new];
}

+ (NSRunLoop*)currentRunLoop
{
    if(![runLoopsStack count]) {
	id runLoop = [concreteEventLoopClass new];
	[runLoopsStack addObject:runLoop];
	[runLoop release];
	return runLoop;
    }

    return [runLoopsStack lastObject];
}

+ (id)allocWithZone:(NSZone*)zone
{
    return NSAllocateObject(concreteEventLoopClass, 0, zone);
}

- init
{
    inputsForMode = [NSMutableDictionary new];
    mode = [NSDefaultRunLoopMode retain];
    return [super init];
}

- (void)dealloc
{
    [inputsForMode release];
    [mode release];
    [super dealloc];
}

- (NSString*)currentMode
{
    return mode;
}

static int compare_fire_dates(id timer1, id timer2, void* userData)
{
    return [[timer1 fireDate] compare:[timer2 fireDate]];
}

- (NSDate*)limitDateForMode:(NSString*)aMode
{
    id timers = [[inputsForMode objectForKey:aMode] timers];
    id currentDate = [NSDate date];
    int i, count = [timers count];

    /* Remove invalid timers */
    for(i = 0; i < count;) {
	if(![[timers objectAtIndex:i] isValid]) {
	    [timers removeObjectAtIndex:i];
	    count--;
	}
	else i++;
    }

    if(!count)
	return nil;

    [timers sortUsingFunction:compare_fire_dates context:NULL];

    for(i = 0; i < count;) {
	id timer = [timers objectAtIndex:0];
	id fireDate = [timer fireDate];
	if([fireDate earlierDate:currentDate] == fireDate) {
	    [timer fire];
	    if(![timer repeats]) {
		[timer invalidate];
		[timers removeObjectAtIndex:0];
		count--;
	    }
	    else i++;
	}
	else break;
    }

    [timers sortUsingFunction:compare_fire_dates context:NULL];

    return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
}

- (void)addTimer:(NSTimer*)aTimer
	forMode:(NSString*)aMode
{
    id manager = [inputsForMode objectForKey:aMode];

    if(!manager)
	[inputsForMode setObject:(manager = [InputManager new])
			  forKey:aMode];
    return [manager addTimer:aTimer];
}

/*  Runs the loop until limitDate or until the earliest limit date for input
    sources in the specified mode. */
- (BOOL)runMode:(NSString*)aMode
	beforeDate:(NSDate*)limitDate
{
    id firstDate;
    id inputManager = [inputsForMode objectForKey:aMode];
    id fileDescriptors = [inputManager fileDescriptors];
    struct timeval tp = { 0, 0 };
    struct timeval* timeout = NULL;
    NSTimeInterval delay = 0;

    if(!limitDate)
	limitDate = [NSDate distantFuture];
    else {
	delay = [limitDate timeIntervalSinceNow];
	    /* delay > 0 means a future date */

	/* If limitDate is in the past return */
	if(delay < 0)
	    return NO;
    }

    /* Find the date of the first timer */
    firstDate = [self limitDateForMode:aMode];

    /* If there are no timers and no file descriptors to listen to, return */
    if(!firstDate && [fileDescriptors count] == 0)
	return NO;

    /* If the minimum fire date is before limitDate run until fire date */
    if([firstDate earlierDate:limitDate] == firstDate)
	limitDate = firstDate;

    /* Compute the timeout for select */
    if([limitDate isEqual:[NSDate distantFuture]])
	timeout = NULL;
    else {
	tp.tv_sec = delay;
	tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
	timeout = &tp;
    }

    mode = aMode;

    while ([limitDate laterDate:[NSDate date]] == limitDate) {
	fd_set set;
	id pool = [NSAutoreleasePool new];
	int i, r, count = [fileDescriptors count];
	id fileDescriptorsCopy;

	FD_ZERO(&set);
	for (i = 0; i < count; i++)
	    FD_SET([[fileDescriptors objectAtIndex:i] fd], &set);

	r = select(FD_SETSIZE, &set, NULL, NULL, timeout);

	/* Fire the timers if any */
	[self limitDateForMode:aMode];

	if(r == -1 && errno == EINTR)
	    continue;

	if(r == 0)
	    break;

	fileDescriptorsCopy = [fileDescriptors copy];
	for (i = 0; r && i < count; i++) {
	    id fdInfo = [fileDescriptorsCopy objectAtIndex:i];

	    if (FD_ISSET([fdInfo fd], &set)) {
		[[fdInfo handler] dataOnFileDescriptor:[fdInfo fd]
				    userInfo:[fdInfo userInfo]];
		r--;
	    }
	}
	[fileDescriptorsCopy release];
	[pool release];
    }

    mode = nil;
    return YES;
}

- (void)acceptInputForMode:(NSString*)aMode
	beforeDate:(NSDate*)limitDate
{
    if(!limitDate)
	limitDate = [NSDate distantFuture];
    else {
	/* If limitDate is in the past return */
	if([limitDate timeIntervalSinceNow] < 0)
	    return;
    }

    while ([limitDate laterDate:[NSDate date]] == limitDate)
	if([self runMode:aMode beforeDate:limitDate] == NO)
	    return;
}

- (void)runUntilDate:(NSDate*)limitDate
{
    id enumerator = [inputsForMode keyEnumerator];
    id inputManagerForAllModes = [InputManager new];
    NSMutableDictionary* allFileDescriptors =
	[inputManagerForAllModes fileDescriptors];
    id allTimers = [inputManagerForAllModes timers];
    id key;

    /*  Set the two arrays with all the file descriptors and timers
	from all the modes */
    while((key = [enumerator nextObject])) {
	id inputManager = [inputsForMode objectForKey:key];
	[allFileDescriptors addEntriesFromDictionary:
				[inputManager fileDescriptors]];
	[allTimers addObjectsFromArray:[inputManager timers]];
    }

    [inputsForMode setObject:inputManagerForAllModes forKey:allModes];

    [self acceptInputForMode:allModes beforeDate:limitDate];

    [inputsForMode removeObjectForKey:allModes];
    [inputManagerForAllModes release];
}

- (void)run
{
    [self runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

@end /* NSRunLoop */


@implementation NSRunLoop (Extensions)
+ (void)setConcreteEventLoopClass:(Class)aClass
{
    concreteEventLoopClass = aClass;
}

- (void)addConnection:(NSConnection*)connection
{
}

- (void)removeConnection:(NSConnection*)connection
{
}

- (void)addFileDescriptor:(int)fd
    handler:handler
    userInfo:(id)anArgument
    forMode:(NSString*)aMode
{
    id manager = [inputsForMode objectForKey:aMode];

    if(!manager)
	[inputsForMode setObject:(manager = [InputManager new])
			  forKey:aMode];
    return [manager addFileDescriptor:fd handler:handler userInfo:anArgument];
}

- (void)removeFileDescriptor:(int)fd
    forMode:(NSString*)aMode
{
    [[inputsForMode objectForKey:aMode] removeFileDescriptor:fd];
}

+ (Class)concreteEventLoopClass		{ return concreteEventLoopClass; }
+ (void)pushRunLoop:(NSRunLoop*)runLoop	{ [runLoopsStack addObject:runLoop]; }
+ (void)popRunLoop			{ [runLoopsStack removeLastObject]; }
+ (NSArray*)runLoopsStack		{ return runLoopsStack; }

// Private methods
- (void)dispatchEventOnConnection:(NSConnection*)connection
{
}

@end /* NSRunLoop (Extensions) */

