#include "config.h"

#include <alloca.h>
#include <unistd.h>

#include <sys/wait.h>

#import "OFProcess.h"
#import "OFString.h"
#import "OFArray.h"

#import "OFInitializationFailedException.h"
#import "OFReadFailedException.h"
#import "OFWriteFailedException.h"

@implementation OFProcess
+ processWithProgram: (OFString*)program
	 programName: (OFString*)programName
	   arguments: (OFArray*)arguments
{
	return [[[self alloc] initWithProgram: program
				  programName: programName
				    arguments: arguments] autorelease];
}

- initWithProgram: (OFString*)program
      programName: (OFString*)programName
	arguments: (OFArray*)arguments
{
	self = [super init];

	@try {
		if (pipe(readPipe) != 0 || pipe(writePipe) != 0)
			@throw [OFInitializationFailedException
			    exceptionWithClass: isa];

		switch ((pid = fork())) {
		case 0:;
			OFString **cArray = [arguments cArray];
			size_t i, count = [arguments count];
			char **argv = alloca((count + 2) * sizeof(char*));

			argv[0] = (char*)[programName cStringWithEncoding:
			    OF_STRING_ENCODING_NATIVE];

			for (i = 0; i < count; i++)
				argv[i + 1] = (char*)[cArray[i]
				    cStringWithEncoding:
				    OF_STRING_ENCODING_NATIVE];

			argv[i + 1] = NULL;

			close(readPipe[0]);
			close(writePipe[1]);
			dup2(writePipe[0], 0);
			dup2(readPipe[1], 1);
			execvp([program cStringWithEncoding:
			    OF_STRING_ENCODING_NATIVE], argv);

			@throw [OFInitializationFailedException
			    exceptionWithClass: isa];
		case -1:
			@throw [OFInitializationFailedException
			    exceptionWithClass: isa];
		default:
			close(readPipe[1]);
			close(writePipe[0]);
			break;
		}
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (BOOL)_isAtEndOfStream
{
	if (readPipe[0] == -1)
		return YES;

	return atEndOfStream;
}

- (size_t)_readNBytes: (size_t)length
	   intoBuffer: (void*)buffer
{
	ssize_t ret;

	if (readPipe[0] == -1 || atEndOfStream ||
	    (ret = read(readPipe[0], buffer, length)) < 0)
		@throw [OFReadFailedException exceptionWithClass: isa
							  stream: self
						 requestedLength: length];

	if (ret == 0)
		atEndOfStream = YES;

	return ret;
}

- (void)_writeNBytes: (size_t)length
	  fromBuffer: (const void*)buffer
{
	if (writePipe[1] == -1 || atEndOfStream ||
	    write(writePipe[1], buffer, length) < length)
		@throw [OFWriteFailedException exceptionWithClass: isa
							   stream: self
						  requestedLength: length];
}

- (void)dealloc
{
	[self close];

	[super dealloc];
}

/*
 * FIXME: Add -[fileDescriptor]. The problem is that we have two FDs, which is
 *	  not yet supported by OFStreamObserver. This has to be split into one
 *	  FD for reading and one for writing.
 */

- (void)close
{
	if (readPipe[0] != -1)
		close(readPipe[0]);
	if (writePipe[1] != -1)
		close(writePipe[1]);

	if (pid != -1)
		waitpid(pid, &status, WNOHANG);

	pid = -1;
	readPipe[0] = -1;
	writePipe[1] = -1;
}
@end
