/*
	pipe_source.m
	
	clang pipe_source.m -o pipe -framework Foundation -D [ USE_SOURCE | USE_IO | USE_FILEHANDLE [ FILEHANDLE_READABILITY | FILEHANDLE_WAIT ] ]
 */

#import <Foundation/Foundation.h>

int main(void) {
	enum RunLoop {
		Dispatch,
		RunLoop,
	} runLoop = Dispatch;

	int filedes[2];
	int pipeError = pipe(filedes);
	if (pipeError != 0) {
		fprintf(stderr, "couldn't create pipe, error: %d\n", errno);
		return 1;
	}

	int readFd = filedes[0], writeFd = filedes[1];

	///
	// Read Strategy

#if defined(USE_SOURCE)
	dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, readFd, 0, dispatch_get_main_queue());
	dispatch_source_set_cancel_handler(readSource, ^{
		fprintf(stderr, "closing read fd\n");
		close(readFd);
	});
	dispatch_source_set_event_handler(readSource, ^{
		uint8_t buffer[1024];
		int readCount = read(readFd, buffer, sizeof(buffer));
		fprintf(stderr, "read %d bytes\n", readCount);

		bool done = (readCount == 0);
		if (done) {
			fprintf(stderr, "EOF encountered\n");
			dispatch_source_cancel(readSource);
			exit(0);
		}
	});
	dispatch_resume(readSource);
#elif defined(USE_IO)
	dispatch_io_t readChannel = dispatch_io_create(DISPATCH_IO_STREAM, readFd, dispatch_get_main_queue(), ^(int error) {
		close(readFd);
	});
	dispatch_io_set_interval(readChannel, NSEC_PER_SEC / 2, DISPATCH_IO_STRICT_INTERVAL);
	__block int readCount = 0;
	dispatch_io_read(readChannel, 0, SIZE_MAX, dispatch_get_main_queue(), ^(bool done, dispatch_data_t data, int error) {
		fprintf(stderr, "readCount: %d done: %d dataSize: %ld error: %d\n", ++readCount, done, dispatch_data_get_size(data), error);

		if (done) {
			fprintf(stderr, "EOF encountered\n");
			dispatch_io_close(readChannel, 0);
			exit(0);
		}
	});
#elif defined(USE_FILEHANDLE)
	NSFileHandle *readHandle = [[NSFileHandle alloc] initWithFileDescriptor:readFd closeOnDealloc:YES];

#if defined(FILEHANDLE_READABILITY)
	// EOF is not encountered
	readHandle.readabilityHandler = ^(NSFileHandle *readHandle) {
		int readCount = readHandle.availableData.length;
		fprintf(stderr, "read %d bytes\n", readCount);

		bool done = (readCount == 0);
		if (done) {
			fprintf(stderr, "EOF encountered\n");
			exit(0);
		}
	};
#elif defined(FILEHANDLE_WAIT)
	// EOF is encountered
	id readObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSFileHandleDataAvailableNotification object:readHandle queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *notification) {
		int readCount = readHandle.availableData.length;
		fprintf(stderr, "read %d bytes\n", readCount);

		bool done = (readCount == 0);
		if (done) {
			fprintf(stderr, "EOF encountered\n");
			exit(0);
		}

		[readHandle waitForDataInBackgroundAndNotify];
	}];
	[readHandle waitForDataInBackgroundAndNotify];
#else
#error no file handle strategy defined
#endif

	runLoop = RunLoop;
#else
#error no read strategy defined
#endif

	///
	// Write

	char *const output = "Hello, world.";

	write(writeFd, output, 6);

	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		write(writeFd, output + 6, 7);
		close(writeFd);
	});

	///
	// Event Loop

	switch (runLoop) {
		case Dispatch:
			dispatch_main();
		case RunLoop:
			[NSRunLoop.currentRunLoop run];
	}
}